mirror of
https://github.com/facebookarchive/prepack.git
synced 2024-09-11 14:46:37 +03:00
Reduce places where a PossiblyNormalCompletion can be returned.
Summary: Release note: none A PossiblyNormalCompletion is neither normal nor abrupt but a weird entanglement of the two. Hence such a completion should neither be returned from an evaluation function nor thrown, unless of course, a completion is actually expected (evaluateCompletion), in which case it is returned just like any other completion. This request establishes this behavior and changes the return types of the evaluate functions accordingly. In terms of behavior, it now consistently stores possibly normal completions in the current context and updates them there as appropriate. Only when a completion is explicitly requested, does the wave function collapse. One side effect of this is that joined abrupt completions are also better supported now and they turn into possibly normal completions when incorporate one or more return completions and become the return value of a call. This means that early termination is no longer necessary when a joined abrupt completion is encountered. Closes https://github.com/facebook/prepack/pull/1072 Reviewed By: cblappert Differential Revision: D6027356 Pulled By: hermanventer fbshipit-source-id: 7aca428f0ecf07a5865de842037fe1eda3b1817f
This commit is contained in:
parent
f7025fc2ed
commit
1aa2f7baa8
@ -81,6 +81,18 @@ export class JoinedAbruptCompletions extends AbruptCompletion {
|
||||
consequentEffects: Effects;
|
||||
alternate: AbruptCompletion;
|
||||
alternateEffects: Effects;
|
||||
|
||||
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 JoinedAbruptCompletions) {
|
||||
if (this.consequent.containsBreakOrContinue()) return true;
|
||||
}
|
||||
if (this.alternate instanceof JoinedAbruptCompletions) {
|
||||
if (this.alternate.containsBreakOrContinue()) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Possibly normal completions have to be treated like normal completions
|
||||
@ -129,4 +141,16 @@ export class PossiblyNormalCompletion extends NormalCompletion {
|
||||
consequentEffects: Effects;
|
||||
alternate: Completion | Value;
|
||||
alternateEffects: Effects;
|
||||
|
||||
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 JoinedAbruptCompletions || this.consequent instanceof PossiblyNormalCompletion) {
|
||||
if (this.consequent.containsBreakOrContinue()) return true;
|
||||
}
|
||||
if (this.alternate instanceof JoinedAbruptCompletions || this.alternate instanceof PossiblyNormalCompletion) {
|
||||
if (this.alternate.containsBreakOrContinue()) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -25,7 +25,6 @@ import {
|
||||
AbruptCompletion,
|
||||
Completion,
|
||||
JoinedAbruptCompletions,
|
||||
NormalCompletion,
|
||||
PossiblyNormalCompletion,
|
||||
ThrowCompletion,
|
||||
} from "./completions.js";
|
||||
@ -1023,7 +1022,10 @@ export class LexicalEnvironment {
|
||||
try {
|
||||
return this.evaluate(ast, strictCode, metadata);
|
||||
} catch (err) {
|
||||
if (err instanceof JoinedAbruptCompletions || err instanceof PossiblyNormalCompletion) {
|
||||
if (
|
||||
(err instanceof JoinedAbruptCompletions || err instanceof PossiblyNormalCompletion) &&
|
||||
err.containsBreakOrContinue()
|
||||
) {
|
||||
AbstractValue.reportIntrospectionError(err.joinCondition);
|
||||
throw new FatalError();
|
||||
}
|
||||
@ -1230,16 +1232,11 @@ export class LexicalEnvironment {
|
||||
this.realm.debuggerInstance.checkForActions(ast);
|
||||
}
|
||||
let res = this.evaluateAbstract(ast, strictCode, metadata);
|
||||
if (res instanceof PossiblyNormalCompletion) {
|
||||
let error = new CompilerDiagnostic("Global code may end abruptly", res.location, "PP0016", "FatalError");
|
||||
this.realm.handleError(error);
|
||||
throw new FatalError();
|
||||
}
|
||||
invariant(res instanceof Value || res instanceof Reference, ast.type);
|
||||
return res;
|
||||
}
|
||||
|
||||
evaluateAbstract(ast: BabelNode, strictCode: boolean, metadata?: any): NormalCompletion | Value | Reference {
|
||||
evaluateAbstract(ast: BabelNode, strictCode: boolean, metadata?: any): Value | Reference {
|
||||
this.realm.currentLocation = ast.loc;
|
||||
this.realm.testTimeout();
|
||||
|
||||
|
@ -13,7 +13,6 @@ import type { BabelNodeBlockStatement } from "babel-types";
|
||||
import type { Realm } from "../realm.js";
|
||||
import type { LexicalEnvironment } from "../environment.js";
|
||||
|
||||
import { NormalCompletion } from "../completions.js";
|
||||
import { StringValue, Value } from "../values/index.js";
|
||||
import { EvaluateStatements, NewDeclarativeEnvironment, BlockDeclarationInstantiation } from "../methods/index.js";
|
||||
|
||||
@ -23,7 +22,7 @@ export default function(
|
||||
strictCode: boolean,
|
||||
env: LexicalEnvironment,
|
||||
realm: Realm
|
||||
): NormalCompletion | Value {
|
||||
): Value {
|
||||
// 1. Let oldEnv be the running execution context's LexicalEnvironment.
|
||||
let oldEnv = realm.getRunningContext().lexicalEnvironment;
|
||||
|
||||
@ -38,7 +37,7 @@ export default function(
|
||||
|
||||
try {
|
||||
// 5. Let blockValue be the result of evaluating StatementList.
|
||||
let blockValue: void | NormalCompletion | Value;
|
||||
let blockValue: void | Value;
|
||||
|
||||
if (ast.directives) {
|
||||
for (let directive of ast.directives) {
|
||||
|
@ -10,7 +10,7 @@
|
||||
/* @flow */
|
||||
|
||||
import { CompilerDiagnostic, FatalError } from "../errors.js";
|
||||
import { AbruptCompletion, Completion, NormalCompletion } from "../completions.js";
|
||||
import { AbruptCompletion, PossiblyNormalCompletion } from "../completions.js";
|
||||
import type { Realm } from "../realm.js";
|
||||
import type { LexicalEnvironment } from "../environment.js";
|
||||
import { EnvironmentRecord } from "../environment.js";
|
||||
@ -41,7 +41,7 @@ export default function(
|
||||
strictCode: boolean,
|
||||
env: LexicalEnvironment,
|
||||
realm: Realm
|
||||
): Completion | Value {
|
||||
): Value {
|
||||
if (ast.callee.type === "Super") {
|
||||
return SuperCall(ast.arguments, strictCode, env, realm);
|
||||
}
|
||||
@ -64,7 +64,7 @@ function callBothFunctionsAndJoinTheirEffects(
|
||||
strictCode: boolean,
|
||||
env: LexicalEnvironment,
|
||||
realm: Realm
|
||||
): Completion | Value {
|
||||
): Value {
|
||||
let [cond, func1, func2] = args;
|
||||
invariant(cond instanceof AbstractValue && cond.getType() === BooleanValue);
|
||||
invariant(Value.isTypeCompatibleWith(func1.getType(), FunctionValue));
|
||||
@ -85,11 +85,12 @@ function callBothFunctionsAndJoinTheirEffects(
|
||||
[compl2, gen2, bindings2, properties2, createdObj2]
|
||||
);
|
||||
let completion = joinedEffects[0];
|
||||
if (completion instanceof NormalCompletion) {
|
||||
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.getRunningContext().composeWithSavedCompletion(completion);
|
||||
realm.captureEffects();
|
||||
}
|
||||
|
||||
@ -99,7 +100,7 @@ function callBothFunctionsAndJoinTheirEffects(
|
||||
|
||||
// return or throw completion
|
||||
if (completion instanceof AbruptCompletion) throw completion;
|
||||
invariant(completion instanceof NormalCompletion || completion instanceof Value);
|
||||
invariant(completion instanceof Value);
|
||||
return completion;
|
||||
}
|
||||
|
||||
@ -110,7 +111,7 @@ function EvaluateCall(
|
||||
strictCode: boolean,
|
||||
env: LexicalEnvironment,
|
||||
realm: Realm
|
||||
): Completion | Value {
|
||||
): Value {
|
||||
function generateRuntimeCall() {
|
||||
let args = [func];
|
||||
let [thisArg, propName] = ref instanceof Reference ? [ref.base, ref.referencedName] : [];
|
||||
|
@ -9,7 +9,6 @@
|
||||
|
||||
/* @flow */
|
||||
|
||||
import type { NormalCompletion } from "../completions.js";
|
||||
import type { LexicalEnvironment } from "../environment.js";
|
||||
import { AbstractValue, ConcreteValue, Value } from "../values/index.js";
|
||||
import type { Reference } from "../environment.js";
|
||||
@ -24,7 +23,7 @@ export default function(
|
||||
strictCode: boolean,
|
||||
env: LexicalEnvironment,
|
||||
realm: Realm
|
||||
): NormalCompletion | Value | Reference {
|
||||
): Value | Reference {
|
||||
let exprRef = env.evaluate(ast.test, strictCode);
|
||||
let exprValue = GetValue(realm, exprRef);
|
||||
|
||||
|
@ -9,7 +9,7 @@
|
||||
|
||||
/* @flow */
|
||||
|
||||
import { AbruptCompletion, Completion, NormalCompletion } from "../completions.js";
|
||||
import { AbruptCompletion, PossiblyNormalCompletion } from "../completions.js";
|
||||
import type { Realm } from "../realm.js";
|
||||
import { construct_empty_effects } from "../realm.js";
|
||||
import type { LexicalEnvironment } from "../environment.js";
|
||||
@ -20,12 +20,7 @@ import type { BabelNode, BabelNodeIfStatement } from "babel-types";
|
||||
import invariant from "../invariant.js";
|
||||
import { withPathCondition, withInversePathCondition } from "../utils/paths.js";
|
||||
|
||||
export function evaluate(
|
||||
ast: BabelNodeIfStatement,
|
||||
strictCode: boolean,
|
||||
env: LexicalEnvironment,
|
||||
realm: Realm
|
||||
): Completion | Value {
|
||||
export function evaluate(ast: BabelNodeIfStatement, strictCode: boolean, env: LexicalEnvironment, realm: Realm): Value {
|
||||
// 1. Let exprRef be the result of evaluating Expression
|
||||
let exprRef = env.evaluate(ast.test, strictCode);
|
||||
// 2. Let exprValue be ToBoolean(? GetValue(exprRef))
|
||||
@ -51,6 +46,7 @@ export function evaluate(
|
||||
if (stmtCompletion instanceof AbruptCompletion) {
|
||||
throw stmtCompletion;
|
||||
}
|
||||
invariant(stmtCompletion instanceof Value);
|
||||
return stmtCompletion;
|
||||
}
|
||||
invariant(exprValue instanceof AbstractValue);
|
||||
@ -63,6 +59,7 @@ export function evaluate(
|
||||
if (stmtCompletion instanceof AbruptCompletion) {
|
||||
throw stmtCompletion;
|
||||
}
|
||||
invariant(stmtCompletion instanceof Value);
|
||||
return stmtCompletion;
|
||||
} else if (!exprValue.mightNotBeFalse()) {
|
||||
let stmtCompletion;
|
||||
@ -73,6 +70,7 @@ export function evaluate(
|
||||
if (stmtCompletion instanceof AbruptCompletion) {
|
||||
throw stmtCompletion;
|
||||
}
|
||||
invariant(stmtCompletion instanceof Value);
|
||||
return stmtCompletion;
|
||||
} else {
|
||||
invariant(exprValue instanceof AbstractValue);
|
||||
@ -87,7 +85,7 @@ export function evaluateWithAbstractConditional(
|
||||
strictCode: boolean,
|
||||
env: LexicalEnvironment,
|
||||
realm: Realm
|
||||
): NormalCompletion | Value {
|
||||
): Value {
|
||||
// Evaluate consequent and alternate in sandboxes and get their effects.
|
||||
let [compl1, gen1, bindings1, properties1, createdObj1] = withPathCondition(condValue, () => {
|
||||
return realm.evaluateNodeForEffects(consequent, strictCode, env);
|
||||
@ -106,11 +104,12 @@ export function evaluateWithAbstractConditional(
|
||||
[compl2, gen2, bindings2, properties2, createdObj2]
|
||||
);
|
||||
let completion = joinedEffects[0];
|
||||
if (completion instanceof NormalCompletion) {
|
||||
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.getRunningContext().composeWithSavedCompletion(completion);
|
||||
realm.captureEffects();
|
||||
}
|
||||
// Note that the effects of (non joining) abrupt branches are not included
|
||||
@ -119,6 +118,6 @@ export function evaluateWithAbstractConditional(
|
||||
|
||||
// return or throw completion
|
||||
if (completion instanceof AbruptCompletion) throw completion;
|
||||
invariant(completion instanceof NormalCompletion || completion instanceof Value);
|
||||
invariant(completion instanceof Value);
|
||||
return completion;
|
||||
}
|
||||
|
@ -10,7 +10,7 @@
|
||||
/* @flow */
|
||||
|
||||
import type { Realm } from "../realm.js";
|
||||
import { AbruptCompletion, Completion, NormalCompletion } from "../completions.js";
|
||||
import { AbruptCompletion, PossiblyNormalCompletion } from "../completions.js";
|
||||
import { construct_empty_effects } from "../realm.js";
|
||||
import type { LexicalEnvironment } from "../environment.js";
|
||||
import { AbstractValue, ConcreteValue, Value } from "../values/index.js";
|
||||
@ -25,7 +25,7 @@ export default function(
|
||||
strictCode: boolean,
|
||||
env: LexicalEnvironment,
|
||||
realm: Realm
|
||||
): Completion | Value | Reference {
|
||||
): Value | Reference {
|
||||
let lref = env.evaluate(ast.left, strictCode);
|
||||
let lval = GetValue(realm, lref);
|
||||
|
||||
@ -82,11 +82,12 @@ export default function(
|
||||
);
|
||||
}
|
||||
let completion = joinedEffects[0];
|
||||
if (completion instanceof NormalCompletion) {
|
||||
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.getRunningContext().composeWithSavedCompletion(completion);
|
||||
realm.captureEffects();
|
||||
}
|
||||
// Note that the effects of (non joining) abrupt branches are not included
|
||||
@ -95,7 +96,7 @@ export default function(
|
||||
|
||||
// return or throw completion
|
||||
if (completion instanceof AbruptCompletion) throw completion;
|
||||
invariant(completion instanceof NormalCompletion || completion instanceof Value); // references do not survive join
|
||||
invariant(completion instanceof Value); // references do not survive join
|
||||
if (lval instanceof Value && compl2 instanceof Value) {
|
||||
// joinEffects does the right thing for the side effects of the second expression but for the result the join
|
||||
// produces a conditional expressions of the form (a ? b : a) for a && b and (a ? a : b) for a || b
|
||||
|
@ -9,13 +9,26 @@
|
||||
|
||||
/* @flow */
|
||||
|
||||
import { Completion, JoinedAbruptCompletions, ThrowCompletion } from "../completions.js";
|
||||
import {
|
||||
AbruptCompletion,
|
||||
Completion,
|
||||
JoinedAbruptCompletions,
|
||||
PossiblyNormalCompletion,
|
||||
ReturnCompletion,
|
||||
ThrowCompletion,
|
||||
} from "../completions.js";
|
||||
import type { Realm } from "../realm.js";
|
||||
import type { LexicalEnvironment } from "../environment.js";
|
||||
import { AbstractValue, Value, EmptyValue } from "../values/index.js";
|
||||
import { GlobalEnvironmentRecord } from "../environment.js";
|
||||
import { FindVarScopedDeclarations } from "../methods/function.js";
|
||||
import { BoundNames } from "../methods/index.js";
|
||||
import {
|
||||
BoundNames,
|
||||
composePossiblyNormalCompletions,
|
||||
FindVarScopedDeclarations,
|
||||
incorporateSavedCompletion,
|
||||
stopEffectCaptureJoinApplyAndReturnCompletion,
|
||||
updatePossiblyNormalCompletionWithValue,
|
||||
} from "../methods/index.js";
|
||||
import IsStrict from "../utils/strict.js";
|
||||
import invariant from "../invariant.js";
|
||||
import traverseFast from "../utils/traverse-fast.js";
|
||||
@ -224,14 +237,18 @@ export default function(ast: BabelNodeProgram, strictCode: boolean, env: Lexical
|
||||
|
||||
GlobalDeclarationInstantiation(realm, ast, env, strictCode);
|
||||
|
||||
let context = realm.getRunningContext();
|
||||
let val;
|
||||
|
||||
for (let node of ast.body) {
|
||||
if (node.type !== "FunctionDeclaration") {
|
||||
let potentialVal = env.evaluateCompletion(node, strictCode);
|
||||
if (potentialVal instanceof Completion) {
|
||||
let potentialVal = env.evaluateCompletionDeref(node, strictCode);
|
||||
potentialVal = incorporateSavedCompletion(realm, potentialVal);
|
||||
if (potentialVal instanceof AbruptCompletion) {
|
||||
if (!realm.useAbstractInterpretation) throw potentialVal;
|
||||
// We are about the leave this program and this presents a join point where all non exeptional control flows
|
||||
// converge into a single flow using the joined effects as the new state.
|
||||
// The call to incorporateSavedCompletion above, has already taken care of the join.
|
||||
// What remains to be done is to emit throw statements to the generator.
|
||||
if (potentialVal instanceof JoinedAbruptCompletions) {
|
||||
emitConditionalThrow(potentialVal.joinCondition, potentialVal.consequent, potentialVal.alternate);
|
||||
potentialVal = potentialVal.value;
|
||||
@ -242,20 +259,33 @@ export default function(ast: BabelNodeProgram, strictCode: boolean, env: Lexical
|
||||
invariant(false); // other kinds of abrupt completions should not get this far
|
||||
}
|
||||
}
|
||||
let sc = context.savedCompletion;
|
||||
if (sc !== undefined) {
|
||||
emitConditionalThrow(sc.joinCondition, sc.consequent, sc.alternate);
|
||||
if (potentialVal !== undefined && !(potentialVal instanceof EmptyValue)) {
|
||||
if (val instanceof PossiblyNormalCompletion) {
|
||||
if (potentialVal instanceof PossiblyNormalCompletion) {
|
||||
val = composePossiblyNormalCompletions(realm, val, potentialVal);
|
||||
} else {
|
||||
invariant(potentialVal instanceof Value);
|
||||
updatePossiblyNormalCompletionWithValue(realm, val, potentialVal);
|
||||
}
|
||||
} else {
|
||||
val = potentialVal;
|
||||
}
|
||||
}
|
||||
if (!(potentialVal instanceof EmptyValue)) val = potentialVal;
|
||||
}
|
||||
}
|
||||
|
||||
let directives = ast.directives;
|
||||
if (!val && directives && directives.length) {
|
||||
let directive = directives[directives.length - 1];
|
||||
val = env.evaluate(directive, strictCode);
|
||||
}
|
||||
|
||||
if (val instanceof PossiblyNormalCompletion) {
|
||||
// There are still some conditional throws to emit and state still has to be joined in.
|
||||
stopEffectCaptureJoinApplyAndReturnCompletion(val, new ReturnCompletion(realm.intrinsics.undefined), realm);
|
||||
emitConditionalThrow(val.joinCondition, val.consequent, val.alternate);
|
||||
val = val.value;
|
||||
}
|
||||
|
||||
invariant(val === undefined || val instanceof Value);
|
||||
return val || realm.intrinsics.empty;
|
||||
|
||||
|
@ -13,7 +13,6 @@ import type { BabelNodeExpression, BabelNodeSpreadElement } from "babel-types";
|
||||
import type { Realm } from "../realm.js";
|
||||
import type { LexicalEnvironment } from "../environment.js";
|
||||
import { FunctionEnvironmentRecord } from "../environment.js";
|
||||
import { Completion } from "../completions.js";
|
||||
import { Value, UndefinedValue, ObjectValue } from "../values/index.js";
|
||||
import {
|
||||
GetNewTarget,
|
||||
@ -55,7 +54,7 @@ export default function SuperCall(
|
||||
strictCode: boolean,
|
||||
env: LexicalEnvironment,
|
||||
realm: Realm
|
||||
): Completion | Value {
|
||||
): Value {
|
||||
// 1. Let newTarget be GetNewTarget().
|
||||
let newTarget = GetNewTarget(realm);
|
||||
|
||||
|
@ -9,62 +9,60 @@
|
||||
|
||||
/* @flow */
|
||||
|
||||
import type { Realm } from "../realm.js";
|
||||
import type { LexicalEnvironment } from "../environment.js";
|
||||
import { AbruptCompletion, Completion, PossiblyNormalCompletion, ThrowCompletion } from "../completions.js";
|
||||
import { joinEffects, UpdateEmpty } from "../methods/index.js";
|
||||
import type { Effects, Realm } from "../realm.js";
|
||||
import { type LexicalEnvironment } from "../environment.js";
|
||||
import {
|
||||
AbruptCompletion,
|
||||
JoinedAbruptCompletions,
|
||||
PossiblyNormalCompletion,
|
||||
ThrowCompletion,
|
||||
} from "../completions.js";
|
||||
import {
|
||||
incorporateSavedCompletion,
|
||||
joinEffects,
|
||||
UpdateEmpty,
|
||||
updatePossiblyNormalCompletionWithSubsequentEffects,
|
||||
} from "../methods/index.js";
|
||||
import { Value } from "../values/index.js";
|
||||
import type { BabelNodeTryStatement } from "babel-types";
|
||||
import invariant from "../invariant.js";
|
||||
|
||||
export default function(
|
||||
ast: BabelNodeTryStatement,
|
||||
strictCode: boolean,
|
||||
env: LexicalEnvironment,
|
||||
realm: Realm
|
||||
): PossiblyNormalCompletion | Value {
|
||||
export default function(ast: BabelNodeTryStatement, strictCode: boolean, env: LexicalEnvironment, realm: Realm): Value {
|
||||
let completions = [];
|
||||
|
||||
let blockRes = env.evaluateAbstractCompletion(ast.block, strictCode);
|
||||
let blockRes = env.evaluateCompletionDeref(ast.block, strictCode);
|
||||
blockRes = incorporateSavedCompletion(realm, blockRes);
|
||||
if (blockRes instanceof PossiblyNormalCompletion) {
|
||||
let abruptCompletion;
|
||||
let abruptEffects;
|
||||
if (blockRes.consequent instanceof AbruptCompletion) {
|
||||
abruptCompletion = blockRes.consequent;
|
||||
abruptEffects = blockRes.consequentEffects;
|
||||
} else {
|
||||
abruptCompletion = blockRes.alternate;
|
||||
abruptEffects = blockRes.alternateEffects;
|
||||
// The current state may have advanced since the time control forked into the various paths recorded in blockRes.
|
||||
// Update the normal path and restore the global state to what it was at the time of the fork.
|
||||
let subsequentEffects = realm.getCapturedEffects(blockRes.value);
|
||||
invariant(subsequentEffects !== undefined);
|
||||
realm.stopEffectCaptureAndUndoEffects();
|
||||
updatePossiblyNormalCompletionWithSubsequentEffects(realm, blockRes, subsequentEffects);
|
||||
}
|
||||
|
||||
if (ast.handler) {
|
||||
if (blockRes instanceof ThrowCompletion) {
|
||||
blockRes = env.evaluateCompletionDeref(ast.handler, strictCode, blockRes);
|
||||
} else if (blockRes instanceof JoinedAbruptCompletions || blockRes instanceof PossiblyNormalCompletion) {
|
||||
let handlerEffects = composeNestedThrowEffectsWithHandler(blockRes);
|
||||
blockRes = handlerEffects[0];
|
||||
// If there is a normal execution path following the handler, we need to update the current state
|
||||
if (blockRes instanceof Value || blockRes instanceof PossiblyNormalCompletion) realm.applyEffects(handlerEffects);
|
||||
}
|
||||
if (abruptCompletion instanceof ThrowCompletion && ast.handler) {
|
||||
let normalEffects = realm.getCapturedEffects(blockRes.value);
|
||||
invariant(normalEffects !== undefined);
|
||||
realm.stopEffectCaptureAndUndoEffects();
|
||||
let handlerEffects = realm.evaluateForEffects(() => {
|
||||
realm.applyEffects(abruptEffects);
|
||||
invariant(ast.handler);
|
||||
return env.evaluateAbstractCompletion(ast.handler, strictCode, abruptCompletion);
|
||||
});
|
||||
let jointEffects;
|
||||
if (blockRes.consequent instanceof AbruptCompletion)
|
||||
jointEffects = joinEffects(realm, blockRes.joinCondition, handlerEffects, normalEffects);
|
||||
else jointEffects = joinEffects(realm, blockRes.joinCondition, normalEffects, handlerEffects);
|
||||
realm.applyEffects(jointEffects);
|
||||
completions.unshift(jointEffects[0]);
|
||||
}
|
||||
completions.unshift(blockRes);
|
||||
|
||||
if (ast.finalizer) {
|
||||
if (blockRes instanceof PossiblyNormalCompletion || blockRes instanceof JoinedAbruptCompletions) {
|
||||
completions.unshift(composeNestedEffectsWithFinalizer(blockRes));
|
||||
} else {
|
||||
completions.unshift(blockRes);
|
||||
}
|
||||
} else {
|
||||
if (blockRes instanceof ThrowCompletion && ast.handler) {
|
||||
completions.unshift(env.evaluateCompletion(ast.handler, strictCode, blockRes));
|
||||
} else {
|
||||
completions.unshift(blockRes);
|
||||
completions.unshift(env.evaluateCompletion(ast.finalizer, strictCode));
|
||||
}
|
||||
}
|
||||
|
||||
if (ast.finalizer) {
|
||||
completions.unshift(env.evaluateCompletion(ast.finalizer, strictCode));
|
||||
}
|
||||
// Restart effect capture if one of the paths may continue
|
||||
if (blockRes instanceof PossiblyNormalCompletion) realm.captureEffects();
|
||||
|
||||
// use the last completion record
|
||||
for (let completion of completions) {
|
||||
@ -77,9 +75,86 @@ export default function(
|
||||
|
||||
// otherwise use the last returned value
|
||||
for (let completion of completions) {
|
||||
if (completion instanceof Value || completion instanceof Completion)
|
||||
return (UpdateEmpty(realm, completion, realm.intrinsics.undefined): any);
|
||||
if (completion instanceof PossiblyNormalCompletion)
|
||||
completion = realm.getRunningContext().composeWithSavedCompletion(completion);
|
||||
if (completion instanceof Value) return (UpdateEmpty(realm, completion, realm.intrinsics.undefined): any);
|
||||
}
|
||||
|
||||
invariant(false);
|
||||
|
||||
// The finalizer is not a join point, so update each path in the completion separately.
|
||||
function composeNestedEffectsWithFinalizer(
|
||||
c: PossiblyNormalCompletion | JoinedAbruptCompletions,
|
||||
priorEffects: Array<Effects> = []
|
||||
) {
|
||||
priorEffects.push(c.consequentEffects);
|
||||
let consequent = c.consequent;
|
||||
if (consequent instanceof PossiblyNormalCompletion || consequent instanceof JoinedAbruptCompletions) {
|
||||
composeNestedEffectsWithFinalizer(consequent, priorEffects);
|
||||
} else {
|
||||
c.consequentEffects = realm.evaluateForEffects(() => {
|
||||
for (let priorEffect of priorEffects) realm.applyEffects(priorEffect);
|
||||
invariant(ast.finalizer);
|
||||
return env.evaluateCompletionDeref(ast.finalizer, strictCode);
|
||||
});
|
||||
let fc = c.consequentEffects[0];
|
||||
// If the finalizer had an abrupt completion, it overrides the try-block's completion.
|
||||
if (fc instanceof AbruptCompletion) c.consequent = fc;
|
||||
else c.consequentEffects[0] = consequent;
|
||||
}
|
||||
priorEffects.pop();
|
||||
priorEffects.push(c.alternateEffects);
|
||||
let alternate = c.alternate;
|
||||
if (alternate instanceof PossiblyNormalCompletion || alternate instanceof JoinedAbruptCompletions) {
|
||||
composeNestedEffectsWithFinalizer(alternate, priorEffects);
|
||||
} else {
|
||||
c.alternateEffects = realm.evaluateForEffects(() => {
|
||||
for (let priorEffect of priorEffects) realm.applyEffects(priorEffect);
|
||||
invariant(ast.finalizer);
|
||||
return env.evaluateCompletionDeref(ast.finalizer, strictCode);
|
||||
});
|
||||
let fc = c.alternateEffects[0];
|
||||
// If the finalizer had an abrupt completion, it overrides the try-block's completion.
|
||||
if (fc instanceof AbruptCompletion) c.alternate = fc;
|
||||
else c.alternateEffects[0] = alternate;
|
||||
}
|
||||
}
|
||||
|
||||
// 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 joinEffects to create a new container at every level of the recursion.
|
||||
function composeNestedThrowEffectsWithHandler(
|
||||
c: PossiblyNormalCompletion | JoinedAbruptCompletions,
|
||||
priorEffects: Array<Effects> = []
|
||||
): Effects {
|
||||
let consequent = c.consequent;
|
||||
let consequentEffects = c.consequentEffects;
|
||||
priorEffects.push(consequentEffects);
|
||||
if (consequent instanceof JoinedAbruptCompletions || consequent instanceof PossiblyNormalCompletion) {
|
||||
consequentEffects = composeNestedThrowEffectsWithHandler(consequent, priorEffects);
|
||||
} else if (consequent instanceof ThrowCompletion) {
|
||||
consequentEffects = realm.evaluateForEffects(() => {
|
||||
for (let priorEffect of priorEffects) realm.applyEffects(priorEffect);
|
||||
invariant(ast.handler);
|
||||
return env.evaluateCompletionDeref(ast.handler, strictCode, consequent);
|
||||
});
|
||||
}
|
||||
priorEffects.pop();
|
||||
let alternate = c.alternate;
|
||||
let alternateEffects = c.alternateEffects;
|
||||
priorEffects.push(alternateEffects);
|
||||
if (alternate instanceof PossiblyNormalCompletion || alternate instanceof JoinedAbruptCompletions) {
|
||||
alternateEffects = composeNestedThrowEffectsWithHandler(alternate, priorEffects);
|
||||
} else if (alternate instanceof ThrowCompletion) {
|
||||
alternateEffects = realm.evaluateForEffects(() => {
|
||||
for (let priorEffect of priorEffects) realm.applyEffects(priorEffect);
|
||||
invariant(ast.handler);
|
||||
return env.evaluateCompletionDeref(ast.handler, strictCode, alternate);
|
||||
});
|
||||
}
|
||||
priorEffects.pop();
|
||||
return joinEffects(realm, c.joinCondition, consequentEffects, alternateEffects);
|
||||
}
|
||||
}
|
||||
|
@ -517,7 +517,7 @@ export function SymbolDescriptiveString(realm: Realm, sym: SymbolValue): string
|
||||
}
|
||||
|
||||
// ECMA262 6.2.2.5
|
||||
export function UpdateEmpty(realm: Realm, completionRecord: Value | Completion, value: Value): Value | Completion {
|
||||
export function UpdateEmpty(realm: Realm, completionRecord: Completion | Value, value: Value): Completion | Value {
|
||||
// 1. Assert: If completionRecord.[[Type]] is either return or throw, then completionRecord.[[Value]] is not empty.
|
||||
if (completionRecord instanceof ReturnCompletion || completionRecord instanceof ThrowCompletion) {
|
||||
invariant(completionRecord.value, "expected completion record to have a value");
|
||||
|
@ -26,12 +26,12 @@ import {
|
||||
AbstractValue,
|
||||
} from "../values/index.js";
|
||||
import {
|
||||
composePossiblyNormalCompletions,
|
||||
FunctionDeclarationInstantiation,
|
||||
GetBase,
|
||||
GetIterator,
|
||||
GetValue,
|
||||
HasSomeCompatibleType,
|
||||
incorporateSavedCompletion,
|
||||
IsCallable,
|
||||
IsPropertyKey,
|
||||
IsPropertyReference,
|
||||
@ -350,32 +350,31 @@ export function OrdinaryCallEvaluateBody(
|
||||
let code = F.$ECMAScriptCode;
|
||||
invariant(code !== undefined);
|
||||
let context = realm.getRunningContext();
|
||||
let c = context.lexicalEnvironment.evaluateAbstractCompletion(code, F.$Strict);
|
||||
let c = context.lexicalEnvironment.evaluateCompletionDeref(code, F.$Strict);
|
||||
// We are about the leave this function and this presents a join point where all non exeptional control flows
|
||||
// converge into a single flow using the joined effects as the new state.
|
||||
if (c instanceof PossiblyNormalCompletion) {
|
||||
// There were earlier, conditional exits from the function
|
||||
// We join together the current effects with the effects of any earlier returns that are tracked in c.
|
||||
c = incorporateSavedCompletion(realm, c);
|
||||
let joinedEffects;
|
||||
if (c instanceof PossiblyNormalCompletion || c instanceof JoinedAbruptCompletions) {
|
||||
let e = realm.getCapturedEffects();
|
||||
invariant(e !== undefined);
|
||||
realm.stopEffectCaptureAndUndoEffects();
|
||||
let joinedEffects = joinEffectsAndPromoteNestedReturnCompletions(realm, c, e);
|
||||
realm.applyEffects(joinedEffects);
|
||||
c = joinedEffects[0];
|
||||
if (e !== undefined) {
|
||||
// There were earlier, conditional exits from the function
|
||||
// We join together the current effects with the effects of any earlier returns that are tracked in c.
|
||||
realm.stopEffectCaptureAndUndoEffects();
|
||||
} else {
|
||||
e = construct_empty_effects(realm);
|
||||
}
|
||||
joinedEffects = joinEffectsAndPromoteNestedReturnCompletions(realm, c, e);
|
||||
}
|
||||
if (c instanceof JoinedAbruptCompletions) {
|
||||
// There are two or more returns and/or throws that unconditionally terminate the function
|
||||
// We need to join the return flows together.
|
||||
let e = construct_empty_effects(realm); // nothing happened since the components of c captured their effects
|
||||
let joinedEffects = joinEffectsAndPromoteNestedReturnCompletions(realm, c, e);
|
||||
if (joinedEffects !== undefined) {
|
||||
let result = joinedEffects[0];
|
||||
if (result instanceof ReturnCompletion) {
|
||||
realm.applyEffects(joinedEffects);
|
||||
return result.value;
|
||||
return result;
|
||||
}
|
||||
invariant(result instanceof JoinedAbruptCompletions);
|
||||
if (!(result.consequent instanceof ReturnCompletion || result.alternate instanceof ReturnCompletion)) {
|
||||
// Control is leaving this function only via throw completions. This is not a joint point.
|
||||
realm.applyEffects(joinedEffects);
|
||||
throw result;
|
||||
}
|
||||
// There is a normal return exit, but also one or more throw completions.
|
||||
@ -399,13 +398,7 @@ export function OrdinaryCallEvaluateBody(
|
||||
// while stashing away the throw completions so that the next completion we return
|
||||
// incorporates them.
|
||||
let [joinedEffects, possiblyNormalCompletion] = unbundleReturnCompletion(realm, c);
|
||||
if (context.savedCompletion !== undefined)
|
||||
context.savedCompletion = composePossiblyNormalCompletions(
|
||||
realm,
|
||||
context.savedCompletion,
|
||||
possiblyNormalCompletion
|
||||
);
|
||||
else context.savedCompletion = possiblyNormalCompletion;
|
||||
realm.getRunningContext().composeWithSavedCompletion(possiblyNormalCompletion);
|
||||
realm.captureEffects();
|
||||
return joinedEffects;
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ import {
|
||||
PossiblyNormalCompletion,
|
||||
} from "../completions.js";
|
||||
import { ExecutionContext } from "../realm.js";
|
||||
import { GlobalEnvironmentRecord, ObjectEnvironmentRecord, Reference } from "../environment.js";
|
||||
import { GlobalEnvironmentRecord, ObjectEnvironmentRecord } from "../environment.js";
|
||||
import {
|
||||
AbstractValue,
|
||||
BoundFunctionValue,
|
||||
@ -46,7 +46,7 @@ import {
|
||||
composePossiblyNormalCompletions,
|
||||
joinAndRemoveNestedReturnCompletions,
|
||||
joinPossiblyNormalCompletionWithAbruptCompletion,
|
||||
stopEffectCaptureAndJoinCompletions,
|
||||
stopEffectCaptureJoinApplyAndReturnCompletion,
|
||||
updatePossiblyNormalCompletionWithValue,
|
||||
BoundNames,
|
||||
ContainsExpression,
|
||||
@ -695,13 +695,7 @@ function InternalCall(
|
||||
realm.popContext(calleeContext);
|
||||
invariant(realm.getRunningContext() === callerContext);
|
||||
if (calleeContext.savedCompletion !== undefined) {
|
||||
if (callerContext.savedCompletion !== undefined)
|
||||
callerContext.savedCompletion = composePossiblyNormalCompletions(
|
||||
realm,
|
||||
callerContext.savedCompletion,
|
||||
calleeContext.savedCompletion
|
||||
);
|
||||
else callerContext.savedCompletion = calleeContext.savedCompletion;
|
||||
callerContext.composeWithSavedCompletion(calleeContext.savedCompletion);
|
||||
realm.captureEffects();
|
||||
}
|
||||
|
||||
@ -1131,7 +1125,15 @@ export function PerformEval(realm: Realm, x: Value, evalRealm: Realm, strictCall
|
||||
}
|
||||
}
|
||||
|
||||
export function incorporateSavedCompletion(realm: Realm, c: void | Completion | Value): void | Completion | Value {
|
||||
// If c is an abrupt completion and context.savedCompletion is defined, the result is an instance of
|
||||
// JoinedAbruptCompletions and the effects that have been captured since the PossiblyNormalCompletion instance
|
||||
// in context.savedCompletion has been created, becomes the effects of the branch that terminates in c.
|
||||
// If c is a normal completion, the result is context.savedCompletion, with its value updated to c.
|
||||
// If c is undefined, the result is just context.savedCompletion.
|
||||
export function incorporateSavedCompletion(
|
||||
realm: Realm,
|
||||
c: void | AbruptCompletion | Value
|
||||
): void | Completion | Value {
|
||||
let context = realm.getRunningContext();
|
||||
let savedCompletion = context.savedCompletion;
|
||||
if (savedCompletion !== undefined) {
|
||||
@ -1140,19 +1142,15 @@ export function incorporateSavedCompletion(realm: Realm, c: void | Completion |
|
||||
if (c instanceof Value) {
|
||||
updatePossiblyNormalCompletionWithValue(realm, savedCompletion, c);
|
||||
return savedCompletion;
|
||||
} else if (c instanceof PossiblyNormalCompletion) {
|
||||
return composePossiblyNormalCompletions(realm, savedCompletion, c);
|
||||
} else {
|
||||
invariant(c instanceof AbruptCompletion);
|
||||
let e = realm.getCapturedEffects();
|
||||
invariant(e !== undefined);
|
||||
realm.stopEffectCaptureAndUndoEffects();
|
||||
e[0] = c;
|
||||
let joined_effects = joinPossiblyNormalCompletionWithAbruptCompletion(realm, savedCompletion, c, e);
|
||||
realm.applyEffects(joined_effects);
|
||||
let jc = joined_effects[0];
|
||||
invariant(jc instanceof AbruptCompletion);
|
||||
throw jc;
|
||||
return jc;
|
||||
}
|
||||
}
|
||||
return c;
|
||||
@ -1160,15 +1158,16 @@ export function incorporateSavedCompletion(realm: Realm, c: void | Completion |
|
||||
|
||||
export function EvaluateStatements(
|
||||
body: Array<BabelNodeStatement>,
|
||||
blockValue: void | NormalCompletion | Value,
|
||||
initialBlockValue: void | Value,
|
||||
strictCode: boolean,
|
||||
blockEnv: LexicalEnvironment,
|
||||
realm: Realm
|
||||
): NormalCompletion | Value {
|
||||
): Value {
|
||||
let context = realm.getRunningContext();
|
||||
let blockValue = initialBlockValue;
|
||||
for (let node of body) {
|
||||
if (node.type !== "FunctionDeclaration") {
|
||||
let res = blockEnv.evaluateAbstractCompletion(node, strictCode);
|
||||
invariant(!(res instanceof Reference));
|
||||
let res = blockEnv.evaluateCompletionDeref(node, strictCode);
|
||||
res = incorporateSavedCompletion(realm, res);
|
||||
if (!(res instanceof EmptyValue)) {
|
||||
if (blockValue === undefined || blockValue instanceof Value) {
|
||||
@ -1178,7 +1177,7 @@ export function EvaluateStatements(
|
||||
} else {
|
||||
invariant(blockValue instanceof PossiblyNormalCompletion);
|
||||
if (res instanceof AbruptCompletion) {
|
||||
throw stopEffectCaptureAndJoinCompletions(blockValue, res, realm);
|
||||
throw stopEffectCaptureJoinApplyAndReturnCompletion(blockValue, res, realm);
|
||||
} else {
|
||||
if (res instanceof Value) {
|
||||
updatePossiblyNormalCompletionWithValue(realm, blockValue, res);
|
||||
@ -1194,6 +1193,8 @@ export function EvaluateStatements(
|
||||
}
|
||||
|
||||
// 7. Return blockValue.
|
||||
if (blockValue instanceof PossiblyNormalCompletion) blockValue = context.composeWithSavedCompletion(blockValue);
|
||||
invariant(blockValue === undefined || blockValue instanceof Value);
|
||||
return blockValue || realm.intrinsics.empty;
|
||||
}
|
||||
|
||||
|
@ -12,16 +12,17 @@
|
||||
import type { Realm } from "../realm.js";
|
||||
import type { PropertyKeyValue, CallableObjectValue } from "../types.js";
|
||||
import {
|
||||
Value,
|
||||
AbstractObjectValue,
|
||||
AbstractValue,
|
||||
BoundFunctionValue,
|
||||
NumberValue,
|
||||
ProxyValue,
|
||||
UndefinedValue,
|
||||
StringValue,
|
||||
ObjectValue,
|
||||
EmptyValue,
|
||||
NullValue,
|
||||
AbstractObjectValue,
|
||||
NumberValue,
|
||||
ObjectValue,
|
||||
ProxyValue,
|
||||
StringValue,
|
||||
UndefinedValue,
|
||||
Value,
|
||||
} from "../values/index.js";
|
||||
import { Reference } from "../environment.js";
|
||||
import { FatalError } from "../errors.js";
|
||||
@ -115,9 +116,8 @@ export function OrdinaryGet(
|
||||
}
|
||||
|
||||
// c. Return ? parent.[[Get]](P, Receiver).
|
||||
if (descValue.mightHaveBeenDeleted()) {
|
||||
if (descValue.mightHaveBeenDeleted() && descValue instanceof AbstractValue) {
|
||||
// We don't know for sure that O.P does not exist.
|
||||
invariant(descValue instanceof AbstractValue);
|
||||
let parentVal = OrdinaryGet(realm, parent, P, descValue, true);
|
||||
if (parentVal instanceof UndefinedValue)
|
||||
// even O.P returns undefined it is still the right value.
|
||||
@ -129,7 +129,7 @@ export function OrdinaryGet(
|
||||
let cond = AbstractValue.createFromBinaryOp(realm, "!==", descValue, realm.intrinsics.empty);
|
||||
return joinValuesAsConditional(realm, cond, descValue, parentVal);
|
||||
}
|
||||
invariant(!desc);
|
||||
invariant(!desc || descValue instanceof EmptyValue);
|
||||
return parent.$Get(P, Receiver);
|
||||
}
|
||||
|
||||
|
@ -33,19 +33,18 @@ import { AbstractValue, ObjectValue, Value } from "../values/index.js";
|
||||
|
||||
import invariant from "../invariant.js";
|
||||
|
||||
export function stopEffectCaptureAndJoinCompletions(
|
||||
export function stopEffectCaptureJoinApplyAndReturnCompletion(
|
||||
c1: PossiblyNormalCompletion,
|
||||
c2: AbruptCompletion,
|
||||
realm: Realm
|
||||
): Completion {
|
||||
): AbruptCompletion {
|
||||
let e = realm.getCapturedEffects();
|
||||
invariant(e !== undefined);
|
||||
realm.stopEffectCaptureAndUndoEffects();
|
||||
e[0] = c2;
|
||||
let joined_effects = joinPossiblyNormalCompletionWithAbruptCompletion(realm, c1, c2, e);
|
||||
realm.applyEffects(joined_effects);
|
||||
let result = joined_effects[0];
|
||||
invariant(result instanceof Completion);
|
||||
invariant(result instanceof AbruptCompletion);
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -137,6 +136,33 @@ export function composePossiblyNormalCompletions(
|
||||
}
|
||||
}
|
||||
|
||||
export function updatePossiblyNormalCompletionWithSubsequentEffects(
|
||||
realm: Realm,
|
||||
pnc: PossiblyNormalCompletion,
|
||||
subsequentEffects: Effects
|
||||
) {
|
||||
let v = subsequentEffects[0];
|
||||
invariant(v instanceof Value);
|
||||
pnc.value = v;
|
||||
if (pnc.consequent instanceof AbruptCompletion) {
|
||||
if (pnc.alternate instanceof Value) {
|
||||
pnc.alternate = v;
|
||||
pnc.alternateEffects = realm.composeEffects(pnc.alternateEffects, subsequentEffects);
|
||||
} else {
|
||||
invariant(pnc.alternate instanceof PossiblyNormalCompletion);
|
||||
updatePossiblyNormalCompletionWithSubsequentEffects(realm, pnc.alternate, subsequentEffects);
|
||||
}
|
||||
} else {
|
||||
if (pnc.consequent instanceof Value) {
|
||||
pnc.consequent = v;
|
||||
pnc.consequentEffects = realm.composeEffects(pnc.consequentEffects, subsequentEffects);
|
||||
} else {
|
||||
invariant(pnc.consequent instanceof PossiblyNormalCompletion);
|
||||
updatePossiblyNormalCompletionWithSubsequentEffects(realm, pnc.consequent, subsequentEffects);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function updatePossiblyNormalCompletionWithValue(realm: Realm, pnc: PossiblyNormalCompletion, v: Value) {
|
||||
pnc.value = v;
|
||||
if (pnc.consequent instanceof AbruptCompletion) {
|
||||
@ -158,21 +184,40 @@ export function updatePossiblyNormalCompletionWithValue(realm: Realm, pnc: Possi
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the joined effects of all of the paths in pnc.
|
||||
// The normal path in pnc is modified to become terminated by ac,
|
||||
// so the overall completion will always be an instance of JoinedAbruptCompletions
|
||||
export function joinPossiblyNormalCompletionWithAbruptCompletion(
|
||||
realm: Realm,
|
||||
pnc: PossiblyNormalCompletion,
|
||||
ac: AbruptCompletion,
|
||||
e: Effects
|
||||
pnc: PossiblyNormalCompletion, // a forked path with a non abrupt (normal) component
|
||||
ac: AbruptCompletion, // an abrupt completion that completes the normal path
|
||||
e: Effects // effects collected after pnc was constructed
|
||||
): Effects {
|
||||
// set up e with ac as the completion. It's OK to do this repeatedly since ac is not changed by recursive calls.
|
||||
e[0] = ac;
|
||||
if (pnc.consequent instanceof AbruptCompletion) {
|
||||
if (pnc.alternate instanceof Value) return joinEffects(realm, pnc.joinCondition, pnc.consequentEffects, e);
|
||||
if (pnc.alternate instanceof Value) {
|
||||
return joinEffects(
|
||||
realm,
|
||||
pnc.joinCondition,
|
||||
pnc.consequentEffects,
|
||||
realm.composeEffects(pnc.alternateEffects, e)
|
||||
);
|
||||
}
|
||||
invariant(pnc.alternate instanceof PossiblyNormalCompletion);
|
||||
let alternate_effects = joinPossiblyNormalCompletionWithAbruptCompletion(realm, pnc.alternate, ac, e);
|
||||
invariant(pnc.consequent instanceof AbruptCompletion);
|
||||
return joinEffects(realm, pnc.joinCondition, pnc.consequentEffects, alternate_effects);
|
||||
} else {
|
||||
invariant(pnc.alternate instanceof AbruptCompletion);
|
||||
if (pnc.consequent instanceof Value) return joinEffects(realm, pnc.joinCondition, e, pnc.alternateEffects);
|
||||
if (pnc.consequent instanceof Value) {
|
||||
return joinEffects(
|
||||
realm,
|
||||
pnc.joinCondition,
|
||||
realm.composeEffects(pnc.consequentEffects, e),
|
||||
pnc.alternateEffects
|
||||
);
|
||||
}
|
||||
invariant(pnc.consequent instanceof PossiblyNormalCompletion);
|
||||
let consequent_effects = joinPossiblyNormalCompletionWithAbruptCompletion(realm, pnc.consequent, ac, e);
|
||||
invariant(pnc.alternate instanceof AbruptCompletion);
|
||||
@ -266,14 +311,10 @@ export function joinEffectsAndPromoteNestedReturnCompletions(
|
||||
let e1 = joinEffectsAndPromoteNestedReturnCompletions(realm, c.consequent, e, c.consequentEffects);
|
||||
let e2 = joinEffectsAndPromoteNestedReturnCompletions(realm, c.alternate, e, c.alternateEffects);
|
||||
if (e1[0] instanceof AbruptCompletion) {
|
||||
if (!(e2[0] instanceof ReturnCompletion)) {
|
||||
invariant(e2[0] instanceof Value); // otherwise c cannot possibly be normal
|
||||
e2[0] = new ReturnCompletion(realm.intrinsics.undefined, realm.currentLocation);
|
||||
}
|
||||
if (e2[0] instanceof Value) e2[0] = new ReturnCompletion(realm.intrinsics.undefined, realm.currentLocation);
|
||||
return joinEffects(realm, c.joinCondition, e1, e2);
|
||||
} else if (e2[0] instanceof AbruptCompletion) {
|
||||
invariant(e1[0] instanceof Value); // otherwise c cannot possibly be normal
|
||||
e1[0] = new ReturnCompletion(realm.intrinsics.undefined, realm.currentLocation);
|
||||
if (e1[0] instanceof Value) e1[0] = new ReturnCompletion(realm.intrinsics.undefined, realm.currentLocation);
|
||||
return joinEffects(realm, c.joinCondition, e1, e2);
|
||||
}
|
||||
}
|
||||
@ -444,8 +485,6 @@ function joinResults(
|
||||
|
||||
export function composeGenerators(realm: Realm, generator1: Generator, generator2: Generator): Generator {
|
||||
let result = new Generator(realm);
|
||||
generator1.parent = result;
|
||||
generator2.parent = result;
|
||||
if (!generator1.empty() || !generator2.empty()) {
|
||||
result.composeGenerators(generator1, generator2);
|
||||
}
|
||||
@ -459,8 +498,6 @@ function joinGenerators(
|
||||
generator2: Generator
|
||||
): Generator {
|
||||
let result = new Generator(realm);
|
||||
generator1.parent = result;
|
||||
generator2.parent = result;
|
||||
if (!generator1.empty() || !generator2.empty()) {
|
||||
result.joinGenerators(joinCondition, generator1, generator2);
|
||||
}
|
||||
@ -515,9 +552,10 @@ export function joinValues(
|
||||
v2: void | Value | Array<Value> | Array<{ $Key: void | Value, $Value: void | Value }>,
|
||||
getAbstractValue: (void | Value, void | Value) => Value
|
||||
): Value | Array<Value> | Array<{ $Key: void | Value, $Value: void | Value }> {
|
||||
if (Array.isArray(v1)) {
|
||||
invariant(Array.isArray(v2)); // This use of Array is restricted to internal properties that are always arrays
|
||||
return joinArrays(realm, ((v1: any): Array<Value>), ((v2: any): Array<Value>), getAbstractValue);
|
||||
if (Array.isArray(v1) || Array.isArray(v2)) {
|
||||
invariant(v1 === undefined || Array.isArray(v1));
|
||||
invariant(v2 === undefined || Array.isArray(v2));
|
||||
return joinArrays(realm, ((v1: any): void | Array<Value>), ((v2: any): void | Array<Value>), getAbstractValue);
|
||||
}
|
||||
invariant(v1 === undefined || v1 instanceof Value);
|
||||
invariant(v2 === undefined || v2 instanceof Value);
|
||||
@ -536,27 +574,27 @@ export function joinValues(
|
||||
|
||||
function joinArrays(
|
||||
realm: Realm,
|
||||
v1: Array<Value> | Array<{ $Key: void | Value, $Value: void | Value }>,
|
||||
v2: Array<Value> | Array<{ $Key: void | Value, $Value: void | Value }>,
|
||||
v1: void | Array<Value> | Array<{ $Key: void | Value, $Value: void | Value }>,
|
||||
v2: void | Array<Value> | Array<{ $Key: void | Value, $Value: void | Value }>,
|
||||
getAbstractValue: (void | Value, void | Value) => Value
|
||||
): Array<Value> | Array<{ $Key: void | Value, $Value: void | Value }> {
|
||||
let e = v1[0] || v2[0];
|
||||
let e = (v1 && v1[0]) || (v2 && v2[0]);
|
||||
if (e instanceof Value) return joinArraysOfValues(realm, (v1: any), (v2: any), getAbstractValue);
|
||||
else return joinArrayOfsMapEntries(realm, (v1: any), (v2: any), getAbstractValue);
|
||||
}
|
||||
|
||||
function joinArrayOfsMapEntries(
|
||||
realm: Realm,
|
||||
a1: Array<{ $Key: void | Value, $Value: void | Value }>,
|
||||
a2: Array<{ $Key: void | Value, $Value: void | Value }>,
|
||||
a1: void | Array<{ $Key: void | Value, $Value: void | Value }>,
|
||||
a2: void | Array<{ $Key: void | Value, $Value: void | Value }>,
|
||||
getAbstractValue: (void | Value, void | Value) => Value
|
||||
): Array<{ $Key: void | Value, $Value: void | Value }> {
|
||||
let empty = realm.intrinsics.empty;
|
||||
let n = Math.max(a1.length, a2.length);
|
||||
let n = Math.max((a1 && a1.length) || 0, (a2 && a2.length) || 0);
|
||||
let result = [];
|
||||
for (let i = 0; i < n; i++) {
|
||||
let { $Key: key1, $Value: val1 } = a1[i] || { $Key: empty, $Value: empty };
|
||||
let { $Key: key2, $Value: val2 } = a2[i] || { $Key: empty, $Value: empty };
|
||||
let { $Key: key1, $Value: val1 } = (a1 && a1[i]) || { $Key: empty, $Value: empty };
|
||||
let { $Key: key2, $Value: val2 } = (a2 && a2[i]) || { $Key: empty, $Value: empty };
|
||||
if (key1 === undefined && key2 === undefined) {
|
||||
result[i] = { $Key: undefined, $Value: undefined };
|
||||
} else {
|
||||
@ -570,14 +608,14 @@ function joinArrayOfsMapEntries(
|
||||
|
||||
function joinArraysOfValues(
|
||||
realm: Realm,
|
||||
a1: Array<Value>,
|
||||
a2: Array<Value>,
|
||||
a1: void | Array<Value>,
|
||||
a2: void | Array<Value>,
|
||||
getAbstractValue: (void | Value, void | Value) => Value
|
||||
): Array<Value> {
|
||||
let n = Math.max(a1.length, a2.length);
|
||||
let n = Math.max((a1 && a1.length) || 0, (a2 && a2.length) || 0);
|
||||
let result = [];
|
||||
for (let i = 0; i < n; i++) {
|
||||
result[i] = getAbstractValue(a1[i], a2[i]);
|
||||
result[i] = getAbstractValue((a1 && a1[i]) || undefined, (a2 && a2[i]) || undefined);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
@ -22,13 +22,12 @@ import {
|
||||
GetReferencedName,
|
||||
GetThisValue,
|
||||
GetValue,
|
||||
incorporateSavedCompletion,
|
||||
IsInTailPosition,
|
||||
IsPropertyReference,
|
||||
joinEffects,
|
||||
PerformEval,
|
||||
SameValue,
|
||||
stopEffectCaptureAndJoinCompletions,
|
||||
stopEffectCaptureJoinApplyAndReturnCompletion,
|
||||
unbundleNormalCompletion,
|
||||
} from "../methods/index.js";
|
||||
import { AbstractValue, BooleanValue, FunctionValue, Value } from "../values/index.js";
|
||||
@ -67,7 +66,7 @@ export default function(
|
||||
partialArgs.push((argAst: any));
|
||||
if (argValue instanceof AbruptCompletion) {
|
||||
if (completion instanceof PossiblyNormalCompletion)
|
||||
completion = stopEffectCaptureAndJoinCompletions(completion, argValue, realm);
|
||||
completion = stopEffectCaptureJoinApplyAndReturnCompletion(completion, argValue, realm);
|
||||
else completion = argValue;
|
||||
let resultAst = t.callExpression((calleeAst: any), partialArgs);
|
||||
return [completion, resultAst, io];
|
||||
@ -84,10 +83,9 @@ export default function(
|
||||
}
|
||||
|
||||
let callResult = EvaluateCall(ref, func, ast, argVals, strictCode, env, realm);
|
||||
completion = incorporateSavedCompletion(realm, completion);
|
||||
if (callResult instanceof AbruptCompletion) {
|
||||
if (completion instanceof PossiblyNormalCompletion)
|
||||
completion = stopEffectCaptureAndJoinCompletions(completion, callResult, realm);
|
||||
completion = stopEffectCaptureJoinApplyAndReturnCompletion(completion, callResult, realm);
|
||||
else completion = callResult;
|
||||
let resultAst = t.callExpression((calleeAst: any), partialArgs);
|
||||
return [completion, resultAst, io];
|
||||
@ -110,7 +108,7 @@ function callBothFunctionsAndJoinTheirEffects(
|
||||
strictCode: boolean,
|
||||
env: LexicalEnvironment,
|
||||
realm: Realm
|
||||
): Completion | Value {
|
||||
): AbruptCompletion | Value {
|
||||
let [cond, func1, func2] = funcs;
|
||||
invariant(cond instanceof AbstractValue && cond.getType() === BooleanValue);
|
||||
invariant(Value.isTypeCompatibleWith(func1.getType(), FunctionValue));
|
||||
@ -136,7 +134,7 @@ function callBothFunctionsAndJoinTheirEffects(
|
||||
// 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();
|
||||
joinedCompletion = realm.getRunningContext().composeWithSavedCompletion(joinedCompletion);
|
||||
}
|
||||
|
||||
// Note that the effects of (non joining) abrupt branches are not included
|
||||
@ -144,7 +142,7 @@ function callBothFunctionsAndJoinTheirEffects(
|
||||
realm.applyEffects(joinedEffects);
|
||||
|
||||
// return or throw completion
|
||||
invariant(joinedCompletion instanceof Completion || joinedCompletion instanceof Value);
|
||||
invariant(joinedCompletion instanceof AbruptCompletion || joinedCompletion instanceof Value);
|
||||
return joinedCompletion;
|
||||
}
|
||||
|
||||
@ -156,7 +154,7 @@ function EvaluateCall(
|
||||
strictCode: boolean,
|
||||
env: LexicalEnvironment,
|
||||
realm: Realm
|
||||
): Completion | Value {
|
||||
): AbruptCompletion | Value {
|
||||
if (func instanceof AbstractValue && Value.isTypeCompatibleWith(func.getType(), FunctionValue)) {
|
||||
if (func.kind === "conditional")
|
||||
return callBothFunctionsAndJoinTheirEffects(func.args, ast, argList, strictCode, env, realm);
|
||||
|
103
src/realm.js
103
src/realm.js
@ -30,13 +30,13 @@ import type { Binding } from "./environment.js";
|
||||
import {
|
||||
cloneDescriptor,
|
||||
composeGenerators,
|
||||
composePossiblyNormalCompletions,
|
||||
Construct,
|
||||
GetValue,
|
||||
incorporateSavedCompletion,
|
||||
ThrowIfMightHaveBeenDeleted,
|
||||
ToString,
|
||||
} from "./methods/index.js";
|
||||
import type { NormalCompletion } from "./completions.js";
|
||||
import { Completion, ThrowCompletion, AbruptCompletion, PossiblyNormalCompletion } from "./completions.js";
|
||||
import type { Compatibility, RealmOptions } from "./options.js";
|
||||
import invariant from "./invariant.js";
|
||||
@ -91,6 +91,12 @@ export class ExecutionContext {
|
||||
savedEffects: void | Effects;
|
||||
savedCompletion: void | PossiblyNormalCompletion;
|
||||
|
||||
composeWithSavedCompletion(completion: PossiblyNormalCompletion): Value {
|
||||
if (this.savedCompletion === undefined) this.savedCompletion = completion;
|
||||
else this.savedCompletion = composePossiblyNormalCompletions(this.realm, this.savedCompletion, completion);
|
||||
return completion.value;
|
||||
}
|
||||
|
||||
setCaller(context: ExecutionContext): void {
|
||||
this.caller = context;
|
||||
}
|
||||
@ -233,7 +239,7 @@ export class Realm {
|
||||
env: LexicalEnvironment,
|
||||
realm: Realm,
|
||||
metadata?: any
|
||||
) => NormalCompletion | Value | Reference,
|
||||
) => Value | Reference,
|
||||
};
|
||||
partialEvaluators: {
|
||||
[key: string]: (
|
||||
@ -359,7 +365,7 @@ export class Realm {
|
||||
// in the form of a completion, a code generator, a map of changed variable
|
||||
// bindings and a map of changed property bindings.
|
||||
evaluateNodeForEffects(ast: BabelNode, strictCode: boolean, env: LexicalEnvironment, state?: any): Effects {
|
||||
return this.evaluateForEffects(() => env.evaluateAbstractCompletion(ast, strictCode), state);
|
||||
return this.evaluateForEffects(() => env.evaluateCompletion(ast, strictCode), state);
|
||||
}
|
||||
|
||||
evaluateAndRevertInGlobalEnv(func: () => Value): void {
|
||||
@ -397,50 +403,57 @@ export class Realm {
|
||||
this.generator = new Generator(this);
|
||||
this.createdObjects = new Set();
|
||||
|
||||
for (let t1 of this.tracers) t1.beginEvaluateForEffects(state);
|
||||
|
||||
let c;
|
||||
let result;
|
||||
try {
|
||||
c = f();
|
||||
if (c instanceof Reference) c = GetValue(this, c);
|
||||
c = incorporateSavedCompletion(this, c);
|
||||
invariant(c !== undefined);
|
||||
for (let t1 of this.tracers) t1.beginEvaluateForEffects(state);
|
||||
|
||||
invariant(this.generator !== undefined);
|
||||
invariant(this.modifiedBindings !== undefined);
|
||||
invariant(this.modifiedProperties !== undefined);
|
||||
invariant(this.createdObjects !== undefined);
|
||||
let astGenerator = this.generator;
|
||||
let astBindings = this.modifiedBindings;
|
||||
let astProperties = this.modifiedProperties;
|
||||
let astCreatedObjects = this.createdObjects;
|
||||
let c;
|
||||
try {
|
||||
c = f();
|
||||
if (c instanceof Reference) c = GetValue(this, c);
|
||||
if (c instanceof Value || c instanceof AbruptCompletion) c = incorporateSavedCompletion(this, c);
|
||||
invariant(c !== undefined);
|
||||
|
||||
// Return the captured state changes and evaluation result
|
||||
result = [c, astGenerator, astBindings, astProperties, astCreatedObjects];
|
||||
if (c instanceof PossiblyNormalCompletion) {
|
||||
let savedEffects = context.savedEffects;
|
||||
if (savedEffects !== undefined) {
|
||||
// add prior effects that are not already present
|
||||
result = this.composeEffects(savedEffects, result);
|
||||
this.updateAbruptCompletions(savedEffects, c);
|
||||
context.savedEffects = undefined;
|
||||
invariant(this.generator !== undefined);
|
||||
invariant(this.modifiedBindings !== undefined);
|
||||
invariant(this.modifiedProperties !== undefined);
|
||||
invariant(this.createdObjects !== undefined);
|
||||
let astGenerator = this.generator;
|
||||
let astBindings = this.modifiedBindings;
|
||||
let astProperties = this.modifiedProperties;
|
||||
let astCreatedObjects = this.createdObjects;
|
||||
|
||||
// Return the captured state changes and evaluation result
|
||||
result = [c, astGenerator, astBindings, astProperties, astCreatedObjects];
|
||||
if (c instanceof PossiblyNormalCompletion) {
|
||||
let savedEffects = context.savedEffects;
|
||||
if (savedEffects !== undefined) {
|
||||
// add prior effects that are not already present
|
||||
result = this.composeEffects(savedEffects, result);
|
||||
this.updateAbruptCompletions(savedEffects, c);
|
||||
context.savedEffects = undefined;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
} finally {
|
||||
// Roll back the state changes
|
||||
if (context.savedEffects !== undefined) {
|
||||
this.stopEffectCaptureAndUndoEffects();
|
||||
}
|
||||
if (result !== undefined) {
|
||||
this.restoreBindings(result[2]);
|
||||
this.restoreProperties(result[3]);
|
||||
} else {
|
||||
this.restoreBindings(this.modifiedBindings);
|
||||
this.restoreProperties(this.modifiedProperties);
|
||||
}
|
||||
context.savedEffects = savedContextEffects;
|
||||
this.generator = saved_generator;
|
||||
this.modifiedBindings = savedBindings;
|
||||
this.modifiedProperties = savedProperties;
|
||||
this.createdObjects = saved_createdObjects;
|
||||
}
|
||||
return result;
|
||||
} finally {
|
||||
// Roll back the state changes
|
||||
if (context.savedEffects !== undefined) {
|
||||
this.stopEffectCaptureAndUndoEffects();
|
||||
}
|
||||
this.restoreBindings(this.modifiedBindings);
|
||||
this.restoreProperties(this.modifiedProperties);
|
||||
context.savedEffects = savedContextEffects;
|
||||
this.generator = saved_generator;
|
||||
this.modifiedBindings = savedBindings;
|
||||
this.modifiedProperties = savedProperties;
|
||||
this.createdObjects = saved_createdObjects;
|
||||
|
||||
for (let t2 of this.tracers) t2.endEvaluateForEffects(state, result);
|
||||
}
|
||||
}
|
||||
@ -458,16 +471,12 @@ export class Realm {
|
||||
if (pb) {
|
||||
pb.forEach((val, key, m) => rb.set(key, val));
|
||||
}
|
||||
sb.forEach((val, key, m) => {
|
||||
if (!rb.has(key)) rb.set(key, val);
|
||||
});
|
||||
sb.forEach((val, key, m) => rb.set(key, val));
|
||||
|
||||
if (pp) {
|
||||
pp.forEach((desc, propertyBinding, m) => sp.set(propertyBinding, desc));
|
||||
pp.forEach((desc, propertyBinding, m) => rp.set(propertyBinding, desc));
|
||||
}
|
||||
sp.forEach((val, key, m) => {
|
||||
if (!rp.has(key)) rp.set(key, val);
|
||||
});
|
||||
sp.forEach((val, key, m) => rp.set(key, val));
|
||||
|
||||
if (po) {
|
||||
po.forEach((ob, a) => ro.add(ob));
|
||||
|
@ -311,12 +311,11 @@ export class Modules {
|
||||
this.initializedModules.clear();
|
||||
let globalInitializedModulesMap = this._getGlobalProperty("__initializedModules");
|
||||
invariant(globalInitializedModulesMap instanceof ObjectValue);
|
||||
for (let moduleId of globalInitializedModulesMap.getOwnPropertyKeysArray()) {
|
||||
for (let moduleId of globalInitializedModulesMap.properties.keys()) {
|
||||
let property = globalInitializedModulesMap.properties.get(moduleId);
|
||||
invariant(property);
|
||||
let moduleValue = property.descriptor && property.descriptor.value;
|
||||
invariant(moduleValue instanceof Value);
|
||||
this.initializedModules.set(moduleId, moduleValue);
|
||||
if (moduleValue instanceof Value) this.initializedModules.set(moduleId, moduleValue);
|
||||
}
|
||||
}
|
||||
|
||||
@ -420,7 +419,7 @@ export class Modules {
|
||||
let result = effects[0];
|
||||
if (result instanceof Completion) {
|
||||
console.log(`=== UNEXPECTED ERROR during ${message} ===`);
|
||||
this.logger.logCompletion(result);
|
||||
//this.logger.logCompletion(result);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,3 @@
|
||||
// throws introspection error
|
||||
|
||||
let x = global.__abstract ? __abstract("boolean", "true") : true;
|
||||
|
||||
try {
|
||||
|
Loading…
Reference in New Issue
Block a user