UNPKG

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