Emit code for exceptions that make it to the top level.

Summary:
Release note: Generate code for uncaught exceptions

This is a high priority fix for a land blocking issue. Please review ASAP.

Exceptions that (conditionally or unconditionally) propagate to the top level program used to cause Prepack to fail.

The new behavior is to generate top level code that will (conditionally) throw the exceptions at runtime.
Closes https://github.com/facebook/prepack/pull/1083

Differential Revision: D6050038

Pulled By: hermanventer

fbshipit-source-id: 666c058befdebf2ed5f97bed74818db57b86f34b
This commit is contained in:
Herman Venter 2017-10-12 23:36:11 -07:00 committed by Facebook Github Bot
parent acf67a3362
commit b0e977136c
16 changed files with 122 additions and 29 deletions

View File

@ -209,8 +209,12 @@ function runTest(name, code, options, args) {
let unique = 27277;
let oldUniqueSuffix = "";
try {
expected = exec(`${addedCode}\n(function () {${code} // keep newline here as code may end with comment
}).call(this);`);
try {
expected = exec(`${addedCode}\n(function () {${code} // keep newline here as code may end with comment
}).call(this);`);
} catch (e) {
expected = e;
}
let i = 0;
let max = addedCode ? 1 : 4;
@ -237,7 +241,11 @@ function runTest(name, code, options, args) {
}
}
if (markersIssue) break;
actual = exec(addedCode + newCode);
try {
actual = exec(addedCode + newCode);
} catch (e) {
actual = e;
}
if (expected !== actual) {
console.log(chalk.red("Output mismatch!"));
break;

View File

@ -109,7 +109,7 @@ export default class TypesDomain {
}
static logicalOp(op: BabelNodeLogicalOperator, left: TypesDomain, right: TypesDomain): TypesDomain {
return TypesDomain.joinValues(left, right);
return left.joinWith(right.getType());
}
// return the type of the result in the case where there is no exception

View File

@ -9,11 +9,10 @@
/* @flow */
import { Completion } from "../completions.js";
import { Completion, JoinedAbruptCompletions, ThrowCompletion } from "../completions.js";
import type { Realm } from "../realm.js";
import type { LexicalEnvironment } from "../environment.js";
import { FatalError } from "../errors.js";
import { Value, EmptyValue } from "../values/index.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";
@ -21,6 +20,7 @@ import IsStrict from "../utils/strict.js";
import invariant from "../invariant.js";
import traverseFast from "../utils/traverse-fast.js";
import type { BabelNodeProgram } from "babel-types";
import * as t from "babel-types";
// ECMA262 15.1.11
export function GlobalDeclarationInstantiation(
@ -232,12 +232,19 @@ export default function(ast: BabelNodeProgram, strictCode: boolean, env: Lexical
let potentialVal = env.evaluateCompletion(node, strictCode);
if (potentialVal instanceof Completion) {
if (!realm.useAbstractInterpretation) throw potentialVal;
// todo: emit code to throw exeptions at runtime
throw new FatalError();
if (potentialVal instanceof JoinedAbruptCompletions) {
emitConditionalThrow(potentialVal.joinCondition, potentialVal.consequent, potentialVal.alternate);
potentialVal = potentialVal.value;
} else if (potentialVal instanceof ThrowCompletion) {
emitThrow(potentialVal.value);
potentialVal = realm.intrinsics.undefined;
} else {
invariant(false); // other kinds of abrupt completions should not get this far
}
}
if (context.savedCompletion !== undefined) {
// todo: emit conditional code to throw exeptions at runtime
throw new FatalError();
let sc = context.savedCompletion;
if (sc !== undefined) {
emitConditionalThrow(sc.joinCondition, sc.consequent, sc.alternate);
}
if (!(potentialVal instanceof EmptyValue)) val = potentialVal;
}
@ -251,4 +258,55 @@ export default function(ast: BabelNodeProgram, strictCode: boolean, env: Lexical
invariant(val === undefined || val instanceof Value);
return val || realm.intrinsics.empty;
function emitThrow(value: Value) {
let generator = realm.generator;
invariant(generator !== undefined);
generator.emitStatement([value], ([argument]) => t.throwStatement(argument));
}
function emitConditionalThrow(
condition: AbstractValue,
trueBranch: Completion | Value,
falseBranch: Completion | Value
) {
let generator = realm.generator;
invariant(generator !== undefined);
let [args, buildfunc] = deconstruct(condition, trueBranch, falseBranch);
generator.emitStatement(args, buildfunc);
}
function deconstruct(condition: AbstractValue, trueBranch: Completion | Value, falseBranch: Completion | Value) {
let targs;
let tfunc;
let fargs;
let ffunc;
if (trueBranch instanceof JoinedAbruptCompletions) {
[targs, tfunc] = deconstruct(trueBranch.joinCondition, trueBranch.consequent, trueBranch.alternate);
} else if (trueBranch instanceof ThrowCompletion) {
targs = [trueBranch.value];
tfunc = ([argument]) => t.throwStatement(argument);
} else {
targs = [];
tfunc = nodes => t.emptyStatement();
}
if (falseBranch instanceof JoinedAbruptCompletions) {
[fargs, ffunc] = deconstruct(falseBranch.joinCondition, falseBranch.consequent, falseBranch.alternate);
} else if (falseBranch instanceof ThrowCompletion) {
fargs = [falseBranch.value];
ffunc = ([argument]) => t.throwStatement(argument);
} else {
fargs = [];
ffunc = nodes => t.emptyStatement();
}
let args = [condition].concat(targs).concat(fargs);
let func = nodes => {
return t.ifStatement(
nodes[0],
tfunc(nodes.splice(1, targs.length)),
ffunc(nodes.splice(targs.length + 1, fargs.length))
);
};
return [args, func];
}
}

View File

@ -1078,6 +1078,6 @@ export function KeyedBindingInitialization(
}
// 3. Return the result of performing BindingInitialization for BindingPattern passing v and environment as arguments.
return BindingInitialization(realm, node, v, strictCode, environment);
return BindingInitialization(realm, node, v, strictCode, env);
}
}

View File

@ -1130,7 +1130,7 @@ export function PerformEval(realm: Realm, x: Value, evalRealm: Realm, strictCall
}
}
export function incorporateSavedCompletion(realm: Realm, c: void | Completion | Value): Completion | Value {
export function incorporateSavedCompletion(realm: Realm, c: void | Completion | Value): void | Completion | Value {
let context = realm.getRunningContext();
let savedCompletion = context.savedCompletion;
if (savedCompletion !== undefined) {

View File

@ -413,7 +413,9 @@ function joinResults(
return new JoinedAbruptCompletions(realm, joinCondition, result1, e1, result2, e2);
}
if (result1 instanceof Value && result2 instanceof Value) {
return joinValues(realm, result1, result2, getAbstractValue);
let val = joinValues(realm, result1, result2, getAbstractValue);
invariant(val instanceof Value);
return val;
}
if (result1 instanceof PossiblyNormalCompletion && result2 instanceof PossiblyNormalCompletion) {
return composePossiblyNormalCompletions(realm, result1, result2);

View File

@ -710,6 +710,7 @@ export function ToPropertyKeyPartial(
): AbstractValue | SymbolValue | string /* but not StringValue */ {
if (arg instanceof ConcreteValue) return ToPropertyKey(realm, arg);
if (arg.mightNotBeString()) arg.throwIfNotConcrete();
invariant(arg instanceof AbstractValue);
return arg;
}

View File

@ -343,7 +343,7 @@ export class Realm {
return this.evaluateForEffects(() => env.evaluateAbstractCompletion(ast, strictCode), state);
}
evaluateAndRevertInGlobalEnv(func: () => void): void {
evaluateAndRevertInGlobalEnv(func: () => Value): void {
this.wrapInGlobalEnv(() => this.evaluateForEffects(func));
}
@ -386,6 +386,7 @@ export class Realm {
c = f();
if (c instanceof Reference) c = GetValue(this, c);
c = incorporateSavedCompletion(this, c);
invariant(c !== undefined);
invariant(this.generator !== undefined);
invariant(this.modifiedBindings !== undefined);
@ -476,10 +477,10 @@ export class Realm {
}
context.savedEffects = [
this.intrinsics.undefined,
this.generator,
this.modifiedBindings,
this.modifiedProperties,
this.createdObjects,
(this.generator: any),
(this.modifiedBindings: any),
(this.modifiedProperties: any),
(this.createdObjects: any),
];
this.generator = new Generator(this);
this.modifiedBindings = new Map();

View File

@ -1273,6 +1273,7 @@ export class ResidualHeapSerializer {
this.realm.restoreProperties(modifiedProperties);
}
}
return this.realm.intrinsics.undefined;
};
this.realm.evaluateAndRevertInGlobalEnv(processAdditionalFunctionValuesFn);
return rewrittenAdditionalFunctions;

View File

@ -616,6 +616,7 @@ export class ResidualHeapVisitor {
});
}
}
return this.realm.intrinsics.undefined;
}
visitRoots(): void {

View File

@ -226,7 +226,14 @@ export class Generator {
args: Array<Value>,
kind?: string
): AbstractValue {
return this.derive(types, values, args, nodes => t.callExpression(createCallee(), nodes));
return this.derive(types, values, args, (nodes: any) => t.callExpression(createCallee(), nodes));
}
emitStatement(args: Array<Value>, buildNode_: (Array<BabelNodeExpression>) => BabelNodeStatement) {
this.addEntry({
args,
buildNode: buildNode_,
});
}
emitVoidExpression(

View File

@ -1,5 +1,4 @@
// throws introspection error
let x = __abstract("boolean", "true")
let x = global.__abstract ? __abstract("boolean", "true") : true;
y = 1;
y1 = 2;
@ -16,3 +15,5 @@ function f(b) {
}
z = f(!x);
inspect = function() { return z; }

View File

@ -1,5 +1,4 @@
// throws introspection error
let x = __abstract("boolean", "true")
let x = global.__abstract ? __abstract("boolean", "true") : true
y = 1;
y1 = 2;
@ -16,3 +15,5 @@ function f(b) {
}
z = f(!x);
inspect = function() { return z; }

View File

@ -1,5 +1,4 @@
// throws introspection error
let x = __abstract("boolean", "true");
let x = global.__abstract ? __abstract("boolean", "true") : true
y = 1;
@ -9,3 +8,5 @@ function f(b) {
}
z = f(!x);
inspect = function() { return z; }

View File

@ -1,5 +1,4 @@
// throws introspection error
let x = __abstract("boolean", "true");
let x = global.__abstract ? __abstract("boolean", "true") : true
y = 1;
@ -13,3 +12,5 @@ function g(b) {
}
z = g(x);
inspect = function() { return z; }

View File

@ -0,0 +1,10 @@
let x = global.__abstract ? __abstract("boolean", "true") : true;
function foo(b) {
if (b) throw new Error("is true");
return "is false";
}
z = foo(!x);
inspect = function() { return z; }