Do full joins (#2402)

Summary:
Release note: Rewrote the joining logic to always do a full join at every join point

Closes #2151 #2222 #2279

I've spent a lot of time in the last few months trying to sort out problems that arise from effects being applied too many or too few times. Fixing these feel a bit like playing wack a mole and in the end no fix goes unpunished.

Stepping back a bit from the fray, it seems to me that the root cause of all this pain is the fact that joins of different kinds of completions get delayed.

Before we had path conditions and the simplifier this seemed like a rather good thing since exceptional paths did not contribute values to the normal paths and we thus had fewer abstract values to deal with and fewer places where Prepack would grind to a halt.

In the current state of things, however, it seems perfectly possible to join in all branches at every join point. I've had to decrease some limits, in particular the number of times we go around a loop with conditional exits. I've also had to make the test runner impose a limit on how many times the simplifier can invoke Path.implies.

Nevertheless, the tests seem to pass and hopefully this will also fix quite a lot of bugs that have been unresolved for many months already.
Pull Request resolved: https://github.com/facebook/prepack/pull/2402

Differential Revision: D9236263

Pulled By: hermanventer

fbshipit-source-id: 92a25b591591297afeba536429226c5a0291f451
This commit is contained in:
Herman Venter 2018-08-11 20:49:07 -07:00 committed by Facebook Github Bot
parent 671ea300ba
commit 7157849a44
144 changed files with 981 additions and 5178 deletions

View File

@ -59,7 +59,6 @@ jobs:
yarn test-react yarn test-react
yarn test-sourcemaps yarn test-sourcemaps
yarn test-std-in 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 --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 #yarn test-test262-new --statusFile ~/artifacts/test262-new-status.txt --timeout 120 --verbose
- store_artifacts: - store_artifacts:

View File

@ -31,8 +31,6 @@
"lint": "eslint src scripts", "lint": "eslint src scripts",
"flow": "flow", "flow": "flow",
"flow-ci": "flow version; flow check", "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": "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-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", "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-std-in": "bash < scripts/test-std-in.sh",
"test-react": "jest", "test-react": "jest",
"test-react-fast": "SKIP_REACT_JSX_TESTS=true 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-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", "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", "repl": "node lib/repl-cli.js",

View File

@ -8,9 +8,7 @@
*/ */
/* @flow */ /* @flow */
// This file just runs the 4 test runners in one file for coverage // This file just runs the 3 test runners in one file for coverage
require("./test-residual.js");
require("./test-runner.js"); require("./test-runner.js");
require("./generate-sourcemaps-test.js"); require("./generate-sourcemaps-test.js");

View File

@ -54,7 +54,6 @@ function runTest(name: string, code: string): boolean {
console.log(chalk.inverse(name)); console.log(chalk.inverse(name));
let recover = code.includes("// recover-from-errors"); 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 compatibility = code.includes("// jsc") ? "jsc-600-1-4-17" : undefined;
let expectedErrors = code.match(/\/\/\s*expected errors:\s*(.*)/); let expectedErrors = code.match(/\/\/\s*expected errors:\s*(.*)/);
@ -68,7 +67,6 @@ function runTest(name: string, code: string): boolean {
try { try {
let options = { let options = {
internalDebug: false, internalDebug: false,
delayUnsupportedRequires,
mathRandomSeed: "0", mathRandomSeed: "0",
errorHandler: errorHandler.bind(null, recover ? "Recover" : "Fail", errors), errorHandler: errorHandler.bind(null, recover ? "Recover" : "Fail", errors),
serialize: true, serialize: true,

View File

@ -68,7 +68,6 @@ function runTest(name: string, code: string): boolean {
let options = { let options = {
internalDebug: true, internalDebug: true,
compatibility: "jsc-600-1-4-17", compatibility: "jsc-600-1-4-17",
delayUnsupportedRequires: true,
accelerateUnsupportedRequires: true, accelerateUnsupportedRequires: true,
mathRandomSeed: "0", mathRandomSeed: "0",
errorHandler, errorHandler,

View File

@ -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 <string>]`;
}
// 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();

View File

@ -307,7 +307,7 @@ function verifyFunctionOrderings(code: string): boolean {
return true; return true;
} }
function unescapleUniqueSuffix(code: string, uniqueSuffix?: string) { function unescapeUniqueSuffix(code: string, uniqueSuffix?: string) {
return uniqueSuffix != null ? code.replace(new RegExp(uniqueSuffix, "g"), "") : code; 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)); 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 compatibility = code.includes("// jsc") ? "jsc-600-1-4-17" : undefined;
let initializeMoreModules = code.includes("// initialize more modules"); let initializeMoreModules = code.includes("// initialize more modules");
let delayUnsupportedRequires = code.includes("// delay unsupported requires");
if (args.verbose || code.includes("// inline expressions")) options.inlineExpressions = true; if (args.verbose || code.includes("// inline expressions")) options.inlineExpressions = true;
options.invariantLevel = code.includes("// omit invariants") || args.verbose ? 0 : 99; options.invariantLevel = code.includes("// omit invariants") || args.verbose ? 0 : 99;
if (code.includes("// emit concrete model")) options.emitConcreteModel = true; 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 compileJSXWithBabel = code.includes("// babel:jsx");
let functionCloneCountMatch = code.match(/\/\/ serialized function clone count: (\d+)/); let functionCloneCountMatch = code.match(/\/\/ serialized function clone count: (\d+)/);
options = ((Object.assign({}, options, { options = ((Object.assign({}, options, {
abstractValueImpliesMax: 2000,
compatibility, compatibility,
debugNames: args.debugNames, debugNames: args.debugNames,
debugScopes: args.debugScopes, debugScopes: args.debugScopes,
initializeMoreModules, initializeMoreModules,
delayUnsupportedRequires,
errorHandler: diag => "Fail", errorHandler: diag => "Fail",
internalDebug: true, internalDebug: true,
serialize: true, serialize: true,
@ -381,7 +380,6 @@ function runTest(name, code, options: PrepackOptions, args) {
initializeGlobals(realm); initializeGlobals(realm);
let serializerOptions = { let serializerOptions = {
initializeMoreModules, initializeMoreModules,
delayUnsupportedRequires,
internalDebug: true, internalDebug: true,
lazyObjectsRuntime: options.lazyObjectsRuntime, lazyObjectsRuntime: options.lazyObjectsRuntime,
}; };
@ -459,7 +457,6 @@ function runTest(name, code, options: PrepackOptions, args) {
addedCode = code.substring(i + injectAtRuntime.length, code.indexOf("\n", i)); addedCode = code.substring(i + injectAtRuntime.length, code.indexOf("\n", i));
options.residual = false; options.residual = false;
} }
if (delayUnsupportedRequires) options.residual = false;
if (args.es5) { if (args.es5) {
code = transformWithBabel(code, [], [["@babel/env", { forceAllTransforms: true, modules: false }]]); code = transformWithBabel(code, [], [["@babel/env", { forceAllTransforms: true, modules: false }]]);
} }
@ -590,7 +587,7 @@ function runTest(name, code, options: PrepackOptions, args) {
codeToRun = augmentCodeWithLazyObjectSupport(codeToRun, args.lazyObjectsRuntime); codeToRun = augmentCodeWithLazyObjectSupport(codeToRun, args.lazyObjectsRuntime);
} }
if (args.verbose) console.log(codeToRun); if (args.verbose) console.log(codeToRun);
codeIterations.push(unescapleUniqueSuffix(codeToRun, options.uniqueSuffix)); codeIterations.push(unescapeUniqueSuffix(codeToRun, options.uniqueSuffix));
if (args.es5) { if (args.es5) {
codeToRun = transformWithBabel( codeToRun = transformWithBabel(
codeToRun, codeToRun,
@ -640,9 +637,7 @@ function runTest(name, code, options: PrepackOptions, args) {
} }
if (singleIterationOnly) return Promise.reject({ type: "RETURN", value: true }); if (singleIterationOnly) return Promise.reject({ type: "RETURN", value: true });
if ( if (
unescapleUniqueSuffix(oldCode, oldUniqueSuffix) === unescapeUniqueSuffix(oldCode, oldUniqueSuffix) === unescapeUniqueSuffix(newCode, newUniqueSuffix)
unescapleUniqueSuffix(newCode, newUniqueSuffix) ||
delayUnsupportedRequires
) { ) {
// The generated code reached a fixed point! // The generated code reached a fixed point!
return Promise.reject({ type: "RETURN", value: true }); return Promise.reject({ type: "RETURN", value: true });
@ -922,7 +917,7 @@ function argsParse(): ProgramArgs {
es5: false, // if true test marked as es6 only are not run es5: false, // if true test marked as es6 only are not run
filter: "", filter: "",
outOfProcessRuntime: "", // if set, assumed to be a JS runtime and is used 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, lazyObjectsRuntime: LAZY_OBJECTS_RUNTIME_NAME,
noLazySupport: false, noLazySupport: false,
fast: false, fast: false,

View File

@ -11,78 +11,108 @@
import type { BabelNodeSourceLocation } from "@babel/types"; import type { BabelNodeSourceLocation } from "@babel/types";
import invariant from "./invariant.js"; 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"; import { AbstractValue, EmptyValue, Value } from "./values/index.js";
export class Completion { export class Completion {
constructor(value: Value, precedingEffects: void | Effects, location: ?BabelNodeSourceLocation, target?: ?string) { constructor(value: Value, location: ?BabelNodeSourceLocation, target?: ?string) {
let e = precedingEffects;
if (e !== undefined) {
if (e.result === undefined) e.result = this;
else e = e.shallowCloneWithResult(this);
}
this.value = value; this.value = value;
this.effects = e;
this.target = target; this.target = target;
this.location = location; this.location = location;
invariant(this.constructor !== Completion, "Completion is an abstract base class"); invariant(this.constructor !== Completion, "Completion is an abstract base class");
} }
value: Value; value: Value;
effects: void | Effects;
target: ?string; target: ?string;
location: ?BabelNodeSourceLocation; location: ?BabelNodeSourceLocation;
shallowCloneWithoutEffects(): Completion { containsSelectedCompletion(selector: Completion => boolean): boolean {
invariant(false, "Completion.shallowCloneWithoutEffects is an abstract base method and should not be called"); return selector(this);
} }
toDisplayString(): string { toDisplayString(): string {
return "[" + this.constructor.name + " value " + (this.value ? this.value.toDisplayString() : "undefined") + "]"; return "[" + this.constructor.name + " value " + (this.value ? this.value.toDisplayString() : "undefined") + "]";
} }
static makeAllNormalCompletionsResultInUndefined(completion: Completion): void {
let undefinedVal = completion.value.$Realm.intrinsics.undefined;
if (completion instanceof SimpleNormalCompletion) completion.value = undefinedVal;
else if (completion instanceof JoinedNormalAndAbruptCompletions) {
if (completion.composedWith !== undefined)
Completion.makeAllNormalCompletionsResultInUndefined(completion.composedWith);
Completion.makeAllNormalCompletionsResultInUndefined(completion.consequent);
Completion.makeAllNormalCompletionsResultInUndefined(completion.alternate);
}
}
static makeSelectedCompletionsInfeasible(selector: Completion => boolean, completion: Completion): void {
let bottomValue = completion.value.$Realm.intrinsics.__bottomValue;
if (selector(completion)) completion.value = bottomValue;
else if (completion instanceof JoinedNormalAndAbruptCompletions || completion instanceof JoinedAbruptCompletions) {
if (completion instanceof JoinedNormalAndAbruptCompletions && completion.composedWith !== undefined)
Completion.makeSelectedCompletionsInfeasible(selector, completion.composedWith);
Completion.makeSelectedCompletionsInfeasible(selector, completion.consequent);
Completion.makeSelectedCompletionsInfeasible(selector, completion.alternate);
}
}
static normalizeSelectedCompletions(selector: Completion => boolean, completion: Completion): Completion {
if (selector(completion)) return new SimpleNormalCompletion(completion.value);
let normalizedComposedWith;
if (completion instanceof JoinedNormalAndAbruptCompletions) {
invariant(completion.savedEffects === undefined); // caller should not used a still saved completion for this
if (completion.composedWith !== undefined)
normalizedComposedWith = Completion.normalizeSelectedCompletions(selector, completion.composedWith);
}
if (completion instanceof JoinedNormalAndAbruptCompletions || completion instanceof JoinedAbruptCompletions) {
let nc = Completion.normalizeSelectedCompletions(selector, completion.consequent);
let na = Completion.normalizeSelectedCompletions(selector, completion.alternate);
if (normalizedComposedWith === undefined) {
if (nc === completion.consequent && na === completion.alternate) return completion;
if (nc instanceof AbruptCompletion && na instanceof AbruptCompletion) return completion;
if (nc instanceof SimpleNormalCompletion && na instanceof SimpleNormalCompletion)
return new SimpleNormalCompletion(
AbstractValue.createFromConditionalOp(completion.value.$Realm, completion.joinCondition, nc.value, na.value)
);
invariant(nc instanceof AbruptCompletion || nc instanceof NormalCompletion);
invariant(na instanceof AbruptCompletion || na instanceof NormalCompletion);
return new JoinedNormalAndAbruptCompletions(completion.joinCondition, nc, na);
}
invariant(nc instanceof AbruptCompletion || nc instanceof NormalCompletion);
invariant(na instanceof AbruptCompletion || na instanceof NormalCompletion);
let result = new JoinedNormalAndAbruptCompletions(completion.joinCondition, nc, na);
invariant(normalizedComposedWith instanceof JoinedNormalAndAbruptCompletions);
result.composedWith = normalizedComposedWith;
return result;
}
return completion;
}
} }
// Normal completions are returned just like spec completions // Normal completions are returned just like spec completions
export class NormalCompletion extends Completion { export class NormalCompletion extends Completion {
constructor(value: Value, precedingEffects: void | Effects, location: ?BabelNodeSourceLocation, target?: ?string) { constructor(value: Value, location: ?BabelNodeSourceLocation, target?: ?string) {
super(value, precedingEffects, location, target); super(value, location, target);
invariant(this.constructor !== NormalCompletion, "NormalCompletion is an abstract base class"); invariant(this.constructor !== NormalCompletion, "NormalCompletion is an abstract base class");
} }
shallowCloneWithoutEffects(): NormalCompletion {
invariant(false, "NormalCompletion.shallowCloneWithoutEffects is an abstract base method and should not be called");
}
} }
// SimpleNormalCompletions are returned just like spec completions. This class exists as the parallel for // SimpleNormalCompletions are returned just like spec completions.
// PossiblyNormalCompletion to make comparisons easier. // They chiefly exist for use in joined completions.
export class SimpleNormalCompletion extends NormalCompletion { export class SimpleNormalCompletion extends NormalCompletion {}
shallowCloneWithoutEffects(): SimpleNormalCompletion {
return new SimpleNormalCompletion(this.value, undefined, this.location, this.target);
}
}
// Abrupt completions are thrown as exeptions, to make it a easier // Abrupt completions are thrown as exeptions, to make it a easier
// to quickly get to the matching high level construct. // to quickly get to the matching high level construct.
export class AbruptCompletion extends Completion { export class AbruptCompletion extends Completion {
constructor(value: Value, precedingEffects: void | Effects, location: ?BabelNodeSourceLocation, target?: ?string) { constructor(value: Value, location: ?BabelNodeSourceLocation, target?: ?string) {
super(value, precedingEffects, location, target); super(value, location, target);
invariant(this.constructor !== AbruptCompletion, "AbruptCompletion is an abstract base class"); 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 { export class ThrowCompletion extends AbruptCompletion {
constructor( constructor(value: Value, location: ?BabelNodeSourceLocation, nativeStack?: ?string) {
value: Value, super(value, location);
precedingEffects: void | Effects,
location: ?BabelNodeSourceLocation,
nativeStack?: ?string
) {
super(value, precedingEffects, location);
this.nativeStack = nativeStack || new Error().stack; this.nativeStack = nativeStack || new Error().stack;
let realm = value.$Realm; let realm = value.$Realm;
if (realm.isInPureScope()) { if (realm.isInPureScope()) {
@ -93,48 +123,32 @@ export class ThrowCompletion extends AbruptCompletion {
} }
nativeStack: string; nativeStack: string;
shallowCloneWithoutEffects(): ThrowCompletion {
return new ThrowCompletion(this.value, undefined, this.location, this.nativeStack);
}
} }
export class ContinueCompletion extends AbruptCompletion { export class ContinueCompletion extends AbruptCompletion {
constructor(value: Value, precedingEffects: void | Effects, location: ?BabelNodeSourceLocation, target: ?string) { constructor(value: Value, location: ?BabelNodeSourceLocation, target: ?string) {
super(value, precedingEffects, location, target || null); super(value, location, target || null);
}
shallowCloneWithoutEffects(): ContinueCompletion {
return new ContinueCompletion(this.value, undefined, this.location, this.target);
} }
} }
export class BreakCompletion extends AbruptCompletion { export class BreakCompletion extends AbruptCompletion {
constructor(value: Value, precedingEffects: void | Effects, location: ?BabelNodeSourceLocation, target: ?string) { constructor(value: Value, location: ?BabelNodeSourceLocation, target: ?string) {
super(value, precedingEffects, location, target || null); super(value, location, target || null);
}
shallowCloneWithoutEffects(): BreakCompletion {
return new BreakCompletion(this.value, undefined, this.location, this.target);
} }
} }
export class ReturnCompletion extends AbruptCompletion { export class ReturnCompletion extends AbruptCompletion {
constructor(value: Value, precedingEffects: void | Effects, location: ?BabelNodeSourceLocation) { constructor(value: Value, location: ?BabelNodeSourceLocation) {
super(value, precedingEffects, location); super(value, location);
if (value instanceof EmptyValue) { if (value instanceof EmptyValue) {
this.value = value.$Realm.intrinsics.undefined; this.value = value.$Realm.intrinsics.undefined;
} }
} }
shallowCloneWithoutEffects(): ReturnCompletion {
return new ReturnCompletion(this.value, undefined, this.location);
}
} }
export class ForkedAbruptCompletion extends AbruptCompletion { export class JoinedAbruptCompletions extends AbruptCompletion {
constructor(realm: Realm, joinCondition: AbstractValue, consequent: AbruptCompletion, alternate: AbruptCompletion) { constructor(joinCondition: AbstractValue, consequent: AbruptCompletion, alternate: AbruptCompletion) {
super(realm.intrinsics.empty, undefined, consequent.location); super(joinCondition.$Realm.intrinsics.empty, consequent.location);
this.joinCondition = joinCondition; this.joinCondition = joinCondition;
this.consequent = consequent; this.consequent = consequent;
this.alternate = alternate; this.alternate = alternate;
@ -144,36 +158,16 @@ export class ForkedAbruptCompletion extends AbruptCompletion {
consequent: AbruptCompletion; consequent: AbruptCompletion;
alternate: AbruptCompletion; alternate: AbruptCompletion;
shallowCloneWithoutEffects(): ForkedAbruptCompletion { containsSelectedCompletion(selector: Completion => boolean): boolean {
return new ForkedAbruptCompletion(this.value.$Realm, this.joinCondition, this.consequent, this.alternate); if (selector(this.consequent)) return true;
} if (selector(this.alternate)) return true;
if (this.consequent instanceof JoinedAbruptCompletions) {
// For convenience, this.consequent.effects should always be defined, but accessing it directly requires if (this.consequent.containsSelectedCompletion(selector)) return true;
// verifying that with an invariant. }
get consequentEffects(): Effects { if (this.alternate instanceof JoinedAbruptCompletions) {
invariant(this.consequent.effects); if (this.alternate.containsSelectedCompletion(selector)) return true;
return this.consequent.effects; }
} return false;
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;
} }
toDisplayString(): string { toDisplayString(): string {
@ -182,112 +176,46 @@ export class ForkedAbruptCompletion extends AbruptCompletion {
superString + " c: [" + this.consequent.toDisplayString() + "] a: [" + this.alternate.toDisplayString() + "]]" 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 // This should never be thrown, therefore it is treated as a NormalCompletion even though it is also Abrupt.
// and are thus never thrown. At the end of a try block or loop body, however, export class JoinedNormalAndAbruptCompletions extends NormalCompletion {
// action must be taken to deal with the possibly abrupt case of the completion.
export class PossiblyNormalCompletion extends NormalCompletion {
constructor( constructor(
value: Value,
joinCondition: AbstractValue, joinCondition: AbstractValue,
consequent: Completion, consequent: AbruptCompletion | NormalCompletion,
alternate: Completion, alternate: AbruptCompletion | NormalCompletion
savedPathConditions: Array<AbstractValue>,
savedEffects: void | Effects = undefined
) { ) {
invariant(consequent instanceof NormalCompletion || alternate instanceof NormalCompletion); super(consequent instanceof NormalCompletion ? consequent.value : alternate.value, consequent.location);
super(value, undefined, consequent.location);
this.joinCondition = joinCondition; this.joinCondition = joinCondition;
this.consequent = consequent; this.consequent = consequent;
this.alternate = alternate; this.alternate = alternate;
this.savedEffects = savedEffects; this.pathConditionsAtCreation = [].concat(joinCondition.$Realm.pathConditions);
this.savedPathConditions = savedPathConditions;
} }
joinCondition: AbstractValue; joinCondition: AbstractValue;
consequent: Completion; consequent: AbruptCompletion | NormalCompletion;
alternate: Completion; alternate: AbruptCompletion | NormalCompletion;
composedWith: void | JoinedNormalAndAbruptCompletions;
pathConditionsAtCreation: Array<AbstractValue>;
savedEffects: void | Effects; savedEffects: void | Effects;
// The path conditions that applied at the time of the oldest fork that caused this completion to arise.
savedPathConditions: Array<AbstractValue>;
shallowCloneWithoutEffects(): PossiblyNormalCompletion { containsSelectedCompletion(selector: Completion => boolean): boolean {
let consequentEffects = this.consequentEffects; if (this.composedWith !== undefined && this.composedWith.containsSelectedCompletion(selector)) return true;
let alternateEffects = this.alternateEffects; if (selector(this.consequent)) return true;
invariant(this.consequent === consequentEffects.result); if (selector(this.alternate)) return true;
invariant(this.alternate === alternateEffects.result); if (
return new PossiblyNormalCompletion( this.consequent instanceof JoinedAbruptCompletions ||
this.value, this.consequent instanceof JoinedNormalAndAbruptCompletions
this.joinCondition, ) {
this.consequent, if (this.consequent.containsSelectedCompletion(selector)) return true;
this.alternate, }
this.savedPathConditions, if (
this.savedEffects this.alternate instanceof JoinedAbruptCompletions ||
); this.alternate instanceof JoinedNormalAndAbruptCompletions
} ) {
if (this.alternate.containsSelectedCompletion(selector)) return true;
// For convenience, this.consequent.effects should always be defined, but accessing it directly requires }
// verifying that with an invariant. return false;
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;
} }
toDisplayString(): string { toDisplayString(): string {
@ -296,46 +224,4 @@ export class PossiblyNormalCompletion extends NormalCompletion {
superString + " c: [" + this.consequent.toDisplayString() + "] a: [" + this.alternate.toDisplayString() + "]]" 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;
}
} }

View File

@ -16,7 +16,6 @@ import initializeGlobal from "./intrinsics/ecma262/global.js";
import type { RealmOptions } from "./options.js"; import type { RealmOptions } from "./options.js";
import { RealmStatistics } from "./statistics.js"; import { RealmStatistics } from "./statistics.js";
import * as evaluators from "./evaluators/index.js"; import * as evaluators from "./evaluators/index.js";
import * as partialEvaluators from "./partial-evaluators/index.js";
import { Environment, DebugReproManager } from "./singletons.js"; import { Environment, DebugReproManager } from "./singletons.js";
import { ObjectValue } from "./values/index.js"; import { ObjectValue } from "./values/index.js";
import { DebugServer } from "./debugger/server/Debugger.js"; import { DebugServer } from "./debugger/server/Debugger.js";
@ -50,7 +49,6 @@ export default function(
r.$GlobalObject = new ObjectValue(r, i.ObjectPrototype, "global"); r.$GlobalObject = new ObjectValue(r, i.ObjectPrototype, "global");
initializeGlobal(r); initializeGlobal(r);
for (let name in evaluators) r.evaluators[name] = evaluators[name]; 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.simplifyAndRefineAbstractValue = simplifyAndRefineAbstractValue.bind(null, r, false);
r.simplifyAndRefineAbstractCondition = simplifyAndRefineAbstractValue.bind(null, r, true); r.simplifyAndRefineAbstractCondition = simplifyAndRefineAbstractValue.bind(null, r, true);
r.$GlobalEnv = Environment.NewGlobalEnvironment(r, r.$GlobalObject, r.$GlobalObject); r.$GlobalEnv = Environment.NewGlobalEnvironment(r, r.$GlobalObject, r.$GlobalObject);

View File

@ -15,6 +15,7 @@ import {
AbstractValue, AbstractValue,
BooleanValue, BooleanValue,
ConcreteValue, ConcreteValue,
EmptyValue,
FunctionValue, FunctionValue,
NumberValue, NumberValue,
IntegralValue, IntegralValue,
@ -33,10 +34,15 @@ export default class TypesDomain {
this._type = type === Value ? undefined : type; this._type = type === Value ? undefined : type;
} }
static topVal: TypesDomain = new TypesDomain(undefined); static topVal: TypesDomain;
static bottomVal: TypesDomain;
_type: void | typeof Value; _type: void | typeof Value;
isBottom(): boolean {
return this._type instanceof EmptyValue;
}
isTop(): boolean { isTop(): boolean {
return this._type === undefined; 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 // return the type of the result in the case where there is no exception
static binaryOp(op: BabelBinaryOperator, left: TypesDomain, right: TypesDomain): TypesDomain { static binaryOp(op: BabelBinaryOperator, left: TypesDomain, right: TypesDomain): TypesDomain {
if (left.isBottom() || right.isBottom()) return TypesDomain.bottomVal;
let lType = left._type; let lType = left._type;
let rType = right._type; let rType = right._type;
let resultType = Value; let resultType = Value;
@ -105,8 +112,9 @@ export default class TypesDomain {
} }
joinWith(t: typeof Value): TypesDomain { joinWith(t: typeof Value): TypesDomain {
if (this.isBottom()) return t === EmptyValue ? TypesDomain.bottomVal : new TypesDomain(t);
let type = this.getType(); 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)) { if (Value.isTypeCompatibleWith(type, NumberValue) && Value.isTypeCompatibleWith(t, NumberValue)) {
return new TypesDomain(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 // 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 // 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 { static unaryOp(op: BabelUnaryOperator, operand: TypesDomain): TypesDomain {
if (operand.isBottom()) return TypesDomain.bottomVal;
const type = operand._type; const type = operand._type;
let resultType = Value; let resultType = Value;
switch (op) { switch (op) {

View File

@ -54,7 +54,8 @@ export default class ValuesDomain {
this._elements = values; this._elements = values;
} }
static topVal = new ValuesDomain(undefined); static topVal: ValuesDomain;
static bottomVal: ValuesDomain;
_elements: void | Set<ConcreteValue>; _elements: void | Set<ConcreteValue>;
@ -79,6 +80,10 @@ export default class ValuesDomain {
return elems.has(x); return elems.has(x);
} }
isBottom(): boolean {
return this._elements !== undefined && this._elements.size === 0;
}
isTop(): boolean { isTop(): boolean {
return this._elements === undefined; 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 // 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. // Cartesian product of the value sets of the operands.
static binaryOp(realm: Realm, op: BabelBinaryOperator, left: ValuesDomain, right: ValuesDomain): ValuesDomain { static binaryOp(realm: Realm, op: BabelBinaryOperator, left: ValuesDomain, right: ValuesDomain): ValuesDomain {
if (left.isBottom() || right.isBottom()) return ValuesDomain.bottomVal;
let leftElements = left._elements; let leftElements = left._elements;
let rightElements = right._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. // 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 { static unaryOp(realm: Realm, op: BabelUnaryOperator, operandValues: ValuesDomain): ValuesDomain {
if (operandValues.isBottom()) return ValuesDomain.bottomVal;
let operandElements = operandValues._elements; let operandElements = operandValues._elements;
if (operandElements === undefined) return ValuesDomain.topVal; if (operandElements === undefined) return ValuesDomain.topVal;
let resultSet = new Set(); let resultSet = new Set();
@ -503,6 +510,7 @@ export default class ValuesDomain {
invariant(y instanceof ConcreteValue); invariant(y instanceof ConcreteValue);
union.add(y); union.add(y);
} }
if (union.size === 0) return ValuesDomain.bottomVal;
return new ValuesDomain(union); return new ValuesDomain(union);
} }
@ -517,6 +525,7 @@ export default class ValuesDomain {
invariant(v1 instanceof ConcreteValue); invariant(v1 instanceof ConcreteValue);
invariant(v2 instanceof ConcreteValue); invariant(v2 instanceof ConcreteValue);
if (v1 === v2) intersection.add(v1); if (v1 === v2) intersection.add(v1);
if (intersection.size === 0) return ValuesDomain.bottomVal;
return new ValuesDomain(intersection); return new ValuesDomain(intersection);
} }
@ -532,11 +541,12 @@ export default class ValuesDomain {
invariant(y instanceof ConcreteValue); invariant(y instanceof ConcreteValue);
if (elements === undefined || elements.has(y)) intersection.add(y); if (elements === undefined || elements.has(y)) intersection.add(y);
} }
if (intersection.size === 0) return ValuesDomain.bottomVal;
return new ValuesDomain(intersection); return new ValuesDomain(intersection);
} }
promoteEmptyToUndefined(): ValuesDomain { promoteEmptyToUndefined(): ValuesDomain {
if (this.isTop()) return this; if (this.isTop() || this.isBottom()) return this;
let newSet = new Set(); let newSet = new Set();
for (let cval of this.getElements()) { for (let cval of this.getElements()) {
if (cval instanceof EmptyValue) newSet.add(cval.$Realm.intrinsics.undefined); if (cval instanceof EmptyValue) newSet.add(cval.$Realm.intrinsics.undefined);

View File

@ -15,17 +15,14 @@ import type {
BabelNodeFile, BabelNodeFile,
BabelNodeLVal, BabelNodeLVal,
BabelNodePosition, BabelNodePosition,
BabelNodeStatement,
BabelNodeSourceLocation, BabelNodeSourceLocation,
} from "@babel/types"; } from "@babel/types";
import type { Realm } from "./realm.js"; 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 * as t from "@babel/types";
import { AbruptCompletion, Completion, ThrowCompletion } from "./completions.js"; import { AbruptCompletion, Completion, ThrowCompletion } from "./completions.js";
import { CompilerDiagnostic, FatalError } from "./errors.js"; import { CompilerDiagnostic, FatalError } from "./errors.js";
import { defaultOptions } from "./options.js";
import type { PartialEvaluatorOptions } from "./options";
import { ExecutionContext } from "./realm.js"; import { ExecutionContext } from "./realm.js";
import { import {
AbstractValue, AbstractValue,
@ -41,7 +38,6 @@ import {
UndefinedValue, UndefinedValue,
Value, Value,
} from "./values/index.js"; } from "./values/index.js";
import generate from "@babel/generator";
import parse from "./utils/parse.js"; import parse from "./utils/parse.js";
import invariant from "./invariant.js"; import invariant from "./invariant.js";
import traverseFast from "./utils/traverse-fast.js"; import traverseFast from "./utils/traverse-fast.js";
@ -1077,35 +1073,6 @@ export class LexicalEnvironment {
Properties.PutValue(this.realm, globalValue, rvalue); Properties.PutValue(this.realm, globalValue, rvalue);
} }
partiallyEvaluateCompletionDeref(
ast: BabelNode,
strictCode: boolean,
metadata?: any
): [Completion | Value, BabelNode, Array<BabelNodeStatement>] {
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<BabelNodeStatement>] {
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 { evaluateCompletionDeref(ast: BabelNode, strictCode: boolean, metadata?: any): AbruptCompletion | Value {
let result = this.evaluateCompletion(ast, strictCode, metadata); let result = this.evaluateCompletion(ast, strictCode, metadata);
if (result instanceof Reference) result = Environment.GetValue(this.realm, result); if (result instanceof Reference) result = Environment.GetValue(this.realm, result);
@ -1216,37 +1183,6 @@ export class LexicalEnvironment {
return [Environment.GetValue(this.realm, res), code]; return [Environment.GetValue(this.realm, res), code];
} }
executePartialEvaluator(
sources: Array<SourceFile>,
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( execute(
code: string, code: string,
filename: string, filename: string,
@ -1434,20 +1370,6 @@ export class LexicalEnvironment {
if (result instanceof Reference) result = Environment.GetValue(this.realm, result); if (result instanceof Reference) result = Environment.GetValue(this.realm, result);
return result; return result;
} }
partiallyEvaluate(
ast: BabelNode,
strictCode: boolean,
metadata?: any
): [Completion | Reference | Value, BabelNode, Array<BabelNodeStatement>] {
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 // ECMA262 6.2.3

View File

@ -26,7 +26,6 @@ import {
UndefinedValue, UndefinedValue,
Value, Value,
} from "../values/index.js"; } from "../values/index.js";
import { AbruptCompletion, PossiblyNormalCompletion, SimpleNormalCompletion } from "../completions.js";
import { Environment, Havoc, To } from "../singletons.js"; import { Environment, Havoc, To } from "../singletons.js";
import type { BabelBinaryOperator, BabelNodeBinaryExpression, BabelNodeSourceLocation } from "@babel/types"; import type { BabelBinaryOperator, BabelNodeBinaryExpression, BabelNodeSourceLocation } from "@babel/types";
import { createOperationDescriptor } from "../utils/generator.js"; import { createOperationDescriptor } from "../utils/generator.js";
@ -284,23 +283,8 @@ export function computeBinary(
} }
if (isPure && effects) { 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); realm.applyEffects(effects);
let completion = effects.result; return realm.returnOrThrowCompletion(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;
} }
// If this ended up reporting an error, it might not be pure, so we'll leave it in // If this ended up reporting an error, it might not be pure, so we'll leave it in

View File

@ -21,5 +21,5 @@ export default function(
env: LexicalEnvironment, env: LexicalEnvironment,
realm: Realm realm: Realm
): Value { ): 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);
} }

View File

@ -10,7 +10,6 @@
/* @flow */ /* @flow */
import { CompilerDiagnostic, FatalError } from "../errors.js"; import { CompilerDiagnostic, FatalError } from "../errors.js";
import { AbruptCompletion, Completion, PossiblyNormalCompletion, SimpleNormalCompletion } from "../completions.js";
import type { Realm } from "../realm.js"; import type { Realm } from "../realm.js";
import { type LexicalEnvironment, type BaseValue, mightBecomeAnObject } from "../environment.js"; import { type LexicalEnvironment, type BaseValue, mightBecomeAnObject } from "../environment.js";
import { EnvironmentRecord } from "../environment.js"; import { EnvironmentRecord } from "../environment.js";
@ -217,29 +216,9 @@ function callBothFunctionsAndJoinTheirEffects(
"callBothFunctionsAndJoinTheirEffects/2" "callBothFunctionsAndJoinTheirEffects/2"
); );
let r1 = e1.result; let joinedEffects = Join.joinEffects(cond, e1, e2);
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.
realm.applyEffects(joinedEffects); realm.applyEffects(joinedEffects);
return realm.returnOrThrowCompletion(joinedEffects.result);
// return or throw completion
if (completion instanceof AbruptCompletion) throw completion;
invariant(completion instanceof Value);
return completion;
} }
function generateRuntimeCall( function generateRuntimeCall(
@ -308,22 +287,8 @@ function tryToEvaluateCallOrLeaveAsAbstract(
} finally { } finally {
realm.suppressDiagnostics = savedSuppressDiagnostics; 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); realm.applyEffects(effects);
let completion = effects.result; return realm.returnOrThrowCompletion(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;
} }
function EvaluateCall( function EvaluateCall(

View File

@ -21,5 +21,5 @@ export default function(
env: LexicalEnvironment, env: LexicalEnvironment,
realm: Realm realm: Realm
): Value { ): 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);
} }

View File

@ -15,8 +15,8 @@ import { FatalError } from "../errors.js";
import { Value } from "../values/index.js"; import { Value } from "../values/index.js";
import { EmptyValue } from "../values/index.js"; import { EmptyValue } from "../values/index.js";
import { UpdateEmpty } from "../methods/index.js"; import { UpdateEmpty } from "../methods/index.js";
import { LoopContinues, InternalGetResultValue, TryToApplyEffectsOfJoiningBranches } from "./ForOfStatement.js"; import { LoopContinues, InternalGetResultValue } from "./ForOfStatement.js";
import { AbruptCompletion, BreakCompletion, ForkedAbruptCompletion, SimpleNormalCompletion } from "../completions.js"; import { AbruptCompletion, BreakCompletion, SimpleNormalCompletion } from "../completions.js";
import { Environment, To } from "../singletons.js"; import { Environment, To } from "../singletons.js";
import invariant from "../invariant.js"; import invariant from "../invariant.js";
import type { BabelNodeDoWhileStatement } from "@babel/types"; import type { BabelNodeDoWhileStatement } from "@babel/types";
@ -38,9 +38,8 @@ export default function(
while (true) { while (true) {
// a. Let stmt be the result of evaluating Statement. // a. Let stmt be the result of evaluating Statement.
let stmt = env.evaluateCompletion(body, strictCode); 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); 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)). // b. If LoopContinues(stmt, labelSet) is false, return Completion(UpdateEmpty(stmt, V)).
if (LoopContinues(realm, stmt, labelSet) === false) { if (LoopContinues(realm, stmt, labelSet) === false) {

View File

@ -14,7 +14,14 @@ import type { LexicalEnvironment } from "../environment.js";
import { CompilerDiagnostic, FatalError } from "../errors.js"; import { CompilerDiagnostic, FatalError } from "../errors.js";
import { DeclarativeEnvironmentRecord } from "../environment.js"; import { DeclarativeEnvironmentRecord } from "../environment.js";
import { Reference } 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 { import {
AbstractObjectValue, AbstractObjectValue,
AbstractValue, AbstractValue,
@ -33,7 +40,7 @@ import {
DestructuringAssignmentEvaluation, DestructuringAssignmentEvaluation,
GetIterator, GetIterator,
} from "../methods/index.js"; } from "../methods/index.js";
import { Environment, Join, Properties, To } from "../singletons.js"; import { Environment, Properties, To } from "../singletons.js";
import type { import type {
BabelNode, BabelNode,
BabelNodeForOfStatement, BabelNodeForOfStatement,
@ -45,37 +52,23 @@ import type {
export type IterationKind = "iterate" | "enumerate"; export type IterationKind = "iterate" | "enumerate";
export type LhsKind = "lexicalBinding" | "varBinding" | "assignment"; export type LhsKind = "lexicalBinding" | "varBinding" | "assignment";
export function InternalGetResultValue(realm: Realm, result: Value | AbruptCompletion): Value { export function InternalGetResultValue(realm: Realm, result: Value | Completion): Value {
if (result instanceof AbruptCompletion) { if (result instanceof Completion) {
return result.value; return result.value;
} else { } else {
return result; 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 // ECMA262 13.7.1.2
export function LoopContinues(realm: Realm, completion: Value | AbruptCompletion, labelSet: ?Array<string>): boolean { export function LoopContinues(realm: Realm, completion: Value | Completion, labelSet: ?Array<string>): boolean {
// 1. If completion.[[Type]] is normal, return true. // 1. If completion.[[Type]] is normal, return true.
if (completion instanceof Value) return true; if (completion instanceof Value || completion instanceof NormalCompletion) return true;
invariant(completion instanceof AbruptCompletion); if (completion instanceof JoinedAbruptCompletions) {
return (
LoopContinues(realm, completion.consequent, labelSet) || LoopContinues(realm, completion.alternate, labelSet)
);
}
// 2. If completion.[[Type]] is not continue, return false. // 2. If completion.[[Type]] is not continue, return false.
if (!(completion instanceof ContinueCompletion)) return false; if (!(completion instanceof ContinueCompletion)) return false;
@ -167,7 +160,7 @@ export function ForInOfHeadEvaluation(
// a. If exprValue.[[Value]] is null or undefined, then // a. If exprValue.[[Value]] is null or undefined, then
if (exprValue instanceof NullValue || exprValue instanceof UndefinedValue) { if (exprValue instanceof NullValue || exprValue instanceof UndefinedValue) {
// i. Return Completion{[[Type]]: break, [[Value]]: empty, [[Target]]: empty}. // 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). // b. Let obj be ToObject(exprValue).
@ -346,7 +339,6 @@ export function ForInOfBodyEvaluation(
// i. Let result be the result of evaluating stmt. // i. Let result be the result of evaluating stmt.
let result = env.evaluateCompletion(stmt, strictCode); let result = env.evaluateCompletion(stmt, strictCode);
invariant(result instanceof Value || result instanceof AbruptCompletion); 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. // j. Set the running execution context's LexicalEnvironment to oldEnv.

View File

@ -10,7 +10,7 @@
/* @flow */ /* @flow */
import type { LexicalEnvironment } from "../environment.js"; import type { LexicalEnvironment } from "../environment.js";
import type { Realm } from "../realm.js"; import { Realm } from "../realm.js";
import { import {
AbstractValue, AbstractValue,
Value, Value,
@ -23,19 +23,19 @@ import {
BreakCompletion, BreakCompletion,
Completion, Completion,
ContinueCompletion, ContinueCompletion,
ForkedAbruptCompletion, JoinedAbruptCompletions,
PossiblyNormalCompletion, JoinedNormalAndAbruptCompletions,
ReturnCompletion, ReturnCompletion,
SimpleNormalCompletion,
ThrowCompletion, ThrowCompletion,
SimpleNormalCompletion,
} from "../completions.js"; } from "../completions.js";
import traverse from "@babel/traverse"; import traverse from "@babel/traverse";
import type { BabelTraversePath } from "@babel/traverse"; import type { BabelTraversePath } from "@babel/traverse";
import { TypesDomain, ValuesDomain } from "../domains/index.js"; import { TypesDomain, ValuesDomain } from "../domains/index.js";
import { CompilerDiagnostic, FatalError } from "../errors.js"; import { CompilerDiagnostic, FatalError } from "../errors.js";
import { UpdateEmpty } from "../methods/index.js"; import { UpdateEmpty } from "../methods/index.js";
import { LoopContinues, InternalGetResultValue, TryToApplyEffectsOfJoiningBranches } from "./ForOfStatement.js"; import { LoopContinues, InternalGetResultValue } from "./ForOfStatement.js";
import { Environment, Functions, Havoc, Join, To } from "../singletons.js"; import { Environment, Functions, Havoc, To } from "../singletons.js";
import invariant from "../invariant.js"; import invariant from "../invariant.js";
import * as t from "@babel/types"; import * as t from "@babel/types";
import type { BabelNodeExpression, BabelNodeForStatement, BabelNodeBlockStatement } from "@babel/types"; import type { BabelNodeExpression, BabelNodeForStatement, BabelNodeBlockStatement } from "@babel/types";
@ -103,6 +103,7 @@ function ForBodyEvaluation(
// 3. Repeat // 3. Repeat
while (true) { while (true) {
let result;
// a. If test is not [empty], then // a. If test is not [empty], then
if (test) { if (test) {
// i. Let testRef be the result of evaluating 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). // iii. If ToBoolean(testValue) is false, return NormalCompletion(V).
if (!To.ToBooleanPartial(realm, testValue)) { if (!To.ToBooleanPartial(realm, testValue)) {
// joinAllLoopExits does not handle labeled break/continue, so only use it when doing AI result = Functions.incorporateSavedCompletion(realm, V);
if (realm.useAbstractInterpretation) return joinAllLoopExits(V); if (result instanceof JoinedNormalAndAbruptCompletions) {
let selector = c => c instanceof BreakCompletion && !c.target;
result = Completion.normalizeSelectedCompletions(selector, result);
result = realm.composeWithSavedCompletion(result);
}
return V; return V;
} }
} }
// b. Let result be the result of evaluating stmt. // 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); 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)). // c. If LoopContinues(result, labelSet) is false, return Completion(UpdateEmpty(result, V)).
if (!LoopContinues(realm, result, labelSet)) { if (!LoopContinues(realm, result, labelSet)) {
invariant(result instanceof AbruptCompletion); 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 // ECMA262 13.1.7
if (result instanceof BreakCompletion) { if (result instanceof BreakCompletion) {
if (!result.target) return (UpdateEmpty(realm, result, V): any).value; 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); return realm.returnOrThrowCompletion(result);
} else if (realm.useAbstractInterpretation) {
// This is a join point for conditional continue completions lurking in realm.savedCompletion
if (containsContinueCompletion(realm.savedCompletion)) {
result = joinAllLoopContinues(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]]. // d. If result.[[Value]] is not empty, let V be result.[[Value]].
let resultValue = InternalGetResultValue(realm, result); let resultValue = InternalGetResultValue(realm, result);
@ -161,14 +169,14 @@ function ForBodyEvaluation(
// ii. Perform ? GetValue(incRef). // ii. Perform ? GetValue(incRef).
Environment.GetValue(realm, incRef); Environment.GetValue(realm, incRef);
} else if (realm.useAbstractInterpretation) { } 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 // 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 // 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 // against loops that are actually infinite. However, because there may be so
// many forked execution paths, and they're non linear, then it might // many forked execution paths, and they're non linear, then it might
// computationally lead to a something that seems like an infinite loop. // computationally lead to a something that seems like an infinite loop.
possibleInfiniteLoopIterations++; possibleInfiniteLoopIterations++;
if (possibleInfiniteLoopIterations > 100) { if (possibleInfiniteLoopIterations > 12) {
failIfContainsBreakOrReturnOrThrowCompletion(realm.savedCompletion); failIfContainsBreakOrReturnOrThrowCompletion(realm.savedCompletion);
} }
} }
@ -187,136 +195,11 @@ function ForBodyEvaluation(
realm.handleError(diagnostic); realm.handleError(diagnostic);
throw new FatalError(); throw new FatalError();
} }
if (c instanceof PossiblyNormalCompletion || c instanceof ForkedAbruptCompletion) { if (c instanceof JoinedAbruptCompletions || c instanceof JoinedNormalAndAbruptCompletions) {
failIfContainsBreakOrReturnOrThrowCompletion(c.consequent); failIfContainsBreakOrReturnOrThrowCompletion(c.consequent);
failIfContainsBreakOrReturnOrThrowCompletion(c.alternate); 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 = { let BailOutWrapperClosureRefVisitor = {
@ -490,22 +373,8 @@ function tryToEvaluateForStatementOrLeaveAsAbstract(
} finally { } finally {
realm.suppressDiagnostics = savedSuppressDiagnostics; 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); realm.applyEffects(effects);
let completion = effects.result; return realm.returnOrThrowCompletion(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;
} }
// ECMA262 13.7.4.7 // ECMA262 13.7.4.7

View File

@ -13,7 +13,12 @@ import type { Realm } from "../realm.js";
import type { LexicalEnvironment } from "../environment.js"; import type { LexicalEnvironment } from "../environment.js";
import { Value } from "../values/index.js"; import { Value } from "../values/index.js";
import type { Reference } from "../environment.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 type { BabelNode, BabelNodeLabeledStatement, BabelNodeVariableDeclaration } from "@babel/types";
import invariant from "../invariant.js"; import invariant from "../invariant.js";
@ -44,6 +49,15 @@ function LabelledEvaluation(
if (stmtResult instanceof BreakCompletion && stmtResult.target === label) { if (stmtResult instanceof BreakCompletion && stmtResult.target === label) {
// a. Let stmtResult be NormalCompletion(stmtResult.[[Value]]). // a. Let stmtResult be NormalCompletion(stmtResult.[[Value]]).
normalCompletionStmtResult = 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 { } else {
// 5. Return Completion(stmtResult). // 5. Return Completion(stmtResult).
throw stmtResult; throw stmtResult;

View File

@ -11,7 +11,7 @@
import type { Realm } from "../realm.js"; import type { Realm } from "../realm.js";
import { Effects } 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 { InfeasiblePathError } from "../errors.js";
import { construct_empty_effects } from "../realm.js"; import { construct_empty_effects } from "../realm.js";
import type { LexicalEnvironment } from "../environment.js"; import type { LexicalEnvironment } from "../environment.js";
@ -89,22 +89,14 @@ export default function(
// use lval as is for the join condition. // use lval as is for the join condition.
let joinedEffects; let joinedEffects;
if (ast.operator === "&&") { if (ast.operator === "&&") {
joinedEffects = Join.joinForkOrChoose( joinedEffects = Join.joinEffects(
realm, lcond,
lval, new Effects(result2, generator2, modifiedBindings2, modifiedProperties2, createdObjects2),
new Effects(
result2.shallowCloneWithoutEffects(),
generator2,
modifiedBindings2,
modifiedProperties2,
createdObjects2
),
new Effects(new SimpleNormalCompletion(lval), generator1, modifiedBindings1, modifiedProperties1, createdObjects1) new Effects(new SimpleNormalCompletion(lval), generator1, modifiedBindings1, modifiedProperties1, createdObjects1)
); );
} else { } else {
joinedEffects = Join.joinForkOrChoose( joinedEffects = Join.joinEffects(
realm, lcond,
lval,
new Effects( new Effects(
new SimpleNormalCompletion(lval), new SimpleNormalCompletion(lval),
generator1, generator1,
@ -112,38 +104,18 @@ export default function(
modifiedProperties1, modifiedProperties1,
createdObjects1 createdObjects1
), ),
new Effects( new Effects(result2, generator2, modifiedBindings2, modifiedProperties2, createdObjects2)
result2.shallowCloneWithoutEffects(),
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 realm.applyEffects(joinedEffects);
if (completion instanceof AbruptCompletion) throw completion; let completion = realm.returnOrThrowCompletion(joinedEffects.result);
if (completion instanceof SimpleNormalCompletion) completion = completion.value; if (lval instanceof Value && result2.value instanceof Value) {
if (result2 instanceof SimpleNormalCompletion) result2 = result2.value; // joinEffects does the right thing for the side effects of the second expression but for the result the join
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
// produces a conditional expressions of the form (a ? b : a) for a && b and (a ? a : b) for a || b // 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 // 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. // 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; return completion;
} }

View File

@ -11,7 +11,6 @@
import type { Realm } from "../realm.js"; import type { Realm } from "../realm.js";
import type { LexicalEnvironment } from "../environment.js"; import type { LexicalEnvironment } from "../environment.js";
import { AbruptCompletion, PossiblyNormalCompletion, SimpleNormalCompletion } from "../completions.js";
import { TypesDomain, ValuesDomain } from "../domains/index.js"; import { TypesDomain, ValuesDomain } from "../domains/index.js";
import { ObjectValue, Value, AbstractObjectValue, AbstractValue } from "../values/index.js"; import { ObjectValue, Value, AbstractObjectValue, AbstractValue } from "../values/index.js";
import { Environment, Havoc } from "../singletons.js"; import { Environment, Havoc } from "../singletons.js";
@ -113,21 +112,8 @@ function tryToEvaluateConstructOrLeaveAsAbstract(
throw error; 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); realm.applyEffects(effects);
let completion = effects.result; let completion = realm.returnOrThrowCompletion(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 ObjectValue || completion instanceof AbstractObjectValue); invariant(completion instanceof ObjectValue || completion instanceof AbstractObjectValue);
return completion; return completion;
} }

View File

@ -9,13 +9,18 @@
/* @flow */ /* @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 { Realm } from "../realm.js";
import type { LexicalEnvironment } from "../environment.js"; import type { LexicalEnvironment } from "../environment.js";
import { Value, EmptyValue } from "../values/index.js"; import { Value, EmptyValue } from "../values/index.js";
import { GlobalEnvironmentRecord } from "../environment.js"; import { GlobalEnvironmentRecord } from "../environment.js";
import { Environment, Functions, Join } from "../singletons.js"; import { Environment, Functions, Join } from "../singletons.js";
import { Generator } from "../utils/generator.js";
import IsStrict from "../utils/strict.js"; import IsStrict from "../utils/strict.js";
import invariant from "../invariant.js"; import invariant from "../invariant.js";
import traverseFast from "../utils/traverse-fast.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); GlobalDeclarationInstantiation(realm, ast, env, strictCode);
let val; let val, res;
for (let node of ast.body) { for (let node of ast.body) {
if (node.type !== "FunctionDeclaration") { if (node.type !== "FunctionDeclaration") {
let res = env.evaluateCompletionDeref(node, strictCode); res = env.evaluateCompletionDeref(node, strictCode);
if (res instanceof AbruptCompletion) { if (res instanceof AbruptCompletion && !realm.useAbstractInterpretation) throw res;
if (!realm.useAbstractInterpretation) throw res; res = Functions.incorporateSavedCompletion(realm, res);
let generator = realm.generator; if (res instanceof Completion) {
invariant(generator !== undefined); emitThrowStatementsIfNeeded(res);
// We are about the leave this program and this presents a join point where all control flows if (res instanceof ThrowCompletion) return res.value; // Program ends here at runtime, so don't carry on
// converge into a single flow using the joined effects as the new state. res = res.value;
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;
} }
if (!(res instanceof EmptyValue)) { if (!(res instanceof EmptyValue)) {
val = res; 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 // 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. // converge into a single flow and the joined effects become the final state.
invariant(val === undefined || val instanceof Value);
if (val instanceof Value) { if (val instanceof Value) {
let res = Functions.incorporateSavedCompletion(realm, val); res = Functions.incorporateSavedCompletion(realm, val);
if (res instanceof PossiblyNormalCompletion) { if (res instanceof Completion) emitThrowStatementsIfNeeded(res);
// 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.
} }
invariant(val === undefined || val instanceof Value);
return val || realm.intrinsics.empty; 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
}
}
} }

View File

@ -28,5 +28,5 @@ export default function(
} else { } else {
arg = realm.intrinsics.undefined; arg = realm.intrinsics.undefined;
} }
throw new ReturnCompletion(arg, undefined, ast.loc); throw new ReturnCompletion(arg, ast.loc);
} }

View File

@ -11,21 +11,19 @@
import type { Realm } from "../realm.js"; import type { Realm } from "../realm.js";
import type { LexicalEnvironment } from "../environment.js"; import type { LexicalEnvironment } from "../environment.js";
import { CompilerDiagnostic, InfeasiblePathError } from "../errors.js"; import { InfeasiblePathError } from "../errors.js";
import { Reference } from "../environment.js";
import { computeBinary } from "./BinaryExpression.js"; import { computeBinary } from "./BinaryExpression.js";
import { import {
AbruptCompletion, AbruptCompletion,
BreakCompletion, BreakCompletion,
SimpleNormalCompletion,
PossiblyNormalCompletion,
Completion, Completion,
JoinedAbruptCompletions,
JoinedNormalAndAbruptCompletions,
} from "../completions.js"; } from "../completions.js";
import { InternalGetResultValue } from "./ForOfStatement.js"; import { InternalGetResultValue } from "./ForOfStatement.js";
import { EmptyValue, AbstractValue, Value } from "../values/index.js"; import { EmptyValue, AbstractValue, Value } from "../values/index.js";
import { StrictEqualityComparisonPartial, UpdateEmpty } from "../methods/index.js"; import { StrictEqualityComparisonPartial, UpdateEmpty } from "../methods/index.js";
import { Environment, Path, Join } from "../singletons.js"; import { Environment, Functions, Join, Path } from "../singletons.js";
import { FatalError } from "../errors.js";
import type { BabelNodeSwitchStatement, BabelNodeSwitchCase, BabelNodeExpression } from "@babel/types"; import type { BabelNodeSwitchStatement, BabelNodeSwitchCase, BabelNodeExpression } from "@babel/types";
import invariant from "../invariant.js"; import invariant from "../invariant.js";
@ -62,19 +60,10 @@ function AbstractCaseBlockEvaluation(
let c = cases[caseIndex]; let c = cases[caseIndex];
for (let i = 0; i < c.consequent.length; i += 1) { for (let i = 0; i < c.consequent.length; i += 1) {
let node = c.consequent[i]; let node = c.consequent[i];
let r = env.evaluateCompletion(node, strictCode); let r = env.evaluateCompletionDeref(node, strictCode);
invariant(!(r instanceof Reference));
if (r instanceof PossiblyNormalCompletion) { if (r instanceof JoinedNormalAndAbruptCompletions) {
// TODO correct handling of PossiblyNormal and AbruptCompletion r = realm.composeWithSavedCompletion(r);
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();
} }
result = UpdateEmpty(realm, r, result); result = UpdateEmpty(realm, r, result);
@ -84,19 +73,21 @@ function AbstractCaseBlockEvaluation(
if (result instanceof Completion) break; if (result instanceof Completion) break;
caseIndex++; caseIndex++;
} }
let sc = Functions.incorporateSavedCompletion(realm, result);
invariant(sc !== undefined);
result = sc;
if (result instanceof BreakCompletion) { if (result instanceof JoinedAbruptCompletions || result instanceof JoinedNormalAndAbruptCompletions) {
let selector = c => c instanceof BreakCompletion && !c.target;
let jc = AbstractValue.createJoinConditionForSelectedCompletions(selector, result);
let jv = AbstractValue.createFromConditionalOp(realm, jc, realm.intrinsics.empty, result.value);
result = Completion.normalizeSelectedCompletions(selector, result);
realm.composeWithSavedCompletion(result);
return jv;
} else if (result instanceof BreakCompletion) {
return result.value; return result.value;
} else if (result instanceof AbruptCompletion) { } else if (result instanceof AbruptCompletion) {
// TODO correct handling of PossiblyNormal and AbruptCompletion throw result;
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();
} else { } else {
invariant(result instanceof Value); invariant(result instanceof Value);
return result; return result;
@ -177,26 +168,10 @@ function AbstractCaseBlockEvaluation(
invariant(trueEffects !== undefined); invariant(trueEffects !== undefined);
invariant(falseEffects !== undefined); invariant(falseEffects !== undefined);
let joinedEffects = Join.joinForkOrChoose(realm, selectionResult, trueEffects, falseEffects); let joinedEffects = Join.joinEffects(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.
realm.applyEffects(joinedEffects); realm.applyEffects(joinedEffects);
// return or throw completion return realm.returnOrThrowCompletion(joinedEffects.result);
if (completion instanceof AbruptCompletion) throw completion;
if (completion instanceof SimpleNormalCompletion) {
completion = completion.value;
}
invariant(completion instanceof Value);
return completion;
} }
}; };

View File

@ -24,5 +24,5 @@ export default function(
): Value { ): Value {
let exprRef = env.evaluate(ast.argument, strictCode); let exprRef = env.evaluate(ast.argument, strictCode);
let exprValue = Environment.GetValue(realm, exprRef); let exprValue = Environment.GetValue(realm, exprRef);
throw new ThrowCompletion(exprValue, undefined, ast.loc); throw new ThrowCompletion(exprValue, ast.loc);
} }

View File

@ -9,172 +9,96 @@
/* @flow */ /* @flow */
import type { Effects, Realm } from "../realm.js"; import type { Realm } from "../realm.js";
import { type LexicalEnvironment } from "../environment.js"; import { type LexicalEnvironment } from "../environment.js";
import { import {
AbruptCompletion, AbruptCompletion,
ForkedAbruptCompletion, Completion,
PossiblyNormalCompletion, JoinedAbruptCompletions,
JoinedNormalAndAbruptCompletions,
ThrowCompletion, ThrowCompletion,
SimpleNormalCompletion,
NormalCompletion,
} from "../completions.js"; } from "../completions.js";
import { UpdateEmpty } from "../methods/index.js"; import { UpdateEmpty } from "../methods/index.js";
import { Functions, Join } from "../singletons.js"; import { InfeasiblePathError } from "../errors.js";
import { Value } from "../values/index.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 type { BabelNodeTryStatement } from "@babel/types";
import invariant from "../invariant.js"; import invariant from "../invariant.js";
export default function(ast: BabelNodeTryStatement, strictCode: boolean, env: LexicalEnvironment, realm: Realm): Value { 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()) { if (realm.isInPureScope()) {
// TODO(1264): This is used to issue a warning if we have abstract function calls in here. // 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 // 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. // then we might need it to know whether we should bother tracking error handling.
realm.isInPureTryStatement = true; realm.isInPureTryStatement = true;
} }
let blockRes; let blockRes = env.evaluateCompletionDeref(ast.block, strictCode);
try { // this is a join point for break and continue completions
blockRes = env.evaluateCompletionDeref(ast.block, strictCode); blockRes = Functions.incorporateSavedCompletion(realm, blockRes);
} finally { invariant(blockRes !== undefined);
realm.isInPureTryStatement = wasInPureTryStatement; realm.isInPureTryStatement = savedIsInPureTryStatement;
}
let handlerRes = blockRes; let result = blockRes;
let handler = ast.handler; let handler = ast.handler;
if (handler) { let selector = c => c instanceof ThrowCompletion;
// The start of the catch handler is a join point where all throw completions come together if (handler && blockRes instanceof Completion && blockRes.containsSelectedCompletion(selector)) {
blockRes = Functions.incorporateSavedCompletion(realm, blockRes);
if (blockRes instanceof ThrowCompletion) { if (blockRes instanceof ThrowCompletion) {
handlerRes = env.evaluateCompletionDeref(handler, strictCode, blockRes); result = env.evaluateCompletionDeref(handler, strictCode, blockRes);
// Note: The handler may have introduced new forks } else {
} else if (blockRes instanceof ForkedAbruptCompletion || blockRes instanceof PossiblyNormalCompletion) { invariant(blockRes instanceof JoinedAbruptCompletions || blockRes instanceof JoinedNormalAndAbruptCompletions);
if (blockRes instanceof PossiblyNormalCompletion) { // put the handler under a guard that excludes normal paths from entering it.
// The throw completions have not been joined and we are going to keep it that way. let joinCondition = AbstractValue.createJoinConditionForSelectedCompletions(selector, blockRes);
// The current state may have advanced since the time control forked into the various paths recorded in blockRes. try {
// Update the normal path and restore the global state to what it was at the time of the fork. let handlerEffects = Path.withCondition(joinCondition, () => {
let subsequentEffects = realm.getCapturedEffects(blockRes.value); invariant(blockRes instanceof Completion);
realm.stopEffectCaptureAndUndoEffects(blockRes); let joinedThrow = new ThrowCompletion(Join.joinValuesOfSelectedCompletions(selector, blockRes));
Join.updatePossiblyNormalCompletionWithSubsequentEffects(realm, blockRes, subsequentEffects); let handlerEval = () => env.evaluateCompletionDeref(handler, strictCode, joinedThrow);
return realm.evaluateForEffects(handlerEval, undefined, "joinTryBlockWithHandlers");
});
Completion.makeSelectedCompletionsInfeasible(selector, blockRes);
let emptyEffects = construct_empty_effects(realm, blockRes);
handlerEffects = Join.joinEffects(joinCondition, handlerEffects, emptyEffects);
realm.applyEffects(handlerEffects);
result = handlerEffects.result;
} catch (e) {
if (!(e instanceof InfeasiblePathError)) throw e;
// It turns out that the handler is not reachable after all so just do nothing and carry on
} }
// Add effects of normal exits from handler to blockRes and apply to global state
let handlerEffects = composeNestedThrowEffectsWithHandler(blockRes);
realm.applyEffects(handlerEffects);
handlerRes = handlerEffects.result;
} else {
// The handler is not invoked, so just carry on.
} }
} }
let finalizerRes = handlerRes;
if (ast.finalizer) { if (ast.finalizer) {
// The start of the finalizer is a join point where all threads of control come together. let res = env.evaluateCompletionDeref(ast.finalizer, strictCode);
// However, we choose to keep the threads unjoined and to apply the finalizer separately to each thread. result = composeResults(result, res);
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> = []
): 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> = []
): 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);
} }
return realm.returnOrThrowCompletion(UpdateEmpty(realm, result, realm.intrinsics.undefined));
} }

View File

@ -11,7 +11,7 @@
import type { Realm } from "../realm.js"; import type { Realm } from "../realm.js";
import type { LexicalEnvironment } from "../environment.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 { CompilerDiagnostic, FatalError } from "../errors.js";
import { TypesDomain, ValuesDomain } from "../domains/index.js"; import { TypesDomain, ValuesDomain } from "../domains/index.js";
import { import {
@ -129,23 +129,8 @@ function tryToEvaluateOperationOrLeaveAsAbstract(
throw error; 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); realm.applyEffects(effects);
let completion = effects.result; return realm.returnOrThrowCompletion(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;
} }
function evaluateOperation( function evaluateOperation(

View File

@ -30,7 +30,7 @@ export default function(realm: Realm, obj: ObjectValue): void {
let g = context; let g = context;
// 2. Let C be Completion{[[Type]]: return, [[Value]]: value, [[Target]]: empty}. // 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). // 3. Return ? GeneratorResumeAbrupt(g, C).
return GeneratorResumeAbrupt(realm, g, C); return GeneratorResumeAbrupt(realm, g, C);
@ -42,7 +42,7 @@ export default function(realm: Realm, obj: ObjectValue): void {
let g = context; let g = context;
// 2. Let C be Completion{[[Type]]: throw, [[Value]]: exception, [[Target]]: empty}. // 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). // 3. Return ? GeneratorResumeAbrupt(g, C).
return GeneratorResumeAbrupt(realm, g, C); return GeneratorResumeAbrupt(realm, g, C);

View File

@ -13,7 +13,7 @@ import { TypesDomain, ValuesDomain } from "../../domains/index.js";
import { FatalError } from "../../errors.js"; import { FatalError } from "../../errors.js";
import { Realm } from "../../realm.js"; import { Realm } from "../../realm.js";
import { NativeFunctionValue } from "../../values/index.js"; import { NativeFunctionValue } from "../../values/index.js";
import { AbruptCompletion, PossiblyNormalCompletion } from "../../completions.js"; //import { AbruptCompletion } from "../../completions.js";
import { import {
AbstractValue, AbstractValue,
AbstractObjectValue, AbstractObjectValue,
@ -190,19 +190,8 @@ function tryAndApplySourceOrRecover(
} finally { } finally {
realm.suppressDiagnostics = savedSuppressDiagnostics; 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); realm.applyEffects(effects);
let completion = effects.result; realm.returnOrThrowCompletion(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;
return to_must_be_partial; return to_must_be_partial;
} }

View File

@ -503,7 +503,7 @@ export default function(realm: Realm, obj: ObjectValue): ObjectValue {
// 7. Search string for the first occurrence of searchString and // 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 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 be searchString.
let matched = searchString; let matched = searchString;

View File

@ -9,18 +9,20 @@
/* @flow strict-local */ /* @flow strict-local */
import { TypesDomain, ValuesDomain } from "../domains/index.js";
import type { Intrinsics } from "../types.js"; import type { Intrinsics } from "../types.js";
import type { Realm } from "../realm.js"; import type { Realm } from "../realm.js";
import { import {
NumberValue, AbstractValue,
StringValue,
NullValue,
UndefinedValue,
EmptyValue,
ObjectValue,
SymbolValue,
BooleanValue, BooleanValue,
EmptyValue,
NativeFunctionValue, NativeFunctionValue,
NullValue,
NumberValue,
ObjectValue,
StringValue,
SymbolValue,
UndefinedValue,
} from "../values/index.js"; } from "../values/index.js";
import { Functions } from "../singletons.js"; import { Functions } from "../singletons.js";
@ -467,5 +469,21 @@ export function initialize(i: Intrinsics, realm: Realm): Intrinsics {
// 8.2.2, step 12 // 8.2.2, step 12
Functions.AddRestrictedFunctionProperties(i.FunctionPrototype, realm); 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; return i;
} }

View File

@ -22,20 +22,28 @@ import { FatalError } from "../errors.js";
import { Realm, ExecutionContext } from "../realm.js"; import { Realm, ExecutionContext } from "../realm.js";
import Value from "../values/Value.js"; import Value from "../values/Value.js";
import { import {
FunctionValue,
ECMAScriptSourceFunctionValue,
ObjectValue,
NullValue,
UndefinedValue,
NativeFunctionValue,
AbstractObjectValue, AbstractObjectValue,
AbstractValue, AbstractValue,
ECMAScriptSourceFunctionValue,
FunctionValue,
NativeFunctionValue,
NullValue,
ObjectValue,
UndefinedValue,
} from "../values/index.js"; } from "../values/index.js";
import { GetIterator, HasSomeCompatibleType, IsCallable, IsPropertyKey, IteratorStep, IteratorValue } from "./index.js"; import { GetIterator, HasSomeCompatibleType, IsCallable, IsPropertyKey, IteratorStep, IteratorValue } from "./index.js";
import { GeneratorStart } from "./generator.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 { 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 invariant from "../invariant.js";
import { createOperationDescriptor } from "../utils/generator.js"; import { createOperationDescriptor } from "../utils/generator.js";
import type { BabelNodeExpression, BabelNodeSpreadElement, BabelNodeTemplateLiteral } from "@babel/types"; import type { BabelNodeExpression, BabelNodeSpreadElement, BabelNodeTemplateLiteral } from "@babel/types";
@ -291,7 +299,7 @@ function callNativeFunctionValue(
realm: Realm, realm: Realm,
f: NativeFunctionValue, f: NativeFunctionValue,
argumentsList: Array<Value> argumentsList: Array<Value>
): Value | AbruptCompletion { ): void | AbruptCompletion {
let env = realm.getRunningContext().lexicalEnvironment; let env = realm.getRunningContext().lexicalEnvironment;
let context = env.environmentRecord.GetThisBinding(); let context = env.environmentRecord.GetThisBinding();
@ -306,7 +314,7 @@ function callNativeFunctionValue(
mightBecomeAnObject(contextVal) mightBecomeAnObject(contextVal)
); );
let completion = f.callCallback( 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), ((contextVal: any): AbstractObjectValue | ObjectValue | NullValue | UndefinedValue),
argumentsList, argumentsList,
env.environmentRecord.$NewTarget 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") { if (context instanceof AbstractObjectValue && context.kind === "conditional") {
let [condValue, consequentVal, alternateVal] = context.args; 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 // ECMA262 9.2.1.3
@ -357,7 +367,7 @@ export function OrdinaryCallEvaluateBody(
realm: Realm, realm: Realm,
f: ECMAScriptFunctionValue, f: ECMAScriptFunctionValue,
argumentsList: Array<Value> argumentsList: Array<Value>
): Reference | Value | AbruptCompletion { ): void | AbruptCompletion {
if (f instanceof NativeFunctionValue) { if (f instanceof NativeFunctionValue) {
return callNativeFunctionValue(realm, f, argumentsList); return callNativeFunctionValue(realm, f, argumentsList);
} else { } else {
@ -379,7 +389,7 @@ export function OrdinaryCallEvaluateBody(
GeneratorStart(realm, G, code); GeneratorStart(realm, G, code);
// 4. Return Completion{[[Type]]: return, [[Value]]: G, [[Target]]: empty}. // 4. Return Completion{[[Type]]: return, [[Value]]: G, [[Target]]: empty}.
return new ReturnCompletion(G, undefined, realm.currentLocation); return new ReturnCompletion(G, realm.currentLocation);
} else { } else {
// TODO #1586: abstractRecursionSummarization is disabled for now, as it is likely too limiting // TODO #1586: abstractRecursionSummarization is disabled for now, as it is likely too limiting
// (as observed in large internal tests). // (as observed in large internal tests).
@ -398,15 +408,15 @@ export function OrdinaryCallEvaluateBody(
realm.applyEffects(effects); realm.applyEffects(effects);
let c = effects.result; let c = effects.result;
return processResult(() => { return processResult(() => {
invariant(c instanceof Value || c instanceof AbruptCompletion); if (c instanceof AbruptCompletion || c instanceof JoinedNormalAndAbruptCompletions) return c;
return c; return undefined;
}); });
} }
} finally { } finally {
F.isSelfRecursive = savedIsSelfRecursive; F.isSelfRecursive = savedIsSelfRecursive;
} }
function guardedCall() { function guardedCall(): Value | Completion {
let currentLocation = realm.currentLocation; let currentLocation = realm.currentLocation;
if (F.activeArguments !== undefined && F.activeArguments.has(currentLocation)) { if (F.activeArguments !== undefined && F.activeArguments.has(currentLocation)) {
let [previousPathLength, previousArguments] = F.activeArguments.get(currentLocation); let [previousPathLength, previousArguments] = F.activeArguments.get(currentLocation);
@ -418,7 +428,7 @@ export function OrdinaryCallEvaluateBody(
if (Widen.containsArraysOfValue(realm, previousArguments, widenedArgumentsList)) { if (Widen.containsArraysOfValue(realm, previousArguments, widenedArgumentsList)) {
// Reached a fixed point. Executing this call will not add any knowledge // Reached a fixed point. Executing this call will not add any knowledge
// about the effects of the original call. // about the effects of the original call.
return AbstractValue.createFromType(realm, Value, "widened return result"); return realm.intrinsics.undefined;
} else { } else {
argumentsList = widenedArgumentsList; argumentsList = widenedArgumentsList;
} }
@ -427,13 +437,13 @@ export function OrdinaryCallEvaluateBody(
try { try {
if (F.activeArguments === undefined) F.activeArguments = new Map(); if (F.activeArguments === undefined) F.activeArguments = new Map();
F.activeArguments.set(currentLocation, [realm.pathConditions.length, argumentsList]); F.activeArguments.set(currentLocation, [realm.pathConditions.length, argumentsList]);
return normalCall(); return normalCall() || realm.intrinsics.undefined;
} finally { } finally {
F.activeArguments.delete(currentLocation); F.activeArguments.delete(currentLocation);
} }
} }
function normalCall() { function normalCall(): void | AbruptCompletion {
// 1. Perform ? FunctionDeclarationInstantiation(F, argumentsList). // 1. Perform ? FunctionDeclarationInstantiation(F, argumentsList).
Functions.FunctionDeclarationInstantiation(realm, F, argumentsList); Functions.FunctionDeclarationInstantiation(realm, F, argumentsList);
@ -442,55 +452,47 @@ export function OrdinaryCallEvaluateBody(
let code = F.$ECMAScriptCode; let code = F.$ECMAScriptCode;
invariant(code !== undefined); invariant(code !== undefined);
let context = realm.getRunningContext(); 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; let priorSavedCompletion = realm.savedCompletion;
realm.savedCompletion = undefined;
let c;
try { try {
realm.savedCompletion = undefined; c = getCompletion();
let c = getCompletion(); } catch (e) {
invariant(!(e instanceof AbruptCompletion));
// We are about the leave this function and this presents a join point where all non exceptional control flows throw e;
// converge into a single flow using their joint effects to update the post join point state.
if (!(c instanceof ReturnCompletion)) {
if (!(c instanceof AbruptCompletion)) {
c = new ReturnCompletion(realm.intrinsics.undefined, undefined, realm.currentLocation);
}
}
invariant(c instanceof AbruptCompletion);
// If there is a saved completion (i.e. unjoined abruptly completing control flows) then combine them with c
let abruptCompletion = Functions.incorporateSavedCompletion(realm, c);
invariant(abruptCompletion instanceof AbruptCompletion);
// If there is single completion, we don't need to join
if (!(abruptCompletion instanceof ForkedAbruptCompletion)) return abruptCompletion;
// If none of the completions are return completions, there is no need to join either
if (!abruptCompletion.containsCompletion(ReturnCompletion)) return abruptCompletion;
// Apply the joined effects of return completions to the current state since these now join the normal path
let joinedReturnEffects = Join.extractAndJoinCompletionsOfType(ReturnCompletion, realm, abruptCompletion);
realm.applyEffects(joinedReturnEffects);
c = joinedReturnEffects.result;
invariant(c instanceof ReturnCompletion);
// We now make a PossiblyNormalCompletion out of abruptCompletion.
// extractAndJoinCompletionsOfType helped with this by cheating and turning all of its nested completions
// that contain return completions into PossiblyNormalCompletions.
let remainingCompletions = abruptCompletion.transferChildrenToPossiblyNormalCompletion();
// If there are no throw completions left inside remainingCompletions, just return.
if (!remainingCompletions.containsCompletion(ThrowCompletion)) return c;
// Stash the remaining completions in the realm start tracking the effects that need to be appended
// to the normal branch at the next join point.
realm.composeWithSavedCompletion(remainingCompletions);
return c;
} finally {
realm.incorporatePriorSavedCompletion(priorSavedCompletion);
} }
c = 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;
} }
} }
} }

View File

@ -14,7 +14,13 @@ import type { PropertyKeyValue } from "../types.js";
import { FatalError } from "../errors.js"; import { FatalError } from "../errors.js";
import type { Realm } from "../realm.js"; import type { Realm } from "../realm.js";
import type { ECMAScriptFunctionValue } from "../values/index.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 { GlobalEnvironmentRecord, ObjectEnvironmentRecord } from "../environment.js";
import { import {
AbstractValue, AbstractValue,
@ -121,8 +127,8 @@ function InternalCall(
return result.value; return result.value;
} }
// 10. ReturnIfAbrupt(result). or if possibly abrupt // 10. ReturnIfAbrupt(result).
if (result instanceof Completion) { if (result instanceof AbruptCompletion) {
throw result; 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 // Composes realm.savedCompletion with c, clears realm.savedCompletion and return the composition.
// 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.
// Call this only when a join point has been reached. // 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; let savedCompletion = realm.savedCompletion;
if (savedCompletion !== undefined) { 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; realm.savedCompletion = undefined;
if (c === undefined) return savedCompletion; realm.pathConditions = [].concat(savedCompletion.pathConditionsAtCreation);
if (c instanceof Value) { if (c === undefined) c = realm.intrinsics.empty;
Join.updatePossiblyNormalCompletionWithValue(realm, savedCompletion, c); if (c instanceof Value) c = new SimpleNormalCompletion(c);
return savedCompletion; if (savedCompletion instanceof JoinedNormalAndAbruptCompletions) {
} else { let subsequentEffects = realm.getCapturedEffects(c);
let e = realm.getCapturedEffects();
realm.stopEffectCaptureAndUndoEffects(savedCompletion); 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; return c;
} }
@ -1165,34 +1165,6 @@ export class FunctionImplementation {
return blockValue || realm.intrinsics.empty; return blockValue || realm.intrinsics.empty;
} }
PartiallyEvaluateStatements(
body: Array<BabelNodeStatement>,
blockValue: void | NormalCompletion | Value,
strictCode: boolean,
blockEnv: LexicalEnvironment,
realm: Realm
): [Completion | Value, Array<BabelNodeStatement>] {
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 // ECMA262 9.2.5
FunctionCreate( FunctionCreate(
realm: Realm, realm: Realm,

View File

@ -9,7 +9,6 @@
/* @flow */ /* @flow */
import { AbruptCompletion, Completion, PossiblyNormalCompletion } from "../completions.js";
import { InfeasiblePathError } from "../errors.js"; import { InfeasiblePathError } from "../errors.js";
import { construct_empty_effects, type Realm, Effects } from "../realm.js"; import { construct_empty_effects, type Realm, Effects } from "../realm.js";
import type { PropertyKeyValue, CallableObjectValue } from "../types.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 // Join the effects, creating an abstract view of what happened, regardless
// of the actual value of ownDesc.joinCondition. // of the actual value of ownDesc.joinCondition.
if (result1 instanceof Completion) result1 = result1.shallowCloneWithoutEffects(); let joinedEffects = Join.joinEffects(
if (result2 instanceof Completion) result2 = result2.shallowCloneWithoutEffects();
let joinedEffects = Join.joinForkOrChoose(
realm,
joinCondition, joinCondition,
new Effects(result1, generator1, modifiedBindings1, modifiedProperties1, createdObjects1), new Effects(result1, generator1, modifiedBindings1, modifiedProperties1, createdObjects1),
new Effects(result2, generator2, modifiedBindings2, modifiedProperties2, createdObjects2) 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); realm.applyEffects(joinedEffects);
return realm.returnOrThrowCompletion(joinedEffects.result);
// return or throw completion
if (completion instanceof AbruptCompletion) throw completion;
invariant(completion instanceof Value);
return completion;
function OrdinaryGetHelper() { function OrdinaryGetHelper() {
let descValue = !desc let descValue = !desc

View File

@ -10,9 +10,8 @@
/* @flow */ /* @flow */
import type { Binding } from "../environment.js"; import type { Binding } from "../environment.js";
import { FatalError } from "../errors.js"; import type { Bindings, BindingEntry, PropertyBindings, CreatedObjects, Realm } from "../realm.js";
import type { Bindings, BindingEntry, EvaluationResult, PropertyBindings, CreatedObjects, Realm } from "../realm.js"; import { construct_empty_effects, Effects } from "../realm.js";
import { Effects } from "../realm.js";
import type { Descriptor, PropertyBinding } from "../types.js"; import type { Descriptor, PropertyBinding } from "../types.js";
import { import {
@ -20,29 +19,22 @@ import {
BreakCompletion, BreakCompletion,
Completion, Completion,
ContinueCompletion, ContinueCompletion,
PossiblyNormalCompletion, JoinedAbruptCompletions,
ForkedAbruptCompletion, JoinedNormalAndAbruptCompletions,
SimpleNormalCompletion, SimpleNormalCompletion,
NormalCompletion, NormalCompletion,
ReturnCompletion, ReturnCompletion,
ThrowCompletion, ThrowCompletion,
} from "../completions.js"; } from "../completions.js";
import { Reference } from "../environment.js";
import { cloneDescriptor, equalDescriptors, IsDataDescriptor, StrictEqualityComparison } from "../methods/index.js"; import { cloneDescriptor, equalDescriptors, IsDataDescriptor, StrictEqualityComparison } from "../methods/index.js";
import { construct_empty_effects } from "../realm.js";
import { Path } from "../singletons.js"; import { Path } from "../singletons.js";
import { Generator } from "../utils/generator.js"; import { Generator } from "../utils/generator.js";
import { AbstractValue, ConcreteValue, EmptyValue, Value } from "../values/index.js"; import { AbstractValue, ConcreteValue, EmptyValue, Value } from "../values/index.js";
import invariant from "../invariant.js"; import invariant from "../invariant.js";
function joinGenerators( function joinGenerators(joinCondition: AbstractValue, generator1: Generator, generator2: Generator): Generator {
realm: Realm, let realm = joinCondition.$Realm;
joinCondition: AbstractValue,
generator1: Generator,
generator2: Generator
): Generator {
// TODO #2222: Check if `realm.pathConditions` is correct here.
let result = new Generator(realm, "joined", realm.pathConditions); let result = new Generator(realm, "joined", realm.pathConditions);
if (!generator1.empty() || !generator2.empty()) { if (!generator1.empty() || !generator2.empty()) {
result.joinGenerators(joinCondition, generator1, generator2); result.joinGenerators(joinCondition, generator1, generator2);
@ -99,405 +91,128 @@ function joinArraysOfValues(
} }
export class JoinImplementation { export class JoinImplementation {
stopEffectCaptureJoinApplyAndReturnCompletion( composeCompletions(leftCompletion: void | Completion | Value, rightCompletion: Completion | Value): Completion {
c1: PossiblyNormalCompletion, if (leftCompletion instanceof AbruptCompletion) return leftCompletion;
c2: AbruptCompletion, if (leftCompletion instanceof JoinedNormalAndAbruptCompletions) {
realm: Realm if (rightCompletion instanceof JoinedNormalAndAbruptCompletions) {
): ForkedAbruptCompletion { rightCompletion.composedWith = leftCompletion;
let e = realm.getCapturedEffects(); rightCompletion.pathConditionsAtCreation = leftCompletion.pathConditionsAtCreation;
realm.stopEffectCaptureAndUndoEffects(c1); return rightCompletion;
return this.replacePossiblyNormalCompletionWithForkedAbruptCompletion(realm, c1, c2, e); }
let c = this.composeCompletions(leftCompletion.consequent, rightCompletion);
if (c instanceof Value) c = new SimpleNormalCompletion(c);
let a = this.composeCompletions(leftCompletion.alternate, rightCompletion);
if (a instanceof Value) a = new SimpleNormalCompletion(a);
let joinedCompletion = this.joinCompletions(leftCompletion.joinCondition, c, a);
if (joinedCompletion instanceof JoinedNormalAndAbruptCompletions) {
joinedCompletion.composedWith = leftCompletion.composedWith;
joinedCompletion.pathConditionsAtCreation = leftCompletion.pathConditionsAtCreation;
joinedCompletion.savedEffects = leftCompletion.savedEffects;
}
return joinedCompletion;
}
if (rightCompletion instanceof Value) rightCompletion = new SimpleNormalCompletion(rightCompletion);
return rightCompletion;
} }
unbundleNormalCompletion( composeWithEffects(completion: Completion, effects: Effects): Effects {
completionOrValue: Completion | Value | Reference if (completion instanceof AbruptCompletion) return construct_empty_effects(completion.value.$Realm, completion);
): [void | NormalCompletion, Value | Reference] { if (completion instanceof SimpleNormalCompletion) return effects.shallowCloneWithResult(effects.result);
let completion, value; invariant(completion instanceof JoinedNormalAndAbruptCompletions);
if (completionOrValue instanceof PossiblyNormalCompletion) { let e1 = this.composeWithEffects(completion.consequent, effects);
completion = completionOrValue; let e2 = this.composeWithEffects(completion.alternate, effects);
value = completionOrValue.value; return this.joinEffects(completion.joinCondition, e1, e2);
} else {
invariant(completionOrValue instanceof Value || completionOrValue instanceof Reference);
value = completionOrValue;
}
return [completion, value];
} }
composeNormalCompletions( _collapseSimilarCompletions(joinCondition: AbstractValue, c1: Completion, c2: Completion): void | Completion {
leftCompletion: void | NormalCompletion, let realm = joinCondition.$Realm;
rightCompletion: void | NormalCompletion,
resultValue: Value,
realm: Realm
): PossiblyNormalCompletion | Value {
if (leftCompletion instanceof PossiblyNormalCompletion) {
if (rightCompletion instanceof PossiblyNormalCompletion) {
this.updatePossiblyNormalCompletionWithValue(realm, rightCompletion, resultValue);
return this.composePossiblyNormalCompletions(realm, leftCompletion, rightCompletion);
}
this.updatePossiblyNormalCompletionWithValue(realm, leftCompletion, resultValue);
return leftCompletion;
} else if (rightCompletion instanceof PossiblyNormalCompletion) {
this.updatePossiblyNormalCompletionWithValue(realm, rightCompletion, resultValue);
return rightCompletion;
} else {
invariant(leftCompletion === undefined && rightCompletion === undefined);
return resultValue;
}
}
composePossiblyNormalCompletions(
realm: Realm,
pnc: PossiblyNormalCompletion,
c: PossiblyNormalCompletion,
priorEffects?: Effects
): PossiblyNormalCompletion {
invariant(c.savedEffects === undefined); // the caller should ensure this
let savedPathConditions = pnc.savedPathConditions;
if (pnc.consequent instanceof AbruptCompletion) {
let ae = pnc.alternateEffects;
let na;
if (pnc.alternate instanceof SimpleNormalCompletion) {
na = c.shallowCloneWithoutEffects();
let newAlternateEffects = ae.shallowCloneWithResult(na);
if (priorEffects) newAlternateEffects = realm.composeEffects(priorEffects, newAlternateEffects);
return new PossiblyNormalCompletion(
c.value,
pnc.joinCondition,
pnc.consequent,
newAlternateEffects.result,
savedPathConditions,
pnc.savedEffects
);
}
invariant(pnc.alternate instanceof PossiblyNormalCompletion);
na = this.composePossiblyNormalCompletions(realm, pnc.alternate, c, priorEffects);
ae.shallowCloneWithResult(na);
return new PossiblyNormalCompletion(
c.value,
pnc.joinCondition,
pnc.consequent,
na,
savedPathConditions,
pnc.savedEffects
);
} else {
let ce = pnc.consequentEffects;
let nc;
if (pnc.consequent instanceof SimpleNormalCompletion) {
nc = c.shallowCloneWithoutEffects();
let newConsequentEffects = ce.shallowCloneWithResult(nc);
if (priorEffects) newConsequentEffects = realm.composeEffects(priorEffects, newConsequentEffects);
return new PossiblyNormalCompletion(
c.value,
pnc.joinCondition,
newConsequentEffects.result,
pnc.alternate,
savedPathConditions,
pnc.savedEffects
);
}
invariant(pnc.consequent instanceof PossiblyNormalCompletion);
nc = this.composePossiblyNormalCompletions(realm, pnc.consequent, c);
ce.shallowCloneWithResult(nc);
return new PossiblyNormalCompletion(
c.value,
pnc.joinCondition,
nc,
pnc.alternate,
savedPathConditions,
pnc.savedEffects
);
}
}
updatePossiblyNormalCompletionWithSubsequentEffects(
realm: Realm,
pnc: PossiblyNormalCompletion,
subsequentEffects: Effects
): void {
let v = subsequentEffects.result;
invariant(v instanceof SimpleNormalCompletion);
pnc.value = v.value;
if (pnc.consequent instanceof AbruptCompletion) {
if (pnc.alternate instanceof SimpleNormalCompletion) {
let ce = realm.composeEffects(pnc.alternateEffects, subsequentEffects);
pnc.alternate = ce.result;
} else {
invariant(pnc.alternate instanceof PossiblyNormalCompletion);
this.updatePossiblyNormalCompletionWithSubsequentEffects(realm, pnc.alternate, subsequentEffects);
}
} else {
if (pnc.consequent instanceof SimpleNormalCompletion) {
let ce = realm.composeEffects(pnc.consequentEffects, subsequentEffects);
pnc.consequent = ce.result;
} else {
invariant(pnc.consequent instanceof PossiblyNormalCompletion);
this.updatePossiblyNormalCompletionWithSubsequentEffects(realm, pnc.consequent, subsequentEffects);
}
if (pnc.alternate instanceof SimpleNormalCompletion) {
let ce = realm.composeEffects(pnc.alternateEffects, subsequentEffects);
pnc.alternate = ce.result;
} else if (pnc.alternate instanceof PossiblyNormalCompletion) {
this.updatePossiblyNormalCompletionWithSubsequentEffects(realm, pnc.alternate, subsequentEffects);
}
}
}
updatePossiblyNormalCompletionWithValue(realm: Realm, pnc: PossiblyNormalCompletion, v: Value): void {
let updateNonAbruptCompletionWithValue = (c: Completion, val: Value) => {
if (c instanceof SimpleNormalCompletion) {
c.value = v;
} else if (c instanceof PossiblyNormalCompletion) {
this.updatePossiblyNormalCompletionWithValue(realm, c, val);
} else {
invariant(false);
}
};
pnc.value = v;
let pncc = pnc.consequent;
let pnca = pnc.alternate;
if (pncc instanceof AbruptCompletion) {
Path.withInverseCondition(pnc.joinCondition, () => {
let sv = v instanceof AbstractValue ? realm.simplifyAndRefineAbstractValue(v) : v;
updateNonAbruptCompletionWithValue(pnca, sv);
});
} else {
Path.withCondition(pnc.joinCondition, () => {
let sv = v instanceof AbstractValue ? realm.simplifyAndRefineAbstractValue(v) : v;
updateNonAbruptCompletionWithValue(pncc, sv);
});
if (!(pnca instanceof AbruptCompletion)) {
Path.withInverseCondition(pnc.joinCondition, () => {
let sv = v instanceof AbstractValue ? realm.simplifyAndRefineAbstractValue(v) : v;
updateNonAbruptCompletionWithValue(pnca, sv);
});
}
}
}
replacePossiblyNormalCompletionWithForkedAbruptCompletion(
realm: Realm,
// a forked path with a non abrupt (normal) component
pnc: PossiblyNormalCompletion,
// an abrupt completion that completes the normal path
ac: AbruptCompletion,
// effects collected after pnc was constructed
e: Effects
): ForkedAbruptCompletion {
let recurse = (xpnc, xe, nac, ne): ForkedAbruptCompletion => {
let nx = this.replacePossiblyNormalCompletionWithForkedAbruptCompletion(realm, xpnc, nac, ne);
xe.shallowCloneWithResult(nx);
return nx;
};
let cloneEffects = () => {
let nac = ac.shallowCloneWithoutEffects();
let ne = e.shallowCloneWithResult(nac);
return [nac, ne];
};
ac = ac.shallowCloneWithoutEffects();
e.result = ac;
ac.effects = e;
// # match (pncc, pnca)
let pncc = pnc.consequent;
let pnca = pnc.alternate;
// * case (AbruptCompletion, SimpleNormalCompletion)
// * case (AbruptCompletion, PossiblyNormalCompletion)
if (pncc instanceof AbruptCompletion) {
if (pnca instanceof SimpleNormalCompletion) {
// todo: simplify with implied path condition
e = realm.composeEffects(pnc.alternateEffects, e);
invariant(e.result instanceof AbruptCompletion);
return new ForkedAbruptCompletion(realm, pnc.joinCondition, pncc, e.result);
}
invariant(pnca instanceof PossiblyNormalCompletion);
let na = recurse(pnca, pnc.alternateEffects, ac, e);
return new ForkedAbruptCompletion(realm, pnc.joinCondition, pncc, na);
}
// * case (SimpleNormalCompletion, AbruptCompletion)
// * case (PossiblyNormalCompletion, AbruptCompletion)
if (pnca instanceof AbruptCompletion) {
if (pncc instanceof SimpleNormalCompletion) {
// todo: simplify with implied path condition
e = realm.composeEffects(pnc.consequentEffects, e);
invariant(e.result instanceof AbruptCompletion);
return new ForkedAbruptCompletion(realm, pnc.joinCondition, e.result, pnca);
}
invariant(pncc instanceof PossiblyNormalCompletion);
let nc = recurse(pncc, pnc.consequentEffects, ac, e);
return new ForkedAbruptCompletion(realm, pnc.joinCondition, nc, pnca);
}
// * case (SimpleNormalCompletion, SimpleNormalCompletion)
// * case (SimpleNormalCompletion, PossibleNormalCompletion)
if (pncc instanceof SimpleNormalCompletion) {
let nce = realm.composeEffects(pnc.consequentEffects, e);
invariant(nce.result instanceof AbruptCompletion);
let nc = nce.result;
[ac, e] = cloneEffects();
let na, nae;
if (pnca instanceof SimpleNormalCompletion) {
nae = realm.composeEffects(pnc.alternateEffects, e);
invariant(nae.result instanceof AbruptCompletion);
na = nae.result;
} else {
invariant(pnca instanceof PossiblyNormalCompletion);
na = recurse(pnca, pnc.alternateEffects, ac, e);
}
return new ForkedAbruptCompletion(realm, pnc.joinCondition, nc, na);
}
// * case (PossibleNormalCompletion, SimpleNormalCompletion)
if (pnca instanceof SimpleNormalCompletion) {
let nae = realm.composeEffects(pnc.alternateEffects, e);
invariant(nae.result instanceof AbruptCompletion);
let na = nae.result;
invariant(pncc instanceof PossiblyNormalCompletion);
[ac, e] = cloneEffects();
let nc = recurse(pncc, pnc.consequentEffects, ac, e);
return new ForkedAbruptCompletion(realm, pnc.joinCondition, nc, na);
}
// * case (PossibleNormalCompletion, PossibleNormalCompletion)
invariant(pncc instanceof PossiblyNormalCompletion);
invariant(pnca instanceof PossiblyNormalCompletion);
let nc = recurse(pncc, pnc.consequentEffects, ac, e);
[ac, e] = cloneEffects();
let na = recurse(pnca, pnc.alternateEffects, ac, e);
return new ForkedAbruptCompletion(realm, pnc.joinCondition, nc, na);
// Impossible cases:
// * case (AbruptCompletion, AbruptCompletion)
}
joinNormalCompletions(
realm: Realm,
joinCondition: AbstractValue,
c: NormalCompletion,
ce: Effects,
a: NormalCompletion,
ae: Effects
): PossiblyNormalCompletion {
let getAbstractValue = (v1: void | Value, v2: void | Value): Value => { let getAbstractValue = (v1: void | Value, v2: void | Value): Value => {
if (v1 instanceof EmptyValue) return v2 || realm.intrinsics.undefined; if (v1 instanceof EmptyValue) return v2 || realm.intrinsics.undefined;
if (v2 instanceof EmptyValue) return v1 || realm.intrinsics.undefined; if (v2 instanceof EmptyValue) return v1 || realm.intrinsics.undefined;
return AbstractValue.createFromConditionalOp(realm, joinCondition, v1, v2); return AbstractValue.createFromConditionalOp(realm, joinCondition, v1, v2);
}; };
let rv = this.joinValues(realm, c.value, a.value, getAbstractValue); if (c1 instanceof BreakCompletion && c2 instanceof BreakCompletion && c1.target === c2.target) {
invariant(rv instanceof Value); let val = this.joinValues(realm, c1.value, c2.value, getAbstractValue);
a.value = rv; invariant(val instanceof Value);
return new PossiblyNormalCompletion(rv, joinCondition, c, a, []); return new BreakCompletion(val, joinCondition.expressionLocation, c1.target);
}
if (c1 instanceof ContinueCompletion && c2 instanceof ContinueCompletion && c1.target === c2.target) {
return new ContinueCompletion(realm.intrinsics.empty, joinCondition.expressionLocation, c1.target);
}
if (c1 instanceof ReturnCompletion && c2 instanceof ReturnCompletion) {
let val = this.joinValues(realm, c1.value, c2.value, getAbstractValue);
invariant(val instanceof Value);
return new ReturnCompletion(val, joinCondition.expressionLocation);
}
if (c1 instanceof ThrowCompletion && c2 instanceof ThrowCompletion) {
getAbstractValue = (v1: void | Value, v2: void | Value) => {
return AbstractValue.createFromConditionalOp(realm, joinCondition, v1, v2);
};
let val = this.joinValues(realm, c1.value, c2.value, getAbstractValue);
invariant(val instanceof Value);
return new ThrowCompletion(val, c1.location);
}
if (c1 instanceof SimpleNormalCompletion && c2 instanceof SimpleNormalCompletion) {
return new SimpleNormalCompletion(getAbstractValue(c1.value, c2.value));
}
return undefined;
} }
// Join all effects that result in completions of type CompletionType. joinCompletions(joinCondition: Value, c1: Completion, c2: Completion): Completion {
// Erase all completions of type Completion type from c, so that we never join them again. if (!joinCondition.mightNotBeTrue()) return c1;
// Also erase any generators that appears in branches resulting in completions of type CompletionType. if (!joinCondition.mightNotBeFalse()) return c2;
// Note that c is modified in place and should be replaced with a PossiblyNormalCompletion by the caller invariant(joinCondition instanceof AbstractValue);
// if either of its branches cease to be an AbruptCompletion.
extractAndJoinCompletionsOfType(CompletionType: typeof AbruptCompletion, realm: Realm, c: AbruptCompletion): Effects {
let emptyEffects = construct_empty_effects(realm);
if (c instanceof CompletionType) {
emptyEffects.result = c.shallowCloneWithoutEffects();
return emptyEffects;
}
if (!(c instanceof ForkedAbruptCompletion)) {
return emptyEffects;
}
// Join up the consequent and alternate completions and compose them with their prefix effects
let ce = this.extractAndJoinCompletionsOfType(CompletionType, realm, c.consequent);
// ce will be applied to the global state before any non joining branches in c.consequent, so move
// the generator from c.consequentEffects to ce.generator so that all branches will see its effects.
ce = realm.composeEffects(c.consequentEffects, ce);
// ce now incorporates c.consequentEffects.generator, so remove it from there.
c.consequentEffects.generator = emptyEffects.generator;
if (ce.result instanceof CompletionType) {
// Erase completions of type CompletionType and prepare for transformation of c to a possibly normal completion
if (c.consequent instanceof CompletionType) {
c.updateConsequentKeepingCurrentEffects(new SimpleNormalCompletion(realm.intrinsics.empty, undefined));
} else if (c.consequent instanceof ForkedAbruptCompletion && c.consequent.containsCompletion(NormalCompletion)) {
c.updateConsequentKeepingCurrentEffects((c.consequent.transferChildrenToPossiblyNormalCompletion(): any));
}
} else {
ce.result = new CompletionType(realm.intrinsics.empty);
}
let ae = this.extractAndJoinCompletionsOfType(CompletionType, realm, c.alternate);
// ae will be applied to the global state before any non joining branches in c.alternate, so move
// the generator from c.alternateEffects to ae.generator so that all branches will see its effects.
ae = realm.composeEffects(c.alternateEffects, ae);
// ae now incorporates c.alternateEffects.generator, so remove it from there.
c.alternateEffects.generator = emptyEffects.generator;
if (ae.result instanceof CompletionType) {
// Erase completions of type CompletionType and prepare for transformation of c to a possibly normal completion
if (c.alternate instanceof CompletionType) {
c.updateAlternateKeepingCurrentEffects(new SimpleNormalCompletion(realm.intrinsics.empty, undefined));
} else if (c.alternate instanceof ForkedAbruptCompletion && c.alternate.containsCompletion(NormalCompletion)) {
c.updateAlternateKeepingCurrentEffects((c.alternate.transferChildrenToPossiblyNormalCompletion(): any));
}
} else {
ae.result = new CompletionType(realm.intrinsics.empty);
}
let e = this.joinForkOrChoose(realm, c.joinCondition, ce, ae); let c = this._collapseSimilarCompletions(joinCondition, c1, c2);
if (e.result instanceof ForkedAbruptCompletion) { if (c === undefined) {
if (e.result.consequent instanceof CompletionType && e.result.alternate instanceof CompletionType) { if (c1 instanceof AbruptCompletion && c2 instanceof AbruptCompletion)
let result = this.collapseResults(realm, e.result.joinCondition, e, e.result.consequent, e.result.alternate); c = new JoinedAbruptCompletions(joinCondition, c1, c2);
e = result.effects; else {
invariant(e !== undefined); 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.mightNotBeTrue()) return e1;
if (!joinCondition.mightNotBeFalse()) return e2; if (!joinCondition.mightNotBeFalse()) return e2;
invariant(joinCondition instanceof AbstractValue); invariant(joinCondition instanceof AbstractValue);
let { let {
result: result1, result: c1,
generator: generator1, generator: generator1,
modifiedBindings: modifiedBindings1, modifiedBindings: modifiedBindings1,
modifiedProperties: modifiedProperties1, modifiedProperties: modifiedProperties1,
createdObjects: createdObjects1, createdObjects: createdObjects1,
} = e1; } = e1;
invariant(result1.effects === e1);
let { let {
result: result2, result: c2,
generator: generator2, generator: generator2,
modifiedBindings: modifiedBindings2, modifiedBindings: modifiedBindings2,
modifiedProperties: modifiedProperties2, modifiedProperties: modifiedProperties2,
createdObjects: createdObjects2, createdObjects: createdObjects2,
} = e2; } = 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); let c = this.joinCompletions(joinCondition, c1, c2);
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 [modifiedGenerator1, modifiedGenerator2, bindings] = this.joinBindings( let [modifiedGenerator1, modifiedGenerator2, bindings] = this._joinBindings(
realm,
joinCondition, joinCondition,
generator1, generator1,
modifiedBindings1, modifiedBindings1,
generator2, generator2,
modifiedBindings2 modifiedBindings2
); );
let generator = joinGenerators(joinCondition, modifiedGenerator1, modifiedGenerator2);
let properties = this.joinPropertyBindings( let properties = this.joinPropertyBindings(
realm, realm,
joinCondition, joinCondition,
@ -514,156 +229,31 @@ export class JoinImplementation {
createdObjects.add(o); createdObjects.add(o);
}); });
let generator = joinGenerators(realm, joinCondition, modifiedGenerator1, modifiedGenerator2); return new Effects(c, generator, bindings, properties, createdObjects);
return new Effects(result, generator, bindings, properties, createdObjects);
} }
joinNestedEffects(realm: Realm, c: Completion, precedingEffects?: Effects): Effects { joinValuesOfSelectedCompletions(selector: Completion => boolean, completion: Completion): Value {
if (c instanceof PossiblyNormalCompletion || c instanceof ForkedAbruptCompletion) { let realm = completion.value.$Realm;
let e1 = this.joinNestedEffects(realm, c.consequent, c.consequentEffects); if (completion instanceof JoinedAbruptCompletions || completion instanceof JoinedNormalAndAbruptCompletions) {
let e2 = this.joinNestedEffects(realm, c.alternate, c.alternateEffects); let joinCondition = completion.joinCondition;
let e3 = this.joinForkOrChoose(realm, c.joinCondition, e1, e2); let c = this.joinValuesOfSelectedCompletions(selector, completion.consequent);
let r = this.collapseResults(realm, c.joinCondition, e3, e1.result, e2.result); let a = this.joinValuesOfSelectedCompletions(selector, completion.alternate);
let re = r.effects; let getAbstractValue = (v1: void | Value, v2: void | Value): Value => {
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) => {
return AbstractValue.createFromConditionalOp(realm, joinCondition, v1, v2); return AbstractValue.createFromConditionalOp(realm, joinCondition, v1, v2);
}; };
let val = this.joinValues(realm, result1.value, result2.value, getAbstractValue); let jv = this.joinValues(realm, c, a, getAbstractValue);
invariant(val instanceof Value); invariant(jv instanceof Value);
return new ThrowCompletion(val, precedingEffects, result1.location); if (completion instanceof JoinedNormalAndAbruptCompletions && completion.composedWith !== undefined) {
} let composedWith = completion.composedWith;
if (result1 instanceof SimpleNormalCompletion && result2 instanceof SimpleNormalCompletion) { let cjv = this.joinValuesOfSelectedCompletions(selector, composedWith);
return new SimpleNormalCompletion(getAbstractValue(result1.value, result2.value), precedingEffects); joinCondition = AbstractValue.createJoinConditionForSelectedCompletions(selector, composedWith);
} jv = this.joinValues(realm, jv, cjv, getAbstractValue);
AbstractValue.reportIntrospectionError(joinCondition); invariant(jv instanceof Value);
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 jv;
return new PossiblyNormalCompletion(
completion.value,
joinCondition,
result1,
result2,
savedPathConditions,
savedEffects
);
} }
if (result2 instanceof AbruptCompletion) { if (selector(completion)) return completion.value;
let completion = result1; return realm.intrinsics.empty;
let savedEffects;
let savedPathConditions = [];
if (result1 instanceof PossiblyNormalCompletion) {
completion = result1.getNormalCompletion();
savedEffects = result1.savedEffects;
savedPathConditions = result1.savedPathConditions;
}
invariant(completion instanceof SimpleNormalCompletion);
return new PossiblyNormalCompletion(
completion.value,
joinCondition,
result1,
result2,
savedPathConditions,
savedEffects
);
}
invariant(false);
}
composeGenerators(realm: Realm, generator1: Generator, generator2: Generator): Generator {
// TODO #2222: The path condition of the resulting generator should really just be generator2.pathConditions,
// and we shouldn't have to bring in generator1.pathConditions. We have observed that this causes an issue
// in InstantRender.
let result = new Generator(realm, "composed", generator1.pathConditions.concat(generator2.pathConditions));
// We copy the entries here because actually composing the generators breaks the serializer
if (!generator1.empty()) result.appendGenerator(generator1, "");
if (!generator2.empty()) result.appendGenerator(generator2, "");
return result;
} }
// Creates a single map that joins together maps m1 and m2 using the given join // 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] // 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 // 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. // and abstract value with expression "joinCondition ? m1[key] : m2[key]" if not.
joinBindings( _joinBindings(
realm: Realm,
joinCondition: AbstractValue, joinCondition: AbstractValue,
g1: Generator, g1: Generator,
m1: Bindings, m1: Bindings,
g2: Generator, g2: Generator,
m2: Bindings m2: Bindings
): [Generator, Generator, Bindings] { ): [Generator, Generator, Bindings] {
let realm = joinCondition.$Realm;
let getAbstractValue = (v1: void | Value, v2: void | Value) => { 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 rewritten1 = false;
let rewritten2 = 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. // 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); let value = hasLeaked ? undefined : this.joinValues(realm, v1, v2, getAbstractValue);
invariant(value === undefined || value instanceof Value); invariant(value === undefined || value instanceof Value);
let previousHasLeaked, previousValue; return { hasLeaked, value };
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 };
}; };
let joinedBindings = this.joinMaps(m1, m2, join); let joinedBindings = this.joinMaps(m1, m2, join);
return [g1, g2, joinedBindings]; return [g1, g2, joinedBindings];
@ -910,28 +489,10 @@ export class JoinImplementation {
undefined, undefined,
"mapAndJoin" "mapAndJoin"
); );
joinedEffects = joinedEffects = joinedEffects === undefined ? effects : this.joinEffects(condition, effects, joinedEffects);
joinedEffects === undefined ? effects : this.joinForkOrChoose(realm, condition, effects, joinedEffects);
} }
invariant(joinedEffects !== undefined); 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); realm.applyEffects(joinedEffects);
return realm.returnOrThrowCompletion(joinedEffects.result);
// return or throw completion
if (completion instanceof AbruptCompletion) throw completion;
if (completion instanceof SimpleNormalCompletion) {
completion = completion.value;
}
invariant(completion instanceof Value);
return completion;
} }
} }

View File

@ -9,7 +9,6 @@
/* @flow */ /* @flow */
import { AbruptCompletion, Completion, PossiblyNormalCompletion, SimpleNormalCompletion } from "../completions.js";
import { construct_empty_effects, type Realm, Effects } from "../realm.js"; import { construct_empty_effects, type Realm, Effects } from "../realm.js";
import type { Descriptor, PropertyBinding, PropertyKeyValue } from "../types.js"; import type { Descriptor, PropertyBinding, PropertyKeyValue } from "../types.js";
import { import {
@ -314,57 +313,41 @@ export class PropertiesImplementation {
if (joinCondition !== undefined) { if (joinCondition !== undefined) {
let descriptor2 = ownDesc.descriptor2; let descriptor2 = ownDesc.descriptor2;
ownDesc = ownDesc.descriptor1; 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 { let {
result: result1, result: result1,
generator: generator1, generator: generator1,
modifiedBindings: modifiedBindings1, modifiedBindings: modifiedBindings1,
modifiedProperties: modifiedProperties1, modifiedProperties: modifiedProperties1,
createdObjects: createdObjects1, createdObjects: createdObjects1,
} = Path.withCondition(joinCondition, () => { } = e1;
ownDesc = descriptor2;
let e2 = Path.withInverseCondition(joinCondition, () => {
return ownDesc !== undefined 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); : construct_empty_effects(realm);
}); });
ownDesc = descriptor2;
let { let {
result: result2, result: result2,
generator: generator2, generator: generator2,
modifiedBindings: modifiedBindings2, modifiedBindings: modifiedBindings2,
modifiedProperties: modifiedProperties2, modifiedProperties: modifiedProperties2,
createdObjects: createdObjects2, createdObjects: createdObjects2,
} = Path.withInverseCondition(joinCondition, () => { } = e2;
return ownDesc !== undefined
? realm.evaluateForEffects(() => new BooleanValue(realm, OrdinarySetHelper()), undefined, "OrdinarySet/2")
: construct_empty_effects(realm);
});
// Join the effects, creating an abstract view of what happened, regardless // Join the effects, creating an abstract view of what happened, regardless
// of the actual value of ownDesc.joinCondition. // of the actual value of ownDesc.joinCondition.
if (result1 instanceof Completion) result1 = result1.shallowCloneWithoutEffects(); let joinedEffects = Join.joinEffects(
if (result2 instanceof Completion) result2 = result2.shallowCloneWithoutEffects();
let joinedEffects = Join.joinForkOrChoose(
realm,
joinCondition, joinCondition,
new Effects(result1, generator1, modifiedBindings1, modifiedProperties1, createdObjects1), new Effects(result1, generator1, modifiedBindings1, modifiedProperties1, createdObjects1),
new Effects(result2, generator2, modifiedBindings2, modifiedProperties2, createdObjects2) 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); realm.applyEffects(joinedEffects);
return To.ToBooleanPartial(realm, realm.returnOrThrowCompletion(joinedEffects.result));
// 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 OrdinarySetHelper(); return OrdinarySetHelper();

View File

@ -15,7 +15,7 @@ import type { Bindings, BindingEntry, EvaluationResult, PropertyBindings, Create
import { Effects } from "../realm.js"; import { Effects } from "../realm.js";
import type { Descriptor, PropertyBinding } from "../types.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 { Reference } from "../environment.js";
import { cloneDescriptor, equalDescriptors, IsDataDescriptor, StrictEqualityComparison } from "./index.js"; import { cloneDescriptor, equalDescriptors, IsDataDescriptor, StrictEqualityComparison } from "./index.js";
import { Generator, createOperationDescriptor } from "../utils/generator.js"; import { Generator, createOperationDescriptor } from "../utils/generator.js";
@ -103,7 +103,7 @@ export class WidenImplementation {
realm: Realm, realm: Realm,
result1: EvaluationResult, result1: EvaluationResult,
result2: EvaluationResult result2: EvaluationResult
): PossiblyNormalCompletion | SimpleNormalCompletion { ): JoinedNormalAndAbruptCompletions | SimpleNormalCompletion {
invariant(!(result1 instanceof Reference || result2 instanceof Reference), "loop bodies should not result in refs"); invariant(!(result1 instanceof Reference || result2 instanceof Reference), "loop bodies should not result in refs");
invariant( invariant(
!(result1 instanceof AbruptCompletion || result2 instanceof AbruptCompletion), !(result1 instanceof AbruptCompletion || result2 instanceof AbruptCompletion),
@ -114,7 +114,7 @@ export class WidenImplementation {
invariant(val instanceof Value); invariant(val instanceof Value);
return new SimpleNormalCompletion(val); 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 //todo: #1174 figure out how to deal with loops that have embedded conditional exits
// widen join pathConditions // widen join pathConditions
// widen normal result and Effects // widen normal result and Effects
@ -141,6 +141,9 @@ export class WidenImplementation {
widenBindings(realm: Realm, m1: Bindings, m2: Bindings): Bindings { widenBindings(realm: Realm, m1: Bindings, m2: Bindings): Bindings {
let widen = (b: Binding, b1: void | BindingEntry, b2: void | BindingEntry) => { 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; 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 invariant(b2 !== undefined); // Local variables are not going to get deleted as a result of widening
let v2 = b2.value; let v2 = b2.value;
@ -168,14 +171,7 @@ export class WidenImplementation {
result.operationDescriptor = createOperationDescriptor("WIDENED_IDENTIFIER", { id: phiName }); result.operationDescriptor = createOperationDescriptor("WIDENED_IDENTIFIER", { id: phiName });
} }
invariant(result instanceof Value); invariant(result instanceof Value);
let previousHasLeaked = b2.previousHasLeaked; return { hasLeaked, value: result };
let previousValue = b2.previousValue;
return {
hasLeaked: previousHasLeaked,
value: result,
previousHasLeaked,
previousValue,
};
}; };
return this.widenMaps(m1, m2, widen); return this.widenMaps(m1, m2, widen);
} }

View File

@ -65,7 +65,6 @@ export type RealmOptions = {
export type SerializerOptions = { export type SerializerOptions = {
lazyObjectsRuntime?: string, lazyObjectsRuntime?: string,
delayInitializations?: boolean, delayInitializations?: boolean,
delayUnsupportedRequires?: boolean,
accelerateUnsupportedRequires?: boolean, accelerateUnsupportedRequires?: boolean,
initializeMoreModules?: boolean, initializeMoreModules?: boolean,
internalDebug?: boolean, internalDebug?: boolean,

View File

@ -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<BabelNodeStatement>] {
// 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];
}

View File

@ -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<BabelNodeStatement>] {
let result = env.evaluateCompletionDeref(ast, strictCode);
return [result, ast, []];
}

View File

@ -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<BabelNodeStatement>] {
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
);
}

View File

@ -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<BabelNodeStatement>] {
let result = env.evaluateCompletionDeref(ast, strictCode);
return [result, ast, []];
}

View File

@ -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<BabelNodeStatement>] {
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<BabelNodeStatement>,
realm: Realm
): [Completion | Value, BabelNodeExpression, Array<BabelNodeStatement>] {
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];
}

View File

@ -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<BabelNodeStatement>] {
// 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);
}
}

View File

@ -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<BabelNodeStatement>] {
let result = new BooleanValue(realm, ast.value);
return [result, ast, []];
}

View File

@ -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<BabelNodeStatement>] {
let result = new BreakCompletion(realm.intrinsics.empty, undefined, ast.loc, ast.label && ast.label.name);
return [result, ast, []];
}

View File

@ -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<BabelNodeStatement>] {
// 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<Value>,
ast: BabelNodeCallExpression,
argVals: Array<Value>,
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<Value>,
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;
}
}

View File

@ -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<BabelNodeStatement>] {
invariant(thrownValue instanceof ThrowCompletion, "Metadata isn't a throw completion");
let result = env.evaluateCompletionDeref(ast, strictCode);
return [result, ast, []];
}

View File

@ -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<BabelNodeStatement>] {
let result = env.evaluateCompletionDeref(ast, strictCode);
return [result, ast, []];
}

View File

@ -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<BabelNodeStatement>] {
throw new FatalError("TODO: ClassExpression");
}

View File

@ -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<BabelNodeStatement>] {
let result = env.evaluateCompletionDeref(ast, strictCode);
return [result, ast, []];
}

View File

@ -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<BabelNodeStatement>] {
let result = new ContinueCompletion(realm.intrinsics.empty, undefined, ast.loc, ast.label && ast.label.name);
return [result, ast, []];
}

View File

@ -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<BabelNodeStatement>] {
let result = env.evaluateCompletionDeref(ast.value, strictCode);
return [result, ast, []];
}

View File

@ -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";

View File

@ -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";

View File

@ -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<string>
): [AbruptCompletion | Value, BabelNodeDoWhileStatement, Array<BabelNodeStatement>] {
let result = env.evaluateCompletionDeref(ast, strictCode);
return [result, ast, []];
}

View File

@ -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<BabelNodeStatement>] {
return [realm.intrinsics.empty, ast, []];
}

View File

@ -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<BabelNodeStatement>] {
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];
}

View File

@ -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<BabelNodeStatement>] {
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];
}

View File

@ -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<string>
): [AbruptCompletion | Value, BabelNodeForInStatement, Array<BabelNodeStatement>] {
let result = env.evaluateCompletionDeref(ast, strictCode);
return [result, ast, []];
}

View File

@ -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<string>
): [AbruptCompletion | Value, BabelNodeForOfStatement, Array<BabelNodeStatement>] {
let result = env.evaluateCompletionDeref(ast, strictCode);
return [result, ast, []];
}

View File

@ -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<string>
): [AbruptCompletion | Value, BabelNodeForStatement, Array<BabelNodeStatement>] {
let result = env.evaluateCompletionDeref(ast, strictCode);
return [result, ast, []];
}

View File

@ -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<BabelNodeStatement>] {
let result = env.evaluateCompletionDeref(ast, strictCode);
return [result, ast, []];
}

View File

@ -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<BabelNodeStatement>] {
let result = env.evaluateCompletionDeref(ast, strictCode);
return [result, ast, []];
}

View File

@ -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<BabelNodeStatement>] {
let result = env.evaluateCompletion(ast, strictCode);
return [result, ast, []];
}

View File

@ -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<BabelNodeStatement>] {
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];
}

View File

@ -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<BabelNodeStatement>] {
let result = env.evaluateCompletionDeref(ast, strictCode);
return [result, ast, []];
}

View File

@ -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<BabelNodeStatement>] {
let result = env.evaluateCompletionDeref(ast, strictCode);
return [result, ast, []];
}

View File

@ -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<BabelNodeStatement>] {
let result = env.evaluateCompletion(ast, strictCode);
return [result, ast, []];
}

View File

@ -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<BabelNodeStatement>] {
let result = env.evaluateCompletionDeref(ast, strictCode);
return [result, ast, []];
}

View File

@ -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<BabelNodeStatement>] {
let result = env.evaluateCompletionDeref(ast, strictCode);
return [result, ast, []];
}

View File

@ -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<BabelNodeStatement>] {
let result = realm.intrinsics.null;
return [result, ast, []];
}

View File

@ -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<BabelNodeStatement>] {
let result = new NumberValue(realm, ast.value);
return [result, ast, []];
}

View File

@ -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<BabelNodeStatement>] {
let result = env.evaluateCompletionDeref(ast, strictCode);
return [result, ast, []];
}

View File

@ -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<BabelNodeStatement>] {
strictCode = IsStrict(ast);
GlobalDeclarationInstantiation(realm, ast, env, strictCode);
let partialBody: Array<BabelNodeStatement | BabelNodeModuleDeclaration> = [];
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), []];
}

View File

@ -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<BabelNodeStatement>] {
let result = RegExpCreate(
realm,
new StringValue(realm, ast.pattern),
ast.flags ? new StringValue(realm, ast.flags) : undefined
);
return [result, ast, []];
}

View File

@ -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<BabelNodeStatement>] {
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, []];
}

View File

@ -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<BabelNodeStatement>] {
let result = env.evaluateCompletionDeref(ast, strictCode);
return [result, ast, []];
}

View File

@ -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<BabelNodeStatement>] {
let result = new StringValue(realm, ast.value);
return [result, ast, []];
}

View File

@ -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<string>
): [AbruptCompletion | Value, BabelNodeSwitchStatement, Array<BabelNodeStatement>] {
let result = env.evaluateCompletionDeref(ast, strictCode);
return [result, ast, []];
}

View File

@ -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<BabelNodeStatement>] {
let result = env.evaluateCompletionDeref(ast, strictCode);
return [result, ast, []];
}

View File

@ -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<BabelNodeStatement>] {
let result = env.evaluateCompletionDeref(ast, strictCode);
return [result, ast, []];
}

View File

@ -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<BabelNodeStatement>] {
let result = env.evaluateCompletionDeref(ast, strictCode);
return [result, ast, []];
}

View File

@ -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<BabelNodeStatement>] {
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];
}

View File

@ -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<BabelNodeStatement>] {
let result = env.evaluateCompletionDeref(ast, strictCode);
return [result, ast, []];
}

View File

@ -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<BabelNodeStatement>] {
let result = env.evaluateCompletionDeref(ast, strictCode);
return [result, ast, []];
}

View File

@ -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<BabelNodeStatement>] {
let result = env.evaluateCompletionDeref(ast, strictCode);
return [result, ast, []];
}

View File

@ -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<BabelNodeStatement>] {
let result = env.evaluateCompletionDeref(ast, strictCode);
return [result, ast, []];
}

View File

@ -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<string>
): [AbruptCompletion | Value, BabelNodeWhileStatement, Array<BabelNodeStatement>] {
let result = env.evaluateCompletionDeref(ast, strictCode);
return [result, ast, []];
}

View File

@ -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<BabelNodeStatement>] {
let result = env.evaluateCompletionDeref(ast, strictCode);
return [result, ast, []];
}

View File

@ -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<BabelNodeStatement>] {
throw new FatalError("TODO: YieldExpression");
}

View File

@ -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";

View File

@ -118,7 +118,6 @@ function run(
logStatistics: false, logStatistics: false,
logModules: false, logModules: false,
delayInitializations: false, delayInitializations: false,
delayUnsupportedRequires: false,
accelerateUnsupportedRequires: true, accelerateUnsupportedRequires: true,
internalDebug: false, internalDebug: false,
debugScopes: false, debugScopes: false,

View File

@ -22,7 +22,6 @@ export type PrepackOptions = {|
compatibility?: Compatibility, compatibility?: Compatibility,
debugNames?: boolean, debugNames?: boolean,
delayInitializations?: boolean, delayInitializations?: boolean,
delayUnsupportedRequires?: boolean,
accelerateUnsupportedRequires?: boolean, accelerateUnsupportedRequires?: boolean,
inputSourceMapFilenames?: Array<string>, inputSourceMapFilenames?: Array<string>,
internalDebug?: boolean, internalDebug?: boolean,
@ -122,7 +121,6 @@ export function getSerializerOptions({
lazyObjectsRuntime, lazyObjectsRuntime,
heapGraphFormat, heapGraphFormat,
delayInitializations = false, delayInitializations = false,
delayUnsupportedRequires = false,
accelerateUnsupportedRequires = true, accelerateUnsupportedRequires = true,
internalDebug = false, internalDebug = false,
debugScopes = false, debugScopes = false,
@ -136,7 +134,6 @@ export function getSerializerOptions({
}: PrepackOptions): SerializerOptions { }: PrepackOptions): SerializerOptions {
let result: SerializerOptions = { let result: SerializerOptions = {
delayInitializations, delayInitializations,
delayUnsupportedRequires,
accelerateUnsupportedRequires, accelerateUnsupportedRequires,
initializeMoreModules, initializeMoreModules,
internalDebug, internalDebug,

View File

@ -54,19 +54,13 @@ export function prepackSources(
if (options.check) { if (options.check) {
realm.generator = new Generator(realm, "main", realm.pathConditions); realm.generator = new Generator(realm, "main", realm.pathConditions);
let logger = new Logger(realm, !!options.internalDebug); let logger = new Logger(realm, !!options.internalDebug);
let modules = new Modules( let modules = new Modules(realm, logger, !!options.logModules, !!options.accelerateUnsupportedRequires);
realm,
logger,
!!options.logModules,
!!options.delayUnsupportedRequires,
!!options.accelerateUnsupportedRequires
);
let [result] = realm.$GlobalEnv.executeSources(sourceFileCollection.toArray()); let [result] = realm.$GlobalEnv.executeSources(sourceFileCollection.toArray());
if (result instanceof AbruptCompletion) throw result; if (result instanceof AbruptCompletion) throw result;
invariant(options.check); invariant(options.check);
checkResidualFunctions(modules, options.check[0], options.check[1]); checkResidualFunctions(modules, options.check[0], options.check[1]);
return { code: "", map: undefined }; return { code: "", map: undefined };
} else if (options.serialize === true || options.residual !== true) { } else {
let serializer = new Serializer(realm, getSerializerOptions(options)); let serializer = new Serializer(realm, getSerializerOptions(options));
let serialized = serializer.init(sourceFileCollection, options.sourceMaps, options.onParse); let serialized = serializer.init(sourceFileCollection, options.sourceMaps, options.onParse);
@ -88,30 +82,7 @@ export function prepackSources(
serialized.sourceFilePaths = sourcePaths; serialized.sourceFilePaths = sourcePaths;
} }
if (!options.residual) return serialized; 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 };
} }
} }

View File

@ -10,7 +10,7 @@
/* @flow */ /* @flow */
import { Realm } from "../realm.js"; 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 type { BabelNode, BabelNodeJSXIdentifier } from "@babel/types";
import { parseExpression } from "@babel/parser"; import { parseExpression } from "@babel/parser";
import { import {
@ -795,12 +795,14 @@ export function getValueFromFunctionCall(
let completion; let completion;
let createdObjects = realm.createdObjects; let createdObjects = realm.createdObjects;
try { try {
let value;
if (isConstructor) { if (isConstructor) {
invariant(newCall); invariant(newCall);
completion = newCall(args, func); value = newCall(args, func);
} else { } else {
completion = funcCall(funcThis, args); value = funcCall(funcThis, args);
} }
completion = new SimpleNormalCompletion(value);
} catch (error) { } catch (error) {
if (error instanceof AbruptCompletion) { if (error instanceof AbruptCompletion) {
completion = error; completion = error;
@ -810,18 +812,7 @@ export function getValueFromFunctionCall(
} finally { } finally {
invariant(createdObjects === realm.createdObjects, "realm.createdObjects was not correctly restored"); invariant(createdObjects === realm.createdObjects, "realm.createdObjects was not correctly restored");
} }
if (completion instanceof PossiblyNormalCompletion) { return realm.returnOrThrowCompletion(completion);
// 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;
} }
function isEventProp(name: string): boolean { function isEventProp(name: string): boolean {

View File

@ -44,7 +44,7 @@ import {
UndefinedValue, UndefinedValue,
Value, Value,
} from "./values/index.js"; } from "./values/index.js";
import type { TypesDomain, ValuesDomain } from "./domains/index.js"; import { TypesDomain, ValuesDomain } from "./domains/index.js";
import { import {
LexicalEnvironment, LexicalEnvironment,
Reference, Reference,
@ -57,8 +57,9 @@ import { cloneDescriptor, Construct } from "./methods/index.js";
import { import {
AbruptCompletion, AbruptCompletion,
Completion, Completion,
ForkedAbruptCompletion, JoinedAbruptCompletions,
PossiblyNormalCompletion, JoinedNormalAndAbruptCompletions,
NormalCompletion,
SimpleNormalCompletion, SimpleNormalCompletion,
ThrowCompletion, ThrowCompletion,
} from "./completions.js"; } from "./completions.js";
@ -67,16 +68,10 @@ import invariant from "./invariant.js";
import seedrandom from "seedrandom"; import seedrandom from "seedrandom";
import { createOperationDescriptor, Generator, type TemporalOperationEntry } from "./utils/generator.js"; import { createOperationDescriptor, Generator, type TemporalOperationEntry } from "./utils/generator.js";
import { PreludeGenerator } from "./utils/PreludeGenerator.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 { ReactSymbolTypes } from "./react/utils.js";
import type { BabelNode, BabelNodeSourceLocation, BabelNodeLVal, BabelNodeStatement } from "@babel/types"; import type { BabelNode, BabelNodeSourceLocation, BabelNodeLVal } from "@babel/types";
import { Utils } from "./singletons.js"; export type BindingEntry = { hasLeaked: boolean, value: void | Value };
export type BindingEntry = {
hasLeaked: void | boolean,
value: void | Value,
previousHasLeaked: void | boolean,
previousValue: void | Value,
};
export type Bindings = Map<Binding, BindingEntry>; export type Bindings = Map<Binding, BindingEntry>;
export type EvaluationResult = Completion | Reference; export type EvaluationResult = Completion | Reference;
export type PropertyBindings = Map<PropertyBinding, void | Descriptor>; export type PropertyBindings = Map<PropertyBinding, void | Descriptor>;
@ -95,7 +90,7 @@ export class Effects {
propertyBindings: PropertyBindings, propertyBindings: PropertyBindings,
createdObjects: CreatedObjects createdObjects: CreatedObjects
) { ) {
this._result = result; this.result = result;
this.generator = generator; this.generator = generator;
this.modifiedBindings = bindings; this.modifiedBindings = bindings;
this.modifiedProperties = propertyBindings; this.modifiedProperties = propertyBindings;
@ -103,20 +98,9 @@ export class Effects {
this.canBeApplied = true; this.canBeApplied = true;
this._id = effects_uid++; 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; generator: Generator;
modifiedBindings: Bindings; modifiedBindings: Bindings;
modifiedProperties: PropertyBindings; modifiedProperties: PropertyBindings;
@ -219,11 +203,8 @@ export class ExecutionContext {
export function construct_empty_effects( export function construct_empty_effects(
realm: Realm, realm: Realm,
c: Completion = new SimpleNormalCompletion(realm.intrinsics.empty, undefined) c: Completion = new SimpleNormalCompletion(realm.intrinsics.empty)
): Effects { ): 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( return new Effects(
c, c,
new Generator(realm, "construct_empty_effects", realm.pathConditions), new Generator(realm, "construct_empty_effects", realm.pathConditions),
@ -281,7 +262,6 @@ export class Realm {
this.intrinsics = ({}: any); this.intrinsics = ({}: any);
this.$GlobalObject = (({}: any): ObjectValue); this.$GlobalObject = (({}: any): ObjectValue);
this.evaluators = (Object.create(null): any); this.evaluators = (Object.create(null): any);
this.partialEvaluators = (Object.create(null): any);
this.$GlobalEnv = ((undefined: any): LexicalEnvironment); this.$GlobalEnv = ((undefined: any): LexicalEnvironment);
this.derivedIds = new Map(); this.derivedIds = new Map();
@ -369,7 +349,7 @@ export class Realm {
(sideEffectType: SideEffectType, binding: void | Binding | PropertyBinding, expressionLocation: any) => void (sideEffectType: SideEffectType, binding: void | Binding | PropertyBinding, expressionLocation: any) => void
>; >;
reportPropertyAccess: void | (PropertyBinding => void); reportPropertyAccess: void | (PropertyBinding => void);
savedCompletion: void | PossiblyNormalCompletion; savedCompletion: void | JoinedNormalAndAbruptCompletions;
activeLexicalEnvironments: Set<LexicalEnvironment>; activeLexicalEnvironments: Set<LexicalEnvironment>;
@ -447,15 +427,6 @@ export class Realm {
metadata?: any metadata?: any
) => Value | Reference, ) => Value | Reference,
}; };
partialEvaluators: {
[key: string]: (
ast: BabelNode,
strictCode: boolean,
env: LexicalEnvironment,
realm: Realm,
metadata?: any
) => [Completion | Reference | Value, BabelNode, Array<BabelNodeStatement>],
};
simplifyAndRefineAbstractValue: AbstractValue => Value; simplifyAndRefineAbstractValue: AbstractValue => Value;
simplifyAndRefineAbstractCondition: AbstractValue => Value; simplifyAndRefineAbstractCondition: AbstractValue => Value;
@ -560,26 +531,6 @@ export class Realm {
} }
} }
clearBlockBindingsFromCompletion(completion: Completion, environmentRecord: DeclarativeEnvironmentRecord): void {
if (completion instanceof PossiblyNormalCompletion) {
this.clearBlockBindings(completion.alternateEffects.modifiedBindings, environmentRecord);
this.clearBlockBindings(completion.consequentEffects.modifiedBindings, environmentRecord);
if (completion.savedEffects !== undefined)
this.clearBlockBindings(completion.savedEffects.modifiedBindings, environmentRecord);
if (completion.alternate instanceof Completion)
this.clearBlockBindingsFromCompletion(completion.alternate, environmentRecord);
if (completion.consequent instanceof Completion)
this.clearBlockBindingsFromCompletion(completion.consequent, environmentRecord);
} else if (completion instanceof ForkedAbruptCompletion) {
this.clearBlockBindings(completion.alternateEffects.modifiedBindings, environmentRecord);
this.clearBlockBindings(completion.consequentEffects.modifiedBindings, environmentRecord);
if (completion.alternate instanceof Completion)
this.clearBlockBindingsFromCompletion(completion.alternate, environmentRecord);
if (completion.consequent instanceof Completion)
this.clearBlockBindingsFromCompletion(completion.consequent, environmentRecord);
}
}
// Call when a scope falls out of scope and should be destroyed. // Call when a scope falls out of scope and should be destroyed.
// Clears the Bindings corresponding to the disappearing Scope from ModifiedBindings // Clears the Bindings corresponding to the disappearing Scope from ModifiedBindings
onDestroyScope(lexicalEnvironment: LexicalEnvironment): void { onDestroyScope(lexicalEnvironment: LexicalEnvironment): void {
@ -590,8 +541,6 @@ export class Realm {
let environmentRecord = lexicalEnvironment.environmentRecord; let environmentRecord = lexicalEnvironment.environmentRecord;
if (environmentRecord instanceof DeclarativeEnvironmentRecord) { if (environmentRecord instanceof DeclarativeEnvironmentRecord) {
this.clearBlockBindings(modifiedBindings, environmentRecord); this.clearBlockBindings(modifiedBindings, environmentRecord);
if (this.savedCompletion !== undefined)
this.clearBlockBindingsFromCompletion(this.savedCompletion, environmentRecord);
} }
} }
@ -633,31 +582,10 @@ export class Realm {
} }
} }
clearFunctionBindingsFromCompletion(completion: Completion, funcVal: FunctionValue): void {
if (completion instanceof PossiblyNormalCompletion) {
this.clearFunctionBindings(completion.alternateEffects.modifiedBindings, funcVal);
this.clearFunctionBindings(completion.consequentEffects.modifiedBindings, funcVal);
if (completion.savedEffects !== undefined)
this.clearFunctionBindings(completion.savedEffects.modifiedBindings, funcVal);
if (completion.alternate instanceof Completion)
this.clearFunctionBindingsFromCompletion(completion.alternate, funcVal);
if (completion.consequent instanceof Completion)
this.clearFunctionBindingsFromCompletion(completion.consequent, funcVal);
} else if (completion instanceof ForkedAbruptCompletion) {
this.clearFunctionBindings(completion.alternateEffects.modifiedBindings, funcVal);
this.clearFunctionBindings(completion.consequentEffects.modifiedBindings, funcVal);
if (completion.alternate instanceof Completion)
this.clearFunctionBindingsFromCompletion(completion.alternate, funcVal);
if (completion.consequent instanceof Completion)
this.clearFunctionBindingsFromCompletion(completion.consequent, funcVal);
}
}
popContext(context: ExecutionContext): void { popContext(context: ExecutionContext): void {
let funcVal = context.function; let funcVal = context.function;
if (funcVal) { if (funcVal) {
this.clearFunctionBindings(this.modifiedBindings, funcVal); this.clearFunctionBindings(this.modifiedBindings, funcVal);
if (this.savedCompletion !== undefined) this.clearFunctionBindingsFromCompletion(this.savedCompletion, funcVal);
} }
let c = this.contextStack.pop(); let c = this.contextStack.pop();
invariant(c === context); invariant(c === context);
@ -733,7 +661,11 @@ export class Realm {
bubbleSideEffectReports: boolean, bubbleSideEffectReports: boolean,
reportSideEffectFunc: reportSideEffectFunc:
| null | null
| ((sideEffectType: SideEffectType, binding: void | Binding | PropertyBinding, value: void | Value) => void) | ((
sideEffectType: SideEffectType,
binding: void | Binding | PropertyBinding,
location: ?BabelNodeSourceLocation
) => void)
): T { ): T {
let saved_createdObjectsTrackedForLeaks = this.createdObjectsTrackedForLeaks; let saved_createdObjectsTrackedForLeaks = this.createdObjectsTrackedForLeaks;
let saved_reportSideEffectCallbacks; let saved_reportSideEffectCallbacks;
@ -787,7 +719,7 @@ export class Realm {
invariant(this.isInPureScope(), "only abstract abrupt completion in pure functions"); 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. // 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. // 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. // Currently we just issue a recoverable error instead if this might matter.
let value = f(); let value = f();
@ -834,7 +766,7 @@ export class Realm {
result = func(effects); result = func(effects);
return this.intrinsics.undefined; return this.intrinsics.undefined;
} finally { } finally {
this.undoBindings(effects.modifiedBindings); this.restoreBindings(effects.modifiedBindings);
this.restoreProperties(effects.modifiedProperties); this.restoreProperties(effects.modifiedProperties);
invariant(!effects.canBeApplied); invariant(!effects.canBeApplied);
effects.canBeApplied = true; effects.canBeApplied = true;
@ -848,22 +780,6 @@ export class Realm {
return this.wrapInGlobalEnv(() => this.evaluateNodeForEffects(node, false, this.$GlobalEnv, state, generatorName)); return this.wrapInGlobalEnv(() => this.evaluateNodeForEffects(node, false, this.$GlobalEnv, state, generatorName));
} }
partiallyEvaluateNodeForEffects(
ast: BabelNode,
strictCode: boolean,
env: LexicalEnvironment
): [Effects, BabelNode, Array<BabelNodeStatement>] {
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 // Use this to evaluate code for internal purposes, so that the tracked state does not get polluted
evaluateWithoutEffects<T>(f: () => T): T { evaluateWithoutEffects<T>(f: () => T): T {
// Save old state and set up undefined state // Save old state and set up undefined state
@ -912,21 +828,9 @@ export class Realm {
if (e instanceof AbruptCompletion) c = e; if (e instanceof AbruptCompletion) c = e;
else throw e; else throw e;
} }
// This is a join point for the normal branch of a PossiblyNormalCompletion. // This is a join point for any normal completions inside realm.savedCompletion
if (c instanceof Value || c instanceof AbruptCompletion) { c = Functions.incorporateSavedCompletion(this, c);
c = Functions.incorporateSavedCompletion(this, c);
if (c instanceof Completion && c.effects !== undefined) c = c.shallowCloneWithoutEffects();
}
invariant(c !== undefined); 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.generator !== undefined);
invariant(this.modifiedBindings !== undefined); invariant(this.modifiedBindings !== undefined);
@ -952,12 +856,14 @@ export class Realm {
return result; return result;
} finally { } finally {
// Roll back the state changes // Roll back the state changes
if (this.savedCompletion !== undefined) this.stopEffectCaptureAndUndoEffects(this.savedCompletion);
if (result !== undefined) { if (result !== undefined) {
this.undoBindings(result.modifiedBindings); this.restoreBindings(result.modifiedBindings);
this.restoreProperties(result.modifiedProperties); this.restoreProperties(result.modifiedProperties);
} else { } else {
this.undoBindings(this.modifiedBindings); if (this.savedCompletion !== undefined) {
this.stopEffectCaptureAndUndoEffects(this.savedCompletion);
}
this.restoreBindings(this.modifiedBindings);
this.restoreProperties(this.modifiedProperties); this.restoreProperties(this.modifiedProperties);
} }
this.generator = saved_generator; this.generator = saved_generator;
@ -1015,14 +921,6 @@ export class Realm {
this.applyEffects(effects); this.applyEffects(effects);
let resultVal = effects.result; let resultVal = effects.result;
if (resultVal instanceof AbruptCompletion) throw resultVal; 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; return resultVal.value;
} catch (e) { } catch (e) {
if (diagnostic !== undefined) return diagnostic; if (diagnostic !== undefined) return diagnostic;
@ -1044,10 +942,10 @@ export class Realm {
}; };
let effects1 = this.evaluateForEffects(f, undefined, "evaluateForFixpointEffects/1"); let effects1 = this.evaluateForEffects(f, undefined, "evaluateForFixpointEffects/1");
while (true) { while (true) {
this.redoBindings(effects1.modifiedBindings); this.restoreBindings(effects1.modifiedBindings);
this.restoreProperties(effects1.modifiedProperties); this.restoreProperties(effects1.modifiedProperties);
let effects2 = this.evaluateForEffects(f, undefined, "evaluateForFixpointEffects/2"); let effects2 = this.evaluateForEffects(f, undefined, "evaluateForFixpointEffects/2");
this.undoBindings(effects1.modifiedBindings); this.restoreBindings(effects1.modifiedBindings);
this.restoreProperties(effects1.modifiedProperties); this.restoreProperties(effects1.modifiedProperties);
if (Widen.containsEffects(effects1, effects2)) { if (Widen.containsEffects(effects1, effects2)) {
// effects1 includes every value present in effects2, so doing another iteration using effects2 will not // effects1 includes every value present in effects2, so doing another iteration using effects2 will not
@ -1087,7 +985,6 @@ export class Realm {
} catch (e) { } catch (e) {
if (!(e instanceof InfeasiblePathError)) throw e; if (!(e instanceof InfeasiblePathError)) throw e;
} }
invariant(effects1 === undefined || effects1.result.effects === effects1);
let effects2; let effects2;
try { try {
@ -1095,41 +992,20 @@ export class Realm {
} catch (e) { } catch (e) {
if (!(e instanceof InfeasiblePathError)) throw 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) {
if (effects1 === undefined && effects2 === undefined) throw new InfeasiblePathError(); if (effects1 === undefined && effects2 === undefined) throw new InfeasiblePathError();
joinedEffects = effects1 || effects2; effects = effects1 || effects2;
invariant(joinedEffects !== undefined); invariant(effects !== undefined);
completion = joinedEffects.result;
this.applyEffects(joinedEffects, "evaluateWithAbstractConditional");
} else { } else {
// Join the effects, creating an abstract view of what happened, regardless // Join the effects, creating an abstract view of what happened, regardless
// of the actual value of condValue. // of the actual value of condValue.
joinedEffects = Join.joinForkOrChoose(this, condValue, effects1, effects2); effects = Join.joinEffects(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");
}
} }
this.applyEffects(effects);
// return or throw completion return condValue.$Realm.returnOrThrowCompletion(effects.result);
if (completion instanceof AbruptCompletion) throw completion;
if (completion instanceof SimpleNormalCompletion) completion = completion.value;
invariant(completion instanceof Value);
return completion;
} }
_applyPropertiesToNewlyCreatedObjects( _applyPropertiesToNewlyCreatedObjects(
@ -1225,187 +1101,126 @@ export class Realm {
}); });
} }
composeEffects(priorEffects: Effects, subsequentEffects: Effects): Effects { returnOrThrowCompletion(completion: Completion | Value): Value {
let result = construct_empty_effects(this, subsequentEffects.result.shallowCloneWithoutEffects()); if (completion instanceof Value) completion = new SimpleNormalCompletion(completion);
if (completion instanceof AbruptCompletion) {
result.generator = Join.composeGenerators( let c = Functions.incorporateSavedCompletion(this, completion);
this, invariant(c instanceof Completion);
priorEffects.generator || result.generator, completion = c;
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)); let cc = this.composeWithSavedCompletion(completion);
if (cc instanceof AbruptCompletion) throw cc;
if (priorEffects.modifiedProperties) { return cc.value;
priorEffects.modifiedProperties.forEach((desc, propertyBinding, m) =>
result.modifiedProperties.set(propertyBinding, desc)
);
}
subsequentEffects.modifiedProperties.forEach((val, key, m) => result.modifiedProperties.set(key, val));
if (priorEffects.createdObjects) {
priorEffects.createdObjects.forEach((ob, a) => result.createdObjects.add(ob));
}
subsequentEffects.createdObjects.forEach((ob, a) => result.createdObjects.add(ob));
return result;
} }
updateAbruptCompletions(priorEffects: Effects, c: PossiblyNormalCompletion): void { composeWithSavedCompletion(completion: Completion): Completion {
if (c.consequent instanceof AbruptCompletion) {
c.consequent.effects = this.composeEffects(priorEffects, c.consequentEffects);
let alternate = c.alternate;
if (alternate instanceof PossiblyNormalCompletion) this.updateAbruptCompletions(priorEffects, alternate);
} else {
invariant(c.alternate instanceof AbruptCompletion);
c.alternate.effects = this.composeEffects(priorEffects, c.alternateEffects);
let consequent = c.consequent;
if (consequent instanceof PossiblyNormalCompletion) this.updateAbruptCompletions(priorEffects, consequent);
}
}
wrapSavedCompletion(completion: PossiblyNormalCompletion): void {
if (this.savedCompletion !== undefined) {
if (completion.consequent instanceof AbruptCompletion) {
completion.alternate = this.savedCompletion;
} else {
completion.consequent = this.savedCompletion;
}
completion.savedEffects = this.savedCompletion.savedEffects;
} else {
this.captureEffects(completion);
}
this.savedCompletion = completion;
}
composeWithSavedCompletion(completion: PossiblyNormalCompletion): Value {
if (this.savedCompletion === undefined) { if (this.savedCompletion === undefined) {
this.savedCompletion = completion; if (completion instanceof JoinedNormalAndAbruptCompletions) {
this.savedCompletion.savedPathConditions = this.pathConditions; this.savedCompletion = completion;
this.pathConditions = [].concat(this.pathConditions); this.pushPathConditionsLeadingToCompletionOfType(NormalCompletion, completion);
this.captureEffects(completion); this.captureEffects(completion);
}
return completion;
} else { } else {
let savedCompletion = this.savedCompletion; let cc = Join.composeCompletions(this.savedCompletion, completion);
let e = this.getCapturedEffects(); if (cc instanceof JoinedNormalAndAbruptCompletions) {
this.stopEffectCaptureAndUndoEffects(savedCompletion); this.savedCompletion = cc;
savedCompletion = Join.composePossiblyNormalCompletions(this, savedCompletion, completion, e); this.pushPathConditionsLeadingToCompletionOfType(NormalCompletion, completion);
this.applyEffects(e); if (cc.savedEffects === undefined) this.captureEffects(cc);
this.captureEffects(savedCompletion); } else {
this.savedCompletion = savedCompletion; this.savedCompletion = undefined;
}
return cc;
} }
let realm = this; }
pushPathConditionsLeadingToNormalCompletion(completion);
return completion.value;
function pushPathConditionsLeadingToNormalCompletion(c: ForkedAbruptCompletion | PossiblyNormalCompletion) { pushPathConditionsLeadingToCompletionOfType(CompletionType: typeof Completion, completion: Completion): void {
if (allPathsAreAbrupt(c.consequent)) { let realm = this;
Path.pushInverseAndRefine(c.joinCondition); let bottomValue = realm.intrinsics.__bottomValue;
if (c.alternate instanceof PossiblyNormalCompletion || c.alternate instanceof ForkedAbruptCompletion) // Note that if a completion of type CompletionType has a value is that is bottom, that completion is unreachable
pushPathConditionsLeadingToNormalCompletion(c.alternate); // and pushing its corresponding path condition would cause an InfeasiblePathError to be thrown.
} else if (allPathsAreAbrupt(c.alternate)) { if (completion instanceof JoinedAbruptCompletions || completion instanceof JoinedNormalAndAbruptCompletions) {
Path.pushAndRefine(c.joinCondition); if (completion.consequent.value === bottomValue || allPathsAreDifferent(completion.consequent)) {
if (c.consequent instanceof PossiblyNormalCompletion || c.consequent instanceof ForkedAbruptCompletion) if (completion.alternate.value === bottomValue || allPathsAreDifferent(completion.alternate)) return;
pushPathConditionsLeadingToNormalCompletion(c.consequent); Path.pushInverseAndRefine(completion.joinCondition);
} else if (allPathsAreNormal(c.consequent)) { this.pushPathConditionsLeadingToCompletionOfType(CompletionType, completion.alternate);
if (!allPathsAreNormal(c.alternate)) { } else if (completion.alternate.value === bottomValue || allPathsAreDifferent(completion.alternate)) {
let alternatePC = getNormalPathConditionFor(c.alternate); if (completion.consequent.value === bottomValue) return;
let disjunct = AbstractValue.createFromLogicalOp(realm, "||", c.joinCondition, alternatePC); 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); Path.pushAndRefine(disjunct);
} }
} else if (allPathsAreNormal(c.alternate)) { } else if (allPathsAreTheSame(completion.alternate)) {
let consequentPC = getNormalPathConditionFor(c.consequent); let consequentPC = getPathConditionForSame(completion.consequent);
let inverse = AbstractValue.createFromUnaryOp(realm, "!", c.joinCondition); let inverse = AbstractValue.createFromUnaryOp(realm, "!", completion.joinCondition);
let disjunct = AbstractValue.createFromLogicalOp(realm, "||", inverse, consequentPC); let disjunct = AbstractValue.createFromLogicalOp(realm, "||", inverse, consequentPC);
Path.pushAndRefine(disjunct); Path.pushAndRefine(disjunct);
} else { } else {
let jc = c.joinCondition; let jc = completion.joinCondition;
let consequentPC = AbstractValue.createFromLogicalOp(realm, "&&", jc, getNormalPathConditionFor(c.consequent)); let cpc = AbstractValue.createFromLogicalOp(realm, "&&", jc, getPathConditionForSame(completion.consequent));
let ijc = AbstractValue.createFromUnaryOp(realm, "!", jc); let ijc = AbstractValue.createFromUnaryOp(realm, "!", jc);
let alternatePC = AbstractValue.createFromLogicalOp(realm, "&&", ijc, getNormalPathConditionFor(c.alternate)); let apc = AbstractValue.createFromLogicalOp(realm, "&&", ijc, getPathConditionForSame(completion.alternate));
let disjunct = AbstractValue.createFromLogicalOp(realm, "||", consequentPC, alternatePC); let disjunct = AbstractValue.createFromLogicalOp(realm, "||", cpc, apc);
Path.pushAndRefine(disjunct); Path.pushAndRefine(disjunct);
} }
} }
return;
function allPathsAreAbrupt(c: Completion): boolean { function allPathsAreDifferent(c: Completion): boolean {
if (c instanceof ForkedAbruptCompletion) return allPathsAreAbrupt(c.consequent) && allPathsAreAbrupt(c.alternate); if (c instanceof JoinedAbruptCompletions || c instanceof JoinedNormalAndAbruptCompletions)
if (c instanceof AbruptCompletion) return true; return allPathsAreDifferent(c.consequent) && allPathsAreDifferent(c.alternate);
return false; if (c instanceof CompletionType) return false;
}
function allPathsAreNormal(c: Completion): boolean {
if (c instanceof PossiblyNormalCompletion || c instanceof ForkedAbruptCompletion)
return allPathsAreNormal(c.consequent) && allPathsAreNormal(c.alternate);
if (c instanceof AbruptCompletion) return false;
return true; return true;
} }
function getNormalPathConditionFor(c: Completion): Value { function allPathsAreTheSame(c: Completion): boolean {
invariant(c instanceof PossiblyNormalCompletion || c instanceof ForkedAbruptCompletion); if (c instanceof JoinedAbruptCompletions || c instanceof JoinedNormalAndAbruptCompletions)
if (allPathsAreAbrupt(c.consequent)) { return allPathsAreTheSame(c.consequent) && allPathsAreTheSame(c.alternate);
invariant(!allPathsAreAbrupt(c.alternate)); if (c instanceof CompletionType) return true;
return false;
}
function getPathConditionForSame(c: Completion): Value {
invariant(c instanceof JoinedAbruptCompletions || c instanceof JoinedNormalAndAbruptCompletions);
if (c.consequent.value === bottomValue || allPathsAreDifferent(c.consequent)) {
invariant(!allPathsAreDifferent(c.alternate));
let inverse = AbstractValue.createFromUnaryOp(realm, "!", c.joinCondition); let inverse = AbstractValue.createFromUnaryOp(realm, "!", c.joinCondition);
if (allPathsAreNormal(c.alternate)) return inverse; if (allPathsAreTheSame(c.alternate)) return inverse;
return AbstractValue.createFromLogicalOp(realm, "&&", inverse, getNormalPathConditionFor(c.alternate)); return AbstractValue.createFromLogicalOp(realm, "&&", inverse, getPathConditionForSame(c.alternate));
} else if (allPathsAreAbrupt(c.alternate)) { } else if (c.alternate.value === bottomValue || allPathsAreDifferent(c.alternate)) {
invariant(!allPathsAreAbrupt(c.consequent)); invariant(!allPathsAreDifferent(c.consequent));
if (allPathsAreNormal(c.consequent)) return c.joinCondition; if (allPathsAreTheSame(c.consequent)) return c.joinCondition;
return AbstractValue.createFromLogicalOp(realm, "&&", c.joinCondition, getNormalPathConditionFor(c.consequent)); return AbstractValue.createFromLogicalOp(realm, "&&", c.joinCondition, getPathConditionForSame(c.consequent));
} else if (allPathsAreNormal(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. // In principle the simplifier shoud reduce the result of the else clause to this case. This does less work.
invariant(!allPathsAreNormal(c.alternate)); invariant(!allPathsAreTheSame(c.alternate));
invariant(!allPathsAreAbrupt(c.alternate)); invariant(!allPathsAreDifferent(c.alternate));
let ijc = AbstractValue.createFromUnaryOp(realm, "!", c.joinCondition); 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); 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. // In principle the simplifier shoud reduce the result of the else clause to this case. This does less work.
invariant(!allPathsAreNormal(c.consequent)); invariant(!allPathsAreTheSame(c.consequent));
invariant(!allPathsAreAbrupt(c.consequent)); invariant(!allPathsAreDifferent(c.consequent));
let jc = c.joinCondition; 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 ijc = AbstractValue.createFromUnaryOp(realm, "!", jc);
return AbstractValue.createFromLogicalOp(realm, "||", consequentPC, ijc); return AbstractValue.createFromLogicalOp(realm, "||", consequentPC, ijc);
} else { } else {
let jc = c.joinCondition; 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 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); return AbstractValue.createFromLogicalOp(realm, "||", consequentPC, alternatePC);
} }
} }
} }
incorporatePriorSavedCompletion(priorCompletion: void | PossiblyNormalCompletion): void { captureEffects(completion: JoinedNormalAndAbruptCompletions): 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 {
invariant(completion.savedEffects === undefined); invariant(completion.savedEffects === undefined);
completion.savedEffects = new Effects( completion.savedEffects = new Effects(
new SimpleNormalCompletion(this.intrinsics.undefined), new SimpleNormalCompletion(this.intrinsics.undefined),
@ -1420,13 +1235,13 @@ export class Realm {
this.createdObjects = new Set(); 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.generator !== undefined);
invariant(this.modifiedBindings !== undefined); invariant(this.modifiedBindings !== undefined);
invariant(this.modifiedProperties !== undefined); invariant(this.modifiedProperties !== undefined);
invariant(this.createdObjects !== undefined); invariant(this.createdObjects !== undefined);
return new Effects( return new Effects(
new SimpleNormalCompletion(v), v instanceof Completion ? v : new SimpleNormalCompletion(v),
this.generator, this.generator,
this.modifiedBindings, this.modifiedBindings,
this.modifiedProperties, this.modifiedProperties,
@ -1434,9 +1249,9 @@ export class Realm {
); );
} }
stopEffectCaptureAndUndoEffects(completion: PossiblyNormalCompletion): void { stopEffectCaptureAndUndoEffects(completion: JoinedNormalAndAbruptCompletions): void {
// Roll back the state changes // Roll back the state changes
this.undoBindings(this.modifiedBindings); this.restoreBindings(this.modifiedBindings);
this.restoreProperties(this.modifiedProperties); this.restoreProperties(this.modifiedProperties);
// Restore saved state // Restore saved state
@ -1465,7 +1280,7 @@ export class Realm {
if (appendGenerator) this.appendGenerator(generator, leadingComment); if (appendGenerator) this.appendGenerator(generator, leadingComment);
// Restore modifiedBindings // Restore modifiedBindings
this.redoBindings(modifiedBindings); this.restoreBindings(modifiedBindings);
this.restoreProperties(modifiedProperties); this.restoreProperties(modifiedProperties);
// track modifiedBindings // track modifiedBindings
@ -1567,10 +1382,8 @@ export class Realm {
if (this.modifiedBindings !== undefined && !this.modifiedBindings.has(binding)) { if (this.modifiedBindings !== undefined && !this.modifiedBindings.has(binding)) {
this.modifiedBindings.set(binding, { this.modifiedBindings.set(binding, {
hasLeaked: undefined, hasLeaked: binding.hasLeaked,
value: undefined, value: binding.value,
previousHasLeaked: binding.hasLeaked,
previousValue: binding.value,
}); });
} }
return binding; return binding;
@ -1648,21 +1461,17 @@ export class Realm {
return result; 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; if (modifiedBindings === undefined) return;
modifiedBindings.forEach(({ hasLeaked, value }, binding, m) => { modifiedBindings.forEach(({ hasLeaked, value }, binding, m) => {
binding.hasLeaked = hasLeaked || false; let l = binding.hasLeaked;
let v = binding.value;
binding.hasLeaked = hasLeaked;
binding.value = value; binding.value = value;
}); m.set(binding, { hasLeaked: l, value: v });
}
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;
}); });
} }
@ -1755,7 +1564,7 @@ export class Realm {
if (typeof message === "string") message = new StringValue(this, message); if (typeof message === "string") message = new StringValue(this, message);
invariant(message instanceof StringValue); invariant(message instanceof StringValue);
this.nextContextLocation = this.currentLocation; 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 { appendGenerator(generator: Generator, leadingComment: string = ""): void {

View File

@ -10,7 +10,7 @@
/* @flow */ /* @flow */
import type { BabelNodeSourceLocation } from "@babel/types"; import type { BabelNodeSourceLocation } from "@babel/types";
import { Completion, PossiblyNormalCompletion } from "../completions.js"; import { AbruptCompletion } from "../completions.js";
import { CompilerDiagnostic, FatalError } from "../errors.js"; import { CompilerDiagnostic, FatalError } from "../errors.js";
import invariant from "../invariant.js"; import invariant from "../invariant.js";
import { type Effects, type PropertyBindings, Realm } from "../realm.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 call = Utils.createModelledFunctionCall(this.realm, functionValue, argModel);
let realm = this.realm; let realm = this.realm;
let logCompilerDiagnostic = (msg: string) => { let logCompilerDiagnostic = (msg: string, location: ?BabelNodeSourceLocation) => {
let error = new CompilerDiagnostic(msg, undefined, "PP1007", "Warning"); let error = new CompilerDiagnostic(msg, location, "PP1007", "Warning");
realm.handleError(error); realm.handleError(error);
}; };
let effects: Effects = realm.evaluatePure( let effects: Effects = realm.evaluatePure(
@ -293,7 +293,7 @@ export class Functions {
invariant(additionalFunctionEffects !== undefined); invariant(additionalFunctionEffects !== undefined);
let e1 = additionalFunctionEffects.effects; let e1 = additionalFunctionEffects.effects;
invariant(e1 !== undefined); invariant(e1 !== undefined);
if (e1.result instanceof Completion && !e1.result instanceof PossiblyNormalCompletion) { if (e1.result instanceof AbruptCompletion) {
let error = new CompilerDiagnostic( let error = new CompilerDiagnostic(
`Additional function ${fun1Name} will terminate abruptly`, `Additional function ${fun1Name} will terminate abruptly`,
e1.result.location, e1.result.location,

View File

@ -51,7 +51,6 @@ export class Serializer {
this.realm, this.realm,
this.logger, this.logger,
!!serializerOptions.logModules, !!serializerOptions.logModules,
!!serializerOptions.delayUnsupportedRequires,
!!serializerOptions.accelerateUnsupportedRequires !!serializerOptions.accelerateUnsupportedRequires
); );
this.functions = new Functions(this.realm, this.modules.moduleTracer); this.functions = new Functions(this.realm, this.modules.moduleTracer);

Some files were not shown because too many files have changed in this diff Show More