code-path-state.js 69 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370
  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. // Contexts
  13. //-----------------------------------------------------------------------------
  14. /**
  15. * Represents the context in which a `break` statement can be used.
  16. *
  17. * A `break` statement without a label is only valid in a few places in
  18. * JavaScript: any type of loop or a `switch` statement. Otherwise, `break`
  19. * without a label causes a syntax error. For these contexts, `breakable` is
  20. * set to `true` to indicate that a `break` without a label is valid.
  21. *
  22. * However, a `break` statement with a label is also valid inside of a labeled
  23. * statement. For example, this is valid:
  24. *
  25. * a : {
  26. * break a;
  27. * }
  28. *
  29. * The `breakable` property is set false for labeled statements to indicate
  30. * that `break` without a label is invalid.
  31. */
  32. class BreakContext {
  33. /**
  34. * Creates a new instance.
  35. * @param {BreakContext} upperContext The previous `BreakContext`.
  36. * @param {boolean} breakable Indicates if we are inside a statement where
  37. * `break` without a label will exit the statement.
  38. * @param {string|null} label The label for the statement.
  39. * @param {ForkContext} forkContext The current fork context.
  40. */
  41. constructor(upperContext, breakable, label, forkContext) {
  42. /**
  43. * The previous `BreakContext`
  44. * @type {BreakContext}
  45. */
  46. this.upper = upperContext;
  47. /**
  48. * Indicates if we are inside a statement where `break` without a label
  49. * will exit the statement.
  50. * @type {boolean}
  51. */
  52. this.breakable = breakable;
  53. /**
  54. * The label associated with the statement.
  55. * @type {string|null}
  56. */
  57. this.label = label;
  58. /**
  59. * The fork context for the `break`.
  60. * @type {ForkContext}
  61. */
  62. this.brokenForkContext = ForkContext.newEmpty(forkContext);
  63. }
  64. }
  65. /**
  66. * Represents the context for `ChainExpression` nodes.
  67. */
  68. class ChainContext {
  69. /**
  70. * Creates a new instance.
  71. * @param {ChainContext} upperContext The previous `ChainContext`.
  72. */
  73. constructor(upperContext) {
  74. /**
  75. * The previous `ChainContext`
  76. * @type {ChainContext}
  77. */
  78. this.upper = upperContext;
  79. /**
  80. * The number of choice contexts inside of the `ChainContext`.
  81. * @type {number}
  82. */
  83. this.choiceContextCount = 0;
  84. }
  85. }
  86. /**
  87. * Represents a choice in the code path.
  88. *
  89. * Choices are created by logical operators such as `&&`, loops, conditionals,
  90. * and `if` statements. This is the point at which the code path has a choice of
  91. * which direction to go.
  92. *
  93. * The result of a choice might be in the left (test) expression of another choice,
  94. * and in that case, may create a new fork. For example, `a || b` is a choice
  95. * but does not create a new fork because the result of the expression is
  96. * not used as the test expression in another expression. In this case,
  97. * `isForkingAsResult` is false. In the expression `a || b || c`, the `a || b`
  98. * expression appears as the test expression for `|| c`, so the
  99. * result of `a || b` creates a fork because execution may or may not
  100. * continue to `|| c`. `isForkingAsResult` for `a || b` in this case is true
  101. * while `isForkingAsResult` for `|| c` is false. (`isForkingAsResult` is always
  102. * false for `if` statements, conditional expressions, and loops.)
  103. *
  104. * All of the choices except one (`??`) operate on a true/false fork, meaning if
  105. * true go one way and if false go the other (tracked by `trueForkContext` and
  106. * `falseForkContext`). The `??` operator doesn't operate on true/false because
  107. * the left expression is evaluated to be nullish or not, so only if nullish do
  108. * we fork to the right expression (tracked by `nullishForkContext`).
  109. */
  110. class ChoiceContext {
  111. /**
  112. * Creates a new instance.
  113. * @param {ChoiceContext} upperContext The previous `ChoiceContext`.
  114. * @param {string} kind The kind of choice. If it's a logical or assignment expression, this
  115. * is `"&&"` or `"||"` or `"??"`; if it's an `if` statement or
  116. * conditional expression, this is `"test"`; otherwise, this is `"loop"`.
  117. * @param {boolean} isForkingAsResult Indicates if the result of the choice
  118. * creates a fork.
  119. * @param {ForkContext} forkContext The containing `ForkContext`.
  120. */
  121. constructor(upperContext, kind, isForkingAsResult, forkContext) {
  122. /**
  123. * The previous `ChoiceContext`
  124. * @type {ChoiceContext}
  125. */
  126. this.upper = upperContext;
  127. /**
  128. * The kind of choice. If it's a logical or assignment expression, this
  129. * is `"&&"` or `"||"` or `"??"`; if it's an `if` statement or
  130. * conditional expression, this is `"test"`; otherwise, this is `"loop"`.
  131. * @type {string}
  132. */
  133. this.kind = kind;
  134. /**
  135. * Indicates if the result of the choice forks the code path.
  136. * @type {boolean}
  137. */
  138. this.isForkingAsResult = isForkingAsResult;
  139. /**
  140. * The fork context for the `true` path of the choice.
  141. * @type {ForkContext}
  142. */
  143. this.trueForkContext = ForkContext.newEmpty(forkContext);
  144. /**
  145. * The fork context for the `false` path of the choice.
  146. * @type {ForkContext}
  147. */
  148. this.falseForkContext = ForkContext.newEmpty(forkContext);
  149. /**
  150. * The fork context for when the choice result is `null` or `undefined`.
  151. * @type {ForkContext}
  152. */
  153. this.nullishForkContext = ForkContext.newEmpty(forkContext);
  154. /**
  155. * Indicates if any of `trueForkContext`, `falseForkContext`, or
  156. * `nullishForkContext` have been updated with segments from a child context.
  157. * @type {boolean}
  158. */
  159. this.processed = false;
  160. }
  161. }
  162. /**
  163. * Base class for all loop contexts.
  164. */
  165. class LoopContextBase {
  166. /**
  167. * Creates a new instance.
  168. * @param {LoopContext|null} upperContext The previous `LoopContext`.
  169. * @param {string} type The AST node's `type` for the loop.
  170. * @param {string|null} label The label for the loop from an enclosing `LabeledStatement`.
  171. * @param {BreakContext} breakContext The context for breaking the loop.
  172. */
  173. constructor(upperContext, type, label, breakContext) {
  174. /**
  175. * The previous `LoopContext`.
  176. * @type {LoopContext}
  177. */
  178. this.upper = upperContext;
  179. /**
  180. * The AST node's `type` for the loop.
  181. * @type {string}
  182. */
  183. this.type = type;
  184. /**
  185. * The label for the loop from an enclosing `LabeledStatement`.
  186. * @type {string|null}
  187. */
  188. this.label = label;
  189. /**
  190. * The fork context for when `break` is encountered.
  191. * @type {ForkContext}
  192. */
  193. this.brokenForkContext = breakContext.brokenForkContext;
  194. }
  195. }
  196. /**
  197. * Represents the context for a `while` loop.
  198. */
  199. class WhileLoopContext extends LoopContextBase {
  200. /**
  201. * Creates a new instance.
  202. * @param {LoopContext|null} upperContext The previous `LoopContext`.
  203. * @param {string|null} label The label for the loop from an enclosing `LabeledStatement`.
  204. * @param {BreakContext} breakContext The context for breaking the loop.
  205. */
  206. constructor(upperContext, label, breakContext) {
  207. super(upperContext, "WhileStatement", label, breakContext);
  208. /**
  209. * The hardcoded literal boolean test condition for
  210. * the loop. Used to catch infinite or skipped loops.
  211. * @type {boolean|undefined}
  212. */
  213. this.test = void 0;
  214. /**
  215. * The segments representing the test condition where `continue` will
  216. * jump to. The test condition will typically have just one segment but
  217. * it's possible for there to be more than one.
  218. * @type {Array<CodePathSegment>|null}
  219. */
  220. this.continueDestSegments = null;
  221. }
  222. }
  223. /**
  224. * Represents the context for a `do-while` loop.
  225. */
  226. class DoWhileLoopContext extends LoopContextBase {
  227. /**
  228. * Creates a new instance.
  229. * @param {LoopContext|null} upperContext The previous `LoopContext`.
  230. * @param {string|null} label The label for the loop from an enclosing `LabeledStatement`.
  231. * @param {BreakContext} breakContext The context for breaking the loop.
  232. * @param {ForkContext} forkContext The enclosing fork context.
  233. */
  234. constructor(upperContext, label, breakContext, forkContext) {
  235. super(upperContext, "DoWhileStatement", label, breakContext);
  236. /**
  237. * The hardcoded literal boolean test condition for
  238. * the loop. Used to catch infinite or skipped loops.
  239. * @type {boolean|undefined}
  240. */
  241. this.test = void 0;
  242. /**
  243. * The segments at the start of the loop body. This is the only loop
  244. * where the test comes at the end, so the first iteration always
  245. * happens and we need a reference to the first statements.
  246. * @type {Array<CodePathSegment>|null}
  247. */
  248. this.entrySegments = null;
  249. /**
  250. * The fork context to follow when a `continue` is found.
  251. * @type {ForkContext}
  252. */
  253. this.continueForkContext = ForkContext.newEmpty(forkContext);
  254. }
  255. }
  256. /**
  257. * Represents the context for a `for` loop.
  258. */
  259. class ForLoopContext extends LoopContextBase {
  260. /**
  261. * Creates a new instance.
  262. * @param {LoopContext|null} upperContext The previous `LoopContext`.
  263. * @param {string|null} label The label for the loop from an enclosing `LabeledStatement`.
  264. * @param {BreakContext} breakContext The context for breaking the loop.
  265. */
  266. constructor(upperContext, label, breakContext) {
  267. super(upperContext, "ForStatement", label, breakContext);
  268. /**
  269. * The hardcoded literal boolean test condition for
  270. * the loop. Used to catch infinite or skipped loops.
  271. * @type {boolean|undefined}
  272. */
  273. this.test = void 0;
  274. /**
  275. * The end of the init expression. This may change during the lifetime
  276. * of the instance as we traverse the loop because some loops don't have
  277. * an init expression.
  278. * @type {Array<CodePathSegment>|null}
  279. */
  280. this.endOfInitSegments = null;
  281. /**
  282. * The start of the test expression. This may change during the lifetime
  283. * of the instance as we traverse the loop because some loops don't have
  284. * a test expression.
  285. * @type {Array<CodePathSegment>|null}
  286. */
  287. this.testSegments = null;
  288. /**
  289. * The end of the test expression. This may change during the lifetime
  290. * of the instance as we traverse the loop because some loops don't have
  291. * a test expression.
  292. * @type {Array<CodePathSegment>|null}
  293. */
  294. this.endOfTestSegments = null;
  295. /**
  296. * The start of the update expression. This may change during the lifetime
  297. * of the instance as we traverse the loop because some loops don't have
  298. * an update expression.
  299. * @type {Array<CodePathSegment>|null}
  300. */
  301. this.updateSegments = null;
  302. /**
  303. * The end of the update expression. This may change during the lifetime
  304. * of the instance as we traverse the loop because some loops don't have
  305. * an update expression.
  306. * @type {Array<CodePathSegment>|null}
  307. */
  308. this.endOfUpdateSegments = null;
  309. /**
  310. * The segments representing the test condition where `continue` will
  311. * jump to. The test condition will typically have just one segment but
  312. * it's possible for there to be more than one. This may change during the
  313. * lifetime of the instance as we traverse the loop because some loops
  314. * don't have an update expression. When there is an update expression, this
  315. * will end up pointing to that expression; otherwise it will end up pointing
  316. * to the test expression.
  317. * @type {Array<CodePathSegment>|null}
  318. */
  319. this.continueDestSegments = null;
  320. }
  321. }
  322. /**
  323. * Represents the context for a `for-in` loop.
  324. *
  325. * Terminology:
  326. * - "left" means the part of the loop to the left of the `in` keyword. For
  327. * example, in `for (var x in y)`, the left is `var x`.
  328. * - "right" means the part of the loop to the right of the `in` keyword. For
  329. * example, in `for (var x in y)`, the right is `y`.
  330. */
  331. class ForInLoopContext extends LoopContextBase {
  332. /**
  333. * Creates a new instance.
  334. * @param {LoopContext|null} upperContext The previous `LoopContext`.
  335. * @param {string|null} label The label for the loop from an enclosing `LabeledStatement`.
  336. * @param {BreakContext} breakContext The context for breaking the loop.
  337. */
  338. constructor(upperContext, label, breakContext) {
  339. super(upperContext, "ForInStatement", label, breakContext);
  340. /**
  341. * The segments that came immediately before the start of the loop.
  342. * This allows you to traverse backwards out of the loop into the
  343. * surrounding code. This is necessary to evaluate the right expression
  344. * correctly, as it must be evaluated in the same way as the left
  345. * expression, but the pointer to these segments would otherwise be
  346. * lost if not stored on the instance. Once the right expression has
  347. * been evaluated, this property is no longer used.
  348. * @type {Array<CodePathSegment>|null}
  349. */
  350. this.prevSegments = null;
  351. /**
  352. * Segments representing the start of everything to the left of the
  353. * `in` keyword. This can be used to move forward towards
  354. * `endOfLeftSegments`. `leftSegments` and `endOfLeftSegments` are
  355. * effectively the head and tail of a doubly-linked list.
  356. * @type {Array<CodePathSegment>|null}
  357. */
  358. this.leftSegments = null;
  359. /**
  360. * Segments representing the end of everything to the left of the
  361. * `in` keyword. This can be used to move backward towards `leftSegments`.
  362. * `leftSegments` and `endOfLeftSegments` are effectively the head
  363. * and tail of a doubly-linked list.
  364. * @type {Array<CodePathSegment>|null}
  365. */
  366. this.endOfLeftSegments = null;
  367. /**
  368. * The segments representing the left expression where `continue` will
  369. * jump to. In `for-in` loops, `continue` must always re-execute the
  370. * left expression each time through the loop. This contains the same
  371. * segments as `leftSegments`, but is duplicated here so each loop
  372. * context has the same property pointing to where `continue` should
  373. * end up.
  374. * @type {Array<CodePathSegment>|null}
  375. */
  376. this.continueDestSegments = null;
  377. }
  378. }
  379. /**
  380. * Represents the context for a `for-of` loop.
  381. */
  382. class ForOfLoopContext extends LoopContextBase {
  383. /**
  384. * Creates a new instance.
  385. * @param {LoopContext|null} upperContext The previous `LoopContext`.
  386. * @param {string|null} label The label for the loop from an enclosing `LabeledStatement`.
  387. * @param {BreakContext} breakContext The context for breaking the loop.
  388. */
  389. constructor(upperContext, label, breakContext) {
  390. super(upperContext, "ForOfStatement", label, breakContext);
  391. /**
  392. * The segments that came immediately before the start of the loop.
  393. * This allows you to traverse backwards out of the loop into the
  394. * surrounding code. This is necessary to evaluate the right expression
  395. * correctly, as it must be evaluated in the same way as the left
  396. * expression, but the pointer to these segments would otherwise be
  397. * lost if not stored on the instance. Once the right expression has
  398. * been evaluated, this property is no longer used.
  399. * @type {Array<CodePathSegment>|null}
  400. */
  401. this.prevSegments = null;
  402. /**
  403. * Segments representing the start of everything to the left of the
  404. * `of` keyword. This can be used to move forward towards
  405. * `endOfLeftSegments`. `leftSegments` and `endOfLeftSegments` are
  406. * effectively the head and tail of a doubly-linked list.
  407. * @type {Array<CodePathSegment>|null}
  408. */
  409. this.leftSegments = null;
  410. /**
  411. * Segments representing the end of everything to the left of the
  412. * `of` keyword. This can be used to move backward towards `leftSegments`.
  413. * `leftSegments` and `endOfLeftSegments` are effectively the head
  414. * and tail of a doubly-linked list.
  415. * @type {Array<CodePathSegment>|null}
  416. */
  417. this.endOfLeftSegments = null;
  418. /**
  419. * The segments representing the left expression where `continue` will
  420. * jump to. In `for-in` loops, `continue` must always re-execute the
  421. * left expression each time through the loop. This contains the same
  422. * segments as `leftSegments`, but is duplicated here so each loop
  423. * context has the same property pointing to where `continue` should
  424. * end up.
  425. * @type {Array<CodePathSegment>|null}
  426. */
  427. this.continueDestSegments = null;
  428. }
  429. }
  430. /**
  431. * Represents the context for any loop.
  432. * @typedef {WhileLoopContext|DoWhileLoopContext|ForLoopContext|ForInLoopContext|ForOfLoopContext} LoopContext
  433. */
  434. /**
  435. * Represents the context for a `switch` statement.
  436. */
  437. class SwitchContext {
  438. /**
  439. * Creates a new instance.
  440. * @param {SwitchContext} upperContext The previous context.
  441. * @param {boolean} hasCase Indicates if there is at least one `case` statement.
  442. * `default` doesn't count.
  443. */
  444. constructor(upperContext, hasCase) {
  445. /**
  446. * The previous context.
  447. * @type {SwitchContext}
  448. */
  449. this.upper = upperContext;
  450. /**
  451. * Indicates if there is at least one `case` statement. `default` doesn't count.
  452. * @type {boolean}
  453. */
  454. this.hasCase = hasCase;
  455. /**
  456. * The `default` keyword.
  457. * @type {Array<CodePathSegment>|null}
  458. */
  459. this.defaultSegments = null;
  460. /**
  461. * The default case body starting segments.
  462. * @type {Array<CodePathSegment>|null}
  463. */
  464. this.defaultBodySegments = null;
  465. /**
  466. * Indicates if a `default` case and is empty exists.
  467. * @type {boolean}
  468. */
  469. this.foundEmptyDefault = false;
  470. /**
  471. * Indicates that a `default` exists and is the last case.
  472. * @type {boolean}
  473. */
  474. this.lastIsDefault = false;
  475. /**
  476. * The number of fork contexts created. This is equivalent to the
  477. * number of `case` statements plus a `default` statement (if present).
  478. * @type {number}
  479. */
  480. this.forkCount = 0;
  481. }
  482. }
  483. /**
  484. * Represents the context for a `try` statement.
  485. */
  486. class TryContext {
  487. /**
  488. * Creates a new instance.
  489. * @param {TryContext} upperContext The previous context.
  490. * @param {boolean} hasFinalizer Indicates if the `try` statement has a
  491. * `finally` block.
  492. * @param {ForkContext} forkContext The enclosing fork context.
  493. */
  494. constructor(upperContext, hasFinalizer, forkContext) {
  495. /**
  496. * The previous context.
  497. * @type {TryContext}
  498. */
  499. this.upper = upperContext;
  500. /**
  501. * Indicates if the `try` statement has a `finally` block.
  502. * @type {boolean}
  503. */
  504. this.hasFinalizer = hasFinalizer;
  505. /**
  506. * Tracks the traversal position inside of the `try` statement. This is
  507. * used to help determine the context necessary to create paths because
  508. * a `try` statement may or may not have `catch` or `finally` blocks,
  509. * and code paths behave differently in those blocks.
  510. * @type {"try"|"catch"|"finally"}
  511. */
  512. this.position = "try";
  513. /**
  514. * If the `try` statement has a `finally` block, this affects how a
  515. * `return` statement behaves in the `try` block. Without `finally`,
  516. * `return` behaves as usual and doesn't require a fork; with `finally`,
  517. * `return` forks into the `finally` block, so we need a fork context
  518. * to track it.
  519. * @type {ForkContext|null}
  520. */
  521. this.returnedForkContext = hasFinalizer
  522. ? ForkContext.newEmpty(forkContext)
  523. : null;
  524. /**
  525. * When a `throw` occurs inside of a `try` block, the code path forks
  526. * into the `catch` or `finally` blocks, and this fork context tracks
  527. * that path.
  528. * @type {ForkContext}
  529. */
  530. this.thrownForkContext = ForkContext.newEmpty(forkContext);
  531. /**
  532. * Indicates if the last segment in the `try` block is reachable.
  533. * @type {boolean}
  534. */
  535. this.lastOfTryIsReachable = false;
  536. /**
  537. * Indicates if the last segment in the `catch` block is reachable.
  538. * @type {boolean}
  539. */
  540. this.lastOfCatchIsReachable = false;
  541. }
  542. }
  543. //------------------------------------------------------------------------------
  544. // Helpers
  545. //------------------------------------------------------------------------------
  546. /**
  547. * Adds given segments into the `dest` array.
  548. * If the `others` array does not include the given segments, adds to the `all`
  549. * array as well.
  550. *
  551. * This adds only reachable and used segments.
  552. * @param {CodePathSegment[]} dest A destination array (`returnedSegments` or `thrownSegments`).
  553. * @param {CodePathSegment[]} others Another destination array (`returnedSegments` or `thrownSegments`).
  554. * @param {CodePathSegment[]} all The unified destination array (`finalSegments`).
  555. * @param {CodePathSegment[]} segments Segments to add.
  556. * @returns {void}
  557. */
  558. function addToReturnedOrThrown(dest, others, all, segments) {
  559. for (let i = 0; i < segments.length; ++i) {
  560. const segment = segments[i];
  561. dest.push(segment);
  562. if (!others.includes(segment)) {
  563. all.push(segment);
  564. }
  565. }
  566. }
  567. /**
  568. * Gets a loop context for a `continue` statement based on a given label.
  569. * @param {CodePathState} state The state to search within.
  570. * @param {string|null} label The label of a `continue` statement.
  571. * @returns {LoopContext} A loop-context for a `continue` statement.
  572. */
  573. function getContinueContext(state, label) {
  574. if (!label) {
  575. return state.loopContext;
  576. }
  577. let context = state.loopContext;
  578. while (context) {
  579. if (context.label === label) {
  580. return context;
  581. }
  582. context = context.upper;
  583. }
  584. /* c8 ignore next */
  585. return null;
  586. }
  587. /**
  588. * Gets a context for a `break` statement.
  589. * @param {CodePathState} state The state to search within.
  590. * @param {string|null} label The label of a `break` statement.
  591. * @returns {BreakContext} A context for a `break` statement.
  592. */
  593. function getBreakContext(state, label) {
  594. let context = state.breakContext;
  595. while (context) {
  596. if (label ? context.label === label : context.breakable) {
  597. return context;
  598. }
  599. context = context.upper;
  600. }
  601. /* c8 ignore next */
  602. return null;
  603. }
  604. /**
  605. * Gets a context for a `return` statement. There is just one special case:
  606. * if there is a `try` statement with a `finally` block, because that alters
  607. * how `return` behaves; otherwise, this just passes through the given state.
  608. * @param {CodePathState} state The state to search within
  609. * @returns {TryContext|CodePathState} A context for a `return` statement.
  610. */
  611. function getReturnContext(state) {
  612. let context = state.tryContext;
  613. while (context) {
  614. if (context.hasFinalizer && context.position !== "finally") {
  615. return context;
  616. }
  617. context = context.upper;
  618. }
  619. return state;
  620. }
  621. /**
  622. * Gets a context for a `throw` statement. There is just one special case:
  623. * if there is a `try` statement with a `finally` block and we are inside of
  624. * a `catch` because that changes how `throw` behaves; otherwise, this just
  625. * passes through the given state.
  626. * @param {CodePathState} state The state to search within.
  627. * @returns {TryContext|CodePathState} A context for a `throw` statement.
  628. */
  629. function getThrowContext(state) {
  630. let context = state.tryContext;
  631. while (context) {
  632. if (
  633. context.position === "try" ||
  634. (context.hasFinalizer && context.position === "catch")
  635. ) {
  636. return context;
  637. }
  638. context = context.upper;
  639. }
  640. return state;
  641. }
  642. /**
  643. * Removes a given value from a given array.
  644. * @param {any[]} elements An array to remove the specific element.
  645. * @param {any} value The value to be removed.
  646. * @returns {void}
  647. */
  648. function removeFromArray(elements, value) {
  649. elements.splice(elements.indexOf(value), 1);
  650. }
  651. /**
  652. * Disconnect given segments.
  653. *
  654. * This is used in a process for switch statements.
  655. * If there is the "default" chunk before other cases, the order is different
  656. * between node's and running's.
  657. * @param {CodePathSegment[]} prevSegments Forward segments to disconnect.
  658. * @param {CodePathSegment[]} nextSegments Backward segments to disconnect.
  659. * @returns {void}
  660. */
  661. function disconnectSegments(prevSegments, nextSegments) {
  662. for (let i = 0; i < prevSegments.length; ++i) {
  663. const prevSegment = prevSegments[i];
  664. const nextSegment = nextSegments[i];
  665. removeFromArray(prevSegment.nextSegments, nextSegment);
  666. removeFromArray(prevSegment.allNextSegments, nextSegment);
  667. removeFromArray(nextSegment.prevSegments, prevSegment);
  668. removeFromArray(nextSegment.allPrevSegments, prevSegment);
  669. }
  670. }
  671. /**
  672. * Creates looping path between two arrays of segments, ensuring that there are
  673. * paths going between matching segments in the arrays.
  674. * @param {CodePathState} state The state to operate on.
  675. * @param {CodePathSegment[]} unflattenedFromSegments Segments which are source.
  676. * @param {CodePathSegment[]} unflattenedToSegments Segments which are destination.
  677. * @returns {void}
  678. */
  679. function makeLooped(state, unflattenedFromSegments, unflattenedToSegments) {
  680. const fromSegments = CodePathSegment.flattenUnusedSegments(
  681. unflattenedFromSegments,
  682. );
  683. const toSegments = CodePathSegment.flattenUnusedSegments(
  684. unflattenedToSegments,
  685. );
  686. const end = Math.min(fromSegments.length, toSegments.length);
  687. /*
  688. * This loop effectively updates a doubly-linked list between two collections
  689. * of segments making sure that segments in the same array indices are
  690. * combined to create a path.
  691. */
  692. for (let i = 0; i < end; ++i) {
  693. // get the segments in matching array indices
  694. const fromSegment = fromSegments[i];
  695. const toSegment = toSegments[i];
  696. /*
  697. * If the destination segment is reachable, then create a path from the
  698. * source segment to the destination segment.
  699. */
  700. if (toSegment.reachable) {
  701. fromSegment.nextSegments.push(toSegment);
  702. }
  703. /*
  704. * If the source segment is reachable, then create a path from the
  705. * destination segment back to the source segment.
  706. */
  707. if (fromSegment.reachable) {
  708. toSegment.prevSegments.push(fromSegment);
  709. }
  710. /*
  711. * Also update the arrays that don't care if the segments are reachable
  712. * or not. This should always happen regardless of anything else.
  713. */
  714. fromSegment.allNextSegments.push(toSegment);
  715. toSegment.allPrevSegments.push(fromSegment);
  716. /*
  717. * If the destination segment has at least two previous segments in its
  718. * path then that means there was one previous segment before this iteration
  719. * of the loop was executed. So, we need to mark the source segment as
  720. * looped.
  721. */
  722. if (toSegment.allPrevSegments.length >= 2) {
  723. CodePathSegment.markPrevSegmentAsLooped(toSegment, fromSegment);
  724. }
  725. // let the code path analyzer know that there's been a loop created
  726. state.notifyLooped(fromSegment, toSegment);
  727. }
  728. }
  729. /**
  730. * Finalizes segments of `test` chunk of a ForStatement.
  731. *
  732. * - Adds `false` paths to paths which are leaving from the loop.
  733. * - Sets `true` paths to paths which go to the body.
  734. * @param {LoopContext} context A loop context to modify.
  735. * @param {ChoiceContext} choiceContext A choice context of this loop.
  736. * @param {CodePathSegment[]} head The current head paths.
  737. * @returns {void}
  738. */
  739. function finalizeTestSegmentsOfFor(context, choiceContext, head) {
  740. /*
  741. * If this choice context doesn't already contain paths from a
  742. * child context, then add the current head to each potential path.
  743. */
  744. if (!choiceContext.processed) {
  745. choiceContext.trueForkContext.add(head);
  746. choiceContext.falseForkContext.add(head);
  747. choiceContext.nullishForkContext.add(head);
  748. }
  749. /*
  750. * If the test condition isn't a hardcoded truthy value, then `break`
  751. * must follow the same path as if the test condition is false. To represent
  752. * that, we append the path for when the loop test is false (represented by
  753. * `falseForkContext`) to the `brokenForkContext`.
  754. */
  755. if (context.test !== true) {
  756. context.brokenForkContext.addAll(choiceContext.falseForkContext);
  757. }
  758. context.endOfTestSegments = choiceContext.trueForkContext.makeNext(0, -1);
  759. }
  760. //------------------------------------------------------------------------------
  761. // Public Interface
  762. //------------------------------------------------------------------------------
  763. /**
  764. * A class which manages state to analyze code paths.
  765. */
  766. class CodePathState {
  767. /**
  768. * Creates a new instance.
  769. * @param {IdGenerator} idGenerator An id generator to generate id for code
  770. * path segments.
  771. * @param {Function} onLooped A callback function to notify looping.
  772. */
  773. constructor(idGenerator, onLooped) {
  774. /**
  775. * The ID generator to use when creating new segments.
  776. * @type {IdGenerator}
  777. */
  778. this.idGenerator = idGenerator;
  779. /**
  780. * A callback function to call when there is a loop.
  781. * @type {Function}
  782. */
  783. this.notifyLooped = onLooped;
  784. /**
  785. * The root fork context for this state.
  786. * @type {ForkContext}
  787. */
  788. this.forkContext = ForkContext.newRoot(idGenerator);
  789. /**
  790. * Context for logical expressions, conditional expressions, `if` statements,
  791. * and loops.
  792. * @type {ChoiceContext}
  793. */
  794. this.choiceContext = null;
  795. /**
  796. * Context for `switch` statements.
  797. * @type {SwitchContext}
  798. */
  799. this.switchContext = null;
  800. /**
  801. * Context for `try` statements.
  802. * @type {TryContext}
  803. */
  804. this.tryContext = null;
  805. /**
  806. * Context for loop statements.
  807. * @type {LoopContext}
  808. */
  809. this.loopContext = null;
  810. /**
  811. * Context for `break` statements.
  812. * @type {BreakContext}
  813. */
  814. this.breakContext = null;
  815. /**
  816. * Context for `ChainExpression` nodes.
  817. * @type {ChainContext}
  818. */
  819. this.chainContext = null;
  820. /**
  821. * An array that tracks the current segments in the state. The array
  822. * starts empty and segments are added with each `onCodePathSegmentStart`
  823. * event and removed with each `onCodePathSegmentEnd` event. Effectively,
  824. * this is tracking the code path segment traversal as the state is
  825. * modified.
  826. * @type {Array<CodePathSegment>}
  827. */
  828. this.currentSegments = [];
  829. /**
  830. * Tracks the starting segment for this path. This value never changes.
  831. * @type {CodePathSegment}
  832. */
  833. this.initialSegment = this.forkContext.head[0];
  834. /**
  835. * The final segments of the code path which are either `return` or `throw`.
  836. * This is a union of the segments in `returnedForkContext` and `thrownForkContext`.
  837. * @type {Array<CodePathSegment>}
  838. */
  839. this.finalSegments = [];
  840. /**
  841. * The final segments of the code path which are `return`. These
  842. * segments are also contained in `finalSegments`.
  843. * @type {Array<CodePathSegment>}
  844. */
  845. this.returnedForkContext = [];
  846. /**
  847. * The final segments of the code path which are `throw`. These
  848. * segments are also contained in `finalSegments`.
  849. * @type {Array<CodePathSegment>}
  850. */
  851. this.thrownForkContext = [];
  852. /*
  853. * We add an `add` method so that these look more like fork contexts and
  854. * can be used interchangeably when a fork context is needed to add more
  855. * segments to a path.
  856. *
  857. * Ultimately, we want anything added to `returned` or `thrown` to also
  858. * be added to `final`. We only add reachable and used segments to these
  859. * arrays.
  860. */
  861. const final = this.finalSegments;
  862. const returned = this.returnedForkContext;
  863. const thrown = this.thrownForkContext;
  864. returned.add = addToReturnedOrThrown.bind(
  865. null,
  866. returned,
  867. thrown,
  868. final,
  869. );
  870. thrown.add = addToReturnedOrThrown.bind(null, thrown, returned, final);
  871. }
  872. /**
  873. * A passthrough property exposing the current pointer as part of the API.
  874. * @type {CodePathSegment[]}
  875. */
  876. get headSegments() {
  877. return this.forkContext.head;
  878. }
  879. /**
  880. * The parent forking context.
  881. * This is used for the root of new forks.
  882. * @type {ForkContext}
  883. */
  884. get parentForkContext() {
  885. const current = this.forkContext;
  886. return current && current.upper;
  887. }
  888. /**
  889. * Creates and stacks new forking context.
  890. * @param {boolean} forkLeavingPath A flag which shows being in a
  891. * "finally" block.
  892. * @returns {ForkContext} The created context.
  893. */
  894. pushForkContext(forkLeavingPath) {
  895. this.forkContext = ForkContext.newEmpty(
  896. this.forkContext,
  897. forkLeavingPath,
  898. );
  899. return this.forkContext;
  900. }
  901. /**
  902. * Pops and merges the last forking context.
  903. * @returns {ForkContext} The last context.
  904. */
  905. popForkContext() {
  906. const lastContext = this.forkContext;
  907. this.forkContext = lastContext.upper;
  908. this.forkContext.replaceHead(lastContext.makeNext(0, -1));
  909. return lastContext;
  910. }
  911. /**
  912. * Creates a new path.
  913. * @returns {void}
  914. */
  915. forkPath() {
  916. this.forkContext.add(this.parentForkContext.makeNext(-1, -1));
  917. }
  918. /**
  919. * Creates a bypass path.
  920. * This is used for such as IfStatement which does not have "else" chunk.
  921. * @returns {void}
  922. */
  923. forkBypassPath() {
  924. this.forkContext.add(this.parentForkContext.head);
  925. }
  926. //--------------------------------------------------------------------------
  927. // ConditionalExpression, LogicalExpression, IfStatement
  928. //--------------------------------------------------------------------------
  929. /**
  930. * Creates a context for ConditionalExpression, LogicalExpression, AssignmentExpression (logical assignments only),
  931. * IfStatement, WhileStatement, DoWhileStatement, or ForStatement.
  932. *
  933. * LogicalExpressions have cases that it goes different paths between the
  934. * `true` case and the `false` case.
  935. *
  936. * For Example:
  937. *
  938. * if (a || b) {
  939. * foo();
  940. * } else {
  941. * bar();
  942. * }
  943. *
  944. * In this case, `b` is evaluated always in the code path of the `else`
  945. * block, but it's not so in the code path of the `if` block.
  946. * So there are 3 paths.
  947. *
  948. * a -> foo();
  949. * a -> b -> foo();
  950. * a -> b -> bar();
  951. * @param {string} kind A kind string.
  952. * If the new context is LogicalExpression's or AssignmentExpression's, this is `"&&"` or `"||"` or `"??"`.
  953. * If it's IfStatement's or ConditionalExpression's, this is `"test"`.
  954. * Otherwise, this is `"loop"`.
  955. * @param {boolean} isForkingAsResult Indicates if the result of the choice
  956. * creates a fork.
  957. * @returns {void}
  958. */
  959. pushChoiceContext(kind, isForkingAsResult) {
  960. this.choiceContext = new ChoiceContext(
  961. this.choiceContext,
  962. kind,
  963. isForkingAsResult,
  964. this.forkContext,
  965. );
  966. }
  967. /**
  968. * Pops the last choice context and finalizes it.
  969. * This is called upon leaving a node that represents a choice.
  970. * @throws {Error} (Unreachable.)
  971. * @returns {ChoiceContext} The popped context.
  972. */
  973. popChoiceContext() {
  974. const poppedChoiceContext = this.choiceContext;
  975. const forkContext = this.forkContext;
  976. const head = forkContext.head;
  977. this.choiceContext = poppedChoiceContext.upper;
  978. switch (poppedChoiceContext.kind) {
  979. case "&&":
  980. case "||":
  981. case "??":
  982. /*
  983. * The `head` are the path of the right-hand operand.
  984. * If we haven't previously added segments from child contexts,
  985. * then we add these segments to all possible forks.
  986. */
  987. if (!poppedChoiceContext.processed) {
  988. poppedChoiceContext.trueForkContext.add(head);
  989. poppedChoiceContext.falseForkContext.add(head);
  990. poppedChoiceContext.nullishForkContext.add(head);
  991. }
  992. /*
  993. * If this context is the left (test) expression for another choice
  994. * context, such as `a || b` in the expression `a || b || c`,
  995. * then we take the segments for this context and move them up
  996. * to the parent context.
  997. */
  998. if (poppedChoiceContext.isForkingAsResult) {
  999. const parentContext = this.choiceContext;
  1000. parentContext.trueForkContext.addAll(
  1001. poppedChoiceContext.trueForkContext,
  1002. );
  1003. parentContext.falseForkContext.addAll(
  1004. poppedChoiceContext.falseForkContext,
  1005. );
  1006. parentContext.nullishForkContext.addAll(
  1007. poppedChoiceContext.nullishForkContext,
  1008. );
  1009. parentContext.processed = true;
  1010. // Exit early so we don't collapse all paths into one.
  1011. return poppedChoiceContext;
  1012. }
  1013. break;
  1014. case "test":
  1015. if (!poppedChoiceContext.processed) {
  1016. /*
  1017. * The head segments are the path of the `if` block here.
  1018. * Updates the `true` path with the end of the `if` block.
  1019. */
  1020. poppedChoiceContext.trueForkContext.clear();
  1021. poppedChoiceContext.trueForkContext.add(head);
  1022. } else {
  1023. /*
  1024. * The head segments are the path of the `else` block here.
  1025. * Updates the `false` path with the end of the `else`
  1026. * block.
  1027. */
  1028. poppedChoiceContext.falseForkContext.clear();
  1029. poppedChoiceContext.falseForkContext.add(head);
  1030. }
  1031. break;
  1032. case "loop":
  1033. /*
  1034. * Loops are addressed in `popLoopContext()` so just return
  1035. * the context without modification.
  1036. */
  1037. return poppedChoiceContext;
  1038. /* c8 ignore next */
  1039. default:
  1040. throw new Error("unreachable");
  1041. }
  1042. /*
  1043. * Merge the true path with the false path to create a single path.
  1044. */
  1045. const combinedForkContext = poppedChoiceContext.trueForkContext;
  1046. combinedForkContext.addAll(poppedChoiceContext.falseForkContext);
  1047. forkContext.replaceHead(combinedForkContext.makeNext(0, -1));
  1048. return poppedChoiceContext;
  1049. }
  1050. /**
  1051. * Creates a code path segment to represent right-hand operand of a logical
  1052. * expression.
  1053. * This is called in the preprocessing phase when entering a node.
  1054. * @throws {Error} (Unreachable.)
  1055. * @returns {void}
  1056. */
  1057. makeLogicalRight() {
  1058. const currentChoiceContext = this.choiceContext;
  1059. const forkContext = this.forkContext;
  1060. if (currentChoiceContext.processed) {
  1061. /*
  1062. * This context was already assigned segments from a child
  1063. * choice context. In this case, we are concerned only about
  1064. * the path that does not short-circuit and so ends up on the
  1065. * right-hand operand of the logical expression.
  1066. */
  1067. let prevForkContext;
  1068. switch (currentChoiceContext.kind) {
  1069. case "&&": // if true then go to the right-hand side.
  1070. prevForkContext = currentChoiceContext.trueForkContext;
  1071. break;
  1072. case "||": // if false then go to the right-hand side.
  1073. prevForkContext = currentChoiceContext.falseForkContext;
  1074. break;
  1075. case "??": // Both true/false can short-circuit, so needs the third path to go to the right-hand side. That's nullishForkContext.
  1076. prevForkContext = currentChoiceContext.nullishForkContext;
  1077. break;
  1078. default:
  1079. throw new Error("unreachable");
  1080. }
  1081. /*
  1082. * Create the segment for the right-hand operand of the logical expression
  1083. * and adjust the fork context pointer to point there. The right-hand segment
  1084. * is added at the end of all segments in `prevForkContext`.
  1085. */
  1086. forkContext.replaceHead(prevForkContext.makeNext(0, -1));
  1087. /*
  1088. * We no longer need this list of segments.
  1089. *
  1090. * Reset `processed` because we've removed the segments from the child
  1091. * choice context. This allows `popChoiceContext()` to continue adding
  1092. * segments later.
  1093. */
  1094. prevForkContext.clear();
  1095. currentChoiceContext.processed = false;
  1096. } else {
  1097. /*
  1098. * This choice context was not assigned segments from a child
  1099. * choice context, which means that it's a terminal logical
  1100. * expression.
  1101. *
  1102. * `head` is the segments for the left-hand operand of the
  1103. * logical expression.
  1104. *
  1105. * Each of the fork contexts below are empty at this point. We choose
  1106. * the path(s) that will short-circuit and add the segment for the
  1107. * left-hand operand to it. Ultimately, this will be the only segment
  1108. * in that path due to the short-circuting, so we are just seeding
  1109. * these paths to start.
  1110. */
  1111. switch (currentChoiceContext.kind) {
  1112. case "&&":
  1113. /*
  1114. * In most contexts, when a && expression evaluates to false,
  1115. * it short circuits, so we need to account for that by setting
  1116. * the `falseForkContext` to the left operand.
  1117. *
  1118. * When a && expression is the left-hand operand for a ??
  1119. * expression, such as `(a && b) ?? c`, a nullish value will
  1120. * also short-circuit in a different way than a false value,
  1121. * so we also set the `nullishForkContext` to the left operand.
  1122. * This path is only used with a ?? expression and is thrown
  1123. * away for any other type of logical expression, so it's safe
  1124. * to always add.
  1125. */
  1126. currentChoiceContext.falseForkContext.add(forkContext.head);
  1127. currentChoiceContext.nullishForkContext.add(
  1128. forkContext.head,
  1129. );
  1130. break;
  1131. case "||": // the true path can short-circuit.
  1132. currentChoiceContext.trueForkContext.add(forkContext.head);
  1133. break;
  1134. case "??": // both can short-circuit.
  1135. currentChoiceContext.trueForkContext.add(forkContext.head);
  1136. currentChoiceContext.falseForkContext.add(forkContext.head);
  1137. break;
  1138. default:
  1139. throw new Error("unreachable");
  1140. }
  1141. /*
  1142. * Create the segment for the right-hand operand of the logical expression
  1143. * and adjust the fork context pointer to point there.
  1144. */
  1145. forkContext.replaceHead(forkContext.makeNext(-1, -1));
  1146. }
  1147. }
  1148. /**
  1149. * Makes a code path segment of the `if` block.
  1150. * @returns {void}
  1151. */
  1152. makeIfConsequent() {
  1153. const context = this.choiceContext;
  1154. const forkContext = this.forkContext;
  1155. /*
  1156. * If any result were not transferred from child contexts,
  1157. * this sets the head segments to both cases.
  1158. * The head segments are the path of the test expression.
  1159. */
  1160. if (!context.processed) {
  1161. context.trueForkContext.add(forkContext.head);
  1162. context.falseForkContext.add(forkContext.head);
  1163. context.nullishForkContext.add(forkContext.head);
  1164. }
  1165. context.processed = false;
  1166. // Creates new path from the `true` case.
  1167. forkContext.replaceHead(context.trueForkContext.makeNext(0, -1));
  1168. }
  1169. /**
  1170. * Makes a code path segment of the `else` block.
  1171. * @returns {void}
  1172. */
  1173. makeIfAlternate() {
  1174. const context = this.choiceContext;
  1175. const forkContext = this.forkContext;
  1176. /*
  1177. * The head segments are the path of the `if` block.
  1178. * Updates the `true` path with the end of the `if` block.
  1179. */
  1180. context.trueForkContext.clear();
  1181. context.trueForkContext.add(forkContext.head);
  1182. context.processed = true;
  1183. // Creates new path from the `false` case.
  1184. forkContext.replaceHead(context.falseForkContext.makeNext(0, -1));
  1185. }
  1186. //--------------------------------------------------------------------------
  1187. // ChainExpression
  1188. //--------------------------------------------------------------------------
  1189. /**
  1190. * Pushes a new `ChainExpression` context to the stack. This method is
  1191. * called when entering a `ChainExpression` node. A chain context is used to
  1192. * count forking in the optional chain then merge them on the exiting from the
  1193. * `ChainExpression` node.
  1194. * @returns {void}
  1195. */
  1196. pushChainContext() {
  1197. this.chainContext = new ChainContext(this.chainContext);
  1198. }
  1199. /**
  1200. * Pop a `ChainExpression` context from the stack. This method is called on
  1201. * exiting from each `ChainExpression` node. This merges all forks of the
  1202. * last optional chaining.
  1203. * @returns {void}
  1204. */
  1205. popChainContext() {
  1206. const context = this.chainContext;
  1207. this.chainContext = context.upper;
  1208. // pop all choice contexts of this.
  1209. for (let i = context.choiceContextCount; i > 0; --i) {
  1210. this.popChoiceContext();
  1211. }
  1212. }
  1213. /**
  1214. * Create a choice context for optional access.
  1215. * This method is called on entering to each `(Call|Member)Expression[optional=true]` node.
  1216. * This creates a choice context as similar to `LogicalExpression[operator="??"]` node.
  1217. * @returns {void}
  1218. */
  1219. makeOptionalNode() {
  1220. if (this.chainContext) {
  1221. this.chainContext.choiceContextCount += 1;
  1222. this.pushChoiceContext("??", false);
  1223. }
  1224. }
  1225. /**
  1226. * Create a fork.
  1227. * This method is called on entering to the `arguments|property` property of each `(Call|Member)Expression` node.
  1228. * @returns {void}
  1229. */
  1230. makeOptionalRight() {
  1231. if (this.chainContext) {
  1232. this.makeLogicalRight();
  1233. }
  1234. }
  1235. //--------------------------------------------------------------------------
  1236. // SwitchStatement
  1237. //--------------------------------------------------------------------------
  1238. /**
  1239. * Creates a context object of SwitchStatement and stacks it.
  1240. * @param {boolean} hasCase `true` if the switch statement has one or more
  1241. * case parts.
  1242. * @param {string|null} label The label text.
  1243. * @returns {void}
  1244. */
  1245. pushSwitchContext(hasCase, label) {
  1246. this.switchContext = new SwitchContext(this.switchContext, hasCase);
  1247. this.pushBreakContext(true, label);
  1248. }
  1249. /**
  1250. * Pops the last context of SwitchStatement and finalizes it.
  1251. *
  1252. * - Disposes all forking stack for `case` and `default`.
  1253. * - Creates the next code path segment from `context.brokenForkContext`.
  1254. * - If the last `SwitchCase` node is not a `default` part, creates a path
  1255. * to the `default` body.
  1256. * @returns {void}
  1257. */
  1258. popSwitchContext() {
  1259. const context = this.switchContext;
  1260. this.switchContext = context.upper;
  1261. const forkContext = this.forkContext;
  1262. const brokenForkContext = this.popBreakContext().brokenForkContext;
  1263. if (context.forkCount === 0) {
  1264. /*
  1265. * When there is only one `default` chunk and there is one or more
  1266. * `break` statements, even if forks are nothing, it needs to merge
  1267. * those.
  1268. */
  1269. if (!brokenForkContext.empty) {
  1270. brokenForkContext.add(forkContext.makeNext(-1, -1));
  1271. forkContext.replaceHead(brokenForkContext.makeNext(0, -1));
  1272. }
  1273. return;
  1274. }
  1275. const lastSegments = forkContext.head;
  1276. this.forkBypassPath();
  1277. const lastCaseSegments = forkContext.head;
  1278. /*
  1279. * `brokenForkContext` is used to make the next segment.
  1280. * It must add the last segment into `brokenForkContext`.
  1281. */
  1282. brokenForkContext.add(lastSegments);
  1283. /*
  1284. * Any value that doesn't match a `case` test should flow to the default
  1285. * case. That happens normally when the default case is last in the `switch`,
  1286. * but if it's not, we need to rewire some of the paths to be correct.
  1287. */
  1288. if (!context.lastIsDefault) {
  1289. if (context.defaultBodySegments) {
  1290. /*
  1291. * There is a non-empty default case, so remove the path from the `default`
  1292. * label to its body for an accurate representation.
  1293. */
  1294. disconnectSegments(
  1295. context.defaultSegments,
  1296. context.defaultBodySegments,
  1297. );
  1298. /*
  1299. * Connect the path from the last non-default case to the body of the
  1300. * default case.
  1301. */
  1302. makeLooped(this, lastCaseSegments, context.defaultBodySegments);
  1303. } else {
  1304. /*
  1305. * There is no default case, so we treat this as if the last case
  1306. * had a `break` in it.
  1307. */
  1308. brokenForkContext.add(lastCaseSegments);
  1309. }
  1310. }
  1311. // Traverse up to the original fork context for the `switch` statement
  1312. for (let i = 0; i < context.forkCount; ++i) {
  1313. this.forkContext = this.forkContext.upper;
  1314. }
  1315. /*
  1316. * Creates a path from all `brokenForkContext` paths.
  1317. * This is a path after `switch` statement.
  1318. */
  1319. this.forkContext.replaceHead(brokenForkContext.makeNext(0, -1));
  1320. }
  1321. /**
  1322. * Makes a code path segment for a `SwitchCase` node.
  1323. * @param {boolean} isCaseBodyEmpty `true` if the body is empty.
  1324. * @param {boolean} isDefaultCase `true` if the body is the default case.
  1325. * @returns {void}
  1326. */
  1327. makeSwitchCaseBody(isCaseBodyEmpty, isDefaultCase) {
  1328. const context = this.switchContext;
  1329. if (!context.hasCase) {
  1330. return;
  1331. }
  1332. /*
  1333. * Merge forks.
  1334. * The parent fork context has two segments.
  1335. * Those are from the current `case` and the body of the previous case.
  1336. */
  1337. const parentForkContext = this.forkContext;
  1338. const forkContext = this.pushForkContext();
  1339. forkContext.add(parentForkContext.makeNext(0, -1));
  1340. /*
  1341. * Add information about the default case.
  1342. *
  1343. * The purpose of this is to identify the starting segments for the
  1344. * default case to make sure there is a path there.
  1345. */
  1346. if (isDefaultCase) {
  1347. /*
  1348. * This is the default case in the `switch`.
  1349. *
  1350. * We first save the current pointer as `defaultSegments` to point
  1351. * to the `default` keyword.
  1352. */
  1353. context.defaultSegments = parentForkContext.head;
  1354. /*
  1355. * If the body of the case is empty then we just set
  1356. * `foundEmptyDefault` to true; otherwise, we save a reference
  1357. * to the current pointer as `defaultBodySegments`.
  1358. */
  1359. if (isCaseBodyEmpty) {
  1360. context.foundEmptyDefault = true;
  1361. } else {
  1362. context.defaultBodySegments = forkContext.head;
  1363. }
  1364. } else {
  1365. /*
  1366. * This is not the default case in the `switch`.
  1367. *
  1368. * If it's not empty and there is already an empty default case found,
  1369. * that means the default case actually comes before this case,
  1370. * and that it will fall through to this case. So, we can now
  1371. * ignore the previous default case (reset `foundEmptyDefault` to false)
  1372. * and set `defaultBodySegments` to the current segments because this is
  1373. * effectively the new default case.
  1374. */
  1375. if (!isCaseBodyEmpty && context.foundEmptyDefault) {
  1376. context.foundEmptyDefault = false;
  1377. context.defaultBodySegments = forkContext.head;
  1378. }
  1379. }
  1380. // keep track if the default case ends up last
  1381. context.lastIsDefault = isDefaultCase;
  1382. context.forkCount += 1;
  1383. }
  1384. //--------------------------------------------------------------------------
  1385. // TryStatement
  1386. //--------------------------------------------------------------------------
  1387. /**
  1388. * Creates a context object of TryStatement and stacks it.
  1389. * @param {boolean} hasFinalizer `true` if the try statement has a
  1390. * `finally` block.
  1391. * @returns {void}
  1392. */
  1393. pushTryContext(hasFinalizer) {
  1394. this.tryContext = new TryContext(
  1395. this.tryContext,
  1396. hasFinalizer,
  1397. this.forkContext,
  1398. );
  1399. }
  1400. /**
  1401. * Pops the last context of TryStatement and finalizes it.
  1402. * @returns {void}
  1403. */
  1404. popTryContext() {
  1405. const context = this.tryContext;
  1406. this.tryContext = context.upper;
  1407. /*
  1408. * If we're inside the `catch` block, that means there is no `finally`,
  1409. * so we can process the `try` and `catch` blocks the simple way and
  1410. * merge their two paths.
  1411. */
  1412. if (context.position === "catch") {
  1413. this.popForkContext();
  1414. return;
  1415. }
  1416. /*
  1417. * The following process is executed only when there is a `finally`
  1418. * block.
  1419. */
  1420. const originalReturnedForkContext = context.returnedForkContext;
  1421. const originalThrownForkContext = context.thrownForkContext;
  1422. // no `return` or `throw` in `try` or `catch` so there's nothing left to do
  1423. if (
  1424. originalReturnedForkContext.empty &&
  1425. originalThrownForkContext.empty
  1426. ) {
  1427. return;
  1428. }
  1429. /*
  1430. * The following process is executed only when there is a `finally`
  1431. * block and there was a `return` or `throw` in the `try` or `catch`
  1432. * blocks.
  1433. */
  1434. // Separate head to normal paths and leaving paths.
  1435. const headSegments = this.forkContext.head;
  1436. this.forkContext = this.forkContext.upper;
  1437. const normalSegments = headSegments.slice(
  1438. 0,
  1439. (headSegments.length / 2) | 0,
  1440. );
  1441. const leavingSegments = headSegments.slice(
  1442. (headSegments.length / 2) | 0,
  1443. );
  1444. // Forwards the leaving path to upper contexts.
  1445. if (!originalReturnedForkContext.empty) {
  1446. getReturnContext(this).returnedForkContext.add(leavingSegments);
  1447. }
  1448. if (!originalThrownForkContext.empty) {
  1449. getThrowContext(this).thrownForkContext.add(leavingSegments);
  1450. }
  1451. // Sets the normal path as the next.
  1452. this.forkContext.replaceHead(normalSegments);
  1453. /*
  1454. * If both paths of the `try` block and the `catch` block are
  1455. * unreachable, the next path becomes unreachable as well.
  1456. */
  1457. if (!context.lastOfTryIsReachable && !context.lastOfCatchIsReachable) {
  1458. this.forkContext.makeUnreachable();
  1459. }
  1460. }
  1461. /**
  1462. * Makes a code path segment for a `catch` block.
  1463. * @returns {void}
  1464. */
  1465. makeCatchBlock() {
  1466. const context = this.tryContext;
  1467. const forkContext = this.forkContext;
  1468. const originalThrownForkContext = context.thrownForkContext;
  1469. /*
  1470. * We are now in a catch block so we need to update the context
  1471. * with that information. This includes creating a new fork
  1472. * context in case we encounter any `throw` statements here.
  1473. */
  1474. context.position = "catch";
  1475. context.thrownForkContext = ForkContext.newEmpty(forkContext);
  1476. context.lastOfTryIsReachable = forkContext.reachable;
  1477. // Merge the thrown paths from the `try` and `catch` blocks
  1478. originalThrownForkContext.add(forkContext.head);
  1479. const thrownSegments = originalThrownForkContext.makeNext(0, -1);
  1480. // Fork to a bypass and the merged thrown path.
  1481. this.pushForkContext();
  1482. this.forkBypassPath();
  1483. this.forkContext.add(thrownSegments);
  1484. }
  1485. /**
  1486. * Makes a code path segment for a `finally` block.
  1487. *
  1488. * In the `finally` block, parallel paths are created. The parallel paths
  1489. * are used as leaving-paths. The leaving-paths are paths from `return`
  1490. * statements and `throw` statements in a `try` block or a `catch` block.
  1491. * @returns {void}
  1492. */
  1493. makeFinallyBlock() {
  1494. const context = this.tryContext;
  1495. let forkContext = this.forkContext;
  1496. const originalReturnedForkContext = context.returnedForkContext;
  1497. const originalThrownForContext = context.thrownForkContext;
  1498. const headOfLeavingSegments = forkContext.head;
  1499. // Update state.
  1500. if (context.position === "catch") {
  1501. // Merges two paths from the `try` block and `catch` block.
  1502. this.popForkContext();
  1503. forkContext = this.forkContext;
  1504. context.lastOfCatchIsReachable = forkContext.reachable;
  1505. } else {
  1506. context.lastOfTryIsReachable = forkContext.reachable;
  1507. }
  1508. context.position = "finally";
  1509. /*
  1510. * If there was no `return` or `throw` in either the `try` or `catch`
  1511. * blocks, then there's no further code paths to create for `finally`.
  1512. */
  1513. if (
  1514. originalReturnedForkContext.empty &&
  1515. originalThrownForContext.empty
  1516. ) {
  1517. // This path does not leave.
  1518. return;
  1519. }
  1520. /*
  1521. * Create a parallel segment from merging returned and thrown.
  1522. * This segment will leave at the end of this `finally` block.
  1523. */
  1524. const segments = forkContext.makeNext(-1, -1);
  1525. for (let i = 0; i < forkContext.count; ++i) {
  1526. const prevSegsOfLeavingSegment = [headOfLeavingSegments[i]];
  1527. for (
  1528. let j = 0;
  1529. j < originalReturnedForkContext.segmentsList.length;
  1530. ++j
  1531. ) {
  1532. prevSegsOfLeavingSegment.push(
  1533. originalReturnedForkContext.segmentsList[j][i],
  1534. );
  1535. }
  1536. for (
  1537. let j = 0;
  1538. j < originalThrownForContext.segmentsList.length;
  1539. ++j
  1540. ) {
  1541. prevSegsOfLeavingSegment.push(
  1542. originalThrownForContext.segmentsList[j][i],
  1543. );
  1544. }
  1545. segments.push(
  1546. CodePathSegment.newNext(
  1547. this.idGenerator.next(),
  1548. prevSegsOfLeavingSegment,
  1549. ),
  1550. );
  1551. }
  1552. this.pushForkContext(true);
  1553. this.forkContext.add(segments);
  1554. }
  1555. /**
  1556. * Makes a code path segment from the first throwable node to the `catch`
  1557. * block or the `finally` block.
  1558. * @returns {void}
  1559. */
  1560. makeFirstThrowablePathInTryBlock() {
  1561. const forkContext = this.forkContext;
  1562. if (!forkContext.reachable) {
  1563. return;
  1564. }
  1565. const context = getThrowContext(this);
  1566. if (
  1567. context === this ||
  1568. context.position !== "try" ||
  1569. !context.thrownForkContext.empty
  1570. ) {
  1571. return;
  1572. }
  1573. context.thrownForkContext.add(forkContext.head);
  1574. forkContext.replaceHead(forkContext.makeNext(-1, -1));
  1575. }
  1576. //--------------------------------------------------------------------------
  1577. // Loop Statements
  1578. //--------------------------------------------------------------------------
  1579. /**
  1580. * Creates a context object of a loop statement and stacks it.
  1581. * @param {string} type The type of the node which was triggered. One of
  1582. * `WhileStatement`, `DoWhileStatement`, `ForStatement`, `ForInStatement`,
  1583. * and `ForStatement`.
  1584. * @param {string|null} label A label of the node which was triggered.
  1585. * @throws {Error} (Unreachable - unknown type.)
  1586. * @returns {void}
  1587. */
  1588. pushLoopContext(type, label) {
  1589. const forkContext = this.forkContext;
  1590. // All loops need a path to account for `break` statements
  1591. const breakContext = this.pushBreakContext(true, label);
  1592. switch (type) {
  1593. case "WhileStatement":
  1594. this.pushChoiceContext("loop", false);
  1595. this.loopContext = new WhileLoopContext(
  1596. this.loopContext,
  1597. label,
  1598. breakContext,
  1599. );
  1600. break;
  1601. case "DoWhileStatement":
  1602. this.pushChoiceContext("loop", false);
  1603. this.loopContext = new DoWhileLoopContext(
  1604. this.loopContext,
  1605. label,
  1606. breakContext,
  1607. forkContext,
  1608. );
  1609. break;
  1610. case "ForStatement":
  1611. this.pushChoiceContext("loop", false);
  1612. this.loopContext = new ForLoopContext(
  1613. this.loopContext,
  1614. label,
  1615. breakContext,
  1616. );
  1617. break;
  1618. case "ForInStatement":
  1619. this.loopContext = new ForInLoopContext(
  1620. this.loopContext,
  1621. label,
  1622. breakContext,
  1623. );
  1624. break;
  1625. case "ForOfStatement":
  1626. this.loopContext = new ForOfLoopContext(
  1627. this.loopContext,
  1628. label,
  1629. breakContext,
  1630. );
  1631. break;
  1632. /* c8 ignore next */
  1633. default:
  1634. throw new Error(`unknown type: "${type}"`);
  1635. }
  1636. }
  1637. /**
  1638. * Pops the last context of a loop statement and finalizes it.
  1639. * @throws {Error} (Unreachable - unknown type.)
  1640. * @returns {void}
  1641. */
  1642. popLoopContext() {
  1643. const context = this.loopContext;
  1644. this.loopContext = context.upper;
  1645. const forkContext = this.forkContext;
  1646. const brokenForkContext = this.popBreakContext().brokenForkContext;
  1647. // Creates a looped path.
  1648. switch (context.type) {
  1649. case "WhileStatement":
  1650. case "ForStatement":
  1651. this.popChoiceContext();
  1652. /*
  1653. * Creates the path from the end of the loop body up to the
  1654. * location where `continue` would jump to.
  1655. */
  1656. makeLooped(
  1657. this,
  1658. forkContext.head,
  1659. context.continueDestSegments,
  1660. );
  1661. break;
  1662. case "DoWhileStatement": {
  1663. const choiceContext = this.popChoiceContext();
  1664. if (!choiceContext.processed) {
  1665. choiceContext.trueForkContext.add(forkContext.head);
  1666. choiceContext.falseForkContext.add(forkContext.head);
  1667. }
  1668. /*
  1669. * If this isn't a hardcoded `true` condition, then `break`
  1670. * should continue down the path as if the condition evaluated
  1671. * to false.
  1672. */
  1673. if (context.test !== true) {
  1674. brokenForkContext.addAll(choiceContext.falseForkContext);
  1675. }
  1676. /*
  1677. * When the condition is true, the loop continues back to the top,
  1678. * so create a path from each possible true condition back to the
  1679. * top of the loop.
  1680. */
  1681. const segmentsList = choiceContext.trueForkContext.segmentsList;
  1682. for (let i = 0; i < segmentsList.length; ++i) {
  1683. makeLooped(this, segmentsList[i], context.entrySegments);
  1684. }
  1685. break;
  1686. }
  1687. case "ForInStatement":
  1688. case "ForOfStatement":
  1689. brokenForkContext.add(forkContext.head);
  1690. /*
  1691. * Creates the path from the end of the loop body up to the
  1692. * left expression (left of `in` or `of`) of the loop.
  1693. */
  1694. makeLooped(this, forkContext.head, context.leftSegments);
  1695. break;
  1696. /* c8 ignore next */
  1697. default:
  1698. throw new Error("unreachable");
  1699. }
  1700. /*
  1701. * If there wasn't a `break` statement in the loop, then we're at
  1702. * the end of the loop's path, so we make an unreachable segment
  1703. * to mark that.
  1704. *
  1705. * If there was a `break` statement, then we continue on into the
  1706. * `brokenForkContext`.
  1707. */
  1708. if (brokenForkContext.empty) {
  1709. forkContext.replaceHead(forkContext.makeUnreachable(-1, -1));
  1710. } else {
  1711. forkContext.replaceHead(brokenForkContext.makeNext(0, -1));
  1712. }
  1713. }
  1714. /**
  1715. * Makes a code path segment for the test part of a WhileStatement.
  1716. * @param {boolean|undefined} test The test value (only when constant).
  1717. * @returns {void}
  1718. */
  1719. makeWhileTest(test) {
  1720. const context = this.loopContext;
  1721. const forkContext = this.forkContext;
  1722. const testSegments = forkContext.makeNext(0, -1);
  1723. // Update state.
  1724. context.test = test;
  1725. context.continueDestSegments = testSegments;
  1726. forkContext.replaceHead(testSegments);
  1727. }
  1728. /**
  1729. * Makes a code path segment for the body part of a WhileStatement.
  1730. * @returns {void}
  1731. */
  1732. makeWhileBody() {
  1733. const context = this.loopContext;
  1734. const choiceContext = this.choiceContext;
  1735. const forkContext = this.forkContext;
  1736. if (!choiceContext.processed) {
  1737. choiceContext.trueForkContext.add(forkContext.head);
  1738. choiceContext.falseForkContext.add(forkContext.head);
  1739. }
  1740. /*
  1741. * If this isn't a hardcoded `true` condition, then `break`
  1742. * should continue down the path as if the condition evaluated
  1743. * to false.
  1744. */
  1745. if (context.test !== true) {
  1746. context.brokenForkContext.addAll(choiceContext.falseForkContext);
  1747. }
  1748. forkContext.replaceHead(choiceContext.trueForkContext.makeNext(0, -1));
  1749. }
  1750. /**
  1751. * Makes a code path segment for the body part of a DoWhileStatement.
  1752. * @returns {void}
  1753. */
  1754. makeDoWhileBody() {
  1755. const context = this.loopContext;
  1756. const forkContext = this.forkContext;
  1757. const bodySegments = forkContext.makeNext(-1, -1);
  1758. // Update state.
  1759. context.entrySegments = bodySegments;
  1760. forkContext.replaceHead(bodySegments);
  1761. }
  1762. /**
  1763. * Makes a code path segment for the test part of a DoWhileStatement.
  1764. * @param {boolean|undefined} test The test value (only when constant).
  1765. * @returns {void}
  1766. */
  1767. makeDoWhileTest(test) {
  1768. const context = this.loopContext;
  1769. const forkContext = this.forkContext;
  1770. context.test = test;
  1771. /*
  1772. * If there is a `continue` statement in the loop then `continueForkContext`
  1773. * won't be empty. We wire up the path from `continue` to the loop
  1774. * test condition and then continue the traversal in the root fork context.
  1775. */
  1776. if (!context.continueForkContext.empty) {
  1777. context.continueForkContext.add(forkContext.head);
  1778. const testSegments = context.continueForkContext.makeNext(0, -1);
  1779. forkContext.replaceHead(testSegments);
  1780. }
  1781. }
  1782. /**
  1783. * Makes a code path segment for the test part of a ForStatement.
  1784. * @param {boolean|undefined} test The test value (only when constant).
  1785. * @returns {void}
  1786. */
  1787. makeForTest(test) {
  1788. const context = this.loopContext;
  1789. const forkContext = this.forkContext;
  1790. const endOfInitSegments = forkContext.head;
  1791. const testSegments = forkContext.makeNext(-1, -1);
  1792. /*
  1793. * Update the state.
  1794. *
  1795. * The `continueDestSegments` are set to `testSegments` because we
  1796. * don't yet know if there is an update expression in this loop. So,
  1797. * from what we already know at this point, a `continue` statement
  1798. * will jump back to the test expression.
  1799. */
  1800. context.test = test;
  1801. context.endOfInitSegments = endOfInitSegments;
  1802. context.continueDestSegments = context.testSegments = testSegments;
  1803. forkContext.replaceHead(testSegments);
  1804. }
  1805. /**
  1806. * Makes a code path segment for the update part of a ForStatement.
  1807. * @returns {void}
  1808. */
  1809. makeForUpdate() {
  1810. const context = this.loopContext;
  1811. const choiceContext = this.choiceContext;
  1812. const forkContext = this.forkContext;
  1813. // Make the next paths of the test.
  1814. if (context.testSegments) {
  1815. finalizeTestSegmentsOfFor(context, choiceContext, forkContext.head);
  1816. } else {
  1817. context.endOfInitSegments = forkContext.head;
  1818. }
  1819. /*
  1820. * Update the state.
  1821. *
  1822. * The `continueDestSegments` are now set to `updateSegments` because we
  1823. * know there is an update expression in this loop. So, a `continue` statement
  1824. * in the loop will jump to the update expression first, and then to any
  1825. * test expression the loop might have.
  1826. */
  1827. const updateSegments = forkContext.makeDisconnected(-1, -1);
  1828. context.continueDestSegments = context.updateSegments = updateSegments;
  1829. forkContext.replaceHead(updateSegments);
  1830. }
  1831. /**
  1832. * Makes a code path segment for the body part of a ForStatement.
  1833. * @returns {void}
  1834. */
  1835. makeForBody() {
  1836. const context = this.loopContext;
  1837. const choiceContext = this.choiceContext;
  1838. const forkContext = this.forkContext;
  1839. /*
  1840. * Determine what to do based on which part of the `for` loop are present.
  1841. * 1. If there is an update expression, then `updateSegments` is not null and
  1842. * we need to assign `endOfUpdateSegments`, and if there is a test
  1843. * expression, we then need to create the looped path to get back to
  1844. * the test condition.
  1845. * 2. If there is no update expression but there is a test expression,
  1846. * then we only need to update the test segment information.
  1847. * 3. If there is no update expression and no test expression, then we
  1848. * just save `endOfInitSegments`.
  1849. */
  1850. if (context.updateSegments) {
  1851. context.endOfUpdateSegments = forkContext.head;
  1852. /*
  1853. * In a `for` loop that has both an update expression and a test
  1854. * condition, execution flows from the test expression into the
  1855. * loop body, to the update expression, and then back to the test
  1856. * expression to determine if the loop should continue.
  1857. *
  1858. * To account for that, we need to make a path from the end of the
  1859. * update expression to the start of the test expression. This is
  1860. * effectively what creates the loop in the code path.
  1861. */
  1862. if (context.testSegments) {
  1863. makeLooped(
  1864. this,
  1865. context.endOfUpdateSegments,
  1866. context.testSegments,
  1867. );
  1868. }
  1869. } else if (context.testSegments) {
  1870. finalizeTestSegmentsOfFor(context, choiceContext, forkContext.head);
  1871. } else {
  1872. context.endOfInitSegments = forkContext.head;
  1873. }
  1874. let bodySegments = context.endOfTestSegments;
  1875. /*
  1876. * If there is a test condition, then there `endOfTestSegments` is also
  1877. * the start of the loop body. If there isn't a test condition then
  1878. * `bodySegments` will be null and we need to look elsewhere to find
  1879. * the start of the body.
  1880. *
  1881. * The body starts at the end of the init expression and ends at the end
  1882. * of the update expression, so we use those locations to determine the
  1883. * body segments.
  1884. */
  1885. if (!bodySegments) {
  1886. const prevForkContext = ForkContext.newEmpty(forkContext);
  1887. prevForkContext.add(context.endOfInitSegments);
  1888. if (context.endOfUpdateSegments) {
  1889. prevForkContext.add(context.endOfUpdateSegments);
  1890. }
  1891. bodySegments = prevForkContext.makeNext(0, -1);
  1892. }
  1893. /*
  1894. * If there was no test condition and no update expression, then
  1895. * `continueDestSegments` will be null. In that case, a
  1896. * `continue` should skip directly to the body of the loop.
  1897. * Otherwise, we want to keep the current `continueDestSegments`.
  1898. */
  1899. context.continueDestSegments =
  1900. context.continueDestSegments || bodySegments;
  1901. // move pointer to the body
  1902. forkContext.replaceHead(bodySegments);
  1903. }
  1904. /**
  1905. * Makes a code path segment for the left part of a ForInStatement and a
  1906. * ForOfStatement.
  1907. * @returns {void}
  1908. */
  1909. makeForInOfLeft() {
  1910. const context = this.loopContext;
  1911. const forkContext = this.forkContext;
  1912. const leftSegments = forkContext.makeDisconnected(-1, -1);
  1913. // Update state.
  1914. context.prevSegments = forkContext.head;
  1915. context.leftSegments = context.continueDestSegments = leftSegments;
  1916. forkContext.replaceHead(leftSegments);
  1917. }
  1918. /**
  1919. * Makes a code path segment for the right part of a ForInStatement and a
  1920. * ForOfStatement.
  1921. * @returns {void}
  1922. */
  1923. makeForInOfRight() {
  1924. const context = this.loopContext;
  1925. const forkContext = this.forkContext;
  1926. const temp = ForkContext.newEmpty(forkContext);
  1927. temp.add(context.prevSegments);
  1928. const rightSegments = temp.makeNext(-1, -1);
  1929. // Update state.
  1930. context.endOfLeftSegments = forkContext.head;
  1931. forkContext.replaceHead(rightSegments);
  1932. }
  1933. /**
  1934. * Makes a code path segment for the body part of a ForInStatement and a
  1935. * ForOfStatement.
  1936. * @returns {void}
  1937. */
  1938. makeForInOfBody() {
  1939. const context = this.loopContext;
  1940. const forkContext = this.forkContext;
  1941. const temp = ForkContext.newEmpty(forkContext);
  1942. temp.add(context.endOfLeftSegments);
  1943. const bodySegments = temp.makeNext(-1, -1);
  1944. // Make a path: `right` -> `left`.
  1945. makeLooped(this, forkContext.head, context.leftSegments);
  1946. // Update state.
  1947. context.brokenForkContext.add(forkContext.head);
  1948. forkContext.replaceHead(bodySegments);
  1949. }
  1950. //--------------------------------------------------------------------------
  1951. // Control Statements
  1952. //--------------------------------------------------------------------------
  1953. /**
  1954. * Creates new context in which a `break` statement can be used. This occurs inside of a loop,
  1955. * labeled statement, or switch statement.
  1956. * @param {boolean} breakable Indicates if we are inside a statement where
  1957. * `break` without a label will exit the statement.
  1958. * @param {string|null} label The label associated with the statement.
  1959. * @returns {BreakContext} The new context.
  1960. */
  1961. pushBreakContext(breakable, label) {
  1962. this.breakContext = new BreakContext(
  1963. this.breakContext,
  1964. breakable,
  1965. label,
  1966. this.forkContext,
  1967. );
  1968. return this.breakContext;
  1969. }
  1970. /**
  1971. * Removes the top item of the break context stack.
  1972. * @returns {Object} The removed context.
  1973. */
  1974. popBreakContext() {
  1975. const context = this.breakContext;
  1976. const forkContext = this.forkContext;
  1977. this.breakContext = context.upper;
  1978. // Process this context here for other than switches and loops.
  1979. if (!context.breakable) {
  1980. const brokenForkContext = context.brokenForkContext;
  1981. if (!brokenForkContext.empty) {
  1982. brokenForkContext.add(forkContext.head);
  1983. forkContext.replaceHead(brokenForkContext.makeNext(0, -1));
  1984. }
  1985. }
  1986. return context;
  1987. }
  1988. /**
  1989. * Makes a path for a `break` statement.
  1990. *
  1991. * It registers the head segment to a context of `break`.
  1992. * It makes new unreachable segment, then it set the head with the segment.
  1993. * @param {string|null} label A label of the break statement.
  1994. * @returns {void}
  1995. */
  1996. makeBreak(label) {
  1997. const forkContext = this.forkContext;
  1998. if (!forkContext.reachable) {
  1999. return;
  2000. }
  2001. const context = getBreakContext(this, label);
  2002. if (context) {
  2003. context.brokenForkContext.add(forkContext.head);
  2004. }
  2005. /* c8 ignore next */
  2006. forkContext.replaceHead(forkContext.makeUnreachable(-1, -1));
  2007. }
  2008. /**
  2009. * Makes a path for a `continue` statement.
  2010. *
  2011. * It makes a looping path.
  2012. * It makes new unreachable segment, then it set the head with the segment.
  2013. * @param {string|null} label A label of the continue statement.
  2014. * @returns {void}
  2015. */
  2016. makeContinue(label) {
  2017. const forkContext = this.forkContext;
  2018. if (!forkContext.reachable) {
  2019. return;
  2020. }
  2021. const context = getContinueContext(this, label);
  2022. if (context) {
  2023. if (context.continueDestSegments) {
  2024. makeLooped(
  2025. this,
  2026. forkContext.head,
  2027. context.continueDestSegments,
  2028. );
  2029. // If the context is a for-in/of loop, this affects a break also.
  2030. if (
  2031. context.type === "ForInStatement" ||
  2032. context.type === "ForOfStatement"
  2033. ) {
  2034. context.brokenForkContext.add(forkContext.head);
  2035. }
  2036. } else {
  2037. context.continueForkContext.add(forkContext.head);
  2038. }
  2039. }
  2040. forkContext.replaceHead(forkContext.makeUnreachable(-1, -1));
  2041. }
  2042. /**
  2043. * Makes a path for a `return` statement.
  2044. *
  2045. * It registers the head segment to a context of `return`.
  2046. * It makes new unreachable segment, then it set the head with the segment.
  2047. * @returns {void}
  2048. */
  2049. makeReturn() {
  2050. const forkContext = this.forkContext;
  2051. if (forkContext.reachable) {
  2052. getReturnContext(this).returnedForkContext.add(forkContext.head);
  2053. forkContext.replaceHead(forkContext.makeUnreachable(-1, -1));
  2054. }
  2055. }
  2056. /**
  2057. * Makes a path for a `throw` statement.
  2058. *
  2059. * It registers the head segment to a context of `throw`.
  2060. * It makes new unreachable segment, then it set the head with the segment.
  2061. * @returns {void}
  2062. */
  2063. makeThrow() {
  2064. const forkContext = this.forkContext;
  2065. if (forkContext.reachable) {
  2066. getThrowContext(this).thrownForkContext.add(forkContext.head);
  2067. forkContext.replaceHead(forkContext.makeUnreachable(-1, -1));
  2068. }
  2069. }
  2070. /**
  2071. * Makes the final path.
  2072. * @returns {void}
  2073. */
  2074. makeFinal() {
  2075. const segments = this.currentSegments;
  2076. if (segments.length > 0 && segments[0].reachable) {
  2077. this.returnedForkContext.add(segments);
  2078. }
  2079. }
  2080. }
  2081. module.exports = CodePathState;