diff --git a/.circleci/config.yml b/.circleci/config.yml index b1ad32979a..56a167513e 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 17534e3a60..db2de7a0c8 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 f7e31743e7..f07d1d3aa5 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 3920658cc4..3ef146a9b8 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 ea6a08a699..47986a5b0d 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 876f2141b6..0000000000 --- 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 c9f3a512a2..d46e31da00 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, @@ -377,7 +376,6 @@ function runTest(name, code, options: PrepackOptions, args) { initializeGlobals(realm); let serializerOptions = { initializeMoreModules, - delayUnsupportedRequires, internalDebug: true, lazyObjectsRuntime: options.lazyObjectsRuntime, }; @@ -455,7 +453,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 }]]); } @@ -586,7 +583,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, @@ -636,9 +633,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 }); @@ -918,7 +913,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 51c7296ffc..05a97d8a54 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") + "]"; } -} -// 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); - invariant(this.constructor !== NormalCompletion, "NormalCompletion is an abstract base class"); + 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); + } } - shallowCloneWithoutEffects(): NormalCompletion { - invariant(false, "NormalCompletion.shallowCloneWithoutEffects is an abstract base method and should not be called"); + 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; } } -// 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); +// Normal completions are returned just like spec completions +export class NormalCompletion extends Completion { + constructor(value: Value, location: ?BabelNodeSourceLocation, target?: ?string) { + super(value, location, target); + invariant(this.constructor !== NormalCompletion, "NormalCompletion is an abstract base class"); } } +// 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 ce126377f1..61af4d26b8 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 835a7954a2..6b13ab1e93 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 447560b957..458dd59b62 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 c2480dd0f8..b4d13080fa 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 a4fffd8dc4..4ee3a49d30 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 f79f836f4f..cd1b674315 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 0b482aa83f..6cca620c9a 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 2e2d3e7229..7e61d836b0 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 3ad17a317f..fdf698ccfa 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 20d421bcbf..a1351bd981 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 7f38ff543d..878a143407 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 eb4c238095..f429b993e9 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 b693878ac2..badf606bd8 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 e2b2776b8f..80a1d85a75 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 5399347c19..52ecb0a93c 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 bddb3f3c4b..305fbd718a 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 d7e6e55590..5db1c9af72 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)); - - 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(); + let r = env.evaluateCompletionDeref(node, strictCode); + + 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++; } - - if (result instanceof BreakCompletion) { + let sc = Functions.incorporateSavedCompletion(realm, result); + invariant(sc !== undefined); + result = sc; + + 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 0190571bd0..38ad5427aa 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 1e8d41bdb0..a1c249b034 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); - } - // Add effects of normal exits from handler to blockRes and apply to global state - let handlerEffects = composeNestedThrowEffectsWithHandler(blockRes); - realm.applyEffects(handlerEffects); - handlerRes = handlerEffects.result; + result = env.evaluateCompletionDeref(handler, strictCode, blockRes); } else { - // The handler is not invoked, so just carry on. + 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 + } } } - 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 cd4a070dfa..7a26a298f2 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 05bb67b583..ca560e98dc 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 53efeb71ef..e2671ed717 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 c1c2c8b9d2..86185168db 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 6b2a140826..704a3ebd87 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, + AbstractValue, + BooleanValue, EmptyValue, + NativeFunctionValue, + NullValue, + NumberValue, ObjectValue, + StringValue, SymbolValue, - BooleanValue, - NativeFunctionValue, + 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 714aedc7b6..de4117071a 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, + AbstractObjectValue, + AbstractValue, ECMAScriptSourceFunctionValue, - ObjectValue, + FunctionValue, + NativeFunctionValue, NullValue, + ObjectValue, UndefinedValue, - NativeFunctionValue, - AbstractObjectValue, - AbstractValue, } 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; - 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; + realm.savedCompletion = undefined; - // 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); + let c; + try { + 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 4f7f797a97..48192896a2 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 457c742aa5..cb0ead5925 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 ffea4238a5..7b33b381d4 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); - } - - 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]; - } - - 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); + 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; } - }; - 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); - }); + 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; } - 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) + 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); } - 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, []); - } - - // 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 (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 (!(c instanceof ForkedAbruptCompletion)) { - return emptyEffects; + if (c1 instanceof ContinueCompletion && c2 instanceof ContinueCompletion && c1.target === c2.target) { + return new ContinueCompletion(realm.intrinsics.empty, joinCondition.expressionLocation, c1.target); } - // 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); + 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); } - 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); + 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; + } - 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); + joinCompletions(joinCondition: Value, c1: Completion, c2: Completion): Completion { + if (!joinCondition.mightNotBeTrue()) return c1; + if (!joinCondition.mightNotBeFalse()) return c2; + invariant(joinCondition instanceof AbstractValue); + + 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; - } - invariant(completion instanceof SimpleNormalCompletion); - return new PossiblyNormalCompletion( - completion.value, - joinCondition, - result1, - result2, - savedPathConditions, - savedEffects - ); - } - if (result2 instanceof AbruptCompletion) { - let completion = result1; - let savedEffects; - let savedPathConditions = []; - if (result1 instanceof PossiblyNormalCompletion) { - completion = result1.getNormalCompletion(); - savedEffects = result1.savedEffects; - savedPathConditions = result1.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; } - 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 1ef6158984..773af65d20 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 { @@ -309,57 +308,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 e65a7788f6..18379c17fc 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 fef529e1e0..6a2e14bd75 100644 --- a/src/options.js +++ b/src/options.js @@ -64,7 +64,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 2ede31a5a3..0000000000 --- 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 e16ac13873..0000000000 --- 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 f716dba248..0000000000 --- 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 2a313b3bf3..0000000000 --- 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 964920a1ac..0000000000 --- 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 6ced59ebdf..0000000000 --- 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 11e707044c..0000000000 --- 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 ff1b1442b4..0000000000 --- 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 c19a3a50f4..0000000000 --- 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 50d6fa93d6..0000000000 --- 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 fe8afafb7e..0000000000 --- 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 a52c1bfded..0000000000 --- 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 71943b6bd8..0000000000 --- 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 e3eaece136..0000000000 --- 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 f98ca93c06..0000000000 --- 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 82281bc7f2..0000000000 --- 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 fee813e807..0000000000 --- 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 13d91ee355..0000000000 --- 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 e737a25364..0000000000 --- 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 6a93c5531e..0000000000 --- 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 8d790b21d2..0000000000 --- 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 33ccf7690d..0000000000 --- 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 6a9409f925..0000000000 --- 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 795fbb64f2..0000000000 --- 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 c980b1e4be..0000000000 --- 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 cb743bce6c..0000000000 --- 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 34b2b56365..0000000000 --- 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 c4efa40bf4..0000000000 --- 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 2a8cbcde64..0000000000 --- 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 9feb7f6204..0000000000 --- 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 7684109add..0000000000 --- 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 1430cb2166..0000000000 --- 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 454355307d..0000000000 --- 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 a11f8faadd..0000000000 --- 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 639bf43054..0000000000 --- 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 e72305701f..0000000000 --- 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 600767db5a..0000000000 --- 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 bde4f75c0b..0000000000 --- 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 2d9e54ce29..0000000000 --- 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 7c258b53e0..0000000000 --- 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 6d79c3b3db..0000000000 --- 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 af82e66149..0000000000 --- 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 179a514dbc..0000000000 --- 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 0c702cad3a..0000000000 --- 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 332a36b032..0000000000 --- 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 66da4ced0a..0000000000 --- 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 d9572ebca0..0000000000 --- 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 e029a19977..0000000000 --- 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 61ecdb2d24..0000000000 --- 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 861cedd748..0000000000 --- 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 764bd99210..0000000000 --- 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 f785325546..0000000000 --- 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 63c027ea51..0000000000 --- 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 1e13b68791..0000000000 --- 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 0674bf0254..26e38200b3 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 18ada43c0b..2c5b61eb3e 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, @@ -119,7 +118,6 @@ export function getSerializerOptions({ lazyObjectsRuntime, heapGraphFormat, delayInitializations = false, - delayUnsupportedRequires = false, accelerateUnsupportedRequires = true, internalDebug = false, debugScopes = false, @@ -133,7 +131,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 3a267001e4..644beb945d 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 4ab8574555..dd7608ec7a 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 { @@ -794,12 +794,14 @@ export function getValueFromFunctionCall( let newCall = func.$Construct; let completion; 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; @@ -807,18 +809,7 @@ export function getValueFromFunctionCall( throw error; } } - 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 97b2e16b16..874e6d6768 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(); @@ -368,7 +348,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; @@ -446,15 +426,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; @@ -558,26 +529,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 { @@ -588,8 +539,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); } } @@ -631,31 +580,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); @@ -731,7 +659,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; @@ -785,7 +717,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(); @@ -832,7 +764,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; @@ -846,22 +778,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 @@ -910,21 +826,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); @@ -950,12 +854,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; @@ -1013,14 +919,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; @@ -1042,10 +940,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 @@ -1085,7 +983,6 @@ export class Realm { } catch (e) { if (!(e instanceof InfeasiblePathError)) throw e; } - invariant(effects1 === undefined || effects1.result.effects === effects1); let effects2; try { @@ -1093,41 +990,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( @@ -1223,187 +1099,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)); - } - 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)); + 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.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); + composeWithSavedCompletion(completion: Completion): Completion { + if (this.savedCompletion === undefined) { + if (completion instanceof JoinedNormalAndAbruptCompletions) { + this.savedCompletion = completion; + this.pushPathConditionsLeadingToCompletionOfType(NormalCompletion, completion); + this.captureEffects(completion); + } + return completion; } 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; + 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 { - completion.consequent = this.savedCompletion; + this.savedCompletion = undefined; } - completion.savedEffects = this.savedCompletion.savedEffects; - } else { - this.captureEffects(completion); + return cc; } - this.savedCompletion = completion; } - composeWithSavedCompletion(completion: PossiblyNormalCompletion): Value { - if (this.savedCompletion === undefined) { - this.savedCompletion = completion; - this.savedCompletion.savedPathConditions = this.pathConditions; - this.pathConditions = [].concat(this.pathConditions); - this.captureEffects(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; - } + pushPathConditionsLeadingToCompletionOfType(CompletionType: typeof Completion, completion: Completion): void { 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); + 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 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 allPathsAreNormal(c: Completion): boolean { - if (c instanceof PossiblyNormalCompletion || c instanceof ForkedAbruptCompletion) - return allPathsAreNormal(c.consequent) && allPathsAreNormal(c.alternate); - if (c instanceof AbruptCompletion) return false; - return true; + 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 getNormalPathConditionFor(c: Completion): Value { - invariant(c instanceof PossiblyNormalCompletion || c instanceof ForkedAbruptCompletion); - if (allPathsAreAbrupt(c.consequent)) { - invariant(!allPathsAreAbrupt(c.alternate)); + 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), @@ -1418,13 +1233,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, @@ -1432,9 +1247,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 @@ -1463,7 +1278,7 @@ export class Realm { if (appendGenerator) this.appendGenerator(generator, leadingComment); // Restore modifiedBindings - this.redoBindings(modifiedBindings); + this.restoreBindings(modifiedBindings); this.restoreProperties(modifiedProperties); // track modifiedBindings @@ -1565,10 +1380,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; @@ -1646,21 +1459,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 }); }); } @@ -1753,7 +1562,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 7855167b5e..71100d6a87 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 9c44dea977..d32f98a40b 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 d16ea5239b..39164c1d79 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 accdbecc3f..edbc10350b 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, - - unbundleNormalCompletion( - completionOrValue: Completion | Value | Reference - ): [void | NormalCompletion, Value | Reference], - - composeNormalCompletions( - leftCompletion: void | NormalCompletion, - rightCompletion: void | NormalCompletion, - resultValue: Value, - realm: Realm - ): PossiblyNormalCompletion | Value, - - composePossiblyNormalCompletions( - realm: Realm, - pnc: PossiblyNormalCompletion, - c: PossiblyNormalCompletion, - priorEffects?: Effects - ): PossiblyNormalCompletion, - - 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, + composeCompletions(leftCompletion: void | Completion | Value, rightCompletion: Completion | Value): Completion, - extractAndJoinCompletionsOfType(CompletionType: typeof AbruptCompletion, realm: Realm, c: AbruptCompletion): Effects, + composeWithEffects(completion: Completion, effects: Effects): Effects, - joinForkOrChoose(realm: Realm, joinCondition: Value, e1: Effects, e2: Effects): Effects, + joinCompletions(joinCondition: Value, c1: Completion, c2: Completion): Completion, - joinNestedEffects(realm: Realm, c: Completion, precedingEffects?: Effects): Effects, + joinEffects(joinCondition: Value, e1: Effects, e2: 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 8d9048984e..cd9da95c02 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 b4f32ea78e..9a277a2627 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 ff1635d601..e29f604c04 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 7e7f7d22c9..7e0ad81bb5 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" @@ -556,6 +556,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 bc67d57bbc..cf12b9b9c9 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 12d1404964..011f098367 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 621edc299f..f4e107e454 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 422ae121df..0000000000 --- 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 be93748912..0000000000 --- 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 cfe4feea53..0000000000 --- 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 8881ab997c..0000000000 --- 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 56301002b0..0000000000 --- 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 3e4b7aa7a1..0000000000 --- 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 6dea340c3a..0000000000 --- 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 a84a761537..0000000000 --- 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 a84a761537..0000000000 --- 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 82bddfccc1..0000000000 --- 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 18e80fdf89..0000000000 --- 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 874489dae3..0000000000 --- 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 62fdb278e9..64551fb17c 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 abd70eb449..f47d6840e5 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 75278fdb6f..915ddd696a 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 7808015931..0000000000 --- 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 bf94bfd503..18a65ec710 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 4cb2842cdf..c89497c896 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 8aacf4f258..c178c8b305 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 eabdeb6686..a841e1cce7 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 13bf747ed2..821848cd63 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 2bd99d9c6d..9a2cf50368 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 d007382703..9ac4afa5fa 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 f7f8d8ef33..1b65b1fcea 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 0000000000..04afc639ae --- /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 0000000000..aee1c114dc --- /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 0000000000..b6077e37bc --- /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 d6b5f2122f..7f145e3f3c 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 0000000000..578f61a68e --- /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 0000000000..e8a4a3f50b --- /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 9d6524b7c0..e4a0fe5add 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 0000000000..638105b5db --- /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 68f2295b98..1eb19516ca 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 1b3a6edc1b..576c740a6d 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 9e661d7c2a..69d7770773 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: