Ohm-Management - Projektarbeit B-ME
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

code-path-state.js 44KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440
  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;