mirror of
https://github.com/facebookarchive/prepack.git
synced 2024-09-11 14:46:37 +03:00
First bit of support for error recovery (#725)
This commit is contained in:
parent
be8383a74e
commit
8fd2bede97
@ -69,8 +69,10 @@ function runTest(name: string, code: string): boolean {
|
||||
speculate: true,
|
||||
},
|
||||
errorHandler.bind(null, recover ? 'RecoverIfPossible' : 'Fail', errors));
|
||||
console.log(chalk.red("Serialization succeeded though it should have failed"));
|
||||
return false;
|
||||
if (!recover) {
|
||||
console.log(chalk.red("Serialization succeeded though it should have failed"));
|
||||
return false;
|
||||
}
|
||||
} catch (e) {
|
||||
// We expect serialization to fail, so catch the error and continue
|
||||
}
|
||||
@ -81,8 +83,14 @@ function runTest(name: string, code: string): boolean {
|
||||
|
||||
for (let i = 0; i < expectedErrors.length; ++i) {
|
||||
for (let prop in expectedErrors[i]) {
|
||||
if (expectedErrors[i][prop] !== errors[i][prop]) {
|
||||
console.log(chalk.red(`Error ${i}: Expected ${expectedErrors[i][prop]} errors, but found ${errors[i][prop]}`));
|
||||
let expected = expectedErrors[i][prop];
|
||||
let actual = (errors[i]: any)[prop];
|
||||
if (prop === "location") {
|
||||
actual = JSON.stringify(actual);
|
||||
expected = JSON.stringify(expected);
|
||||
}
|
||||
if (expected !== actual) {
|
||||
console.log(chalk.red(`Error ${i + 1}: Expected ${expected} errors, but found ${actual}`));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -32,7 +32,3 @@ export class CompilerDiagnostics extends Error {
|
||||
}
|
||||
|
||||
export type ErrorHandler = (error: CompilerDiagnostics) => ErrorHandlerResult;
|
||||
|
||||
export const errorDetails: {[ErrorCode]: string} = {
|
||||
'PP0001': 'Array size must be a concrete number',
|
||||
};
|
||||
|
@ -101,7 +101,7 @@ export default function (ast: BabelNodeAssignmentExpression, strictCode: boolean
|
||||
// 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 r = GetValue(realm, computeBinary(realm, op, lval, rval));
|
||||
let r = GetValue(realm, computeBinary(realm, op, lval, rval, ast.left.loc, ast.right.loc));
|
||||
// 7. Perform ? PutValue(lref, r).
|
||||
PutValue(realm, lref, r);
|
||||
// 8. Return r.
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
import type { Realm } from "../realm.js";
|
||||
import type { LexicalEnvironment } from "../environment.js";
|
||||
import { CompilerDiagnostics } from "../errors.js";
|
||||
import { Value, AbstractValue, UndefinedValue, NullValue, BooleanValue, NumberValue, ObjectValue, StringValue } from "../values/index.js";
|
||||
import { GetValue } from "../methods/index.js";
|
||||
import { HasProperty, HasSomeCompatibleType } from "../methods/index.js";
|
||||
@ -18,7 +19,7 @@ import { Add, AbstractEqualityComparison, StrictEqualityComparison, AbstractRela
|
||||
import { ToUint32, ToInt32, ToNumber, ToPrimitive, ToString, ToPropertyKey } from "../methods/index.js";
|
||||
import { TypesDomain, ValuesDomain } from "../domains/index.js";
|
||||
import * as t from "babel-types";
|
||||
import type { BabelNodeBinaryExpression, BabelBinaryOperator } from "babel-types";
|
||||
import type { BabelNodeBinaryExpression, BabelBinaryOperator, BabelNodeSourceLocation } from "babel-types";
|
||||
import invariant from "../invariant.js";
|
||||
|
||||
export default function (ast: BabelNodeBinaryExpression, strictCode: boolean, env: LexicalEnvironment, realm: Realm): Value {
|
||||
@ -30,15 +31,36 @@ export default function (ast: BabelNodeBinaryExpression, strictCode: boolean, en
|
||||
let rref = env.evaluate(ast.right, strictCode);
|
||||
let rval = GetValue(realm, rref);
|
||||
|
||||
return computeBinary(realm, ast.operator, lval, rval);
|
||||
return computeBinary(realm, ast.operator, lval, rval, ast.left.loc, ast.right.loc);
|
||||
}
|
||||
|
||||
let unknownValueOfOrToString = "might be an object with an unknown valueOf or toString method";
|
||||
|
||||
// Returns result type if binary operation is pure (terminates, does not throw exception, does not read or write heap), otherwise undefined.
|
||||
export function getPureBinaryOperationResultType(realm: Realm, op: BabelBinaryOperator, lval: Value, rval: Value): void | typeof Value {
|
||||
export function getPureBinaryOperationResultType(
|
||||
realm: Realm, op: BabelBinaryOperator, lval: Value, rval: Value, lloc: ?BabelNodeSourceLocation, rloc: ?BabelNodeSourceLocation
|
||||
): void | typeof Value {
|
||||
if (op === "+") {
|
||||
let ltype = GetToPrimitivePureResultType(realm, lval);
|
||||
let rtype = GetToPrimitivePureResultType(realm, rval);
|
||||
if (ltype === undefined || rtype === undefined) return undefined;
|
||||
if (ltype === undefined) {
|
||||
let error = new CompilerDiagnostics(unknownValueOfOrToString, lloc, 'PP0002', 'Error');
|
||||
if (realm.handleError(error) === 'RecoverIfPossible') {
|
||||
// Assume that lval is actually a primitive or otherwise a well behaved object.
|
||||
// Also assume that it does not convert to a string if rtype is a number.
|
||||
return rtype;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
if (rtype === undefined) {
|
||||
let error = new CompilerDiagnostics(unknownValueOfOrToString, rloc, 'PP0002', 'Error');
|
||||
if (realm.handleError(error) === 'RecoverIfPossible') {
|
||||
// Assume that rval is actually a primitive or otherwise a well behaved object.
|
||||
// Also assume that it does not convert to a string if ltype is a number.
|
||||
return ltype;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
if (ltype === StringValue || rtype === StringValue) return StringValue;
|
||||
return NumberValue;
|
||||
} else if (op === "<" || op === ">" || op === ">=" || op === "<=" || op === "!=" || op === "==") {
|
||||
@ -60,7 +82,9 @@ export function getPureBinaryOperationResultType(realm: Realm, op: BabelBinaryOp
|
||||
invariant(false, "unimplemented " + op);
|
||||
}
|
||||
|
||||
export function computeBinary(realm: Realm, op: BabelBinaryOperator, lval: Value, rval: Value): Value {
|
||||
export function computeBinary(
|
||||
realm: Realm, op: BabelBinaryOperator, lval: Value, rval: Value, lloc: ?BabelNodeSourceLocation, rloc: ?BabelNodeSourceLocation
|
||||
): Value {
|
||||
// partial evaluation shortcut for a particular pattern (avoiding general throwIfNotConcrete check)
|
||||
if (op === "==" || op === "===" || op === "!=" || op === "!==") {
|
||||
if (!lval.mightNotBeObject() && HasSomeCompatibleType(rval, NullValue, UndefinedValue) ||
|
||||
@ -69,7 +93,7 @@ export function computeBinary(realm: Realm, op: BabelBinaryOperator, lval: Value
|
||||
}
|
||||
|
||||
if ((lval instanceof AbstractValue) || (rval instanceof AbstractValue)) {
|
||||
let type = getPureBinaryOperationResultType(realm, op, lval, rval);
|
||||
let type = getPureBinaryOperationResultType(realm, op, lval, rval, lloc, rloc);
|
||||
if (type !== undefined) {
|
||||
return realm.createAbstract(new TypesDomain(type), ValuesDomain.topVal, [lval, rval],
|
||||
([lnode, rnode]) => t.binaryExpression(op, lnode, rnode));
|
||||
|
@ -130,5 +130,6 @@ export default function (
|
||||
if (resultAst === undefined) {
|
||||
resultAst = t.assignmentExpression(ast.operator, (last: any), (rast: any));
|
||||
}
|
||||
return createAbstractValueForBinary(op, resultAst, lval, rval, leftCompletion, rightCompletion, resultValue, io, realm);
|
||||
return createAbstractValueForBinary(op, resultAst, lval, rval, last.loc, rast.loc,
|
||||
leftCompletion, rightCompletion, resultValue, io, realm);
|
||||
}
|
||||
|
@ -9,7 +9,8 @@
|
||||
|
||||
/* @flow */
|
||||
|
||||
import type { BabelBinaryOperator, BabelNodeBinaryExpression, BabelNodeExpression, BabelNodeStatement } from "babel-types";
|
||||
import type { BabelBinaryOperator, BabelNodeBinaryExpression, BabelNodeExpression, BabelNodeStatement,
|
||||
BabelNodeSourceLocation } from "babel-types";
|
||||
import type { LexicalEnvironment } from "../environment.js";
|
||||
import type { Realm } from "../realm.js";
|
||||
|
||||
@ -61,16 +62,18 @@ export default function (
|
||||
if (resultAst === undefined) {
|
||||
resultAst = t.binaryExpression(op, (leftAst: any), (rightAst: any));
|
||||
}
|
||||
return createAbstractValueForBinary(op, resultAst, lval, rval, leftCompletion, rightCompletion, resultValue, io, realm);
|
||||
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);
|
||||
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
|
||||
|
@ -655,10 +655,10 @@ export class Realm {
|
||||
// Pass the error to the realm's error-handler
|
||||
// Return value indicates whether the caller should try to recover from the
|
||||
// error or not ('true' means recover if possible).
|
||||
handleError(error: CompilerDiagnostics): ErrorHandlerResult {
|
||||
handleError(diagnostic: CompilerDiagnostics): ErrorHandlerResult {
|
||||
// Default behaviour is to bail on the first error
|
||||
let errorHandler = this.errorHandler;
|
||||
if (!errorHandler) return 'Fail';
|
||||
return errorHandler(error);
|
||||
return errorHandler(diagnostic);
|
||||
}
|
||||
}
|
||||
|
16
test/error-handler/BinaryExpression.js
Normal file
16
test/error-handler/BinaryExpression.js
Normal file
@ -0,0 +1,16 @@
|
||||
// recover-from-errors
|
||||
// expected errors: [{location: {"start":{"line":11,"column":12},"end":{"line":11,"column":13},"identifierName":"y","source":"test/error-handler/BinaryExpression.js"}, errorCode: "PP0002", severity: "Error", message: "might be an object with an unknown valueOf or toString method"}]
|
||||
|
||||
var b = global.__abstract ? __abstract("boolean", true) : true;
|
||||
var x = global.__abstract ? __abstract("number", 123) : 123;
|
||||
var badOb = { valueOf: function() { throw 13;} }
|
||||
var ob = global.__abstract ? __abstract("object", "({ valueOf: function() { throw 13;} })") : badOb;
|
||||
var y = b ? ob : x;
|
||||
|
||||
try {
|
||||
z = 100 + y;
|
||||
} catch (err) {
|
||||
z = 200 + err;
|
||||
}
|
||||
|
||||
inspect = function() { return "" + z; }
|
Loading…
Reference in New Issue
Block a user