UNPKG

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
"use strict"; 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