prepack
Version:
Execute a JS bundle, serialize global state and side effects to a snapshot that can be quickly restored.
1,190 lines (943 loc) • 96 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.ResidualHeapSerializer = void 0;
var _realm = require("../realm.js");
var _index = require("../methods/index.js");
var _index2 = require("../values/index.js");
var t = _interopRequireWildcard(require("@babel/types"));
var _generator = require("../utils/generator.js");
var _PreludeGenerator = require("../utils/PreludeGenerator.js");
var _NameGenerator = require("../utils/NameGenerator.js");
var _invariant = _interopRequireDefault(require("../invariant.js"));
var _types2 = require("./types.js");
var _statistics = require("./statistics.js");
var _logger = require("../utils/logger.js");
var _modules = require("../utils/modules.js");
var _HeapInspector = require("../utils/HeapInspector.js");
var _ResidualFunctions = require("./ResidualFunctions.js");
var _factorify = require("./factorify.js");
var _babelhelpers = require("../utils/babelhelpers.js");
var _Emitter = require("./Emitter.js");
var _ResidualHeapValueIdentifiers = require("./ResidualHeapValueIdentifiers.js");
var _utils = require("./utils.js");
var _errors = require("../errors.js");
var _hoisting = require("../react/hoisting.js");
var _singletons = require("../singletons.js");
var _ResidualReactElementSerializer = require("./ResidualReactElementSerializer.js");
var _environment = require("../environment.js");
var _GeneratorTree = require("./GeneratorTree.js");
var _ResidualFunctionInstantiator = require("./ResidualFunctionInstantiator.js");
var _utils2 = require("../utils.js");
var _ResidualOperationSerializer = require("./ResidualOperationSerializer.js");
var _descriptors = require("../descriptors.js");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
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)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; return newObj; } }
/**
* 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.
*/
function commentStatement(text) {
let s = t.emptyStatement();
s.leadingComments = [{
type: "BlockComment",
value: text
}];
return s;
}
class CountingSemaphore {
constructor(action, initialCount = 1) {
(0, _invariant.default)(initialCount >= 1);
this.count = initialCount;
this.action = action;
}
acquireOne() {
this.count++;
}
releaseOne() {
(0, _invariant.default)(this.count > 0);
if (--this.count === 0) this.action();
}
}
class ResidualHeapSerializer {
constructor(realm, logger, modules, residualHeapValueIdentifiers, residualHeapInspector, residualHeapInfo, options, additionalFunctionValuesAndEffects, referentializer, generatorTree, residualOptimizedFunctions) {
this.realm = realm;
this.logger = logger;
this.modules = modules;
this.residualHeapValueIdentifiers = residualHeapValueIdentifiers;
this.referentializer = referentializer;
this._residualOptimizedFunctions = residualOptimizedFunctions;
let realmGenerator = this.realm.generator;
(0, _invariant.default)(realmGenerator);
this.generator = realmGenerator;
let realmPreludeGenerator = this.realm.preludeGenerator;
(0, _invariant.default)(realmPreludeGenerator);
this.preludeGenerator = realmPreludeGenerator;
this.residualOperationSerializer = new _ResidualOperationSerializer.ResidualOperationSerializer(realm, realmPreludeGenerator);
this.prelude = [];
this._descriptors = new Map();
this.needsEmptyVar = false;
this.needsAuxiliaryConstructor = false;
this.descriptorNameGenerator = this.preludeGenerator.createNameGenerator("$$");
this.factoryNameGenerator = this.preludeGenerator.createNameGenerator("$_");
this.intrinsicNameGenerator = this.preludeGenerator.createNameGenerator("$i_");
this.functionNameGenerator = this.preludeGenerator.createNameGenerator("$f_");
this.initializeConditionNameGenerator = this.preludeGenerator.createNameGenerator("_initialized");
this.initializerNameGenerator = this.preludeGenerator.createNameGenerator("__init_");
this.requireReturns = new Map();
this.serializedValues = new Set();
this._serializedValueWithIdentifiers = new Set();
this.additionalFunctionValueNestedFunctions = new Set();
this.residualReactElementSerializer = new _ResidualReactElementSerializer.ResidualReactElementSerializer(this.realm, this, residualOptimizedFunctions);
this.residualFunctions = new _ResidualFunctions.ResidualFunctions(this.realm, options, this.modules, this.requireReturns, {
getContainingAdditionalFunction: functionValue => {
let instance = this.residualFunctionInstances.get(functionValue);
(0, _invariant.default)(instance !== undefined);
return instance.containingAdditionalFunction;
},
getLocation: value => this.getSerializeObjectIdentifier(value),
createLocation: containingAdditionalFunction => {
let location = t.identifier(this.initializeConditionNameGenerator.generate());
let declar = t.variableDeclaration("var", [t.variableDeclarator(location)]);
this.getPrelude(containingAdditionalFunction).push(declar);
return location;
},
createFunction: (containingAdditionalFunction, statements) => {
let id = t.identifier(this.initializerNameGenerator.generate());
this.getPrelude(containingAdditionalFunction).push(t.functionDeclaration(id, [], t.blockStatement(statements)));
return id;
}
}, this.prelude, this.factoryNameGenerator, residualHeapInfo.functionInfos, residualHeapInfo.functionInstances, residualHeapInfo.classMethodInstances, residualHeapInfo.additionalFunctionValueInfos, this.additionalFunctionValueNestedFunctions, referentializer);
this.emitter = new _Emitter.Emitter(this.residualFunctions, residualHeapInfo.referencedDeclaredValues, residualHeapInfo.conditionalFeasibility, this.realm.derivedIds);
this.mainBody = this.emitter.getBody();
this.residualHeapInspector = residualHeapInspector;
this.residualValues = residualHeapInfo.values;
this.residualFunctionInstances = residualHeapInfo.functionInstances;
this.residualClassMethodInstances = residualHeapInfo.classMethodInstances;
this.residualFunctionInfos = residualHeapInfo.functionInfos;
this._options = options;
this.referencedDeclaredValues = residualHeapInfo.referencedDeclaredValues;
this.activeGeneratorBodies = new Map();
this.additionalFunctionValuesAndEffects = additionalFunctionValuesAndEffects;
this.additionalFunctionValueInfos = residualHeapInfo.additionalFunctionValueInfos;
this.rewrittenAdditionalFunctions = new Map();
this.declarativeEnvironmentRecordsBindings = residualHeapInfo.declarativeEnvironmentRecordsBindings;
this.globalBindings = residualHeapInfo.globalBindings;
this.generatorTree = generatorTree;
this.conditionalFeasibility = residualHeapInfo.conditionalFeasibility;
this.additionalFunctionGenerators = new Map();
this.declaredGlobalLets = new Map();
this._objectSemaphores = new Map();
this.additionalGeneratorRoots = residualHeapInfo.additionalGeneratorRoots;
let environment = realm.$GlobalEnv.environmentRecord;
(0, _invariant.default)(environment instanceof _environment.GlobalEnvironmentRecord);
this.globalEnvironmentRecord = environment;
}
getStatistics() {
(0, _invariant.default)(this.realm.statistics instanceof _statistics.SerializerStatistics, "serialization requires SerializerStatistics");
return this.realm.statistics;
}
_acquireOneObjectSemaphore(object) {
let semaphore = this._objectSemaphores.get(object);
if (semaphore !== undefined) semaphore.acquireOne();
return semaphore;
} // Configures all mutable aspects of an object, in particular:
// symbols, properties, prototype.
// For every created object that corresponds to a value,
// this function should be invoked once.
// Thus, as a side effect, we gather statistics here on all emitted objects.
_emitObjectProperties(obj, properties = obj.properties, objectPrototypeAlreadyEstablished = false, cleanupDummyProperties, skipPrototype = false) {
//inject symbols
for (let [symbol, propertyBinding] of obj.symbols) {
(0, _invariant.default)(propertyBinding);
let desc = propertyBinding.descriptor;
if (desc === undefined) continue; //deleted
let semaphore = this._acquireOneObjectSemaphore(obj);
this.emitter.emitNowOrAfterWaitingForDependencies(this._getDescriptorValues(desc).concat([symbol, obj]), () => {
(0, _invariant.default)(desc !== undefined);
this._emitProperty(obj, symbol, desc);
if (semaphore !== undefined) semaphore.releaseOne();
}, this.emitter.getBody());
} // TODO #2259: Make deduplication in the face of leaking work for custom accessors
let isCertainlyLeaked = !obj.mightNotBeLeakedObject();
let shouldDropAsAssignedProp = descriptor => isCertainlyLeaked && descriptor instanceof _descriptors.PropertyDescriptor && descriptor.get === undefined && descriptor.set === undefined; // inject properties
for (let [key, propertyBinding] of properties) {
(0, _invariant.default)(propertyBinding);
if (propertyBinding.pathNode !== undefined) continue; // Property is assigned to inside loop
let desc = propertyBinding.descriptor;
if (shouldDropAsAssignedProp(desc)) continue;
if (desc === undefined) continue; //deleted
if (this.residualHeapInspector.canIgnoreProperty(obj, key)) continue;
(0, _invariant.default)(desc !== undefined);
let semaphore = this._acquireOneObjectSemaphore(obj);
let body = this.emitter.getBody();
this.emitter.emitNowOrAfterWaitingForDependencies(this._getDescriptorValues(desc).concat(obj), () => {
(0, _invariant.default)(desc !== undefined);
this._emitProperty(obj, key, desc, cleanupDummyProperties != null && cleanupDummyProperties.has(key));
if (semaphore !== undefined) semaphore.releaseOne();
}, body);
} // inject properties with computed names
if (obj.unknownProperty !== undefined) {
let desc = obj.unknownProperty.descriptor;
if (desc !== undefined) {
let semaphore = this._acquireOneObjectSemaphore(obj);
this.emitter.emitNowOrAfterWaitingForDependencies(this._getNestedValuesFromAbstractDescriptor(desc, [obj]), () => {
this._emitPropertiesWithComputedNamesDescriptor(obj, desc);
if (semaphore !== undefined) semaphore.releaseOne();
}, this.emitter.getBody());
}
} // prototype
if (!skipPrototype) {
this._emitObjectPrototype(obj, objectPrototypeAlreadyEstablished);
if (obj instanceof _index2.FunctionValue) this._emitConstructorPrototype(obj);
}
this.getStatistics().objects++;
this.getStatistics().objectProperties += obj.properties.size;
}
_emitObjectPrototype(obj, objectPrototypeAlreadyEstablished) {
let kind = obj.getKind();
let proto = obj.$Prototype;
if (objectPrototypeAlreadyEstablished) {
if (this.realm.invariantLevel >= 3) {
this.emitter.emitNowOrAfterWaitingForDependencies([proto, obj], () => {
(0, _invariant.default)(proto);
let serializedProto = this.serializeValue(proto);
let uid = this.getSerializeObjectIdentifier(obj);
const fetchedPrototype = this.realm.isCompatibleWith(this.realm.MOBILE_JSC_VERSION) || this.realm.isCompatibleWith("mobile") ? t.memberExpression(uid, _babelhelpers.protoExpression) : t.callExpression(this.preludeGenerator.memoizeReference("Object.getPrototypeOf"), [uid]);
let condition = t.binaryExpression("!==", fetchedPrototype, serializedProto);
let consequent = this.residualOperationSerializer.getErrorStatement(t.stringLiteral("unexpected prototype"));
this.emitter.emit(t.ifStatement(condition, consequent));
}, this.emitter.getBody());
}
return;
}
if (proto === this.realm.intrinsics[kind + "Prototype"]) return;
let semaphore = this._acquireOneObjectSemaphore(obj);
this.emitter.emitNowOrAfterWaitingForDependencies([proto, obj], () => {
(0, _invariant.default)(proto);
let serializedProto = this.serializeValue(proto);
let uid = this.getSerializeObjectIdentifier(obj);
if (!this.realm.isCompatibleWith(this.realm.MOBILE_JSC_VERSION) && !this.realm.isCompatibleWith("mobile")) this.emitter.emit(t.expressionStatement(t.callExpression(this.preludeGenerator.memoizeReference("Object.setPrototypeOf"), [uid, serializedProto])));else {
this.emitter.emit(t.expressionStatement(t.assignmentExpression("=", t.memberExpression(uid, _babelhelpers.protoExpression), serializedProto)));
}
if (semaphore !== undefined) semaphore.releaseOne();
}, this.emitter.getBody());
}
_emitConstructorPrototype(func) {
// If the original prototype object was mutated,
// request its serialization here as this might be observable by
// residual code.
let prototype = _HeapInspector.HeapInspector.getPropertyValue(func, "prototype");
if (prototype instanceof _index2.ObjectValue && this.residualValues.has(prototype)) {
this.emitter.emitNowOrAfterWaitingForDependencies([prototype], () => {
(0, _invariant.default)(prototype instanceof _index2.Value);
this.serializeValue(prototype);
}, this.emitter.getBody());
}
}
_getNestedValuesFromAbstractDescriptor(desc, values) {
if (desc === undefined) return values;
if (desc instanceof _descriptors.PropertyDescriptor) {
let val = desc.value;
(0, _invariant.default)(val instanceof _index2.AbstractValue);
return this._getNestedValuesFromAbstract(val, values);
} else if (desc instanceof _descriptors.AbstractJoinedDescriptor) {
values.push(desc.joinCondition);
this._getNestedValuesFromAbstractDescriptor(desc.descriptor1, values);
this._getNestedValuesFromAbstractDescriptor(desc.descriptor2, values);
return values;
} else {
(0, _invariant.default)(false, "unknown descriptor");
}
}
_getNestedValuesFromAbstract(absVal, values) {
if (absVal.kind === "widened property") return values;
if (absVal.kind === "template for prototype member expression") return values;
(0, _invariant.default)(absVal.args.length === 3);
let cond = absVal.args[0];
(0, _invariant.default)(cond instanceof _index2.AbstractValue);
if (cond.kind === "template for property name condition") {
let P = cond.args[0];
values.push(P);
let V = absVal.args[1];
values.push(V);
let W = absVal.args[2];
if (W instanceof _index2.AbstractValue) this._getNestedValuesFromAbstract(W, values);else values.push(W);
} else {
// conditional assignment
values.push(cond);
let consequent = absVal.args[1];
if (consequent instanceof _index2.AbstractValue) {
this._getNestedValuesFromAbstract(consequent, values);
} else {
values.push(consequent);
}
let alternate = absVal.args[2];
if (alternate instanceof _index2.AbstractValue) {
this._getNestedValuesFromAbstract(alternate, values);
} else {
values.push(alternate);
}
}
return values;
}
_emitPropertiesWithComputedNamesDescriptor(obj, desc) {
if (desc === undefined) return;
if (desc instanceof _descriptors.PropertyDescriptor) {
let val = desc.value;
(0, _invariant.default)(val instanceof _index2.AbstractValue);
this._emitPropertiesWithComputedNames(obj, val);
} else if (desc instanceof _descriptors.AbstractJoinedDescriptor) {
let serializedCond = this.serializeValue(desc.joinCondition);
let valuesToProcess = new Set();
let consequentStatement;
let alternateStatement;
if (desc.descriptor1) {
let oldBody = this.emitter.beginEmitting("consequent", {
type: "ConditionalAssignmentBranch",
parentBody: undefined,
entries: [],
done: false
},
/*isChild*/
true);
this._emitPropertiesWithComputedNamesDescriptor(obj, desc.descriptor1);
let consequentBody = this.emitter.endEmitting("consequent", oldBody, valuesToProcess,
/*isChild*/
true);
consequentStatement = t.blockStatement(consequentBody.entries);
}
if (desc.descriptor2) {
let oldBody = this.emitter.beginEmitting("alternate", {
type: "ConditionalAssignmentBranch",
parentBody: undefined,
entries: [],
done: false
},
/*isChild*/
true);
this._emitPropertiesWithComputedNamesDescriptor(obj, desc.descriptor2);
let alternateBody = this.emitter.endEmitting("alternate", oldBody, valuesToProcess,
/*isChild*/
true);
alternateStatement = t.blockStatement(alternateBody.entries);
}
if (consequentStatement) {
this.emitter.emit(t.ifStatement(serializedCond, consequentStatement, alternateStatement));
} else if (alternateStatement) {
this.emitter.emit(t.ifStatement(t.unaryExpression("!", serializedCond), alternateStatement));
}
this.emitter.processValues(valuesToProcess);
} else {
(0, _invariant.default)(false, "unknown descriptor");
}
}
_emitPropertiesWithComputedNames(obj, absVal) {
if (absVal.kind === "widened property") return;
if (absVal.kind === "template for prototype member expression") return;
(0, _invariant.default)(absVal.args.length === 3);
let cond = absVal.args[0];
(0, _invariant.default)(cond instanceof _index2.AbstractValue);
if (cond.kind === "template for property name condition") {
let P = cond.args[0];
(0, _invariant.default)(P instanceof _index2.AbstractValue);
let V = absVal.args[1];
let earlier_props = absVal.args[2];
if (earlier_props instanceof _index2.AbstractValue) this._emitPropertiesWithComputedNames(obj, earlier_props);
let uid = this.getSerializeObjectIdentifier(obj);
let serializedP = this.serializeValue(P);
let serializedV = this.serializeValue(V);
this.emitter.emit(t.expressionStatement(t.assignmentExpression("=", t.memberExpression(uid, serializedP, true), serializedV)));
} else {
// conditional assignment
let serializedCond = this.serializeValue(cond);
let consequent = absVal.args[1];
let valuesToProcess = new Set();
let consequentStatement;
let alternateStatement;
if (consequent instanceof _index2.AbstractValue) {
let oldBody = this.emitter.beginEmitting("consequent", {
type: "ConditionalAssignmentBranch",
parentBody: undefined,
entries: [],
done: false
},
/*isChild*/
true);
this._emitPropertiesWithComputedNames(obj, consequent);
let consequentBody = this.emitter.endEmitting("consequent", oldBody, valuesToProcess,
/*isChild*/
true);
consequentStatement = t.blockStatement(consequentBody.entries);
}
let alternate = absVal.args[2];
if (alternate instanceof _index2.AbstractValue) {
let oldBody = this.emitter.beginEmitting("alternate", {
type: "ConditionalAssignmentBranch",
parentBody: undefined,
entries: [],
done: false
},
/*isChild*/
true);
this._emitPropertiesWithComputedNames(obj, alternate);
let alternateBody = this.emitter.endEmitting("alternate", oldBody, valuesToProcess,
/*isChild*/
true);
alternateStatement = t.blockStatement(alternateBody.entries);
}
if (consequentStatement) {
this.emitter.emit(t.ifStatement(serializedCond, consequentStatement, alternateStatement));
} else if (alternateStatement) {
this.emitter.emit(t.ifStatement(t.unaryExpression("!", serializedCond), alternateStatement));
}
this.emitter.processValues(valuesToProcess);
}
} // Overridable.
getSerializeObjectIdentifier(val) {
return this.residualHeapValueIdentifiers.getIdentifierAndIncrementReferenceCount(val);
}
_emitProperty(val, key, desc, deleteIfMightHaveBeenDeleted = false) {
// Location for the property to be assigned to
let locationFunction = () => {
let serializedKey = key instanceof _index2.SymbolValue || key instanceof _index2.AbstractValue ? this.serializeValue(key) : (0, _babelhelpers.getAsPropertyNameExpression)(key);
let computed = key instanceof _index2.SymbolValue || key instanceof _index2.AbstractValue || !t.isIdentifier(serializedKey);
return t.memberExpression(this.getSerializeObjectIdentifier(val), serializedKey, computed);
};
if (desc === undefined) {
this._deleteProperty(locationFunction());
} else {
this.emitter.emit(this.emitDefinePropertyBody(deleteIfMightHaveBeenDeleted, locationFunction, val, key, desc));
}
}
emitDefinePropertyBody(deleteIfMightHaveBeenDeleted, locationFunction, val, key, desc) {
if (desc instanceof _descriptors.AbstractJoinedDescriptor) {
let cond = this.serializeValue(desc.joinCondition);
(0, _invariant.default)(cond !== undefined);
let trueBody;
let falseBody;
if (desc.descriptor1) trueBody = this.emitDefinePropertyBody(deleteIfMightHaveBeenDeleted, locationFunction, val, key, desc.descriptor1);
if (desc.descriptor2) falseBody = this.emitDefinePropertyBody(deleteIfMightHaveBeenDeleted, locationFunction, val, key, desc.descriptor2);
if (trueBody && falseBody) return t.ifStatement(cond, trueBody, falseBody);
if (trueBody) return t.ifStatement(cond, trueBody);
if (falseBody) return t.ifStatement(t.unaryExpression("!", cond), falseBody);
(0, _invariant.default)(false);
}
(0, _invariant.default)(desc instanceof _descriptors.PropertyDescriptor);
if (locationFunction !== undefined && this._canEmbedProperty(val, key, desc)) {
let descValue = desc.value;
(0, _invariant.default)(descValue instanceof _index2.Value);
(0, _invariant.default)(!this.emitter.getReasonToWaitForDependencies([descValue, val]), "precondition of _emitProperty");
let mightHaveBeenDeleted = descValue.mightHaveBeenDeleted(); // The only case we do not need to remove the dummy property is array index property.
return this._getPropertyAssignmentStatement(locationFunction(), descValue, mightHaveBeenDeleted, deleteIfMightHaveBeenDeleted);
}
let body = [];
let descProps = [];
let boolKeys = ["enumerable", "configurable"];
let valKeys = [];
if (!desc.get && !desc.set) {
boolKeys.push("writable");
valKeys.push("value");
} else {
valKeys.push("set", "get");
}
let descriptorsKey = [];
for (let boolKey of boolKeys) {
if (desc[boolKey] !== undefined) {
let b = desc[boolKey];
(0, _invariant.default)(b !== undefined);
descProps.push(t.objectProperty(t.identifier(boolKey), t.booleanLiteral(b)));
descriptorsKey.push(`${boolKey}:${b.toString()}`);
}
}
descriptorsKey = descriptorsKey.join(",");
let descriptorId = this._descriptors.get(descriptorsKey);
if (descriptorId === undefined) {
descriptorId = t.identifier(this.descriptorNameGenerator.generate(descriptorsKey));
let declar = t.variableDeclaration("var", [t.variableDeclarator(descriptorId, t.objectExpression(descProps))]); // The descriptors are used across all scopes, and thus must be declared in the prelude.
this.prelude.push(declar);
this._descriptors.set(descriptorsKey, descriptorId);
}
(0, _invariant.default)(descriptorId !== undefined);
for (let descKey of valKeys) {
if (desc[descKey] !== undefined) {
let descValue = desc[descKey];
(0, _invariant.default)(descValue instanceof _index2.Value);
if (descValue instanceof _index2.UndefinedValue) {
this.serializeValue(descValue);
continue;
}
(0, _invariant.default)(!this.emitter.getReasonToWaitForDependencies([descValue]), "precondition of _emitProperty");
body.push(t.assignmentExpression("=", t.memberExpression(descriptorId, t.identifier(descKey)), this.serializeValue(descValue)));
}
}
let serializedKey = key instanceof _index2.SymbolValue || key instanceof _index2.AbstractValue ? this.serializeValue(key) : (0, _babelhelpers.getAsPropertyNameExpression)(key,
/*canBeIdentifier*/
false);
(0, _invariant.default)(!this.emitter.getReasonToWaitForDependencies([val]), "precondition of _emitProperty");
body.push(t.callExpression(this.preludeGenerator.memoizeReference("Object.defineProperty"), [this.getSerializeObjectIdentifier(val), serializedKey, descriptorId]));
return t.expressionStatement(t.sequenceExpression(body));
}
_serializeDeclarativeEnvironmentRecordBinding(residualFunctionBinding) {
if (!residualFunctionBinding.serializedValue) {
let value = residualFunctionBinding.value;
(0, _invariant.default)(residualFunctionBinding.declarativeEnvironmentRecord);
if (residualFunctionBinding.hasLeaked) {
this.referentializer.referentializeLeakedBinding(residualFunctionBinding);
} else {
residualFunctionBinding.serializedValue = value !== undefined ? this.serializeValue(value) : _babelhelpers.voidExpression;
if (residualFunctionBinding.modified) {
this.referentializer.referentializeModifiedBinding(residualFunctionBinding);
}
}
if (value !== undefined && value.mightBeObject()) {
// Increment ref count one more time to ensure that this object will be assigned a unique id.
// This ensures that only once instance is created across all possible residual function invocations.
this.residualHeapValueIdentifiers.incrementReferenceCount(value);
}
}
} // Augments an initial set of generators with all generators from
// which any of a given set of function values is referenced.
_getReferencingGenerators(initialGenerators, functionValues, referencingOnlyOptimizedFunction) {
let result = new Set(initialGenerators);
let activeFunctions = functionValues.slice();
let visitedFunctions = new Set();
while (activeFunctions.length > 0) {
let f = activeFunctions.pop();
if (visitedFunctions.has(f)) continue;
visitedFunctions.add(f);
if (f === referencingOnlyOptimizedFunction) {
let g = this.additionalFunctionGenerators.get(f);
(0, _invariant.default)(g !== undefined);
result.add(g);
} else {
let scopes = this.residualValues.get(f);
(0, _invariant.default)(scopes);
for (let scope of scopes) if (scope instanceof _index2.FunctionValue) {
activeFunctions.push(scope);
} else {
(0, _invariant.default)(scope instanceof _generator.Generator);
result.add(scope);
}
}
}
return Array.from(result);
}
_getActiveBodyOfGenerator(generator) {
return generator === this.generator ? this.mainBody : this.activeGeneratorBodies.get(generator);
} // Determine whether initialization code for a value should go into the main body, or a more specific initialization body.
_getTarget(val, trace) {
let scopes = this.residualValues.get(val);
(0, _invariant.default)(scopes !== undefined, "value must have been visited"); // All relevant values were visited in at least one scope.
(0, _invariant.default)(scopes.size >= 1);
if (trace) this._logScopes(scopes); // If a value is used in more than one scope, prevent inlining as it might be an additional root with a particular creation scope
if (scopes.size > 1) this.residualHeapValueIdentifiers.incrementReferenceCount(val); // First, let's figure out from which function and generator scopes this value is referenced.
let functionValues = [];
let generators = [];
for (let scope of scopes) {
if (scope instanceof _index2.FunctionValue) {
functionValues.push(scope);
} else {
(0, _invariant.default)(scope instanceof _generator.Generator, "scope must be either function value or generator");
generators.push(scope);
}
}
let optimizedFunctionRoot = this._residualOptimizedFunctions.tryGetOptimizedFunctionRoot(val);
if (generators.length === 0) {
// This value is only referenced from residual functions.
if (this._options.delayInitializations && (optimizedFunctionRoot === undefined || !functionValues.includes(optimizedFunctionRoot))) {
// We can delay the initialization, and move it into a conditional code block in the residual functions!
let body = this.residualFunctions.residualFunctionInitializers.registerValueOnlyReferencedByResidualFunctions(functionValues, val);
return {
body,
usedOnlyByResidualFunctions: true,
optimizedFunctionRoot,
description: "delay_initializer"
};
}
}
if (trace) console.log(` has optimized function root? ${optimizedFunctionRoot !== undefined ? "yes" : "no"}`); // flatten all function values into the scopes that use them
generators = this._getReferencingGenerators(generators, functionValues, optimizedFunctionRoot);
if (optimizedFunctionRoot === undefined) {
// Remove all generators rooted in optimized functions,
// since we know that there's at least one root that's not in an optimized function
// which requires the value to be emitted outside of the optimized function.
generators = generators.filter(generator => {
let s = generator;
while (s instanceof _generator.Generator) {
s = this.generatorTree.getParent(s);
}
return s === "GLOBAL";
});
if (generators.length === 0) {
// This means that the value was referenced by multiple optimized functions (but not by global code itself),
// and thus it must have existed at the end of global code execution.
// TODO: Emit to the end, not somewhere in the middle of the mainBody.
if (trace) console.log(` no filtered generators`); // TODO #2426: Revisit for nested optimized functions
return {
body: this.mainBody
};
}
}
const getGeneratorParent = g => {
let s = this.generatorTree.getParent(g);
return s instanceof _generator.Generator ? s : undefined;
}; // This value is referenced from more than one generator.
// Let's find the body associated with their common ancestor.
let commonAncestor = Array.from(generators).reduce((x, y) => (0, _utils.commonAncestorOf)(x, y, getGeneratorParent), generators[0]); // In the case where we have no common ancestor but we have an optimized function reference,
// we can attempt to use the generator of the single optimized function
if (commonAncestor === undefined && optimizedFunctionRoot !== undefined) {
commonAncestor = this.additionalFunctionGenerators.get(optimizedFunctionRoot);
}
(0, _invariant.default)(commonAncestor !== undefined, "there must always be a common generator ancestor");
if (trace) console.log(` common ancestor: ${commonAncestor.getName()}`);
let body;
while (true) {
body = this._getActiveBodyOfGenerator(commonAncestor);
if (body !== undefined) break;
commonAncestor = getGeneratorParent(commonAncestor);
(0, _invariant.default)(commonAncestor !== undefined, "there must always be an active body for the common generator ancestor");
} // So we have a (common ancestor) body now.
(0, _invariant.default)(body !== undefined, "there must always be an active body"); // However, there's a potential problem: That body might belong to a generator
// which has nested generators that are currently being processed (they are not "done" yet).
// This becomes a problem when the value for which we are trying to determine the target body
// depends on other values which are only declared in such not-yet-done nested generator!
// So we find all such not-yet-done bodies here, and pick a most nested one
// which is related to one of the scopes this value is used by.
let notYetDoneBodies = new Set();
this.emitter.dependenciesVisitor(val, {
onIntrinsicDerivedObject: dependency => {
if (trace) {
console.log(` depending on intrinsic derived object and an identifier ${dependency.intrinsicName || "?"}`);
}
(0, _invariant.default)(optimizedFunctionRoot === undefined || !!this.emitter.getActiveOptimizedFunction(), "optimized function inconsistency");
let declarationBody = this.emitter.getDeclarationBody(dependency);
if (declarationBody !== undefined) {
if (trace) console.log(` has declaration body`);
for (let b = declarationBody; b !== undefined; b = b.parentBody) {
if (notYetDoneBodies.has(b)) break;
notYetDoneBodies.add(b);
}
}
},
onAbstractValueWithIdentifier: dependency => {
if (trace) console.log(` depending on abstract value with identifier ${dependency.intrinsicName || "?"}`);
(0, _invariant.default)(optimizedFunctionRoot === undefined || !!this.emitter.getActiveOptimizedFunction(), "optimized function inconsistency");
let declarationBody = this.emitter.getDeclarationBody(dependency);
if (declarationBody !== undefined) {
if (trace) console.log(` has declaration body`);
for (let b = declarationBody; b !== undefined; b = b.parentBody) {
if (notYetDoneBodies.has(b)) break;
notYetDoneBodies.add(b);
}
}
}
});
if (trace) console.log(` got ${notYetDoneBodies.size} not yet done bodies`);
for (let s of generators) for (let g = s; g !== undefined; g = getGeneratorParent(g)) {
let scopeBody = this._getActiveBodyOfGenerator(g);
if (scopeBody !== undefined && (scopeBody.nestingLevel || 0) > (body.nestingLevel || 0) && notYetDoneBodies.has(scopeBody)) {
// TODO: If there are multiple such scopeBody's, why is it okay to pick an arbitrary one?
body = scopeBody;
break;
}
}
return {
body,
commonAncestor
};
}
_getValueDebugName(val) {
let name;
if (val instanceof _index2.FunctionValue) {
name = val.getName();
} else {
const id = this.residualHeapValueIdentifiers.getIdentifier(val);
(0, _invariant.default)(id);
name = id.name;
}
return name;
}
_getResidualFunctionBinding(binding) {
let environment = binding.environment;
if (environment === this.globalEnvironmentRecord.$DeclarativeRecord) environment = this.globalEnvironmentRecord;
if (environment === this.globalEnvironmentRecord) {
return this.globalBindings.get(binding.name);
}
(0, _invariant.default)(environment instanceof _environment.DeclarativeEnvironmentRecord, "only declarative environments have bindings");
let residualFunctionBindings = this.declarativeEnvironmentRecordsBindings.get(environment);
if (residualFunctionBindings === undefined) return undefined;
return residualFunctionBindings.get(binding.name);
}
serializeBinding(binding) {
let residualBinding = this._getResidualFunctionBinding(binding);
(0, _invariant.default)(residualBinding !== undefined, "any referenced residual binding should have been visited");
this._serializeDeclarativeEnvironmentRecordBinding(residualBinding);
let location = residualBinding.serializedUnscopedLocation;
(0, _invariant.default)(location !== undefined);
return location;
}
getPrelude(optimizedFunction) {
if (optimizedFunction !== undefined) {
let body = this.residualFunctions.additionalFunctionPreludes.get(optimizedFunction);
(0, _invariant.default)(body !== undefined);
return body;
} else {
return this.prelude;
}
}
_declare(emittingToResidualFunction, optimizedFunctionRoot, bindingType, id, init) {
if (emittingToResidualFunction) {
let declar = t.variableDeclaration(bindingType, [t.variableDeclarator(id)]);
this.getPrelude(optimizedFunctionRoot).push(declar);
let assignment = t.expressionStatement(t.assignmentExpression("=", id, init));
this.emitter.emit(assignment);
} else {
let declar = t.variableDeclaration(bindingType, [t.variableDeclarator(id, init)]);
this.emitter.emit(declar);
}
}
serializeValue(val, referenceOnly, bindingType) {
(0, _invariant.default)(!(val instanceof _index2.ObjectValue && val.refuseSerialization));
if (val instanceof _index2.AbstractValue) {
if (val.kind === "widened") {
this.serializedValues.add(val);
let name = val.intrinsicName;
(0, _invariant.default)(name !== undefined);
return t.identifier(name);
} else if (val.kind === "widened property") {
this.serializedValues.add(val);
return this._serializeAbstractValueHelper(val);
}
} // make sure we're not serializing a class method here
if (val instanceof _index2.ECMAScriptSourceFunctionValue && this.residualClassMethodInstances.has(val)) {
let classMethodInstance = this.residualClassMethodInstances.get(val);
(0, _invariant.default)(classMethodInstance); // anything other than a class constructor should never go through serializeValue()
// so we need to log a nice error message to the user
if (classMethodInstance.methodType !== "constructor") {
let error = new _errors.CompilerDiagnostic("a class method incorrectly went through the serializeValue() code path", val.$ECMAScriptCode.loc, "PP0022", "FatalError");
this.realm.handleError(error);
throw new _errors.FatalError();
}
}
if (this._serializedValueWithIdentifiers.has(val)) {
return this.getSerializeObjectIdentifier(val);
}
this.serializedValues.add(val);
if (!referenceOnly && _HeapInspector.HeapInspector.isLeaf(val)) {
let res = this._serializeValue(val);
(0, _invariant.default)(res !== undefined);
return res;
}
this._serializedValueWithIdentifiers.add(val);
let target = this._getTarget(val);
let oldBody = this.emitter.beginEmitting(val, target.body);
let init = this._serializeValue(val);
let id = this.residualHeapValueIdentifiers.getIdentifier(val);
if (this._options.debugIdentifiers !== undefined && this._options.debugIdentifiers.includes(id.name)) {
console.log(`Tracing value with identifier ${id.name} (${val.constructor.name}) targetting ${target.body.type}`);
this._getTarget(val, true);
}
let result = id;
this.residualHeapValueIdentifiers.incrementReferenceCount(val);
if (this.residualHeapValueIdentifiers.needsIdentifier(val)) {
if (init) {
if (this._options.debugScopes) {
let scopes = this.residualValues.get(val);
(0, _invariant.default)(scopes !== undefined);
const scopeList = Array.from(scopes).map(s => `"${s.getName()}"`).join(",");
let comment = `${this._getValueDebugName(val)} referenced from scopes [${scopeList}]`;
if (target.commonAncestor !== undefined) comment = `${comment} with common ancestor: ${target.commonAncestor.getName()}`;
if (target.description !== undefined) comment = `${comment} => ${target.description} `;
this.emitter.emit(commentStatement(comment));
}
if (init !== id) {
this._declare(!!target.usedOnlyByResidualFunctions, target.optimizedFunctionRoot, bindingType || "var", id, init);
}
this.getStatistics().valueIds++;
if (target.usedOnlyByResidualFunctions) this.getStatistics().delayedValues++;
}
} else {
if (init) {
this.residualHeapValueIdentifiers.deleteIdentifier(val);
result = init;
this.getStatistics().valuesInlined++;
}
}
this.emitter.endEmitting(val, oldBody);
return result;
}
_serializeValueIntrinsic(val) {
let intrinsicName = val.intrinsicName;
(0, _invariant.default)(intrinsicName);
if (val instanceof _index2.ObjectValue && val.intrinsicNameGenerated) {
// The intrinsic was generated at a particular point in time.
return this.preludeGenerator.convertStringToMember(intrinsicName);
} else {
// The intrinsic conceptually exists ahead of time.
(0, _invariant.default)(this.emitter.getBody().type === "MainGenerator" || this.emitter.getBody().type === "OptimizedFunction" || this.emitter.getBody().type === "DelayInitializations");
return this.preludeGenerator.memoizeReference(intrinsicName);
}
}
_getDescriptorValues(desc) {
if (desc === undefined) {
return [];
} else if (desc instanceof _descriptors.PropertyDescriptor) {
(0, _invariant.default)(desc.value === undefined || desc.value instanceof _index2.Value);
if (desc.value !== undefined) return [desc.value];
(0, _invariant.default)(desc.get !== undefined);
(0, _invariant.default)(desc.set !== undefined);
return [desc.get, desc.set];
} else if (desc instanceof _descriptors.AbstractJoinedDescriptor) {
return [desc.joinCondition, ...this._getDescriptorValues(desc.descriptor1), ...this._getDescriptorValues(desc.descriptor2)];
} else {
(0, _invariant.default)(false, "unknown descriptor");
}
}
_deleteProperty(location) {
(0, _invariant.default)(location.type === "MemberExpression");
this.emitter.emit(t.expressionStatement(t.unaryExpression("delete", location, true)));
}
_assignProperty(location, value, mightHaveBeenDeleted, deleteIfMightHaveBeenDeleted = false) {
this.emitter.emit(this._getPropertyAssignmentStatement(location, value, mightHaveBeenDeleted, deleteIfMightHaveBeenDeleted));
}
_getPropertyAssignmentStatement(location, value, mightHaveBeenDeleted, deleteIfMightHaveBeenDeleted = false) {
if (mightHaveBeenDeleted) {
// We always need to serialize this value in order to keep the invariants happy.
let serializedValue = this.serializeValue(value);
let condition;
if (value instanceof _index2.AbstractValue && value.kind === "conditional") {
let [c, x, y] = value.args;
if (x instanceof _index2.EmptyValue) {
if (c instanceof _index2.AbstractValue && c.kind === "!") condition = this.serializeValue(c.args[0]);else condition = t.unaryExpression("!", this.serializeValue(c));
serializedValue = this.serializeValue(y);
} else if (y instanceof _index2.EmptyValue) {
condition = this.serializeValue(c);
serializedValue = this.serializeValue(x);
}
}
if (condition === undefined) {
condition = t.binaryExpression("!==", this.serializeValue(value), this._serializeEmptyValue());
}
let assignment = t.expressionStatement(t.assignmentExpression("=", location, serializedValue));
let deletion = null;
if (deleteIfMightHaveBeenDeleted) {
(0, _invariant.default)(location.type === "MemberExpression");
deletion = t.expressionStatement(t.unaryExpression("delete", location, true));
}
return t.ifStatement(condition, assignment, deletion);
} else {
return t.expressionStatement(t.assignmentExpression("=", location, this.serializeValue(value)));
}
}
_serializeArrayIndexProperties(array, indexPropertyLength, remainingProperties) {
let elems = [];
for (let i = 0; i < indexPropertyLength; i++) {
let key = i + "";
let propertyBinding = remainingProperties.get(key);
let elem = null; // "propertyBinding === undefined" means array has a hole in the middle.
if (propertyBinding !== undefined) {
let descriptor = propertyBinding.descriptor; // "descriptor === undefined" means this array item has been deleted.
(0, _invariant.default)(descriptor === undefined || descriptor instanceof _descriptors.PropertyDescriptor);
if (descriptor !== undefined && descriptor.value !== undefined && this._canEmbedProperty(array, key, descriptor)) {
let elemVal = descriptor.value;
(0, _invariant.default)(elemVal instanceof _index2.Value);
let mightHaveBeenDeleted = elemVal.mightHaveBeenDeleted();
let instantRenderMode = this.realm.instantRender.enabled;
let delayReason;
/* In Instant Render mode, deleted indices are initialized
to the __empty built-in */
if (instantRenderMode) {
if (this.emitter.getReasonToWaitForDependencies(elemVal)) {
this.realm.instantRenderBailout("InstantRender does not yet support cyclical arrays or objects", array.expressionLocation);
}
delayReason = undefined;
} else {
delayReason = this.emitter.getReasonToWaitForDependencies(elemVal) || this.emitter.getReasonToWaitForActiveValue(array, mightHaveBeenDeleted);
}
if (!delayReason) {
elem = this.serializeValue(elemVal);
remainingProperties.delete(key);
}
}
}
elems.push(elem);
}
return elems;
}
_serializeArrayLengthIfNeeded(val, numberOfIndexProperties, remainingProperties) {
const realm = this.realm;
let lenProperty;
if (val.mightBeLeakedObject()) {
lenProperty = this.realm.evaluateWithoutLeakLogic(() => (0, _index.Get)(realm, val, "length"));
} else {
lenProperty = (0, _index.Get)(realm, val, "length");
} // Need to serialize length property if:
// 1. array length is abstract.
// 2. array length is concrete, but different from number of index properties
// we put into initialization list.
if (lenProperty instanceof _index2.AbstractValue || _singletons.To.ToLength(realm, lenProperty) !== numberOfIndexProperties) {
if (!(lenProperty instanceof _index2.AbstractValue) || lenProperty.kind !== "widened property") {
let semaphore = this._acquireOneObjectSemaphore(val);
this.emitter.emitNowOrAfterWaitingForDependencies([val, lenProperty], () => {
this._assignProperty(t.memberExpression(this.getSerializeObjectIdentifier(val), t.identifier("length")), lenProperty, false
/*mightHaveBeenDeleted*/
);
if (semaphore !== undefined) semaphore.releaseOne();
}, this.emitter.getBody());
}
remainingProperties.delete("length");
}
}
_serializeValueArray(val) {
let remainingProperties = new Map(val.properties);
let [unconditionalLength, assignmentNotNeeded] = (0, _utils.getSuggestedArrayLiteralLength)(this.realm, val); // Use the unconditional serialized index properties as array initialization list.
const initProperties = this._serializeArrayIndexProperties(val, unconditionalLength, remainingProperties);
if (!assignmentNotNeeded) this._serializeArrayLengthIfNeeded(val, unconditionalLength, remainingProperties);
this._emitObjectProperties(val, remainingProperties);
return t.arrayExpression(initProperties);
}
_serializeValueMap(val) {
let kind = val.getKind();
let elems = [];
let entries;
let omitDeadEntries;
if (kind === "Map") {
entries = val.$MapData;
omitDeadEntries = false;
} else {
(0, _invariant.default)(kind === "WeakMap");
entries = val.$WeakMapData;
omitDeadEntries = true;
}
(0, _invariant.default)(entries !== undefined);
let len = entries.length;
let mapConstructorDoesntTakeArguments = this.realm.isCompatibleWith(this.realm.MOBILE_JSC_VERSION);
for (let i = 0; i < len; i++) {
let entry = entries[i];
let key = entry.$Key;
let value = entry.$Value;
if (key === undefined || value === undefined || omitDeadEntries && !this.residualValues.has(key)) continue;
let mightHaveBeenDeleted = key.mightHaveBeenDeleted();
let delayReason = this.emitter.getReasonToWaitForDependencies(key) || this.emitter.getReasonToWaitForDependencies(value) || this.emitter.getReasonToWaitForActiveValue(val, mightHaveBeenDeleted || mapConstructorDoesntTakeArguments);
if (delayReason) {
this.emitter.emitAfterWaiting(delayReason, [key, value, val], () => {
(0, _invariant.default)(key !== undefined);
(0, _invariant.default)(value !== undefined);
this.emitter.emit(t.expressionStatement(t.callExpression(t.memberExpression(this.residualHeapValueIdentifiers.getIdentifierAndIncrementReferenceCount(val), t.identifier("set")), [this.serializeValue(key), this.serializeValue(value)])));
}, this.emitter.getBody());
} else {
let serializedKey = this.serializeValue(key);
let serializedValue = this.serializeValue(value);
let elem = t.arrayExpression([serializedKey, serializedValue]);
elems.push(elem);
}
}
this._emitObjectProperties(val);
let args = elems.length > 0 ? [t.arrayExpression(elems)] : [];
return t.newExpression(this.preludeGenerator.memoizeReferen