emit.js 36 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301
  1. /**
  2. * Copyright (c) 2014-present, Facebook, Inc.
  3. *
  4. * This source code is licensed under the MIT license found in the
  5. * LICENSE file in the root directory of this source tree.
  6. */
  7. import assert from "assert";
  8. import * as leap from "./leap";
  9. import * as meta from "./meta";
  10. import * as util from "./util";
  11. let hasOwn = Object.prototype.hasOwnProperty;
  12. function Emitter(contextId) {
  13. assert.ok(this instanceof Emitter);
  14. util.getTypes().assertIdentifier(contextId);
  15. // Used to generate unique temporary names.
  16. this.nextTempId = 0;
  17. // In order to make sure the context object does not collide with
  18. // anything in the local scope, we might have to rename it, so we
  19. // refer to it symbolically instead of just assuming that it will be
  20. // called "context".
  21. this.contextId = contextId;
  22. // An append-only list of Statements that grows each time this.emit is
  23. // called.
  24. this.listing = [];
  25. // A sparse array whose keys correspond to locations in this.listing
  26. // that have been marked as branch/jump targets.
  27. this.marked = [true];
  28. this.insertedLocs = new Set();
  29. // The last location will be marked when this.getDispatchLoop is
  30. // called.
  31. this.finalLoc = this.loc();
  32. // A list of all leap.TryEntry statements emitted.
  33. this.tryEntries = [];
  34. // Each time we evaluate the body of a loop, we tell this.leapManager
  35. // to enter a nested loop context that determines the meaning of break
  36. // and continue statements therein.
  37. this.leapManager = new leap.LeapManager(this);
  38. }
  39. let Ep = Emitter.prototype;
  40. exports.Emitter = Emitter;
  41. // Offsets into this.listing that could be used as targets for branches or
  42. // jumps are represented as numeric Literal nodes. This representation has
  43. // the amazingly convenient benefit of allowing the exact value of the
  44. // location to be determined at any time, even after generating code that
  45. // refers to the location.
  46. Ep.loc = function() {
  47. const l = util.getTypes().numericLiteral(-1)
  48. this.insertedLocs.add(l);
  49. return l;
  50. }
  51. Ep.getInsertedLocs = function() {
  52. return this.insertedLocs;
  53. }
  54. Ep.getContextId = function() {
  55. return util.getTypes().clone(this.contextId);
  56. }
  57. // Sets the exact value of the given location to the offset of the next
  58. // Statement emitted.
  59. Ep.mark = function(loc) {
  60. util.getTypes().assertLiteral(loc);
  61. let index = this.listing.length;
  62. if (loc.value === -1) {
  63. loc.value = index;
  64. } else {
  65. // Locations can be marked redundantly, but their values cannot change
  66. // once set the first time.
  67. assert.strictEqual(loc.value, index);
  68. }
  69. this.marked[index] = true;
  70. return loc;
  71. };
  72. Ep.emit = function(node) {
  73. const t = util.getTypes();
  74. if (t.isExpression(node)) {
  75. node = t.expressionStatement(node);
  76. }
  77. t.assertStatement(node);
  78. this.listing.push(node);
  79. };
  80. // Shorthand for emitting assignment statements. This will come in handy
  81. // for assignments to temporary variables.
  82. Ep.emitAssign = function(lhs, rhs) {
  83. this.emit(this.assign(lhs, rhs));
  84. return lhs;
  85. };
  86. // Shorthand for an assignment statement.
  87. Ep.assign = function(lhs, rhs) {
  88. const t = util.getTypes();
  89. return t.expressionStatement(
  90. t.assignmentExpression("=", t.cloneDeep(lhs), rhs));
  91. };
  92. // Convenience function for generating expressions like context.next,
  93. // context.sent, and context.rval.
  94. Ep.contextProperty = function(name, computed) {
  95. const t = util.getTypes();
  96. return t.memberExpression(
  97. this.getContextId(),
  98. computed ? t.stringLiteral(name) : t.identifier(name),
  99. !!computed
  100. );
  101. };
  102. // Shorthand for setting context.rval and jumping to `context.stop()`.
  103. Ep.stop = function(rval) {
  104. if (rval) {
  105. this.setReturnValue(rval);
  106. }
  107. this.jump(this.finalLoc);
  108. };
  109. Ep.setReturnValue = function(valuePath) {
  110. util.getTypes().assertExpression(valuePath.value);
  111. this.emitAssign(
  112. this.contextProperty("rval"),
  113. this.explodeExpression(valuePath)
  114. );
  115. };
  116. Ep.clearPendingException = function(tryLoc, assignee) {
  117. const t = util.getTypes();
  118. t.assertLiteral(tryLoc);
  119. let catchCall = t.callExpression(
  120. this.contextProperty("catch", true),
  121. [t.clone(tryLoc)]
  122. );
  123. if (assignee) {
  124. this.emitAssign(assignee, catchCall);
  125. } else {
  126. this.emit(catchCall);
  127. }
  128. };
  129. // Emits code for an unconditional jump to the given location, even if the
  130. // exact value of the location is not yet known.
  131. Ep.jump = function(toLoc) {
  132. this.emitAssign(this.contextProperty("next"), toLoc);
  133. this.emit(util.getTypes().breakStatement());
  134. };
  135. // Conditional jump.
  136. Ep.jumpIf = function(test, toLoc) {
  137. const t = util.getTypes();
  138. t.assertExpression(test);
  139. t.assertLiteral(toLoc);
  140. this.emit(t.ifStatement(
  141. test,
  142. t.blockStatement([
  143. this.assign(this.contextProperty("next"), toLoc),
  144. t.breakStatement()
  145. ])
  146. ));
  147. };
  148. // Conditional jump, with the condition negated.
  149. Ep.jumpIfNot = function(test, toLoc) {
  150. const t = util.getTypes();
  151. t.assertExpression(test);
  152. t.assertLiteral(toLoc);
  153. let negatedTest;
  154. if (t.isUnaryExpression(test) &&
  155. test.operator === "!") {
  156. // Avoid double negation.
  157. negatedTest = test.argument;
  158. } else {
  159. negatedTest = t.unaryExpression("!", test);
  160. }
  161. this.emit(t.ifStatement(
  162. negatedTest,
  163. t.blockStatement([
  164. this.assign(this.contextProperty("next"), toLoc),
  165. t.breakStatement()
  166. ])
  167. ));
  168. };
  169. // Returns a unique MemberExpression that can be used to store and
  170. // retrieve temporary values. Since the object of the member expression is
  171. // the context object, which is presumed to coexist peacefully with all
  172. // other local variables, and since we just increment `nextTempId`
  173. // monotonically, uniqueness is assured.
  174. Ep.makeTempVar = function() {
  175. return this.contextProperty("t" + this.nextTempId++);
  176. };
  177. Ep.getContextFunction = function(id) {
  178. const t = util.getTypes();
  179. return t.functionExpression(
  180. id || null/*Anonymous*/,
  181. [this.getContextId()],
  182. t.blockStatement([this.getDispatchLoop()]),
  183. false, // Not a generator anymore!
  184. false // Nor an expression.
  185. );
  186. };
  187. // Turns this.listing into a loop of the form
  188. //
  189. // while (1) switch (context.next) {
  190. // case 0:
  191. // ...
  192. // case n:
  193. // return context.stop();
  194. // }
  195. //
  196. // Each marked location in this.listing will correspond to one generated
  197. // case statement.
  198. Ep.getDispatchLoop = function() {
  199. const self = this;
  200. const t = util.getTypes();
  201. let cases = [];
  202. let current;
  203. // If we encounter a break, continue, or return statement in a switch
  204. // case, we can skip the rest of the statements until the next case.
  205. let alreadyEnded = false;
  206. self.listing.forEach(function(stmt, i) {
  207. if (self.marked.hasOwnProperty(i)) {
  208. cases.push(t.switchCase(
  209. t.numericLiteral(i),
  210. current = []));
  211. alreadyEnded = false;
  212. }
  213. if (!alreadyEnded) {
  214. current.push(stmt);
  215. if (t.isCompletionStatement(stmt))
  216. alreadyEnded = true;
  217. }
  218. });
  219. // Now that we know how many statements there will be in this.listing,
  220. // we can finally resolve this.finalLoc.value.
  221. this.finalLoc.value = this.listing.length;
  222. cases.push(
  223. t.switchCase(this.finalLoc, [
  224. // Intentionally fall through to the "end" case...
  225. ]),
  226. // So that the runtime can jump to the final location without having
  227. // to know its offset, we provide the "end" case as a synonym.
  228. t.switchCase(t.stringLiteral("end"), [
  229. // This will check/clear both context.thrown and context.rval.
  230. t.returnStatement(
  231. t.callExpression(this.contextProperty("stop"), [])
  232. )
  233. ])
  234. );
  235. return t.whileStatement(
  236. t.numericLiteral(1),
  237. t.switchStatement(
  238. t.assignmentExpression(
  239. "=",
  240. this.contextProperty("prev"),
  241. this.contextProperty("next")
  242. ),
  243. cases
  244. )
  245. );
  246. };
  247. Ep.getTryLocsList = function() {
  248. if (this.tryEntries.length === 0) {
  249. // To avoid adding a needless [] to the majority of runtime.wrap
  250. // argument lists, force the caller to handle this case specially.
  251. return null;
  252. }
  253. const t = util.getTypes();
  254. let lastLocValue = 0;
  255. return t.arrayExpression(
  256. this.tryEntries.map(function(tryEntry) {
  257. let thisLocValue = tryEntry.firstLoc.value;
  258. assert.ok(thisLocValue >= lastLocValue, "try entries out of order");
  259. lastLocValue = thisLocValue;
  260. let ce = tryEntry.catchEntry;
  261. let fe = tryEntry.finallyEntry;
  262. let locs = [
  263. tryEntry.firstLoc,
  264. // The null here makes a hole in the array.
  265. ce ? ce.firstLoc : null
  266. ];
  267. if (fe) {
  268. locs[2] = fe.firstLoc;
  269. locs[3] = fe.afterLoc;
  270. }
  271. return t.arrayExpression(locs.map(loc => loc && t.clone(loc)));
  272. })
  273. );
  274. };
  275. // All side effects must be realized in order.
  276. // If any subexpression harbors a leap, all subexpressions must be
  277. // neutered of side effects.
  278. // No destructive modification of AST nodes.
  279. Ep.explode = function(path, ignoreResult) {
  280. const t = util.getTypes();
  281. let node = path.node;
  282. let self = this;
  283. t.assertNode(node);
  284. if (t.isDeclaration(node))
  285. throw getDeclError(node);
  286. if (t.isStatement(node))
  287. return self.explodeStatement(path);
  288. if (t.isExpression(node))
  289. return self.explodeExpression(path, ignoreResult);
  290. switch (node.type) {
  291. case "Program":
  292. return path.get("body").map(
  293. self.explodeStatement,
  294. self
  295. );
  296. case "VariableDeclarator":
  297. throw getDeclError(node);
  298. // These node types should be handled by their parent nodes
  299. // (ObjectExpression, SwitchStatement, and TryStatement, respectively).
  300. case "Property":
  301. case "SwitchCase":
  302. case "CatchClause":
  303. throw new Error(
  304. node.type + " nodes should be handled by their parents");
  305. default:
  306. throw new Error(
  307. "unknown Node of type " +
  308. JSON.stringify(node.type));
  309. }
  310. };
  311. function getDeclError(node) {
  312. return new Error(
  313. "all declarations should have been transformed into " +
  314. "assignments before the Exploder began its work: " +
  315. JSON.stringify(node));
  316. }
  317. Ep.explodeStatement = function(path, labelId) {
  318. const t = util.getTypes();
  319. let stmt = path.node;
  320. let self = this;
  321. let before, after, head;
  322. t.assertStatement(stmt);
  323. if (labelId) {
  324. t.assertIdentifier(labelId);
  325. } else {
  326. labelId = null;
  327. }
  328. // Explode BlockStatement nodes even if they do not contain a yield,
  329. // because we don't want or need the curly braces.
  330. if (t.isBlockStatement(stmt)) {
  331. path.get("body").forEach(function (path) {
  332. self.explodeStatement(path);
  333. });
  334. return;
  335. }
  336. if (!meta.containsLeap(stmt)) {
  337. // Technically we should be able to avoid emitting the statement
  338. // altogether if !meta.hasSideEffects(stmt), but that leads to
  339. // confusing generated code (for instance, `while (true) {}` just
  340. // disappears) and is probably a more appropriate job for a dedicated
  341. // dead code elimination pass.
  342. self.emit(stmt);
  343. return;
  344. }
  345. switch (stmt.type) {
  346. case "ExpressionStatement":
  347. self.explodeExpression(path.get("expression"), true);
  348. break;
  349. case "LabeledStatement":
  350. after = this.loc();
  351. // Did you know you can break from any labeled block statement or
  352. // control structure? Well, you can! Note: when a labeled loop is
  353. // encountered, the leap.LabeledEntry created here will immediately
  354. // enclose a leap.LoopEntry on the leap manager's stack, and both
  355. // entries will have the same label. Though this works just fine, it
  356. // may seem a bit redundant. In theory, we could check here to
  357. // determine if stmt knows how to handle its own label; for example,
  358. // stmt happens to be a WhileStatement and so we know it's going to
  359. // establish its own LoopEntry when we explode it (below). Then this
  360. // LabeledEntry would be unnecessary. Alternatively, we might be
  361. // tempted not to pass stmt.label down into self.explodeStatement,
  362. // because we've handled the label here, but that's a mistake because
  363. // labeled loops may contain labeled continue statements, which is not
  364. // something we can handle in this generic case. All in all, I think a
  365. // little redundancy greatly simplifies the logic of this case, since
  366. // it's clear that we handle all possible LabeledStatements correctly
  367. // here, regardless of whether they interact with the leap manager
  368. // themselves. Also remember that labels and break/continue-to-label
  369. // statements are rare, and all of this logic happens at transform
  370. // time, so it has no additional runtime cost.
  371. self.leapManager.withEntry(
  372. new leap.LabeledEntry(after, stmt.label),
  373. function() {
  374. self.explodeStatement(path.get("body"), stmt.label);
  375. }
  376. );
  377. self.mark(after);
  378. break;
  379. case "WhileStatement":
  380. before = this.loc();
  381. after = this.loc();
  382. self.mark(before);
  383. self.jumpIfNot(self.explodeExpression(path.get("test")), after);
  384. self.leapManager.withEntry(
  385. new leap.LoopEntry(after, before, labelId),
  386. function() { self.explodeStatement(path.get("body")); }
  387. );
  388. self.jump(before);
  389. self.mark(after);
  390. break;
  391. case "DoWhileStatement":
  392. let first = this.loc();
  393. let test = this.loc();
  394. after = this.loc();
  395. self.mark(first);
  396. self.leapManager.withEntry(
  397. new leap.LoopEntry(after, test, labelId),
  398. function() { self.explode(path.get("body")); }
  399. );
  400. self.mark(test);
  401. self.jumpIf(self.explodeExpression(path.get("test")), first);
  402. self.mark(after);
  403. break;
  404. case "ForStatement":
  405. head = this.loc();
  406. let update = this.loc();
  407. after = this.loc();
  408. if (stmt.init) {
  409. // We pass true here to indicate that if stmt.init is an expression
  410. // then we do not care about its result.
  411. self.explode(path.get("init"), true);
  412. }
  413. self.mark(head);
  414. if (stmt.test) {
  415. self.jumpIfNot(self.explodeExpression(path.get("test")), after);
  416. } else {
  417. // No test means continue unconditionally.
  418. }
  419. self.leapManager.withEntry(
  420. new leap.LoopEntry(after, update, labelId),
  421. function() { self.explodeStatement(path.get("body")); }
  422. );
  423. self.mark(update);
  424. if (stmt.update) {
  425. // We pass true here to indicate that if stmt.update is an
  426. // expression then we do not care about its result.
  427. self.explode(path.get("update"), true);
  428. }
  429. self.jump(head);
  430. self.mark(after);
  431. break;
  432. case "TypeCastExpression":
  433. return self.explodeExpression(path.get("expression"));
  434. case "ForInStatement":
  435. head = this.loc();
  436. after = this.loc();
  437. let keyIterNextFn = self.makeTempVar();
  438. self.emitAssign(
  439. keyIterNextFn,
  440. t.callExpression(
  441. util.runtimeProperty("keys"),
  442. [self.explodeExpression(path.get("right"))]
  443. )
  444. );
  445. self.mark(head);
  446. let keyInfoTmpVar = self.makeTempVar();
  447. self.jumpIf(
  448. t.memberExpression(
  449. t.assignmentExpression(
  450. "=",
  451. keyInfoTmpVar,
  452. t.callExpression(t.cloneDeep(keyIterNextFn), [])
  453. ),
  454. t.identifier("done"),
  455. false
  456. ),
  457. after
  458. );
  459. self.emitAssign(
  460. stmt.left,
  461. t.memberExpression(
  462. t.cloneDeep(keyInfoTmpVar),
  463. t.identifier("value"),
  464. false
  465. )
  466. );
  467. self.leapManager.withEntry(
  468. new leap.LoopEntry(after, head, labelId),
  469. function() { self.explodeStatement(path.get("body")); }
  470. );
  471. self.jump(head);
  472. self.mark(after);
  473. break;
  474. case "BreakStatement":
  475. self.emitAbruptCompletion({
  476. type: "break",
  477. target: self.leapManager.getBreakLoc(stmt.label)
  478. });
  479. break;
  480. case "ContinueStatement":
  481. self.emitAbruptCompletion({
  482. type: "continue",
  483. target: self.leapManager.getContinueLoc(stmt.label)
  484. });
  485. break;
  486. case "SwitchStatement":
  487. // Always save the discriminant into a temporary variable in case the
  488. // test expressions overwrite values like context.sent.
  489. let disc = self.emitAssign(
  490. self.makeTempVar(),
  491. self.explodeExpression(path.get("discriminant"))
  492. );
  493. after = this.loc();
  494. let defaultLoc = this.loc();
  495. let condition = defaultLoc;
  496. let caseLocs = [];
  497. // If there are no cases, .cases might be undefined.
  498. let cases = stmt.cases || [];
  499. for (let i = cases.length - 1; i >= 0; --i) {
  500. let c = cases[i];
  501. t.assertSwitchCase(c);
  502. if (c.test) {
  503. condition = t.conditionalExpression(
  504. t.binaryExpression("===", t.cloneDeep(disc), c.test),
  505. caseLocs[i] = this.loc(),
  506. condition
  507. );
  508. } else {
  509. caseLocs[i] = defaultLoc;
  510. }
  511. }
  512. let discriminant = path.get("discriminant");
  513. util.replaceWithOrRemove(discriminant, condition);
  514. self.jump(self.explodeExpression(discriminant));
  515. self.leapManager.withEntry(
  516. new leap.SwitchEntry(after),
  517. function() {
  518. path.get("cases").forEach(function(casePath) {
  519. let i = casePath.key;
  520. self.mark(caseLocs[i]);
  521. casePath.get("consequent").forEach(function (path) {
  522. self.explodeStatement(path);
  523. });
  524. });
  525. }
  526. );
  527. self.mark(after);
  528. if (defaultLoc.value === -1) {
  529. self.mark(defaultLoc);
  530. assert.strictEqual(after.value, defaultLoc.value);
  531. }
  532. break;
  533. case "IfStatement":
  534. let elseLoc = stmt.alternate && this.loc();
  535. after = this.loc();
  536. self.jumpIfNot(
  537. self.explodeExpression(path.get("test")),
  538. elseLoc || after
  539. );
  540. self.explodeStatement(path.get("consequent"));
  541. if (elseLoc) {
  542. self.jump(after);
  543. self.mark(elseLoc);
  544. self.explodeStatement(path.get("alternate"));
  545. }
  546. self.mark(after);
  547. break;
  548. case "ReturnStatement":
  549. self.emitAbruptCompletion({
  550. type: "return",
  551. value: self.explodeExpression(path.get("argument"))
  552. });
  553. break;
  554. case "WithStatement":
  555. throw new Error("WithStatement not supported in generator functions.");
  556. case "TryStatement":
  557. after = this.loc();
  558. let handler = stmt.handler;
  559. let catchLoc = handler && this.loc();
  560. let catchEntry = catchLoc && new leap.CatchEntry(
  561. catchLoc,
  562. handler.param
  563. );
  564. let finallyLoc = stmt.finalizer && this.loc();
  565. let finallyEntry = finallyLoc &&
  566. new leap.FinallyEntry(finallyLoc, after);
  567. let tryEntry = new leap.TryEntry(
  568. self.getUnmarkedCurrentLoc(),
  569. catchEntry,
  570. finallyEntry
  571. );
  572. self.tryEntries.push(tryEntry);
  573. self.updateContextPrevLoc(tryEntry.firstLoc);
  574. self.leapManager.withEntry(tryEntry, function() {
  575. self.explodeStatement(path.get("block"));
  576. if (catchLoc) {
  577. if (finallyLoc) {
  578. // If we have both a catch block and a finally block, then
  579. // because we emit the catch block first, we need to jump over
  580. // it to the finally block.
  581. self.jump(finallyLoc);
  582. } else {
  583. // If there is no finally block, then we need to jump over the
  584. // catch block to the fall-through location.
  585. self.jump(after);
  586. }
  587. self.updateContextPrevLoc(self.mark(catchLoc));
  588. let bodyPath = path.get("handler.body");
  589. let safeParam = self.makeTempVar();
  590. self.clearPendingException(tryEntry.firstLoc, safeParam);
  591. bodyPath.traverse(catchParamVisitor, {
  592. getSafeParam: () => t.cloneDeep(safeParam),
  593. catchParamName: handler.param.name
  594. });
  595. self.leapManager.withEntry(catchEntry, function() {
  596. self.explodeStatement(bodyPath);
  597. });
  598. }
  599. if (finallyLoc) {
  600. self.updateContextPrevLoc(self.mark(finallyLoc));
  601. self.leapManager.withEntry(finallyEntry, function() {
  602. self.explodeStatement(path.get("finalizer"));
  603. });
  604. self.emit(t.returnStatement(t.callExpression(
  605. self.contextProperty("finish"),
  606. [finallyEntry.firstLoc]
  607. )));
  608. }
  609. });
  610. self.mark(after);
  611. break;
  612. case "ThrowStatement":
  613. self.emit(t.throwStatement(
  614. self.explodeExpression(path.get("argument"))
  615. ));
  616. break;
  617. case "ClassDeclaration":
  618. self.emit(self.explodeClass(path));
  619. break;
  620. default:
  621. throw new Error(
  622. "unknown Statement of type " +
  623. JSON.stringify(stmt.type));
  624. }
  625. };
  626. let catchParamVisitor = {
  627. Identifier: function(path, state) {
  628. if (path.node.name === state.catchParamName && util.isReference(path)) {
  629. util.replaceWithOrRemove(path, state.getSafeParam());
  630. }
  631. },
  632. Scope: function(path, state) {
  633. if (path.scope.hasOwnBinding(state.catchParamName)) {
  634. // Don't descend into nested scopes that shadow the catch
  635. // parameter with their own declarations.
  636. path.skip();
  637. }
  638. }
  639. };
  640. Ep.emitAbruptCompletion = function(record) {
  641. if (!isValidCompletion(record)) {
  642. assert.ok(
  643. false,
  644. "invalid completion record: " +
  645. JSON.stringify(record)
  646. );
  647. }
  648. assert.notStrictEqual(
  649. record.type, "normal",
  650. "normal completions are not abrupt"
  651. );
  652. const t = util.getTypes();
  653. let abruptArgs = [t.stringLiteral(record.type)];
  654. if (record.type === "break" ||
  655. record.type === "continue") {
  656. t.assertLiteral(record.target);
  657. abruptArgs[1] = this.insertedLocs.has(record.target)
  658. ? record.target
  659. : t.cloneDeep(record.target);
  660. } else if (record.type === "return" ||
  661. record.type === "throw") {
  662. if (record.value) {
  663. t.assertExpression(record.value);
  664. abruptArgs[1] = this.insertedLocs.has(record.value)
  665. ? record.value
  666. : t.cloneDeep(record.value);
  667. }
  668. }
  669. this.emit(
  670. t.returnStatement(
  671. t.callExpression(
  672. this.contextProperty("abrupt"),
  673. abruptArgs
  674. )
  675. )
  676. );
  677. };
  678. function isValidCompletion(record) {
  679. let type = record.type;
  680. if (type === "normal") {
  681. return !hasOwn.call(record, "target");
  682. }
  683. if (type === "break" ||
  684. type === "continue") {
  685. return !hasOwn.call(record, "value")
  686. && util.getTypes().isLiteral(record.target);
  687. }
  688. if (type === "return" ||
  689. type === "throw") {
  690. return hasOwn.call(record, "value")
  691. && !hasOwn.call(record, "target");
  692. }
  693. return false;
  694. }
  695. // Not all offsets into emitter.listing are potential jump targets. For
  696. // example, execution typically falls into the beginning of a try block
  697. // without jumping directly there. This method returns the current offset
  698. // without marking it, so that a switch case will not necessarily be
  699. // generated for this offset (I say "not necessarily" because the same
  700. // location might end up being marked in the process of emitting other
  701. // statements). There's no logical harm in marking such locations as jump
  702. // targets, but minimizing the number of switch cases keeps the generated
  703. // code shorter.
  704. Ep.getUnmarkedCurrentLoc = function() {
  705. return util.getTypes().numericLiteral(this.listing.length);
  706. };
  707. // The context.prev property takes the value of context.next whenever we
  708. // evaluate the switch statement discriminant, which is generally good
  709. // enough for tracking the last location we jumped to, but sometimes
  710. // context.prev needs to be more precise, such as when we fall
  711. // successfully out of a try block and into a finally block without
  712. // jumping. This method exists to update context.prev to the freshest
  713. // available location. If we were implementing a full interpreter, we
  714. // would know the location of the current instruction with complete
  715. // precision at all times, but we don't have that luxury here, as it would
  716. // be costly and verbose to set context.prev before every statement.
  717. Ep.updateContextPrevLoc = function(loc) {
  718. const t = util.getTypes();
  719. if (loc) {
  720. t.assertLiteral(loc);
  721. if (loc.value === -1) {
  722. // If an uninitialized location literal was passed in, set its value
  723. // to the current this.listing.length.
  724. loc.value = this.listing.length;
  725. } else {
  726. // Otherwise assert that the location matches the current offset.
  727. assert.strictEqual(loc.value, this.listing.length);
  728. }
  729. } else {
  730. loc = this.getUnmarkedCurrentLoc();
  731. }
  732. // Make sure context.prev is up to date in case we fell into this try
  733. // statement without jumping to it. TODO Consider avoiding this
  734. // assignment when we know control must have jumped here.
  735. this.emitAssign(this.contextProperty("prev"), loc);
  736. };
  737. // In order to save the rest of explodeExpression from a combinatorial
  738. // trainwreck of special cases, explodeViaTempVar is responsible for
  739. // deciding when a subexpression needs to be "exploded," which is my
  740. // very technical term for emitting the subexpression as an assignment
  741. // to a temporary variable and the substituting the temporary variable
  742. // for the original subexpression. Think of exploded view diagrams, not
  743. // Michael Bay movies. The point of exploding subexpressions is to
  744. // control the precise order in which the generated code realizes the
  745. // side effects of those subexpressions.
  746. Ep.explodeViaTempVar = function(tempVar, childPath, hasLeapingChildren, ignoreChildResult) {
  747. assert.ok(
  748. !ignoreChildResult || !tempVar,
  749. "Ignoring the result of a child expression but forcing it to " +
  750. "be assigned to a temporary variable?"
  751. );
  752. const t = util.getTypes();
  753. let result = this.explodeExpression(childPath, ignoreChildResult);
  754. if (ignoreChildResult) {
  755. // Side effects already emitted above.
  756. } else if (tempVar || (hasLeapingChildren &&
  757. !t.isLiteral(result))) {
  758. // If tempVar was provided, then the result will always be assigned
  759. // to it, even if the result does not otherwise need to be assigned
  760. // to a temporary variable. When no tempVar is provided, we have
  761. // the flexibility to decide whether a temporary variable is really
  762. // necessary. Unfortunately, in general, a temporary variable is
  763. // required whenever any child contains a yield expression, since it
  764. // is difficult to prove (at all, let alone efficiently) whether
  765. // this result would evaluate to the same value before and after the
  766. // yield (see #206). One narrow case where we can prove it doesn't
  767. // matter (and thus we do not need a temporary variable) is when the
  768. // result in question is a Literal value.
  769. result = this.emitAssign(
  770. tempVar || this.makeTempVar(),
  771. result
  772. );
  773. }
  774. return result;
  775. };
  776. Ep.explodeExpression = function(path, ignoreResult) {
  777. const t = util.getTypes();
  778. let expr = path.node;
  779. if (expr) {
  780. t.assertExpression(expr);
  781. } else {
  782. return expr;
  783. }
  784. let self = this;
  785. let result; // Used optionally by several cases below.
  786. let after;
  787. function finish(expr) {
  788. t.assertExpression(expr);
  789. if (ignoreResult) {
  790. self.emit(expr);
  791. }
  792. return expr;
  793. }
  794. // If the expression does not contain a leap, then we either emit the
  795. // expression as a standalone statement or return it whole.
  796. if (!meta.containsLeap(expr)) {
  797. return finish(expr);
  798. }
  799. // If any child contains a leap (such as a yield or labeled continue or
  800. // break statement), then any sibling subexpressions will almost
  801. // certainly have to be exploded in order to maintain the order of their
  802. // side effects relative to the leaping child(ren).
  803. let hasLeapingChildren = meta.containsLeap.onlyChildren(expr);
  804. // If ignoreResult is true, then we must take full responsibility for
  805. // emitting the expression with all its side effects, and we should not
  806. // return a result.
  807. switch (expr.type) {
  808. case "MemberExpression":
  809. return finish(t.memberExpression(
  810. self.explodeExpression(path.get("object")),
  811. expr.computed
  812. ? self.explodeViaTempVar(null, path.get("property"), hasLeapingChildren)
  813. : expr.property,
  814. expr.computed
  815. ));
  816. case "CallExpression":
  817. let calleePath = path.get("callee");
  818. let argsPath = path.get("arguments");
  819. let newCallee;
  820. let newArgs;
  821. let hasLeapingArgs = argsPath.some(
  822. argPath => meta.containsLeap(argPath.node)
  823. );
  824. let injectFirstArg = null;
  825. if (t.isMemberExpression(calleePath.node)) {
  826. if (hasLeapingArgs) {
  827. // If the arguments of the CallExpression contained any yield
  828. // expressions, then we need to be sure to evaluate the callee
  829. // before evaluating the arguments, but if the callee was a member
  830. // expression, then we must be careful that the object of the
  831. // member expression still gets bound to `this` for the call.
  832. let newObject = self.explodeViaTempVar(
  833. // Assign the exploded callee.object expression to a temporary
  834. // variable so that we can use it twice without reevaluating it.
  835. self.makeTempVar(),
  836. calleePath.get("object"),
  837. hasLeapingChildren
  838. );
  839. let newProperty = calleePath.node.computed
  840. ? self.explodeViaTempVar(null, calleePath.get("property"), hasLeapingChildren)
  841. : calleePath.node.property;
  842. injectFirstArg = newObject;
  843. newCallee = t.memberExpression(
  844. t.memberExpression(
  845. t.cloneDeep(newObject),
  846. newProperty,
  847. calleePath.node.computed
  848. ),
  849. t.identifier("call"),
  850. false
  851. );
  852. } else {
  853. newCallee = self.explodeExpression(calleePath);
  854. }
  855. } else {
  856. newCallee = self.explodeViaTempVar(null, calleePath, hasLeapingChildren);
  857. if (t.isMemberExpression(newCallee)) {
  858. // If the callee was not previously a MemberExpression, then the
  859. // CallExpression was "unqualified," meaning its `this` object
  860. // should be the global object. If the exploded expression has
  861. // become a MemberExpression (e.g. a context property, probably a
  862. // temporary variable), then we need to force it to be unqualified
  863. // by using the (0, object.property)(...) trick; otherwise, it
  864. // will receive the object of the MemberExpression as its `this`
  865. // object.
  866. newCallee = t.sequenceExpression([
  867. t.numericLiteral(0),
  868. t.cloneDeep(newCallee)
  869. ]);
  870. }
  871. }
  872. if (hasLeapingArgs) {
  873. newArgs = argsPath.map(argPath => self.explodeViaTempVar(null, argPath, hasLeapingChildren));
  874. if (injectFirstArg) newArgs.unshift(injectFirstArg);
  875. newArgs = newArgs.map(arg => t.cloneDeep(arg));
  876. } else {
  877. newArgs = path.node.arguments;
  878. }
  879. return finish(t.callExpression(newCallee, newArgs));
  880. case "NewExpression":
  881. return finish(t.newExpression(
  882. self.explodeViaTempVar(null, path.get("callee"), hasLeapingChildren),
  883. path.get("arguments").map(function(argPath) {
  884. return self.explodeViaTempVar(null, argPath, hasLeapingChildren);
  885. })
  886. ));
  887. case "ObjectExpression":
  888. return finish(t.objectExpression(
  889. path.get("properties").map(function(propPath) {
  890. if (propPath.isObjectProperty()) {
  891. return t.objectProperty(
  892. propPath.node.key,
  893. self.explodeViaTempVar(null, propPath.get("value"), hasLeapingChildren),
  894. propPath.node.computed
  895. );
  896. } else {
  897. return propPath.node;
  898. }
  899. })
  900. ));
  901. case "ArrayExpression":
  902. return finish(t.arrayExpression(
  903. path.get("elements").map(function(elemPath) {
  904. if (elemPath.isSpreadElement()) {
  905. return t.spreadElement(
  906. self.explodeViaTempVar(null, elemPath.get("argument"), hasLeapingChildren)
  907. );
  908. } else {
  909. return self.explodeViaTempVar(null, elemPath, hasLeapingChildren);
  910. }
  911. })
  912. ));
  913. case "SequenceExpression":
  914. let lastIndex = expr.expressions.length - 1;
  915. path.get("expressions").forEach(function(exprPath) {
  916. if (exprPath.key === lastIndex) {
  917. result = self.explodeExpression(exprPath, ignoreResult);
  918. } else {
  919. self.explodeExpression(exprPath, true);
  920. }
  921. });
  922. return result;
  923. case "LogicalExpression":
  924. after = this.loc();
  925. if (!ignoreResult) {
  926. result = self.makeTempVar();
  927. }
  928. let left = self.explodeViaTempVar(result, path.get("left"), hasLeapingChildren);
  929. if (expr.operator === "&&") {
  930. self.jumpIfNot(left, after);
  931. } else {
  932. assert.strictEqual(expr.operator, "||");
  933. self.jumpIf(left, after);
  934. }
  935. self.explodeViaTempVar(result, path.get("right"), hasLeapingChildren, ignoreResult);
  936. self.mark(after);
  937. return result;
  938. case "ConditionalExpression":
  939. let elseLoc = this.loc();
  940. after = this.loc();
  941. let test = self.explodeExpression(path.get("test"));
  942. self.jumpIfNot(test, elseLoc);
  943. if (!ignoreResult) {
  944. result = self.makeTempVar();
  945. }
  946. self.explodeViaTempVar(result, path.get("consequent"), hasLeapingChildren, ignoreResult);
  947. self.jump(after);
  948. self.mark(elseLoc);
  949. self.explodeViaTempVar(result, path.get("alternate"), hasLeapingChildren, ignoreResult);
  950. self.mark(after);
  951. return result;
  952. case "UnaryExpression":
  953. return finish(t.unaryExpression(
  954. expr.operator,
  955. // Can't (and don't need to) break up the syntax of the argument.
  956. // Think about delete a[b].
  957. self.explodeExpression(path.get("argument")),
  958. !!expr.prefix
  959. ));
  960. case "BinaryExpression":
  961. return finish(t.binaryExpression(
  962. expr.operator,
  963. self.explodeViaTempVar(null, path.get("left"), hasLeapingChildren),
  964. self.explodeViaTempVar(null, path.get("right"), hasLeapingChildren)
  965. ));
  966. case "AssignmentExpression":
  967. if (expr.operator === "=") {
  968. // If this is a simple assignment, the left hand side does not need
  969. // to be read before the right hand side is evaluated, so we can
  970. // avoid the more complicated logic below.
  971. return finish(t.assignmentExpression(
  972. expr.operator,
  973. self.explodeExpression(path.get("left")),
  974. self.explodeExpression(path.get("right"))
  975. ));
  976. }
  977. const lhs = self.explodeExpression(path.get("left"));
  978. const temp = self.emitAssign(self.makeTempVar(), lhs);
  979. // For example,
  980. //
  981. // x += yield y
  982. //
  983. // becomes
  984. //
  985. // context.t0 = x
  986. // x = context.t0 += yield y
  987. //
  988. // so that the left-hand side expression is read before the yield.
  989. // Fixes https://github.com/facebook/regenerator/issues/345.
  990. return finish(t.assignmentExpression(
  991. "=",
  992. t.cloneDeep(lhs),
  993. t.assignmentExpression(
  994. expr.operator,
  995. t.cloneDeep(temp),
  996. self.explodeExpression(path.get("right"))
  997. )
  998. ));
  999. case "UpdateExpression":
  1000. return finish(t.updateExpression(
  1001. expr.operator,
  1002. self.explodeExpression(path.get("argument")),
  1003. expr.prefix
  1004. ));
  1005. case "YieldExpression":
  1006. after = this.loc();
  1007. let arg = expr.argument && self.explodeExpression(path.get("argument"));
  1008. if (arg && expr.delegate) {
  1009. let result = self.makeTempVar();
  1010. let ret = t.returnStatement(t.callExpression(
  1011. self.contextProperty("delegateYield"),
  1012. [
  1013. arg,
  1014. t.stringLiteral(result.property.name),
  1015. after
  1016. ]
  1017. ));
  1018. ret.loc = expr.loc;
  1019. self.emit(ret);
  1020. self.mark(after);
  1021. return result;
  1022. }
  1023. self.emitAssign(self.contextProperty("next"), after);
  1024. let ret = t.returnStatement(t.cloneDeep(arg) || null);
  1025. // Preserve the `yield` location so that source mappings for the statements
  1026. // link back to the yield properly.
  1027. ret.loc = expr.loc;
  1028. self.emit(ret);
  1029. self.mark(after);
  1030. return self.contextProperty("sent");
  1031. case "ClassExpression":
  1032. return finish(self.explodeClass(path));
  1033. default:
  1034. throw new Error(
  1035. "unknown Expression of type " +
  1036. JSON.stringify(expr.type));
  1037. }
  1038. };
  1039. Ep.explodeClass = function(path) {
  1040. const explodingChildren = [];
  1041. if (path.node.superClass) {
  1042. explodingChildren.push(path.get("superClass"));
  1043. }
  1044. path.get("body.body").forEach(member => {
  1045. if (member.node.computed) {
  1046. explodingChildren.push(member.get("key"));
  1047. }
  1048. });
  1049. const hasLeapingChildren = explodingChildren.some(
  1050. child => meta.containsLeap(child));
  1051. for (let i = 0; i < explodingChildren.length; i++) {
  1052. const child = explodingChildren[i];
  1053. const isLast = i === explodingChildren.length - 1;
  1054. if (isLast) {
  1055. child.replaceWith(this.explodeExpression(child));
  1056. } else {
  1057. child.replaceWith(this.explodeViaTempVar(null, child, hasLeapingChildren));
  1058. }
  1059. }
  1060. return path.node;
  1061. };