prepack
Version:
Execute a JS bundle, serialize global state and side effects to a snapshot that can be quickly restored.
283 lines (223 loc) • 12.2 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = _default;
exports.getPureBinaryOperationResultType = getPureBinaryOperationResultType;
exports.computeBinary = computeBinary;
var _completions = require("../completions.js");
var _index = require("../domains/index.js");
var _errors = require("../errors.js");
var _index2 = require("../values/index.js");
var _singletons = require("../singletons.js");
var _generator = require("../utils/generator.js");
var _invariant = _interopRequireDefault(require("../invariant.js"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* Copyright (c) 2017-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
/* strict-local */
function _default(ast, strictCode, env, realm) {
// evaluate left
let lref = env.evaluate(ast.left, strictCode);
let lval = _singletons.Environment.GetValue(realm, lref); // evaluate right
let rref = env.evaluate(ast.right, strictCode);
let rval = _singletons.Environment.GetValue(realm, rref);
return computeBinary(realm, ast.operator, lval, rval, ast.left.loc, ast.right.loc, ast.loc);
}
let unknownValueOfOrToString = "might be an object with an unknown valueOf or toString or Symbol.toPrimitive method"; // Returns result type if binary operation is pure (terminates, does not throw exception, does not read or write heap), otherwise undefined.
function getPureBinaryOperationResultType(realm, op, lval, rval, lloc, rloc) {
function reportErrorIfNotPure(purityTest, typeIfPure) {
let leftPure = purityTest(realm, lval);
let rightPure = purityTest(realm, rval);
if (leftPure && rightPure) return typeIfPure;
let loc = !leftPure ? lloc : rloc;
let error = new _errors.CompilerDiagnostic(unknownValueOfOrToString, loc, "PP0002", "RecoverableError");
if (realm.handleError(error) === "Recover") {
// Assume that an unknown value is actually a primitive or otherwise a well behaved object.
return typeIfPure;
}
throw new _errors.FatalError();
}
if (op === "+") {
let ltype = _singletons.To.GetToPrimitivePureResultType(realm, lval);
let rtype = _singletons.To.GetToPrimitivePureResultType(realm, rval);
if (ltype === _index2.StringValue || rtype === _index2.StringValue) {
// If either type is a string, the other one will be called with ToString, so that has to be pure.
if (!_singletons.To.IsToStringPure(realm, rval)) {
rtype = undefined;
}
if (!_singletons.To.IsToStringPure(realm, lval)) {
ltype = undefined;
}
} else {
// Otherwise, they will be called with ToNumber, so that has to be pure.
if (!_singletons.To.IsToNumberPure(realm, rval)) {
rtype = undefined;
}
if (!_singletons.To.IsToNumberPure(realm, lval)) {
ltype = undefined;
}
}
if (ltype === undefined || rtype === undefined) {
if (lval.getType() === _index2.SymbolValue || rval.getType() === _index2.SymbolValue) {
// Symbols never implicitly coerce to primitives.
throw realm.createErrorThrowCompletion(realm.intrinsics.TypeError);
}
let loc = ltype === undefined ? lloc : rloc;
let error = new _errors.CompilerDiagnostic(unknownValueOfOrToString, loc, "PP0002", "RecoverableError");
if (realm.handleError(error) === "Recover") {
// Assume that the unknown value is actually a primitive or otherwise a well behaved object.
ltype = lval.getType();
rtype = rval.getType();
if (ltype === _index2.StringValue || rtype === _index2.StringValue) return _index2.StringValue;
if (ltype === _index2.IntegralValue && rtype === _index2.IntegralValue) return _index2.IntegralValue;
if ((ltype === _index2.NumberValue || ltype === _index2.IntegralValue) && (rtype === _index2.NumberValue || rtype === _index2.IntegralValue)) return _index2.NumberValue;
return _index2.Value;
}
throw new _errors.FatalError();
}
if (ltype === _index2.StringValue || rtype === _index2.StringValue) return _index2.StringValue;
return _index2.NumberValue;
} else if (op === "<" || op === ">" || op === ">=" || op === "<=") {
return reportErrorIfNotPure(_singletons.To.IsToPrimitivePure.bind(_singletons.To), _index2.BooleanValue);
} else if (op === "!=" || op === "==") {
let ltype = lval.getType();
let rtype = rval.getType();
if (ltype === _index2.NullValue || ltype === _index2.UndefinedValue || rtype === _index2.NullValue || rtype === _index2.UndefinedValue) return _index2.BooleanValue;
return reportErrorIfNotPure(_singletons.To.IsToPrimitivePure.bind(_singletons.To), _index2.BooleanValue);
} else if (op === "===" || op === "!==") {
return _index2.BooleanValue;
} else if (op === ">>>" || op === "<<" || op === ">>" || op === "&" || op === "|" || op === "^" || op === "**" || op === "%" || op === "/" || op === "*" || op === "-") {
if (lval.getType() === _index2.SymbolValue || rval.getType() === _index2.SymbolValue) {
throw realm.createErrorThrowCompletion(realm.intrinsics.TypeError);
}
return reportErrorIfNotPure(_singletons.To.IsToNumberPure.bind(_singletons.To), _index2.NumberValue);
} else if (op === "in" || op === "instanceof") {
if (rval.mightNotBeObject()) {
let error = new _errors.CompilerDiagnostic(`might not be an object, hence the ${op} operator might throw a TypeError`, rloc, "PP0003", "RecoverableError");
if (realm.handleError(error) === "Recover") {
// Assume that the object is actually a well behaved object.
return _index2.BooleanValue;
}
throw new _errors.FatalError();
}
if (!rval.mightNotBeObject()) {
// Simple object won't throw here, aren't proxy objects or typed arrays and do not have @@hasInstance properties.
if (rval.isSimpleObject()) return _index2.BooleanValue;
}
let error = new _errors.CompilerDiagnostic(`might be an object that behaves badly for the ${op} operator`, rloc, "PP0004", "RecoverableError");
if (realm.handleError(error) === "Recover") {
// Assume that the object is actually a well behaved object.
return _index2.BooleanValue;
}
throw new _errors.FatalError();
}
(0, _invariant.default)(false, "unimplemented " + op);
}
function computeBinary(realm, op, lval, rval, lloc, rloc, loc) {
// partial evaluation shortcut for a particular pattern
if (realm.useAbstractInterpretation && (op === "==" || op === "===" || op === "!=" || op === "!==")) {
if (!lval.mightNotBeObject() && (rval instanceof _index2.NullValue || rval instanceof _index2.UndefinedValue) || (lval instanceof _index2.NullValue || lval instanceof _index2.UndefinedValue) && !rval.mightNotBeObject()) {
return new _index2.BooleanValue(realm, op[0] !== "=");
}
}
let resultType;
const compute = () => {
let lvalIsAbstract = lval instanceof _index2.AbstractValue;
let rvalIsAbstract = rval instanceof _index2.AbstractValue;
if (lvalIsAbstract || rvalIsAbstract) {
// If the left-hand side of an instanceof operation is a primitive,
// and the right-hand side is a simple object (it does not have [Symbol.hasInstance]),
// then the result should always compute to `false`.
if (op === "instanceof" && _index2.Value.isTypeCompatibleWith(lval.getType(), _index2.PrimitiveValue) && rval instanceof _index2.AbstractObjectValue && rval.isSimpleObject()) {
return realm.intrinsics.false;
}
try {
// generate error if binary operation might throw or have side effects
resultType = getPureBinaryOperationResultType(realm, op, lval, rval, lloc, rloc);
let result = _index2.AbstractValue.createFromBinaryOp(realm, op, lval, rval, loc);
if ((op === "in" || op === "instanceof") && result instanceof _index2.AbstractValue && rvalIsAbstract) // This operation is a conditional atemporal
// See #2327
result = _index2.AbstractValue.convertToTemporalIfArgsAreTemporal(realm, result, [rval]
/* throwing does not depend upon lval */
);
return result;
} catch (x) {
if (x instanceof _errors.FatalError) {
// There is no need to revert any effects, because the above operation is pure.
// If this failed and one of the arguments was conditional, try each value
// and join the effects based on the condition.
if (lval instanceof _index2.AbstractValue && lval.kind === "conditional") {
let [condition, consequentL, alternateL] = lval.args;
(0, _invariant.default)(condition instanceof _index2.AbstractValue);
return realm.evaluateWithAbstractConditional(condition, () => realm.evaluateForEffects(() => computeBinary(realm, op, consequentL, rval, lloc, rloc, loc), undefined, "ConditionalBinaryExpression/1"), () => realm.evaluateForEffects(() => computeBinary(realm, op, alternateL, rval, lloc, rloc, loc), undefined, "ConditionalBinaryExpression/2"));
}
if (rval instanceof _index2.AbstractValue && rval.kind === "conditional") {
let [condition, consequentR, alternateR] = rval.args;
(0, _invariant.default)(condition instanceof _index2.AbstractValue);
return realm.evaluateWithAbstractConditional(condition, () => realm.evaluateForEffects(() => computeBinary(realm, op, lval, consequentR, lloc, rloc, loc), undefined, "ConditionalBinaryExpression/3"), () => realm.evaluateForEffects(() => computeBinary(realm, op, lval, alternateR, lloc, rloc, loc), undefined, "ConditionalBinaryExpression/4"));
}
}
throw x;
}
} else {
// ECMA262 12.10.3
// 5. If Type(rval) is not Object, throw a TypeError exception.
if (op === "in" && !(rval instanceof _index2.ObjectValue)) {
throw realm.createErrorThrowCompletion(realm.intrinsics.TypeError);
}
(0, _invariant.default)(lval instanceof _index2.ConcreteValue);
(0, _invariant.default)(rval instanceof _index2.ConcreteValue);
const result = _index.ValuesDomain.computeBinary(realm, op, lval, rval);
resultType = result.getType();
return result;
}
};
if (realm.isInPureScope()) {
// If we're in pure mode we can recover even if this operation might not be pure.
// To do that, we'll temporarily override the error handler.
const previousErrorHandler = realm.errorHandler;
let isPure = true;
realm.errorHandler = diagnostic => {
isPure = false;
return "Recover";
};
let effects;
try {
effects = realm.evaluateForEffects(compute, undefined, "computeBinary");
} catch (x) {
if (x instanceof _errors.FatalError) {
isPure = false;
} else {
throw x;
}
} finally {
realm.errorHandler = previousErrorHandler;
}
if (isPure && effects) {
realm.applyEffects(effects);
if (effects.result instanceof _completions.SimpleNormalCompletion) return effects.result.value;
} // If this ended up reporting an error, it might not be pure, so we'll leave it in
// as a temporal operation with a known return type.
// Some of these values may trigger side-effectful user code such as valueOf.
// To be safe, we have to leak them.
_singletons.Leak.value(realm, lval, loc);
if (op !== "in") {
// The "in" operator have side-effects on its right val other than throw.
_singletons.Leak.value(realm, rval, loc);
}
return realm.evaluateWithPossibleThrowCompletion(() => _index2.AbstractValue.createTemporalFromBuildFunction(realm, resultType, [lval, rval], (0, _generator.createOperationDescriptor)("BINARY_EXPRESSION", {
binaryOperator: op
}), {
isPure: true
}), _index.TypesDomain.topVal, _index.ValuesDomain.topVal);
}
return compute();
}
//# sourceMappingURL=BinaryExpression.js.map