code-path-state.js 47 KB

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