From 7157849a44b5136362551b02140c3e4653c980fa Mon Sep 17 00:00:00 2001 From: Herman Venter Date: Sat, 11 Aug 2018 20:49:07 -0700 Subject: [PATCH] Do full joins (#2402) Summary: Release note: Rewrote the joining logic to always do a full join at every join point Closes #2151 #2222 #2279 I've spent a lot of time in the last few months trying to sort out problems that arise from effects being applied too many or too few times. Fixing these feel a bit like playing wack a mole and in the end no fix goes unpunished. Stepping back a bit from the fray, it seems to me that the root cause of all this pain is the fact that joins of different kinds of completions get delayed. Before we had path conditions and the simplifier this seemed like a rather good thing since exceptional paths did not contribute values to the normal paths and we thus had fewer abstract values to deal with and fewer places where Prepack would grind to a halt. In the current state of things, however, it seems perfectly possible to join in all branches at every join point. I've had to decrease some limits, in particular the number of times we go around a loop with conditional exits. I've also had to make the test runner impose a limit on how many times the simplifier can invoke Path.implies. Nevertheless, the tests seem to pass and hopefully this will also fix quite a lot of bugs that have been unresolved for many months already. Pull Request resolved: https://github.com/facebook/prepack/pull/2402 Differential Revision: D9236263 Pulled By: hermanventer fbshipit-source-id: 92a25b591591297afeba536429226c5a0291f451 --- .circleci/config.yml | 1 - package.json | 4 +- scripts/multi-runner.js | 4 +- scripts/test-error-handler.js | 2 - scripts/test-internal.js | 1 - scripts/test-residual.js | 262 ------- scripts/test-runner.js | 15 +- src/completions.js | 340 +++------ src/construct_realm.js | 2 - src/domains/TypesDomain.js | 13 +- src/domains/ValuesDomain.js | 14 +- src/environment.js | 80 +-- src/evaluators/BinaryExpression.js | 18 +- src/evaluators/BreakStatement.js | 2 +- src/evaluators/CallExpression.js | 41 +- src/evaluators/ContinueStatement.js | 2 +- src/evaluators/DoWhileStatement.js | 7 +- src/evaluators/ForOfStatement.js | 46 +- src/evaluators/ForStatement.js | 199 +----- src/evaluators/LabeledStatement.js | 16 +- src/evaluators/LogicalExpression.js | 52 +- src/evaluators/NewExpression.js | 16 +- src/evaluators/Program.js | 90 ++- src/evaluators/ReturnStatement.js | 2 +- src/evaluators/SwitchStatement.js | 67 +- src/evaluators/ThrowStatement.js | 2 +- src/evaluators/TryStatement.js | 210 ++---- src/evaluators/UnaryExpression.js | 19 +- src/intrinsics/ecma262/GeneratorPrototype.js | 4 +- src/intrinsics/ecma262/Object.js | 15 +- src/intrinsics/ecma262/StringPrototype.js | 2 +- src/intrinsics/index.js | 32 +- src/methods/call.js | 130 ++-- src/methods/function.js | 72 +- src/methods/get.js | 22 +- src/methods/join.js | 661 +++--------------- src/methods/properties.js | 41 +- src/methods/widen.js | 18 +- src/options.js | 1 - src/partial-evaluators/ArrayExpression.js | 154 ---- .../ArrowFunctionExpression.js | 28 - .../AssignmentExpression.js | 163 ----- src/partial-evaluators/AwaitExpression.js | 27 - src/partial-evaluators/BinaryExpression.js | 122 ---- src/partial-evaluators/BlockStatement.js | 62 -- src/partial-evaluators/BooleanLiteral.js | 26 - src/partial-evaluators/BreakStatement.js | 26 - src/partial-evaluators/CallExpression.js | 228 ------ src/partial-evaluators/CatchClause.js | 32 - src/partial-evaluators/ClassDeclaration.js | 27 - src/partial-evaluators/ClassExpression.js | 27 - .../ConditionalExpression.js | 27 - src/partial-evaluators/ContinueStatement.js | 26 - src/partial-evaluators/Directive.js | 27 - src/partial-evaluators/DirectiveLiteral.js | 12 - src/partial-evaluators/DoExpression.js | 12 - src/partial-evaluators/DoWhileStatement.js | 28 - src/partial-evaluators/EmptyStatement.js | 25 - src/partial-evaluators/ExpressionStatement.js | 30 - src/partial-evaluators/File.js | 29 - src/partial-evaluators/ForInStatement.js | 29 - src/partial-evaluators/ForOfStatement.js | 29 - src/partial-evaluators/ForStatement.js | 29 - src/partial-evaluators/FunctionDeclaration.js | 28 - src/partial-evaluators/FunctionExpression.js | 27 - src/partial-evaluators/Identifier.js | 28 - src/partial-evaluators/IfStatement.js | 103 --- src/partial-evaluators/LabeledStatement.js | 30 - src/partial-evaluators/LogicalExpression.js | 27 - src/partial-evaluators/MemberExpression.js | 28 - src/partial-evaluators/MetaProperty.js | 28 - src/partial-evaluators/NewExpression.js | 27 - src/partial-evaluators/NullLiteral.js | 25 - src/partial-evaluators/NumericLiteral.js | 26 - src/partial-evaluators/ObjectExpression.js | 28 - src/partial-evaluators/Program.js | 55 -- src/partial-evaluators/RegExpLiteral.js | 31 - src/partial-evaluators/ReturnStatement.js | 32 - src/partial-evaluators/SequenceExpression.js | 27 - src/partial-evaluators/StringLiteral.js | 26 - src/partial-evaluators/SwitchStatement.js | 29 - .../TaggedTemplateExpression.js | 28 - src/partial-evaluators/TemplateLiteral.js | 28 - src/partial-evaluators/ThisExpression.js | 28 - src/partial-evaluators/ThrowStatement.js | 32 - src/partial-evaluators/TryStatement.js | 27 - src/partial-evaluators/UnaryExpression.js | 27 - src/partial-evaluators/UpdateExpression.js | 27 - src/partial-evaluators/VariableDeclaration.js | 28 - src/partial-evaluators/WhileStatement.js | 28 - src/partial-evaluators/WithStatement.js | 28 - src/partial-evaluators/YieldExpression.js | 25 - src/partial-evaluators/index.js | 64 -- src/prepack-cli.js | 1 - src/prepack-options.js | 3 - src/prepack-standalone.js | 35 +- src/react/utils.js | 21 +- src/realm.js | 457 ++++-------- src/serializer/functions.js | 8 +- src/serializer/serializer.js | 1 - src/serializer/utils.js | 13 +- src/types.js | 141 +--- src/utils/generator.js | 84 +-- src/utils/modules.js | 127 +--- src/utils/parse.js | 2 +- src/values/AbstractValue.js | 45 +- src/values/NativeFunctionValue.js | 1 - .../ModifiedObjectPropertyLimitation.js | 3 +- test/error-handler/bad-functions.js | 4 +- test/error-handler/forLoop1.js | 15 - test/error-handler/require_throws2.js | 123 ---- test/residual/If.js | 32 - test/residual/arrayExpression.js | 1 - test/residual/block.js | 3 - test/residual/call.js | 49 -- test/residual/call2.js | 5 - test/residual/call3.js | 19 - test/residual/call4.js | 19 - test/residual/call5.js | 18 - test/residual/call6.js | 7 - test/residual/throw.js | 1 - test/serializer/abstract/Break2.js | 10 +- test/serializer/abstract/Continue2.js | 8 +- test/serializer/abstract/Switch.js | 26 +- test/serializer/abstract/Throw7.js | 27 - test/serializer/abstract/require_tracking.js | 2 +- test/serializer/abstract/require_tracking2.js | 1 - .../additional-functions/ToObject.js | 2 +- .../additional-functions/conditions2.js | 2 +- .../optimizations/require_accelerate.js | 1 - .../serializer/optimizations/require_delay.js | 1 - .../require_spec_accelerate_delay.js | 1 - .../optimizations/require_throws1.js | 1 - .../optimized-functions/ArgumentProperty.js | 15 + .../optimized-functions/ComposeJoins.js | 27 + .../optimized-functions/ConditionalReturn2.js | 20 + .../optimized-functions/ForLoop3.js | 2 + .../optimized-functions/Issue1856.js | 16 + .../optimized-functions/Issue2151.js | 17 + .../optimized-functions/LoopBailout7.js | 2 + .../optimized-functions/NullCheck.js | 12 + .../serializer/optimized-functions/Switch2.js | 1 - .../serializer/optimized-functions/Switch3.js | 1 - .../serializer/optimized-functions/Switch4.js | 1 - 144 files changed, 981 insertions(+), 5178 deletions(-) delete mode 100644 scripts/test-residual.js delete mode 100644 src/partial-evaluators/ArrayExpression.js delete mode 100644 src/partial-evaluators/ArrowFunctionExpression.js delete mode 100644 src/partial-evaluators/AssignmentExpression.js delete mode 100644 src/partial-evaluators/AwaitExpression.js delete mode 100644 src/partial-evaluators/BinaryExpression.js delete mode 100644 src/partial-evaluators/BlockStatement.js delete mode 100644 src/partial-evaluators/BooleanLiteral.js delete mode 100644 src/partial-evaluators/BreakStatement.js delete mode 100644 src/partial-evaluators/CallExpression.js delete mode 100644 src/partial-evaluators/CatchClause.js delete mode 100644 src/partial-evaluators/ClassDeclaration.js delete mode 100644 src/partial-evaluators/ClassExpression.js delete mode 100644 src/partial-evaluators/ConditionalExpression.js delete mode 100644 src/partial-evaluators/ContinueStatement.js delete mode 100644 src/partial-evaluators/Directive.js delete mode 100644 src/partial-evaluators/DirectiveLiteral.js delete mode 100644 src/partial-evaluators/DoExpression.js delete mode 100644 src/partial-evaluators/DoWhileStatement.js delete mode 100644 src/partial-evaluators/EmptyStatement.js delete mode 100644 src/partial-evaluators/ExpressionStatement.js delete mode 100644 src/partial-evaluators/File.js delete mode 100644 src/partial-evaluators/ForInStatement.js delete mode 100644 src/partial-evaluators/ForOfStatement.js delete mode 100644 src/partial-evaluators/ForStatement.js delete mode 100644 src/partial-evaluators/FunctionDeclaration.js delete mode 100644 src/partial-evaluators/FunctionExpression.js delete mode 100644 src/partial-evaluators/Identifier.js delete mode 100644 src/partial-evaluators/IfStatement.js delete mode 100644 src/partial-evaluators/LabeledStatement.js delete mode 100644 src/partial-evaluators/LogicalExpression.js delete mode 100644 src/partial-evaluators/MemberExpression.js delete mode 100644 src/partial-evaluators/MetaProperty.js delete mode 100644 src/partial-evaluators/NewExpression.js delete mode 100644 src/partial-evaluators/NullLiteral.js delete mode 100644 src/partial-evaluators/NumericLiteral.js delete mode 100644 src/partial-evaluators/ObjectExpression.js delete mode 100644 src/partial-evaluators/Program.js delete mode 100644 src/partial-evaluators/RegExpLiteral.js delete mode 100644 src/partial-evaluators/ReturnStatement.js delete mode 100644 src/partial-evaluators/SequenceExpression.js delete mode 100644 src/partial-evaluators/StringLiteral.js delete mode 100644 src/partial-evaluators/SwitchStatement.js delete mode 100644 src/partial-evaluators/TaggedTemplateExpression.js delete mode 100644 src/partial-evaluators/TemplateLiteral.js delete mode 100644 src/partial-evaluators/ThisExpression.js delete mode 100644 src/partial-evaluators/ThrowStatement.js delete mode 100644 src/partial-evaluators/TryStatement.js delete mode 100644 src/partial-evaluators/UnaryExpression.js delete mode 100644 src/partial-evaluators/UpdateExpression.js delete mode 100644 src/partial-evaluators/VariableDeclaration.js delete mode 100644 src/partial-evaluators/WhileStatement.js delete mode 100644 src/partial-evaluators/WithStatement.js delete mode 100644 src/partial-evaluators/YieldExpression.js delete mode 100644 src/partial-evaluators/index.js delete mode 100644 test/error-handler/forLoop1.js delete mode 100644 test/error-handler/require_throws2.js delete mode 100644 test/residual/If.js delete mode 100644 test/residual/arrayExpression.js delete mode 100644 test/residual/block.js delete mode 100644 test/residual/call.js delete mode 100644 test/residual/call2.js delete mode 100644 test/residual/call3.js delete mode 100644 test/residual/call4.js delete mode 100644 test/residual/call5.js delete mode 100644 test/residual/call6.js delete mode 100644 test/residual/throw.js delete mode 100644 test/serializer/abstract/Throw7.js create mode 100644 test/serializer/optimized-functions/ArgumentProperty.js create mode 100644 test/serializer/optimized-functions/ComposeJoins.js create mode 100644 test/serializer/optimized-functions/ConditionalReturn2.js create mode 100644 test/serializer/optimized-functions/Issue1856.js create mode 100644 test/serializer/optimized-functions/Issue2151.js create mode 100644 test/serializer/optimized-functions/NullCheck.js diff --git a/.circleci/config.yml b/.circleci/config.yml index b1ad32979..56a167513 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -59,7 +59,6 @@ jobs: yarn test-react yarn test-sourcemaps yarn test-std-in - yarn test-residual yarn test-test262 --expectedCounts 11944,5641,0 --statusFile ~/artifacts/test262-status.txt --timeout 120 --cpuScale 0.25 --verbose #yarn test-test262-new --statusFile ~/artifacts/test262-new-status.txt --timeout 120 --verbose - store_artifacts: diff --git a/package.json b/package.json index 17534e3a6..db2de7a0c 100644 --- a/package.json +++ b/package.json @@ -31,8 +31,6 @@ "lint": "eslint src scripts", "flow": "flow", "flow-ci": "flow version; flow check", - "test-residual": "babel-node scripts/test-residual.js", - "test-residual-with-coverage": "./node_modules/.bin/istanbul cover ./lib/test-residual.js --dir coverage.residual && ./node_modules/.bin/remap-istanbul -i coverage.residual/coverage.json -o coverage-sourcemapped.residual -t html", "test-serializer": "babel-node --stack_trace_limit=200 --stack_size=10000 scripts/test-runner.js", "test-serializer-single": "yarn test-serializer --debugNames --verbose --fast --filter", "test-serializer-with-coverage": "./node_modules/.bin/istanbul cover ./lib/test-error-handler.js --dir coverage.error && ./node_modules/.bin/istanbul cover ./lib/test-runner.js && ./node_modules/.bin/remap-istanbul -i coverage.error/coverage.json -i coverage/coverage.json -o coverage-sourcemapped -t html", @@ -47,7 +45,7 @@ "test-std-in": "bash < scripts/test-std-in.sh", "test-react": "jest", "test-react-fast": "SKIP_REACT_JSX_TESTS=true jest", - "test": "yarn test-residual && yarn test-serializer && yarn test-sourcemaps && yarn test-error-handler && yarn test-std-in && yarn test-test262 && yarn test-internal && yarn test-internal-react && yarn test-react", + "test": "yarn test-serializer && yarn test-sourcemaps && yarn test-error-handler && yarn test-std-in && yarn test-test262 && yarn test-internal && yarn test-internal-react && yarn test-react", "test-coverage-most": "./node_modules/.bin/istanbul --stack_size=10000 --max_old_space_size=16384 cover ./lib/multi-runner.js --dir coverage.most && ./node_modules/.bin/remap-istanbul -i coverage.most/coverage.json -o coverage-sourcemapped -t html", "test-all-coverage": "./node_modules/.bin/istanbul --stack_size=10000 --max_old_space_size=16384 cover ./lib/multi-runner.js --dir coverage.most && ./node_modules/.bin/istanbul --stack_size=10000 --max_old_space_size=16384 cover ./lib/test262-runner.js --timeout 50 --singleThreaded && ./node_modules/.bin/remap-istanbul -i coverage/coverage.json -i coverage.most/coverage.json -o coverage-sourcemapped -t html", "repl": "node lib/repl-cli.js", diff --git a/scripts/multi-runner.js b/scripts/multi-runner.js index f7e31743e..f07d1d3aa 100644 --- a/scripts/multi-runner.js +++ b/scripts/multi-runner.js @@ -8,9 +8,7 @@ */ /* @flow */ -// This file just runs the 4 test runners in one file for coverage -require("./test-residual.js"); - +// This file just runs the 3 test runners in one file for coverage require("./test-runner.js"); require("./generate-sourcemaps-test.js"); diff --git a/scripts/test-error-handler.js b/scripts/test-error-handler.js index 3920658cc..3ef146a9b 100644 --- a/scripts/test-error-handler.js +++ b/scripts/test-error-handler.js @@ -54,7 +54,6 @@ function runTest(name: string, code: string): boolean { console.log(chalk.inverse(name)); let recover = code.includes("// recover-from-errors"); - let delayUnsupportedRequires = code.includes("// delay unsupported requires"); let compatibility = code.includes("// jsc") ? "jsc-600-1-4-17" : undefined; let expectedErrors = code.match(/\/\/\s*expected errors:\s*(.*)/); @@ -68,7 +67,6 @@ function runTest(name: string, code: string): boolean { try { let options = { internalDebug: false, - delayUnsupportedRequires, mathRandomSeed: "0", errorHandler: errorHandler.bind(null, recover ? "Recover" : "Fail", errors), serialize: true, diff --git a/scripts/test-internal.js b/scripts/test-internal.js index ea6a08a69..47986a5b0 100644 --- a/scripts/test-internal.js +++ b/scripts/test-internal.js @@ -68,7 +68,6 @@ function runTest(name: string, code: string): boolean { let options = { internalDebug: true, compatibility: "jsc-600-1-4-17", - delayUnsupportedRequires: true, accelerateUnsupportedRequires: true, mathRandomSeed: "0", errorHandler, diff --git a/scripts/test-residual.js b/scripts/test-residual.js deleted file mode 100644 index 876f2141b..000000000 --- a/scripts/test-residual.js +++ /dev/null @@ -1,262 +0,0 @@ -/** - * Copyright (c) 2017-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -/* @flow */ - -let construct_realm = require("../lib/construct_realm.js").default; -let initializeGlobals = require("../lib/globals.js").default; -let AbruptCompletion = require("../lib/completions.js").AbruptCompletion; -let ThrowCompletion = require("../lib/completions.js").ThrowCompletion; -let FatalError = require("../lib/errors.js").FatalError; - -let chalk = require("chalk"); -let path = require("path"); -let fs = require("fs"); -let vm = require("vm"); -let os = require("os"); -let minimist = require("minimist"); -const EOL = os.EOL; - -function search(dir, relative) { - let tests = []; - - for (let name of fs.readdirSync(dir)) { - let loc = path.join(dir, name); - let stat = fs.statSync(loc); - - if (stat.isFile()) { - tests.push({ - file: fs.readFileSync(loc, "utf8"), - name: path.join(relative, name), - }); - } else if (stat.isDirectory()) { - tests = tests.concat(search(loc, path.join(relative, name))); - } - } - - return tests; -} - -let tests = search(`${__dirname}/../test/residual`, "test/residual"); - -function exec(code) { - let script = new vm.Script( - `var global = this; var self = this; var __result = ${code} // keep newline here as code may end with comment -; report(__result);`, - { cachedDataProduced: false } - ); - - let result = ""; - let logOutput = ""; - - function write(prefix, values) { - logOutput += "\n" + prefix + values.join(""); - } - - script.runInNewContext({ - setTimeout: setTimeout, - setInterval: setInterval, - clearTimeout: clearTimeout, - clearInterval: clearInterval, - report: function(s) { - result = s; - }, - console: { - log(...s) { - write("", s); - }, - warn(...s) { - write("WARN:", s); - }, - error(...s) { - write("ERROR:", s); - }, - }, - }); - return result + logOutput; -} - -function runTest(name, code, args) { - let realmOptions = { residual: true }; - let sources = [{ filePath: name, fileContents: code }]; - console.log(chalk.inverse(name)); - if (code.includes("// throws introspection error")) { - try { - let realm = construct_realm(realmOptions); - initializeGlobals(realm); - let result = realm.$GlobalEnv.executePartialEvaluator(sources); - if (result instanceof ThrowCompletion) throw result.value; - } catch (err) { - if (err instanceof FatalError) return true; - console.error(err); - } - return false; - } else { - let expected, actual; - let codeIterations = []; - let markersToFind = []; - for (let [positive, marker] of [[true, "// does contain:"], [false, "// does not contain:"]]) { - if (code.includes(marker)) { - let i = code.indexOf(marker); - let value = code.substring(i + marker.length, code.indexOf("\n", i)); - markersToFind.push({ positive, value, start: i + marker.length }); - } - } - try { - try { - expected = exec(`(function () { ${code}; // keep newline here as code may end with comment - return __result; }).call(this);`); - } catch (e) { - expected = "" + e; - } - - let i = 0; - let max = 4; - let oldCode = code; - for (; i < max; i++) { - let realm = construct_realm(realmOptions); - initializeGlobals(realm); - let result = realm.$GlobalEnv.executePartialEvaluator(sources); - if (result instanceof ThrowCompletion) throw result.value; - if (result instanceof AbruptCompletion) throw result; - let newCode = result.code; - if (args.verbose && i === 0) console.log(newCode); - codeIterations.push(newCode); - let markersIssue = false; - for (let { positive, value, start } of markersToFind) { - let found = newCode.indexOf(value, start) !== -1; - if (found !== positive) { - console.error(chalk.red(`Output ${positive ? "does not contain" : "contains"} forbidden string: ${value}`)); - markersIssue = true; - } - } - if (markersIssue) break; - try { - actual = exec(`(function () { ${newCode}; // keep newline here as code may end with comment - return __result; }).call(this);`); - } catch (e) { - actual = "" + e; - } - if (expected !== actual) { - console.error(chalk.red("Output mismatch!")); - break; - } - if (oldCode === newCode) { - // The generated code reached a fixed point! - return true; - } - oldCode = newCode; - } - if (i === max) { - console.error(chalk.red(`Code generation did not reach fixed point after ${max} iterations!`)); - } - } catch (err) { - console.error(err); - } - console.log(chalk.underline("original code")); - console.log(code); - console.log(chalk.underline("output of inspect() on original code")); - console.log(expected); - for (let i = 0; i < codeIterations.length; i++) { - console.log(chalk.underline(`generated code in iteration ${i}`)); - console.log(codeIterations[i]); - } - console.log(chalk.underline("output of inspect() on last generated code iteration")); - console.log(actual); - return false; - } -} -function run(args) { - let failed = 0; - let passed = 0; - let total = 0; - - for (let test of tests) { - // filter hidden files - if (path.basename(test.name)[0] === ".") continue; - if (test.name.endsWith("~")) continue; - if (test.file.includes("// skip")) continue; - if (!test.name.includes(args.filter)) continue; - - total++; - if (runTest(test.name, test.file, args)) passed++; - else failed++; - } - - console.log("Passed:", `${passed}/${total}`, (Math.floor((passed / total) * 100) || 0) + "%"); - return failed === 0; -} - -// Object to store all command line arguments -class ProgramArgs { - verbose: boolean; - filter: string; - constructor(verbose: boolean, filter: string) { - this.verbose = verbose; - this.filter = filter; //lets user choose specific test files, runs all tests if omitted - } -} - -// Execution of tests begins here -function main(): number { - try { - let args = argsParse(); - if (!run(args)) { - process.exit(1); - } else { - return 0; - } - } catch (e) { - if (e instanceof ArgsParseError) { - console.error("Illegal argument: %s.\n%s", e.message, usage()); - } else { - console.error(e); - } - return 1; - } - return 0; -} - -// Helper function to provide correct usage information to the user -function usage(): string { - return `Usage: ${process.argv[0]} ${process.argv[1]} ` + EOL + `[--verbose] [--filter ]`; -} - -// NOTE: inheriting from Error does not seem to pass through an instanceof -// check -class ArgsParseError { - message: string; - constructor(message: string) { - this.message = message; - } -} - -// Parses through the command line arguments and throws errors if usage is incorrect -function argsParse(): ProgramArgs { - let parsedArgs = minimist(process.argv.slice(2), { - string: ["filter"], - boolean: ["verbose"], - default: { - verbose: false, - filter: "", - }, - }); - if (typeof parsedArgs.verbose !== "boolean") { - throw new ArgsParseError("verbose must be a boolean (either --verbose or not)"); - } - if (typeof parsedArgs.filter !== "string") { - throw new ArgsParseError( - "filter must be a string (relative path from serialize directory) (--filter abstract/Residual.js)" - ); - } - let programArgs = new ProgramArgs(parsedArgs.verbose, parsedArgs.filter); - return programArgs; -} - -main(); diff --git a/scripts/test-runner.js b/scripts/test-runner.js index 347c9ed9a..15c406421 100644 --- a/scripts/test-runner.js +++ b/scripts/test-runner.js @@ -307,7 +307,7 @@ function verifyFunctionOrderings(code: string): boolean { return true; } -function unescapleUniqueSuffix(code: string, uniqueSuffix?: string) { +function unescapeUniqueSuffix(code: string, uniqueSuffix?: string) { return uniqueSuffix != null ? code.replace(new RegExp(uniqueSuffix, "g"), "") : code; } @@ -341,7 +341,6 @@ function runTest(name, code, options: PrepackOptions, args) { if (!args.fast && args.filter === "") console.log(chalk.inverse(name) + " " + JSON.stringify(options)); let compatibility = code.includes("// jsc") ? "jsc-600-1-4-17" : undefined; let initializeMoreModules = code.includes("// initialize more modules"); - let delayUnsupportedRequires = code.includes("// delay unsupported requires"); if (args.verbose || code.includes("// inline expressions")) options.inlineExpressions = true; options.invariantLevel = code.includes("// omit invariants") || args.verbose ? 0 : 99; if (code.includes("// emit concrete model")) options.emitConcreteModel = true; @@ -354,11 +353,11 @@ function runTest(name, code, options: PrepackOptions, args) { let compileJSXWithBabel = code.includes("// babel:jsx"); let functionCloneCountMatch = code.match(/\/\/ serialized function clone count: (\d+)/); options = ((Object.assign({}, options, { + abstractValueImpliesMax: 2000, compatibility, debugNames: args.debugNames, debugScopes: args.debugScopes, initializeMoreModules, - delayUnsupportedRequires, errorHandler: diag => "Fail", internalDebug: true, serialize: true, @@ -381,7 +380,6 @@ function runTest(name, code, options: PrepackOptions, args) { initializeGlobals(realm); let serializerOptions = { initializeMoreModules, - delayUnsupportedRequires, internalDebug: true, lazyObjectsRuntime: options.lazyObjectsRuntime, }; @@ -459,7 +457,6 @@ function runTest(name, code, options: PrepackOptions, args) { addedCode = code.substring(i + injectAtRuntime.length, code.indexOf("\n", i)); options.residual = false; } - if (delayUnsupportedRequires) options.residual = false; if (args.es5) { code = transformWithBabel(code, [], [["@babel/env", { forceAllTransforms: true, modules: false }]]); } @@ -590,7 +587,7 @@ function runTest(name, code, options: PrepackOptions, args) { codeToRun = augmentCodeWithLazyObjectSupport(codeToRun, args.lazyObjectsRuntime); } if (args.verbose) console.log(codeToRun); - codeIterations.push(unescapleUniqueSuffix(codeToRun, options.uniqueSuffix)); + codeIterations.push(unescapeUniqueSuffix(codeToRun, options.uniqueSuffix)); if (args.es5) { codeToRun = transformWithBabel( codeToRun, @@ -640,9 +637,7 @@ function runTest(name, code, options: PrepackOptions, args) { } if (singleIterationOnly) return Promise.reject({ type: "RETURN", value: true }); if ( - unescapleUniqueSuffix(oldCode, oldUniqueSuffix) === - unescapleUniqueSuffix(newCode, newUniqueSuffix) || - delayUnsupportedRequires + unescapeUniqueSuffix(oldCode, oldUniqueSuffix) === unescapeUniqueSuffix(newCode, newUniqueSuffix) ) { // The generated code reached a fixed point! return Promise.reject({ type: "RETURN", value: true }); @@ -922,7 +917,7 @@ function argsParse(): ProgramArgs { es5: false, // if true test marked as es6 only are not run filter: "", outOfProcessRuntime: "", // if set, assumed to be a JS runtime and is used - // to run tests. If not a seperate node context used. + // to run tests. If not a separate node context used. lazyObjectsRuntime: LAZY_OBJECTS_RUNTIME_NAME, noLazySupport: false, fast: false, diff --git a/src/completions.js b/src/completions.js index 51c7296ff..05a97d8a5 100644 --- a/src/completions.js +++ b/src/completions.js @@ -11,78 +11,108 @@ import type { BabelNodeSourceLocation } from "@babel/types"; import invariant from "./invariant.js"; -import { Effects, Realm } from "./realm.js"; +import type { Effects } from "./realm.js"; import { AbstractValue, EmptyValue, Value } from "./values/index.js"; export class Completion { - constructor(value: Value, precedingEffects: void | Effects, location: ?BabelNodeSourceLocation, target?: ?string) { - let e = precedingEffects; - if (e !== undefined) { - if (e.result === undefined) e.result = this; - else e = e.shallowCloneWithResult(this); - } + constructor(value: Value, location: ?BabelNodeSourceLocation, target?: ?string) { this.value = value; - this.effects = e; this.target = target; this.location = location; invariant(this.constructor !== Completion, "Completion is an abstract base class"); } value: Value; - effects: void | Effects; target: ?string; location: ?BabelNodeSourceLocation; - shallowCloneWithoutEffects(): Completion { - invariant(false, "Completion.shallowCloneWithoutEffects is an abstract base method and should not be called"); + containsSelectedCompletion(selector: Completion => boolean): boolean { + return selector(this); } toDisplayString(): string { return "[" + this.constructor.name + " value " + (this.value ? this.value.toDisplayString() : "undefined") + "]"; } + + static makeAllNormalCompletionsResultInUndefined(completion: Completion): void { + let undefinedVal = completion.value.$Realm.intrinsics.undefined; + if (completion instanceof SimpleNormalCompletion) completion.value = undefinedVal; + else if (completion instanceof JoinedNormalAndAbruptCompletions) { + if (completion.composedWith !== undefined) + Completion.makeAllNormalCompletionsResultInUndefined(completion.composedWith); + Completion.makeAllNormalCompletionsResultInUndefined(completion.consequent); + Completion.makeAllNormalCompletionsResultInUndefined(completion.alternate); + } + } + + static makeSelectedCompletionsInfeasible(selector: Completion => boolean, completion: Completion): void { + let bottomValue = completion.value.$Realm.intrinsics.__bottomValue; + if (selector(completion)) completion.value = bottomValue; + else if (completion instanceof JoinedNormalAndAbruptCompletions || completion instanceof JoinedAbruptCompletions) { + if (completion instanceof JoinedNormalAndAbruptCompletions && completion.composedWith !== undefined) + Completion.makeSelectedCompletionsInfeasible(selector, completion.composedWith); + Completion.makeSelectedCompletionsInfeasible(selector, completion.consequent); + Completion.makeSelectedCompletionsInfeasible(selector, completion.alternate); + } + } + + static normalizeSelectedCompletions(selector: Completion => boolean, completion: Completion): Completion { + if (selector(completion)) return new SimpleNormalCompletion(completion.value); + let normalizedComposedWith; + if (completion instanceof JoinedNormalAndAbruptCompletions) { + invariant(completion.savedEffects === undefined); // caller should not used a still saved completion for this + if (completion.composedWith !== undefined) + normalizedComposedWith = Completion.normalizeSelectedCompletions(selector, completion.composedWith); + } + if (completion instanceof JoinedNormalAndAbruptCompletions || completion instanceof JoinedAbruptCompletions) { + let nc = Completion.normalizeSelectedCompletions(selector, completion.consequent); + let na = Completion.normalizeSelectedCompletions(selector, completion.alternate); + if (normalizedComposedWith === undefined) { + if (nc === completion.consequent && na === completion.alternate) return completion; + if (nc instanceof AbruptCompletion && na instanceof AbruptCompletion) return completion; + if (nc instanceof SimpleNormalCompletion && na instanceof SimpleNormalCompletion) + return new SimpleNormalCompletion( + AbstractValue.createFromConditionalOp(completion.value.$Realm, completion.joinCondition, nc.value, na.value) + ); + invariant(nc instanceof AbruptCompletion || nc instanceof NormalCompletion); + invariant(na instanceof AbruptCompletion || na instanceof NormalCompletion); + return new JoinedNormalAndAbruptCompletions(completion.joinCondition, nc, na); + } + invariant(nc instanceof AbruptCompletion || nc instanceof NormalCompletion); + invariant(na instanceof AbruptCompletion || na instanceof NormalCompletion); + let result = new JoinedNormalAndAbruptCompletions(completion.joinCondition, nc, na); + invariant(normalizedComposedWith instanceof JoinedNormalAndAbruptCompletions); + result.composedWith = normalizedComposedWith; + return result; + } + return completion; + } } // Normal completions are returned just like spec completions export class NormalCompletion extends Completion { - constructor(value: Value, precedingEffects: void | Effects, location: ?BabelNodeSourceLocation, target?: ?string) { - super(value, precedingEffects, location, target); + constructor(value: Value, location: ?BabelNodeSourceLocation, target?: ?string) { + super(value, location, target); invariant(this.constructor !== NormalCompletion, "NormalCompletion is an abstract base class"); } - - shallowCloneWithoutEffects(): NormalCompletion { - invariant(false, "NormalCompletion.shallowCloneWithoutEffects is an abstract base method and should not be called"); - } } -// SimpleNormalCompletions are returned just like spec completions. This class exists as the parallel for -// PossiblyNormalCompletion to make comparisons easier. -export class SimpleNormalCompletion extends NormalCompletion { - shallowCloneWithoutEffects(): SimpleNormalCompletion { - return new SimpleNormalCompletion(this.value, undefined, this.location, this.target); - } -} +// SimpleNormalCompletions are returned just like spec completions. +// They chiefly exist for use in joined completions. +export class SimpleNormalCompletion extends NormalCompletion {} // Abrupt completions are thrown as exeptions, to make it a easier // to quickly get to the matching high level construct. export class AbruptCompletion extends Completion { - constructor(value: Value, precedingEffects: void | Effects, location: ?BabelNodeSourceLocation, target?: ?string) { - super(value, precedingEffects, location, target); + constructor(value: Value, location: ?BabelNodeSourceLocation, target?: ?string) { + super(value, location, target); invariant(this.constructor !== AbruptCompletion, "AbruptCompletion is an abstract base class"); } - - shallowCloneWithoutEffects(): AbruptCompletion { - invariant(false, "AbruptCompletion.shallowCloneWithoutEffects is an abstract base method and should not be called"); - } } export class ThrowCompletion extends AbruptCompletion { - constructor( - value: Value, - precedingEffects: void | Effects, - location: ?BabelNodeSourceLocation, - nativeStack?: ?string - ) { - super(value, precedingEffects, location); + constructor(value: Value, location: ?BabelNodeSourceLocation, nativeStack?: ?string) { + super(value, location); this.nativeStack = nativeStack || new Error().stack; let realm = value.$Realm; if (realm.isInPureScope()) { @@ -93,48 +123,32 @@ export class ThrowCompletion extends AbruptCompletion { } nativeStack: string; - - shallowCloneWithoutEffects(): ThrowCompletion { - return new ThrowCompletion(this.value, undefined, this.location, this.nativeStack); - } } export class ContinueCompletion extends AbruptCompletion { - constructor(value: Value, precedingEffects: void | Effects, location: ?BabelNodeSourceLocation, target: ?string) { - super(value, precedingEffects, location, target || null); - } - - shallowCloneWithoutEffects(): ContinueCompletion { - return new ContinueCompletion(this.value, undefined, this.location, this.target); + constructor(value: Value, location: ?BabelNodeSourceLocation, target: ?string) { + super(value, location, target || null); } } export class BreakCompletion extends AbruptCompletion { - constructor(value: Value, precedingEffects: void | Effects, location: ?BabelNodeSourceLocation, target: ?string) { - super(value, precedingEffects, location, target || null); - } - - shallowCloneWithoutEffects(): BreakCompletion { - return new BreakCompletion(this.value, undefined, this.location, this.target); + constructor(value: Value, location: ?BabelNodeSourceLocation, target: ?string) { + super(value, location, target || null); } } export class ReturnCompletion extends AbruptCompletion { - constructor(value: Value, precedingEffects: void | Effects, location: ?BabelNodeSourceLocation) { - super(value, precedingEffects, location); + constructor(value: Value, location: ?BabelNodeSourceLocation) { + super(value, location); if (value instanceof EmptyValue) { this.value = value.$Realm.intrinsics.undefined; } } - - shallowCloneWithoutEffects(): ReturnCompletion { - return new ReturnCompletion(this.value, undefined, this.location); - } } -export class ForkedAbruptCompletion extends AbruptCompletion { - constructor(realm: Realm, joinCondition: AbstractValue, consequent: AbruptCompletion, alternate: AbruptCompletion) { - super(realm.intrinsics.empty, undefined, consequent.location); +export class JoinedAbruptCompletions extends AbruptCompletion { + constructor(joinCondition: AbstractValue, consequent: AbruptCompletion, alternate: AbruptCompletion) { + super(joinCondition.$Realm.intrinsics.empty, consequent.location); this.joinCondition = joinCondition; this.consequent = consequent; this.alternate = alternate; @@ -144,36 +158,16 @@ export class ForkedAbruptCompletion extends AbruptCompletion { consequent: AbruptCompletion; alternate: AbruptCompletion; - shallowCloneWithoutEffects(): ForkedAbruptCompletion { - return new ForkedAbruptCompletion(this.value.$Realm, this.joinCondition, this.consequent, this.alternate); - } - - // For convenience, this.consequent.effects should always be defined, but accessing it directly requires - // verifying that with an invariant. - get consequentEffects(): Effects { - invariant(this.consequent.effects); - return this.consequent.effects; - } - - get alternateEffects(): Effects { - invariant(this.alternate.effects); - return this.alternate.effects; - } - - updateConsequentKeepingCurrentEffects(newConsequent: AbruptCompletion): AbruptCompletion { - let e = this.consequent.effects; - invariant(e); - newConsequent.effects = e.shallowCloneWithResult(newConsequent); - this.consequent = newConsequent; - return this; - } - - updateAlternateKeepingCurrentEffects(newAlternate: AbruptCompletion): AbruptCompletion { - let e = this.alternate.effects; - invariant(e); - newAlternate.effects = e.shallowCloneWithResult(newAlternate); - this.alternate = newAlternate; - return this; + containsSelectedCompletion(selector: Completion => boolean): boolean { + if (selector(this.consequent)) return true; + if (selector(this.alternate)) return true; + if (this.consequent instanceof JoinedAbruptCompletions) { + if (this.consequent.containsSelectedCompletion(selector)) return true; + } + if (this.alternate instanceof JoinedAbruptCompletions) { + if (this.alternate.containsSelectedCompletion(selector)) return true; + } + return false; } toDisplayString(): string { @@ -182,112 +176,46 @@ export class ForkedAbruptCompletion extends AbruptCompletion { superString + " c: [" + this.consequent.toDisplayString() + "] a: [" + this.alternate.toDisplayString() + "]]" ); } - - containsCompletion(CompletionType: typeof Completion): boolean { - if (this.consequent instanceof CompletionType) return true; - if (this.alternate instanceof CompletionType) return true; - if (this.consequent instanceof ForkedAbruptCompletion) { - if (this.consequent.containsCompletion(CompletionType)) return true; - } - if (this.alternate instanceof ForkedAbruptCompletion) { - if (this.alternate.containsCompletion(CompletionType)) return true; - } - return false; - } - - containsBreakOrContinue(): boolean { - if (this.consequent instanceof BreakCompletion || this.consequent instanceof ContinueCompletion) return true; - if (this.alternate instanceof BreakCompletion || this.alternate instanceof ContinueCompletion) return true; - if (this.consequent instanceof ForkedAbruptCompletion) { - if (this.consequent.containsBreakOrContinue()) return true; - } - if (this.alternate instanceof ForkedAbruptCompletion) { - if (this.alternate.containsBreakOrContinue()) return true; - } - return false; - } - - transferChildrenToPossiblyNormalCompletion(): PossiblyNormalCompletion { - invariant(this.consequent.value instanceof EmptyValue || this.alternate.value instanceof EmptyValue); - return new PossiblyNormalCompletion( - this.value.$Realm.intrinsics.empty, - this.joinCondition, - this.consequent, - this.alternate, - [] - ); - } } -// Possibly normal completions have to be treated like normal completions -// and are thus never thrown. At the end of a try block or loop body, however, -// action must be taken to deal with the possibly abrupt case of the completion. -export class PossiblyNormalCompletion extends NormalCompletion { +// This should never be thrown, therefore it is treated as a NormalCompletion even though it is also Abrupt. +export class JoinedNormalAndAbruptCompletions extends NormalCompletion { constructor( - value: Value, joinCondition: AbstractValue, - consequent: Completion, - alternate: Completion, - savedPathConditions: Array, - savedEffects: void | Effects = undefined + consequent: AbruptCompletion | NormalCompletion, + alternate: AbruptCompletion | NormalCompletion ) { - invariant(consequent instanceof NormalCompletion || alternate instanceof NormalCompletion); - super(value, undefined, consequent.location); + super(consequent instanceof NormalCompletion ? consequent.value : alternate.value, consequent.location); this.joinCondition = joinCondition; this.consequent = consequent; this.alternate = alternate; - this.savedEffects = savedEffects; - this.savedPathConditions = savedPathConditions; + this.pathConditionsAtCreation = [].concat(joinCondition.$Realm.pathConditions); } joinCondition: AbstractValue; - consequent: Completion; - alternate: Completion; + consequent: AbruptCompletion | NormalCompletion; + alternate: AbruptCompletion | NormalCompletion; + composedWith: void | JoinedNormalAndAbruptCompletions; + pathConditionsAtCreation: Array; savedEffects: void | Effects; - // The path conditions that applied at the time of the oldest fork that caused this completion to arise. - savedPathConditions: Array; - shallowCloneWithoutEffects(): PossiblyNormalCompletion { - let consequentEffects = this.consequentEffects; - let alternateEffects = this.alternateEffects; - invariant(this.consequent === consequentEffects.result); - invariant(this.alternate === alternateEffects.result); - return new PossiblyNormalCompletion( - this.value, - this.joinCondition, - this.consequent, - this.alternate, - this.savedPathConditions, - this.savedEffects - ); - } - - // For convenience, this.consequent.effects should always be defined, but accessing it directly requires - // verifying that with an invariant. - get consequentEffects(): Effects { - invariant(this.consequent.effects); - return this.consequent.effects; - } - - get alternateEffects(): Effects { - invariant(this.alternate.effects); - return this.alternate.effects; - } - - updateConsequentKeepingCurrentEffects(newConsequent: Completion): PossiblyNormalCompletion { - if (newConsequent instanceof NormalCompletion) this.value = newConsequent.value; - let e = this.consequentEffects; - let effects = e.shallowCloneWithResult(newConsequent); - this.consequent = effects.result; - return this; - } - - updateAlternateKeepingCurrentEffects(newAlternate: Completion): PossiblyNormalCompletion { - if (newAlternate instanceof NormalCompletion) this.value = newAlternate.value; - let e = this.alternateEffects; - let effects = e.shallowCloneWithResult(newAlternate); - this.alternate = effects.result; - return this; + containsSelectedCompletion(selector: Completion => boolean): boolean { + if (this.composedWith !== undefined && this.composedWith.containsSelectedCompletion(selector)) return true; + if (selector(this.consequent)) return true; + if (selector(this.alternate)) return true; + if ( + this.consequent instanceof JoinedAbruptCompletions || + this.consequent instanceof JoinedNormalAndAbruptCompletions + ) { + if (this.consequent.containsSelectedCompletion(selector)) return true; + } + if ( + this.alternate instanceof JoinedAbruptCompletions || + this.alternate instanceof JoinedNormalAndAbruptCompletions + ) { + if (this.alternate.containsSelectedCompletion(selector)) return true; + } + return false; } toDisplayString(): string { @@ -296,46 +224,4 @@ export class PossiblyNormalCompletion extends NormalCompletion { superString + " c: [" + this.consequent.toDisplayString() + "] a: [" + this.alternate.toDisplayString() + "]]" ); } - - getNormalCompletion(): SimpleNormalCompletion { - let result; - if (this.alternate instanceof SimpleNormalCompletion) { - result = this.alternate; - } else if (this.consequent instanceof SimpleNormalCompletion) { - result = this.consequent; - } else { - if (this.alternate instanceof PossiblyNormalCompletion) { - result = this.alternate.getNormalCompletion(); - } else { - invariant(this.consequent instanceof PossiblyNormalCompletion); - result = this.consequent.getNormalCompletion(); - } - } - invariant(result.value === this.value); - return result; - } - - containsCompletion(CompletionType: typeof Completion): boolean { - if (this.consequent instanceof CompletionType) return true; - if (this.alternate instanceof CompletionType) return true; - if (this.consequent instanceof ForkedAbruptCompletion || this.consequent instanceof PossiblyNormalCompletion) { - if (this.consequent.containsCompletion(CompletionType)) return true; - } - if (this.alternate instanceof ForkedAbruptCompletion || this.alternate instanceof PossiblyNormalCompletion) { - if (this.alternate.containsCompletion(CompletionType)) return true; - } - return false; - } - - containsBreakOrContinue(): boolean { - if (this.consequent instanceof BreakCompletion || this.consequent instanceof ContinueCompletion) return true; - if (this.alternate instanceof BreakCompletion || this.alternate instanceof ContinueCompletion) return true; - if (this.consequent instanceof ForkedAbruptCompletion || this.consequent instanceof PossiblyNormalCompletion) { - if (this.consequent.containsBreakOrContinue()) return true; - } - if (this.alternate instanceof ForkedAbruptCompletion || this.alternate instanceof PossiblyNormalCompletion) { - if (this.alternate.containsBreakOrContinue()) return true; - } - return false; - } } diff --git a/src/construct_realm.js b/src/construct_realm.js index ce126377f..61af4d26b 100644 --- a/src/construct_realm.js +++ b/src/construct_realm.js @@ -16,7 +16,6 @@ import initializeGlobal from "./intrinsics/ecma262/global.js"; import type { RealmOptions } from "./options.js"; import { RealmStatistics } from "./statistics.js"; import * as evaluators from "./evaluators/index.js"; -import * as partialEvaluators from "./partial-evaluators/index.js"; import { Environment, DebugReproManager } from "./singletons.js"; import { ObjectValue } from "./values/index.js"; import { DebugServer } from "./debugger/server/Debugger.js"; @@ -50,7 +49,6 @@ export default function( r.$GlobalObject = new ObjectValue(r, i.ObjectPrototype, "global"); initializeGlobal(r); for (let name in evaluators) r.evaluators[name] = evaluators[name]; - for (let name in partialEvaluators) r.partialEvaluators[name] = partialEvaluators[name]; r.simplifyAndRefineAbstractValue = simplifyAndRefineAbstractValue.bind(null, r, false); r.simplifyAndRefineAbstractCondition = simplifyAndRefineAbstractValue.bind(null, r, true); r.$GlobalEnv = Environment.NewGlobalEnvironment(r, r.$GlobalObject, r.$GlobalObject); diff --git a/src/domains/TypesDomain.js b/src/domains/TypesDomain.js index 835a7954a..6b13ab1e9 100644 --- a/src/domains/TypesDomain.js +++ b/src/domains/TypesDomain.js @@ -15,6 +15,7 @@ import { AbstractValue, BooleanValue, ConcreteValue, + EmptyValue, FunctionValue, NumberValue, IntegralValue, @@ -33,10 +34,15 @@ export default class TypesDomain { this._type = type === Value ? undefined : type; } - static topVal: TypesDomain = new TypesDomain(undefined); + static topVal: TypesDomain; + static bottomVal: TypesDomain; _type: void | typeof Value; + isBottom(): boolean { + return this._type instanceof EmptyValue; + } + isTop(): boolean { return this._type === undefined; } @@ -47,6 +53,7 @@ export default class TypesDomain { // return the type of the result in the case where there is no exception static binaryOp(op: BabelBinaryOperator, left: TypesDomain, right: TypesDomain): TypesDomain { + if (left.isBottom() || right.isBottom()) return TypesDomain.bottomVal; let lType = left._type; let rType = right._type; let resultType = Value; @@ -105,8 +112,9 @@ export default class TypesDomain { } joinWith(t: typeof Value): TypesDomain { + if (this.isBottom()) return t === EmptyValue ? TypesDomain.bottomVal : new TypesDomain(t); let type = this.getType(); - if (type === t) return this; + if (type === t || t instanceof EmptyValue) return this; if (Value.isTypeCompatibleWith(type, NumberValue) && Value.isTypeCompatibleWith(t, NumberValue)) { return new TypesDomain(NumberValue); } @@ -129,6 +137,7 @@ export default class TypesDomain { // return the type of the result in the case where there is no exception // note that the type of the operand has no influence on the type of the non exceptional result static unaryOp(op: BabelUnaryOperator, operand: TypesDomain): TypesDomain { + if (operand.isBottom()) return TypesDomain.bottomVal; const type = operand._type; let resultType = Value; switch (op) { diff --git a/src/domains/ValuesDomain.js b/src/domains/ValuesDomain.js index 447560b95..458dd59b6 100644 --- a/src/domains/ValuesDomain.js +++ b/src/domains/ValuesDomain.js @@ -54,7 +54,8 @@ export default class ValuesDomain { this._elements = values; } - static topVal = new ValuesDomain(undefined); + static topVal: ValuesDomain; + static bottomVal: ValuesDomain; _elements: void | Set; @@ -79,6 +80,10 @@ export default class ValuesDomain { return elems.has(x); } + isBottom(): boolean { + return this._elements !== undefined && this._elements.size === 0; + } + isTop(): boolean { return this._elements === undefined; } @@ -91,6 +96,7 @@ export default class ValuesDomain { // return a set of values that may be result of performing the given operation on each pair in the // Cartesian product of the value sets of the operands. static binaryOp(realm: Realm, op: BabelBinaryOperator, left: ValuesDomain, right: ValuesDomain): ValuesDomain { + if (left.isBottom() || right.isBottom()) return ValuesDomain.bottomVal; let leftElements = left._elements; let rightElements = right._elements; // Return top if left and/or right are top or if the size of the value set would get to be quite large. @@ -413,6 +419,7 @@ export default class ValuesDomain { } static unaryOp(realm: Realm, op: BabelUnaryOperator, operandValues: ValuesDomain): ValuesDomain { + if (operandValues.isBottom()) return ValuesDomain.bottomVal; let operandElements = operandValues._elements; if (operandElements === undefined) return ValuesDomain.topVal; let resultSet = new Set(); @@ -503,6 +510,7 @@ export default class ValuesDomain { invariant(y instanceof ConcreteValue); union.add(y); } + if (union.size === 0) return ValuesDomain.bottomVal; return new ValuesDomain(union); } @@ -517,6 +525,7 @@ export default class ValuesDomain { invariant(v1 instanceof ConcreteValue); invariant(v2 instanceof ConcreteValue); if (v1 === v2) intersection.add(v1); + if (intersection.size === 0) return ValuesDomain.bottomVal; return new ValuesDomain(intersection); } @@ -532,11 +541,12 @@ export default class ValuesDomain { invariant(y instanceof ConcreteValue); if (elements === undefined || elements.has(y)) intersection.add(y); } + if (intersection.size === 0) return ValuesDomain.bottomVal; return new ValuesDomain(intersection); } promoteEmptyToUndefined(): ValuesDomain { - if (this.isTop()) return this; + if (this.isTop() || this.isBottom()) return this; let newSet = new Set(); for (let cval of this.getElements()) { if (cval instanceof EmptyValue) newSet.add(cval.$Realm.intrinsics.undefined); diff --git a/src/environment.js b/src/environment.js index c2480dd0f..b4d13080f 100644 --- a/src/environment.js +++ b/src/environment.js @@ -15,17 +15,14 @@ import type { BabelNodeFile, BabelNodeLVal, BabelNodePosition, - BabelNodeStatement, BabelNodeSourceLocation, } from "@babel/types"; import type { Realm } from "./realm.js"; -import type { SourceFile, SourceMap, SourceType } from "./types.js"; +import type { SourceFile, SourceType } from "./types.js"; import * as t from "@babel/types"; import { AbruptCompletion, Completion, ThrowCompletion } from "./completions.js"; import { CompilerDiagnostic, FatalError } from "./errors.js"; -import { defaultOptions } from "./options.js"; -import type { PartialEvaluatorOptions } from "./options"; import { ExecutionContext } from "./realm.js"; import { AbstractValue, @@ -41,7 +38,6 @@ import { UndefinedValue, Value, } from "./values/index.js"; -import generate from "@babel/generator"; import parse from "./utils/parse.js"; import invariant from "./invariant.js"; import traverseFast from "./utils/traverse-fast.js"; @@ -1077,35 +1073,6 @@ export class LexicalEnvironment { Properties.PutValue(this.realm, globalValue, rvalue); } - partiallyEvaluateCompletionDeref( - ast: BabelNode, - strictCode: boolean, - metadata?: any - ): [Completion | Value, BabelNode, Array] { - let [result, partial_ast, partial_io] = this.partiallyEvaluateCompletion(ast, strictCode, metadata); - if (result instanceof Reference) { - result = Environment.GetValue(this.realm, result); - } - return [result, partial_ast, partial_io]; - } - - partiallyEvaluateCompletion( - ast: BabelNode, - strictCode: boolean, - metadata?: any - ): [Completion | Reference | Value, BabelNode, Array] { - try { - return this.partiallyEvaluate(ast, strictCode, metadata); - } catch (err) { - if (err instanceof Completion) return [err, ast, []]; - if (err instanceof Error) - // rethrowing Error should preserve stack trace - throw err; - // let's wrap into a proper Error to create stack trace - throw new FatalError(err); - } - } - evaluateCompletionDeref(ast: BabelNode, strictCode: boolean, metadata?: any): AbruptCompletion | Value { let result = this.evaluateCompletion(ast, strictCode, metadata); if (result instanceof Reference) result = Environment.GetValue(this.realm, result); @@ -1216,37 +1183,6 @@ export class LexicalEnvironment { return [Environment.GetValue(this.realm, res), code]; } - executePartialEvaluator( - sources: Array, - options: PartialEvaluatorOptions = defaultOptions, - sourceType: SourceType = "script" - ): AbruptCompletion | { code: string, map?: SourceMap } { - let [ast, code] = this.concatenateAndParse(sources, sourceType); - let context = new ExecutionContext(); - context.lexicalEnvironment = this; - context.variableEnvironment = this; - context.realm = this.realm; - this.realm.pushContext(context); - let partialAST; - try { - [, partialAST] = this.partiallyEvaluateCompletionDeref(ast, false); - } finally { - this.realm.popContext(context); - this.realm.onDestroyScope(context.lexicalEnvironment); - if (!this.destroyed) this.realm.onDestroyScope(this); - invariant( - this.realm.activeLexicalEnvironments.size === 0, - `expected 0 active lexical environments, got ${this.realm.activeLexicalEnvironments.size}` - ); - } - invariant(partialAST.type === "File"); - let fileAst = ((partialAST: any): BabelNodeFile); - let prog = t.program(fileAst.program.body, ast.program.directives); - this.fixupFilenames(prog); - // The type signature for generate is not complete, hence the any - return generate(prog, { sourceMaps: options.sourceMaps }, (code: any)); - } - execute( code: string, filename: string, @@ -1434,20 +1370,6 @@ export class LexicalEnvironment { if (result instanceof Reference) result = Environment.GetValue(this.realm, result); return result; } - - partiallyEvaluate( - ast: BabelNode, - strictCode: boolean, - metadata?: any - ): [Completion | Reference | Value, BabelNode, Array] { - let partialEvaluator = this.realm.partialEvaluators[(ast.type: string)]; - if (partialEvaluator) { - return partialEvaluator(ast, strictCode, this, this.realm, metadata); - } - - let err = new TypeError(`Unsupported node type ${ast.type}`); - throw err; - } } // ECMA262 6.2.3 diff --git a/src/evaluators/BinaryExpression.js b/src/evaluators/BinaryExpression.js index a4fffd8dc..4ee3a49d3 100644 --- a/src/evaluators/BinaryExpression.js +++ b/src/evaluators/BinaryExpression.js @@ -26,7 +26,6 @@ import { UndefinedValue, Value, } from "../values/index.js"; -import { AbruptCompletion, PossiblyNormalCompletion, SimpleNormalCompletion } from "../completions.js"; import { Environment, Havoc, To } from "../singletons.js"; import type { BabelBinaryOperator, BabelNodeBinaryExpression, BabelNodeSourceLocation } from "@babel/types"; import { createOperationDescriptor } from "../utils/generator.js"; @@ -284,23 +283,8 @@ export function computeBinary( } if (isPure && effects) { - // Note that the effects of (non joining) abrupt branches are not included - // in effects, but are tracked separately inside completion. realm.applyEffects(effects); - let completion = effects.result; - if (completion instanceof PossiblyNormalCompletion) { - // in this case one of the branches may complete abruptly, which means that - // not all control flow branches join into one flow at this point. - // Consequently we have to continue tracking changes until the point where - // all the branches come together into one. - completion = realm.composeWithSavedCompletion(completion); - } else if (completion instanceof SimpleNormalCompletion) { - completion = completion.value; - } - // return or throw completion - if (completion instanceof AbruptCompletion) throw completion; - invariant(completion instanceof Value); - return completion; + return realm.returnOrThrowCompletion(effects.result); } // If this ended up reporting an error, it might not be pure, so we'll leave it in diff --git a/src/evaluators/BreakStatement.js b/src/evaluators/BreakStatement.js index f79f836f4..cd1b67431 100644 --- a/src/evaluators/BreakStatement.js +++ b/src/evaluators/BreakStatement.js @@ -21,5 +21,5 @@ export default function( env: LexicalEnvironment, realm: Realm ): Value { - throw new BreakCompletion(realm.intrinsics.empty, undefined, ast.loc, ast.label && ast.label.name); + throw new BreakCompletion(realm.intrinsics.empty, ast.loc, ast.label && ast.label.name); } diff --git a/src/evaluators/CallExpression.js b/src/evaluators/CallExpression.js index 0b482aa83..6cca620c9 100644 --- a/src/evaluators/CallExpression.js +++ b/src/evaluators/CallExpression.js @@ -10,7 +10,6 @@ /* @flow */ import { CompilerDiagnostic, FatalError } from "../errors.js"; -import { AbruptCompletion, Completion, PossiblyNormalCompletion, SimpleNormalCompletion } from "../completions.js"; import type { Realm } from "../realm.js"; import { type LexicalEnvironment, type BaseValue, mightBecomeAnObject } from "../environment.js"; import { EnvironmentRecord } from "../environment.js"; @@ -217,29 +216,9 @@ function callBothFunctionsAndJoinTheirEffects( "callBothFunctionsAndJoinTheirEffects/2" ); - let r1 = e1.result; - if (r1 instanceof Completion) r1 = r1.shallowCloneWithoutEffects(); - let r2 = e2.result; - if (r2 instanceof Completion) r2 = r2.shallowCloneWithoutEffects(); - let joinedEffects = Join.joinForkOrChoose(realm, cond, e1.shallowCloneWithResult(r1), e2.shallowCloneWithResult(r2)); - let completion = joinedEffects.result; - if (completion instanceof SimpleNormalCompletion) completion = completion.value; - if (completion instanceof PossiblyNormalCompletion) { - // in this case one of the branches may complete abruptly, which means that - // not all control flow branches join into one flow at this point. - // Consequently we have to continue tracking changes until the point where - // all the branches come together into one. - completion = realm.composeWithSavedCompletion(completion); - } - - // Note that the effects of (non joining) abrupt branches are not included - // in joinedEffects, but are tracked separately inside completion. + let joinedEffects = Join.joinEffects(cond, e1, e2); realm.applyEffects(joinedEffects); - - // return or throw completion - if (completion instanceof AbruptCompletion) throw completion; - invariant(completion instanceof Value); - return completion; + return realm.returnOrThrowCompletion(joinedEffects.result); } function generateRuntimeCall( @@ -308,22 +287,8 @@ function tryToEvaluateCallOrLeaveAsAbstract( } finally { realm.suppressDiagnostics = savedSuppressDiagnostics; } - // Note that the effects of (non joining) abrupt branches are not included - // in effects, but are tracked separately inside completion. realm.applyEffects(effects); - let completion = effects.result; - if (completion instanceof PossiblyNormalCompletion) { - // in this case one of the branches may complete abruptly, which means that - // not all control flow branches join into one flow at this point. - // Consequently we have to continue tracking changes until the point where - // all the branches come together into one. - completion = realm.composeWithSavedCompletion(completion); - } - // return or throw completion - if (completion instanceof AbruptCompletion) throw completion; - if (completion instanceof SimpleNormalCompletion) completion = completion.value; - invariant(completion instanceof Value); - return completion; + return realm.returnOrThrowCompletion(effects.result); } function EvaluateCall( diff --git a/src/evaluators/ContinueStatement.js b/src/evaluators/ContinueStatement.js index 2e2d3e722..7e61d836b 100644 --- a/src/evaluators/ContinueStatement.js +++ b/src/evaluators/ContinueStatement.js @@ -21,5 +21,5 @@ export default function( env: LexicalEnvironment, realm: Realm ): Value { - throw new ContinueCompletion(realm.intrinsics.empty, undefined, ast.loc, ast.label && ast.label.name); + throw new ContinueCompletion(realm.intrinsics.empty, ast.loc, ast.label && ast.label.name); } diff --git a/src/evaluators/DoWhileStatement.js b/src/evaluators/DoWhileStatement.js index 3ad17a317..fdf698ccf 100644 --- a/src/evaluators/DoWhileStatement.js +++ b/src/evaluators/DoWhileStatement.js @@ -15,8 +15,8 @@ import { FatalError } from "../errors.js"; import { Value } from "../values/index.js"; import { EmptyValue } from "../values/index.js"; import { UpdateEmpty } from "../methods/index.js"; -import { LoopContinues, InternalGetResultValue, TryToApplyEffectsOfJoiningBranches } from "./ForOfStatement.js"; -import { AbruptCompletion, BreakCompletion, ForkedAbruptCompletion, SimpleNormalCompletion } from "../completions.js"; +import { LoopContinues, InternalGetResultValue } from "./ForOfStatement.js"; +import { AbruptCompletion, BreakCompletion, SimpleNormalCompletion } from "../completions.js"; import { Environment, To } from "../singletons.js"; import invariant from "../invariant.js"; import type { BabelNodeDoWhileStatement } from "@babel/types"; @@ -38,9 +38,8 @@ export default function( while (true) { // a. Let stmt be the result of evaluating Statement. let stmt = env.evaluateCompletion(body, strictCode); - //todo: check if stmt is a PossiblyNormalCompletion and defer to fixpoint computation below + //todo: check if stmt is JoinedNormalAndAbruptCompletions and defer to fixpoint computation below invariant(stmt instanceof Value || stmt instanceof AbruptCompletion); - if (stmt instanceof ForkedAbruptCompletion) stmt = TryToApplyEffectsOfJoiningBranches(realm, stmt); // b. If LoopContinues(stmt, labelSet) is false, return Completion(UpdateEmpty(stmt, V)). if (LoopContinues(realm, stmt, labelSet) === false) { diff --git a/src/evaluators/ForOfStatement.js b/src/evaluators/ForOfStatement.js index 20d421bcb..a1351bd98 100644 --- a/src/evaluators/ForOfStatement.js +++ b/src/evaluators/ForOfStatement.js @@ -14,7 +14,14 @@ import type { LexicalEnvironment } from "../environment.js"; import { CompilerDiagnostic, FatalError } from "../errors.js"; import { DeclarativeEnvironmentRecord } from "../environment.js"; import { Reference } from "../environment.js"; -import { BreakCompletion, AbruptCompletion, ContinueCompletion, ForkedAbruptCompletion } from "../completions.js"; +import { + AbruptCompletion, + BreakCompletion, + Completion, + ContinueCompletion, + JoinedAbruptCompletions, + NormalCompletion, +} from "../completions.js"; import { AbstractObjectValue, AbstractValue, @@ -33,7 +40,7 @@ import { DestructuringAssignmentEvaluation, GetIterator, } from "../methods/index.js"; -import { Environment, Join, Properties, To } from "../singletons.js"; +import { Environment, Properties, To } from "../singletons.js"; import type { BabelNode, BabelNodeForOfStatement, @@ -45,37 +52,23 @@ import type { export type IterationKind = "iterate" | "enumerate"; export type LhsKind = "lexicalBinding" | "varBinding" | "assignment"; -export function InternalGetResultValue(realm: Realm, result: Value | AbruptCompletion): Value { - if (result instanceof AbruptCompletion) { +export function InternalGetResultValue(realm: Realm, result: Value | Completion): Value { + if (result instanceof Completion) { return result.value; } else { return result; } } -export function TryToApplyEffectsOfJoiningBranches(realm: Realm, c: ForkedAbruptCompletion): AbruptCompletion { - let joinedEffects = Join.joinNestedEffects(realm, c); - let jr = joinedEffects.result; - invariant(jr instanceof AbruptCompletion); - if (jr instanceof ContinueCompletion || jr instanceof BreakCompletion) { - // The end of a loop body is join point for these. - realm.applyEffects(joinedEffects, "end of loop body"); - } else if (jr instanceof ForkedAbruptCompletion) { - if (jr.containsBreakOrContinue()) { - // todo: extract the continue completions, apply those while stashing the other completions - // in realm.savedCompletion. This may need customization depending on the caller. - AbstractValue.reportIntrospectionError(jr.joinCondition); - throw new FatalError(); - } - } - return jr; -} - // ECMA262 13.7.1.2 -export function LoopContinues(realm: Realm, completion: Value | AbruptCompletion, labelSet: ?Array): boolean { +export function LoopContinues(realm: Realm, completion: Value | Completion, labelSet: ?Array): boolean { // 1. If completion.[[Type]] is normal, return true. - if (completion instanceof Value) return true; - invariant(completion instanceof AbruptCompletion); + if (completion instanceof Value || completion instanceof NormalCompletion) return true; + if (completion instanceof JoinedAbruptCompletions) { + return ( + LoopContinues(realm, completion.consequent, labelSet) || LoopContinues(realm, completion.alternate, labelSet) + ); + } // 2. If completion.[[Type]] is not continue, return false. if (!(completion instanceof ContinueCompletion)) return false; @@ -167,7 +160,7 @@ export function ForInOfHeadEvaluation( // a. If exprValue.[[Value]] is null or undefined, then if (exprValue instanceof NullValue || exprValue instanceof UndefinedValue) { // i. Return Completion{[[Type]]: break, [[Value]]: empty, [[Target]]: empty}. - throw new BreakCompletion(realm.intrinsics.empty, undefined, expr.loc, null); + throw new BreakCompletion(realm.intrinsics.empty, expr.loc, null); } // b. Let obj be ToObject(exprValue). @@ -346,7 +339,6 @@ export function ForInOfBodyEvaluation( // i. Let result be the result of evaluating stmt. let result = env.evaluateCompletion(stmt, strictCode); invariant(result instanceof Value || result instanceof AbruptCompletion); - if (result instanceof ForkedAbruptCompletion) result = TryToApplyEffectsOfJoiningBranches(realm, result); // j. Set the running execution context's LexicalEnvironment to oldEnv. diff --git a/src/evaluators/ForStatement.js b/src/evaluators/ForStatement.js index 7f38ff543..878a14340 100644 --- a/src/evaluators/ForStatement.js +++ b/src/evaluators/ForStatement.js @@ -10,7 +10,7 @@ /* @flow */ import type { LexicalEnvironment } from "../environment.js"; -import type { Realm } from "../realm.js"; +import { Realm } from "../realm.js"; import { AbstractValue, Value, @@ -23,19 +23,19 @@ import { BreakCompletion, Completion, ContinueCompletion, - ForkedAbruptCompletion, - PossiblyNormalCompletion, + JoinedAbruptCompletions, + JoinedNormalAndAbruptCompletions, ReturnCompletion, - SimpleNormalCompletion, ThrowCompletion, + SimpleNormalCompletion, } from "../completions.js"; import traverse from "@babel/traverse"; import type { BabelTraversePath } from "@babel/traverse"; import { TypesDomain, ValuesDomain } from "../domains/index.js"; import { CompilerDiagnostic, FatalError } from "../errors.js"; import { UpdateEmpty } from "../methods/index.js"; -import { LoopContinues, InternalGetResultValue, TryToApplyEffectsOfJoiningBranches } from "./ForOfStatement.js"; -import { Environment, Functions, Havoc, Join, To } from "../singletons.js"; +import { LoopContinues, InternalGetResultValue } from "./ForOfStatement.js"; +import { Environment, Functions, Havoc, To } from "../singletons.js"; import invariant from "../invariant.js"; import * as t from "@babel/types"; import type { BabelNodeExpression, BabelNodeForStatement, BabelNodeBlockStatement } from "@babel/types"; @@ -103,6 +103,7 @@ function ForBodyEvaluation( // 3. Repeat while (true) { + let result; // a. If test is not [empty], then if (test) { // i. Let testRef be the result of evaluating test. @@ -113,37 +114,44 @@ function ForBodyEvaluation( // iii. If ToBoolean(testValue) is false, return NormalCompletion(V). if (!To.ToBooleanPartial(realm, testValue)) { - // joinAllLoopExits does not handle labeled break/continue, so only use it when doing AI - if (realm.useAbstractInterpretation) return joinAllLoopExits(V); + result = Functions.incorporateSavedCompletion(realm, V); + if (result instanceof JoinedNormalAndAbruptCompletions) { + let selector = c => c instanceof BreakCompletion && !c.target; + result = Completion.normalizeSelectedCompletions(selector, result); + result = realm.composeWithSavedCompletion(result); + } return V; } } // b. Let result be the result of evaluating stmt. - let result = env.evaluateCompletion(stmt, strictCode); + result = env.evaluateCompletion(stmt, strictCode); invariant(result instanceof Value || result instanceof AbruptCompletion); - if (result instanceof ForkedAbruptCompletion) result = TryToApplyEffectsOfJoiningBranches(realm, result); + + // this is a join point for break and continue completions + result = Functions.incorporateSavedCompletion(realm, result); + invariant(result !== undefined); + if (result instanceof Value) result = new SimpleNormalCompletion(result); // c. If LoopContinues(result, labelSet) is false, return Completion(UpdateEmpty(result, V)). if (!LoopContinues(realm, result, labelSet)) { invariant(result instanceof AbruptCompletion); - // joinAllLoopExits does not handle labeled break/continue, so only use it when doing AI - if (realm.useAbstractInterpretation) { - result = UpdateEmpty(realm, result, V); - invariant(result instanceof AbruptCompletion); - return joinAllLoopExits(result); - } // ECMA262 13.1.7 if (result instanceof BreakCompletion) { if (!result.target) return (UpdateEmpty(realm, result, V): any).value; + } else if (result instanceof JoinedAbruptCompletions) { + let selector = c => c instanceof BreakCompletion && !c.target; + if (result.containsSelectedCompletion(selector)) { + result = Completion.normalizeSelectedCompletions(selector, result); + } } - throw UpdateEmpty(realm, result, V); - } else if (realm.useAbstractInterpretation) { - // This is a join point for conditional continue completions lurking in realm.savedCompletion - if (containsContinueCompletion(realm.savedCompletion)) { - result = joinAllLoopContinues(result); - } + return realm.returnOrThrowCompletion(result); } + if (result instanceof JoinedNormalAndAbruptCompletions) { + result = Completion.normalizeSelectedCompletions(c => c instanceof ContinueCompletion, result); + } + invariant(result instanceof Completion); + result = realm.composeWithSavedCompletion(result); // d. If result.[[Value]] is not empty, let V be result.[[Value]]. let resultValue = InternalGetResultValue(realm, result); @@ -161,14 +169,14 @@ function ForBodyEvaluation( // ii. Perform ? GetValue(incRef). Environment.GetValue(realm, incRef); } else if (realm.useAbstractInterpretation) { - // If we have no increment and we've hit 100 iterations of trying to evaluate + // If we have no increment and we've hit 12 iterations of trying to evaluate // this loop body, then see if we have a break, return or throw completion in a // guarded condition and fail if it does. We already have logic to guard // against loops that are actually infinite. However, because there may be so // many forked execution paths, and they're non linear, then it might // computationally lead to a something that seems like an infinite loop. possibleInfiniteLoopIterations++; - if (possibleInfiniteLoopIterations > 100) { + if (possibleInfiniteLoopIterations > 12) { failIfContainsBreakOrReturnOrThrowCompletion(realm.savedCompletion); } } @@ -187,136 +195,11 @@ function ForBodyEvaluation( realm.handleError(diagnostic); throw new FatalError(); } - if (c instanceof PossiblyNormalCompletion || c instanceof ForkedAbruptCompletion) { + if (c instanceof JoinedAbruptCompletions || c instanceof JoinedNormalAndAbruptCompletions) { failIfContainsBreakOrReturnOrThrowCompletion(c.consequent); failIfContainsBreakOrReturnOrThrowCompletion(c.alternate); } } - - function failIfContainsBreakOrContinueCompletionWithNonLocalTarget(c: void | Completion | Value) { - if (c === undefined) return; - if (c instanceof ContinueCompletion || c instanceof BreakCompletion) { - if (!c.target) return; - if (labelSet && labelSet.indexOf(c.target) >= 0) { - c.target = null; - return; - } - let diagnostic = new CompilerDiagnostic( - "break or continue with target cannot be guarded by abstract condition", - c.location, - "PP0034", - "FatalError" - ); - realm.handleError(diagnostic); - throw new FatalError(); - } - if (c instanceof PossiblyNormalCompletion || c instanceof ForkedAbruptCompletion) { - failIfContainsBreakOrContinueCompletionWithNonLocalTarget(c.consequent); - failIfContainsBreakOrContinueCompletionWithNonLocalTarget(c.alternate); - } - } - - function containsContinueCompletion(c: void | Completion | Value) { - if (c === undefined) return false; - if (c instanceof ContinueCompletion) { - if (!c.target) return true; - if (labelSet && labelSet.indexOf(c.target) >= 0) { - c.target = null; - return true; - } - return false; - } - if (c instanceof PossiblyNormalCompletion || c instanceof ForkedAbruptCompletion) - return containsContinueCompletion(c.consequent) || containsContinueCompletion(c.alternate); - return false; - } - - function joinAllLoopContinues( - valueOrCompletionAtLoopContinuePoint: Value | AbruptCompletion - ): Value | AbruptCompletion { - // We are about start the next loop iteration and this presents a join point where all non loop breaking abrupt - // control flows converge into a single flow using their joined effects as the new state. - failIfContainsBreakOrContinueCompletionWithNonLocalTarget(realm.savedCompletion); - - // Incorporate the savedCompletion (we should only get called if there is one). - invariant(realm.savedCompletion !== undefined); - if (valueOrCompletionAtLoopContinuePoint instanceof Value) - valueOrCompletionAtLoopContinuePoint = new ContinueCompletion(valueOrCompletionAtLoopContinuePoint, undefined); - let abruptCompletion = Functions.incorporateSavedCompletion(realm, valueOrCompletionAtLoopContinuePoint); - invariant(abruptCompletion instanceof AbruptCompletion); - - // If there is now a single completion, we don't need to join - if (!(abruptCompletion instanceof ForkedAbruptCompletion)) return abruptCompletion; - invariant(containsContinueCompletion(abruptCompletion)); - - // Apply the joined effects of continue completions to the current state since these now join the normal path - let joinedContinueEffects = Join.extractAndJoinCompletionsOfType(ContinueCompletion, realm, abruptCompletion); - realm.applyEffects(joinedContinueEffects); - let c = joinedContinueEffects.result; - invariant(c instanceof ContinueCompletion); - - // We now make a PossiblyNormalCompletion out of abruptCompletion. - // extractAndJoinCompletionsOfType helped with this by cheating and turning all of its nested completions - // that contain continue completions into PossiblyNormalCompletions. - let remainingCompletions = abruptCompletion.transferChildrenToPossiblyNormalCompletion(); - - // At this stage there can still be other kinds of abrupt completions left inside abruptCompletion. If not just return. - let stillAbrupt = - remainingCompletions.containsCompletion(BreakCompletion) || - remainingCompletions.containsCompletion(ReturnCompletion) || - remainingCompletions.containsCompletion(ThrowCompletion); - if (!stillAbrupt) return c; - - // Stash the remaining completions in the realm start tracking the effects that need to be appended - // to the normal branch at the next join point. - realm.savedCompletion = remainingCompletions; - realm.captureEffects(remainingCompletions); // so that we can join the normal path wtih them later on - return c; - } - - function joinAllLoopExits(valueOrCompletionAtUnconditionalExit: Value | AbruptCompletion): Value { - // We are about the leave this loop and this presents a join point where all loop breaking control flows - // converge into a single flow using their joined effects as the new state. - failIfContainsBreakOrContinueCompletionWithNonLocalTarget(realm.savedCompletion); - - // Incorporate the savedCompletion if there is one. - if (valueOrCompletionAtUnconditionalExit instanceof Value) - valueOrCompletionAtUnconditionalExit = new BreakCompletion(valueOrCompletionAtUnconditionalExit, undefined); - let abruptCompletion = Functions.incorporateSavedCompletion(realm, valueOrCompletionAtUnconditionalExit); - invariant(abruptCompletion instanceof AbruptCompletion); - - // If there is now a single completion, we don't need to join - if (abruptCompletion instanceof BreakCompletion) return (UpdateEmpty(realm, abruptCompletion, V): any).value; - if (!(abruptCompletion instanceof ForkedAbruptCompletion)) throw abruptCompletion; - - // If there are no breaks, we don't need to join - if (!abruptCompletion.containsCompletion(BreakCompletion)) throw abruptCompletion; - - // Apply the joined effects of break completions to the current state since these now join the normal path - let joinedBreakEffects = Join.extractAndJoinCompletionsOfType(BreakCompletion, realm, abruptCompletion); - realm.applyEffects(joinedBreakEffects); - let c = joinedBreakEffects.result; - invariant(c instanceof BreakCompletion); - - // We now make a PossiblyNormalCompletion out of abruptCompletion. - // extractAndJoinCompletionsOfType helped with this by cheating and turning all of its nested completions - // that contain continue completions into PossiblyNormalCompletions. - let remainingCompletions = abruptCompletion.transferChildrenToPossiblyNormalCompletion(); - - // At this stage there can still be other kinds of abrupt completions left inside abruptCompletion. If not just return. - let stillAbrupt = - remainingCompletions.containsCompletion(ReturnCompletion) || - remainingCompletions.containsCompletion(ThrowCompletion); - if (!stillAbrupt) return (UpdateEmpty(realm, c, V): any).value; - - // Stash the remaining completions in the realm start tracking the effects that need to be appended - // to the normal branch at the next join point. - realm.savedCompletion = remainingCompletions; - realm.captureEffects(remainingCompletions); // so that we can join the normal path wtih them later on - - // ECMA262 13.1.7 - return (UpdateEmpty(realm, c, V): any).value; - } } let BailOutWrapperClosureRefVisitor = { @@ -490,22 +373,8 @@ function tryToEvaluateForStatementOrLeaveAsAbstract( } finally { realm.suppressDiagnostics = savedSuppressDiagnostics; } - // Note that the effects of (non joining) abrupt branches are not included - // in effects, but are tracked separately inside completion. realm.applyEffects(effects); - let completion = effects.result; - if (completion instanceof PossiblyNormalCompletion) { - // in this case one of the branches may complete abruptly, which means that - // not all control flow branches join into one flow at this point. - // Consequently we have to continue tracking changes until the point where - // all the branches come together into one. - completion = realm.composeWithSavedCompletion(completion); - } - // return or throw completion - if (completion instanceof AbruptCompletion) throw completion; - if (completion instanceof SimpleNormalCompletion) completion = completion.value; - invariant(completion instanceof Value); - return completion; + return realm.returnOrThrowCompletion(effects.result); } // ECMA262 13.7.4.7 diff --git a/src/evaluators/LabeledStatement.js b/src/evaluators/LabeledStatement.js index eb4c23809..f429b993e 100644 --- a/src/evaluators/LabeledStatement.js +++ b/src/evaluators/LabeledStatement.js @@ -13,7 +13,12 @@ import type { Realm } from "../realm.js"; import type { LexicalEnvironment } from "../environment.js"; import { Value } from "../values/index.js"; import type { Reference } from "../environment.js"; -import { BreakCompletion } from "../completions.js"; +import { + BreakCompletion, + Completion, + JoinedAbruptCompletions, + JoinedNormalAndAbruptCompletions, +} from "../completions.js"; import type { BabelNode, BabelNodeLabeledStatement, BabelNodeVariableDeclaration } from "@babel/types"; import invariant from "../invariant.js"; @@ -44,6 +49,15 @@ function LabelledEvaluation( if (stmtResult instanceof BreakCompletion && stmtResult.target === label) { // a. Let stmtResult be NormalCompletion(stmtResult.[[Value]]). normalCompletionStmtResult = stmtResult.value; + } else if ( + stmtResult instanceof JoinedAbruptCompletions || + stmtResult instanceof JoinedNormalAndAbruptCompletions + ) { + let nc = Completion.normalizeSelectedCompletions( + c => c instanceof BreakCompletion && c.target === label, + stmtResult + ); + return realm.returnOrThrowCompletion(nc); } else { // 5. Return Completion(stmtResult). throw stmtResult; diff --git a/src/evaluators/LogicalExpression.js b/src/evaluators/LogicalExpression.js index b693878ac..badf606bd 100644 --- a/src/evaluators/LogicalExpression.js +++ b/src/evaluators/LogicalExpression.js @@ -11,7 +11,7 @@ import type { Realm } from "../realm.js"; import { Effects } from "../realm.js"; -import { AbruptCompletion, PossiblyNormalCompletion, SimpleNormalCompletion } from "../completions.js"; +import { SimpleNormalCompletion } from "../completions.js"; import { InfeasiblePathError } from "../errors.js"; import { construct_empty_effects } from "../realm.js"; import type { LexicalEnvironment } from "../environment.js"; @@ -89,22 +89,14 @@ export default function( // use lval as is for the join condition. let joinedEffects; if (ast.operator === "&&") { - joinedEffects = Join.joinForkOrChoose( - realm, - lval, - new Effects( - result2.shallowCloneWithoutEffects(), - generator2, - modifiedBindings2, - modifiedProperties2, - createdObjects2 - ), + joinedEffects = Join.joinEffects( + lcond, + new Effects(result2, generator2, modifiedBindings2, modifiedProperties2, createdObjects2), new Effects(new SimpleNormalCompletion(lval), generator1, modifiedBindings1, modifiedProperties1, createdObjects1) ); } else { - joinedEffects = Join.joinForkOrChoose( - realm, - lval, + joinedEffects = Join.joinEffects( + lcond, new Effects( new SimpleNormalCompletion(lval), generator1, @@ -112,38 +104,18 @@ export default function( modifiedProperties1, createdObjects1 ), - new Effects( - result2.shallowCloneWithoutEffects(), - generator2, - modifiedBindings2, - modifiedProperties2, - createdObjects2 - ) + new Effects(result2, generator2, modifiedBindings2, modifiedProperties2, createdObjects2) ); } - let completion = joinedEffects.result; - if (completion instanceof PossiblyNormalCompletion) { - // in this case the evaluation of ast.right may complete abruptly, which means that - // not all control flow branches join into one flow at this point. - // Consequently we have to continue tracking changes until the point where - // all the branches come together into one. - completion = realm.composeWithSavedCompletion(completion); - } - // Note that the effects of (non joining) abrupt branches are not included - // in joinedEffects, but are tracked separately inside completion. - realm.applyEffects(joinedEffects); - // return or throw completion - if (completion instanceof AbruptCompletion) throw completion; - if (completion instanceof SimpleNormalCompletion) completion = completion.value; - if (result2 instanceof SimpleNormalCompletion) result2 = result2.value; - invariant(completion instanceof Value); - if (lval instanceof Value && result2 instanceof Value) { - // joinForkOrChoose does the right thing for the side effects of the second expression but for the result the join + realm.applyEffects(joinedEffects); + let completion = realm.returnOrThrowCompletion(joinedEffects.result); + if (lval instanceof Value && result2.value instanceof Value) { + // joinEffects does the right thing for the side effects of the second expression but for the result the join // produces a conditional expressions of the form (a ? b : a) for a && b and (a ? a : b) for a || b // Rather than look for this pattern everywhere, we override this behavior and replace the completion with // the actual logical operator. This helps with simplification and reasoning when dealing with path conditions. - completion = AbstractValue.createFromLogicalOp(realm, ast.operator, lval, result2, ast.loc); + completion = AbstractValue.createFromLogicalOp(realm, ast.operator, lval, result2.value, ast.loc); } return completion; } diff --git a/src/evaluators/NewExpression.js b/src/evaluators/NewExpression.js index e2b2776b8..80a1d85a7 100644 --- a/src/evaluators/NewExpression.js +++ b/src/evaluators/NewExpression.js @@ -11,7 +11,6 @@ import type { Realm } from "../realm.js"; import type { LexicalEnvironment } from "../environment.js"; -import { AbruptCompletion, PossiblyNormalCompletion, SimpleNormalCompletion } from "../completions.js"; import { TypesDomain, ValuesDomain } from "../domains/index.js"; import { ObjectValue, Value, AbstractObjectValue, AbstractValue } from "../values/index.js"; import { Environment, Havoc } from "../singletons.js"; @@ -113,21 +112,8 @@ function tryToEvaluateConstructOrLeaveAsAbstract( throw error; } } - // Note that the effects of (non joining) abrupt branches are not included - // in joinedEffects, but are tracked separately inside completion. realm.applyEffects(effects); - let completion = effects.result; - if (completion instanceof PossiblyNormalCompletion) { - // in this case one of the branches may complete abruptly, which means that - // not all control flow branches join into one flow at this point. - // Consequently we have to continue tracking changes until the point where - // all the branches come together into one. - completion = realm.composeWithSavedCompletion(completion); - } - - // return or throw completion - if (completion instanceof AbruptCompletion) throw completion; - if (completion instanceof SimpleNormalCompletion) completion = completion.value; + let completion = realm.returnOrThrowCompletion(effects.result); invariant(completion instanceof ObjectValue || completion instanceof AbstractObjectValue); return completion; } diff --git a/src/evaluators/Program.js b/src/evaluators/Program.js index 5399347c1..52ecb0a93 100644 --- a/src/evaluators/Program.js +++ b/src/evaluators/Program.js @@ -9,13 +9,18 @@ /* @flow */ -import { AbruptCompletion, ForkedAbruptCompletion, PossiblyNormalCompletion, ThrowCompletion } from "../completions.js"; +import { + AbruptCompletion, + Completion, + JoinedAbruptCompletions, + JoinedNormalAndAbruptCompletions, + ThrowCompletion, +} from "../completions.js"; import type { Realm } from "../realm.js"; import type { LexicalEnvironment } from "../environment.js"; import { Value, EmptyValue } from "../values/index.js"; import { GlobalEnvironmentRecord } from "../environment.js"; import { Environment, Functions, Join } from "../singletons.js"; -import { Generator } from "../utils/generator.js"; import IsStrict from "../utils/strict.js"; import invariant from "../invariant.js"; import traverseFast from "../utils/traverse-fast.js"; @@ -223,30 +228,17 @@ export default function(ast: BabelNodeProgram, strictCode: boolean, env: Lexical GlobalDeclarationInstantiation(realm, ast, env, strictCode); - let val; + let val, res; for (let node of ast.body) { if (node.type !== "FunctionDeclaration") { - let res = env.evaluateCompletionDeref(node, strictCode); - if (res instanceof AbruptCompletion) { - if (!realm.useAbstractInterpretation) throw res; - let generator = realm.generator; - invariant(generator !== undefined); - // We are about the leave this program and this presents a join point where all control flows - // converge into a single flow using the joined effects as the new state. - res = Functions.incorporateSavedCompletion(realm, res); - if (res instanceof ForkedAbruptCompletion && res.containsCompletion(ThrowCompletion)) { - // The global state is now at the point where the first fork occurred. - let joinedEffects = Join.joinNestedEffects(realm, res); - realm.applyEffects(joinedEffects); - res = joinedEffects.result; - } else if (res instanceof ThrowCompletion) { - generator.emitThrow(res.value); - res = realm.intrinsics.undefined; - } else { - invariant(false); // other kinds of abrupt completions should not get this far - } - break; + res = env.evaluateCompletionDeref(node, strictCode); + if (res instanceof AbruptCompletion && !realm.useAbstractInterpretation) throw res; + res = Functions.incorporateSavedCompletion(realm, res); + if (res instanceof Completion) { + emitThrowStatementsIfNeeded(res); + if (res instanceof ThrowCompletion) return res.value; // Program ends here at runtime, so don't carry on + res = res.value; } if (!(res instanceof EmptyValue)) { val = res; @@ -262,35 +254,33 @@ export default function(ast: BabelNodeProgram, strictCode: boolean, env: Lexical // We are about to leave this program and this presents a join point where all control flows // converge into a single flow and the joined effects become the final state. + invariant(val === undefined || val instanceof Value); if (val instanceof Value) { - let res = Functions.incorporateSavedCompletion(realm, val); - if (res instanceof PossiblyNormalCompletion) { - // Get state to be joined in - let e = realm.getCapturedEffects(); - realm.stopEffectCaptureAndUndoEffects(res); - // The global state is now at the point where the last fork occurred. - if (res.containsCompletion(ThrowCompletion)) { - // Join e with the remaining completions - let normalGenerator = e.generator; - e.generator = new Generator(realm, "dummy", normalGenerator.pathConditions); // This generator comes after everything else. - let r = new ThrowCompletion(realm.intrinsics.empty, e); - let fc = Join.replacePossiblyNormalCompletionWithForkedAbruptCompletion(realm, res, r, e); - let allEffects = Join.extractAndJoinCompletionsOfType(ThrowCompletion, realm, fc); - realm.applyEffects(allEffects, "all code", true); - r = allEffects.result; - invariant(r instanceof ThrowCompletion); - let generator = realm.generator; - invariant(generator !== undefined); - generator.emitConditionalThrow(r.value); - realm.appendGenerator(normalGenerator); - } else { - realm.applyEffects(e, "all code", true); - } - } - } else { - // program was empty. Nothing to do. + res = Functions.incorporateSavedCompletion(realm, val); + if (res instanceof Completion) emitThrowStatementsIfNeeded(res); } - invariant(val === undefined || val instanceof Value); return val || realm.intrinsics.empty; + + function emitThrowStatementsIfNeeded(completion: Completion): void { + let generator = realm.generator; + invariant(generator !== undefined); + if ( + res instanceof ThrowCompletion && + res.value !== realm.intrinsics.__bottomValue && + !(res.value instanceof EmptyValue) + ) { + generator.emitThrow(res.value); + } else if ( + (res instanceof JoinedAbruptCompletions || res instanceof JoinedNormalAndAbruptCompletions) && + res.containsSelectedCompletion(c => c instanceof ThrowCompletion) + ) { + let selector = c => + c instanceof ThrowCompletion && c.value !== realm.intrinsics.__bottomValue && !(c.value instanceof EmptyValue); + generator.emitConditionalThrow(Join.joinValuesOfSelectedCompletions(selector, res)); + res = realm.intrinsics.undefined; + } else { + invariant(res instanceof Value); // other kinds of abrupt completions should not get this far + } + } } diff --git a/src/evaluators/ReturnStatement.js b/src/evaluators/ReturnStatement.js index bddb3f3c4..305fbd718 100644 --- a/src/evaluators/ReturnStatement.js +++ b/src/evaluators/ReturnStatement.js @@ -28,5 +28,5 @@ export default function( } else { arg = realm.intrinsics.undefined; } - throw new ReturnCompletion(arg, undefined, ast.loc); + throw new ReturnCompletion(arg, ast.loc); } diff --git a/src/evaluators/SwitchStatement.js b/src/evaluators/SwitchStatement.js index d7e6e5559..5db1c9af7 100644 --- a/src/evaluators/SwitchStatement.js +++ b/src/evaluators/SwitchStatement.js @@ -11,21 +11,19 @@ import type { Realm } from "../realm.js"; import type { LexicalEnvironment } from "../environment.js"; -import { CompilerDiagnostic, InfeasiblePathError } from "../errors.js"; -import { Reference } from "../environment.js"; +import { InfeasiblePathError } from "../errors.js"; import { computeBinary } from "./BinaryExpression.js"; import { AbruptCompletion, BreakCompletion, - SimpleNormalCompletion, - PossiblyNormalCompletion, Completion, + JoinedAbruptCompletions, + JoinedNormalAndAbruptCompletions, } from "../completions.js"; import { InternalGetResultValue } from "./ForOfStatement.js"; import { EmptyValue, AbstractValue, Value } from "../values/index.js"; import { StrictEqualityComparisonPartial, UpdateEmpty } from "../methods/index.js"; -import { Environment, Path, Join } from "../singletons.js"; -import { FatalError } from "../errors.js"; +import { Environment, Functions, Join, Path } from "../singletons.js"; import type { BabelNodeSwitchStatement, BabelNodeSwitchCase, BabelNodeExpression } from "@babel/types"; import invariant from "../invariant.js"; @@ -62,19 +60,10 @@ function AbstractCaseBlockEvaluation( let c = cases[caseIndex]; for (let i = 0; i < c.consequent.length; i += 1) { let node = c.consequent[i]; - let r = env.evaluateCompletion(node, strictCode); - invariant(!(r instanceof Reference)); + let r = env.evaluateCompletionDeref(node, strictCode); - if (r instanceof PossiblyNormalCompletion) { - // TODO correct handling of PossiblyNormal and AbruptCompletion - let diagnostic = new CompilerDiagnostic( - "case block containing a throw, return or continue is not yet supported", - r.location, - "PP0027", - "FatalError" - ); - realm.handleError(diagnostic); - throw new FatalError(); + if (r instanceof JoinedNormalAndAbruptCompletions) { + r = realm.composeWithSavedCompletion(r); } result = UpdateEmpty(realm, r, result); @@ -84,19 +73,21 @@ function AbstractCaseBlockEvaluation( if (result instanceof Completion) break; caseIndex++; } + let sc = Functions.incorporateSavedCompletion(realm, result); + invariant(sc !== undefined); + result = sc; - if (result instanceof BreakCompletion) { + if (result instanceof JoinedAbruptCompletions || result instanceof JoinedNormalAndAbruptCompletions) { + let selector = c => c instanceof BreakCompletion && !c.target; + let jc = AbstractValue.createJoinConditionForSelectedCompletions(selector, result); + let jv = AbstractValue.createFromConditionalOp(realm, jc, realm.intrinsics.empty, result.value); + result = Completion.normalizeSelectedCompletions(selector, result); + realm.composeWithSavedCompletion(result); + return jv; + } else if (result instanceof BreakCompletion) { return result.value; } else if (result instanceof AbruptCompletion) { - // TODO correct handling of PossiblyNormal and AbruptCompletion - let diagnostic = new CompilerDiagnostic( - "case block containing a throw, return or continue is not yet supported", - result.location, - "PP0027", - "FatalError" - ); - realm.handleError(diagnostic); - throw new FatalError(); + throw result; } else { invariant(result instanceof Value); return result; @@ -177,26 +168,10 @@ function AbstractCaseBlockEvaluation( invariant(trueEffects !== undefined); invariant(falseEffects !== undefined); - let joinedEffects = Join.joinForkOrChoose(realm, selectionResult, trueEffects, falseEffects); - let completion = joinedEffects.result; - if (completion instanceof PossiblyNormalCompletion) { - // in this case one of the branches may complete abruptly, which means that - // not all control flow branches join into one flow at this point. - // Consequently we have to continue tracking changes until the point where - // all the branches come together into one. - completion = realm.composeWithSavedCompletion(completion); - } - // Note that the effects of (non joining) abrupt branches are not included - // in joinedEffects, but are tracked separately inside completion. + let joinedEffects = Join.joinEffects(selectionResult, trueEffects, falseEffects); realm.applyEffects(joinedEffects); - // return or throw completion - if (completion instanceof AbruptCompletion) throw completion; - if (completion instanceof SimpleNormalCompletion) { - completion = completion.value; - } - invariant(completion instanceof Value); - return completion; + return realm.returnOrThrowCompletion(joinedEffects.result); } }; diff --git a/src/evaluators/ThrowStatement.js b/src/evaluators/ThrowStatement.js index 0190571bd..38ad5427a 100644 --- a/src/evaluators/ThrowStatement.js +++ b/src/evaluators/ThrowStatement.js @@ -24,5 +24,5 @@ export default function( ): Value { let exprRef = env.evaluate(ast.argument, strictCode); let exprValue = Environment.GetValue(realm, exprRef); - throw new ThrowCompletion(exprValue, undefined, ast.loc); + throw new ThrowCompletion(exprValue, ast.loc); } diff --git a/src/evaluators/TryStatement.js b/src/evaluators/TryStatement.js index 1e8d41bdb..a1c249b03 100644 --- a/src/evaluators/TryStatement.js +++ b/src/evaluators/TryStatement.js @@ -9,172 +9,96 @@ /* @flow */ -import type { Effects, Realm } from "../realm.js"; +import type { Realm } from "../realm.js"; import { type LexicalEnvironment } from "../environment.js"; import { AbruptCompletion, - ForkedAbruptCompletion, - PossiblyNormalCompletion, + Completion, + JoinedAbruptCompletions, + JoinedNormalAndAbruptCompletions, ThrowCompletion, - SimpleNormalCompletion, - NormalCompletion, } from "../completions.js"; import { UpdateEmpty } from "../methods/index.js"; -import { Functions, Join } from "../singletons.js"; -import { Value } from "../values/index.js"; +import { InfeasiblePathError } from "../errors.js"; +import { construct_empty_effects } from "../realm.js"; +import { Functions, Join, Path } from "../singletons.js"; +import { AbstractValue, Value } from "../values/index.js"; import type { BabelNodeTryStatement } from "@babel/types"; import invariant from "../invariant.js"; export default function(ast: BabelNodeTryStatement, strictCode: boolean, env: LexicalEnvironment, realm: Realm): Value { - let wasInPureTryStatement = realm.isInPureTryStatement; + if (realm.useAbstractInterpretation) return joinTryBlockWithHandlers(ast, strictCode, env, realm); + + let blockRes = env.evaluateCompletionDeref(ast.block, strictCode); + let result = blockRes; + + if (blockRes instanceof ThrowCompletion && ast.handler) { + result = env.evaluateCompletionDeref(ast.handler, strictCode, blockRes); + } + + if (ast.finalizer) { + result = composeResults(result, env.evaluateCompletionDeref(ast.finalizer, strictCode)); + } + + return realm.returnOrThrowCompletion(UpdateEmpty(realm, result, realm.intrinsics.undefined)); +} + +function composeResults(r1: Completion | Value, r2: Completion | Value): Completion | Value { + if (r2 instanceof AbruptCompletion) return r2; + return Join.composeCompletions(r2, r1); +} + +function joinTryBlockWithHandlers( + ast: BabelNodeTryStatement, + strictCode: boolean, + env: LexicalEnvironment, + realm: Realm +): Value { + let savedIsInPureTryStatement = realm.isInPureTryStatement; if (realm.isInPureScope()) { // TODO(1264): This is used to issue a warning if we have abstract function calls in here. // We might not need it once we have full support for handling potential errors. Even // then we might need it to know whether we should bother tracking error handling. realm.isInPureTryStatement = true; } - let blockRes; - try { - blockRes = env.evaluateCompletionDeref(ast.block, strictCode); - } finally { - realm.isInPureTryStatement = wasInPureTryStatement; - } + let blockRes = env.evaluateCompletionDeref(ast.block, strictCode); + // this is a join point for break and continue completions + blockRes = Functions.incorporateSavedCompletion(realm, blockRes); + invariant(blockRes !== undefined); + realm.isInPureTryStatement = savedIsInPureTryStatement; - let handlerRes = blockRes; + let result = blockRes; let handler = ast.handler; - if (handler) { - // The start of the catch handler is a join point where all throw completions come together - blockRes = Functions.incorporateSavedCompletion(realm, blockRes); + let selector = c => c instanceof ThrowCompletion; + if (handler && blockRes instanceof Completion && blockRes.containsSelectedCompletion(selector)) { if (blockRes instanceof ThrowCompletion) { - handlerRes = env.evaluateCompletionDeref(handler, strictCode, blockRes); - // Note: The handler may have introduced new forks - } else if (blockRes instanceof ForkedAbruptCompletion || blockRes instanceof PossiblyNormalCompletion) { - if (blockRes instanceof PossiblyNormalCompletion) { - // The throw completions have not been joined and we are going to keep it that way. - // The current state may have advanced since the time control forked into the various paths recorded in blockRes. - // Update the normal path and restore the global state to what it was at the time of the fork. - let subsequentEffects = realm.getCapturedEffects(blockRes.value); - realm.stopEffectCaptureAndUndoEffects(blockRes); - Join.updatePossiblyNormalCompletionWithSubsequentEffects(realm, blockRes, subsequentEffects); + result = env.evaluateCompletionDeref(handler, strictCode, blockRes); + } else { + invariant(blockRes instanceof JoinedAbruptCompletions || blockRes instanceof JoinedNormalAndAbruptCompletions); + // put the handler under a guard that excludes normal paths from entering it. + let joinCondition = AbstractValue.createJoinConditionForSelectedCompletions(selector, blockRes); + try { + let handlerEffects = Path.withCondition(joinCondition, () => { + invariant(blockRes instanceof Completion); + let joinedThrow = new ThrowCompletion(Join.joinValuesOfSelectedCompletions(selector, blockRes)); + let handlerEval = () => env.evaluateCompletionDeref(handler, strictCode, joinedThrow); + return realm.evaluateForEffects(handlerEval, undefined, "joinTryBlockWithHandlers"); + }); + Completion.makeSelectedCompletionsInfeasible(selector, blockRes); + let emptyEffects = construct_empty_effects(realm, blockRes); + handlerEffects = Join.joinEffects(joinCondition, handlerEffects, emptyEffects); + realm.applyEffects(handlerEffects); + result = handlerEffects.result; + } catch (e) { + if (!(e instanceof InfeasiblePathError)) throw e; + // It turns out that the handler is not reachable after all so just do nothing and carry on } - // Add effects of normal exits from handler to blockRes and apply to global state - let handlerEffects = composeNestedThrowEffectsWithHandler(blockRes); - realm.applyEffects(handlerEffects); - handlerRes = handlerEffects.result; - } else { - // The handler is not invoked, so just carry on. } } - let finalizerRes = handlerRes; if (ast.finalizer) { - // The start of the finalizer is a join point where all threads of control come together. - // However, we choose to keep the threads unjoined and to apply the finalizer separately to each thread. - if (blockRes instanceof PossiblyNormalCompletion || blockRes instanceof ForkedAbruptCompletion) { - // The current global state is a the point of the fork that led to blockRes - // All subsequent effects are kept inside the branches of blockRes. - let finalizerEffects = composeNestedEffectsWithFinalizer(blockRes); - finalizerRes = finalizerEffects.result; - // The result may become abrupt because of the finalizer, but it cannot become normal. - invariant(!(finalizerRes instanceof SimpleNormalCompletion)); - } else { - // A single thread of control has produced a normal blockRes and the global state is up to date. - finalizerRes = env.evaluateCompletion(ast.finalizer, strictCode); - } - } - - if (finalizerRes instanceof AbruptCompletion) throw finalizerRes; - if (finalizerRes instanceof PossiblyNormalCompletion) realm.composeWithSavedCompletion(finalizerRes); - if (handlerRes instanceof NormalCompletion) handlerRes = handlerRes.value; - if (handlerRes instanceof Value) return (UpdateEmpty(realm, handlerRes, realm.intrinsics.undefined): any); - throw handlerRes; - - // The handler is a potential join point for all throw completions, but is easier to not do the join here because - // it is tricky to join the joined and composed result of the throw completions with the non exceptional completions. - // Unfortunately, things are still complicated because the handler may turn abrupt completions into normal - // completions and the other way around. When this happens the container has to change its type. - // We do this by call joinForkOrChoose to create a new container at every level of the recursion. - function composeNestedThrowEffectsWithHandler( - c: PossiblyNormalCompletion | ForkedAbruptCompletion, - priorEffects: Array = [] - ): Effects { - let consequent = c.consequent; - let consequentEffects = c.consequentEffects; - priorEffects.push(consequentEffects); - if (consequent instanceof ForkedAbruptCompletion || consequent instanceof PossiblyNormalCompletion) { - consequentEffects = composeNestedThrowEffectsWithHandler(consequent, priorEffects); - } else if (consequent instanceof ThrowCompletion) { - consequentEffects = realm.evaluateForEffectsWithPriorEffects( - priorEffects, - () => { - invariant(ast.handler); - return env.evaluateCompletionDeref(ast.handler, strictCode, consequent); - }, - "composeNestedThrowEffectsWithHandler/1" - ); - } - priorEffects.pop(); - let alternate = c.alternate; - let alternateEffects = c.alternateEffects; - priorEffects.push(alternateEffects); - if (alternate instanceof PossiblyNormalCompletion || alternate instanceof ForkedAbruptCompletion) { - alternateEffects = composeNestedThrowEffectsWithHandler(alternate, priorEffects); - } else if (alternate instanceof ThrowCompletion) { - alternateEffects = realm.evaluateForEffectsWithPriorEffects( - priorEffects, - () => { - invariant(ast.handler); - return env.evaluateCompletionDeref(ast.handler, strictCode, alternate); - }, - "composeNestedThrowEffectsWithHandler/2" - ); - } - priorEffects.pop(); - return Join.joinForkOrChoose(realm, c.joinCondition, consequentEffects, alternateEffects); - } - - // The finalizer is not a join point, so update each path in the completion separately. - // Things are complicated because the finalizer may turn normal completions into abrupt completions. - // When this happens the container has to change its type. - // We do this by call joinForkOrChoose to create a new container at every level of the recursion. - function composeNestedEffectsWithFinalizer( - c: PossiblyNormalCompletion | ForkedAbruptCompletion, - priorEffects: Array = [] - ): Effects { - let consequent = c.consequent; - let consequentEffects = c.consequentEffects; - priorEffects.push(consequentEffects); - if (consequent instanceof ForkedAbruptCompletion || consequent instanceof PossiblyNormalCompletion) { - consequentEffects = composeNestedThrowEffectsWithHandler(consequent, priorEffects); - } else { - consequentEffects = realm.evaluateForEffectsWithPriorEffects( - priorEffects, - () => { - invariant(ast.finalizer); - return env.evaluateCompletionDeref(ast.finalizer, strictCode); - }, - "composeNestedEffectsWithFinalizer/1" - ); - if (!(consequentEffects.result instanceof AbruptCompletion)) consequentEffects.result = consequent; - } - priorEffects.pop(); - let alternate = c.alternate; - let alternateEffects = c.alternateEffects; - priorEffects.push(alternateEffects); - if (alternate instanceof PossiblyNormalCompletion || alternate instanceof ForkedAbruptCompletion) { - alternateEffects = composeNestedThrowEffectsWithHandler(alternate, priorEffects); - } else { - alternateEffects = realm.evaluateForEffectsWithPriorEffects( - priorEffects, - () => { - invariant(ast.finalizer); - return env.evaluateCompletionDeref(ast.finalizer, strictCode); - }, - "composeNestedEffectsWithFinalizer/2" - ); - if (!(alternateEffects.result instanceof AbruptCompletion)) alternateEffects.result = alternate; - } - priorEffects.pop(); - return Join.joinForkOrChoose(realm, c.joinCondition, consequentEffects, alternateEffects); + let res = env.evaluateCompletionDeref(ast.finalizer, strictCode); + result = composeResults(result, res); } + return realm.returnOrThrowCompletion(UpdateEmpty(realm, result, realm.intrinsics.undefined)); } diff --git a/src/evaluators/UnaryExpression.js b/src/evaluators/UnaryExpression.js index cd4a070df..7a26a298f 100644 --- a/src/evaluators/UnaryExpression.js +++ b/src/evaluators/UnaryExpression.js @@ -11,7 +11,7 @@ import type { Realm } from "../realm.js"; import type { LexicalEnvironment } from "../environment.js"; -import { AbruptCompletion, PossiblyNormalCompletion, SimpleNormalCompletion } from "../completions.js"; +//import { SimpleNormalCompletion } from "../completions.js"; import { CompilerDiagnostic, FatalError } from "../errors.js"; import { TypesDomain, ValuesDomain } from "../domains/index.js"; import { @@ -129,23 +129,8 @@ function tryToEvaluateOperationOrLeaveAsAbstract( throw error; } } - // Note that the effects of (non joining) abrupt branches are not included - // in joinedEffects, but are tracked separately inside completion. realm.applyEffects(effects); - let completion = effects.result; - if (completion instanceof PossiblyNormalCompletion) { - // in this case one of the branches may complete abruptly, which means that - // not all control flow branches join into one flow at this point. - // Consequently we have to continue tracking changes until the point where - // all the branches come together into one. - completion = realm.composeWithSavedCompletion(completion); - } - - // return or throw completion - if (completion instanceof AbruptCompletion) throw completion; - if (completion instanceof SimpleNormalCompletion) completion = completion.value; - invariant(completion instanceof Value); - return completion; + return realm.returnOrThrowCompletion(effects.result); } function evaluateOperation( diff --git a/src/intrinsics/ecma262/GeneratorPrototype.js b/src/intrinsics/ecma262/GeneratorPrototype.js index 05bb67b58..ca560e98d 100644 --- a/src/intrinsics/ecma262/GeneratorPrototype.js +++ b/src/intrinsics/ecma262/GeneratorPrototype.js @@ -30,7 +30,7 @@ export default function(realm: Realm, obj: ObjectValue): void { let g = context; // 2. Let C be Completion{[[Type]]: return, [[Value]]: value, [[Target]]: empty}. - let C = new ReturnCompletion(value, undefined, realm.currentLocation); + let C = new ReturnCompletion(value, realm.currentLocation); // 3. Return ? GeneratorResumeAbrupt(g, C). return GeneratorResumeAbrupt(realm, g, C); @@ -42,7 +42,7 @@ export default function(realm: Realm, obj: ObjectValue): void { let g = context; // 2. Let C be Completion{[[Type]]: throw, [[Value]]: exception, [[Target]]: empty}. - let C = new ReturnCompletion(exception, undefined, realm.currentLocation); + let C = new ReturnCompletion(exception, realm.currentLocation); // 3. Return ? GeneratorResumeAbrupt(g, C). return GeneratorResumeAbrupt(realm, g, C); diff --git a/src/intrinsics/ecma262/Object.js b/src/intrinsics/ecma262/Object.js index 53efeb71e..e2671ed71 100644 --- a/src/intrinsics/ecma262/Object.js +++ b/src/intrinsics/ecma262/Object.js @@ -13,7 +13,7 @@ import { TypesDomain, ValuesDomain } from "../../domains/index.js"; import { FatalError } from "../../errors.js"; import { Realm } from "../../realm.js"; import { NativeFunctionValue } from "../../values/index.js"; -import { AbruptCompletion, PossiblyNormalCompletion } from "../../completions.js"; +//import { AbruptCompletion } from "../../completions.js"; import { AbstractValue, AbstractObjectValue, @@ -190,19 +190,8 @@ function tryAndApplySourceOrRecover( } finally { realm.suppressDiagnostics = savedSuppressDiagnostics; } - // Note that the effects of (non joining) abrupt branches are not included - // in effects, but are tracked separately inside completion. realm.applyEffects(effects); - let completion = effects.result; - if (completion instanceof PossiblyNormalCompletion) { - // in this case one of the branches may complete abruptly, which means that - // not all control flow branches join into one flow at this point. - // Consequently we have to continue tracking changes until the point where - // all the branches come together into one. - completion = realm.composeWithSavedCompletion(completion); - } - // return or throw completion - if (completion instanceof AbruptCompletion) throw completion; + realm.returnOrThrowCompletion(effects.result); return to_must_be_partial; } diff --git a/src/intrinsics/ecma262/StringPrototype.js b/src/intrinsics/ecma262/StringPrototype.js index c1c2c8b9d..86185168d 100644 --- a/src/intrinsics/ecma262/StringPrototype.js +++ b/src/intrinsics/ecma262/StringPrototype.js @@ -503,7 +503,7 @@ export default function(realm: Realm, obj: ObjectValue): ObjectValue { // 7. Search string for the first occurrence of searchString and // let pos be the index within string of the first code unit of the matched substring and - let pos = string.search(searchString); + let pos = string.indexOf(searchString); // let matched be searchString. let matched = searchString; diff --git a/src/intrinsics/index.js b/src/intrinsics/index.js index 6b2a14082..704a3ebd8 100644 --- a/src/intrinsics/index.js +++ b/src/intrinsics/index.js @@ -9,18 +9,20 @@ /* @flow strict-local */ +import { TypesDomain, ValuesDomain } from "../domains/index.js"; import type { Intrinsics } from "../types.js"; import type { Realm } from "../realm.js"; import { - NumberValue, - StringValue, - NullValue, - UndefinedValue, - EmptyValue, - ObjectValue, - SymbolValue, + AbstractValue, BooleanValue, + EmptyValue, NativeFunctionValue, + NullValue, + NumberValue, + ObjectValue, + StringValue, + SymbolValue, + UndefinedValue, } from "../values/index.js"; import { Functions } from "../singletons.js"; @@ -467,5 +469,21 @@ export function initialize(i: Intrinsics, realm: Realm): Intrinsics { // 8.2.2, step 12 Functions.AddRestrictedFunctionProperties(i.FunctionPrototype, realm); + // + if (realm.useAbstractInterpretation) { + TypesDomain.topVal = new TypesDomain(undefined); + ValuesDomain.topVal = new ValuesDomain(undefined); + i.__topValue = new AbstractValue(realm, TypesDomain.topVal, ValuesDomain.topVal, Number.MAX_SAFE_INTEGER, []); + TypesDomain.bottomVal = new TypesDomain(EmptyValue); + ValuesDomain.bottomVal = new ValuesDomain(new Set()); + i.__bottomValue = new AbstractValue( + realm, + TypesDomain.bottomVal, + ValuesDomain.bottomVal, + Number.MIN_SAFE_INTEGER, + [] + ); + } + return i; } diff --git a/src/methods/call.js b/src/methods/call.js index 714aedc7b..de4117071 100644 --- a/src/methods/call.js +++ b/src/methods/call.js @@ -22,20 +22,28 @@ import { FatalError } from "../errors.js"; import { Realm, ExecutionContext } from "../realm.js"; import Value from "../values/Value.js"; import { - FunctionValue, - ECMAScriptSourceFunctionValue, - ObjectValue, - NullValue, - UndefinedValue, - NativeFunctionValue, AbstractObjectValue, AbstractValue, + ECMAScriptSourceFunctionValue, + FunctionValue, + NativeFunctionValue, + NullValue, + ObjectValue, + UndefinedValue, } from "../values/index.js"; import { GetIterator, HasSomeCompatibleType, IsCallable, IsPropertyKey, IteratorStep, IteratorValue } from "./index.js"; import { GeneratorStart } from "./generator.js"; -import { ReturnCompletion, AbruptCompletion, ThrowCompletion, ForkedAbruptCompletion } from "../completions.js"; +import { + AbruptCompletion, + Completion, + JoinedAbruptCompletions, + JoinedNormalAndAbruptCompletions, + NormalCompletion, + ReturnCompletion, + ThrowCompletion, +} from "../completions.js"; import { GetTemplateObject, GetV, GetThisValue } from "./get.js"; -import { Create, Environment, Functions, Join, Havoc, To, Widen } from "../singletons.js"; +import { Create, Environment, Functions, Havoc, Join, To, Widen } from "../singletons.js"; import invariant from "../invariant.js"; import { createOperationDescriptor } from "../utils/generator.js"; import type { BabelNodeExpression, BabelNodeSpreadElement, BabelNodeTemplateLiteral } from "@babel/types"; @@ -291,7 +299,7 @@ function callNativeFunctionValue( realm: Realm, f: NativeFunctionValue, argumentsList: Array -): Value | AbruptCompletion { +): void | AbruptCompletion { let env = realm.getRunningContext().lexicalEnvironment; let context = env.environmentRecord.GetThisBinding(); @@ -306,7 +314,7 @@ function callNativeFunctionValue( mightBecomeAnObject(contextVal) ); let completion = f.callCallback( - // this is to get around Flow not understanding the above invariant + // TODO: this is not right. Either fix the type signature of callCallback or wrap contextVal in a coercion ((contextVal: any): AbstractObjectValue | ObjectValue | NullValue | UndefinedValue), argumentsList, env.environmentRecord.$NewTarget @@ -323,7 +331,7 @@ function callNativeFunctionValue( } }; - const wrapInReturnCompletion = contextVal => new ReturnCompletion(contextVal, undefined, realm.currentLocation); + const wrapInReturnCompletion = contextVal => new ReturnCompletion(contextVal, realm.currentLocation); if (context instanceof AbstractObjectValue && context.kind === "conditional") { let [condValue, consequentVal, alternateVal] = context.args; @@ -349,7 +357,9 @@ function callNativeFunctionValue( ) ); } - return functionCall(context, false); + let c = functionCall(context, false); + if (c instanceof AbruptCompletion) return c; + return undefined; } // ECMA262 9.2.1.3 @@ -357,7 +367,7 @@ export function OrdinaryCallEvaluateBody( realm: Realm, f: ECMAScriptFunctionValue, argumentsList: Array -): Reference | Value | AbruptCompletion { +): void | AbruptCompletion { if (f instanceof NativeFunctionValue) { return callNativeFunctionValue(realm, f, argumentsList); } else { @@ -379,7 +389,7 @@ export function OrdinaryCallEvaluateBody( GeneratorStart(realm, G, code); // 4. Return Completion{[[Type]]: return, [[Value]]: G, [[Target]]: empty}. - return new ReturnCompletion(G, undefined, realm.currentLocation); + return new ReturnCompletion(G, realm.currentLocation); } else { // TODO #1586: abstractRecursionSummarization is disabled for now, as it is likely too limiting // (as observed in large internal tests). @@ -398,15 +408,15 @@ export function OrdinaryCallEvaluateBody( realm.applyEffects(effects); let c = effects.result; return processResult(() => { - invariant(c instanceof Value || c instanceof AbruptCompletion); - return c; + if (c instanceof AbruptCompletion || c instanceof JoinedNormalAndAbruptCompletions) return c; + return undefined; }); } } finally { F.isSelfRecursive = savedIsSelfRecursive; } - function guardedCall() { + function guardedCall(): Value | Completion { let currentLocation = realm.currentLocation; if (F.activeArguments !== undefined && F.activeArguments.has(currentLocation)) { let [previousPathLength, previousArguments] = F.activeArguments.get(currentLocation); @@ -418,7 +428,7 @@ export function OrdinaryCallEvaluateBody( if (Widen.containsArraysOfValue(realm, previousArguments, widenedArgumentsList)) { // Reached a fixed point. Executing this call will not add any knowledge // about the effects of the original call. - return AbstractValue.createFromType(realm, Value, "widened return result"); + return realm.intrinsics.undefined; } else { argumentsList = widenedArgumentsList; } @@ -427,13 +437,13 @@ export function OrdinaryCallEvaluateBody( try { if (F.activeArguments === undefined) F.activeArguments = new Map(); F.activeArguments.set(currentLocation, [realm.pathConditions.length, argumentsList]); - return normalCall(); + return normalCall() || realm.intrinsics.undefined; } finally { F.activeArguments.delete(currentLocation); } } - function normalCall() { + function normalCall(): void | AbruptCompletion { // 1. Perform ? FunctionDeclarationInstantiation(F, argumentsList). Functions.FunctionDeclarationInstantiation(realm, F, argumentsList); @@ -442,55 +452,47 @@ export function OrdinaryCallEvaluateBody( let code = F.$ECMAScriptCode; invariant(code !== undefined); let context = realm.getRunningContext(); - return processResult(() => context.lexicalEnvironment.evaluateCompletionDeref(code, F.$Strict)); + return processResult(() => { + let c = context.lexicalEnvironment.evaluateCompletionDeref(code, F.$Strict); + if (c instanceof AbruptCompletion || c instanceof JoinedNormalAndAbruptCompletions) return c; + return undefined; + }); } - function processResult(getCompletion: () => AbruptCompletion | Value): AbruptCompletion | Value { + function processResult( + getCompletion: () => void | AbruptCompletion | JoinedNormalAndAbruptCompletions + ): void | AbruptCompletion { + // We don't want the callee to see abrupt completions from the caller. let priorSavedCompletion = realm.savedCompletion; + realm.savedCompletion = undefined; + + let c; try { - realm.savedCompletion = undefined; - let c = getCompletion(); - - // We are about the leave this function and this presents a join point where all non exceptional control flows - // converge into a single flow using their joint effects to update the post join point state. - if (!(c instanceof ReturnCompletion)) { - if (!(c instanceof AbruptCompletion)) { - c = new ReturnCompletion(realm.intrinsics.undefined, undefined, realm.currentLocation); - } - } - invariant(c instanceof AbruptCompletion); - - // If there is a saved completion (i.e. unjoined abruptly completing control flows) then combine them with c - let abruptCompletion = Functions.incorporateSavedCompletion(realm, c); - invariant(abruptCompletion instanceof AbruptCompletion); - - // If there is single completion, we don't need to join - if (!(abruptCompletion instanceof ForkedAbruptCompletion)) return abruptCompletion; - - // If none of the completions are return completions, there is no need to join either - if (!abruptCompletion.containsCompletion(ReturnCompletion)) return abruptCompletion; - - // Apply the joined effects of return completions to the current state since these now join the normal path - let joinedReturnEffects = Join.extractAndJoinCompletionsOfType(ReturnCompletion, realm, abruptCompletion); - realm.applyEffects(joinedReturnEffects); - c = joinedReturnEffects.result; - invariant(c instanceof ReturnCompletion); - - // We now make a PossiblyNormalCompletion out of abruptCompletion. - // extractAndJoinCompletionsOfType helped with this by cheating and turning all of its nested completions - // that contain return completions into PossiblyNormalCompletions. - let remainingCompletions = abruptCompletion.transferChildrenToPossiblyNormalCompletion(); - - // If there are no throw completions left inside remainingCompletions, just return. - if (!remainingCompletions.containsCompletion(ThrowCompletion)) return c; - - // Stash the remaining completions in the realm start tracking the effects that need to be appended - // to the normal branch at the next join point. - realm.composeWithSavedCompletion(remainingCompletions); - return c; - } finally { - realm.incorporatePriorSavedCompletion(priorSavedCompletion); + c = getCompletion(); + } catch (e) { + invariant(!(e instanceof AbruptCompletion)); + throw e; } + c = Functions.incorporateSavedCompletion(realm, c); // in case the callee had conditional abrupt completions + realm.savedCompletion = priorSavedCompletion; + if (c === undefined) return undefined; // the callee had no returns or throws + if (c instanceof ThrowCompletion || c instanceof ReturnCompletion) return c; + // Non mixed completions will not be joined completions, but single completions with joined values. + // At this point it must be true that + // c contains return completions and possibly also normal completions (which are implicitly "return undefined;") + // and c also contains throw completions. Hence we assert: + invariant(c instanceof JoinedAbruptCompletions || c instanceof JoinedNormalAndAbruptCompletions); + + // We want to add only the throw completions to priorSavedCompletion (but must keep their conditions in tact). + // The (joined) return completions must be returned to our caller + let rc = c; + Completion.makeAllNormalCompletionsResultInUndefined(c); + c = Completion.normalizeSelectedCompletions(r => r instanceof ReturnCompletion, c); + invariant(c.containsSelectedCompletion(r => r instanceof NormalCompletion)); + let rv = Join.joinValuesOfSelectedCompletions(r => r instanceof NormalCompletion, c); + rc = new ReturnCompletion(rv); + if (c.containsSelectedCompletion(r => r instanceof ThrowCompletion)) realm.composeWithSavedCompletion(c); + return rc; } } } diff --git a/src/methods/function.js b/src/methods/function.js index 4f7f797a9..48192896a 100644 --- a/src/methods/function.js +++ b/src/methods/function.js @@ -14,7 +14,13 @@ import type { PropertyKeyValue } from "../types.js"; import { FatalError } from "../errors.js"; import type { Realm } from "../realm.js"; import type { ECMAScriptFunctionValue } from "../values/index.js"; -import { Completion, ReturnCompletion, AbruptCompletion, NormalCompletion } from "../completions.js"; +import { + AbruptCompletion, + Completion, + JoinedNormalAndAbruptCompletions, + ReturnCompletion, + SimpleNormalCompletion, +} from "../completions.js"; import { GlobalEnvironmentRecord, ObjectEnvironmentRecord } from "../environment.js"; import { AbstractValue, @@ -121,8 +127,8 @@ function InternalCall( return result.value; } - // 10. ReturnIfAbrupt(result). or if possibly abrupt - if (result instanceof Completion) { + // 10. ReturnIfAbrupt(result). + if (result instanceof AbruptCompletion) { throw result; } @@ -1113,31 +1119,25 @@ export class FunctionImplementation { } } - // If c is an abrupt completion and realm.savedCompletion is defined, the result is an instance of - // ForkedAbruptCompletion and the effects that have been captured since the PossiblyNormalCompletion instance - // in realm.savedCompletion has been created, becomes the effects of the branch that terminates in c. - // If c is a normal completion, the result is realm.savedCompletion, with its value updated to c. - // If c is undefined, the result is just realm.savedCompletion. + // Composes realm.savedCompletion with c, clears realm.savedCompletion and return the composition. // Call this only when a join point has been reached. - incorporateSavedCompletion(realm: Realm, c: void | AbruptCompletion | Value): void | Completion | Value { + incorporateSavedCompletion(realm: Realm, c: void | Completion | Value): void | Completion | Value { let savedCompletion = realm.savedCompletion; if (savedCompletion !== undefined) { - if (savedCompletion.savedPathConditions) { - // Since we are joining several control flow paths, we need the curent path conditions to reflect - // only the refinements that applied at the corresponding fork point. - realm.pathConditions = savedCompletion.savedPathConditions; - savedCompletion.savedPathConditions = []; - } realm.savedCompletion = undefined; - if (c === undefined) return savedCompletion; - if (c instanceof Value) { - Join.updatePossiblyNormalCompletionWithValue(realm, savedCompletion, c); - return savedCompletion; - } else { - let e = realm.getCapturedEffects(); + realm.pathConditions = [].concat(savedCompletion.pathConditionsAtCreation); + if (c === undefined) c = realm.intrinsics.empty; + if (c instanceof Value) c = new SimpleNormalCompletion(c); + if (savedCompletion instanceof JoinedNormalAndAbruptCompletions) { + let subsequentEffects = realm.getCapturedEffects(c); realm.stopEffectCaptureAndUndoEffects(savedCompletion); - return Join.replacePossiblyNormalCompletionWithForkedAbruptCompletion(realm, savedCompletion, c, e); + let joinedEffects = Join.composeWithEffects(savedCompletion, subsequentEffects); + realm.applyEffects(joinedEffects); + realm.savedCompletion = savedCompletion.composedWith; + if (realm.savedCompletion !== undefined) return this.incorporateSavedCompletion(realm, joinedEffects.result); + return joinedEffects.result; } + return Join.composeCompletions(savedCompletion, c); } return c; } @@ -1165,34 +1165,6 @@ export class FunctionImplementation { return blockValue || realm.intrinsics.empty; } - PartiallyEvaluateStatements( - body: Array, - blockValue: void | NormalCompletion | Value, - strictCode: boolean, - blockEnv: LexicalEnvironment, - realm: Realm - ): [Completion | Value, Array] { - let statementAsts = []; - for (let node of body) { - if (node.type !== "FunctionDeclaration") { - let [res, nast, nio] = blockEnv.partiallyEvaluateCompletionDeref(node, strictCode); - for (let ioAst of nio) statementAsts.push(ioAst); - statementAsts.push((nast: any)); - if (!(res instanceof EmptyValue)) { - if (blockValue === undefined || blockValue instanceof Value) { - if (res instanceof AbruptCompletion) - return [UpdateEmpty(realm, res, blockValue || realm.intrinsics.empty), statementAsts]; - invariant(res instanceof NormalCompletion || res instanceof Value); - blockValue = res; - } - } - } - } - - // 7. Return blockValue. - return [blockValue || realm.intrinsics.empty, statementAsts]; - } - // ECMA262 9.2.5 FunctionCreate( realm: Realm, diff --git a/src/methods/get.js b/src/methods/get.js index 457c742aa..cb0ead592 100644 --- a/src/methods/get.js +++ b/src/methods/get.js @@ -9,7 +9,6 @@ /* @flow */ -import { AbruptCompletion, Completion, PossiblyNormalCompletion } from "../completions.js"; import { InfeasiblePathError } from "../errors.js"; import { construct_empty_effects, type Realm, Effects } from "../realm.js"; import type { PropertyKeyValue, CallableObjectValue } from "../types.js"; @@ -194,30 +193,13 @@ export function OrdinaryGet( } // Join the effects, creating an abstract view of what happened, regardless // of the actual value of ownDesc.joinCondition. - if (result1 instanceof Completion) result1 = result1.shallowCloneWithoutEffects(); - if (result2 instanceof Completion) result2 = result2.shallowCloneWithoutEffects(); - let joinedEffects = Join.joinForkOrChoose( - realm, + let joinedEffects = Join.joinEffects( joinCondition, new Effects(result1, generator1, modifiedBindings1, modifiedProperties1, createdObjects1), new Effects(result2, generator2, modifiedBindings2, modifiedProperties2, createdObjects2) ); - let completion = joinedEffects.result; - if (completion instanceof PossiblyNormalCompletion) { - // in this case one of the branches may complete abruptly, which means that - // not all control flow branches join into one flow at this point. - // Consequently we have to continue tracking changes until the point where - // all the branches come together into one. - completion = realm.composeWithSavedCompletion(completion); - } - // Note that the effects of (non joining) abrupt branches are not included - // in joinedEffects, but are tracked separately inside completion. realm.applyEffects(joinedEffects); - - // return or throw completion - if (completion instanceof AbruptCompletion) throw completion; - invariant(completion instanceof Value); - return completion; + return realm.returnOrThrowCompletion(joinedEffects.result); function OrdinaryGetHelper() { let descValue = !desc diff --git a/src/methods/join.js b/src/methods/join.js index ffea4238a..7b33b381d 100644 --- a/src/methods/join.js +++ b/src/methods/join.js @@ -10,9 +10,8 @@ /* @flow */ import type { Binding } from "../environment.js"; -import { FatalError } from "../errors.js"; -import type { Bindings, BindingEntry, EvaluationResult, PropertyBindings, CreatedObjects, Realm } from "../realm.js"; -import { Effects } from "../realm.js"; +import type { Bindings, BindingEntry, PropertyBindings, CreatedObjects, Realm } from "../realm.js"; +import { construct_empty_effects, Effects } from "../realm.js"; import type { Descriptor, PropertyBinding } from "../types.js"; import { @@ -20,29 +19,22 @@ import { BreakCompletion, Completion, ContinueCompletion, - PossiblyNormalCompletion, - ForkedAbruptCompletion, + JoinedAbruptCompletions, + JoinedNormalAndAbruptCompletions, SimpleNormalCompletion, NormalCompletion, ReturnCompletion, ThrowCompletion, } from "../completions.js"; -import { Reference } from "../environment.js"; import { cloneDescriptor, equalDescriptors, IsDataDescriptor, StrictEqualityComparison } from "../methods/index.js"; -import { construct_empty_effects } from "../realm.js"; import { Path } from "../singletons.js"; import { Generator } from "../utils/generator.js"; import { AbstractValue, ConcreteValue, EmptyValue, Value } from "../values/index.js"; import invariant from "../invariant.js"; -function joinGenerators( - realm: Realm, - joinCondition: AbstractValue, - generator1: Generator, - generator2: Generator -): Generator { - // TODO #2222: Check if `realm.pathConditions` is correct here. +function joinGenerators(joinCondition: AbstractValue, generator1: Generator, generator2: Generator): Generator { + let realm = joinCondition.$Realm; let result = new Generator(realm, "joined", realm.pathConditions); if (!generator1.empty() || !generator2.empty()) { result.joinGenerators(joinCondition, generator1, generator2); @@ -99,405 +91,128 @@ function joinArraysOfValues( } export class JoinImplementation { - stopEffectCaptureJoinApplyAndReturnCompletion( - c1: PossiblyNormalCompletion, - c2: AbruptCompletion, - realm: Realm - ): ForkedAbruptCompletion { - let e = realm.getCapturedEffects(); - realm.stopEffectCaptureAndUndoEffects(c1); - return this.replacePossiblyNormalCompletionWithForkedAbruptCompletion(realm, c1, c2, e); + composeCompletions(leftCompletion: void | Completion | Value, rightCompletion: Completion | Value): Completion { + if (leftCompletion instanceof AbruptCompletion) return leftCompletion; + if (leftCompletion instanceof JoinedNormalAndAbruptCompletions) { + if (rightCompletion instanceof JoinedNormalAndAbruptCompletions) { + rightCompletion.composedWith = leftCompletion; + rightCompletion.pathConditionsAtCreation = leftCompletion.pathConditionsAtCreation; + return rightCompletion; + } + let c = this.composeCompletions(leftCompletion.consequent, rightCompletion); + if (c instanceof Value) c = new SimpleNormalCompletion(c); + let a = this.composeCompletions(leftCompletion.alternate, rightCompletion); + if (a instanceof Value) a = new SimpleNormalCompletion(a); + let joinedCompletion = this.joinCompletions(leftCompletion.joinCondition, c, a); + if (joinedCompletion instanceof JoinedNormalAndAbruptCompletions) { + joinedCompletion.composedWith = leftCompletion.composedWith; + joinedCompletion.pathConditionsAtCreation = leftCompletion.pathConditionsAtCreation; + joinedCompletion.savedEffects = leftCompletion.savedEffects; + } + return joinedCompletion; + } + if (rightCompletion instanceof Value) rightCompletion = new SimpleNormalCompletion(rightCompletion); + return rightCompletion; } - unbundleNormalCompletion( - completionOrValue: Completion | Value | Reference - ): [void | NormalCompletion, Value | Reference] { - let completion, value; - if (completionOrValue instanceof PossiblyNormalCompletion) { - completion = completionOrValue; - value = completionOrValue.value; - } else { - invariant(completionOrValue instanceof Value || completionOrValue instanceof Reference); - value = completionOrValue; - } - return [completion, value]; + composeWithEffects(completion: Completion, effects: Effects): Effects { + if (completion instanceof AbruptCompletion) return construct_empty_effects(completion.value.$Realm, completion); + if (completion instanceof SimpleNormalCompletion) return effects.shallowCloneWithResult(effects.result); + invariant(completion instanceof JoinedNormalAndAbruptCompletions); + let e1 = this.composeWithEffects(completion.consequent, effects); + let e2 = this.composeWithEffects(completion.alternate, effects); + return this.joinEffects(completion.joinCondition, e1, e2); } - composeNormalCompletions( - leftCompletion: void | NormalCompletion, - rightCompletion: void | NormalCompletion, - resultValue: Value, - realm: Realm - ): PossiblyNormalCompletion | Value { - if (leftCompletion instanceof PossiblyNormalCompletion) { - if (rightCompletion instanceof PossiblyNormalCompletion) { - this.updatePossiblyNormalCompletionWithValue(realm, rightCompletion, resultValue); - return this.composePossiblyNormalCompletions(realm, leftCompletion, rightCompletion); - } - this.updatePossiblyNormalCompletionWithValue(realm, leftCompletion, resultValue); - return leftCompletion; - } else if (rightCompletion instanceof PossiblyNormalCompletion) { - this.updatePossiblyNormalCompletionWithValue(realm, rightCompletion, resultValue); - return rightCompletion; - } else { - invariant(leftCompletion === undefined && rightCompletion === undefined); - return resultValue; - } - } - - composePossiblyNormalCompletions( - realm: Realm, - pnc: PossiblyNormalCompletion, - c: PossiblyNormalCompletion, - priorEffects?: Effects - ): PossiblyNormalCompletion { - invariant(c.savedEffects === undefined); // the caller should ensure this - let savedPathConditions = pnc.savedPathConditions; - if (pnc.consequent instanceof AbruptCompletion) { - let ae = pnc.alternateEffects; - let na; - if (pnc.alternate instanceof SimpleNormalCompletion) { - na = c.shallowCloneWithoutEffects(); - let newAlternateEffects = ae.shallowCloneWithResult(na); - if (priorEffects) newAlternateEffects = realm.composeEffects(priorEffects, newAlternateEffects); - return new PossiblyNormalCompletion( - c.value, - pnc.joinCondition, - pnc.consequent, - newAlternateEffects.result, - savedPathConditions, - pnc.savedEffects - ); - } - invariant(pnc.alternate instanceof PossiblyNormalCompletion); - na = this.composePossiblyNormalCompletions(realm, pnc.alternate, c, priorEffects); - ae.shallowCloneWithResult(na); - return new PossiblyNormalCompletion( - c.value, - pnc.joinCondition, - pnc.consequent, - na, - savedPathConditions, - pnc.savedEffects - ); - } else { - let ce = pnc.consequentEffects; - let nc; - if (pnc.consequent instanceof SimpleNormalCompletion) { - nc = c.shallowCloneWithoutEffects(); - let newConsequentEffects = ce.shallowCloneWithResult(nc); - if (priorEffects) newConsequentEffects = realm.composeEffects(priorEffects, newConsequentEffects); - return new PossiblyNormalCompletion( - c.value, - pnc.joinCondition, - newConsequentEffects.result, - pnc.alternate, - savedPathConditions, - pnc.savedEffects - ); - } - invariant(pnc.consequent instanceof PossiblyNormalCompletion); - nc = this.composePossiblyNormalCompletions(realm, pnc.consequent, c); - ce.shallowCloneWithResult(nc); - return new PossiblyNormalCompletion( - c.value, - pnc.joinCondition, - nc, - pnc.alternate, - savedPathConditions, - pnc.savedEffects - ); - } - } - - updatePossiblyNormalCompletionWithSubsequentEffects( - realm: Realm, - pnc: PossiblyNormalCompletion, - subsequentEffects: Effects - ): void { - let v = subsequentEffects.result; - invariant(v instanceof SimpleNormalCompletion); - pnc.value = v.value; - if (pnc.consequent instanceof AbruptCompletion) { - if (pnc.alternate instanceof SimpleNormalCompletion) { - let ce = realm.composeEffects(pnc.alternateEffects, subsequentEffects); - pnc.alternate = ce.result; - } else { - invariant(pnc.alternate instanceof PossiblyNormalCompletion); - this.updatePossiblyNormalCompletionWithSubsequentEffects(realm, pnc.alternate, subsequentEffects); - } - } else { - if (pnc.consequent instanceof SimpleNormalCompletion) { - let ce = realm.composeEffects(pnc.consequentEffects, subsequentEffects); - pnc.consequent = ce.result; - } else { - invariant(pnc.consequent instanceof PossiblyNormalCompletion); - this.updatePossiblyNormalCompletionWithSubsequentEffects(realm, pnc.consequent, subsequentEffects); - } - if (pnc.alternate instanceof SimpleNormalCompletion) { - let ce = realm.composeEffects(pnc.alternateEffects, subsequentEffects); - pnc.alternate = ce.result; - } else if (pnc.alternate instanceof PossiblyNormalCompletion) { - this.updatePossiblyNormalCompletionWithSubsequentEffects(realm, pnc.alternate, subsequentEffects); - } - } - } - - updatePossiblyNormalCompletionWithValue(realm: Realm, pnc: PossiblyNormalCompletion, v: Value): void { - let updateNonAbruptCompletionWithValue = (c: Completion, val: Value) => { - if (c instanceof SimpleNormalCompletion) { - c.value = v; - } else if (c instanceof PossiblyNormalCompletion) { - this.updatePossiblyNormalCompletionWithValue(realm, c, val); - } else { - invariant(false); - } - }; - pnc.value = v; - let pncc = pnc.consequent; - let pnca = pnc.alternate; - if (pncc instanceof AbruptCompletion) { - Path.withInverseCondition(pnc.joinCondition, () => { - let sv = v instanceof AbstractValue ? realm.simplifyAndRefineAbstractValue(v) : v; - updateNonAbruptCompletionWithValue(pnca, sv); - }); - } else { - Path.withCondition(pnc.joinCondition, () => { - let sv = v instanceof AbstractValue ? realm.simplifyAndRefineAbstractValue(v) : v; - updateNonAbruptCompletionWithValue(pncc, sv); - }); - if (!(pnca instanceof AbruptCompletion)) { - Path.withInverseCondition(pnc.joinCondition, () => { - let sv = v instanceof AbstractValue ? realm.simplifyAndRefineAbstractValue(v) : v; - updateNonAbruptCompletionWithValue(pnca, sv); - }); - } - } - } - - replacePossiblyNormalCompletionWithForkedAbruptCompletion( - realm: Realm, - // a forked path with a non abrupt (normal) component - pnc: PossiblyNormalCompletion, - // an abrupt completion that completes the normal path - ac: AbruptCompletion, - // effects collected after pnc was constructed - e: Effects - ): ForkedAbruptCompletion { - let recurse = (xpnc, xe, nac, ne): ForkedAbruptCompletion => { - let nx = this.replacePossiblyNormalCompletionWithForkedAbruptCompletion(realm, xpnc, nac, ne); - xe.shallowCloneWithResult(nx); - return nx; - }; - - let cloneEffects = () => { - let nac = ac.shallowCloneWithoutEffects(); - let ne = e.shallowCloneWithResult(nac); - return [nac, ne]; - }; - - ac = ac.shallowCloneWithoutEffects(); - e.result = ac; - ac.effects = e; - - // # match (pncc, pnca) - let pncc = pnc.consequent; - let pnca = pnc.alternate; - - // * case (AbruptCompletion, SimpleNormalCompletion) - // * case (AbruptCompletion, PossiblyNormalCompletion) - if (pncc instanceof AbruptCompletion) { - if (pnca instanceof SimpleNormalCompletion) { - // todo: simplify with implied path condition - e = realm.composeEffects(pnc.alternateEffects, e); - invariant(e.result instanceof AbruptCompletion); - return new ForkedAbruptCompletion(realm, pnc.joinCondition, pncc, e.result); - } - invariant(pnca instanceof PossiblyNormalCompletion); - let na = recurse(pnca, pnc.alternateEffects, ac, e); - return new ForkedAbruptCompletion(realm, pnc.joinCondition, pncc, na); - } - - // * case (SimpleNormalCompletion, AbruptCompletion) - // * case (PossiblyNormalCompletion, AbruptCompletion) - if (pnca instanceof AbruptCompletion) { - if (pncc instanceof SimpleNormalCompletion) { - // todo: simplify with implied path condition - e = realm.composeEffects(pnc.consequentEffects, e); - invariant(e.result instanceof AbruptCompletion); - return new ForkedAbruptCompletion(realm, pnc.joinCondition, e.result, pnca); - } - invariant(pncc instanceof PossiblyNormalCompletion); - let nc = recurse(pncc, pnc.consequentEffects, ac, e); - return new ForkedAbruptCompletion(realm, pnc.joinCondition, nc, pnca); - } - - // * case (SimpleNormalCompletion, SimpleNormalCompletion) - // * case (SimpleNormalCompletion, PossibleNormalCompletion) - if (pncc instanceof SimpleNormalCompletion) { - let nce = realm.composeEffects(pnc.consequentEffects, e); - invariant(nce.result instanceof AbruptCompletion); - let nc = nce.result; - [ac, e] = cloneEffects(); - let na, nae; - if (pnca instanceof SimpleNormalCompletion) { - nae = realm.composeEffects(pnc.alternateEffects, e); - invariant(nae.result instanceof AbruptCompletion); - na = nae.result; - } else { - invariant(pnca instanceof PossiblyNormalCompletion); - na = recurse(pnca, pnc.alternateEffects, ac, e); - } - return new ForkedAbruptCompletion(realm, pnc.joinCondition, nc, na); - } - - // * case (PossibleNormalCompletion, SimpleNormalCompletion) - if (pnca instanceof SimpleNormalCompletion) { - let nae = realm.composeEffects(pnc.alternateEffects, e); - invariant(nae.result instanceof AbruptCompletion); - let na = nae.result; - invariant(pncc instanceof PossiblyNormalCompletion); - [ac, e] = cloneEffects(); - let nc = recurse(pncc, pnc.consequentEffects, ac, e); - return new ForkedAbruptCompletion(realm, pnc.joinCondition, nc, na); - } - - // * case (PossibleNormalCompletion, PossibleNormalCompletion) - invariant(pncc instanceof PossiblyNormalCompletion); - invariant(pnca instanceof PossiblyNormalCompletion); - let nc = recurse(pncc, pnc.consequentEffects, ac, e); - [ac, e] = cloneEffects(); - let na = recurse(pnca, pnc.alternateEffects, ac, e); - return new ForkedAbruptCompletion(realm, pnc.joinCondition, nc, na); - - // Impossible cases: - // * case (AbruptCompletion, AbruptCompletion) - } - - joinNormalCompletions( - realm: Realm, - joinCondition: AbstractValue, - c: NormalCompletion, - ce: Effects, - a: NormalCompletion, - ae: Effects - ): PossiblyNormalCompletion { + _collapseSimilarCompletions(joinCondition: AbstractValue, c1: Completion, c2: Completion): void | Completion { + let realm = joinCondition.$Realm; let getAbstractValue = (v1: void | Value, v2: void | Value): Value => { if (v1 instanceof EmptyValue) return v2 || realm.intrinsics.undefined; if (v2 instanceof EmptyValue) return v1 || realm.intrinsics.undefined; return AbstractValue.createFromConditionalOp(realm, joinCondition, v1, v2); }; - let rv = this.joinValues(realm, c.value, a.value, getAbstractValue); - invariant(rv instanceof Value); - a.value = rv; - return new PossiblyNormalCompletion(rv, joinCondition, c, a, []); + if (c1 instanceof BreakCompletion && c2 instanceof BreakCompletion && c1.target === c2.target) { + let val = this.joinValues(realm, c1.value, c2.value, getAbstractValue); + invariant(val instanceof Value); + return new BreakCompletion(val, joinCondition.expressionLocation, c1.target); + } + if (c1 instanceof ContinueCompletion && c2 instanceof ContinueCompletion && c1.target === c2.target) { + return new ContinueCompletion(realm.intrinsics.empty, joinCondition.expressionLocation, c1.target); + } + if (c1 instanceof ReturnCompletion && c2 instanceof ReturnCompletion) { + let val = this.joinValues(realm, c1.value, c2.value, getAbstractValue); + invariant(val instanceof Value); + return new ReturnCompletion(val, joinCondition.expressionLocation); + } + if (c1 instanceof ThrowCompletion && c2 instanceof ThrowCompletion) { + getAbstractValue = (v1: void | Value, v2: void | Value) => { + return AbstractValue.createFromConditionalOp(realm, joinCondition, v1, v2); + }; + let val = this.joinValues(realm, c1.value, c2.value, getAbstractValue); + invariant(val instanceof Value); + return new ThrowCompletion(val, c1.location); + } + if (c1 instanceof SimpleNormalCompletion && c2 instanceof SimpleNormalCompletion) { + return new SimpleNormalCompletion(getAbstractValue(c1.value, c2.value)); + } + return undefined; } - // Join all effects that result in completions of type CompletionType. - // Erase all completions of type Completion type from c, so that we never join them again. - // Also erase any generators that appears in branches resulting in completions of type CompletionType. - // Note that c is modified in place and should be replaced with a PossiblyNormalCompletion by the caller - // if either of its branches cease to be an AbruptCompletion. - extractAndJoinCompletionsOfType(CompletionType: typeof AbruptCompletion, realm: Realm, c: AbruptCompletion): Effects { - let emptyEffects = construct_empty_effects(realm); - if (c instanceof CompletionType) { - emptyEffects.result = c.shallowCloneWithoutEffects(); - return emptyEffects; - } - if (!(c instanceof ForkedAbruptCompletion)) { - return emptyEffects; - } - // Join up the consequent and alternate completions and compose them with their prefix effects - let ce = this.extractAndJoinCompletionsOfType(CompletionType, realm, c.consequent); - // ce will be applied to the global state before any non joining branches in c.consequent, so move - // the generator from c.consequentEffects to ce.generator so that all branches will see its effects. - ce = realm.composeEffects(c.consequentEffects, ce); - // ce now incorporates c.consequentEffects.generator, so remove it from there. - c.consequentEffects.generator = emptyEffects.generator; - if (ce.result instanceof CompletionType) { - // Erase completions of type CompletionType and prepare for transformation of c to a possibly normal completion - if (c.consequent instanceof CompletionType) { - c.updateConsequentKeepingCurrentEffects(new SimpleNormalCompletion(realm.intrinsics.empty, undefined)); - } else if (c.consequent instanceof ForkedAbruptCompletion && c.consequent.containsCompletion(NormalCompletion)) { - c.updateConsequentKeepingCurrentEffects((c.consequent.transferChildrenToPossiblyNormalCompletion(): any)); - } - } else { - ce.result = new CompletionType(realm.intrinsics.empty); - } - let ae = this.extractAndJoinCompletionsOfType(CompletionType, realm, c.alternate); - // ae will be applied to the global state before any non joining branches in c.alternate, so move - // the generator from c.alternateEffects to ae.generator so that all branches will see its effects. - ae = realm.composeEffects(c.alternateEffects, ae); - // ae now incorporates c.alternateEffects.generator, so remove it from there. - c.alternateEffects.generator = emptyEffects.generator; - if (ae.result instanceof CompletionType) { - // Erase completions of type CompletionType and prepare for transformation of c to a possibly normal completion - if (c.alternate instanceof CompletionType) { - c.updateAlternateKeepingCurrentEffects(new SimpleNormalCompletion(realm.intrinsics.empty, undefined)); - } else if (c.alternate instanceof ForkedAbruptCompletion && c.alternate.containsCompletion(NormalCompletion)) { - c.updateAlternateKeepingCurrentEffects((c.alternate.transferChildrenToPossiblyNormalCompletion(): any)); - } - } else { - ae.result = new CompletionType(realm.intrinsics.empty); - } + joinCompletions(joinCondition: Value, c1: Completion, c2: Completion): Completion { + if (!joinCondition.mightNotBeTrue()) return c1; + if (!joinCondition.mightNotBeFalse()) return c2; + invariant(joinCondition instanceof AbstractValue); - let e = this.joinForkOrChoose(realm, c.joinCondition, ce, ae); - if (e.result instanceof ForkedAbruptCompletion) { - if (e.result.consequent instanceof CompletionType && e.result.alternate instanceof CompletionType) { - let result = this.collapseResults(realm, e.result.joinCondition, e, e.result.consequent, e.result.alternate); - e = result.effects; - invariant(e !== undefined); + let c = this._collapseSimilarCompletions(joinCondition, c1, c2); + if (c === undefined) { + if (c1 instanceof AbruptCompletion && c2 instanceof AbruptCompletion) + c = new JoinedAbruptCompletions(joinCondition, c1, c2); + else { + invariant(c1 instanceof AbruptCompletion || c1 instanceof NormalCompletion); + invariant(c2 instanceof AbruptCompletion || c2 instanceof NormalCompletion); + c = new JoinedNormalAndAbruptCompletions(joinCondition, c1, c2); } } - return e; + return c; } - joinForkOrChoose(realm: Realm, joinCondition: Value, e1: Effects, e2: Effects): Effects { + joinEffects(joinCondition: Value, e1: Effects, e2: Effects): Effects { + invariant(e1.canBeApplied); + invariant(e2.canBeApplied); if (!joinCondition.mightNotBeTrue()) return e1; if (!joinCondition.mightNotBeFalse()) return e2; invariant(joinCondition instanceof AbstractValue); let { - result: result1, + result: c1, generator: generator1, modifiedBindings: modifiedBindings1, modifiedProperties: modifiedProperties1, createdObjects: createdObjects1, } = e1; - invariant(result1.effects === e1); let { - result: result2, + result: c2, generator: generator2, modifiedBindings: modifiedBindings2, modifiedProperties: modifiedProperties2, createdObjects: createdObjects2, } = e2; - invariant(result2.effects === e2); - let emptyEffects = construct_empty_effects(realm); + let realm = joinCondition.$Realm; - let result = this.joinOrForkResults(realm, joinCondition, result1, result2, e1, e2); - if (result1 instanceof AbruptCompletion) { - if (!(result2 instanceof AbruptCompletion)) { - invariant(result instanceof PossiblyNormalCompletion); - e2.generator = emptyEffects.generator; - return new Effects(result, generator2, modifiedBindings2, modifiedProperties2, createdObjects2); - } - } else if (result2 instanceof AbruptCompletion) { - invariant(result instanceof PossiblyNormalCompletion); - e1.generator = emptyEffects.generator; - return new Effects(result, generator1, modifiedBindings1, modifiedProperties1, createdObjects1); - } + let c = this.joinCompletions(joinCondition, c1, c2); - let [modifiedGenerator1, modifiedGenerator2, bindings] = this.joinBindings( - realm, + let [modifiedGenerator1, modifiedGenerator2, bindings] = this._joinBindings( joinCondition, generator1, modifiedBindings1, generator2, modifiedBindings2 ); + + let generator = joinGenerators(joinCondition, modifiedGenerator1, modifiedGenerator2); + let properties = this.joinPropertyBindings( realm, joinCondition, @@ -514,156 +229,31 @@ export class JoinImplementation { createdObjects.add(o); }); - let generator = joinGenerators(realm, joinCondition, modifiedGenerator1, modifiedGenerator2); - - return new Effects(result, generator, bindings, properties, createdObjects); + return new Effects(c, generator, bindings, properties, createdObjects); } - joinNestedEffects(realm: Realm, c: Completion, precedingEffects?: Effects): Effects { - if (c instanceof PossiblyNormalCompletion || c instanceof ForkedAbruptCompletion) { - let e1 = this.joinNestedEffects(realm, c.consequent, c.consequentEffects); - let e2 = this.joinNestedEffects(realm, c.alternate, c.alternateEffects); - let e3 = this.joinForkOrChoose(realm, c.joinCondition, e1, e2); - let r = this.collapseResults(realm, c.joinCondition, e3, e1.result, e2.result); - let re = r.effects; - invariant(re !== undefined); - return re; - } - if (precedingEffects !== undefined) return precedingEffects; - let result = construct_empty_effects(realm); - result.result = c; - return result; - } - - collapseResults( - realm: Realm, - joinCondition: AbstractValue, - precedingEffects: Effects, - result1: EvaluationResult, - result2: EvaluationResult - ): Completion { - let getAbstractValue = (v1: void | Value, v2: void | Value): Value => { - if (v1 instanceof EmptyValue) return v2 || realm.intrinsics.undefined; - if (v2 instanceof EmptyValue) return v1 || realm.intrinsics.undefined; - return AbstractValue.createFromConditionalOp(realm, joinCondition, v1, v2); - }; - if (result1 instanceof BreakCompletion && result2 instanceof BreakCompletion && result1.target === result2.target) { - let val = this.joinValues(realm, result1.value, result2.value, getAbstractValue); - invariant(val instanceof Value); - return new BreakCompletion(val, precedingEffects, joinCondition.expressionLocation, result1.target); - } - if ( - result1 instanceof ContinueCompletion && - result2 instanceof ContinueCompletion && - result1.target === result2.target - ) { - return new ContinueCompletion( - realm.intrinsics.empty, - precedingEffects, - joinCondition.expressionLocation, - result1.target - ); - } - if (result1 instanceof ReturnCompletion && result2 instanceof ReturnCompletion) { - let val = this.joinValues(realm, result1.value, result2.value, getAbstractValue); - invariant(val instanceof Value); - return new ReturnCompletion(val, precedingEffects, joinCondition.expressionLocation); - } - if (result1 instanceof ThrowCompletion && result2 instanceof ThrowCompletion) { - getAbstractValue = (v1: void | Value, v2: void | Value) => { + joinValuesOfSelectedCompletions(selector: Completion => boolean, completion: Completion): Value { + let realm = completion.value.$Realm; + if (completion instanceof JoinedAbruptCompletions || completion instanceof JoinedNormalAndAbruptCompletions) { + let joinCondition = completion.joinCondition; + let c = this.joinValuesOfSelectedCompletions(selector, completion.consequent); + let a = this.joinValuesOfSelectedCompletions(selector, completion.alternate); + let getAbstractValue = (v1: void | Value, v2: void | Value): Value => { return AbstractValue.createFromConditionalOp(realm, joinCondition, v1, v2); }; - let val = this.joinValues(realm, result1.value, result2.value, getAbstractValue); - invariant(val instanceof Value); - return new ThrowCompletion(val, precedingEffects, result1.location); - } - if (result1 instanceof SimpleNormalCompletion && result2 instanceof SimpleNormalCompletion) { - return new SimpleNormalCompletion(getAbstractValue(result1.value, result2.value), precedingEffects); - } - AbstractValue.reportIntrospectionError(joinCondition); - throw new FatalError(); - } - - joinOrForkResults( - realm: Realm, - joinCondition: AbstractValue, - result1: EvaluationResult, - result2: EvaluationResult, - e1: Effects, - e2: Effects - ): Completion { - invariant(result1.effects === e1); - invariant(e1.result === result1); - invariant(result2.effects === e2); - invariant(e2.result === result2); - let getAbstractValue = (v1: void | Value, v2: void | Value) => { - return AbstractValue.createFromConditionalOp(realm, joinCondition, v1, v2); - }; - if (result1 instanceof Reference || result2 instanceof Reference) { - AbstractValue.reportIntrospectionError(joinCondition); - throw new FatalError(); - } - if (result1 instanceof SimpleNormalCompletion && result2 instanceof SimpleNormalCompletion) { - let val = this.joinValues(realm, result1.value, result2.value, getAbstractValue); - invariant(val instanceof Value); - return new SimpleNormalCompletion(val); - } - if (result1 instanceof AbruptCompletion && result2 instanceof AbruptCompletion) { - return new ForkedAbruptCompletion(realm, joinCondition, result1, result2); - } - if (result1 instanceof NormalCompletion && result2 instanceof NormalCompletion) { - return this.joinNormalCompletions(realm, joinCondition, result1, e1, result2, e2); - } - if (result1 instanceof AbruptCompletion) { - let completion = result2; - let savedEffects; - let savedPathConditions = []; - if (result2 instanceof PossiblyNormalCompletion) { - completion = result2.getNormalCompletion(); - savedEffects = result2.savedEffects; - savedPathConditions = result2.savedPathConditions; + let jv = this.joinValues(realm, c, a, getAbstractValue); + invariant(jv instanceof Value); + if (completion instanceof JoinedNormalAndAbruptCompletions && completion.composedWith !== undefined) { + let composedWith = completion.composedWith; + let cjv = this.joinValuesOfSelectedCompletions(selector, composedWith); + joinCondition = AbstractValue.createJoinConditionForSelectedCompletions(selector, composedWith); + jv = this.joinValues(realm, jv, cjv, getAbstractValue); + invariant(jv instanceof Value); } - invariant(completion instanceof SimpleNormalCompletion); - return new PossiblyNormalCompletion( - completion.value, - joinCondition, - result1, - result2, - savedPathConditions, - savedEffects - ); + return jv; } - if (result2 instanceof AbruptCompletion) { - let completion = result1; - let savedEffects; - let savedPathConditions = []; - if (result1 instanceof PossiblyNormalCompletion) { - completion = result1.getNormalCompletion(); - savedEffects = result1.savedEffects; - savedPathConditions = result1.savedPathConditions; - } - invariant(completion instanceof SimpleNormalCompletion); - return new PossiblyNormalCompletion( - completion.value, - joinCondition, - result1, - result2, - savedPathConditions, - savedEffects - ); - } - invariant(false); - } - - composeGenerators(realm: Realm, generator1: Generator, generator2: Generator): Generator { - // TODO #2222: The path condition of the resulting generator should really just be generator2.pathConditions, - // and we shouldn't have to bring in generator1.pathConditions. We have observed that this causes an issue - // in InstantRender. - let result = new Generator(realm, "composed", generator1.pathConditions.concat(generator2.pathConditions)); - // We copy the entries here because actually composing the generators breaks the serializer - if (!generator1.empty()) result.appendGenerator(generator1, ""); - if (!generator2.empty()) result.appendGenerator(generator2, ""); - return result; + if (selector(completion)) return completion.value; + return realm.intrinsics.empty; } // Creates a single map that joins together maps m1 and m2 using the given join @@ -688,16 +278,16 @@ export class JoinImplementation { // sets of m1 and m2. The value of a pair is the join of m1[key] and m2[key] // where the join is defined to be just m1[key] if m1[key] === m2[key] and // and abstract value with expression "joinCondition ? m1[key] : m2[key]" if not. - joinBindings( - realm: Realm, + _joinBindings( joinCondition: AbstractValue, g1: Generator, m1: Bindings, g2: Generator, m2: Bindings ): [Generator, Generator, Bindings] { + let realm = joinCondition.$Realm; let getAbstractValue = (v1: void | Value, v2: void | Value) => { - return AbstractValue.createFromConditionalOp(realm, joinCondition, v1, v2); + return AbstractValue.createFromConditionalOp(realm, joinCondition, v1, v2, undefined, true, true); }; let rewritten1 = false; let rewritten2 = false; @@ -728,18 +318,7 @@ export class JoinImplementation { // In that case, we reset the value to undefined to prevent any use of the last known value. let value = hasLeaked ? undefined : this.joinValues(realm, v1, v2, getAbstractValue); invariant(value === undefined || value instanceof Value); - let previousHasLeaked, previousValue; - if (b1 !== undefined) { - previousHasLeaked = b1.previousHasLeaked; - previousValue = b1.previousValue; - invariant( - b2 === undefined || (previousHasLeaked === b2.previousHasLeaked && previousValue === b2.previousValue) - ); - } else if (b2 !== undefined) { - previousHasLeaked = b2.previousHasLeaked; - previousValue = b2.previousValue; - } - return { hasLeaked, value, previousHasLeaked, previousValue }; + return { hasLeaked, value }; }; let joinedBindings = this.joinMaps(m1, m2, join); return [g1, g2, joinedBindings]; @@ -910,28 +489,10 @@ export class JoinImplementation { undefined, "mapAndJoin" ); - joinedEffects = - joinedEffects === undefined ? effects : this.joinForkOrChoose(realm, condition, effects, joinedEffects); + joinedEffects = joinedEffects === undefined ? effects : this.joinEffects(condition, effects, joinedEffects); } invariant(joinedEffects !== undefined); - let completion = joinedEffects.result; - if (completion instanceof PossiblyNormalCompletion) { - // in this case one of the branches may complete abruptly, which means that - // not all control flow branches join into one flow at this point. - // Consequently we have to continue tracking changes until the point where - // all the branches come together into one. - completion = realm.composeWithSavedCompletion(completion); - } - // Note that the effects of (non joining) abrupt branches are not included - // in joinedEffects, but are tracked separately inside completion. realm.applyEffects(joinedEffects); - - // return or throw completion - if (completion instanceof AbruptCompletion) throw completion; - if (completion instanceof SimpleNormalCompletion) { - completion = completion.value; - } - invariant(completion instanceof Value); - return completion; + return realm.returnOrThrowCompletion(joinedEffects.result); } } diff --git a/src/methods/properties.js b/src/methods/properties.js index 185786b77..ad667071b 100644 --- a/src/methods/properties.js +++ b/src/methods/properties.js @@ -9,7 +9,6 @@ /* @flow */ -import { AbruptCompletion, Completion, PossiblyNormalCompletion, SimpleNormalCompletion } from "../completions.js"; import { construct_empty_effects, type Realm, Effects } from "../realm.js"; import type { Descriptor, PropertyBinding, PropertyKeyValue } from "../types.js"; import { @@ -314,57 +313,41 @@ export class PropertiesImplementation { if (joinCondition !== undefined) { let descriptor2 = ownDesc.descriptor2; ownDesc = ownDesc.descriptor1; + let e1 = Path.withCondition(joinCondition, () => { + return ownDesc !== undefined + ? realm.evaluateForEffects(() => new BooleanValue(realm, OrdinarySetHelper()), undefined, "OrdinarySet/1") + : construct_empty_effects(realm); + }); let { result: result1, generator: generator1, modifiedBindings: modifiedBindings1, modifiedProperties: modifiedProperties1, createdObjects: createdObjects1, - } = Path.withCondition(joinCondition, () => { + } = e1; + ownDesc = descriptor2; + let e2 = Path.withInverseCondition(joinCondition, () => { return ownDesc !== undefined - ? realm.evaluateForEffects(() => new BooleanValue(realm, OrdinarySetHelper()), undefined, "OrdinarySet/1") + ? realm.evaluateForEffects(() => new BooleanValue(realm, OrdinarySetHelper()), undefined, "OrdinarySet/2") : construct_empty_effects(realm); }); - ownDesc = descriptor2; let { result: result2, generator: generator2, modifiedBindings: modifiedBindings2, modifiedProperties: modifiedProperties2, createdObjects: createdObjects2, - } = Path.withInverseCondition(joinCondition, () => { - return ownDesc !== undefined - ? realm.evaluateForEffects(() => new BooleanValue(realm, OrdinarySetHelper()), undefined, "OrdinarySet/2") - : construct_empty_effects(realm); - }); + } = e2; // Join the effects, creating an abstract view of what happened, regardless // of the actual value of ownDesc.joinCondition. - if (result1 instanceof Completion) result1 = result1.shallowCloneWithoutEffects(); - if (result2 instanceof Completion) result2 = result2.shallowCloneWithoutEffects(); - let joinedEffects = Join.joinForkOrChoose( - realm, + let joinedEffects = Join.joinEffects( joinCondition, new Effects(result1, generator1, modifiedBindings1, modifiedProperties1, createdObjects1), new Effects(result2, generator2, modifiedBindings2, modifiedProperties2, createdObjects2) ); - let completion = joinedEffects.result; - if (completion instanceof PossiblyNormalCompletion) { - // in this case one of the branches may complete abruptly, which means that - // not all control flow branches join into one flow at this point. - // Consequently we have to continue tracking changes until the point where - // all the branches come together into one. - completion = realm.composeWithSavedCompletion(completion); - } - // Note that the effects of (non joining) abrupt branches are not included - // in joinedEffects, but are tracked separately inside completion. realm.applyEffects(joinedEffects); - - // return or throw completion - if (completion instanceof AbruptCompletion) throw completion; - if (completion instanceof SimpleNormalCompletion) completion = completion.value; - invariant(completion instanceof Value); - return To.ToBooleanPartial(realm, completion); + return To.ToBooleanPartial(realm, realm.returnOrThrowCompletion(joinedEffects.result)); } return OrdinarySetHelper(); diff --git a/src/methods/widen.js b/src/methods/widen.js index e65a7788f..18379c17f 100644 --- a/src/methods/widen.js +++ b/src/methods/widen.js @@ -15,7 +15,7 @@ import type { Bindings, BindingEntry, EvaluationResult, PropertyBindings, Create import { Effects } from "../realm.js"; import type { Descriptor, PropertyBinding } from "../types.js"; -import { AbruptCompletion, PossiblyNormalCompletion, SimpleNormalCompletion } from "../completions.js"; +import { AbruptCompletion, JoinedNormalAndAbruptCompletions, SimpleNormalCompletion } from "../completions.js"; import { Reference } from "../environment.js"; import { cloneDescriptor, equalDescriptors, IsDataDescriptor, StrictEqualityComparison } from "./index.js"; import { Generator, createOperationDescriptor } from "../utils/generator.js"; @@ -103,7 +103,7 @@ export class WidenImplementation { realm: Realm, result1: EvaluationResult, result2: EvaluationResult - ): PossiblyNormalCompletion | SimpleNormalCompletion { + ): JoinedNormalAndAbruptCompletions | SimpleNormalCompletion { invariant(!(result1 instanceof Reference || result2 instanceof Reference), "loop bodies should not result in refs"); invariant( !(result1 instanceof AbruptCompletion || result2 instanceof AbruptCompletion), @@ -114,7 +114,7 @@ export class WidenImplementation { invariant(val instanceof Value); return new SimpleNormalCompletion(val); } - if (result1 instanceof PossiblyNormalCompletion || result2 instanceof PossiblyNormalCompletion) { + if (result1 instanceof JoinedNormalAndAbruptCompletions || result2 instanceof JoinedNormalAndAbruptCompletions) { //todo: #1174 figure out how to deal with loops that have embedded conditional exits // widen join pathConditions // widen normal result and Effects @@ -141,6 +141,9 @@ export class WidenImplementation { widenBindings(realm: Realm, m1: Bindings, m2: Bindings): Bindings { let widen = (b: Binding, b1: void | BindingEntry, b2: void | BindingEntry) => { + let l1 = b1 === undefined ? b.hasLeaked : b1.hasLeaked; + let l2 = b2 === undefined ? b.hasLeaked : b2.hasLeaked; + let hasLeaked = l1 || l2; // If either has leaked, then this binding has leaked. let v1 = b1 === undefined || b1.value === undefined ? b.value : b1.value; invariant(b2 !== undefined); // Local variables are not going to get deleted as a result of widening let v2 = b2.value; @@ -168,14 +171,7 @@ export class WidenImplementation { result.operationDescriptor = createOperationDescriptor("WIDENED_IDENTIFIER", { id: phiName }); } invariant(result instanceof Value); - let previousHasLeaked = b2.previousHasLeaked; - let previousValue = b2.previousValue; - return { - hasLeaked: previousHasLeaked, - value: result, - previousHasLeaked, - previousValue, - }; + return { hasLeaked, value: result }; }; return this.widenMaps(m1, m2, widen); } diff --git a/src/options.js b/src/options.js index d0217792e..484232551 100644 --- a/src/options.js +++ b/src/options.js @@ -65,7 +65,6 @@ export type RealmOptions = { export type SerializerOptions = { lazyObjectsRuntime?: string, delayInitializations?: boolean, - delayUnsupportedRequires?: boolean, accelerateUnsupportedRequires?: boolean, initializeMoreModules?: boolean, internalDebug?: boolean, diff --git a/src/partial-evaluators/ArrayExpression.js b/src/partial-evaluators/ArrayExpression.js deleted file mode 100644 index 2ede31a5a..000000000 --- a/src/partial-evaluators/ArrayExpression.js +++ /dev/null @@ -1,154 +0,0 @@ -/** - * Copyright (c) 2017-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -/* @flow */ - -import type { BabelNodeArrayExpression, BabelNodeStatement } from "@babel/types"; -import type { LexicalEnvironment } from "../environment.js"; -import type { Realm } from "../realm.js"; - -import { AbruptCompletion, PossiblyNormalCompletion } from "../completions.js"; -import { FatalError } from "../errors.js"; -import { GetIterator, GetMethod, IteratorStep, IteratorValue } from "../methods/index.js"; -import { AbstractValue, NumberValue, ObjectValue, StringValue, Value } from "../values/index.js"; -import { Create, Properties } from "../singletons.js"; - -import invariant from "../invariant.js"; -import * as t from "@babel/types"; - -// ECMA262 2.2.5.3 -export default function( - ast: BabelNodeArrayExpression, - strictCode: boolean, - env: LexicalEnvironment, - realm: Realm -): [AbruptCompletion | Value, BabelNodeArrayExpression, Array] { - // 1. Let array be ArrayCreate(0). - let array = Create.ArrayCreate(realm, 0); - - // 2. Let len be the result of performing ArrayAccumulation for ElementList with arguments array and 0. - let elements = ast.elements || []; - let partial_elements = []; - let io = []; - let len = elements.length; - let nextIndex = 0; - for (let i = 0; i < len; i++) { - let elem = elements[i]; - if (!elem) { - nextIndex++; - continue; - } - - let elemValue, elemAst, elemIO; - if (elem.type === "SpreadElement") - [elemValue, elemAst, elemIO] = env.partiallyEvaluateCompletionDeref(elem.argument, strictCode); - else [elemValue, elemAst, elemIO] = env.partiallyEvaluateCompletionDeref(elem, strictCode); - io.concat(elemIO); - if (elemValue instanceof AbruptCompletion) { - return [elemValue, ast, io]; //todo: log an error message - } else if (elemValue instanceof PossiblyNormalCompletion) { - // TODO: there was a conditional abrupt completion while evaluating elem, so join states somehow - AbstractValue.reportIntrospectionError(elemValue.value); - throw new FatalError(); - } - invariant(elemValue instanceof Value); - partial_elements[nextIndex] = (elemAst: any); - - // ECMA262 12.2.5.2 - if (elem.type === "SpreadElement") { - let spreadObj = elemValue; - partial_elements[nextIndex] = t.spreadElement((elemAst: any)); - - // update the abstract state with the contents of spreadObj, if known - if (spreadObj instanceof ObjectValue && !spreadObj.isPartialObject()) { - // 3. Let iterator be ? GetIterator(spreadObj). - let iterator = GetIterator(realm, spreadObj); - - // 4. Repeat - while (true) { - // a. Let next be ? IteratorStep(iterator). - let next = IteratorStep(realm, iterator); - - // b. If next is false, return nextIndex. - if (next === false) break; - - // c. Let nextValue be ? IteratorValue(next). - let nextValue = IteratorValue(realm, next); - - // d. Let status be CreateDataProperty(array, ToString(ToUint32(nextIndex)), nextValue). - let status = Create.CreateDataProperty(realm, array, new StringValue(realm, nextIndex + ""), nextValue); - - // e. Assert: status is true. - invariant(status === true); - - // f. Let nextIndex be nextIndex + 1. - nextIndex++; - } - } else { - // Update the abstract state to reflect our lack of complete knowledge - // of all of the properties of the result of evaluating elem. - array.makePartial(); - - // terminate the loop if all elements have been processed - if (i === len - 1) break; - - // If there are elements that come after this spread element, we need - // to take their effects into account for the abstract state that results - // from the array expression. - - // First check if the runtime spread operation cannot fail - if (spreadObj instanceof AbstractValue && spreadObj.getType() === "Array") { - let method = GetMethod(realm, spreadObj, realm.intrinsics.SymbolIterator); - if (method === realm.intrinsics.ArrayProto_values) continue; - } - - // At this point we have to be pessimistic and assume that iterating spreadObj may - // throw an exception, in which case we can't assume that the remaining element - // expressions will be evaluated at runtime. As a consequence their effects - // have be provisional. - // TODO: join states somehow - AbstractValue.reportIntrospectionError(spreadObj); - throw new FatalError(); - } - } else if (array.isPartialObject()) { - // Dealing with an array element that follows on a spread object that - // could not be iterated at compile time, so the index that this element - // will have at runtime is not known at this point. - - let abstractIndex = AbstractValue.createFromType(realm, NumberValue); - array.$SetPartial(abstractIndex, elemValue, array); - } else { - // Redundant steps. - // 1. Let postIndex be the result of performing ArrayAccumulation for ElementList with arguments array and nextIndex. - // 2. ReturnIfAbrupt(postIndex). - // 3. Let padding be the ElisionWidth of Elision; if Elision is not present, use the numeric value zero. - - // 4. Let initResult be the result of evaluating AssignmentExpression. - // 5. Let initValue be ? GetValue(initResult). - let initValue = elemValue; - - // 6. Let created be CreateDataProperty(array, ToString(ToUint32(postIndex+padding)), initValue). - let created = Create.CreateDataProperty(realm, array, new StringValue(realm, nextIndex++ + ""), initValue); - - // 7. Assert: created is true. - invariant(created === true, "expected data property creation"); - } - } - - // Not necessary since we propagate completions with exceptions. - // 3. ReturnIfAbrupt(len). - - // 4. Perform Set(array, "length", ToUint32(len), false). - Properties.Set(realm, array, "length", new NumberValue(realm, nextIndex), false); - - // 5. NOTE: The above Set cannot fail because of the nature of the object returned by ArrayCreate. - - // 6. Return array. - return [array, t.arrayExpression(partial_elements), io]; -} diff --git a/src/partial-evaluators/ArrowFunctionExpression.js b/src/partial-evaluators/ArrowFunctionExpression.js deleted file mode 100644 index e16ac1387..000000000 --- a/src/partial-evaluators/ArrowFunctionExpression.js +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Copyright (c) 2017-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -/* @flow strict-local */ - -import type { BabelNodeArrowFunctionExpression, BabelNodeStatement } from "@babel/types"; -import type { LexicalEnvironment } from "../environment.js"; -import type { Realm } from "../realm.js"; - -import { AbruptCompletion } from "../completions.js"; -import { Value } from "../values/index.js"; - -// ECMA262 14.2.16 -export default function( - ast: BabelNodeArrowFunctionExpression, - strictCode: boolean, - env: LexicalEnvironment, - realm: Realm -): [AbruptCompletion | Value, BabelNodeArrowFunctionExpression, Array] { - let result = env.evaluateCompletionDeref(ast, strictCode); - return [result, ast, []]; -} diff --git a/src/partial-evaluators/AssignmentExpression.js b/src/partial-evaluators/AssignmentExpression.js deleted file mode 100644 index f716dba24..000000000 --- a/src/partial-evaluators/AssignmentExpression.js +++ /dev/null @@ -1,163 +0,0 @@ -/** - * Copyright (c) 2017-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -/* @flow */ - -import type { - BabelBinaryOperator, - BabelNodeAssignmentExpression, - BabelNodeExpression, - BabelNodeStatement, -} from "@babel/types"; -import type { LexicalEnvironment } from "../environment.js"; -import type { Realm } from "../realm.js"; - -import { computeBinary } from "../evaluators/BinaryExpression.js"; -import { createAbstractValueForBinary } from "../partial-evaluators/BinaryExpression.js"; -import { AbruptCompletion, Completion } from "../completions.js"; -import { Reference } from "../environment.js"; -import { FatalError } from "../errors.js"; -import { BooleanValue, ConcreteValue, NullValue, ObjectValue, UndefinedValue, Value } from "../values/index.js"; -import { IsAnonymousFunctionDefinition, IsIdentifierRef, HasOwnProperty } from "../methods/index.js"; -import { Environment, Functions, Join, Properties } from "../singletons.js"; - -import * as t from "@babel/types"; -import invariant from "../invariant.js"; - -// ECMA262 12.15 Assignment Operators -export default function( - ast: BabelNodeAssignmentExpression, - strictCode: boolean, - env: LexicalEnvironment, - realm: Realm -): [Completion | Value, BabelNodeExpression, Array] { - let LeftHandSideExpression = ast.left; - let AssignmentExpression = ast.right; - let AssignmentOperator = ast.operator; - - // AssignmentExpression : LeftHandSideExpression = AssignmentExpression - if (AssignmentOperator === "=") { - // 1. If LeftHandSideExpression is neither an ObjectLiteral nor an ArrayLiteral, then - if (LeftHandSideExpression.type !== "ObjectLiteral" && LeftHandSideExpression.type !== "ArrayLiteral") { - // a. Let lref be the result of evaluating LeftHandSideExpression. - let [lref, last, lio] = env.partiallyEvaluateCompletion(LeftHandSideExpression, strictCode); - - // b. ReturnIfAbrupt(lref). - if (lref instanceof AbruptCompletion) return [lref, (last: any), lio]; - let leftCompletion; - [leftCompletion, lref] = Join.unbundleNormalCompletion(lref); - - // c. Let rref be the result of evaluating AssignmentExpression. - // d. Let rval be ? GetValue(rref). - let [rval, rast, rio] = env.partiallyEvaluateCompletionDeref(AssignmentExpression, strictCode); - let io = lio.concat(rio); - if (rval instanceof AbruptCompletion) { - return [rval, t.assignmentExpression(ast.operator, (last: any), (rast: any)), io]; - } - let rightCompletion; - [rightCompletion, rval] = Join.unbundleNormalCompletion(rval); - invariant(rval instanceof Value); - - // e. If IsAnonymousFunctionDefinition(AssignmentExpression) and IsIdentifierRef of LeftHandSideExpression are both true, then - if ( - IsAnonymousFunctionDefinition(realm, AssignmentExpression) && - IsIdentifierRef(realm, LeftHandSideExpression) - ) { - invariant(rval instanceof ObjectValue); - - // i. Let hasNameProperty be ? HasOwnProperty(rval, "name"). - let hasNameProperty = HasOwnProperty(realm, rval, "name"); - - // ii. If hasNameProperty is false, perform SetFunctionName(rval, GetReferencedName(lref)). - if (!hasNameProperty) { - invariant(lref instanceof Reference); - Functions.SetFunctionName(realm, rval, Environment.GetReferencedName(realm, lref)); - } - } - - // f. Perform ? PutValue(lref, rval). - Properties.PutValue(realm, lref, rval); - - // g. Return rval. - let resultAst = t.assignmentExpression(ast.operator, (last: any), (rast: any)); - rval = Join.composeNormalCompletions(leftCompletion, rightCompletion, rval, realm); - return [rval, resultAst, io]; - } - throw new FatalError("Patterns aren't supported yet"); - // 2. Let assignmentPattern be the parse of the source text corresponding to LeftHandSideExpression using AssignmentPattern[?Yield] as the goal symbol. - // 3. Let rref be the result of evaluating AssignmentExpression. - // 4. Let rval be ? GetValue(rref). - // 5. Let status be the result of performing DestructuringAssignmentEvaluation of assignmentPattern using rval as the argument. - // 6. ReturnIfAbrupt(status). - // 7. Return rval. - } - - // AssignmentExpression : LeftHandSideExpression AssignmentOperator AssignmentExpression - - // 1. Let lref be the result of evaluating LeftHandSideExpression. - let [lref, last, lio] = env.partiallyEvaluateCompletion(LeftHandSideExpression, strictCode); - if (lref instanceof AbruptCompletion) return [lref, (last: any), lio]; - let leftCompletion; - [leftCompletion, lref] = Join.unbundleNormalCompletion(lref); - - // 2. Let lval be ? GetValue(lref). - let lval = Environment.GetValue(realm, lref); - - // 3. Let rref be the result of evaluating AssignmentExpression. - // 4. Let rval be ? GetValue(rref). - let [rval, rast, rio] = env.partiallyEvaluateCompletionDeref(AssignmentExpression, strictCode); - let io = lio.concat(rio); - if (rval instanceof AbruptCompletion) { - return [rval, t.assignmentExpression(ast.operator, (last: any), (rast: any)), io]; - } - let rightCompletion; - [rightCompletion, rval] = Join.unbundleNormalCompletion(rval); - invariant(rval instanceof Value); - - // 5. Let op be the @ where AssignmentOperator is @=. - let op = ((AssignmentOperator.slice(0, -1): any): BabelBinaryOperator); - - // 6. Let r be the result of applying op to lval and rval as if evaluating the expression lval op rval. - let resultValue, resultAst; - if (lval instanceof ConcreteValue) { - if (rval instanceof ConcreteValue) { - resultValue = computeBinary(realm, op, lval, rval); - resultAst = t.assignmentExpression(ast.operator, (last: any), t.valueToNode(resultValue.serialize())); - } - } - // if resultValue is undefined, one or both operands are abstract. - if (resultValue === undefined && (op === "==" || op === "===" || op === "!=" || op === "!==")) { - // When comparing to null or undefined, we can return a compile time value if we know the - // other operand must be an object. - if ( - (!lval.mightNotBeObject() && (rval instanceof NullValue || rval instanceof UndefinedValue)) || - (!rval.mightNotBeObject() && (lval instanceof NullValue || lval instanceof UndefinedValue)) - ) { - resultValue = new BooleanValue(realm, op[0] !== "="); - resultAst = t.assignmentExpression(ast.operator, (last: any), t.valueToNode(resultValue.serialize())); - } - } - // todo: special case if one result is known to be 0 or 1 - if (resultAst === undefined) { - resultAst = t.assignmentExpression(ast.operator, (last: any), (rast: any)); - } - return createAbstractValueForBinary( - op, - resultAst, - lval, - rval, - last.loc, - rast.loc, - leftCompletion, - rightCompletion, - resultValue, - io, - realm - ); -} diff --git a/src/partial-evaluators/AwaitExpression.js b/src/partial-evaluators/AwaitExpression.js deleted file mode 100644 index 2a313b3bf..000000000 --- a/src/partial-evaluators/AwaitExpression.js +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2017-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -/* @flow strict-local */ - -import type { BabelNodeAwaitExpression, BabelNodeStatement } from "@babel/types"; -import type { LexicalEnvironment } from "../environment.js"; -import type { Realm } from "../realm.js"; - -import { AbruptCompletion } from "../completions.js"; -import { Value } from "../values/index.js"; - -export default function( - ast: BabelNodeAwaitExpression, - strictCode: boolean, - env: LexicalEnvironment, - realm: Realm -): [AbruptCompletion | Value, BabelNodeAwaitExpression, Array] { - let result = env.evaluateCompletionDeref(ast, strictCode); - return [result, ast, []]; -} diff --git a/src/partial-evaluators/BinaryExpression.js b/src/partial-evaluators/BinaryExpression.js deleted file mode 100644 index 964920a1a..000000000 --- a/src/partial-evaluators/BinaryExpression.js +++ /dev/null @@ -1,122 +0,0 @@ -/** - * Copyright (c) 2017-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -/* @flow */ - -import type { - BabelBinaryOperator, - BabelNodeBinaryExpression, - BabelNodeExpression, - BabelNodeStatement, - BabelNodeSourceLocation, -} from "@babel/types"; -import type { LexicalEnvironment } from "../environment.js"; -import type { Realm } from "../realm.js"; - -import { computeBinary, getPureBinaryOperationResultType } from "../evaluators/BinaryExpression.js"; -import { AbruptCompletion, Completion, NormalCompletion } from "../completions.js"; -import { FatalError } from "../errors.js"; -import { Join } from "../singletons.js"; -import { AbstractValue, BooleanValue, ConcreteValue, NullValue, UndefinedValue, Value } from "../values/index.js"; - -import * as t from "@babel/types"; -import invariant from "../invariant.js"; - -export default function( - ast: BabelNodeBinaryExpression, - strictCode: boolean, - env: LexicalEnvironment, - realm: Realm -): [Completion | Value, BabelNodeExpression, Array] { - let [lval, leftAst, leftIO] = env.partiallyEvaluateCompletionDeref(ast.left, strictCode); - if (lval instanceof AbruptCompletion) return [lval, (leftAst: any), leftIO]; - let leftCompletion; - [leftCompletion, lval] = Join.unbundleNormalCompletion(lval); - invariant(lval instanceof Value); - - let [rval, rightAst, rightIO] = env.partiallyEvaluateCompletionDeref(ast.right, strictCode); - let io = leftIO.concat(rightIO); - if (rval instanceof AbruptCompletion) { - // todo: if leftCompletion is a PossiblyNormalCompletion, compose - return [rval, t.binaryExpression(ast.operator, (leftAst: any), (rightAst: any)), io]; - } - let rightCompletion; - [rightCompletion, rval] = Join.unbundleNormalCompletion(rval); - invariant(rval instanceof Value); - - let op = ast.operator; - let resultValue, resultAst; - if (lval instanceof ConcreteValue) { - if (rval instanceof ConcreteValue) { - resultValue = computeBinary(realm, op, lval, rval); - resultAst = t.valueToNode(resultValue.serialize()); - } - } - // if resultValue is undefined, one or both operands are abstract. - if (resultValue === undefined && (op === "==" || op === "===" || op === "!=" || op === "!==")) { - // When comparing to null or undefined, we can return a compile time value if we know the - // other operand must be an object. - if ( - (!lval.mightNotBeObject() && (rval instanceof NullValue || rval instanceof UndefinedValue)) || - (!rval.mightNotBeObject() && (lval instanceof NullValue || lval instanceof UndefinedValue)) - ) { - resultValue = new BooleanValue(realm, op[0] !== "="); - resultAst = t.valueToNode(resultValue.serialize()); - } - } - // todo: special case if one result is known to be 0 or 1 - if (resultAst === undefined) { - resultAst = t.binaryExpression(op, (leftAst: any), (rightAst: any)); - } - return createAbstractValueForBinary( - op, - resultAst, - lval, - rval, - leftAst.loc, - rightAst.loc, - leftCompletion, - rightCompletion, - resultValue, - io, - realm - ); -} - -export function createAbstractValueForBinary( - op: BabelBinaryOperator, - ast: BabelNodeExpression, - lval: Value, - rval: Value, - lloc: ?BabelNodeSourceLocation, - rloc: ?BabelNodeSourceLocation, - leftCompletion: void | NormalCompletion, - rightCompletion: void | NormalCompletion, - resultValue: void | Value, - io: Array, - realm: Realm -): [Completion | Value, BabelNodeExpression, Array] { - if (resultValue === undefined) { - let resultType = getPureBinaryOperationResultType(realm, op, lval, rval, lloc, rloc); - if (resultType === undefined) { - // The operation may result in side effects that we cannot track. - // Since we have no idea what those effects are, we can either forget - // (havoc) everything we know at this stage, or we can fault the - // program and/or native model and stop evaluating. - // We choose to do the latter. - // TODO: report the error and carry on assuming no side effects. - let val = lval instanceof AbstractValue ? lval : rval; - AbstractValue.reportIntrospectionError((val: any)); - throw new FatalError(); - } - resultValue = AbstractValue.createFromBinaryOp(realm, op, lval, rval, ast.loc); - } - let r = Join.composeNormalCompletions(leftCompletion, rightCompletion, resultValue, realm); - return [r, ast, io]; -} diff --git a/src/partial-evaluators/BlockStatement.js b/src/partial-evaluators/BlockStatement.js deleted file mode 100644 index 6ced59ebd..000000000 --- a/src/partial-evaluators/BlockStatement.js +++ /dev/null @@ -1,62 +0,0 @@ -/** - * Copyright (c) 2017-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -/* @flow strict-local */ - -import type { BabelNodeBlockStatement, BabelNodeStatement } from "@babel/types"; -import type { Realm } from "../realm.js"; -import type { LexicalEnvironment } from "../environment.js"; - -import { Completion, NormalCompletion } from "../completions.js"; -import { EmptyValue, StringValue, Value } from "../values/index.js"; -import { Environment, Functions } from "../singletons.js"; - -import invariant from "../invariant.js"; -import * as t from "@babel/types"; - -// ECMA262 13.2.13 -export default function( - ast: BabelNodeBlockStatement, - strictCode: boolean, - env: LexicalEnvironment, - realm: Realm -): [Completion | Value, BabelNodeStatement, Array] { - // 1. Let oldEnv be the running execution context's LexicalEnvironment. - let oldEnv = realm.getRunningContext().lexicalEnvironment; - - // 2. Let blockEnv be NewDeclarativeEnvironment(oldEnv). - let blockEnv = Environment.NewDeclarativeEnvironment(realm, oldEnv); - - // 3. Perform BlockDeclarationInstantiation(StatementList, blockEnv). - Environment.BlockDeclarationInstantiation(realm, strictCode, ast.body, blockEnv); - - // 4. Set the running execution context's LexicalEnvironment to blockEnv. - realm.getRunningContext().lexicalEnvironment = blockEnv; - - try { - // 5. Let blockValue be the result of evaluating StatementList. - let blockValue: void | NormalCompletion | Value; - - if (ast.directives) { - for (let directive of ast.directives) { - blockValue = new StringValue(realm, directive.value.value); - } - } - - let [res, bAst] = Functions.PartiallyEvaluateStatements(ast.body, blockValue, strictCode, blockEnv, realm); - invariant(bAst.length > 0 || res instanceof EmptyValue); - if (bAst.length === 0) return [res, t.emptyStatement(), []]; - let rAst = t.blockStatement(bAst, ast.directives); - return [res, rAst, []]; - } finally { - // 6. Set the running execution context's LexicalEnvironment to oldEnv. - realm.getRunningContext().lexicalEnvironment = oldEnv; - realm.onDestroyScope(blockEnv); - } -} diff --git a/src/partial-evaluators/BooleanLiteral.js b/src/partial-evaluators/BooleanLiteral.js deleted file mode 100644 index 11e707044..000000000 --- a/src/partial-evaluators/BooleanLiteral.js +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2017-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -/* @flow strict-local */ - -import type { BabelNodeBooleanLiteral, BabelNodeStatement } from "@babel/types"; -import type { LexicalEnvironment } from "../environment.js"; -import type { Realm } from "../realm.js"; - -import { BooleanValue } from "../values/index.js"; - -export default function( - ast: BabelNodeBooleanLiteral, - strictCode: boolean, - env: LexicalEnvironment, - realm: Realm -): [BooleanValue, BabelNodeBooleanLiteral, Array] { - let result = new BooleanValue(realm, ast.value); - return [result, ast, []]; -} diff --git a/src/partial-evaluators/BreakStatement.js b/src/partial-evaluators/BreakStatement.js deleted file mode 100644 index ff1b1442b..000000000 --- a/src/partial-evaluators/BreakStatement.js +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2017-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -/* @flow strict-local */ - -import type { BabelNodeBreakStatement, BabelNodeStatement } from "@babel/types"; -import type { Realm } from "../realm.js"; -import type { LexicalEnvironment } from "../environment.js"; - -import { BreakCompletion } from "../completions.js"; - -export default function( - ast: BabelNodeBreakStatement, - strictCode: boolean, - env: LexicalEnvironment, - realm: Realm -): [BreakCompletion, BabelNodeBreakStatement, Array] { - let result = new BreakCompletion(realm.intrinsics.empty, undefined, ast.loc, ast.label && ast.label.name); - return [result, ast, []]; -} diff --git a/src/partial-evaluators/CallExpression.js b/src/partial-evaluators/CallExpression.js deleted file mode 100644 index c19a3a50f..000000000 --- a/src/partial-evaluators/CallExpression.js +++ /dev/null @@ -1,228 +0,0 @@ -/** - * Copyright (c) 2017-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -/* @flow */ - -import type { BabelNodeCallExpression, BabelNodeExpression, BabelNodeStatement } from "@babel/types"; -import type { Realm } from "../realm.js"; -import type { LexicalEnvironment } from "../environment.js"; - -import { AbruptCompletion, Completion, PossiblyNormalCompletion, SimpleNormalCompletion } from "../completions.js"; -import { EnvironmentRecord, Reference } from "../environment.js"; -import { EvaluateDirectCallWithArgList, GetThisValue, IsInTailPosition, SameValue } from "../methods/index.js"; -import { Environment, Functions, Join } from "../singletons.js"; -import { AbstractValue, BooleanValue, FunctionValue, Value } from "../values/index.js"; - -import * as t from "@babel/types"; -import invariant from "../invariant.js"; - -// ECMA262 12.3.4.1 -export default function( - ast: BabelNodeCallExpression, - strictCode: boolean, - env: LexicalEnvironment, - realm: Realm -): [Completion | Value, BabelNodeExpression, Array] { - // 1. Let ref be the result of evaluating MemberExpression. - let [ref, calleeAst, calleeIO] = env.partiallyEvaluateCompletion(ast.callee, strictCode); - if (ref instanceof AbruptCompletion) return [ref, (calleeAst: any), calleeIO]; - let completion; - if (ref instanceof PossiblyNormalCompletion) { - completion = ref; - ref = completion.value; - } - invariant(ref instanceof Value || ref instanceof Reference); - - // 2. Let func be ? GetValue(ref). - let func = Environment.GetValue(realm, ref); - - let io = calleeIO; - let partialArgs = []; - let argVals = []; - for (let arg of ast.arguments) { - let [argValue, argAst, argIO] = env.partiallyEvaluateCompletionDeref(arg, strictCode); - io = io.concat(argIO); - partialArgs.push((argAst: any)); - if (argValue instanceof AbruptCompletion) { - if (completion instanceof PossiblyNormalCompletion) - completion = Join.stopEffectCaptureJoinApplyAndReturnCompletion(completion, argValue, realm); - else completion = argValue; - let resultAst = t.callExpression((calleeAst: any), partialArgs); - return [completion, resultAst, io]; - } - if (argValue instanceof PossiblyNormalCompletion) { - argVals.push(argValue.value); - if (completion instanceof PossiblyNormalCompletion) - completion = Join.composeNormalCompletions(completion, argValue, argValue.value, realm); - else completion = argValue; - } else { - invariant(argValue instanceof Value); - argVals.push(argValue); - } - } - - let previousLoc = realm.setNextExecutionContextLocation(ast.loc); - try { - let callResult = EvaluateCall(ref, func, ast, argVals, strictCode, env, realm); - if (callResult instanceof AbruptCompletion) { - if (completion instanceof PossiblyNormalCompletion) - completion = Join.stopEffectCaptureJoinApplyAndReturnCompletion(completion, callResult, realm); - else completion = callResult; - let resultAst = t.callExpression((calleeAst: any), partialArgs); - return [completion, resultAst, io]; - } - let callCompletion; - [callCompletion, callResult] = Join.unbundleNormalCompletion(callResult); - invariant(callResult instanceof Value); - invariant(completion === undefined || completion instanceof PossiblyNormalCompletion); - completion = Join.composeNormalCompletions(completion, callCompletion, callResult, realm); - if (completion instanceof PossiblyNormalCompletion) { - realm.captureEffects(completion); - } - return [completion, t.callExpression((calleeAst: any), partialArgs), io]; - } finally { - realm.setNextExecutionContextLocation(previousLoc); - } -} - -function callBothFunctionsAndJoinTheirEffects( - funcs: Array, - ast: BabelNodeCallExpression, - argVals: Array, - strictCode: boolean, - env: LexicalEnvironment, - realm: Realm -): AbruptCompletion | Value { - let [cond, func1, func2] = funcs; - invariant(cond instanceof AbstractValue && cond.getType() === BooleanValue); - invariant(Value.isTypeCompatibleWith(func1.getType(), FunctionValue)); - invariant(Value.isTypeCompatibleWith(func2.getType(), FunctionValue)); - - const e1 = realm.evaluateForEffects( - () => EvaluateCall(func1, func1, ast, argVals, strictCode, env, realm), - undefined, - "callBothFunctionsAndJoinTheirEffects/1" - ); - let r1 = e1.result.shallowCloneWithoutEffects(); - - const e2 = realm.evaluateForEffects( - () => EvaluateCall(func2, func2, ast, argVals, strictCode, env, realm), - undefined, - "callBothFunctionsAndJoinTheirEffects/2" - ); - let r2 = e2.result.shallowCloneWithoutEffects(); - - let joinedEffects = Join.joinForkOrChoose(realm, cond, e1.shallowCloneWithResult(r1), e2.shallowCloneWithResult(r2)); - let joinedCompletion = joinedEffects.result; - if (joinedCompletion instanceof PossiblyNormalCompletion) { - // in this case one of the branches may complete abruptly, which means that - // not all control flow branches join into one flow at this point. - // Consequently we have to continue tracking changes until the point where - // all the branches come together into one. - joinedCompletion = realm.composeWithSavedCompletion(joinedCompletion); - } - - // Note that the effects of (non joining) abrupt branches are not included - // in joinedEffects, but are tracked separately inside joinedCompletion. - realm.applyEffects(joinedEffects); - - // return or throw completion - if (joinedCompletion instanceof SimpleNormalCompletion) joinedCompletion = joinedCompletion.value; - invariant(joinedCompletion instanceof AbruptCompletion || joinedCompletion instanceof Value); - return joinedCompletion; -} - -function EvaluateCall( - ref: Value | Reference, - func: Value, - ast: BabelNodeCallExpression, - argList: Array, - strictCode: boolean, - env: LexicalEnvironment, - realm: Realm -): AbruptCompletion | Value { - if (func instanceof AbstractValue && Value.isTypeCompatibleWith(func.getType(), FunctionValue)) { - if (func.kind === "conditional") - return callBothFunctionsAndJoinTheirEffects(func.args, ast, argList, strictCode, env, realm); - - // The called function comes from the environmental model and we require that - // such functions have no visible side-effects. Hence we can carry on - // by returning a call node with the arguments updated with their partial counterparts. - // TODO: obtain the type of the return value from the abstract function. - return AbstractValue.createFromType(realm, Value); - } - // If func is abstract and not known to be a safe function, we can't safely continue. - func = func.throwIfNotConcrete(); - - // 3. If Type(ref) is Reference and IsPropertyReference(ref) is false and GetReferencedName(ref) is "eval", then - if ( - ref instanceof Reference && - !Environment.IsPropertyReference(realm, ref) && - Environment.GetReferencedName(realm, ref) === "eval" - ) { - // a. If SameValue(func, %eval%) is true, then - if (SameValue(realm, func, realm.intrinsics.eval)) { - // i. Let argList be ? ArgumentListEvaluation(Arguments). - - // ii. If argList has no elements, return undefined. - if (argList.length === 0) return realm.intrinsics.undefined; - - // iii. Let evalText be the first element of argList. - let evalText = argList[0]; - - // iv. If the source code matching this CallExpression is strict code, let strictCaller be true. Otherwise let strictCaller be false. - let strictCaller = strictCode; - - // v. Let evalRealm be the current Realm Record. - let evalRealm = realm; - - // vi. Return ? PerformEval(evalText, evalRealm, strictCaller, true). - return Functions.PerformEval(realm, evalText, evalRealm, strictCaller, true); - } - } - - let thisValue; - - // 4. If Type(ref) is Reference, then - if (ref instanceof Reference) { - // a. If IsPropertyReference(ref) is true, then - if (Environment.IsPropertyReference(realm, ref)) { - // i. Let thisValue be GetThisValue(ref). - thisValue = GetThisValue(realm, ref); - } else { - // b. Else, the base of ref is an Environment Record - // i. Let refEnv be GetBase(ref). - let refEnv = Environment.GetBase(realm, ref); - invariant(refEnv instanceof EnvironmentRecord); - - // ii. Let thisValue be refEnv.WithBaseObject(). - thisValue = refEnv.WithBaseObject(); - } - } else { - // 5. Else Type(ref) is not Reference, - // a. Let thisValue be undefined. - thisValue = realm.intrinsics.undefined; - } - - // 6. Let thisCall be this CallExpression. - let thisCall = ast; - - // 7. Let tailCall be IsInTailPosition(thisCall). (See 14.6.1) - let tailCall = IsInTailPosition(realm, thisCall); - - // 8. Return ? EvaluateDirectCall(func, thisValue, Arguments, tailCall). - - try { - realm.currentLocation = ast.loc; // this helps us to detect recursive calls - return EvaluateDirectCallWithArgList(realm, strictCode, env, ref, func, thisValue, argList, tailCall); - } catch (err) { - if (err instanceof Completion) return err; - throw err; - } -} diff --git a/src/partial-evaluators/CatchClause.js b/src/partial-evaluators/CatchClause.js deleted file mode 100644 index 50d6fa93d..000000000 --- a/src/partial-evaluators/CatchClause.js +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Copyright (c) 2017-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -/* @flow */ - -import type { BabelNodeCatchClause, BabelNodeStatement } from "@babel/types"; -import type { Realm } from "../realm.js"; -import type { LexicalEnvironment } from "../environment.js"; - -import { AbruptCompletion, ThrowCompletion } from "../completions.js"; -import { Value } from "../values/index.js"; -import invariant from "../invariant.js"; - -// ECAM262 13.15.7 -export default function( - ast: BabelNodeCatchClause, - strictCode: boolean, - env: LexicalEnvironment, - realm: Realm, - thrownValue: any -): [AbruptCompletion | Value, BabelNodeCatchClause, Array] { - invariant(thrownValue instanceof ThrowCompletion, "Metadata isn't a throw completion"); - - let result = env.evaluateCompletionDeref(ast, strictCode); - return [result, ast, []]; -} diff --git a/src/partial-evaluators/ClassDeclaration.js b/src/partial-evaluators/ClassDeclaration.js deleted file mode 100644 index fe8afafb7..000000000 --- a/src/partial-evaluators/ClassDeclaration.js +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2017-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -/* @flow strict-local */ - -import type { BabelNodeClassDeclaration, BabelNodeStatement } from "@babel/types"; -import type { LexicalEnvironment } from "../environment.js"; -import type { Realm } from "../realm.js"; - -import { AbruptCompletion } from "../completions.js"; -import { Value } from "../values/index.js"; - -export default function( - ast: BabelNodeClassDeclaration, - strictCode: boolean, - env: LexicalEnvironment, - realm: Realm -): [AbruptCompletion | Value, BabelNodeClassDeclaration, Array] { - let result = env.evaluateCompletionDeref(ast, strictCode); - return [result, ast, []]; -} diff --git a/src/partial-evaluators/ClassExpression.js b/src/partial-evaluators/ClassExpression.js deleted file mode 100644 index a52c1bfde..000000000 --- a/src/partial-evaluators/ClassExpression.js +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2017-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -/* @flow strict-local */ - -import type { BabelNodeClassExpression, BabelNodeStatement } from "@babel/types"; -import type { LexicalEnvironment } from "../environment.js"; -import { FatalError } from "../errors.js"; -import type { Realm } from "../realm.js"; - -import { AbruptCompletion } from "../completions.js"; -import { Value } from "../values/index.js"; - -export default function( - ast: BabelNodeClassExpression, - strictCode: boolean, - env: LexicalEnvironment, - realm: Realm -): [AbruptCompletion | Value, BabelNodeClassExpression, Array] { - throw new FatalError("TODO: ClassExpression"); -} diff --git a/src/partial-evaluators/ConditionalExpression.js b/src/partial-evaluators/ConditionalExpression.js deleted file mode 100644 index 71943b6bd..000000000 --- a/src/partial-evaluators/ConditionalExpression.js +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2017-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -/* @flow strict-local */ - -import type { BabelNodeConditionalExpression, BabelNodeStatement } from "@babel/types"; -import type { LexicalEnvironment } from "../environment.js"; -import type { Realm } from "../realm.js"; - -import { AbruptCompletion } from "../completions.js"; -import { Value } from "../values/index.js"; - -export default function( - ast: BabelNodeConditionalExpression, - strictCode: boolean, - env: LexicalEnvironment, - realm: Realm -): [AbruptCompletion | Value, BabelNodeConditionalExpression, Array] { - let result = env.evaluateCompletionDeref(ast, strictCode); - return [result, ast, []]; -} diff --git a/src/partial-evaluators/ContinueStatement.js b/src/partial-evaluators/ContinueStatement.js deleted file mode 100644 index e3eaece13..000000000 --- a/src/partial-evaluators/ContinueStatement.js +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2017-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -/* @flow strict-local */ - -import type { BabelNodeContinueStatement, BabelNodeStatement } from "@babel/types"; -import type { LexicalEnvironment } from "../environment.js"; -import type { Realm } from "../realm.js"; - -import { ContinueCompletion } from "../completions.js"; - -export default function( - ast: BabelNodeContinueStatement, - strictCode: boolean, - env: LexicalEnvironment, - realm: Realm -): [ContinueCompletion, BabelNodeContinueStatement, Array] { - let result = new ContinueCompletion(realm.intrinsics.empty, undefined, ast.loc, ast.label && ast.label.name); - return [result, ast, []]; -} diff --git a/src/partial-evaluators/Directive.js b/src/partial-evaluators/Directive.js deleted file mode 100644 index f98ca93c0..000000000 --- a/src/partial-evaluators/Directive.js +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2017-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -/* @flow strict-local */ - -import type { BabelNodeDirective, BabelNodeStatement } from "@babel/types"; -import type { LexicalEnvironment } from "../environment.js"; -import type { Realm } from "../realm.js"; - -import { AbruptCompletion } from "../completions.js"; -import { Value } from "../values/index.js"; - -export default function( - ast: BabelNodeDirective, - strictCode: boolean, - env: LexicalEnvironment, - realm: Realm -): [AbruptCompletion | Value, BabelNodeDirective, Array] { - let result = env.evaluateCompletionDeref(ast.value, strictCode); - return [result, ast, []]; -} diff --git a/src/partial-evaluators/DirectiveLiteral.js b/src/partial-evaluators/DirectiveLiteral.js deleted file mode 100644 index 82281bc7f..000000000 --- a/src/partial-evaluators/DirectiveLiteral.js +++ /dev/null @@ -1,12 +0,0 @@ -/** - * Copyright (c) 2017-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -/* @flow strict-local */ - -export { default } from "./StringLiteral"; diff --git a/src/partial-evaluators/DoExpression.js b/src/partial-evaluators/DoExpression.js deleted file mode 100644 index fee813e80..000000000 --- a/src/partial-evaluators/DoExpression.js +++ /dev/null @@ -1,12 +0,0 @@ -/** - * Copyright (c) 2017-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -/* @flow strict-local */ - -export { default } from "./BlockStatement.js"; diff --git a/src/partial-evaluators/DoWhileStatement.js b/src/partial-evaluators/DoWhileStatement.js deleted file mode 100644 index 13d91ee35..000000000 --- a/src/partial-evaluators/DoWhileStatement.js +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Copyright (c) 2017-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -/* @flow strict-local */ - -import type { BabelNodeDoWhileStatement, BabelNodeStatement } from "@babel/types"; -import type { LexicalEnvironment } from "../environment.js"; -import type { Realm } from "../realm.js"; - -import { AbruptCompletion } from "../completions.js"; -import { Value } from "../values/index.js"; - -export default function( - ast: BabelNodeDoWhileStatement, - strictCode: boolean, - env: LexicalEnvironment, - realm: Realm, - labelSet: ?Array -): [AbruptCompletion | Value, BabelNodeDoWhileStatement, Array] { - let result = env.evaluateCompletionDeref(ast, strictCode); - return [result, ast, []]; -} diff --git a/src/partial-evaluators/EmptyStatement.js b/src/partial-evaluators/EmptyStatement.js deleted file mode 100644 index e737a2536..000000000 --- a/src/partial-evaluators/EmptyStatement.js +++ /dev/null @@ -1,25 +0,0 @@ -/** - * Copyright (c) 2017-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -/* @flow strict-local */ - -import type { BabelNodeEmptyStatement, BabelNodeStatement } from "@babel/types"; -import type { LexicalEnvironment } from "../environment.js"; -import type { Realm } from "../realm.js"; - -import { EmptyValue } from "../values/index.js"; - -export default function( - ast: BabelNodeEmptyStatement, - strictCode: boolean, - env: LexicalEnvironment, - realm: Realm -): [EmptyValue, BabelNodeEmptyStatement, Array] { - return [realm.intrinsics.empty, ast, []]; -} diff --git a/src/partial-evaluators/ExpressionStatement.js b/src/partial-evaluators/ExpressionStatement.js deleted file mode 100644 index 6a93c5531..000000000 --- a/src/partial-evaluators/ExpressionStatement.js +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright (c) 2017-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -/* @flow */ - -import type { BabelNodeExpressionStatement, BabelNodeStatement } from "@babel/types"; -import type { LexicalEnvironment } from "../environment.js"; -import type { Realm } from "../realm.js"; - -import { Completion } from "../completions.js"; -import { Value } from "../values/index.js"; -import * as t from "@babel/types"; - -// ECMA262 13.5.1 -export default function( - ast: BabelNodeExpressionStatement, - strictCode: boolean, - env: LexicalEnvironment, - realm: Realm -): [Completion | Value, BabelNodeExpressionStatement, Array] { - let [result, partial_expression_ast, io] = env.partiallyEvaluateCompletionDeref(ast.expression, strictCode); - let partial_ast = t.expressionStatement((partial_expression_ast: any)); - return [result, partial_ast, io]; -} diff --git a/src/partial-evaluators/File.js b/src/partial-evaluators/File.js deleted file mode 100644 index 8d790b21d..000000000 --- a/src/partial-evaluators/File.js +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Copyright (c) 2017-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -/* @flow */ - -import type { BabelNodeFile, BabelNodeStatement } from "@babel/types"; -import type { LexicalEnvironment } from "../environment.js"; -import type { Realm } from "../realm.js"; - -import { Completion } from "../completions.js"; -import { Value } from "../values/index.js"; -import * as t from "@babel/types"; - -export default function( - ast: BabelNodeFile, - strictCode: boolean, - env: LexicalEnvironment, - realm: Realm -): [Completion | Value, BabelNodeFile, Array] { - let [result, partial_program, io] = env.partiallyEvaluateCompletionDeref(ast.program, strictCode); - let partial_file = t.file((partial_program: any), ast.comments, ast.tokens); - return [result, partial_file, io]; -} diff --git a/src/partial-evaluators/ForInStatement.js b/src/partial-evaluators/ForInStatement.js deleted file mode 100644 index 33ccf7690..000000000 --- a/src/partial-evaluators/ForInStatement.js +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Copyright (c) 2017-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -/* @flow strict-local */ - -import type { BabelNodeForInStatement, BabelNodeStatement } from "@babel/types"; -import type { LexicalEnvironment } from "../environment.js"; -import type { Realm } from "../realm.js"; - -import { AbruptCompletion } from "../completions.js"; -import { Value } from "../values/index.js"; - -// ECMA262 13.7.5.11 -export default function( - ast: BabelNodeForInStatement, - strictCode: boolean, - env: LexicalEnvironment, - realm: Realm, - labelSet: ?Array -): [AbruptCompletion | Value, BabelNodeForInStatement, Array] { - let result = env.evaluateCompletionDeref(ast, strictCode); - return [result, ast, []]; -} diff --git a/src/partial-evaluators/ForOfStatement.js b/src/partial-evaluators/ForOfStatement.js deleted file mode 100644 index 6a9409f92..000000000 --- a/src/partial-evaluators/ForOfStatement.js +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Copyright (c) 2017-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -/* @flow strict-local */ - -import type { BabelNodeForOfStatement, BabelNodeStatement } from "@babel/types"; -import type { LexicalEnvironment } from "../environment.js"; -import type { Realm } from "../realm.js"; - -import { AbruptCompletion } from "../completions.js"; -import { Value } from "../values/index.js"; - -// ECMA262 13.7.5.11 -export default function( - ast: BabelNodeForOfStatement, - strictCode: boolean, - env: LexicalEnvironment, - realm: Realm, - labelSet: ?Array -): [AbruptCompletion | Value, BabelNodeForOfStatement, Array] { - let result = env.evaluateCompletionDeref(ast, strictCode); - return [result, ast, []]; -} diff --git a/src/partial-evaluators/ForStatement.js b/src/partial-evaluators/ForStatement.js deleted file mode 100644 index 795fbb64f..000000000 --- a/src/partial-evaluators/ForStatement.js +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Copyright (c) 2017-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -/* @flow strict-local */ - -import type { BabelNodeForStatement, BabelNodeStatement } from "@babel/types"; -import type { LexicalEnvironment } from "../environment.js"; -import type { Realm } from "../realm.js"; - -import { AbruptCompletion } from "../completions.js"; -import { Value } from "../values/index.js"; - -// ECMA262 13.7.4.7 -export default function( - ast: BabelNodeForStatement, - strictCode: boolean, - env: LexicalEnvironment, - realm: Realm, - labelSet: ?Array -): [AbruptCompletion | Value, BabelNodeForStatement, Array] { - let result = env.evaluateCompletionDeref(ast, strictCode); - return [result, ast, []]; -} diff --git a/src/partial-evaluators/FunctionDeclaration.js b/src/partial-evaluators/FunctionDeclaration.js deleted file mode 100644 index c980b1e4b..000000000 --- a/src/partial-evaluators/FunctionDeclaration.js +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Copyright (c) 2017-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -/* @flow strict-local */ - -import type { BabelNodeFunctionDeclaration, BabelNodeStatement } from "@babel/types"; -import type { LexicalEnvironment } from "../environment.js"; -import type { Realm } from "../realm.js"; - -import { AbruptCompletion } from "../completions.js"; -import { Value } from "../values/index.js"; - -// ECMA262 14.1.20 -export default function( - ast: BabelNodeFunctionDeclaration, - strictCode: boolean, - env: LexicalEnvironment, - realm: Realm -): [AbruptCompletion | Value, BabelNodeFunctionDeclaration, Array] { - let result = env.evaluateCompletionDeref(ast, strictCode); - return [result, ast, []]; -} diff --git a/src/partial-evaluators/FunctionExpression.js b/src/partial-evaluators/FunctionExpression.js deleted file mode 100644 index cb743bce6..000000000 --- a/src/partial-evaluators/FunctionExpression.js +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2017-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -/* @flow strict-local */ - -import type { BabelNodeFunctionExpression, BabelNodeStatement } from "@babel/types"; -import type { LexicalEnvironment } from "../environment.js"; -import type { Realm } from "../realm.js"; - -import { AbruptCompletion } from "../completions.js"; -import { Value } from "../values/index.js"; - -export default function( - ast: BabelNodeFunctionExpression, - strictCode: boolean, - env: LexicalEnvironment, - realm: Realm -): [AbruptCompletion | Value, BabelNodeFunctionExpression, Array] { - let result = env.evaluateCompletionDeref(ast, strictCode); - return [result, ast, []]; -} diff --git a/src/partial-evaluators/Identifier.js b/src/partial-evaluators/Identifier.js deleted file mode 100644 index 34b2b5636..000000000 --- a/src/partial-evaluators/Identifier.js +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Copyright (c) 2017-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -/* @flow strict-local */ - -import type { BabelNodeIdentifier, BabelNodeStatement } from "@babel/types"; -import type { LexicalEnvironment, Reference } from "../environment.js"; -import type { Realm } from "../realm.js"; - -import { AbruptCompletion } from "../completions.js"; -import { Value } from "../values/index.js"; - -// ECMA262 12.1.6 -export default function( - ast: BabelNodeIdentifier, - strictCode: boolean, - env: LexicalEnvironment, - realm: Realm -): [AbruptCompletion | Reference | Value, BabelNodeIdentifier, Array] { - let result = env.evaluateCompletion(ast, strictCode); - return [result, ast, []]; -} diff --git a/src/partial-evaluators/IfStatement.js b/src/partial-evaluators/IfStatement.js deleted file mode 100644 index c4efa40bf..000000000 --- a/src/partial-evaluators/IfStatement.js +++ /dev/null @@ -1,103 +0,0 @@ -/** - * Copyright (c) 2017-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -/* @flow */ - -import type { BabelNodeIfStatement, BabelNodeStatement } from "@babel/types"; -import type { LexicalEnvironment } from "../environment.js"; -import type { Realm } from "../realm.js"; - -import { AbruptCompletion, Completion, PossiblyNormalCompletion } from "../completions.js"; -import { Reference } from "../environment.js"; -import { UpdateEmpty } from "../methods/index.js"; -import { AbstractValue, Value } from "../values/index.js"; -import { construct_empty_effects } from "../realm.js"; -import { Join } from "../singletons.js"; - -import * as t from "@babel/types"; -import invariant from "../invariant.js"; - -export default function( - ast: BabelNodeIfStatement, - strictCode: boolean, - env: LexicalEnvironment, - realm: Realm -): [Completion | Value, BabelNodeStatement, Array] { - let [exprValue, exprAst, exprIO] = env.partiallyEvaluateCompletionDeref(ast.test, strictCode); - if (exprValue instanceof AbruptCompletion) return [exprValue, t.expressionStatement((exprAst: any)), exprIO]; - let completion; - if (exprValue instanceof PossiblyNormalCompletion) { - completion = exprValue; - exprValue = completion.value; - } - invariant(exprValue instanceof Value); - - if (!exprValue.mightNotBeTrue()) { - // 3.a. Let stmtCompletion be the result of evaluating the first Statement - let [stmtCompletion, stmtAst, stmtIO] = env.partiallyEvaluateCompletionDeref(ast.consequent, strictCode); - - // 5. Return Completion(UpdateEmpty(stmtCompletion, undefined) - stmtCompletion = UpdateEmpty(realm, stmtCompletion, realm.intrinsics.undefined); - return [stmtCompletion, (stmtAst: any), exprIO.concat(stmtIO)]; - } else if (!exprValue.mightNotBeFalse()) { - let stmtCompletion, stmtAst, stmtIO; - if (ast.alternate) - // 4.a. Let stmtCompletion be the result of evaluating the second Statement - [stmtCompletion, stmtAst, stmtIO] = env.partiallyEvaluateCompletionDeref(ast.alternate, strictCode); - else { - // 3 (of the if only statement). Return NormalCompletion(undefined) - stmtCompletion = realm.intrinsics.undefined; - stmtAst = t.emptyStatement(); - stmtIO = []; - } - // 5. Return Completion(UpdateEmpty(stmtCompletion, undefined) - stmtCompletion = UpdateEmpty(realm, stmtCompletion, realm.intrinsics.undefined); - return [stmtCompletion, (stmtAst: any), exprIO.concat(stmtIO)]; - } - invariant(exprValue instanceof AbstractValue); - - // Evaluate consequent and alternate in sandboxes and get their effects. - let [consequentEffects, conAst, conIO] = realm.partiallyEvaluateNodeForEffects(ast.consequent, strictCode, env); - let consequentAst = (conAst: any); - if (conIO.length > 0) consequentAst = t.blockStatement(conIO.concat(consequentAst)); - - let [alternateEffects, altAst, altIO] = ast.alternate - ? realm.partiallyEvaluateNodeForEffects(ast.alternate, strictCode, env) - : [construct_empty_effects(realm), undefined, []]; - let alternateAst = (altAst: any); - if (altIO.length > 0) alternateAst = t.blockStatement(altIO.concat(alternateAst)); - - // Join the effects, creating an abstract view of what happened, regardless - // of the actual value of exprValue. - const ce = consequentEffects; - let cr = ce.result.shallowCloneWithoutEffects(); - const ae = alternateEffects; - let ar = ae.result.shallowCloneWithoutEffects(); - let joinedEffects = Join.joinForkOrChoose( - realm, - exprValue, - ce.shallowCloneWithResult(cr), - ae.shallowCloneWithResult(ar) - ); - completion = joinedEffects.result; - if (completion instanceof PossiblyNormalCompletion) { - // in this case one of the branches may complete abruptly, which means that - // not all control flow branches join into one flow at this point. - // Consequently we have to continue tracking changes until the point where - // all the branches come together into one. - realm.captureEffects(completion); - } - // Note that the effects of (non joining) abrupt branches are not included - // in joinedEffects, but are tracked separately inside completion. - realm.applyEffects(joinedEffects); - - let resultAst = t.ifStatement((exprAst: any), (consequentAst: any), (alternateAst: any)); - invariant(!(completion instanceof Reference)); - return [completion, resultAst, exprIO]; -} diff --git a/src/partial-evaluators/LabeledStatement.js b/src/partial-evaluators/LabeledStatement.js deleted file mode 100644 index 2a8cbcde6..000000000 --- a/src/partial-evaluators/LabeledStatement.js +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright (c) 2017-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -/* @flow strict-local */ - -import type { BabelNodeLabeledStatement, BabelNodeStatement } from "@babel/types"; -import type { LexicalEnvironment } from "../environment.js"; -import type { Realm } from "../realm.js"; - -import { AbruptCompletion } from "../completions.js"; -import { Value } from "../values/index.js"; - -// ECMA262 13.13.14 - -// ECMA262 13.13.15 -export default function( - ast: BabelNodeLabeledStatement, - strictCode: boolean, - env: LexicalEnvironment, - realm: Realm -): [AbruptCompletion | Value, BabelNodeLabeledStatement, Array] { - let result = env.evaluateCompletionDeref(ast, strictCode); - return [result, ast, []]; -} diff --git a/src/partial-evaluators/LogicalExpression.js b/src/partial-evaluators/LogicalExpression.js deleted file mode 100644 index 9feb7f620..000000000 --- a/src/partial-evaluators/LogicalExpression.js +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2017-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -/* @flow strict-local */ - -import type { BabelNodeLogicalExpression, BabelNodeStatement } from "@babel/types"; -import type { LexicalEnvironment } from "../environment.js"; -import type { Realm } from "../realm.js"; - -import { AbruptCompletion } from "../completions.js"; -import { Value } from "../values/index.js"; - -export default function( - ast: BabelNodeLogicalExpression, - strictCode: boolean, - env: LexicalEnvironment, - realm: Realm -): [AbruptCompletion | Value, BabelNodeLogicalExpression, Array] { - let result = env.evaluateCompletionDeref(ast, strictCode); - return [result, ast, []]; -} diff --git a/src/partial-evaluators/MemberExpression.js b/src/partial-evaluators/MemberExpression.js deleted file mode 100644 index 7684109ad..000000000 --- a/src/partial-evaluators/MemberExpression.js +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Copyright (c) 2017-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -/* @flow strict-local */ - -import type { BabelNodeMemberExpression, BabelNodeStatement } from "@babel/types"; -import type { LexicalEnvironment, Reference } from "../environment.js"; -import type { Realm } from "../realm.js"; - -import { AbruptCompletion } from "../completions.js"; -import { Value } from "../values/index.js"; - -// ECMA262 12.3.2.1 -export default function( - ast: BabelNodeMemberExpression, - strictCode: boolean, - env: LexicalEnvironment, - realm: Realm -): [AbruptCompletion | Reference | Value, BabelNodeMemberExpression, Array] { - let result = env.evaluateCompletion(ast, strictCode); - return [result, ast, []]; -} diff --git a/src/partial-evaluators/MetaProperty.js b/src/partial-evaluators/MetaProperty.js deleted file mode 100644 index 1430cb216..000000000 --- a/src/partial-evaluators/MetaProperty.js +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Copyright (c) 2017-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -/* @flow strict-local */ - -import type { BabelNodeMetaProperty, BabelNodeStatement } from "@babel/types"; -import type { LexicalEnvironment } from "../environment.js"; -import type { Realm } from "../realm.js"; - -import { AbruptCompletion } from "../completions.js"; -import { Value } from "../values/index.js"; - -// ECMA 12.3.8.1 -export default function( - ast: BabelNodeMetaProperty, - strictCode: boolean, - env: LexicalEnvironment, - realm: Realm -): [AbruptCompletion | Value, BabelNodeMetaProperty, Array] { - let result = env.evaluateCompletionDeref(ast, strictCode); - return [result, ast, []]; -} diff --git a/src/partial-evaluators/NewExpression.js b/src/partial-evaluators/NewExpression.js deleted file mode 100644 index 454355307..000000000 --- a/src/partial-evaluators/NewExpression.js +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2017-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -/* @flow strict-local */ - -import type { BabelNodeNewExpression, BabelNodeStatement } from "@babel/types"; -import type { LexicalEnvironment } from "../environment.js"; -import type { Realm } from "../realm.js"; - -import { AbruptCompletion } from "../completions.js"; -import { Value } from "../values/index.js"; - -export default function( - ast: BabelNodeNewExpression, - strictCode: boolean, - env: LexicalEnvironment, - realm: Realm -): [AbruptCompletion | Value, BabelNodeNewExpression, Array] { - let result = env.evaluateCompletionDeref(ast, strictCode); - return [result, ast, []]; -} diff --git a/src/partial-evaluators/NullLiteral.js b/src/partial-evaluators/NullLiteral.js deleted file mode 100644 index a11f8faad..000000000 --- a/src/partial-evaluators/NullLiteral.js +++ /dev/null @@ -1,25 +0,0 @@ -/** - * Copyright (c) 2017-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -/* @flow strict-local */ - -import type { BabelNodeNullLiteral, BabelNodeStatement } from "@babel/types"; -import type { LexicalEnvironment } from "../environment.js"; -import type { NullValue } from "../values/index.js"; -import type { Realm } from "../realm.js"; - -export default function( - ast: BabelNodeNullLiteral, - strictCode: boolean, - env: LexicalEnvironment, - realm: Realm -): [NullValue, BabelNodeNullLiteral, Array] { - let result = realm.intrinsics.null; - return [result, ast, []]; -} diff --git a/src/partial-evaluators/NumericLiteral.js b/src/partial-evaluators/NumericLiteral.js deleted file mode 100644 index 639bf4305..000000000 --- a/src/partial-evaluators/NumericLiteral.js +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2017-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -/* @flow strict-local */ - -import type { BabelNodeNumericLiteral, BabelNodeStatement } from "@babel/types"; -import type { LexicalEnvironment } from "../environment.js"; -import type { Realm } from "../realm.js"; - -import { NumberValue } from "../values/index.js"; - -export default function( - ast: BabelNodeNumericLiteral, - strictCode: boolean, - env: LexicalEnvironment, - realm: Realm -): [NumberValue, BabelNodeNumericLiteral, Array] { - let result = new NumberValue(realm, ast.value); - return [result, ast, []]; -} diff --git a/src/partial-evaluators/ObjectExpression.js b/src/partial-evaluators/ObjectExpression.js deleted file mode 100644 index e72305701..000000000 --- a/src/partial-evaluators/ObjectExpression.js +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Copyright (c) 2017-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -/* @flow strict-local */ - -import type { BabelNodeObjectExpression, BabelNodeStatement } from "@babel/types"; -import type { LexicalEnvironment } from "../environment.js"; -import type { Realm } from "../realm.js"; - -import { AbruptCompletion } from "../completions.js"; -import { Value } from "../values/index.js"; - -// ECMA262 12.2.6.8 -export default function( - ast: BabelNodeObjectExpression, - strictCode: boolean, - env: LexicalEnvironment, - realm: Realm -): [AbruptCompletion | Value, BabelNodeObjectExpression, Array] { - let result = env.evaluateCompletionDeref(ast, strictCode); - return [result, ast, []]; -} diff --git a/src/partial-evaluators/Program.js b/src/partial-evaluators/Program.js deleted file mode 100644 index 600767db5..000000000 --- a/src/partial-evaluators/Program.js +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Copyright (c) 2017-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -/* @flow */ - -import type { BabelNodeProgram, BabelNodeStatement, BabelNodeModuleDeclaration } from "@babel/types"; -import type { LexicalEnvironment } from "../environment.js"; -import type { Realm } from "../realm.js"; - -import { Completion } from "../completions.js"; -import { EmptyValue, Value } from "../values/index.js"; -import { GlobalDeclarationInstantiation } from "../evaluators/Program.js"; - -import IsStrict from "../utils/strict.js"; -import * as t from "@babel/types"; - -export default function( - ast: BabelNodeProgram, - strictCode: boolean, - env: LexicalEnvironment, - realm: Realm -): [Completion | Value, BabelNodeProgram, Array] { - strictCode = IsStrict(ast); - - GlobalDeclarationInstantiation(realm, ast, env, strictCode); - - let partialBody: Array = []; - let val; - - for (let node of ast.body) { - if (node.type !== "FunctionDeclaration") { - let [potentialVal, partialAst, nio] = env.partiallyEvaluateCompletionDeref(node, strictCode); - for (let ioAst of nio) partialBody.push(ioAst); - partialBody.push((partialAst: any)); - if (!(potentialVal instanceof EmptyValue)) val = potentialVal; - } else { - // TODO: this goes away once residual functions are partially evaluated. - partialBody.push(node); - } - } - - // todo: compute a global fixed point by invoking each escaped (i.e. call back) - // function with dummy arguments and joining their effects with the - // global state until there is no invocation that causes further changes to - // the global state. - - let result = val || realm.intrinsics.empty; - return [result, t.program(partialBody, ast.directives), []]; -} diff --git a/src/partial-evaluators/RegExpLiteral.js b/src/partial-evaluators/RegExpLiteral.js deleted file mode 100644 index bde4f75c0..000000000 --- a/src/partial-evaluators/RegExpLiteral.js +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Copyright (c) 2017-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -/* @flow strict-local */ - -import type { BabelNodeRegExpLiteral, BabelNodeStatement } from "@babel/types"; -import type { LexicalEnvironment } from "../environment.js"; -import type { Realm } from "../realm.js"; - -import { RegExpCreate } from "../methods/index.js"; -import { ObjectValue, StringValue } from "../values/index.js"; - -export default function( - ast: BabelNodeRegExpLiteral, - strictCode: boolean, - env: LexicalEnvironment, - realm: Realm -): [ObjectValue, BabelNodeRegExpLiteral, Array] { - let result = RegExpCreate( - realm, - new StringValue(realm, ast.pattern), - ast.flags ? new StringValue(realm, ast.flags) : undefined - ); - return [result, ast, []]; -} diff --git a/src/partial-evaluators/ReturnStatement.js b/src/partial-evaluators/ReturnStatement.js deleted file mode 100644 index 2d9e54ce2..000000000 --- a/src/partial-evaluators/ReturnStatement.js +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Copyright (c) 2017-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -/* @flow strict-local */ - -import type { BabelNodeReturnStatement, BabelNodeStatement } from "@babel/types"; -import type { Realm } from "../realm.js"; -import type { LexicalEnvironment } from "../environment.js"; - -import { AbruptCompletion, ReturnCompletion } from "../completions.js"; - -export default function( - ast: BabelNodeReturnStatement, - strictCode: boolean, - env: LexicalEnvironment, - realm: Realm -): [AbruptCompletion, BabelNodeReturnStatement, Array] { - let result; - if (ast.argument) { - result = env.evaluateCompletionDeref(ast.argument, strictCode); - } else { - result = realm.intrinsics.undefined; - } - if (!(result instanceof AbruptCompletion)) result = new ReturnCompletion(result, undefined, ast.loc); - return [result, ast, []]; -} diff --git a/src/partial-evaluators/SequenceExpression.js b/src/partial-evaluators/SequenceExpression.js deleted file mode 100644 index 7c258b53e..000000000 --- a/src/partial-evaluators/SequenceExpression.js +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2017-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -/* @flow strict-local */ - -import type { BabelNodeSequenceExpression, BabelNodeStatement } from "@babel/types"; -import type { LexicalEnvironment } from "../environment.js"; -import type { Realm } from "../realm.js"; - -import { AbruptCompletion } from "../completions.js"; -import { Value } from "../values/index.js"; - -export default function( - ast: BabelNodeSequenceExpression, - strictCode: boolean, - env: LexicalEnvironment, - realm: Realm -): [AbruptCompletion | Value, BabelNodeSequenceExpression, Array] { - let result = env.evaluateCompletionDeref(ast, strictCode); - return [result, ast, []]; -} diff --git a/src/partial-evaluators/StringLiteral.js b/src/partial-evaluators/StringLiteral.js deleted file mode 100644 index 6d79c3b3d..000000000 --- a/src/partial-evaluators/StringLiteral.js +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2017-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -/* @flow strict-local */ - -import type { BabelNodeStringLiteral, BabelNodeStatement } from "@babel/types"; -import type { LexicalEnvironment } from "../environment.js"; -import type { Realm } from "../realm.js"; - -import { StringValue } from "../values/index.js"; - -export default function( - ast: BabelNodeStringLiteral, - strictCode: boolean, - env: LexicalEnvironment, - realm: Realm -): [StringValue, BabelNodeStringLiteral, Array] { - let result = new StringValue(realm, ast.value); - return [result, ast, []]; -} diff --git a/src/partial-evaluators/SwitchStatement.js b/src/partial-evaluators/SwitchStatement.js deleted file mode 100644 index af82e6614..000000000 --- a/src/partial-evaluators/SwitchStatement.js +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Copyright (c) 2017-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -/* @flow strict-local */ - -import type { BabelNodeSwitchStatement, BabelNodeStatement } from "@babel/types"; -import type { LexicalEnvironment } from "../environment.js"; -import type { Realm } from "../realm.js"; - -import { AbruptCompletion } from "../completions.js"; -import { Value } from "../values/index.js"; - -// 13.12.11 -export default function( - ast: BabelNodeSwitchStatement, - strictCode: boolean, - env: LexicalEnvironment, - realm: Realm, - labelSet: Array -): [AbruptCompletion | Value, BabelNodeSwitchStatement, Array] { - let result = env.evaluateCompletionDeref(ast, strictCode); - return [result, ast, []]; -} diff --git a/src/partial-evaluators/TaggedTemplateExpression.js b/src/partial-evaluators/TaggedTemplateExpression.js deleted file mode 100644 index 179a514db..000000000 --- a/src/partial-evaluators/TaggedTemplateExpression.js +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Copyright (c) 2017-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -/* @flow strict-local */ - -import type { BabelNodeTaggedTemplateExpression, BabelNodeStatement } from "@babel/types"; -import type { LexicalEnvironment } from "../environment.js"; -import type { Realm } from "../realm.js"; - -import { AbruptCompletion } from "../completions.js"; -import { Value } from "../values/index.js"; - -// ECMA262 12.3.7 -export default function( - ast: BabelNodeTaggedTemplateExpression, - strictCode: boolean, - env: LexicalEnvironment, - realm: Realm -): [AbruptCompletion | Value, BabelNodeTaggedTemplateExpression, Array] { - let result = env.evaluateCompletionDeref(ast, strictCode); - return [result, ast, []]; -} diff --git a/src/partial-evaluators/TemplateLiteral.js b/src/partial-evaluators/TemplateLiteral.js deleted file mode 100644 index 0c702cad3..000000000 --- a/src/partial-evaluators/TemplateLiteral.js +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Copyright (c) 2017-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -/* @flow strict-local */ - -import type { BabelNodeTemplateLiteral, BabelNodeStatement } from "@babel/types"; -import type { LexicalEnvironment } from "../environment.js"; -import type { Realm } from "../realm.js"; - -import { AbruptCompletion } from "../completions.js"; -import { Value } from "../values/index.js"; - -// ECMA262 12.2.9 -export default function( - ast: BabelNodeTemplateLiteral, - strictCode: boolean, - env: LexicalEnvironment, - realm: Realm -): [AbruptCompletion | Value, BabelNodeTemplateLiteral, Array] { - let result = env.evaluateCompletionDeref(ast, strictCode); - return [result, ast, []]; -} diff --git a/src/partial-evaluators/ThisExpression.js b/src/partial-evaluators/ThisExpression.js deleted file mode 100644 index 332a36b03..000000000 --- a/src/partial-evaluators/ThisExpression.js +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Copyright (c) 2017-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -/* @flow strict-local */ - -import type { BabelNodeThisExpression, BabelNodeStatement } from "@babel/types"; -import type { LexicalEnvironment } from "../environment.js"; -import type { Realm } from "../realm.js"; - -import { AbruptCompletion } from "../completions.js"; -import { Value } from "../values/index.js"; - -// ECMA262 12.2.2.1 -export default function( - ast: BabelNodeThisExpression, - strictCode: boolean, - env: LexicalEnvironment, - realm: Realm -): [AbruptCompletion | Value, BabelNodeThisExpression, Array] { - let result = env.evaluateCompletionDeref(ast, strictCode); - return [result, ast, []]; -} diff --git a/src/partial-evaluators/ThrowStatement.js b/src/partial-evaluators/ThrowStatement.js deleted file mode 100644 index 66da4ced0..000000000 --- a/src/partial-evaluators/ThrowStatement.js +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Copyright (c) 2017-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -/* @flow */ - -import type { BabelNode, BabelNodeStatement, BabelNodeThrowStatement } from "@babel/types"; -import type { LexicalEnvironment } from "../environment.js"; -import type { Realm } from "../realm.js"; -import { Completion, ThrowCompletion } from "../completions.js"; -import { Value } from "../values/index.js"; -import * as t from "@babel/types"; - -export default function( - ast: BabelNodeThrowStatement, - strictCode: boolean, - env: LexicalEnvironment, - realm: Realm -): [Completion | Value, BabelNode, Array] { - let [argValue, argAst, io] = env.partiallyEvaluateCompletionDeref(ast.argument, strictCode); - if (argValue instanceof Value) { - let c = new ThrowCompletion(argValue, undefined, ast.loc); - let s = t.throwStatement((argAst: any)); // will be an expression because argValue is a Value - return [c, s, io]; - } - return [argValue, argAst, io]; -} diff --git a/src/partial-evaluators/TryStatement.js b/src/partial-evaluators/TryStatement.js deleted file mode 100644 index d9572ebca..000000000 --- a/src/partial-evaluators/TryStatement.js +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2017-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -/* @flow strict-local */ - -import type { BabelNodeTryStatement, BabelNodeStatement } from "@babel/types"; -import type { LexicalEnvironment } from "../environment.js"; -import type { Realm } from "../realm.js"; - -import { AbruptCompletion } from "../completions.js"; -import { Value } from "../values/index.js"; - -export default function( - ast: BabelNodeTryStatement, - strictCode: boolean, - env: LexicalEnvironment, - realm: Realm -): [AbruptCompletion | Value, BabelNodeTryStatement, Array] { - let result = env.evaluateCompletionDeref(ast, strictCode); - return [result, ast, []]; -} diff --git a/src/partial-evaluators/UnaryExpression.js b/src/partial-evaluators/UnaryExpression.js deleted file mode 100644 index e029a1997..000000000 --- a/src/partial-evaluators/UnaryExpression.js +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2017-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -/* @flow strict-local */ - -import type { BabelNodeUnaryExpression, BabelNodeStatement } from "@babel/types"; -import type { LexicalEnvironment } from "../environment.js"; -import type { Realm } from "../realm.js"; - -import { AbruptCompletion } from "../completions.js"; -import { Value } from "../values/index.js"; - -export default function( - ast: BabelNodeUnaryExpression, - strictCode: boolean, - env: LexicalEnvironment, - realm: Realm -): [AbruptCompletion | Value, BabelNodeUnaryExpression, Array] { - let result = env.evaluateCompletionDeref(ast, strictCode); - return [result, ast, []]; -} diff --git a/src/partial-evaluators/UpdateExpression.js b/src/partial-evaluators/UpdateExpression.js deleted file mode 100644 index 61ecdb2d2..000000000 --- a/src/partial-evaluators/UpdateExpression.js +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2017-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -/* @flow strict-local */ - -import type { BabelNodeUpdateExpression, BabelNodeStatement } from "@babel/types"; -import type { LexicalEnvironment } from "../environment.js"; -import type { Realm } from "../realm.js"; - -import { AbruptCompletion } from "../completions.js"; -import { Value } from "../values/index.js"; - -export default function( - ast: BabelNodeUpdateExpression, - strictCode: boolean, - env: LexicalEnvironment, - realm: Realm -): [AbruptCompletion | Value, BabelNodeUpdateExpression, Array] { - let result = env.evaluateCompletionDeref(ast, strictCode); - return [result, ast, []]; -} diff --git a/src/partial-evaluators/VariableDeclaration.js b/src/partial-evaluators/VariableDeclaration.js deleted file mode 100644 index 861cedd74..000000000 --- a/src/partial-evaluators/VariableDeclaration.js +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Copyright (c) 2017-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -/* @flow strict-local */ - -import type { BabelNodeVariableDeclaration, BabelNodeStatement } from "@babel/types"; -import type { LexicalEnvironment } from "../environment.js"; -import type { Realm } from "../realm.js"; - -import { AbruptCompletion } from "../completions.js"; -import { Value } from "../values/index.js"; - -// ECMA262 13.3.2.4 -export default function( - ast: BabelNodeVariableDeclaration, - strictCode: boolean, - env: LexicalEnvironment, - realm: Realm -): [AbruptCompletion | Value, BabelNodeVariableDeclaration, Array] { - let result = env.evaluateCompletionDeref(ast, strictCode); - return [result, ast, []]; -} diff --git a/src/partial-evaluators/WhileStatement.js b/src/partial-evaluators/WhileStatement.js deleted file mode 100644 index 764bd9921..000000000 --- a/src/partial-evaluators/WhileStatement.js +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Copyright (c) 2017-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -/* @flow strict-local */ - -import type { BabelNodeWhileStatement, BabelNodeStatement } from "@babel/types"; -import type { LexicalEnvironment } from "../environment.js"; -import type { Realm } from "../realm.js"; - -import { AbruptCompletion } from "../completions.js"; -import { Value } from "../values/index.js"; - -export default function( - ast: BabelNodeWhileStatement, - strictCode: boolean, - env: LexicalEnvironment, - realm: Realm, - labelSet: ?Array -): [AbruptCompletion | Value, BabelNodeWhileStatement, Array] { - let result = env.evaluateCompletionDeref(ast, strictCode); - return [result, ast, []]; -} diff --git a/src/partial-evaluators/WithStatement.js b/src/partial-evaluators/WithStatement.js deleted file mode 100644 index f78532554..000000000 --- a/src/partial-evaluators/WithStatement.js +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Copyright (c) 2017-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -/* @flow strict-local */ - -import type { BabelNodeWithStatement, BabelNodeStatement } from "@babel/types"; -import type { LexicalEnvironment } from "../environment.js"; -import type { Realm } from "../realm.js"; - -import { AbruptCompletion } from "../completions.js"; -import { Value } from "../values/index.js"; - -// ECMA262 13.11.7 -export default function( - ast: BabelNodeWithStatement, - strictCode: boolean, - env: LexicalEnvironment, - realm: Realm -): [AbruptCompletion | Value, BabelNodeWithStatement, Array] { - let result = env.evaluateCompletionDeref(ast, strictCode); - return [result, ast, []]; -} diff --git a/src/partial-evaluators/YieldExpression.js b/src/partial-evaluators/YieldExpression.js deleted file mode 100644 index 63c027ea5..000000000 --- a/src/partial-evaluators/YieldExpression.js +++ /dev/null @@ -1,25 +0,0 @@ -/** - * Copyright (c) 2017-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -/* @flow strict-local */ - -import type { BabelNodeYieldExpression, BabelNodeStatement } from "@babel/types"; -import type { LexicalEnvironment } from "../environment.js"; -import { FatalError } from "../errors.js"; -import type { Realm } from "../realm.js"; -import type { Value } from "../values/index.js"; - -export default function( - ast: BabelNodeYieldExpression, - strictCode: boolean, - env: LexicalEnvironment, - realm: Realm -): [Value, BabelNodeYieldExpression, Array] { - throw new FatalError("TODO: YieldExpression"); -} diff --git a/src/partial-evaluators/index.js b/src/partial-evaluators/index.js deleted file mode 100644 index 1e13b6879..000000000 --- a/src/partial-evaluators/index.js +++ /dev/null @@ -1,64 +0,0 @@ -/** - * Copyright (c) 2017-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -/* @flow strict-local */ - -export { default as ArrayExpression } from "./ArrayExpression.js"; -export { default as ArrowFunctionExpression } from "./ArrowFunctionExpression.js"; -export { default as AssignmentExpression } from "./AssignmentExpression.js"; -export { default as AwaitExpression } from "./AwaitExpression.js"; -export { default as BinaryExpression } from "./BinaryExpression.js"; -export { default as BlockStatement } from "./BlockStatement.js"; -export { default as BooleanLiteral } from "./BooleanLiteral.js"; -export { default as BreakStatement } from "./BreakStatement.js"; -export { default as CallExpression } from "./CallExpression.js"; -export { default as CatchClause } from "./CatchClause.js"; -export { default as ClassExpression } from "./ClassExpression.js"; -export { default as ClassDeclaration } from "./ClassDeclaration.js"; -export { default as ConditionalExpression } from "./ConditionalExpression.js"; -export { default as ContinueStatement } from "./ContinueStatement.js"; -export { default as Directive } from "./Directive.js"; -export { default as DirectiveLiteral } from "./DirectiveLiteral.js"; -export { default as DoExpression } from "./DoExpression.js"; -export { default as DoWhileStatement } from "./DoWhileStatement.js"; -export { default as EmptyStatement } from "./EmptyStatement.js"; -export { default as ExpressionStatement } from "./ExpressionStatement.js"; -export { default as File } from "./File.js"; -export { default as ForInStatement } from "./ForInStatement.js"; -export { default as ForOfStatement } from "./ForOfStatement.js"; -export { default as ForStatement } from "./ForStatement.js"; -export { default as FunctionDeclaration } from "./FunctionDeclaration.js"; -export { default as FunctionExpression } from "./FunctionExpression.js"; -export { default as Identifier } from "./Identifier.js"; -export { default as IfStatement } from "./IfStatement.js"; -export { default as LabeledStatement } from "./LabeledStatement.js"; -export { default as LogicalExpression } from "./LogicalExpression.js"; -export { default as MemberExpression } from "./MemberExpression.js"; -export { default as MetaProperty } from "./MetaProperty.js"; -export { default as NewExpression } from "./NewExpression.js"; -export { default as NullLiteral } from "./NullLiteral.js"; -export { default as NumericLiteral } from "./NumericLiteral.js"; -export { default as ObjectExpression } from "./ObjectExpression.js"; -export { default as Program } from "./Program.js"; -export { default as RegExpLiteral } from "./RegExpLiteral.js"; -export { default as ReturnStatement } from "./ReturnStatement.js"; -export { default as SequenceExpression } from "./SequenceExpression.js"; -export { default as StringLiteral } from "./StringLiteral.js"; -export { default as SwitchStatement } from "./SwitchStatement.js"; -export { default as TaggedTemplateExpression } from "./TaggedTemplateExpression.js"; -export { default as TemplateLiteral } from "./TemplateLiteral.js"; -export { default as ThisExpression } from "./ThisExpression.js"; -export { default as ThrowStatement } from "./ThrowStatement.js"; -export { default as TryStatement } from "./TryStatement.js"; -export { default as UnaryExpression } from "./UnaryExpression.js"; -export { default as UpdateExpression } from "./UpdateExpression.js"; -export { default as VariableDeclaration } from "./VariableDeclaration.js"; -export { default as WhileStatement } from "./WhileStatement.js"; -export { default as WithStatement } from "./WithStatement.js"; -export { default as YieldExpression } from "./YieldExpression.js"; diff --git a/src/prepack-cli.js b/src/prepack-cli.js index 0674bf025..26e38200b 100644 --- a/src/prepack-cli.js +++ b/src/prepack-cli.js @@ -118,7 +118,6 @@ function run( logStatistics: false, logModules: false, delayInitializations: false, - delayUnsupportedRequires: false, accelerateUnsupportedRequires: true, internalDebug: false, debugScopes: false, diff --git a/src/prepack-options.js b/src/prepack-options.js index 0660a3875..2b421bcea 100644 --- a/src/prepack-options.js +++ b/src/prepack-options.js @@ -22,7 +22,6 @@ export type PrepackOptions = {| compatibility?: Compatibility, debugNames?: boolean, delayInitializations?: boolean, - delayUnsupportedRequires?: boolean, accelerateUnsupportedRequires?: boolean, inputSourceMapFilenames?: Array, internalDebug?: boolean, @@ -122,7 +121,6 @@ export function getSerializerOptions({ lazyObjectsRuntime, heapGraphFormat, delayInitializations = false, - delayUnsupportedRequires = false, accelerateUnsupportedRequires = true, internalDebug = false, debugScopes = false, @@ -136,7 +134,6 @@ export function getSerializerOptions({ }: PrepackOptions): SerializerOptions { let result: SerializerOptions = { delayInitializations, - delayUnsupportedRequires, accelerateUnsupportedRequires, initializeMoreModules, internalDebug, diff --git a/src/prepack-standalone.js b/src/prepack-standalone.js index 3a267001e..644beb945 100644 --- a/src/prepack-standalone.js +++ b/src/prepack-standalone.js @@ -54,19 +54,13 @@ export function prepackSources( if (options.check) { realm.generator = new Generator(realm, "main", realm.pathConditions); let logger = new Logger(realm, !!options.internalDebug); - let modules = new Modules( - realm, - logger, - !!options.logModules, - !!options.delayUnsupportedRequires, - !!options.accelerateUnsupportedRequires - ); + let modules = new Modules(realm, logger, !!options.logModules, !!options.accelerateUnsupportedRequires); let [result] = realm.$GlobalEnv.executeSources(sourceFileCollection.toArray()); if (result instanceof AbruptCompletion) throw result; invariant(options.check); checkResidualFunctions(modules, options.check[0], options.check[1]); return { code: "", map: undefined }; - } else if (options.serialize === true || options.residual !== true) { + } else { let serializer = new Serializer(realm, getSerializerOptions(options)); let serialized = serializer.init(sourceFileCollection, options.sourceMaps, options.onParse); @@ -88,30 +82,7 @@ export function prepackSources( serialized.sourceFilePaths = sourcePaths; } - if (!options.residual) return serialized; - let residualSources = [ - { - filePath: options.outputFilename || "unknown", - fileContents: serialized.code, - sourceMapContents: serialized.map && JSON.stringify(serialized.map), - }, - ]; - let debugChannel = options.debuggerConfigArgs ? options.debuggerConfigArgs.debugChannel : undefined; - realm = construct_realm(realmOptions, debugChannel); - initializeGlobals(realm); - if (typeof options.additionalGlobals === "function") { - options.additionalGlobals(realm); - } - realm.generator = new Generator(realm, "main", realm.pathConditions); - let result = realm.$GlobalEnv.executePartialEvaluator(residualSources, options); - if (result instanceof AbruptCompletion) throw result; - return { ...result }; - } else { - invariant(options.residual); - realm.generator = new Generator(realm, "main", realm.pathConditions); - let result = realm.$GlobalEnv.executePartialEvaluator(sourceFileCollection.toArray(), options); - if (result instanceof AbruptCompletion) throw result; - return { ...result }; + return serialized; } } diff --git a/src/react/utils.js b/src/react/utils.js index bee4c9f1f..81c405bab 100644 --- a/src/react/utils.js +++ b/src/react/utils.js @@ -10,7 +10,7 @@ /* @flow */ import { Realm } from "../realm.js"; -import { AbruptCompletion, PossiblyNormalCompletion, SimpleNormalCompletion } from "../completions.js"; +import { AbruptCompletion, SimpleNormalCompletion } from "../completions.js"; import type { BabelNode, BabelNodeJSXIdentifier } from "@babel/types"; import { parseExpression } from "@babel/parser"; import { @@ -795,12 +795,14 @@ export function getValueFromFunctionCall( let completion; let createdObjects = realm.createdObjects; try { + let value; if (isConstructor) { invariant(newCall); - completion = newCall(args, func); + value = newCall(args, func); } else { - completion = funcCall(funcThis, args); + value = funcCall(funcThis, args); } + completion = new SimpleNormalCompletion(value); } catch (error) { if (error instanceof AbruptCompletion) { completion = error; @@ -810,18 +812,7 @@ export function getValueFromFunctionCall( } finally { invariant(createdObjects === realm.createdObjects, "realm.createdObjects was not correctly restored"); } - if (completion instanceof PossiblyNormalCompletion) { - // in this case one of the branches may complete abruptly, which means that - // not all control flow branches join into one flow at this point. - // Consequently we have to continue tracking changes until the point where - // all the branches come together into one. - completion = realm.composeWithSavedCompletion(completion); - } - // return or throw completion - if (completion instanceof AbruptCompletion) throw completion; - if (completion instanceof SimpleNormalCompletion) completion = completion.value; - invariant(completion instanceof Value); - return completion; + return realm.returnOrThrowCompletion(completion); } function isEventProp(name: string): boolean { diff --git a/src/realm.js b/src/realm.js index 46f376b81..7d11fab62 100644 --- a/src/realm.js +++ b/src/realm.js @@ -44,7 +44,7 @@ import { UndefinedValue, Value, } from "./values/index.js"; -import type { TypesDomain, ValuesDomain } from "./domains/index.js"; +import { TypesDomain, ValuesDomain } from "./domains/index.js"; import { LexicalEnvironment, Reference, @@ -57,8 +57,9 @@ import { cloneDescriptor, Construct } from "./methods/index.js"; import { AbruptCompletion, Completion, - ForkedAbruptCompletion, - PossiblyNormalCompletion, + JoinedAbruptCompletions, + JoinedNormalAndAbruptCompletions, + NormalCompletion, SimpleNormalCompletion, ThrowCompletion, } from "./completions.js"; @@ -67,16 +68,10 @@ import invariant from "./invariant.js"; import seedrandom from "seedrandom"; import { createOperationDescriptor, Generator, type TemporalOperationEntry } from "./utils/generator.js"; import { PreludeGenerator } from "./utils/PreludeGenerator.js"; -import { Environment, Functions, Join, Properties, To, Widen, Path } from "./singletons.js"; +import { Environment, Functions, Join, Path, Properties, To, Utils, Widen } from "./singletons.js"; import type { ReactSymbolTypes } from "./react/utils.js"; -import type { BabelNode, BabelNodeSourceLocation, BabelNodeLVal, BabelNodeStatement } from "@babel/types"; -import { Utils } from "./singletons.js"; -export type BindingEntry = { - hasLeaked: void | boolean, - value: void | Value, - previousHasLeaked: void | boolean, - previousValue: void | Value, -}; +import type { BabelNode, BabelNodeSourceLocation, BabelNodeLVal } from "@babel/types"; +export type BindingEntry = { hasLeaked: boolean, value: void | Value }; export type Bindings = Map; export type EvaluationResult = Completion | Reference; export type PropertyBindings = Map; @@ -95,7 +90,7 @@ export class Effects { propertyBindings: PropertyBindings, createdObjects: CreatedObjects ) { - this._result = result; + this.result = result; this.generator = generator; this.modifiedBindings = bindings; this.modifiedProperties = propertyBindings; @@ -103,20 +98,9 @@ export class Effects { this.canBeApplied = true; this._id = effects_uid++; - invariant(result.effects === undefined); - result.effects = this; - } - - _result: Completion; - get result(): Completion { - return this._result; - } - set result(completion: Completion): void { - invariant(completion.effects === undefined); - if (completion.effects === undefined) completion.effects = this; //todo: require callers to ensure this - this._result = completion; } + result: Completion; generator: Generator; modifiedBindings: Bindings; modifiedProperties: PropertyBindings; @@ -219,11 +203,8 @@ export class ExecutionContext { export function construct_empty_effects( realm: Realm, - c: Completion = new SimpleNormalCompletion(realm.intrinsics.empty, undefined) + c: Completion = new SimpleNormalCompletion(realm.intrinsics.empty) ): Effects { - // TODO #2222: Check if `realm.pathConditions` is always correct here. - // The path conditions here should probably be empty. - // Picking up the current path conditions from the Realm might be the reason why composition does not work. return new Effects( c, new Generator(realm, "construct_empty_effects", realm.pathConditions), @@ -281,7 +262,6 @@ export class Realm { this.intrinsics = ({}: any); this.$GlobalObject = (({}: any): ObjectValue); this.evaluators = (Object.create(null): any); - this.partialEvaluators = (Object.create(null): any); this.$GlobalEnv = ((undefined: any): LexicalEnvironment); this.derivedIds = new Map(); @@ -369,7 +349,7 @@ export class Realm { (sideEffectType: SideEffectType, binding: void | Binding | PropertyBinding, expressionLocation: any) => void >; reportPropertyAccess: void | (PropertyBinding => void); - savedCompletion: void | PossiblyNormalCompletion; + savedCompletion: void | JoinedNormalAndAbruptCompletions; activeLexicalEnvironments: Set; @@ -447,15 +427,6 @@ export class Realm { metadata?: any ) => Value | Reference, }; - partialEvaluators: { - [key: string]: ( - ast: BabelNode, - strictCode: boolean, - env: LexicalEnvironment, - realm: Realm, - metadata?: any - ) => [Completion | Reference | Value, BabelNode, Array], - }; simplifyAndRefineAbstractValue: AbstractValue => Value; simplifyAndRefineAbstractCondition: AbstractValue => Value; @@ -560,26 +531,6 @@ export class Realm { } } - clearBlockBindingsFromCompletion(completion: Completion, environmentRecord: DeclarativeEnvironmentRecord): void { - if (completion instanceof PossiblyNormalCompletion) { - this.clearBlockBindings(completion.alternateEffects.modifiedBindings, environmentRecord); - this.clearBlockBindings(completion.consequentEffects.modifiedBindings, environmentRecord); - if (completion.savedEffects !== undefined) - this.clearBlockBindings(completion.savedEffects.modifiedBindings, environmentRecord); - if (completion.alternate instanceof Completion) - this.clearBlockBindingsFromCompletion(completion.alternate, environmentRecord); - if (completion.consequent instanceof Completion) - this.clearBlockBindingsFromCompletion(completion.consequent, environmentRecord); - } else if (completion instanceof ForkedAbruptCompletion) { - this.clearBlockBindings(completion.alternateEffects.modifiedBindings, environmentRecord); - this.clearBlockBindings(completion.consequentEffects.modifiedBindings, environmentRecord); - if (completion.alternate instanceof Completion) - this.clearBlockBindingsFromCompletion(completion.alternate, environmentRecord); - if (completion.consequent instanceof Completion) - this.clearBlockBindingsFromCompletion(completion.consequent, environmentRecord); - } - } - // Call when a scope falls out of scope and should be destroyed. // Clears the Bindings corresponding to the disappearing Scope from ModifiedBindings onDestroyScope(lexicalEnvironment: LexicalEnvironment): void { @@ -590,8 +541,6 @@ export class Realm { let environmentRecord = lexicalEnvironment.environmentRecord; if (environmentRecord instanceof DeclarativeEnvironmentRecord) { this.clearBlockBindings(modifiedBindings, environmentRecord); - if (this.savedCompletion !== undefined) - this.clearBlockBindingsFromCompletion(this.savedCompletion, environmentRecord); } } @@ -633,31 +582,10 @@ export class Realm { } } - clearFunctionBindingsFromCompletion(completion: Completion, funcVal: FunctionValue): void { - if (completion instanceof PossiblyNormalCompletion) { - this.clearFunctionBindings(completion.alternateEffects.modifiedBindings, funcVal); - this.clearFunctionBindings(completion.consequentEffects.modifiedBindings, funcVal); - if (completion.savedEffects !== undefined) - this.clearFunctionBindings(completion.savedEffects.modifiedBindings, funcVal); - if (completion.alternate instanceof Completion) - this.clearFunctionBindingsFromCompletion(completion.alternate, funcVal); - if (completion.consequent instanceof Completion) - this.clearFunctionBindingsFromCompletion(completion.consequent, funcVal); - } else if (completion instanceof ForkedAbruptCompletion) { - this.clearFunctionBindings(completion.alternateEffects.modifiedBindings, funcVal); - this.clearFunctionBindings(completion.consequentEffects.modifiedBindings, funcVal); - if (completion.alternate instanceof Completion) - this.clearFunctionBindingsFromCompletion(completion.alternate, funcVal); - if (completion.consequent instanceof Completion) - this.clearFunctionBindingsFromCompletion(completion.consequent, funcVal); - } - } - popContext(context: ExecutionContext): void { let funcVal = context.function; if (funcVal) { this.clearFunctionBindings(this.modifiedBindings, funcVal); - if (this.savedCompletion !== undefined) this.clearFunctionBindingsFromCompletion(this.savedCompletion, funcVal); } let c = this.contextStack.pop(); invariant(c === context); @@ -733,7 +661,11 @@ export class Realm { bubbleSideEffectReports: boolean, reportSideEffectFunc: | null - | ((sideEffectType: SideEffectType, binding: void | Binding | PropertyBinding, value: void | Value) => void) + | (( + sideEffectType: SideEffectType, + binding: void | Binding | PropertyBinding, + location: ?BabelNodeSourceLocation + ) => void) ): T { let saved_createdObjectsTrackedForLeaks = this.createdObjectsTrackedForLeaks; let saved_reportSideEffectCallbacks; @@ -787,7 +719,7 @@ export class Realm { invariant(this.isInPureScope(), "only abstract abrupt completion in pure functions"); // TODO(1264): We should create a new generator for this scope and wrap it in a try/catch. - // We could use the outcome of that as the join condition for a PossiblyNormalCompletion. + // We could use the outcome of that as the join condition for a JoinedNormalAndAbruptCompletions. // We should then compose that with the saved completion and move on to the normal route. // Currently we just issue a recoverable error instead if this might matter. let value = f(); @@ -834,7 +766,7 @@ export class Realm { result = func(effects); return this.intrinsics.undefined; } finally { - this.undoBindings(effects.modifiedBindings); + this.restoreBindings(effects.modifiedBindings); this.restoreProperties(effects.modifiedProperties); invariant(!effects.canBeApplied); effects.canBeApplied = true; @@ -848,22 +780,6 @@ export class Realm { return this.wrapInGlobalEnv(() => this.evaluateNodeForEffects(node, false, this.$GlobalEnv, state, generatorName)); } - partiallyEvaluateNodeForEffects( - ast: BabelNode, - strictCode: boolean, - env: LexicalEnvironment - ): [Effects, BabelNode, Array] { - let nodeAst, nodeIO; - function partialEval() { - let result; - [result, nodeAst, nodeIO] = env.partiallyEvaluateCompletionDeref(ast, strictCode); - return result; - } - let effects = this.evaluateForEffects(partialEval, undefined, "partiallyEvaluateNodeForEffects"); - invariant(nodeAst !== undefined && nodeIO !== undefined); - return [effects, nodeAst, nodeIO]; - } - // Use this to evaluate code for internal purposes, so that the tracked state does not get polluted evaluateWithoutEffects(f: () => T): T { // Save old state and set up undefined state @@ -912,21 +828,9 @@ export class Realm { if (e instanceof AbruptCompletion) c = e; else throw e; } - // This is a join point for the normal branch of a PossiblyNormalCompletion. - if (c instanceof Value || c instanceof AbruptCompletion) { - c = Functions.incorporateSavedCompletion(this, c); - if (c instanceof Completion && c.effects !== undefined) c = c.shallowCloneWithoutEffects(); - } + // This is a join point for any normal completions inside realm.savedCompletion + c = Functions.incorporateSavedCompletion(this, c); invariant(c !== undefined); - if (c instanceof PossiblyNormalCompletion) { - // The current state may have advanced since the time control forked into the various paths recorded in c. - // Update the normal path and restore the global state to what it was at the time of the fork. - let subsequentEffects = this.getCapturedEffects(c.value); - this.stopEffectCaptureAndUndoEffects(c); - Join.updatePossiblyNormalCompletionWithSubsequentEffects(this, c, subsequentEffects); - this.savedCompletion = undefined; - this.applyEffects(subsequentEffects, "subsequentEffects", true); - } invariant(this.generator !== undefined); invariant(this.modifiedBindings !== undefined); @@ -952,12 +856,14 @@ export class Realm { return result; } finally { // Roll back the state changes - if (this.savedCompletion !== undefined) this.stopEffectCaptureAndUndoEffects(this.savedCompletion); if (result !== undefined) { - this.undoBindings(result.modifiedBindings); + this.restoreBindings(result.modifiedBindings); this.restoreProperties(result.modifiedProperties); } else { - this.undoBindings(this.modifiedBindings); + if (this.savedCompletion !== undefined) { + this.stopEffectCaptureAndUndoEffects(this.savedCompletion); + } + this.restoreBindings(this.modifiedBindings); this.restoreProperties(this.modifiedProperties); } this.generator = saved_generator; @@ -1015,14 +921,6 @@ export class Realm { this.applyEffects(effects); let resultVal = effects.result; if (resultVal instanceof AbruptCompletion) throw resultVal; - if (resultVal instanceof PossiblyNormalCompletion) { - // in this case one of the branches may complete abruptly, which means that - // not all control flow branches join into one flow at this point. - // Consequently we have to continue tracking changes until the point where - // all the branches come together into one. - resultVal = this.composeWithSavedCompletion(resultVal); - } - invariant(resultVal instanceof SimpleNormalCompletion); return resultVal.value; } catch (e) { if (diagnostic !== undefined) return diagnostic; @@ -1044,10 +942,10 @@ export class Realm { }; let effects1 = this.evaluateForEffects(f, undefined, "evaluateForFixpointEffects/1"); while (true) { - this.redoBindings(effects1.modifiedBindings); + this.restoreBindings(effects1.modifiedBindings); this.restoreProperties(effects1.modifiedProperties); let effects2 = this.evaluateForEffects(f, undefined, "evaluateForFixpointEffects/2"); - this.undoBindings(effects1.modifiedBindings); + this.restoreBindings(effects1.modifiedBindings); this.restoreProperties(effects1.modifiedProperties); if (Widen.containsEffects(effects1, effects2)) { // effects1 includes every value present in effects2, so doing another iteration using effects2 will not @@ -1087,7 +985,6 @@ export class Realm { } catch (e) { if (!(e instanceof InfeasiblePathError)) throw e; } - invariant(effects1 === undefined || effects1.result.effects === effects1); let effects2; try { @@ -1095,41 +992,20 @@ export class Realm { } catch (e) { if (!(e instanceof InfeasiblePathError)) throw e; } - invariant(effects2 === undefined || effects2.result.effects === effects2); - let joinedEffects, completion; + let effects; if (effects1 === undefined || effects2 === undefined) { if (effects1 === undefined && effects2 === undefined) throw new InfeasiblePathError(); - joinedEffects = effects1 || effects2; - invariant(joinedEffects !== undefined); - completion = joinedEffects.result; - this.applyEffects(joinedEffects, "evaluateWithAbstractConditional"); + effects = effects1 || effects2; + invariant(effects !== undefined); } else { // Join the effects, creating an abstract view of what happened, regardless // of the actual value of condValue. - joinedEffects = Join.joinForkOrChoose(this, condValue, effects1, effects2); - completion = joinedEffects.result; - if (completion instanceof ForkedAbruptCompletion) { - // Note that the effects are tracked separately inside completion and will be applied later. - throw completion; - } - if (completion instanceof PossiblyNormalCompletion) { - // in this case one of the branches may complete abruptly, which means that - // not all control flow branches join into one flow at this point. - // Consequently we have to continue tracking changes until the point where - // all the branches come together into one. - this.applyEffects(joinedEffects, "evaluateWithAbstractConditional"); - completion = this.composeWithSavedCompletion(completion); - } else { - this.applyEffects(joinedEffects, "evaluateWithAbstractConditional"); - } + effects = Join.joinEffects(condValue, effects1, effects2); } + this.applyEffects(effects); - // return or throw completion - if (completion instanceof AbruptCompletion) throw completion; - if (completion instanceof SimpleNormalCompletion) completion = completion.value; - invariant(completion instanceof Value); - return completion; + return condValue.$Realm.returnOrThrowCompletion(effects.result); } _applyPropertiesToNewlyCreatedObjects( @@ -1225,187 +1101,126 @@ export class Realm { }); } - composeEffects(priorEffects: Effects, subsequentEffects: Effects): Effects { - let result = construct_empty_effects(this, subsequentEffects.result.shallowCloneWithoutEffects()); - - result.generator = Join.composeGenerators( - this, - priorEffects.generator || result.generator, - subsequentEffects.generator - ); - - if (priorEffects.modifiedBindings) { - priorEffects.modifiedBindings.forEach((val, key, m) => result.modifiedBindings.set(key, val)); + returnOrThrowCompletion(completion: Completion | Value): Value { + if (completion instanceof Value) completion = new SimpleNormalCompletion(completion); + if (completion instanceof AbruptCompletion) { + let c = Functions.incorporateSavedCompletion(this, completion); + invariant(c instanceof Completion); + completion = c; } - subsequentEffects.modifiedBindings.forEach((val, key, m) => result.modifiedBindings.set(key, val)); - - if (priorEffects.modifiedProperties) { - priorEffects.modifiedProperties.forEach((desc, propertyBinding, m) => - result.modifiedProperties.set(propertyBinding, desc) - ); - } - subsequentEffects.modifiedProperties.forEach((val, key, m) => result.modifiedProperties.set(key, val)); - - if (priorEffects.createdObjects) { - priorEffects.createdObjects.forEach((ob, a) => result.createdObjects.add(ob)); - } - subsequentEffects.createdObjects.forEach((ob, a) => result.createdObjects.add(ob)); - - return result; + let cc = this.composeWithSavedCompletion(completion); + if (cc instanceof AbruptCompletion) throw cc; + return cc.value; } - updateAbruptCompletions(priorEffects: Effects, c: PossiblyNormalCompletion): void { - if (c.consequent instanceof AbruptCompletion) { - c.consequent.effects = this.composeEffects(priorEffects, c.consequentEffects); - let alternate = c.alternate; - if (alternate instanceof PossiblyNormalCompletion) this.updateAbruptCompletions(priorEffects, alternate); - } else { - invariant(c.alternate instanceof AbruptCompletion); - c.alternate.effects = this.composeEffects(priorEffects, c.alternateEffects); - let consequent = c.consequent; - if (consequent instanceof PossiblyNormalCompletion) this.updateAbruptCompletions(priorEffects, consequent); - } - } - - wrapSavedCompletion(completion: PossiblyNormalCompletion): void { - if (this.savedCompletion !== undefined) { - if (completion.consequent instanceof AbruptCompletion) { - completion.alternate = this.savedCompletion; - } else { - completion.consequent = this.savedCompletion; - } - completion.savedEffects = this.savedCompletion.savedEffects; - } else { - this.captureEffects(completion); - } - this.savedCompletion = completion; - } - - composeWithSavedCompletion(completion: PossiblyNormalCompletion): Value { + composeWithSavedCompletion(completion: Completion): Completion { if (this.savedCompletion === undefined) { - this.savedCompletion = completion; - this.savedCompletion.savedPathConditions = this.pathConditions; - this.pathConditions = [].concat(this.pathConditions); - this.captureEffects(completion); + if (completion instanceof JoinedNormalAndAbruptCompletions) { + this.savedCompletion = completion; + this.pushPathConditionsLeadingToCompletionOfType(NormalCompletion, completion); + this.captureEffects(completion); + } + return completion; } else { - let savedCompletion = this.savedCompletion; - let e = this.getCapturedEffects(); - this.stopEffectCaptureAndUndoEffects(savedCompletion); - savedCompletion = Join.composePossiblyNormalCompletions(this, savedCompletion, completion, e); - this.applyEffects(e); - this.captureEffects(savedCompletion); - this.savedCompletion = savedCompletion; + let cc = Join.composeCompletions(this.savedCompletion, completion); + if (cc instanceof JoinedNormalAndAbruptCompletions) { + this.savedCompletion = cc; + this.pushPathConditionsLeadingToCompletionOfType(NormalCompletion, completion); + if (cc.savedEffects === undefined) this.captureEffects(cc); + } else { + this.savedCompletion = undefined; + } + return cc; } - let realm = this; - pushPathConditionsLeadingToNormalCompletion(completion); - return completion.value; + } - function pushPathConditionsLeadingToNormalCompletion(c: ForkedAbruptCompletion | PossiblyNormalCompletion) { - if (allPathsAreAbrupt(c.consequent)) { - Path.pushInverseAndRefine(c.joinCondition); - if (c.alternate instanceof PossiblyNormalCompletion || c.alternate instanceof ForkedAbruptCompletion) - pushPathConditionsLeadingToNormalCompletion(c.alternate); - } else if (allPathsAreAbrupt(c.alternate)) { - Path.pushAndRefine(c.joinCondition); - if (c.consequent instanceof PossiblyNormalCompletion || c.consequent instanceof ForkedAbruptCompletion) - pushPathConditionsLeadingToNormalCompletion(c.consequent); - } else if (allPathsAreNormal(c.consequent)) { - if (!allPathsAreNormal(c.alternate)) { - let alternatePC = getNormalPathConditionFor(c.alternate); - let disjunct = AbstractValue.createFromLogicalOp(realm, "||", c.joinCondition, alternatePC); + pushPathConditionsLeadingToCompletionOfType(CompletionType: typeof Completion, completion: Completion): void { + let realm = this; + let bottomValue = realm.intrinsics.__bottomValue; + // Note that if a completion of type CompletionType has a value is that is bottom, that completion is unreachable + // and pushing its corresponding path condition would cause an InfeasiblePathError to be thrown. + if (completion instanceof JoinedAbruptCompletions || completion instanceof JoinedNormalAndAbruptCompletions) { + if (completion.consequent.value === bottomValue || allPathsAreDifferent(completion.consequent)) { + if (completion.alternate.value === bottomValue || allPathsAreDifferent(completion.alternate)) return; + Path.pushInverseAndRefine(completion.joinCondition); + this.pushPathConditionsLeadingToCompletionOfType(CompletionType, completion.alternate); + } else if (completion.alternate.value === bottomValue || allPathsAreDifferent(completion.alternate)) { + if (completion.consequent.value === bottomValue) return; + Path.pushAndRefine(completion.joinCondition); + this.pushPathConditionsLeadingToCompletionOfType(CompletionType, completion.consequent); + } else if (allPathsAreTheSame(completion.consequent)) { + if (!allPathsAreTheSame(completion.alternate)) { + let alternatePC = getPathConditionForSame(completion.alternate); + let disjunct = AbstractValue.createFromLogicalOp(realm, "||", completion.joinCondition, alternatePC); Path.pushAndRefine(disjunct); } - } else if (allPathsAreNormal(c.alternate)) { - let consequentPC = getNormalPathConditionFor(c.consequent); - let inverse = AbstractValue.createFromUnaryOp(realm, "!", c.joinCondition); + } else if (allPathsAreTheSame(completion.alternate)) { + let consequentPC = getPathConditionForSame(completion.consequent); + let inverse = AbstractValue.createFromUnaryOp(realm, "!", completion.joinCondition); let disjunct = AbstractValue.createFromLogicalOp(realm, "||", inverse, consequentPC); Path.pushAndRefine(disjunct); } else { - let jc = c.joinCondition; - let consequentPC = AbstractValue.createFromLogicalOp(realm, "&&", jc, getNormalPathConditionFor(c.consequent)); + let jc = completion.joinCondition; + let cpc = AbstractValue.createFromLogicalOp(realm, "&&", jc, getPathConditionForSame(completion.consequent)); let ijc = AbstractValue.createFromUnaryOp(realm, "!", jc); - let alternatePC = AbstractValue.createFromLogicalOp(realm, "&&", ijc, getNormalPathConditionFor(c.alternate)); - let disjunct = AbstractValue.createFromLogicalOp(realm, "||", consequentPC, alternatePC); + let apc = AbstractValue.createFromLogicalOp(realm, "&&", ijc, getPathConditionForSame(completion.alternate)); + let disjunct = AbstractValue.createFromLogicalOp(realm, "||", cpc, apc); Path.pushAndRefine(disjunct); } } + return; - function allPathsAreAbrupt(c: Completion): boolean { - if (c instanceof ForkedAbruptCompletion) return allPathsAreAbrupt(c.consequent) && allPathsAreAbrupt(c.alternate); - if (c instanceof AbruptCompletion) return true; - return false; - } - - function allPathsAreNormal(c: Completion): boolean { - if (c instanceof PossiblyNormalCompletion || c instanceof ForkedAbruptCompletion) - return allPathsAreNormal(c.consequent) && allPathsAreNormal(c.alternate); - if (c instanceof AbruptCompletion) return false; + function allPathsAreDifferent(c: Completion): boolean { + if (c instanceof JoinedAbruptCompletions || c instanceof JoinedNormalAndAbruptCompletions) + return allPathsAreDifferent(c.consequent) && allPathsAreDifferent(c.alternate); + if (c instanceof CompletionType) return false; return true; } - function getNormalPathConditionFor(c: Completion): Value { - invariant(c instanceof PossiblyNormalCompletion || c instanceof ForkedAbruptCompletion); - if (allPathsAreAbrupt(c.consequent)) { - invariant(!allPathsAreAbrupt(c.alternate)); + function allPathsAreTheSame(c: Completion): boolean { + if (c instanceof JoinedAbruptCompletions || c instanceof JoinedNormalAndAbruptCompletions) + return allPathsAreTheSame(c.consequent) && allPathsAreTheSame(c.alternate); + if (c instanceof CompletionType) return true; + return false; + } + + function getPathConditionForSame(c: Completion): Value { + invariant(c instanceof JoinedAbruptCompletions || c instanceof JoinedNormalAndAbruptCompletions); + if (c.consequent.value === bottomValue || allPathsAreDifferent(c.consequent)) { + invariant(!allPathsAreDifferent(c.alternate)); let inverse = AbstractValue.createFromUnaryOp(realm, "!", c.joinCondition); - if (allPathsAreNormal(c.alternate)) return inverse; - return AbstractValue.createFromLogicalOp(realm, "&&", inverse, getNormalPathConditionFor(c.alternate)); - } else if (allPathsAreAbrupt(c.alternate)) { - invariant(!allPathsAreAbrupt(c.consequent)); - if (allPathsAreNormal(c.consequent)) return c.joinCondition; - return AbstractValue.createFromLogicalOp(realm, "&&", c.joinCondition, getNormalPathConditionFor(c.consequent)); - } else if (allPathsAreNormal(c.consequent)) { + if (allPathsAreTheSame(c.alternate)) return inverse; + return AbstractValue.createFromLogicalOp(realm, "&&", inverse, getPathConditionForSame(c.alternate)); + } else if (c.alternate.value === bottomValue || allPathsAreDifferent(c.alternate)) { + invariant(!allPathsAreDifferent(c.consequent)); + if (allPathsAreTheSame(c.consequent)) return c.joinCondition; + return AbstractValue.createFromLogicalOp(realm, "&&", c.joinCondition, getPathConditionForSame(c.consequent)); + } else if (allPathsAreTheSame(c.consequent)) { // In principle the simplifier shoud reduce the result of the else clause to this case. This does less work. - invariant(!allPathsAreNormal(c.alternate)); - invariant(!allPathsAreAbrupt(c.alternate)); + invariant(!allPathsAreTheSame(c.alternate)); + invariant(!allPathsAreDifferent(c.alternate)); let ijc = AbstractValue.createFromUnaryOp(realm, "!", c.joinCondition); - let alternatePC = AbstractValue.createFromLogicalOp(realm, "&&", ijc, getNormalPathConditionFor(c.alternate)); + let alternatePC = AbstractValue.createFromLogicalOp(realm, "&&", ijc, getPathConditionForSame(c.alternate)); return AbstractValue.createFromLogicalOp(realm, "||", c.joinCondition, alternatePC); - } else if (allPathsAreNormal(c.alternate)) { + } else if (allPathsAreTheSame(c.alternate)) { // In principle the simplifier shoud reduce the result of the else clause to this case. This does less work. - invariant(!allPathsAreNormal(c.consequent)); - invariant(!allPathsAreAbrupt(c.consequent)); + invariant(!allPathsAreTheSame(c.consequent)); + invariant(!allPathsAreDifferent(c.consequent)); let jc = c.joinCondition; - let consequentPC = AbstractValue.createFromLogicalOp(realm, "&&", jc, getNormalPathConditionFor(c.consequent)); + let consequentPC = AbstractValue.createFromLogicalOp(realm, "&&", jc, getPathConditionForSame(c.consequent)); let ijc = AbstractValue.createFromUnaryOp(realm, "!", jc); return AbstractValue.createFromLogicalOp(realm, "||", consequentPC, ijc); } else { let jc = c.joinCondition; - let consequentPC = AbstractValue.createFromLogicalOp(realm, "&&", jc, getNormalPathConditionFor(c.consequent)); + let consequentPC = AbstractValue.createFromLogicalOp(realm, "&&", jc, getPathConditionForSame(c.consequent)); let ijc = AbstractValue.createFromUnaryOp(realm, "!", jc); - let alternatePC = AbstractValue.createFromLogicalOp(realm, "&&", ijc, getNormalPathConditionFor(c.alternate)); + let alternatePC = AbstractValue.createFromLogicalOp(realm, "&&", ijc, getPathConditionForSame(c.alternate)); return AbstractValue.createFromLogicalOp(realm, "||", consequentPC, alternatePC); } } } - incorporatePriorSavedCompletion(priorCompletion: void | PossiblyNormalCompletion): void { - if (priorCompletion === undefined) return; - // A completion that has been saved and that is still active, will always have savedEffects. - invariant(priorCompletion.savedEffects !== undefined); - if (this.savedCompletion === undefined) { - // priorCompletion must be a previous savedCompletion, so the corresponding tracking maps would have been - // captured in priorCompletion.savedEffects and restored to the realm when clearing out this.savedCompletion. - // Since there is curently no savedCompletion, all the forks subsequent to the last normal fork in - // priorCompletion will have joined up again and their effects will have been applied to the current - // tracking maps. - invariant(this.modifiedBindings !== undefined); - this.savedCompletion = priorCompletion; - } else { - let savedEffects = this.savedCompletion.savedEffects; - invariant(savedEffects !== undefined); - this.redoBindings(savedEffects.modifiedBindings); - this.restoreProperties(savedEffects.modifiedProperties); - Join.updatePossiblyNormalCompletionWithSubsequentEffects(this, priorCompletion, savedEffects); - this.undoBindings(savedEffects.modifiedBindings); - this.restoreProperties(savedEffects.modifiedProperties); - invariant(this.savedCompletion !== undefined); - this.savedCompletion.savedEffects = undefined; - this.savedCompletion = Join.composePossiblyNormalCompletions(this, priorCompletion, this.savedCompletion); - } - } - - captureEffects(completion: PossiblyNormalCompletion): void { + captureEffects(completion: JoinedNormalAndAbruptCompletions): void { invariant(completion.savedEffects === undefined); completion.savedEffects = new Effects( new SimpleNormalCompletion(this.intrinsics.undefined), @@ -1420,13 +1235,13 @@ export class Realm { this.createdObjects = new Set(); } - getCapturedEffects(v?: Value = this.intrinsics.undefined): Effects { + getCapturedEffects(v?: Completion | Value = this.intrinsics.undefined): Effects { invariant(this.generator !== undefined); invariant(this.modifiedBindings !== undefined); invariant(this.modifiedProperties !== undefined); invariant(this.createdObjects !== undefined); return new Effects( - new SimpleNormalCompletion(v), + v instanceof Completion ? v : new SimpleNormalCompletion(v), this.generator, this.modifiedBindings, this.modifiedProperties, @@ -1434,9 +1249,9 @@ export class Realm { ); } - stopEffectCaptureAndUndoEffects(completion: PossiblyNormalCompletion): void { + stopEffectCaptureAndUndoEffects(completion: JoinedNormalAndAbruptCompletions): void { // Roll back the state changes - this.undoBindings(this.modifiedBindings); + this.restoreBindings(this.modifiedBindings); this.restoreProperties(this.modifiedProperties); // Restore saved state @@ -1465,7 +1280,7 @@ export class Realm { if (appendGenerator) this.appendGenerator(generator, leadingComment); // Restore modifiedBindings - this.redoBindings(modifiedBindings); + this.restoreBindings(modifiedBindings); this.restoreProperties(modifiedProperties); // track modifiedBindings @@ -1567,10 +1382,8 @@ export class Realm { if (this.modifiedBindings !== undefined && !this.modifiedBindings.has(binding)) { this.modifiedBindings.set(binding, { - hasLeaked: undefined, - value: undefined, - previousHasLeaked: binding.hasLeaked, - previousValue: binding.value, + hasLeaked: binding.hasLeaked, + value: binding.value, }); } return binding; @@ -1648,21 +1461,17 @@ export class Realm { return result; } - redoBindings(modifiedBindings: void | Bindings): void { + // Restores each Binding in the given map to the value it + // had when it was entered into the map and updates the map to record + // the value the Binding had just before the call to this method. + restoreBindings(modifiedBindings: void | Bindings) { if (modifiedBindings === undefined) return; modifiedBindings.forEach(({ hasLeaked, value }, binding, m) => { - binding.hasLeaked = hasLeaked || false; + let l = binding.hasLeaked; + let v = binding.value; + binding.hasLeaked = hasLeaked; binding.value = value; - }); - } - - undoBindings(modifiedBindings: void | Bindings): void { - if (modifiedBindings === undefined) return; - modifiedBindings.forEach((entry, binding, m) => { - if (entry.hasLeaked === undefined) entry.hasLeaked = binding.hasLeaked; - if (entry.value === undefined) entry.value = binding.value; - binding.hasLeaked = entry.previousHasLeaked || false; - binding.value = entry.previousValue; + m.set(binding, { hasLeaked: l, value: v }); }); } @@ -1755,7 +1564,7 @@ export class Realm { if (typeof message === "string") message = new StringValue(this, message); invariant(message instanceof StringValue); this.nextContextLocation = this.currentLocation; - return new ThrowCompletion(Construct(this, type, [message]), undefined, this.currentLocation); + return new ThrowCompletion(Construct(this, type, [message]), this.currentLocation); } appendGenerator(generator: Generator, leadingComment: string = ""): void { diff --git a/src/serializer/functions.js b/src/serializer/functions.js index 7855167b5..71100d6a8 100644 --- a/src/serializer/functions.js +++ b/src/serializer/functions.js @@ -10,7 +10,7 @@ /* @flow */ import type { BabelNodeSourceLocation } from "@babel/types"; -import { Completion, PossiblyNormalCompletion } from "../completions.js"; +import { AbruptCompletion } from "../completions.js"; import { CompilerDiagnostic, FatalError } from "../errors.js"; import invariant from "../invariant.js"; import { type Effects, type PropertyBindings, Realm } from "../realm.js"; @@ -228,8 +228,8 @@ export class Functions { let call = Utils.createModelledFunctionCall(this.realm, functionValue, argModel); let realm = this.realm; - let logCompilerDiagnostic = (msg: string) => { - let error = new CompilerDiagnostic(msg, undefined, "PP1007", "Warning"); + let logCompilerDiagnostic = (msg: string, location: ?BabelNodeSourceLocation) => { + let error = new CompilerDiagnostic(msg, location, "PP1007", "Warning"); realm.handleError(error); }; let effects: Effects = realm.evaluatePure( @@ -293,7 +293,7 @@ export class Functions { invariant(additionalFunctionEffects !== undefined); let e1 = additionalFunctionEffects.effects; invariant(e1 !== undefined); - if (e1.result instanceof Completion && !e1.result instanceof PossiblyNormalCompletion) { + if (e1.result instanceof AbruptCompletion) { let error = new CompilerDiagnostic( `Additional function ${fun1Name} will terminate abruptly`, e1.result.location, diff --git a/src/serializer/serializer.js b/src/serializer/serializer.js index 9c44dea97..d32f98a40 100644 --- a/src/serializer/serializer.js +++ b/src/serializer/serializer.js @@ -51,7 +51,6 @@ export class Serializer { this.realm, this.logger, !!serializerOptions.logModules, - !!serializerOptions.delayUnsupportedRequires, !!serializerOptions.accelerateUnsupportedRequires ); this.functions = new Functions(this.realm, this.modules.moduleTracer); diff --git a/src/serializer/utils.js b/src/serializer/utils.js index d16ea5239..39164c1d7 100644 --- a/src/serializer/utils.js +++ b/src/serializer/utils.js @@ -28,6 +28,7 @@ import { Logger } from "../utils/logger.js"; import { Generator } from "../utils/generator.js"; import type { AdditionalFunctionEffects } from "./types"; import type { Binding } from "../environment.js"; +import type { BabelNodeSourceLocation } from "@babel/types"; import { optionalStringOfLocation } from "../utils/babelhelpers.js"; /** @@ -203,10 +204,10 @@ export function createAdditionalEffects( } export function handleReportedSideEffect( - exceptionHandler: string => void, + exceptionHandler: (string, ?BabelNodeSourceLocation) => void, sideEffectType: SideEffectType, binding: void | Binding | PropertyBinding, - expressionLocation: any + expressionLocation: ?BabelNodeSourceLocation ): void { // This causes an infinite recursion because creating a callstack causes internal-only side effects if (binding && binding.object && binding.object.intrinsicName === "__checkedBindings") return; @@ -214,7 +215,7 @@ export function handleReportedSideEffect( if (sideEffectType === "MODIFIED_BINDING") { let name = binding ? `"${((binding: any): Binding).name}"` : "unknown"; - exceptionHandler(`side-effects from mutating the binding ${name}${location}`); + exceptionHandler(`side-effects from mutating the binding ${name}${location}`, expressionLocation); } else if (sideEffectType === "MODIFIED_PROPERTY" || sideEffectType === "MODIFIED_GLOBAL") { let name = ""; let pb = ((binding: any): PropertyBinding); @@ -224,11 +225,11 @@ export function handleReportedSideEffect( } if (sideEffectType === "MODIFIED_PROPERTY") { if (!ObjectValue.refuseSerializationOnPropertyBinding(pb)) - exceptionHandler(`side-effects from mutating a property ${name}${location}`); + exceptionHandler(`side-effects from mutating a property ${name}${location}`, expressionLocation); } else { - exceptionHandler(`side-effects from mutating the global object property ${name}${location}`); + exceptionHandler(`side-effects from mutating the global object property ${name}${location}`, expressionLocation); } } else if (sideEffectType === "EXCEPTION_THROWN") { - exceptionHandler(`side-effects from throwing exception${location}`); + exceptionHandler(`side-effects from throwing exception${location}`, expressionLocation); } } diff --git a/src/types.js b/src/types.js index accdbecc3..edbc10350 100644 --- a/src/types.js +++ b/src/types.js @@ -28,15 +28,8 @@ import type { UndefinedValue, } from "./values/index.js"; import { Value } from "./values/index.js"; -import { - AbruptCompletion, - Completion, - ForkedAbruptCompletion, - PossiblyNormalCompletion, - NormalCompletion, -} from "./completions.js"; +import { Completion } from "./completions.js"; import { EnvironmentRecord, LexicalEnvironment, Reference } from "./environment.js"; -import { Generator } from "./utils/generator.js"; import { ObjectValue } from "./values/index.js"; import type { BabelNode, @@ -48,7 +41,7 @@ import type { BabelNodeVariableDeclaration, BabelNodeSourceLocation, } from "@babel/types"; -import type { Bindings, Effects, EvaluationResult, PropertyBindings, CreatedObjects, Realm } from "./realm.js"; +import type { Effects, Realm } from "./realm.js"; import { CompilerDiagnostic } from "./errors.js"; import type { Severity } from "./errors.js"; import type { DebugChannel } from "./debugger/server/channel/DebugChannel.js"; @@ -307,6 +300,8 @@ export type Intrinsics = { __IntrospectionError: NativeFunctionValue, __IntrospectionErrorPrototype: ObjectValue, + __topValue: AbstractValue, + __bottomValue: AbstractValue, }; export type PromiseCapability = { @@ -551,13 +546,9 @@ export type FunctionType = { // ECMA262 18.2.1.1 PerformEval(realm: Realm, x: Value, evalRealm: Realm, strictCaller: boolean, direct: boolean): Value, - // If c is an abrupt completion and realm.savedCompletion is defined, the result is an instance of - // ForkedAbruptCompletion and the effects that have been captured since the PossiblyNormalCompletion instance - // in realm.savedCompletion has been created, becomes the effects of the branch that terminates in c. - // If c is a normal completion, the result is realm.savedCompletion, with its value updated to c. - // If c is undefined, the result is just realm.savedCompletion. + // Composes realm.savedCompletion with c, clears realm.savedCompletion and return the composition. // Call this only when a join point has been reached. - incorporateSavedCompletion(realm: Realm, c: void | AbruptCompletion | Value): void | Completion | Value, + incorporateSavedCompletion(realm: Realm, c: void | Completion | Value): void | Completion | Value, EvaluateStatements( body: Array, @@ -567,14 +558,6 @@ export type FunctionType = { realm: Realm ): Value, - PartiallyEvaluateStatements( - body: Array, - blockValue: void | NormalCompletion | Value, - strictCode: boolean, - blockEnv: LexicalEnvironment, - realm: Realm - ): [Completion | Value, Array], - // ECMA262 9.2.5 FunctionCreate( realm: Realm, @@ -732,117 +715,15 @@ export type EnvironmentType = { }; export type JoinType = { - stopEffectCaptureJoinApplyAndReturnCompletion( - c1: PossiblyNormalCompletion, - c2: AbruptCompletion, - realm: Realm - ): ForkedAbruptCompletion, + composeCompletions(leftCompletion: void | Completion | Value, rightCompletion: Completion | Value): Completion, - unbundleNormalCompletion( - completionOrValue: Completion | Value | Reference - ): [void | NormalCompletion, Value | Reference], + composeWithEffects(completion: Completion, effects: Effects): Effects, - composeNormalCompletions( - leftCompletion: void | NormalCompletion, - rightCompletion: void | NormalCompletion, - resultValue: Value, - realm: Realm - ): PossiblyNormalCompletion | Value, + joinCompletions(joinCondition: Value, c1: Completion, c2: Completion): Completion, - composePossiblyNormalCompletions( - realm: Realm, - pnc: PossiblyNormalCompletion, - c: PossiblyNormalCompletion, - priorEffects?: Effects - ): PossiblyNormalCompletion, + joinEffects(joinCondition: Value, e1: Effects, e2: Effects): Effects, - updatePossiblyNormalCompletionWithSubsequentEffects( - realm: Realm, - pnc: PossiblyNormalCompletion, - subsequentEffects: Effects - ): void, - - updatePossiblyNormalCompletionWithValue(realm: Realm, pnc: PossiblyNormalCompletion, v: Value): void, - - replacePossiblyNormalCompletionWithForkedAbruptCompletion( - realm: Realm, - // a forked path with a non abrupt (normal) component - pnc: PossiblyNormalCompletion, - // an abrupt completion that completes the normal path - ac: AbruptCompletion, - // effects collected after pnc was constructed - e: Effects - ): ForkedAbruptCompletion, - - extractAndJoinCompletionsOfType(CompletionType: typeof AbruptCompletion, realm: Realm, c: AbruptCompletion): Effects, - - joinForkOrChoose(realm: Realm, joinCondition: Value, e1: Effects, e2: Effects): Effects, - - joinNestedEffects(realm: Realm, c: Completion, precedingEffects?: Effects): Effects, - - collapseResults( - realm: Realm, - joinCondition: AbstractValue, - precedingEffects: Effects, - result1: EvaluationResult, - result2: EvaluationResult - ): Completion, - - joinOrForkResults( - realm: Realm, - joinCondition: AbstractValue, - result1: EvaluationResult, - result2: EvaluationResult, - e1: Effects, - e2: Effects - ): Completion, - - composeGenerators(realm: Realm, generator1: Generator, generator2: Generator): Generator, - - // Creates a single map that joins together maps m1 and m2 using the given join - // operator. If an entry is present in one map but not the other, the missing - // entry is treated as if it were there and its value were undefined. - joinMaps(m1: Map, m2: Map, join: (K, void | V, void | V) => V): Map, - - // Creates a single map that has an key, value pair for the union of the key - // sets of m1 and m2. The value of a pair is the join of m1[key] and m2[key] - // where the join is defined to be just m1[key] if m1[key] === m2[key] and - // and abstract value with expression "joinCondition ? m1[key] : m2[key]" if not. - joinBindings( - realm: Realm, - joinCondition: AbstractValue, - g1: Generator, - m1: Bindings, - g2: Generator, - m2: Bindings - ): [Generator, Generator, Bindings], - - // If v1 is known and defined and v1 === v2 return v1, - // otherwise return getAbstractValue(v1, v2) - joinValues( - realm: Realm, - v1: void | Value | Array | Array<{ $Key: void | Value, $Value: void | Value }>, - v2: void | Value | Array | Array<{ $Key: void | Value, $Value: void | Value }>, - getAbstractValue: (void | Value, void | Value) => Value - ): Value | Array | Array<{ $Key: void | Value, $Value: void | Value }>, - - joinPropertyBindings( - realm: Realm, - joinCondition: AbstractValue, - m1: PropertyBindings, - m2: PropertyBindings, - c1: CreatedObjects, - c2: CreatedObjects - ): PropertyBindings, - - // Returns a field by field join of two descriptors. - // Descriptors with get/set are not yet supported. - joinDescriptors( - realm: Realm, - joinCondition: AbstractValue, - d1: void | Descriptor, - d2: void | Descriptor - ): void | Descriptor, + joinValuesOfSelectedCompletions(selector: (Completion) => boolean, completion: Completion): Value, mapAndJoin( realm: Realm, diff --git a/src/utils/generator.js b/src/utils/generator.js index 8d9048984..cd9da95c0 100644 --- a/src/utils/generator.js +++ b/src/utils/generator.js @@ -24,6 +24,7 @@ import { type AbstractValueKind, BooleanValue, ConcreteValue, + EmptyValue, FunctionValue, NullValue, NumberValue, @@ -37,14 +38,7 @@ import { import { CompilerDiagnostic } from "../errors.js"; import { TypesDomain, ValuesDomain } from "../domains/index.js"; import invariant from "../invariant.js"; -import { - AbruptCompletion, - ForkedAbruptCompletion, - ThrowCompletion, - ReturnCompletion, - PossiblyNormalCompletion, - SimpleNormalCompletion, -} from "../completions.js"; +import { JoinedNormalAndAbruptCompletions, SimpleNormalCompletion, ThrowCompletion } from "../completions.js"; import type { BabelNodeExpression, BabelNodeIdentifier, @@ -54,7 +48,7 @@ import type { BabelNodeBlockStatement, BabelNodeLVal, } from "@babel/types"; -import { concretize, Utils } from "../singletons.js"; +import { concretize, Join, Utils } from "../singletons.js"; import type { SerializerOptions } from "../options.js"; import type { ShapeInformationInterface } from "../types.js"; import { PreludeGenerator } from "./PreludeGenerator.js"; @@ -507,61 +501,6 @@ class ReturnValueEntry extends GeneratorEntry { } } -class IfThenElseEntry extends GeneratorEntry { - constructor(generator: Generator, completion: PossiblyNormalCompletion | ForkedAbruptCompletion, realm: Realm) { - super(realm); - this.completion = completion; - this.containingGenerator = generator; - this.condition = completion.joinCondition; - - this.consequentGenerator = Generator.fromEffects(completion.consequentEffects, realm, "ConsequentEffects"); - this.alternateGenerator = Generator.fromEffects(completion.alternateEffects, realm, "AlternateEffects"); - } - - completion: PossiblyNormalCompletion | ForkedAbruptCompletion; - containingGenerator: Generator; - - condition: Value; - consequentGenerator: Generator; - alternateGenerator: Generator; - - toDisplayJson(depth: number): DisplayResult { - if (depth <= 0) return `IfThenElseEntry${this.index}`; - return Utils.verboseToDisplayJson( - { - type: "IfThenElse", - condition: this.condition, - consequent: this.consequentGenerator, - alternate: this.alternateGenerator, - }, - depth - ); - } - - visit(context: VisitEntryCallbacks, containingGenerator: Generator): boolean { - invariant( - containingGenerator === this.containingGenerator, - "This entry requires effects to be applied and may not be moved" - ); - this.condition = context.visitEquivalentValue(this.condition); - context.visitGenerator(this.consequentGenerator, containingGenerator); - context.visitGenerator(this.alternateGenerator, containingGenerator); - return true; - } - - serialize(context: SerializationContext): void { - let valuesToProcess = new Set(); - context.emit( - context.serializeCondition(this.condition, this.consequentGenerator, this.alternateGenerator, valuesToProcess) - ); - context.processValues(valuesToProcess); - } - - getDependencies(): void | Array { - return [this.consequentGenerator, this.alternateGenerator]; - } -} - class BindingAssignmentEntry extends GeneratorEntry { constructor(realm: Realm, binding: Binding, value: Value) { super(realm); @@ -651,14 +590,15 @@ export class Generator { } if (result instanceof UndefinedValue) return output; - if (result instanceof SimpleNormalCompletion || result instanceof ReturnCompletion) { + if (result instanceof SimpleNormalCompletion) { output.emitReturnValue(result.value); - } else if (result instanceof PossiblyNormalCompletion || result instanceof ForkedAbruptCompletion) { - output.emitIfThenElse(result, realm); } else if (result instanceof ThrowCompletion) { output.emitThrow(result.value); - } else if (result instanceof AbruptCompletion) { - // no-op + } else if (result instanceof JoinedNormalAndAbruptCompletions) { + let selector = c => + c instanceof ThrowCompletion && c.value !== realm.intrinsics.__bottomValue && !(c.value instanceof EmptyValue); + output.emitConditionalThrow(Join.joinValuesOfSelectedCompletions(selector, result)); + output.emitReturnValue(result.value); } else { invariant(false); } @@ -726,10 +666,6 @@ export class Generator { this._entries.push(new ReturnValueEntry(this.realm, this, result)); } - emitIfThenElse(result: PossiblyNormalCompletion | ForkedAbruptCompletion, realm: Realm): void { - this._entries.push(new IfThenElseEntry(this, result, realm)); - } - getName(): string { return `${this._name}(#${this.id})`; } @@ -839,6 +775,8 @@ export class Generator { } emitConditionalThrow(value: Value): void { + if (value instanceof EmptyValue) return; + this._issueThrowCompilerDiagnostic(value); this._addEntry({ args: [value], operationDescriptor: createOperationDescriptor("CONDITIONAL_THROW", { value }), diff --git a/src/utils/modules.js b/src/utils/modules.js index b4f32ea78..9a277a262 100644 --- a/src/utils/modules.js +++ b/src/utils/modules.js @@ -10,14 +10,13 @@ /* @flow */ import { GlobalEnvironmentRecord, DeclarativeEnvironmentRecord } from "../environment.js"; -import { CompilerDiagnostic, FatalError } from "../errors.js"; +import { FatalError } from "../errors.js"; import { Realm, Tracer } from "../realm.js"; import type { Effects } from "../realm.js"; import { Get } from "../methods/index.js"; -import { AbruptCompletion, PossiblyNormalCompletion, SimpleNormalCompletion } from "../completions.js"; +import { AbruptCompletion, SimpleNormalCompletion } from "../completions.js"; import { Environment } from "../singletons.js"; import { - AbstractValue, Value, FunctionValue, ObjectValue, @@ -39,7 +38,6 @@ import type { import invariant from "../invariant.js"; import { Logger } from "./logger.js"; import { SerializerStatistics } from "../serializer/statistics.js"; -import { createOperationDescriptor } from "./generator.js"; function downgradeErrorsToWarnings(realm: Realm, f: () => any) { let savedHandler = realm.errorHandler; @@ -182,107 +180,6 @@ export class ModuleTracer extends Tracer { return effects; } - // If a require fails, recover from it and delay the factory call until runtime - // Also, only in this mode, consider "accelerating" require calls, see below. - _callRequireAndDelayIfNeeded(moduleIdValue: number | string, performCall: () => Value): void | Value { - let realm = this.modules.realm; - this.log(`>require(${moduleIdValue})`); - let isTopLevelRequire = this.requireStack.length === 0; - if (this.evaluateForEffectsNesting > 0) { - if (isTopLevelRequire) { - let diagnostic = new CompilerDiagnostic( - "Non-deterministically conditional top-level require not currently supported", - realm.currentLocation, - "PP0017", - "FatalError" - ); - realm.handleError(diagnostic); - throw new FatalError(); - } else if (!this.modules.isModuleInitialized(moduleIdValue)) - // Nested require call: We record that this happened. Just so that - // if we discover later this this require call needs to get delayed, - // then we still know (some of) which modules it in turn required, - // and then we'll later "accelerate" requiring them to preserve the - // require ordering. See below for more details on acceleration. - this.uninitializedModuleIdsRequiredInEvaluateForEffects.add(moduleIdValue); - - return undefined; - } else { - return downgradeErrorsToWarnings(realm, () => { - let result; - try { - this.requireStack.push(moduleIdValue); - let requireSequenceStart = this.requireSequence.length; - this.requireSequence.push(moduleIdValue); - const previousNumDelayedModules = this.getStatistics().delayedModules; - let effects = this._callRequireAndAccelerate(isTopLevelRequire, moduleIdValue, performCall); - if (effects === undefined || effects.result instanceof AbruptCompletion) { - console.log(`delaying require(${moduleIdValue})`); - this.getStatistics().delayedModules = previousNumDelayedModules + 1; - // So we are about to emit a delayed require(...) call. - // However, before we do that, let's try to require all modules that we - // know this delayed require call will require. - // This way, we ensure that those modules will be fully initialized - // before the require call executes. - // TODO #690: More needs to be done to make the delayUnsupportedRequires - // feature completely safe. Open issues are: - // 1) Side-effects on the heap of delayed factory functions are not discovered or rejected. - // 2) While we do process an appropriate list of transitively required modules here, - // it's likely just a subset / prefix of all transivitely required modules, as - // more modules would have been required if the Introspection exception had not been thrown. - // To be correct, those modules would have to be prepacked here as well. - // TODO #798: Watch out for an upcoming change to the __d module declaration where the statically known - // list of dependencies will be announced, so we'll no longer have to guess. - let nestedModulesIds = new Set(); - for (let i = requireSequenceStart; i < this.requireSequence.length; i++) { - let nestedModuleId = this.requireSequence[i]; - if (nestedModulesIds.has(nestedModuleId)) continue; - nestedModulesIds.add(nestedModuleId); - this.modules.tryInitializeModule( - nestedModuleId, - `initialization of module ${nestedModuleId} as it's required by module ${moduleIdValue}` - ); - } - - let propName = moduleIdValue + ""; - invariant(typeof propName === "string"); - result = AbstractValue.createTemporalFromBuildFunction( - realm, - Value, - [new StringValue(realm, propName)], - createOperationDescriptor("MODULES_REQUIRE") - ); - } else { - result = effects.result; - if (result instanceof SimpleNormalCompletion) { - realm.applyEffects(effects, `initialization of module ${moduleIdValue}`); - this.modules.recordModuleInitialized(moduleIdValue, result.value); - } else if (result instanceof PossiblyNormalCompletion) { - let warning = new CompilerDiagnostic( - "Module import may fail with an exception", - result.location, - "PP0018", - "Warning" - ); - realm.handleError(warning); - result = result.value; - realm.applyEffects(effects, `initialization of module ${moduleIdValue}`); - } else { - invariant(false); - } - } - } finally { - let popped = this.requireStack.pop(); - invariant(popped === moduleIdValue); - this.log(` { if (value === undefined || value instanceof NullValue || value instanceof UndefinedValue) return []; if (value instanceof ArrayValue) { @@ -324,18 +221,10 @@ export class ModuleTracer extends Tracer { // Do some sanity checks and request require(...) calls with bad arguments if (moduleId instanceof NumberValue || moduleId instanceof StringValue) { moduleIdValue = moduleId.value; - if (!this.modules.moduleIds.has(moduleIdValue) && this.modules.delayUnsupportedRequires) { - this.modules.logger.logError(moduleId, "Module referenced by require call has not been defined."); - } } else { - if (this.modules.delayUnsupportedRequires) { - this.modules.logger.logError(moduleId, "First argument to require function is not a number or string value."); - } return undefined; } - - if (this.modules.delayUnsupportedRequires) return this._callRequireAndDelayIfNeeded(moduleIdValue, performCall); - else return this._callRequireAndRecord(moduleIdValue, performCall); + return this._callRequireAndRecord(moduleIdValue, performCall); } else if (F === this.modules.getDefine()) { // Here, we handle calls of the form // __d(factoryFunction, moduleId, dependencyArray) @@ -385,13 +274,7 @@ export class ModuleTracer extends Tracer { } export class Modules { - constructor( - realm: Realm, - logger: Logger, - logModules: boolean, - delayUnsupportedRequires: boolean, - accelerateUnsupportedRequires: boolean - ) { + constructor(realm: Realm, logger: Logger, logModules: boolean, accelerateUnsupportedRequires: boolean) { this.realm = realm; this.logger = logger; this._require = realm.intrinsics.undefined; @@ -400,7 +283,6 @@ export class Modules { this.moduleIds = new Set(); this.initializedModules = new Map(); realm.tracers.push((this.moduleTracer = new ModuleTracer(this, logModules))); - this.delayUnsupportedRequires = delayUnsupportedRequires; this.accelerateUnsupportedRequires = accelerateUnsupportedRequires; this.disallowDelayingRequiresOverride = false; } @@ -413,7 +295,6 @@ export class Modules { moduleIds: Set; initializedModules: Map; active: boolean; - delayUnsupportedRequires: boolean; accelerateUnsupportedRequires: boolean; disallowDelayingRequiresOverride: boolean; moduleTracer: ModuleTracer; diff --git a/src/utils/parse.js b/src/utils/parse.js index ff1635d60..e29f604c0 100644 --- a/src/utils/parse.js +++ b/src/utils/parse.js @@ -67,7 +67,7 @@ export default function( loc: e.loc, stackDecorated: false, }; - throw new ThrowCompletion(error, undefined, e.loc); + throw new ThrowCompletion(error, e.loc); } else { throw e; } diff --git a/src/values/AbstractValue.js b/src/values/AbstractValue.js index f97567fca..766a12f5f 100644 --- a/src/values/AbstractValue.js +++ b/src/values/AbstractValue.js @@ -16,6 +16,7 @@ import type { BabelNodeSourceLocation, BabelUnaryOperator, } from "@babel/types"; +import { Completion, JoinedAbruptCompletions, JoinedNormalAndAbruptCompletions } from "../completions.js"; import { CompilerDiagnostic, FatalError } from "../errors.js"; import type { Realm } from "../realm.js"; import { createOperationDescriptor, type OperationDescriptor } from "../utils/generator.js"; @@ -51,7 +52,6 @@ export type AbstractValueKind = | "abstractConcreteUnion" | "build function" | "widened property" - | "widened return result" | "widened numeric property" | "conditional" | "resolved" @@ -557,6 +557,49 @@ export default class AbstractValue extends Value { throw new FatalError(); } + static createJoinConditionForSelectedCompletions( + selector: Completion => boolean, + completion: JoinedAbruptCompletions | JoinedNormalAndAbruptCompletions + ): AbstractValue { + let jc = completion.joinCondition; + let realm = jc.$Realm; + let c = completion.consequent; + let a = completion.alternate; + let cContains = c.containsSelectedCompletion(selector); + let aContains = a.containsSelectedCompletion(selector); + invariant(cContains || aContains); + if (cContains && !aContains) return jc; + if (!cContains && aContains) return negate(jc); + invariant(cContains && aContains); + let cCond; + if (selector(c)) cCond = jc; + else { + invariant(c instanceof JoinedAbruptCompletions || c instanceof JoinedNormalAndAbruptCompletions); + cCond = AbstractValue.createJoinConditionForSelectedCompletions(selector, c); + } + let aCond; + if (selector(a)) aCond = negate(jc); + else { + invariant(a instanceof JoinedAbruptCompletions || a instanceof JoinedNormalAndAbruptCompletions); + aCond = AbstractValue.createJoinConditionForSelectedCompletions(selector, a); + } + let or = AbstractValue.createFromLogicalOp(realm, "||", cCond, aCond, undefined, true, true); + invariant(or instanceof AbstractValue); + if (completion instanceof JoinedNormalAndAbruptCompletions && completion.composedWith !== undefined) { + let composedCond = AbstractValue.createJoinConditionForSelectedCompletions(selector, completion.composedWith); + let and = AbstractValue.createFromLogicalOp(realm, "&&", composedCond, or); + invariant(and instanceof AbstractValue); + return and; + } + return or; + + function negate(v: AbstractValue): AbstractValue { + let nv = AbstractValue.createFromUnaryOp(realm, "!", v, true, v.expressionLocation, true, true); + invariant(nv instanceof AbstractValue); + return nv; + } + } + static createFromBinaryOp( realm: Realm, op: BabelBinaryOperator, diff --git a/src/values/NativeFunctionValue.js b/src/values/NativeFunctionValue.js index bc67d57bb..cf12b9b9c 100644 --- a/src/values/NativeFunctionValue.js +++ b/src/values/NativeFunctionValue.js @@ -119,7 +119,6 @@ export default class NativeFunctionValue extends ECMAScriptFunctionValue { } return new ReturnCompletion( this.callback(context, argsList, originalLength, newTarget), - undefined, this.$Realm.currentLocation ); } diff --git a/test/error-handler/ModifiedObjectPropertyLimitation.js b/test/error-handler/ModifiedObjectPropertyLimitation.js index 12d140496..011f09836 100644 --- a/test/error-handler/ModifiedObjectPropertyLimitation.js +++ b/test/error-handler/ModifiedObjectPropertyLimitation.js @@ -1,4 +1,5 @@ -// expected errors: [{"severity":"Warning","errorCode":"PP1007","callStack":"Error\n "},{"location":{"start":{"line":5,"column":12},"end":{"line":5,"column":14},"source":"test/error-handler/ModifiedObjectPropertyLimitation.js"},"severity":"Warning","errorCode":"PP0023","callStack":"Error\n "},{"location":{"start":{"line":5,"column":12},"end":{"line":5,"column":14},"source":"test/error-handler/ModifiedObjectPropertyLimitation.js"},"severity":"FatalError","errorCode":"PP1006","callStack":"Error\n "}] +// recover-from-errors +// expected errors: [{"severity":"Warning","errorCode":"PP1007","callStack":"Error\n "},{"severity":"Warning","errorCode":"PP0023","callStack":"Error\n "}] (function() { let p = {}; function f(c) { diff --git a/test/error-handler/bad-functions.js b/test/error-handler/bad-functions.js index 621edc299..f4e107e45 100644 --- a/test/error-handler/bad-functions.js +++ b/test/error-handler/bad-functions.js @@ -1,10 +1,10 @@ // recover-from-errors -// expected errors: [{"severity":"Warning","errorCode":"PP1007","callStack":"Error\n "},{"severity":"Warning","errorCode":"PP1007","callStack":"Error\n "},{"location":{"start":{"line":7,"column":26},"end":{"line":7,"column":35},"identifierName":"Exception","source":"test/error-handler/bad-functions.js"},"severity":"Warning","errorCode":"PP0023","callStack":"Error\n "},{"severity":"Warning","errorCode":"PP1007","callStack":"Error\n "},{"location":{"start":{"line":12,"column":13},"end":{"line":12,"column":18},"source":"test/error-handler/bad-functions.js"},"severity":"RecoverableError","errorCode":"PP1003"},{"location":{"start":{"line":8,"column":13},"end":{"line":8,"column":18},"source":"test/error-handler/bad-functions.js"},"severity":"RecoverableError","errorCode":"PP1003"}] +// expected errors: [{"severity":"Warning","errorCode":"PP1007","callStack":"Error\n "},{"severity":"Warning","errorCode":"PP1007","callStack":"Error\n "},{"severity":"Warning","errorCode":"PP0023","callStack":"Error\n "},{"severity":"Warning","errorCode":"PP1007","callStack":"Error\n "},{"location":{"start":{"line":12,"column":13},"end":{"line":12,"column":18},"source":"test/error-handler/bad-functions.js"},"severity":"RecoverableError","errorCode":"PP1003"},{"location":{"start":{"line":8,"column":13},"end":{"line":8,"column":18},"source":"test/error-handler/bad-functions.js"},"severity":"RecoverableError","errorCode":"PP1003"}] var wildcard = global.__abstract ? global.__abstract("number", "123") : 123; global.a = ""; function additional1() { - if (wildcard) throw new Exception(); + if (wildcard) throw new Error(); global.a = "foo"; } diff --git a/test/error-handler/forLoop1.js b/test/error-handler/forLoop1.js deleted file mode 100644 index 422ae121d..000000000 --- a/test/error-handler/forLoop1.js +++ /dev/null @@ -1,15 +0,0 @@ -// expected errors: [{"location":{"start":{"line":9,"column":17},"end":{"line":9,"column":32},"source":"test/error-handler/forLoop1.js"},"severity":"FatalError","errorCode":"PP0034"}] - -var x = global.__abstract ? (x = __abstract("number", "(1)")) : 1; -let i; -let j; - -label: for (i = 0; i < 2; i++) { - for (j = 0; j < 2; j++) { - if (i === x) continue label; - } -} - -inspect = function() { - return j; -}; diff --git a/test/error-handler/require_throws2.js b/test/error-handler/require_throws2.js deleted file mode 100644 index be9374891..000000000 --- a/test/error-handler/require_throws2.js +++ /dev/null @@ -1,123 +0,0 @@ -// delay unsupported requires -// recover-from-errors -// expected errors: [{"location":{"start":{"line":84,"column":4},"end":{"line":84,"column":12},"source":"test/error-handler/require_throws2.js"},"severity":"Warning","errorCode":"PP0018"}] - -let b = global.__abstract ? __abstract("boolean", "true") : true; - -var modules = Object.create(null); - -__d = define; -function require(moduleId) { - var moduleIdReallyIsNumber = moduleId; - var module = modules[moduleIdReallyIsNumber]; - return module && module.isInitialized ? module.exports : guardedLoadModule(moduleIdReallyIsNumber, module); -} - -function define(factory, moduleId, dependencyMap) { - if (moduleId in modules) { - return; - } - modules[moduleId] = { - dependencyMap: dependencyMap, - exports: undefined, - factory: factory, - hasError: false, - isInitialized: false, - }; - - var _verboseName = arguments[3]; - if (_verboseName) { - modules[moduleId].verboseName = _verboseName; - verboseNamesToModuleIds[_verboseName] = moduleId; - } -} - -var inGuard = false; -function guardedLoadModule(moduleId, module) { - if (!inGuard && global.ErrorUtils) { - inGuard = true; - var returnValue = void 0; - try { - returnValue = loadModuleImplementation(moduleId, module); - } catch (e) { - global.ErrorUtils.reportFatalError(e); - } - inGuard = false; - return returnValue; - } else { - return loadModuleImplementation(moduleId, module); - } -} - -function loadModuleImplementation(moduleId, module) { - var nativeRequire = global.nativeRequire; - if (!module && nativeRequire) { - nativeRequire(moduleId); - module = modules[moduleId]; - } - - if (!module) { - throw unknownModuleError(moduleId); - } - - if (module.hasError) { - throw moduleThrewError(moduleId); - } - - module.isInitialized = true; - var exports = (module.exports = {}); - var _module = module, - factory = _module.factory, - dependencyMap = _module.dependencyMap; - try { - var _moduleObject = { exports: exports }; - - factory(global, require, _moduleObject, exports, dependencyMap); - - module.factory = undefined; - - return (module.exports = _moduleObject.exports); - } catch (e) { - module.hasError = true; - module.isInitialized = false; - module.exports = undefined; - throw e; - } -} - -function unknownModuleError(id) { - var message = 'Requiring unknown module "' + id + '".'; - return Error(message); -} - -function moduleThrewError(id) { - return Error('Requiring module "' + id + '", which threw an exception.'); -} - -// === End require code === - -define(function(global, require, module, exports) { - var obj = global.__abstract - ? __makeSimple(__abstract({ unsupported: true }, "({unsupported: true})")) - : { unsupported: true }; - if (obj.unsupported) { - exports.magic = 42; - } else { - exports.magic = 23; - } - if (!b) throw "something bad"; - exports.notmagic = 666; -}, 0, null); - -define(function(global, require, module, exports) { - var x = require(0); - module.exports = function() { - return x.notmagic; - }; -}, 1, null); - -var f = require(1); - -inspect = function() { - return f().magic; -}; diff --git a/test/residual/If.js b/test/residual/If.js deleted file mode 100644 index cfe4feea5..000000000 --- a/test/residual/If.js +++ /dev/null @@ -1,32 +0,0 @@ -let b = global.__abstract ? __abstract("boolean", "true") : true; - -let x; -let y = 1; -if (b) { - x = true; - y = false; -} else { - x = []; - y = null; -} - -let z = 1; -if (b) { - z = 2; -} - -if (x) { - z = 3; -} - -if (y) { - z = 4; -} - -if (y) { - z = 5; -} else { - z = 6; -} - -let __result = y + "" + z; diff --git a/test/residual/arrayExpression.js b/test/residual/arrayExpression.js deleted file mode 100644 index 8881ab997..000000000 --- a/test/residual/arrayExpression.js +++ /dev/null @@ -1 +0,0 @@ -var __result = [1 + 2, "2", [3], ...[4, 5]]; diff --git a/test/residual/block.js b/test/residual/block.js deleted file mode 100644 index 56301002b..000000000 --- a/test/residual/block.js +++ /dev/null @@ -1,3 +0,0 @@ -{ - let __result = 1; -} diff --git a/test/residual/call.js b/test/residual/call.js deleted file mode 100644 index 3e4b7aa7a..000000000 --- a/test/residual/call.js +++ /dev/null @@ -1,49 +0,0 @@ -// skip -let b = global.__abstract ? __abstract("boolean", "true") : true; - -let y = 1; - -function foo(x) { - if (b) y += x; - else throw x; -} - -foo(2); - -function bar(x) { - if (x) return foo; - else throw foo; -} - -if (b) bar(b)(3); -else bar(false)(4); - -if (b) { -} else { - bar(b)(bar(false)); -} - -function alwaysThrow() { - throw "always"; -} - -if (b) { -} else { - bar(alwaysThrow()); -} -bar(bar(b)); - -if (b) { -} else { - alwaysThrow(bar(b), bar(b)); -} - -function plain() { - y += 3; -} -plain(); - -var ob = { p: plain }; -ob.p(); - -__result = y; diff --git a/test/residual/call2.js b/test/residual/call2.js deleted file mode 100644 index 6dea340c3..000000000 --- a/test/residual/call2.js +++ /dev/null @@ -1,5 +0,0 @@ -function f() { - return 123; -} -var g = global.__abstract ? global.__abstract(f, "f") : f; -__result = g(); diff --git a/test/residual/call3.js b/test/residual/call3.js deleted file mode 100644 index a84a76153..000000000 --- a/test/residual/call3.js +++ /dev/null @@ -1,19 +0,0 @@ -var o = global.__abstract ? global.__abstract("number", "1") : 1; -var obj = {}; -function bar(x) { - if (o > 1) { - obj.foo = function() { - return 1 + x; - }; - } else if (o > 2) { - obj.foo = function() { - return 2 + x; - }; - } else { - obj.foo = function() { - return 3 + x; - }; - } -} -bar(5); -__result = obj.foo(); diff --git a/test/residual/call4.js b/test/residual/call4.js deleted file mode 100644 index a84a76153..000000000 --- a/test/residual/call4.js +++ /dev/null @@ -1,19 +0,0 @@ -var o = global.__abstract ? global.__abstract("number", "1") : 1; -var obj = {}; -function bar(x) { - if (o > 1) { - obj.foo = function() { - return 1 + x; - }; - } else if (o > 2) { - obj.foo = function() { - return 2 + x; - }; - } else { - obj.foo = function() { - return 3 + x; - }; - } -} -bar(5); -__result = obj.foo(); diff --git a/test/residual/call5.js b/test/residual/call5.js deleted file mode 100644 index 82bddfccc..000000000 --- a/test/residual/call5.js +++ /dev/null @@ -1,18 +0,0 @@ -let b = global.__abstract ? __abstract("boolean", "true") : true; - -function foo() { - return 1; -} - -function bar() { - throw 2; -} - -let fooBar; -if (b) { - fooBar = foo; -} else { - fooBar = bar; -} - -__result = fooBar(); diff --git a/test/residual/call6.js b/test/residual/call6.js deleted file mode 100644 index 18e80fdf8..000000000 --- a/test/residual/call6.js +++ /dev/null @@ -1,7 +0,0 @@ -eval("var ohSo = 'evil';"); -evil = eval(); -eval = function() { - return " very "; -}; -very = eval(); -__result = ohSo + very + evil; diff --git a/test/residual/throw.js b/test/residual/throw.js deleted file mode 100644 index 874489dae..000000000 --- a/test/residual/throw.js +++ /dev/null @@ -1 +0,0 @@ -throw "a string"; diff --git a/test/serializer/abstract/Break2.js b/test/serializer/abstract/Break2.js index 62fdb278e..64551fb17 100644 --- a/test/serializer/abstract/Break2.js +++ b/test/serializer/abstract/Break2.js @@ -1,9 +1,7 @@ -// throws introspection error - +let x = global.__abstract ? __abstract("boolean", "true") : true; let arr = []; function foo() { - let x = __abstract("boolean", "true"); xyz: while (true) { arr[0] = 123; if (x) break; @@ -11,4 +9,8 @@ function foo() { } } -z = foo(); +var z = foo(); + +inspect = function() { + return z; +}; diff --git a/test/serializer/abstract/Continue2.js b/test/serializer/abstract/Continue2.js index abd70eb44..f47d6840e 100644 --- a/test/serializer/abstract/Continue2.js +++ b/test/serializer/abstract/Continue2.js @@ -1,6 +1,4 @@ -// throws introspection error - -let x = __abstract("boolean", "true"); +let x = global.__abstract ? __abstract("boolean", "true") : true; let arr = []; @@ -9,3 +7,7 @@ for (let i of [1, 2, 3]) { if (x) continue; else break; } + +inspect = function() { + return JSON.stringify(arr); +}; diff --git a/test/serializer/abstract/Switch.js b/test/serializer/abstract/Switch.js index 75278fdb6..915ddd696 100644 --- a/test/serializer/abstract/Switch.js +++ b/test/serializer/abstract/Switch.js @@ -1,5 +1,5 @@ let x = global.__abstract ? __abstract("number", "1") : 1; -z1 = z2 = z3 = z4 = z5 = z6 = z7 = z8 = z9 = z10 = z11 = 10; +global.z1 = global.z2 = global.z3 = global.z4 = global.z5 = global.z6 = global.z7 = global.z8 = global.z9 = global.z10 = global.z11 = 10; switch (x) { } @@ -103,7 +103,6 @@ switch (x) { break; } -// throws introspection error switch (x) { case 0: throw 1; @@ -117,7 +116,6 @@ switch (x) { throw 2; } -// throws introspection error switch (x) { case 1: if (z10 === 13) z11 = 12; @@ -137,26 +135,26 @@ switch (x) { inspect = function() { return ( "" + - z1 + + global.z1 + " " + - z2 + + global.z2 + " " + - z3 + + global.z3 + " " + - z4 + + global.z4 + " " + - z5 + + global.z5 + " " + - z6 + + global.z6 + " " + - z7 + + global.z7 + " " + - z8 + + global.z8 + " " + - z9 + + global.z9 + " " + - z10 + + global.z10 + " " + - z11 + global.z11 ); }; diff --git a/test/serializer/abstract/Throw7.js b/test/serializer/abstract/Throw7.js deleted file mode 100644 index 780801593..000000000 --- a/test/serializer/abstract/Throw7.js +++ /dev/null @@ -1,27 +0,0 @@ -// delay unsupported requires - -let x = global.__abstract ? __abstract("boolean", "true") : true; - -function __d(factory, moduleId) {} - -function foo() { - let r = { xyz: 123 }; - if (!x) throw "something bad"; - return r; -} - -function require(i) { - try { - return foo(); - } catch (e) { - throw e; - } -} - -__d(foo, 0); - -var z = require(0); - -inspect = function() { - return z; -}; diff --git a/test/serializer/abstract/require_tracking.js b/test/serializer/abstract/require_tracking.js index bf94bfd50..18a65ec71 100644 --- a/test/serializer/abstract/require_tracking.js +++ b/test/serializer/abstract/require_tracking.js @@ -1,5 +1,5 @@ // es6 -// delay unsupported requires +// throws introspection error var modules = Object.create(null); diff --git a/test/serializer/abstract/require_tracking2.js b/test/serializer/abstract/require_tracking2.js index 4cb2842cd..c89497c89 100644 --- a/test/serializer/abstract/require_tracking2.js +++ b/test/serializer/abstract/require_tracking2.js @@ -1,5 +1,4 @@ // es6 -// delay unsupported requires var modules = Object.create(null); diff --git a/test/serializer/additional-functions/ToObject.js b/test/serializer/additional-functions/ToObject.js index 8aacf4f25..c178c8b30 100644 --- a/test/serializer/additional-functions/ToObject.js +++ b/test/serializer/additional-functions/ToObject.js @@ -1,4 +1,4 @@ -// does not contain:(props).x +// throws introspection error (function() { function URIBase(uri) { if (uri instanceof URIBase) { diff --git a/test/serializer/additional-functions/conditions2.js b/test/serializer/additional-functions/conditions2.js index eabdeb668..a841e1cce 100644 --- a/test/serializer/additional-functions/conditions2.js +++ b/test/serializer/additional-functions/conditions2.js @@ -1,4 +1,4 @@ -// expected Warning: PP1007,PP0023 +// expected Warning: PP0023 if (!this.__evaluatePureFunction) { this.__evaluatePureFunction = function(f) { return f(); diff --git a/test/serializer/optimizations/require_accelerate.js b/test/serializer/optimizations/require_accelerate.js index 13bf747ed..821848cd6 100644 --- a/test/serializer/optimizations/require_accelerate.js +++ b/test/serializer/optimizations/require_accelerate.js @@ -1,5 +1,4 @@ // es6 -// delay unsupported requires var modules = Object.create(null); diff --git a/test/serializer/optimizations/require_delay.js b/test/serializer/optimizations/require_delay.js index 2bd99d9c6..9a2cf5036 100644 --- a/test/serializer/optimizations/require_delay.js +++ b/test/serializer/optimizations/require_delay.js @@ -1,5 +1,4 @@ // es6 -// delay unsupported requires var modules = Object.create(null); diff --git a/test/serializer/optimizations/require_spec_accelerate_delay.js b/test/serializer/optimizations/require_spec_accelerate_delay.js index d00738270..9ac4afa5f 100644 --- a/test/serializer/optimizations/require_spec_accelerate_delay.js +++ b/test/serializer/optimizations/require_spec_accelerate_delay.js @@ -1,5 +1,4 @@ // es6 -// delay unsupported requires // initialize more modules var modules = Object.create(null); diff --git a/test/serializer/optimizations/require_throws1.js b/test/serializer/optimizations/require_throws1.js index f7f8d8ef3..1b65b1fce 100644 --- a/test/serializer/optimizations/require_throws1.js +++ b/test/serializer/optimizations/require_throws1.js @@ -1,4 +1,3 @@ -// delay unsupported requires let b = global.__abstract ? __abstract("boolean", "true") : true; var modules = Object.create(null); diff --git a/test/serializer/optimized-functions/ArgumentProperty.js b/test/serializer/optimized-functions/ArgumentProperty.js new file mode 100644 index 000000000..04afc639a --- /dev/null +++ b/test/serializer/optimized-functions/ArgumentProperty.js @@ -0,0 +1,15 @@ +function fn(arg) { + if (arg !== null) { + if (arg.foo) { + return 42; + } + } +} + +if (global.__optimize) { + __optimize(fn); +} + +inspect = function() { + return JSON.stringify([fn(null), fn({}), fn({ foo: true })]); +}; diff --git a/test/serializer/optimized-functions/ComposeJoins.js b/test/serializer/optimized-functions/ComposeJoins.js new file mode 100644 index 000000000..aee1c114d --- /dev/null +++ b/test/serializer/optimized-functions/ComposeJoins.js @@ -0,0 +1,27 @@ +function fn(_ref) { + var className = _ref.className; + var comment = _ref.comment; + var author = comment.author; + var authorID = author && author.id; + var authorName = author && author.name; + if (!author || !authorID || authorName == null) { + return null; + } + if (author.url) { + return { + props: { + className: null, + + uid: authorID, + }, + children: authorName, + }; + } else { + return { + props: { className: className }, + children: authorName, + }; + } +} + +this.__optimize && __optimize(fn); diff --git a/test/serializer/optimized-functions/ConditionalReturn2.js b/test/serializer/optimized-functions/ConditionalReturn2.js new file mode 100644 index 000000000..b6077e37b --- /dev/null +++ b/test/serializer/optimized-functions/ConditionalReturn2.js @@ -0,0 +1,20 @@ +function bar() { + return 123; +} + +function fn(x) { + if (!x) { + return bar(); + } + + var foo = x.foo; + if (!foo) { + return 456; + } +} + +this.__optimize && __optimize(fn); + +inspect = function() { + return JSON.stringify(fn()); +}; diff --git a/test/serializer/optimized-functions/ForLoop3.js b/test/serializer/optimized-functions/ForLoop3.js index d6b5f2122..7f145e3f3 100644 --- a/test/serializer/optimized-functions/ForLoop3.js +++ b/test/serializer/optimized-functions/ForLoop3.js @@ -1,3 +1,5 @@ +// expected Warning,RecoverableError: PP1007, PP0023, PP1002 +// throws introspection error (function() { function fn(arg) { if (arg.foo()) { diff --git a/test/serializer/optimized-functions/Issue1856.js b/test/serializer/optimized-functions/Issue1856.js new file mode 100644 index 000000000..578f61a68 --- /dev/null +++ b/test/serializer/optimized-functions/Issue1856.js @@ -0,0 +1,16 @@ +let p = {}; +function f(c) { + let o = {}; + if (c) { + o.__proto__ = p; + throw o; + } +} +if (global.__optimize) __optimize(f); +inspect = function() { + try { + f(true); + } catch (e) { + return e.$Prototype === p; + } +}; diff --git a/test/serializer/optimized-functions/Issue2151.js b/test/serializer/optimized-functions/Issue2151.js new file mode 100644 index 000000000..e8a4a3f50 --- /dev/null +++ b/test/serializer/optimized-functions/Issue2151.js @@ -0,0 +1,17 @@ +function bad(v) { + if (v == null) { + return null; + } + var a = v.a, + b = v.b; + if (a == null || b == null) { + return a && b; + } + return v; +} + +if (global.__optimize) __optimize(bad); + +inspect = function() { + return bad(null); +}; diff --git a/test/serializer/optimized-functions/LoopBailout7.js b/test/serializer/optimized-functions/LoopBailout7.js index 9d6524b7c..e4a0fe5ad 100644 --- a/test/serializer/optimized-functions/LoopBailout7.js +++ b/test/serializer/optimized-functions/LoopBailout7.js @@ -1,3 +1,5 @@ +// expected Warning,RecoverableError: PP1007, PP0023, PP1002 +// throws introspection error function fn(x, oldItems) { var items = []; for (; i !== x; ) { diff --git a/test/serializer/optimized-functions/NullCheck.js b/test/serializer/optimized-functions/NullCheck.js new file mode 100644 index 000000000..638105b5d --- /dev/null +++ b/test/serializer/optimized-functions/NullCheck.js @@ -0,0 +1,12 @@ +function func1(v) { + if (v == null) return null; + var a = v.a; + if (a == null) return null; + return a; +} + +if (global.__optimize) __optimize(func1); + +inspect = function() { + return func1(); +}; diff --git a/test/serializer/optimized-functions/Switch2.js b/test/serializer/optimized-functions/Switch2.js index 68f2295b9..1eb19516c 100644 --- a/test/serializer/optimized-functions/Switch2.js +++ b/test/serializer/optimized-functions/Switch2.js @@ -1,6 +1,5 @@ let x = global.__abstract ? __abstract("number", "1") : 1; -// throws introspection error function f(x) { switch (x) { default: diff --git a/test/serializer/optimized-functions/Switch3.js b/test/serializer/optimized-functions/Switch3.js index 1b3a6edc1..576c740a6 100644 --- a/test/serializer/optimized-functions/Switch3.js +++ b/test/serializer/optimized-functions/Switch3.js @@ -1,6 +1,5 @@ let x = global.__abstract ? __abstract("number", "1") : 1; -// throws introspection error function g(x) { switch (x) { case 0: diff --git a/test/serializer/optimized-functions/Switch4.js b/test/serializer/optimized-functions/Switch4.js index 9e661d7c2..69d777077 100644 --- a/test/serializer/optimized-functions/Switch4.js +++ b/test/serializer/optimized-functions/Switch4.js @@ -1,7 +1,6 @@ let x = global.__abstract ? __abstract("number", "1") : 1; let c = global.__abstract ? __abstract("boolean", "true") : true; -// throws introspection error function h(x, c) { switch (x) { case 0: