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
JavaScript
"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