First bit of support for error recovery (#725)

This commit is contained in:
Herman Venter 2017-06-13 14:09:18 -07:00 committed by GitHub
parent be8383a74e
commit 8fd2bede97
8 changed files with 69 additions and 21 deletions

View File

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

View File

@ -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',
};

View File

@ -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.

View File

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

View File

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

View File

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

View File

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

View 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; }