UNPKG

prepack

Version:

Execute a JS bundle, serialize global state and side effects to a snapshot that can be quickly restored.

666 lines (536 loc) 25 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; /** * 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. */ var _errors = require("../errors.js"); var _index = require("./index.js"); var _utils = require("../react/utils.js"); var _builder = require("../utils/builder.js"); var _builder2 = _interopRequireDefault(_builder); var _index2 = require("../methods/index.js"); var _singletons = require("../singletons.js"); var _invariant = require("../invariant.js"); var _invariant2 = _interopRequireDefault(_invariant); var _babelTypes = require("babel-types"); var t = _interopRequireWildcard(_babelTypes); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function isWidenedValue(v) { if (!(v instanceof _index.AbstractValue)) return false; if (v.kind === "widened" || v.kind === "widened property") return true; for (let a of v.args) { if (isWidenedValue(a)) return true; } return false; } const lengthTemplateSrc = "(A).length"; const lengthTemplate = (0, _builder2.default)(lengthTemplateSrc); class ObjectValue extends _index.ConcreteValue { constructor(realm, proto, intrinsicName, refuseSerialization = false) { super(realm, intrinsicName); realm.recordNewObject(this); if (realm.useAbstractInterpretation) this.setupBindings(this.getTrackedPropertyNames()); this.$Prototype = proto || realm.intrinsics.null; this.$Extensible = realm.intrinsics.true; this._isPartial = realm.intrinsics.false; this._isHavoced = realm.intrinsics.false; this._isSimple = realm.intrinsics.false; this._simplicityIsTransitive = realm.intrinsics.false; this._isFinal = realm.intrinsics.false; this.properties = new Map(); this.symbols = new Map(); this.refuseSerialization = refuseSerialization; this.$IsClassPrototype = false; } getTrackedPropertyNames() { return ObjectValue.trackedPropertyNames; } setupBindings(propertyNames) { for (let propName of propertyNames) { let desc = { writeable: true, value: undefined }; this[propName + "_binding"] = { descriptor: desc, object: this, key: propName }; } } static setupTrackedPropertyAccessors(propertyNames) { for (let propName of propertyNames) { Object.defineProperty(ObjectValue.prototype, propName, { configurable: true, get: function () { let binding = this[propName + "_binding"]; return binding.descriptor.value; }, set: function (v) { (0, _invariant2.default)(!this.isHavocedObject(), "cannot mutate a havoced object"); let binding = this[propName + "_binding"]; this.$Realm.recordModifiedProperty(binding); binding.descriptor.value = v; } }); } } // undefined when the property is "missing" // error // function // promise // iterator // set // react // map // weak map // weak set // date // of type number // array // regex // string // data view // array buffer // generator // typed array // backpointer to the constructor if this object was created its prototype object // partial objects // tainted objects // If true, the object has no property getters or setters and it is safe // to return AbstractValue for unknown properties. // If true, it is not safe to perform any more mutations that would change // the object's serialized form. // Specifies whether the object is a template that needs to be created in a scope // If set, this happened during object initialization and the value is never changed again, so not tracked. // If true, then unknown properties should return transitively simple abstract object values // The abstract object for which this object is the template. // Use this instead of the object itself when deriving temporal values for object properties. // An object value with an intrinsic name can either exist from the beginning of time, // or it can be associated with a particular point in time by being used as a template // when deriving an abstract value via a generator. // ReactElement // ES2015 classes equals(x) { return x instanceof ObjectValue && this.getHash() === x.getHash(); } getHash() { if (!this.hashValue) { this.hashValue = ++this.$Realm.objectCount; } return this.hashValue; } // We track some internal state as properties on the global object, these should // never be serialized. mightBeFalse() { return false; } mightNotBeObject() { return false; } throwIfNotObject() { return this; } makeNotPartial() { this._isPartial = this.$Realm.intrinsics.false; } makePartial() { this._isPartial = this.$Realm.intrinsics.true; } makeSimple(option) { this._isSimple = this.$Realm.intrinsics.true; this._simplicityIsTransitive = new _index.BooleanValue(this.$Realm, option === "transitive" || option instanceof _index.StringValue && option.value === "transitive"); } makeFinal() { this._isFinal = this.$Realm.intrinsics.true; } isPartialObject() { return !!this._isPartial && this._isPartial.mightBeTrue(); } isFinalObject() { return !!this._isFinal && this._isFinal.mightBeTrue(); } havoc() { this._isHavoced = this.$Realm.intrinsics.true; } isHavocedObject() { return !!this._isHavoced && this._isHavoced.mightBeTrue(); } isSimpleObject() { if (this._isSimple && !this._isSimple.mightNotBeTrue()) return true; if (this.isPartialObject()) return false; if (this.symbols.size > 0) return false; for (let propertyBinding of this.properties.values()) { let desc = propertyBinding.descriptor; if (desc === undefined) continue; // deleted if (!(0, _index2.IsDataDescriptor)(this.$Realm, desc)) return false; if (!desc.writable) return false; } if (this.$Prototype instanceof _index.NullValue) return true; if (this.$Prototype === this.$Realm.intrinsics.ObjectPrototype) return true; (0, _invariant2.default)(this.$Prototype); return this.$Prototype.isSimpleObject(); } isTransitivelySimple() { return !!this._simplicityIsTransitive && !this._simplicityIsTransitive.mightNotBeTrue(); } getExtensible() { return this.$Extensible.throwIfNotConcreteBoolean().value; } setExtensible(v) { this.$Extensible = v ? this.$Realm.intrinsics.true : this.$Realm.intrinsics.false; } getKind() { // we can deduce the natural prototype by checking whether the following internal slots are present if (this.$SymbolData !== undefined) return "Symbol"; if (this.$StringData !== undefined) return "String"; if (this.$NumberData !== undefined) return "Number"; if (this.$BooleanData !== undefined) return "Boolean"; if (this.$DateValue !== undefined) return "Date"; if (this.$RegExpMatcher !== undefined) return "RegExp"; if (this.$SetData !== undefined) return "Set"; if (this.$MapData !== undefined) return "Map"; if (this.$DataView !== undefined) return "DataView"; if (this.$ArrayBufferData !== undefined) return "ArrayBuffer"; if (this.$WeakMapData !== undefined) return "WeakMap"; if (this.$WeakSetData !== undefined) return "WeakSet"; if ((0, _utils.isReactElement)(this) && this.$Realm.react.enabled) return "ReactElement"; if (this.$TypedArrayName !== undefined) return this.$TypedArrayName; // TODO #26 #712: Promises. All kinds of iterators. Generators. return "Object"; } defineNativeMethod(name, length, callback, desc = {}) { let intrinsicName; if (typeof name === "string") { if (this.intrinsicName) intrinsicName = `${this.intrinsicName}.${name}`; } else if (name instanceof _index.SymbolValue) { if (this.intrinsicName && name.intrinsicName) intrinsicName = `${this.intrinsicName}[${name.intrinsicName}]`; } else { (0, _invariant2.default)(false); } let fnValue = new _index.NativeFunctionValue(this.$Realm, intrinsicName, name, length, callback, false); this.defineNativeProperty(name, fnValue, desc); return fnValue; } defineNativeProperty(name, value, desc = {}) { (0, _invariant2.default)(!value || value instanceof _index.Value); this.$DefineOwnProperty(name, _extends({ value, writable: true, enumerable: false, configurable: true }, desc)); } defineNativeGetter(name, callback, desc = {}) { let intrinsicName, funcName; if (typeof name === "string") { funcName = `get ${name}`; if (this.intrinsicName) intrinsicName = `${this.intrinsicName}.${name}`; } else if (name instanceof _index.SymbolValue) { funcName = name.$Description instanceof _index.Value ? `get [${name.$Description.throwIfNotConcreteString().value}]` : `get [${"?"}]`; if (this.intrinsicName && name.intrinsicName) intrinsicName = `${this.intrinsicName}[${name.intrinsicName}]`; } else { (0, _invariant2.default)(false); } let func = new _index.NativeFunctionValue(this.$Realm, intrinsicName, funcName, 0, callback); this.$DefineOwnProperty(name, _extends({ get: func, set: this.$Realm.intrinsics.undefined, enumerable: false, configurable: true }, desc)); } defineNativeConstant(name, value, desc = {}) { (0, _invariant2.default)(!value || value instanceof _index.Value); this.$DefineOwnProperty(name, _extends({ value, writable: false, enumerable: false, configurable: false }, desc)); } getOwnPropertyKeysArray() { if (this.isPartialObject() || this.isHavocedObject() || this.unknownProperty !== undefined) { _index.AbstractValue.reportIntrospectionError(this); throw new _errors.FatalError(); } let keyArray = Array.from(this.properties.keys()); keyArray = keyArray.filter(x => { let pb = this.properties.get(x); if (!pb || pb.descriptor === undefined) return false; let pv = pb.descriptor.value; if (pv === undefined) return true; (0, _invariant2.default)(pv instanceof _index.Value); if (!pv.mightHaveBeenDeleted()) return true; // The property may or may not be there at runtime. // We can at best return an abstract keys array. // For now just terminate. (0, _invariant2.default)(pv instanceof _index.AbstractValue); _index.AbstractValue.reportIntrospectionError(pv); throw new _errors.FatalError(); }); this.$Realm.callReportObjectGetOwnProperties(this); return keyArray; } _serialize(set, stack) { let obj = set({}); for (let [key, propertyBinding] of this.properties) { let desc = propertyBinding.descriptor; if (desc === undefined) continue; // deleted _singletons.Properties.ThrowIfMightHaveBeenDeleted(desc.value); let serializedDesc = { enumerable: desc.enumerable, configurable: desc.configurable }; if (desc.value) { serializedDesc.writable = desc.writable; (0, _invariant2.default)(desc.value instanceof _index.Value); serializedDesc.value = desc.value.serialize(stack); } else { (0, _invariant2.default)(desc.get !== undefined); serializedDesc.get = desc.get.serialize(stack); (0, _invariant2.default)(desc.set !== undefined); serializedDesc.set = desc.set.serialize(stack); } Object.defineProperty(obj, key, serializedDesc); } return obj; } // ECMA262 9.1.1 $GetPrototypeOf() { return this.$Prototype; } // ECMA262 9.1.2 $SetPrototypeOf(V) { // 1. Return ! OrdinarySetPrototypeOf(O, V). return _singletons.Properties.OrdinarySetPrototypeOf(this.$Realm, this, V); } // ECMA262 9.1.3 $IsExtensible() { // 1. Return ! OrdinaryIsExtensible(O). return (0, _index2.OrdinaryIsExtensible)(this.$Realm, this); } // ECMA262 9.1.4 $PreventExtensions() { // 1. Return ! OrdinaryPreventExtensions(O). return (0, _index2.OrdinaryPreventExtensions)(this.$Realm, this); } // ECMA262 9.1.5 $GetOwnProperty(P) { // 1. Return ! OrdinaryGetOwnProperty(O, P). return _singletons.Properties.OrdinaryGetOwnProperty(this.$Realm, this, P); } // ECMA262 9.1.6 $DefineOwnProperty(P, Desc) { // 1. Return ? OrdinaryDefineOwnProperty(O, P, Desc). return _singletons.Properties.OrdinaryDefineOwnProperty(this.$Realm, this, P, Desc); } // ECMA262 9.1.7 $HasProperty(P) { if (this.unknownProperty !== undefined && this.$GetOwnProperty(P) === undefined) { _index.AbstractValue.reportIntrospectionError(this, P); throw new _errors.FatalError(); } return (0, _index2.OrdinaryHasProperty)(this.$Realm, this, P); } // ECMA262 9.1.8 $Get(P, Receiver) { let prop = this.unknownProperty; if (prop !== undefined && prop.descriptor !== undefined && this.$GetOwnProperty(P) === undefined) { let desc = prop.descriptor; (0, _invariant2.default)(desc !== undefined); let val = desc.value; (0, _invariant2.default)(val instanceof _index.AbstractValue); let propName; if (P instanceof _index.StringValue) { propName = P; } else if (typeof P === "string") { propName = new _index.StringValue(this.$Realm, P); } else { _index.AbstractValue.reportIntrospectionError(val, "abstract computed property name"); throw new _errors.FatalError(); } return this.specializeJoin(val, propName); } // 1. Return ? OrdinaryGet(O, P, Receiver). return (0, _index2.OrdinaryGet)(this.$Realm, this, P, Receiver); } $GetPartial(P, Receiver) { if (Receiver instanceof _index.AbstractValue && Receiver.getType() === _index.StringValue && P === "length") { return _index.AbstractValue.createFromTemplate(this.$Realm, lengthTemplate, _index.NumberValue, [Receiver], lengthTemplateSrc); } if (!(P instanceof _index.AbstractValue)) return this.$Get(P, Receiver); // We assume that simple objects have no getter/setter properties. if (this !== Receiver || !this.isSimpleObject() || P.mightNotBeString() && P.mightNotBeNumber() && !P.isSimpleObject()) { // if P is an abstract value that we don't know about, but we're in pure scope // then if the object is simple, then we can safely continue without throwing // the introspection error below, since converting P to a string is assumed to // be well behaved in a pure scope if (!(this.$Realm.isInPureScope() && this.isSimpleObject() && this === Receiver)) { _index.AbstractValue.reportIntrospectionError(P, "TODO: #1021"); throw new _errors.FatalError(); } } // If all else fails, use this expression let result; if (this.isPartialObject()) { if (isWidenedValue(P)) { return _index.AbstractValue.createTemporalFromBuildFunction(this.$Realm, _index.Value, [this, P], ([o, p]) => t.memberExpression(o, p, true)); } result = _index.AbstractValue.createFromType(this.$Realm, _index.Value, "sentinel member expression"); result.args = [this, P]; } else { result = this.$Realm.intrinsics.undefined; } // Get a specialization of the join of all values written to the object // with abstract property names. let prop = this.unknownProperty; if (prop !== undefined) { let desc = prop.descriptor; if (desc !== undefined) { let val = desc.value; (0, _invariant2.default)(val instanceof _index.AbstractValue); result = this.specializeJoin(val, P); } } // Join in all of the other values that were written to the object with // concrete property names. for (let [key, propertyBinding] of this.properties) { let desc = propertyBinding.descriptor; if (desc === undefined) continue; // deleted (0, _invariant2.default)(desc.value !== undefined); // otherwise this is not simple let val = desc.value; (0, _invariant2.default)(val instanceof _index.Value); let cond = _index.AbstractValue.createFromBinaryOp(this.$Realm, "===", P, new _index.StringValue(this.$Realm, key), undefined, "check for known property"); result = _singletons.Join.joinValuesAsConditional(this.$Realm, cond, val, result); } return result; } specializeJoin(absVal, propName) { if (absVal.kind === "widened property") { let ob = absVal.args[0]; if (propName instanceof _index.StringValue) { let pName = propName.value; let pNumber = +pName; if (pName === pNumber + "") propName = new _index.NumberValue(this.$Realm, pNumber); } return _index.AbstractValue.createTemporalFromBuildFunction(this.$Realm, absVal.getType(), [ob, propName], ([o, p]) => { return t.memberExpression(o, p, true); }); } (0, _invariant2.default)(absVal.args.length === 3 && absVal.kind === "conditional"); let generic_cond = absVal.args[0]; (0, _invariant2.default)(generic_cond instanceof _index.AbstractValue); let cond = this.specializeCond(generic_cond, propName); let arg1 = absVal.args[1]; if (arg1 instanceof _index.AbstractValue && arg1.args.length === 3) arg1 = this.specializeJoin(arg1, propName); let arg2 = absVal.args[2]; if (arg2 instanceof _index.AbstractValue && arg2.args.length === 3) arg2 = this.specializeJoin(arg2, propName); return _index.AbstractValue.createFromConditionalOp(this.$Realm, cond, arg1, arg2, absVal.expressionLocation); } specializeCond(absVal, propName) { if (absVal.kind === "template for property name condition") return _index.AbstractValue.createFromBinaryOp(this.$Realm, "===", absVal.args[0], propName); return absVal; } // ECMA262 9.1.9 $Set(P, V, Receiver) { // 1. Return ? OrdinarySet(O, P, V, Receiver). return _singletons.Properties.OrdinarySet(this.$Realm, this, P, V, Receiver); } $SetPartial(P, V, Receiver) { if (!(P instanceof _index.AbstractValue)) return this.$Set(P, V, Receiver); let pIsLoopVar = isWidenedValue(P); let pIsNumeric = _index.Value.isTypeCompatibleWith(P.getType(), _index.NumberValue); function createTemplate(realm, propName) { return _index.AbstractValue.createFromBinaryOp(realm, "===", propName, new _index.StringValue(realm, ""), undefined, "template for property name condition"); } // We assume that simple objects have no getter/setter properties and // that all properties are writable. if (this !== Receiver || !this.isSimpleObject() || P.mightNotBeString() && P.mightNotBeNumber() && !P.isSimpleObject()) { _index.AbstractValue.reportIntrospectionError(P, "TODO #1021"); throw new _errors.FatalError(); } let prop; if (this.unknownProperty === undefined) { prop = { descriptor: undefined, object: this, key: P }; this.unknownProperty = prop; } else { prop = this.unknownProperty; } this.$Realm.recordModifiedProperty(prop); let desc = prop.descriptor; if (desc === undefined) { let newVal = V; if (!(V instanceof _index.UndefinedValue) && !isWidenedValue(P)) { // join V with undefined, using a property name test as the condition let cond = createTemplate(this.$Realm, P); newVal = _singletons.Join.joinValuesAsConditional(this.$Realm, cond, V, this.$Realm.intrinsics.undefined); } prop.descriptor = { writable: true, enumerable: true, configurable: true, value: newVal }; } else { // join V with current value of this.unknownProperty. I.e. weak update. let oldVal = desc.value; (0, _invariant2.default)(oldVal instanceof _index.Value); let newVal = oldVal; if (!(V instanceof _index.UndefinedValue)) { if (isWidenedValue(P)) { newVal = V; // It will be widened later on } else { let cond = createTemplate(this.$Realm, P); newVal = _singletons.Join.joinValuesAsConditional(this.$Realm, cond, V, oldVal); } } desc.value = newVal; } // Since we don't know the name of the property we are writing to, we also need // to perform weak updates of all of the known properties. // First clear out this.unknownProperty so that helper routines know its OK to update the properties let savedUnknownProperty = this.unknownProperty; this.unknownProperty = undefined; for (let [key, propertyBinding] of this.properties) { if (pIsLoopVar && pIsNumeric) { // Delete numeric properties and don't do weak updates on other properties. if (key !== +key + "") continue; this.properties.delete(key); continue; } let oldVal = this.$Realm.intrinsics.empty; if (propertyBinding.descriptor && propertyBinding.descriptor.value) { oldVal = propertyBinding.descriptor.value; (0, _invariant2.default)(oldVal instanceof _index.Value); // otherwise this is not simple } let cond = _index.AbstractValue.createFromBinaryOp(this.$Realm, "===", P, new _index.StringValue(this.$Realm, key)); let newVal = _singletons.Join.joinValuesAsConditional(this.$Realm, cond, V, oldVal); _singletons.Properties.OrdinarySet(this.$Realm, this, key, newVal, Receiver); } this.unknownProperty = savedUnknownProperty; return true; } // ECMA262 9.1.10 $Delete(P) { if (this.unknownProperty !== undefined) { // TODO #946: generate a delete from the object _index.AbstractValue.reportIntrospectionError(this, P); throw new _errors.FatalError(); } // 1. Return ? OrdinaryDelete(O, P). return _singletons.Properties.OrdinaryDelete(this.$Realm, this, P); } // ECMA262 9.1.11 $OwnPropertyKeys() { return (0, _index2.OrdinaryOwnPropertyKeys)(this.$Realm, this); } } exports.default = ObjectValue; ObjectValue.trackedPropertyNames = ["_isPartial", "_isHavoced", "_isSimple", "_isFinal", "_simplicityIsTransitive", "$ArrayIteratorNextIndex", "$DateValue", "$Extensible", "$IteratedList", "$IteratedObject", "$IteratedSet", "$IteratedString", "$Map", "$MapData", "$MapNextIndex", "$Prototype", "$SetData", "$SetNextIndex", "$StringIteratorNextIndex", "$WeakMapData", "$WeakSetData"]; //# sourceMappingURL=ObjectValue.js.map