code-path-state.js 44 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441
  1. /**
  2. * @fileoverview A class to manage state of generating a code path.
  3. * @author Toru Nagashima
  4. */
  5. "use strict";
  6. //------------------------------------------------------------------------------
  7. // Requirements
  8. //------------------------------------------------------------------------------
  9. const CodePathSegment = require("./code-path-segment"),
  10. ForkContext = require("./fork-context");
  11. //------------------------------------------------------------------------------
  12. // Helpers
  13. //------------------------------------------------------------------------------
  14. /**
  15. * Adds given segments into the `dest` array.
  16. * If the `others` array does not includes the given segments, adds to the `all`
  17. * array as well.
  18. *
  19. * This adds only reachable and used segments.
  20. *
  21. * @param {CodePathSegment[]} dest - A destination array (`returnedSegments` or `thrownSegments`).
  22. * @param {CodePathSegment[]} others - Another destination array (`returnedSegments` or `thrownSegments`).
  23. * @param {CodePathSegment[]} all - The unified destination array (`finalSegments`).
  24. * @param {CodePathSegment[]} segments - Segments to add.
  25. * @returns {void}
  26. */
  27. function addToReturnedOrThrown(dest, others, all, segments) {
  28. for (let i = 0; i < segments.length; ++i) {
  29. const segment = segments[i];
  30. dest.push(segment);
  31. if (others.indexOf(segment) === -1) {
  32. all.push(segment);
  33. }
  34. }
  35. }
  36. /**
  37. * Gets a loop-context for a `continue` statement.
  38. *
  39. * @param {CodePathState} state - A state to get.
  40. * @param {string} label - The label of a `continue` statement.
  41. * @returns {LoopContext} A loop-context for a `continue` statement.
  42. */
  43. function getContinueContext(state, label) {
  44. if (!label) {
  45. return state.loopContext;
  46. }
  47. let context = state.loopContext;
  48. while (context) {
  49. if (context.label === label) {
  50. return context;
  51. }
  52. context = context.upper;
  53. }
  54. /* istanbul ignore next: foolproof (syntax error) */
  55. return null;
  56. }
  57. /**
  58. * Gets a context for a `break` statement.
  59. *
  60. * @param {CodePathState} state - A state to get.
  61. * @param {string} label - The label of a `break` statement.
  62. * @returns {LoopContext|SwitchContext} A context for a `break` statement.
  63. */
  64. function getBreakContext(state, label) {
  65. let context = state.breakContext;
  66. while (context) {
  67. if (label ? context.label === label : context.breakable) {
  68. return context;
  69. }
  70. context = context.upper;
  71. }
  72. /* istanbul ignore next: foolproof (syntax error) */
  73. return null;
  74. }
  75. /**
  76. * Gets a context for a `return` statement.
  77. *
  78. * @param {CodePathState} state - A state to get.
  79. * @returns {TryContext|CodePathState} A context for a `return` statement.
  80. */
  81. function getReturnContext(state) {
  82. let context = state.tryContext;
  83. while (context) {
  84. if (context.hasFinalizer && context.position !== "finally") {
  85. return context;
  86. }
  87. context = context.upper;
  88. }
  89. return state;
  90. }
  91. /**
  92. * Gets a context for a `throw` statement.
  93. *
  94. * @param {CodePathState} state - A state to get.
  95. * @returns {TryContext|CodePathState} A context for a `throw` statement.
  96. */
  97. function getThrowContext(state) {
  98. let context = state.tryContext;
  99. while (context) {
  100. if (context.position === "try" ||
  101. (context.hasFinalizer && context.position === "catch")
  102. ) {
  103. return context;
  104. }
  105. context = context.upper;
  106. }
  107. return state;
  108. }
  109. /**
  110. * Removes a given element from a given array.
  111. *
  112. * @param {any[]} xs - An array to remove the specific element.
  113. * @param {any} x - An element to be removed.
  114. * @returns {void}
  115. */
  116. function remove(xs, x) {
  117. xs.splice(xs.indexOf(x), 1);
  118. }
  119. /**
  120. * Disconnect given segments.
  121. *
  122. * This is used in a process for switch statements.
  123. * If there is the "default" chunk before other cases, the order is different
  124. * between node's and running's.
  125. *
  126. * @param {CodePathSegment[]} prevSegments - Forward segments to disconnect.
  127. * @param {CodePathSegment[]} nextSegments - Backward segments to disconnect.
  128. * @returns {void}
  129. */
  130. function removeConnection(prevSegments, nextSegments) {
  131. for (let i = 0; i < prevSegments.length; ++i) {
  132. const prevSegment = prevSegments[i];
  133. const nextSegment = nextSegments[i];
  134. remove(prevSegment.nextSegments, nextSegment);
  135. remove(prevSegment.allNextSegments, nextSegment);
  136. remove(nextSegment.prevSegments, prevSegment);
  137. remove(nextSegment.allPrevSegments, prevSegment);
  138. }
  139. }
  140. /**
  141. * Creates looping path.
  142. *
  143. * @param {CodePathState} state - The instance.
  144. * @param {CodePathSegment[]} unflattenedFromSegments - Segments which are source.
  145. * @param {CodePathSegment[]} unflattenedToSegments - Segments which are destination.
  146. * @returns {void}
  147. */
  148. function makeLooped(state, unflattenedFromSegments, unflattenedToSegments) {
  149. const fromSegments = CodePathSegment.flattenUnusedSegments(unflattenedFromSegments);
  150. const toSegments = CodePathSegment.flattenUnusedSegments(unflattenedToSegments);
  151. const end = Math.min(fromSegments.length, toSegments.length);
  152. for (let i = 0; i < end; ++i) {
  153. const fromSegment = fromSegments[i];
  154. const toSegment = toSegments[i];
  155. if (toSegment.reachable) {
  156. fromSegment.nextSegments.push(toSegment);
  157. }
  158. if (fromSegment.reachable) {
  159. toSegment.prevSegments.push(fromSegment);
  160. }
  161. fromSegment.allNextSegments.push(toSegment);
  162. toSegment.allPrevSegments.push(fromSegment);
  163. if (toSegment.allPrevSegments.length >= 2) {
  164. CodePathSegment.markPrevSegmentAsLooped(toSegment, fromSegment);
  165. }
  166. state.notifyLooped(fromSegment, toSegment);
  167. }
  168. }
  169. /**
  170. * Finalizes segments of `test` chunk of a ForStatement.
  171. *
  172. * - Adds `false` paths to paths which are leaving from the loop.
  173. * - Sets `true` paths to paths which go to the body.
  174. *
  175. * @param {LoopContext} context - A loop context to modify.
  176. * @param {ChoiceContext} choiceContext - A choice context of this loop.
  177. * @param {CodePathSegment[]} head - The current head paths.
  178. * @returns {void}
  179. */
  180. function finalizeTestSegmentsOfFor(context, choiceContext, head) {
  181. if (!choiceContext.processed) {
  182. choiceContext.trueForkContext.add(head);
  183. choiceContext.falseForkContext.add(head);
  184. }
  185. if (context.test !== true) {
  186. context.brokenForkContext.addAll(choiceContext.falseForkContext);
  187. }
  188. context.endOfTestSegments = choiceContext.trueForkContext.makeNext(0, -1);
  189. }
  190. //------------------------------------------------------------------------------
  191. // Public Interface
  192. //------------------------------------------------------------------------------
  193. /**
  194. * A class which manages state to analyze code paths.
  195. */
  196. class CodePathState {
  197. /**
  198. * @param {IdGenerator} idGenerator - An id generator to generate id for code
  199. * path segments.
  200. * @param {Function} onLooped - A callback function to notify looping.
  201. */
  202. constructor(idGenerator, onLooped) {
  203. this.idGenerator = idGenerator;
  204. this.notifyLooped = onLooped;
  205. this.forkContext = ForkContext.newRoot(idGenerator);
  206. this.choiceContext = null;
  207. this.switchContext = null;
  208. this.tryContext = null;
  209. this.loopContext = null;
  210. this.breakContext = null;
  211. this.currentSegments = [];
  212. this.initialSegment = this.forkContext.head[0];
  213. // returnedSegments and thrownSegments push elements into finalSegments also.
  214. const final = this.finalSegments = [];
  215. const returned = this.returnedForkContext = [];
  216. const thrown = this.thrownForkContext = [];
  217. returned.add = addToReturnedOrThrown.bind(null, returned, thrown, final);
  218. thrown.add = addToReturnedOrThrown.bind(null, thrown, returned, final);
  219. }
  220. /**
  221. * The head segments.
  222. * @type {CodePathSegment[]}
  223. */
  224. get headSegments() {
  225. return this.forkContext.head;
  226. }
  227. /**
  228. * The parent forking context.
  229. * This is used for the root of new forks.
  230. * @type {ForkContext}
  231. */
  232. get parentForkContext() {
  233. const current = this.forkContext;
  234. return current && current.upper;
  235. }
  236. /**
  237. * Creates and stacks new forking context.
  238. *
  239. * @param {boolean} forkLeavingPath - A flag which shows being in a
  240. * "finally" block.
  241. * @returns {ForkContext} The created context.
  242. */
  243. pushForkContext(forkLeavingPath) {
  244. this.forkContext = ForkContext.newEmpty(
  245. this.forkContext,
  246. forkLeavingPath
  247. );
  248. return this.forkContext;
  249. }
  250. /**
  251. * Pops and merges the last forking context.
  252. * @returns {ForkContext} The last context.
  253. */
  254. popForkContext() {
  255. const lastContext = this.forkContext;
  256. this.forkContext = lastContext.upper;
  257. this.forkContext.replaceHead(lastContext.makeNext(0, -1));
  258. return lastContext;
  259. }
  260. /**
  261. * Creates a new path.
  262. * @returns {void}
  263. */
  264. forkPath() {
  265. this.forkContext.add(this.parentForkContext.makeNext(-1, -1));
  266. }
  267. /**
  268. * Creates a bypass path.
  269. * This is used for such as IfStatement which does not have "else" chunk.
  270. *
  271. * @returns {void}
  272. */
  273. forkBypassPath() {
  274. this.forkContext.add(this.parentForkContext.head);
  275. }
  276. //--------------------------------------------------------------------------
  277. // ConditionalExpression, LogicalExpression, IfStatement
  278. //--------------------------------------------------------------------------
  279. /**
  280. * Creates a context for ConditionalExpression, LogicalExpression,
  281. * IfStatement, WhileStatement, DoWhileStatement, or ForStatement.
  282. *
  283. * LogicalExpressions have cases that it goes different paths between the
  284. * `true` case and the `false` case.
  285. *
  286. * For Example:
  287. *
  288. * if (a || b) {
  289. * foo();
  290. * } else {
  291. * bar();
  292. * }
  293. *
  294. * In this case, `b` is evaluated always in the code path of the `else`
  295. * block, but it's not so in the code path of the `if` block.
  296. * So there are 3 paths.
  297. *
  298. * a -> foo();
  299. * a -> b -> foo();
  300. * a -> b -> bar();
  301. *
  302. * @param {string} kind - A kind string.
  303. * If the new context is LogicalExpression's, this is `"&&"` or `"||"`.
  304. * If it's IfStatement's or ConditionalExpression's, this is `"test"`.
  305. * Otherwise, this is `"loop"`.
  306. * @param {boolean} isForkingAsResult - A flag that shows that goes different
  307. * paths between `true` and `false`.
  308. * @returns {void}
  309. */
  310. pushChoiceContext(kind, isForkingAsResult) {
  311. this.choiceContext = {
  312. upper: this.choiceContext,
  313. kind,
  314. isForkingAsResult,
  315. trueForkContext: ForkContext.newEmpty(this.forkContext),
  316. falseForkContext: ForkContext.newEmpty(this.forkContext),
  317. processed: false
  318. };
  319. }
  320. /**
  321. * Pops the last choice context and finalizes it.
  322. *
  323. * @returns {ChoiceContext} The popped context.
  324. */
  325. popChoiceContext() {
  326. const context = this.choiceContext;
  327. this.choiceContext = context.upper;
  328. const forkContext = this.forkContext;
  329. const headSegments = forkContext.head;
  330. switch (context.kind) {
  331. case "&&":
  332. case "||":
  333. /*
  334. * If any result were not transferred from child contexts,
  335. * this sets the head segments to both cases.
  336. * The head segments are the path of the right-hand operand.
  337. */
  338. if (!context.processed) {
  339. context.trueForkContext.add(headSegments);
  340. context.falseForkContext.add(headSegments);
  341. }
  342. /*
  343. * Transfers results to upper context if this context is in
  344. * test chunk.
  345. */
  346. if (context.isForkingAsResult) {
  347. const parentContext = this.choiceContext;
  348. parentContext.trueForkContext.addAll(context.trueForkContext);
  349. parentContext.falseForkContext.addAll(context.falseForkContext);
  350. parentContext.processed = true;
  351. return context;
  352. }
  353. break;
  354. case "test":
  355. if (!context.processed) {
  356. /*
  357. * The head segments are the path of the `if` block here.
  358. * Updates the `true` path with the end of the `if` block.
  359. */
  360. context.trueForkContext.clear();
  361. context.trueForkContext.add(headSegments);
  362. } else {
  363. /*
  364. * The head segments are the path of the `else` block here.
  365. * Updates the `false` path with the end of the `else`
  366. * block.
  367. */
  368. context.falseForkContext.clear();
  369. context.falseForkContext.add(headSegments);
  370. }
  371. break;
  372. case "loop":
  373. /*
  374. * Loops are addressed in popLoopContext().
  375. * This is called from popLoopContext().
  376. */
  377. return context;
  378. /* istanbul ignore next */
  379. default:
  380. throw new Error("unreachable");
  381. }
  382. // Merges all paths.
  383. const prevForkContext = context.trueForkContext;
  384. prevForkContext.addAll(context.falseForkContext);
  385. forkContext.replaceHead(prevForkContext.makeNext(0, -1));
  386. return context;
  387. }
  388. /**
  389. * Makes a code path segment of the right-hand operand of a logical
  390. * expression.
  391. *
  392. * @returns {void}
  393. */
  394. makeLogicalRight() {
  395. const context = this.choiceContext;
  396. const forkContext = this.forkContext;
  397. if (context.processed) {
  398. /*
  399. * This got segments already from the child choice context.
  400. * Creates the next path from own true/false fork context.
  401. */
  402. const prevForkContext =
  403. context.kind === "&&" ? context.trueForkContext
  404. /* kind === "||" */ : context.falseForkContext;
  405. forkContext.replaceHead(prevForkContext.makeNext(0, -1));
  406. prevForkContext.clear();
  407. context.processed = false;
  408. } else {
  409. /*
  410. * This did not get segments from the child choice context.
  411. * So addresses the head segments.
  412. * The head segments are the path of the left-hand operand.
  413. */
  414. if (context.kind === "&&") {
  415. // The path does short-circuit if false.
  416. context.falseForkContext.add(forkContext.head);
  417. } else {
  418. // The path does short-circuit if true.
  419. context.trueForkContext.add(forkContext.head);
  420. }
  421. forkContext.replaceHead(forkContext.makeNext(-1, -1));
  422. }
  423. }
  424. /**
  425. * Makes a code path segment of the `if` block.
  426. *
  427. * @returns {void}
  428. */
  429. makeIfConsequent() {
  430. const context = this.choiceContext;
  431. const forkContext = this.forkContext;
  432. /*
  433. * If any result were not transferred from child contexts,
  434. * this sets the head segments to both cases.
  435. * The head segments are the path of the test expression.
  436. */
  437. if (!context.processed) {
  438. context.trueForkContext.add(forkContext.head);
  439. context.falseForkContext.add(forkContext.head);
  440. }
  441. context.processed = false;
  442. // Creates new path from the `true` case.
  443. forkContext.replaceHead(
  444. context.trueForkContext.makeNext(0, -1)
  445. );
  446. }
  447. /**
  448. * Makes a code path segment of the `else` block.
  449. *
  450. * @returns {void}
  451. */
  452. makeIfAlternate() {
  453. const context = this.choiceContext;
  454. const forkContext = this.forkContext;
  455. /*
  456. * The head segments are the path of the `if` block.
  457. * Updates the `true` path with the end of the `if` block.
  458. */
  459. context.trueForkContext.clear();
  460. context.trueForkContext.add(forkContext.head);
  461. context.processed = true;
  462. // Creates new path from the `false` case.
  463. forkContext.replaceHead(
  464. context.falseForkContext.makeNext(0, -1)
  465. );
  466. }
  467. //--------------------------------------------------------------------------
  468. // SwitchStatement
  469. //--------------------------------------------------------------------------
  470. /**
  471. * Creates a context object of SwitchStatement and stacks it.
  472. *
  473. * @param {boolean} hasCase - `true` if the switch statement has one or more
  474. * case parts.
  475. * @param {string|null} label - The label text.
  476. * @returns {void}
  477. */
  478. pushSwitchContext(hasCase, label) {
  479. this.switchContext = {
  480. upper: this.switchContext,
  481. hasCase,
  482. defaultSegments: null,
  483. defaultBodySegments: null,
  484. foundDefault: false,
  485. lastIsDefault: false,
  486. countForks: 0
  487. };
  488. this.pushBreakContext(true, label);
  489. }
  490. /**
  491. * Pops the last context of SwitchStatement and finalizes it.
  492. *
  493. * - Disposes all forking stack for `case` and `default`.
  494. * - Creates the next code path segment from `context.brokenForkContext`.
  495. * - If the last `SwitchCase` node is not a `default` part, creates a path
  496. * to the `default` body.
  497. *
  498. * @returns {void}
  499. */
  500. popSwitchContext() {
  501. const context = this.switchContext;
  502. this.switchContext = context.upper;
  503. const forkContext = this.forkContext;
  504. const brokenForkContext = this.popBreakContext().brokenForkContext;
  505. if (context.countForks === 0) {
  506. /*
  507. * When there is only one `default` chunk and there is one or more
  508. * `break` statements, even if forks are nothing, it needs to merge
  509. * those.
  510. */
  511. if (!brokenForkContext.empty) {
  512. brokenForkContext.add(forkContext.makeNext(-1, -1));
  513. forkContext.replaceHead(brokenForkContext.makeNext(0, -1));
  514. }
  515. return;
  516. }
  517. const lastSegments = forkContext.head;
  518. this.forkBypassPath();
  519. const lastCaseSegments = forkContext.head;
  520. /*
  521. * `brokenForkContext` is used to make the next segment.
  522. * It must add the last segment into `brokenForkContext`.
  523. */
  524. brokenForkContext.add(lastSegments);
  525. /*
  526. * A path which is failed in all case test should be connected to path
  527. * of `default` chunk.
  528. */
  529. if (!context.lastIsDefault) {
  530. if (context.defaultBodySegments) {
  531. /*
  532. * Remove a link from `default` label to its chunk.
  533. * It's false route.
  534. */
  535. removeConnection(context.defaultSegments, context.defaultBodySegments);
  536. makeLooped(this, lastCaseSegments, context.defaultBodySegments);
  537. } else {
  538. /*
  539. * It handles the last case body as broken if `default` chunk
  540. * does not exist.
  541. */
  542. brokenForkContext.add(lastCaseSegments);
  543. }
  544. }
  545. // Pops the segment context stack until the entry segment.
  546. for (let i = 0; i < context.countForks; ++i) {
  547. this.forkContext = this.forkContext.upper;
  548. }
  549. /*
  550. * Creates a path from all brokenForkContext paths.
  551. * This is a path after switch statement.
  552. */
  553. this.forkContext.replaceHead(brokenForkContext.makeNext(0, -1));
  554. }
  555. /**
  556. * Makes a code path segment for a `SwitchCase` node.
  557. *
  558. * @param {boolean} isEmpty - `true` if the body is empty.
  559. * @param {boolean} isDefault - `true` if the body is the default case.
  560. * @returns {void}
  561. */
  562. makeSwitchCaseBody(isEmpty, isDefault) {
  563. const context = this.switchContext;
  564. if (!context.hasCase) {
  565. return;
  566. }
  567. /*
  568. * Merge forks.
  569. * The parent fork context has two segments.
  570. * Those are from the current case and the body of the previous case.
  571. */
  572. const parentForkContext = this.forkContext;
  573. const forkContext = this.pushForkContext();
  574. forkContext.add(parentForkContext.makeNext(0, -1));
  575. /*
  576. * Save `default` chunk info.
  577. * If the `default` label is not at the last, we must make a path from
  578. * the last `case` to the `default` chunk.
  579. */
  580. if (isDefault) {
  581. context.defaultSegments = parentForkContext.head;
  582. if (isEmpty) {
  583. context.foundDefault = true;
  584. } else {
  585. context.defaultBodySegments = forkContext.head;
  586. }
  587. } else {
  588. if (!isEmpty && context.foundDefault) {
  589. context.foundDefault = false;
  590. context.defaultBodySegments = forkContext.head;
  591. }
  592. }
  593. context.lastIsDefault = isDefault;
  594. context.countForks += 1;
  595. }
  596. //--------------------------------------------------------------------------
  597. // TryStatement
  598. //--------------------------------------------------------------------------
  599. /**
  600. * Creates a context object of TryStatement and stacks it.
  601. *
  602. * @param {boolean} hasFinalizer - `true` if the try statement has a
  603. * `finally` block.
  604. * @returns {void}
  605. */
  606. pushTryContext(hasFinalizer) {
  607. this.tryContext = {
  608. upper: this.tryContext,
  609. position: "try",
  610. hasFinalizer,
  611. returnedForkContext: hasFinalizer
  612. ? ForkContext.newEmpty(this.forkContext)
  613. : null,
  614. thrownForkContext: ForkContext.newEmpty(this.forkContext),
  615. lastOfTryIsReachable: false,
  616. lastOfCatchIsReachable: false
  617. };
  618. }
  619. /**
  620. * Pops the last context of TryStatement and finalizes it.
  621. *
  622. * @returns {void}
  623. */
  624. popTryContext() {
  625. const context = this.tryContext;
  626. this.tryContext = context.upper;
  627. if (context.position === "catch") {
  628. // Merges two paths from the `try` block and `catch` block merely.
  629. this.popForkContext();
  630. return;
  631. }
  632. /*
  633. * The following process is executed only when there is the `finally`
  634. * block.
  635. */
  636. const returned = context.returnedForkContext;
  637. const thrown = context.thrownForkContext;
  638. if (returned.empty && thrown.empty) {
  639. return;
  640. }
  641. // Separate head to normal paths and leaving paths.
  642. const headSegments = this.forkContext.head;
  643. this.forkContext = this.forkContext.upper;
  644. const normalSegments = headSegments.slice(0, headSegments.length / 2 | 0);
  645. const leavingSegments = headSegments.slice(headSegments.length / 2 | 0);
  646. // Forwards the leaving path to upper contexts.
  647. if (!returned.empty) {
  648. getReturnContext(this).returnedForkContext.add(leavingSegments);
  649. }
  650. if (!thrown.empty) {
  651. getThrowContext(this).thrownForkContext.add(leavingSegments);
  652. }
  653. // Sets the normal path as the next.
  654. this.forkContext.replaceHead(normalSegments);
  655. /*
  656. * If both paths of the `try` block and the `catch` block are
  657. * unreachable, the next path becomes unreachable as well.
  658. */
  659. if (!context.lastOfTryIsReachable && !context.lastOfCatchIsReachable) {
  660. this.forkContext.makeUnreachable();
  661. }
  662. }
  663. /**
  664. * Makes a code path segment for a `catch` block.
  665. *
  666. * @returns {void}
  667. */
  668. makeCatchBlock() {
  669. const context = this.tryContext;
  670. const forkContext = this.forkContext;
  671. const thrown = context.thrownForkContext;
  672. // Update state.
  673. context.position = "catch";
  674. context.thrownForkContext = ForkContext.newEmpty(forkContext);
  675. context.lastOfTryIsReachable = forkContext.reachable;
  676. // Merge thrown paths.
  677. thrown.add(forkContext.head);
  678. const thrownSegments = thrown.makeNext(0, -1);
  679. // Fork to a bypass and the merged thrown path.
  680. this.pushForkContext();
  681. this.forkBypassPath();
  682. this.forkContext.add(thrownSegments);
  683. }
  684. /**
  685. * Makes a code path segment for a `finally` block.
  686. *
  687. * In the `finally` block, parallel paths are created. The parallel paths
  688. * are used as leaving-paths. The leaving-paths are paths from `return`
  689. * statements and `throw` statements in a `try` block or a `catch` block.
  690. *
  691. * @returns {void}
  692. */
  693. makeFinallyBlock() {
  694. const context = this.tryContext;
  695. let forkContext = this.forkContext;
  696. const returned = context.returnedForkContext;
  697. const thrown = context.thrownForkContext;
  698. const headOfLeavingSegments = forkContext.head;
  699. // Update state.
  700. if (context.position === "catch") {
  701. // Merges two paths from the `try` block and `catch` block.
  702. this.popForkContext();
  703. forkContext = this.forkContext;
  704. context.lastOfCatchIsReachable = forkContext.reachable;
  705. } else {
  706. context.lastOfTryIsReachable = forkContext.reachable;
  707. }
  708. context.position = "finally";
  709. if (returned.empty && thrown.empty) {
  710. // This path does not leave.
  711. return;
  712. }
  713. /*
  714. * Create a parallel segment from merging returned and thrown.
  715. * This segment will leave at the end of this finally block.
  716. */
  717. const segments = forkContext.makeNext(-1, -1);
  718. for (let i = 0; i < forkContext.count; ++i) {
  719. const prevSegsOfLeavingSegment = [headOfLeavingSegments[i]];
  720. for (let j = 0; j < returned.segmentsList.length; ++j) {
  721. prevSegsOfLeavingSegment.push(returned.segmentsList[j][i]);
  722. }
  723. for (let j = 0; j < thrown.segmentsList.length; ++j) {
  724. prevSegsOfLeavingSegment.push(thrown.segmentsList[j][i]);
  725. }
  726. segments.push(
  727. CodePathSegment.newNext(
  728. this.idGenerator.next(),
  729. prevSegsOfLeavingSegment
  730. )
  731. );
  732. }
  733. this.pushForkContext(true);
  734. this.forkContext.add(segments);
  735. }
  736. /**
  737. * Makes a code path segment from the first throwable node to the `catch`
  738. * block or the `finally` block.
  739. *
  740. * @returns {void}
  741. */
  742. makeFirstThrowablePathInTryBlock() {
  743. const forkContext = this.forkContext;
  744. if (!forkContext.reachable) {
  745. return;
  746. }
  747. const context = getThrowContext(this);
  748. if (context === this ||
  749. context.position !== "try" ||
  750. !context.thrownForkContext.empty
  751. ) {
  752. return;
  753. }
  754. context.thrownForkContext.add(forkContext.head);
  755. forkContext.replaceHead(forkContext.makeNext(-1, -1));
  756. }
  757. //--------------------------------------------------------------------------
  758. // Loop Statements
  759. //--------------------------------------------------------------------------
  760. /**
  761. * Creates a context object of a loop statement and stacks it.
  762. *
  763. * @param {string} type - The type of the node which was triggered. One of
  764. * `WhileStatement`, `DoWhileStatement`, `ForStatement`, `ForInStatement`,
  765. * and `ForStatement`.
  766. * @param {string|null} label - A label of the node which was triggered.
  767. * @returns {void}
  768. */
  769. pushLoopContext(type, label) {
  770. const forkContext = this.forkContext;
  771. const breakContext = this.pushBreakContext(true, label);
  772. switch (type) {
  773. case "WhileStatement":
  774. this.pushChoiceContext("loop", false);
  775. this.loopContext = {
  776. upper: this.loopContext,
  777. type,
  778. label,
  779. test: void 0,
  780. continueDestSegments: null,
  781. brokenForkContext: breakContext.brokenForkContext
  782. };
  783. break;
  784. case "DoWhileStatement":
  785. this.pushChoiceContext("loop", false);
  786. this.loopContext = {
  787. upper: this.loopContext,
  788. type,
  789. label,
  790. test: void 0,
  791. entrySegments: null,
  792. continueForkContext: ForkContext.newEmpty(forkContext),
  793. brokenForkContext: breakContext.brokenForkContext
  794. };
  795. break;
  796. case "ForStatement":
  797. this.pushChoiceContext("loop", false);
  798. this.loopContext = {
  799. upper: this.loopContext,
  800. type,
  801. label,
  802. test: void 0,
  803. endOfInitSegments: null,
  804. testSegments: null,
  805. endOfTestSegments: null,
  806. updateSegments: null,
  807. endOfUpdateSegments: null,
  808. continueDestSegments: null,
  809. brokenForkContext: breakContext.brokenForkContext
  810. };
  811. break;
  812. case "ForInStatement":
  813. case "ForOfStatement":
  814. this.loopContext = {
  815. upper: this.loopContext,
  816. type,
  817. label,
  818. prevSegments: null,
  819. leftSegments: null,
  820. endOfLeftSegments: null,
  821. continueDestSegments: null,
  822. brokenForkContext: breakContext.brokenForkContext
  823. };
  824. break;
  825. /* istanbul ignore next */
  826. default:
  827. throw new Error(`unknown type: "${type}"`);
  828. }
  829. }
  830. /**
  831. * Pops the last context of a loop statement and finalizes it.
  832. *
  833. * @returns {void}
  834. */
  835. popLoopContext() {
  836. const context = this.loopContext;
  837. this.loopContext = context.upper;
  838. const forkContext = this.forkContext;
  839. const brokenForkContext = this.popBreakContext().brokenForkContext;
  840. // Creates a looped path.
  841. switch (context.type) {
  842. case "WhileStatement":
  843. case "ForStatement":
  844. this.popChoiceContext();
  845. makeLooped(
  846. this,
  847. forkContext.head,
  848. context.continueDestSegments
  849. );
  850. break;
  851. case "DoWhileStatement": {
  852. const choiceContext = this.popChoiceContext();
  853. if (!choiceContext.processed) {
  854. choiceContext.trueForkContext.add(forkContext.head);
  855. choiceContext.falseForkContext.add(forkContext.head);
  856. }
  857. if (context.test !== true) {
  858. brokenForkContext.addAll(choiceContext.falseForkContext);
  859. }
  860. // `true` paths go to looping.
  861. const segmentsList = choiceContext.trueForkContext.segmentsList;
  862. for (let i = 0; i < segmentsList.length; ++i) {
  863. makeLooped(
  864. this,
  865. segmentsList[i],
  866. context.entrySegments
  867. );
  868. }
  869. break;
  870. }
  871. case "ForInStatement":
  872. case "ForOfStatement":
  873. brokenForkContext.add(forkContext.head);
  874. makeLooped(
  875. this,
  876. forkContext.head,
  877. context.leftSegments
  878. );
  879. break;
  880. /* istanbul ignore next */
  881. default:
  882. throw new Error("unreachable");
  883. }
  884. // Go next.
  885. if (brokenForkContext.empty) {
  886. forkContext.replaceHead(forkContext.makeUnreachable(-1, -1));
  887. } else {
  888. forkContext.replaceHead(brokenForkContext.makeNext(0, -1));
  889. }
  890. }
  891. /**
  892. * Makes a code path segment for the test part of a WhileStatement.
  893. *
  894. * @param {boolean|undefined} test - The test value (only when constant).
  895. * @returns {void}
  896. */
  897. makeWhileTest(test) {
  898. const context = this.loopContext;
  899. const forkContext = this.forkContext;
  900. const testSegments = forkContext.makeNext(0, -1);
  901. // Update state.
  902. context.test = test;
  903. context.continueDestSegments = testSegments;
  904. forkContext.replaceHead(testSegments);
  905. }
  906. /**
  907. * Makes a code path segment for the body part of a WhileStatement.
  908. *
  909. * @returns {void}
  910. */
  911. makeWhileBody() {
  912. const context = this.loopContext;
  913. const choiceContext = this.choiceContext;
  914. const forkContext = this.forkContext;
  915. if (!choiceContext.processed) {
  916. choiceContext.trueForkContext.add(forkContext.head);
  917. choiceContext.falseForkContext.add(forkContext.head);
  918. }
  919. // Update state.
  920. if (context.test !== true) {
  921. context.brokenForkContext.addAll(choiceContext.falseForkContext);
  922. }
  923. forkContext.replaceHead(choiceContext.trueForkContext.makeNext(0, -1));
  924. }
  925. /**
  926. * Makes a code path segment for the body part of a DoWhileStatement.
  927. *
  928. * @returns {void}
  929. */
  930. makeDoWhileBody() {
  931. const context = this.loopContext;
  932. const forkContext = this.forkContext;
  933. const bodySegments = forkContext.makeNext(-1, -1);
  934. // Update state.
  935. context.entrySegments = bodySegments;
  936. forkContext.replaceHead(bodySegments);
  937. }
  938. /**
  939. * Makes a code path segment for the test part of a DoWhileStatement.
  940. *
  941. * @param {boolean|undefined} test - The test value (only when constant).
  942. * @returns {void}
  943. */
  944. makeDoWhileTest(test) {
  945. const context = this.loopContext;
  946. const forkContext = this.forkContext;
  947. context.test = test;
  948. // Creates paths of `continue` statements.
  949. if (!context.continueForkContext.empty) {
  950. context.continueForkContext.add(forkContext.head);
  951. const testSegments = context.continueForkContext.makeNext(0, -1);
  952. forkContext.replaceHead(testSegments);
  953. }
  954. }
  955. /**
  956. * Makes a code path segment for the test part of a ForStatement.
  957. *
  958. * @param {boolean|undefined} test - The test value (only when constant).
  959. * @returns {void}
  960. */
  961. makeForTest(test) {
  962. const context = this.loopContext;
  963. const forkContext = this.forkContext;
  964. const endOfInitSegments = forkContext.head;
  965. const testSegments = forkContext.makeNext(-1, -1);
  966. // Update state.
  967. context.test = test;
  968. context.endOfInitSegments = endOfInitSegments;
  969. context.continueDestSegments = context.testSegments = testSegments;
  970. forkContext.replaceHead(testSegments);
  971. }
  972. /**
  973. * Makes a code path segment for the update part of a ForStatement.
  974. *
  975. * @returns {void}
  976. */
  977. makeForUpdate() {
  978. const context = this.loopContext;
  979. const choiceContext = this.choiceContext;
  980. const forkContext = this.forkContext;
  981. // Make the next paths of the test.
  982. if (context.testSegments) {
  983. finalizeTestSegmentsOfFor(
  984. context,
  985. choiceContext,
  986. forkContext.head
  987. );
  988. } else {
  989. context.endOfInitSegments = forkContext.head;
  990. }
  991. // Update state.
  992. const updateSegments = forkContext.makeDisconnected(-1, -1);
  993. context.continueDestSegments = context.updateSegments = updateSegments;
  994. forkContext.replaceHead(updateSegments);
  995. }
  996. /**
  997. * Makes a code path segment for the body part of a ForStatement.
  998. *
  999. * @returns {void}
  1000. */
  1001. makeForBody() {
  1002. const context = this.loopContext;
  1003. const choiceContext = this.choiceContext;
  1004. const forkContext = this.forkContext;
  1005. // Update state.
  1006. if (context.updateSegments) {
  1007. context.endOfUpdateSegments = forkContext.head;
  1008. // `update` -> `test`
  1009. if (context.testSegments) {
  1010. makeLooped(
  1011. this,
  1012. context.endOfUpdateSegments,
  1013. context.testSegments
  1014. );
  1015. }
  1016. } else if (context.testSegments) {
  1017. finalizeTestSegmentsOfFor(
  1018. context,
  1019. choiceContext,
  1020. forkContext.head
  1021. );
  1022. } else {
  1023. context.endOfInitSegments = forkContext.head;
  1024. }
  1025. let bodySegments = context.endOfTestSegments;
  1026. if (!bodySegments) {
  1027. /*
  1028. * If there is not the `test` part, the `body` path comes from the
  1029. * `init` part and the `update` part.
  1030. */
  1031. const prevForkContext = ForkContext.newEmpty(forkContext);
  1032. prevForkContext.add(context.endOfInitSegments);
  1033. if (context.endOfUpdateSegments) {
  1034. prevForkContext.add(context.endOfUpdateSegments);
  1035. }
  1036. bodySegments = prevForkContext.makeNext(0, -1);
  1037. }
  1038. context.continueDestSegments = context.continueDestSegments || bodySegments;
  1039. forkContext.replaceHead(bodySegments);
  1040. }
  1041. /**
  1042. * Makes a code path segment for the left part of a ForInStatement and a
  1043. * ForOfStatement.
  1044. *
  1045. * @returns {void}
  1046. */
  1047. makeForInOfLeft() {
  1048. const context = this.loopContext;
  1049. const forkContext = this.forkContext;
  1050. const leftSegments = forkContext.makeDisconnected(-1, -1);
  1051. // Update state.
  1052. context.prevSegments = forkContext.head;
  1053. context.leftSegments = context.continueDestSegments = leftSegments;
  1054. forkContext.replaceHead(leftSegments);
  1055. }
  1056. /**
  1057. * Makes a code path segment for the right part of a ForInStatement and a
  1058. * ForOfStatement.
  1059. *
  1060. * @returns {void}
  1061. */
  1062. makeForInOfRight() {
  1063. const context = this.loopContext;
  1064. const forkContext = this.forkContext;
  1065. const temp = ForkContext.newEmpty(forkContext);
  1066. temp.add(context.prevSegments);
  1067. const rightSegments = temp.makeNext(-1, -1);
  1068. // Update state.
  1069. context.endOfLeftSegments = forkContext.head;
  1070. forkContext.replaceHead(rightSegments);
  1071. }
  1072. /**
  1073. * Makes a code path segment for the body part of a ForInStatement and a
  1074. * ForOfStatement.
  1075. *
  1076. * @returns {void}
  1077. */
  1078. makeForInOfBody() {
  1079. const context = this.loopContext;
  1080. const forkContext = this.forkContext;
  1081. const temp = ForkContext.newEmpty(forkContext);
  1082. temp.add(context.endOfLeftSegments);
  1083. const bodySegments = temp.makeNext(-1, -1);
  1084. // Make a path: `right` -> `left`.
  1085. makeLooped(this, forkContext.head, context.leftSegments);
  1086. // Update state.
  1087. context.brokenForkContext.add(forkContext.head);
  1088. forkContext.replaceHead(bodySegments);
  1089. }
  1090. //--------------------------------------------------------------------------
  1091. // Control Statements
  1092. //--------------------------------------------------------------------------
  1093. /**
  1094. * Creates new context for BreakStatement.
  1095. *
  1096. * @param {boolean} breakable - The flag to indicate it can break by
  1097. * an unlabeled BreakStatement.
  1098. * @param {string|null} label - The label of this context.
  1099. * @returns {Object} The new context.
  1100. */
  1101. pushBreakContext(breakable, label) {
  1102. this.breakContext = {
  1103. upper: this.breakContext,
  1104. breakable,
  1105. label,
  1106. brokenForkContext: ForkContext.newEmpty(this.forkContext)
  1107. };
  1108. return this.breakContext;
  1109. }
  1110. /**
  1111. * Removes the top item of the break context stack.
  1112. *
  1113. * @returns {Object} The removed context.
  1114. */
  1115. popBreakContext() {
  1116. const context = this.breakContext;
  1117. const forkContext = this.forkContext;
  1118. this.breakContext = context.upper;
  1119. // Process this context here for other than switches and loops.
  1120. if (!context.breakable) {
  1121. const brokenForkContext = context.brokenForkContext;
  1122. if (!brokenForkContext.empty) {
  1123. brokenForkContext.add(forkContext.head);
  1124. forkContext.replaceHead(brokenForkContext.makeNext(0, -1));
  1125. }
  1126. }
  1127. return context;
  1128. }
  1129. /**
  1130. * Makes a path for a `break` statement.
  1131. *
  1132. * It registers the head segment to a context of `break`.
  1133. * It makes new unreachable segment, then it set the head with the segment.
  1134. *
  1135. * @param {string} label - A label of the break statement.
  1136. * @returns {void}
  1137. */
  1138. makeBreak(label) {
  1139. const forkContext = this.forkContext;
  1140. if (!forkContext.reachable) {
  1141. return;
  1142. }
  1143. const context = getBreakContext(this, label);
  1144. /* istanbul ignore else: foolproof (syntax error) */
  1145. if (context) {
  1146. context.brokenForkContext.add(forkContext.head);
  1147. }
  1148. forkContext.replaceHead(forkContext.makeUnreachable(-1, -1));
  1149. }
  1150. /**
  1151. * Makes a path for a `continue` statement.
  1152. *
  1153. * It makes a looping path.
  1154. * It makes new unreachable segment, then it set the head with the segment.
  1155. *
  1156. * @param {string} label - A label of the continue statement.
  1157. * @returns {void}
  1158. */
  1159. makeContinue(label) {
  1160. const forkContext = this.forkContext;
  1161. if (!forkContext.reachable) {
  1162. return;
  1163. }
  1164. const context = getContinueContext(this, label);
  1165. /* istanbul ignore else: foolproof (syntax error) */
  1166. if (context) {
  1167. if (context.continueDestSegments) {
  1168. makeLooped(this, forkContext.head, context.continueDestSegments);
  1169. // If the context is a for-in/of loop, this effects a break also.
  1170. if (context.type === "ForInStatement" ||
  1171. context.type === "ForOfStatement"
  1172. ) {
  1173. context.brokenForkContext.add(forkContext.head);
  1174. }
  1175. } else {
  1176. context.continueForkContext.add(forkContext.head);
  1177. }
  1178. }
  1179. forkContext.replaceHead(forkContext.makeUnreachable(-1, -1));
  1180. }
  1181. /**
  1182. * Makes a path for a `return` statement.
  1183. *
  1184. * It registers the head segment to a context of `return`.
  1185. * It makes new unreachable segment, then it set the head with the segment.
  1186. *
  1187. * @returns {void}
  1188. */
  1189. makeReturn() {
  1190. const forkContext = this.forkContext;
  1191. if (forkContext.reachable) {
  1192. getReturnContext(this).returnedForkContext.add(forkContext.head);
  1193. forkContext.replaceHead(forkContext.makeUnreachable(-1, -1));
  1194. }
  1195. }
  1196. /**
  1197. * Makes a path for a `throw` statement.
  1198. *
  1199. * It registers the head segment to a context of `throw`.
  1200. * It makes new unreachable segment, then it set the head with the segment.
  1201. *
  1202. * @returns {void}
  1203. */
  1204. makeThrow() {
  1205. const forkContext = this.forkContext;
  1206. if (forkContext.reachable) {
  1207. getThrowContext(this).thrownForkContext.add(forkContext.head);
  1208. forkContext.replaceHead(forkContext.makeUnreachable(-1, -1));
  1209. }
  1210. }
  1211. /**
  1212. * Makes the final path.
  1213. * @returns {void}
  1214. */
  1215. makeFinal() {
  1216. const segments = this.currentSegments;
  1217. if (segments.length > 0 && segments[0].reachable) {
  1218. this.returnedForkContext.add(segments);
  1219. }
  1220. }
  1221. }
  1222. module.exports = CodePathState;