UNPKG

prepack

Version:

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

1,070 lines (952 loc) 79.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ResidualHeapSerializer = undefined; var _realm = require("../realm.js"); var _index = require("../methods/index.js"); var _index2 = require("../values/index.js"); var _completions = require("../completions.js"); var _babelTypes = require("babel-types"); var t = _interopRequireWildcard(_babelTypes); var _generator = require("../utils/generator.js"); var _invariant = require("../invariant.js"); var _invariant2 = _interopRequireDefault(_invariant); var _types = require("./types.js"); var _logger = require("../utils/logger.js"); var _modules = require("../utils/modules.js"); var _ResidualHeapInspector = require("./ResidualHeapInspector.js"); var _ResidualFunctions = require("./ResidualFunctions.js"); var _factorify = require("./factorify.js"); var _internalizer = require("../utils/internalizer.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"); 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)) 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 ResidualHeapSerializer { constructor(realm, logger, modules, residualHeapValueIdentifiers, residualHeapInspector, residualValues, residualFunctionInstances, residualClassMethodInstances, residualFunctionInfos, options, referencedDeclaredValues, additionalFunctionValuesAndEffects, additionalFunctionValueInfos, declarativeEnvironmentRecordsBindings, statistics, react, referentializer, generatorParents) { this.realm = realm; this.logger = logger; this.modules = modules; this.residualHeapValueIdentifiers = residualHeapValueIdentifiers; this.statistics = statistics; this.react = react; this.referentializer = referentializer; let realmGenerator = this.realm.generator; (0, _invariant2.default)(realmGenerator); this.generator = realmGenerator; let realmPreludeGenerator = this.realm.preludeGenerator; (0, _invariant2.default)(realmPreludeGenerator); this.preludeGenerator = 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.requireReturns = new Map(); this.serializedValues = new Set(); this._serializedValueWithIdentifiers = new Set(); this.additionalFunctionValueNestedFunctions = new Set(); this.residualReactElementSerializer = new _ResidualReactElementSerializer.ResidualReactElementSerializer(this.realm, this); this.residualFunctions = new _ResidualFunctions.ResidualFunctions(this.realm, this.statistics, options, this.modules, this.requireReturns, { getLocation: value => this.getSerializeObjectIdentifier(value), createLocation: () => { let location = t.identifier(this.initializeConditionNameGenerator.generate()); // TODO: This function may get to create locations to be used in additional functions; is the global prelude the right place? this.prelude.push(t.variableDeclaration("var", [t.variableDeclarator(location)])); return location; } }, this.prelude, this.preludeGenerator.createNameGenerator("__init_"), this.factoryNameGenerator, residualFunctionInfos, residualFunctionInstances, residualClassMethodInstances, additionalFunctionValueInfos, this.additionalFunctionValueNestedFunctions, referentializer); this.emitter = new _Emitter.Emitter(this.residualFunctions, referencedDeclaredValues); this.mainBody = this.emitter.getBody(); this.residualHeapInspector = residualHeapInspector; this.residualValues = residualValues; this.residualFunctionInstances = residualFunctionInstances; this.residualClassMethodInstances = residualClassMethodInstances; this.residualFunctionInfos = residualFunctionInfos; this._options = options; this.referencedDeclaredValues = referencedDeclaredValues; this.activeGeneratorBodies = new Map(); this.additionalFunctionValuesAndEffects = additionalFunctionValuesAndEffects; this.additionalFunctionValueInfos = additionalFunctionValueInfos; this.rewrittenAdditionalFunctions = new Map(); this.declarativeEnvironmentRecordsBindings = declarativeEnvironmentRecordsBindings; this.getGeneratorParent = generatorParents.get.bind(generatorParents); this.additionalFunctionGenerators = new Map(); this.additionalFunctionGeneratorsInverse = new Map(); } // function values nested in additional functions can't delay initializations // TODO: revisit this and fix additional functions to be capable of delaying initializations // 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, _invariant2.default)(propertyBinding); let desc = propertyBinding.descriptor; if (desc === undefined) continue; //deleted this.emitter.emitNowOrAfterWaitingForDependencies(this._getDescriptorValues(desc).concat([symbol, obj]), () => { (0, _invariant2.default)(desc !== undefined); return this._emitProperty(obj, symbol, desc); }); } // inject properties for (let [key, propertyBinding] of properties) { (0, _invariant2.default)(propertyBinding); if (propertyBinding.pathNode !== undefined) continue; // Property is assigned to inside loop let desc = propertyBinding.descriptor; if (desc === undefined) continue; //deleted if (this.residualHeapInspector.canIgnoreProperty(obj, key)) continue; (0, _invariant2.default)(desc !== undefined); this.emitter.emitNowOrAfterWaitingForDependencies(this._getDescriptorValues(desc).concat(obj), () => { (0, _invariant2.default)(desc !== undefined); return this._emitProperty(obj, key, desc, cleanupDummyProperties != null && cleanupDummyProperties.has(key)); }); } // inject properties with computed names if (obj.unknownProperty !== undefined) { let desc = obj.unknownProperty.descriptor; if (desc !== undefined) { let val = desc.value; (0, _invariant2.default)(val instanceof _index2.AbstractValue); this.emitter.emitNowOrAfterWaitingForDependencies(this._getNestedAbstractValues(val, [obj]), () => { (0, _invariant2.default)(val instanceof _index2.AbstractValue); this._emitPropertiesWithComputedNames(obj, val); }); } } // prototype if (!skipPrototype) { this._emitObjectPrototype(obj, objectPrototypeAlreadyEstablished); if (obj instanceof _index2.FunctionValue) this._emitConstructorPrototype(obj); } this.statistics.objects++; this.statistics.objectProperties += obj.properties.size; } _emitObjectPrototype(obj, objectPrototypeAlreadyEstablished) { let kind = obj.getKind(); let proto = obj.$Prototype; if (objectPrototypeAlreadyEstablished) { // Emitting an assertion. This can be removed in the future, or put under a DEBUG flag. this.emitter.emitNowOrAfterWaitingForDependencies([proto, obj], () => { (0, _invariant2.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, _internalizer.protoExpression) : t.callExpression(this.preludeGenerator.memoizeReference("Object.getPrototypeOf"), [uid]); let condition = t.binaryExpression("!==", fetchedPrototype, serializedProto); let throwblock = t.blockStatement([t.throwStatement(t.newExpression(t.identifier("Error"), [t.stringLiteral("unexpected prototype")]))]); this.emitter.emit(t.ifStatement(condition, throwblock)); }); return; } if (proto === this.realm.intrinsics[kind + "Prototype"]) return; this.emitter.emitNowOrAfterWaitingForDependencies([proto, obj], () => { (0, _invariant2.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, _internalizer.protoExpression), serializedProto))); } }); } _emitConstructorPrototype(func) { // If the original prototype object was mutated, // request its serialization here as this might be observable by // residual code. let prototype = _ResidualHeapInspector.ResidualHeapInspector.getPropertyValue(func, "prototype"); if (prototype instanceof _index2.ObjectValue && this.residualValues.has(prototype)) { this.emitter.emitNowOrAfterWaitingForDependencies([func], () => { (0, _invariant2.default)(prototype instanceof _index2.Value); this.serializeValue(prototype); }); } } _getNestedAbstractValues(absVal, values) { if (absVal.kind === "widened property") return values; (0, _invariant2.default)(absVal.args.length === 3); let cond = absVal.args[0]; (0, _invariant2.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._getNestedAbstractValues(W, values);else values.push(W); } else { // conditional assignment values.push(cond); let consequent = absVal.args[1]; (0, _invariant2.default)(consequent instanceof _index2.AbstractValue); let alternate = absVal.args[2]; (0, _invariant2.default)(alternate instanceof _index2.AbstractValue); this._getNestedAbstractValues(consequent, values); this._getNestedAbstractValues(alternate, values); } return values; } _emitPropertiesWithComputedNames(obj, absVal) { if (absVal.kind === "widened property") return; (0, _invariant2.default)(absVal.args.length === 3); let cond = absVal.args[0]; (0, _invariant2.default)(cond instanceof _index2.AbstractValue); if (cond.kind === "template for property name condition") { let P = cond.args[0]; (0, _invariant2.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]; (0, _invariant2.default)(consequent instanceof _index2.AbstractValue); let alternate = absVal.args[2]; (0, _invariant2.default)(alternate 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, /*isChild*/true); let consequentStatement = t.blockStatement(consequentBody.entries); 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, /*isChild*/true); let alternateStatement = t.blockStatement(alternateBody.entries); this.emitter.emit(t.ifStatement(serializedCond, consequentStatement, alternateStatement)); } } // 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 ? this.serializeValue(key) : this.generator.getAsPropertyNameExpression(key); let computed = key instanceof _index2.SymbolValue || !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.joinCondition) { let cond = this.serializeValue(desc.joinCondition); (0, _invariant2.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, _invariant2.default)(false); } if (locationFunction !== undefined && this._canEmbedProperty(val, key, desc)) { let descValue = desc.value; (0, _invariant2.default)(descValue instanceof _index2.Value); (0, _invariant2.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._getPropertyAssignment(locationFunction, () => { (0, _invariant2.default)(descValue instanceof _index2.Value); return this.serializeValue(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 (boolKey in desc) { let b = desc[boolKey]; (0, _invariant2.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, _invariant2.default)(descriptorId !== undefined); for (let descKey of valKeys) { if (descKey in desc) { let descValue = desc[descKey]; (0, _invariant2.default)(descValue instanceof _index2.Value); if (descValue instanceof _index2.UndefinedValue) { this.serializeValue(descValue); continue; } (0, _invariant2.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 ? this.serializeValue(key) : this.generator.getAsPropertyNameExpression(key, /*canBeIdentifier*/false); (0, _invariant2.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, name, instance) { if (!residualFunctionBinding.serializedValue) { let value = residualFunctionBinding.value; (0, _invariant2.default)(value); (0, _invariant2.default)(residualFunctionBinding.declarativeEnvironmentRecord); // Set up binding identity before starting to serialize value. This is needed in case of recursive dependencies. residualFunctionBinding.serializedValue = this.serializeValue(value); if (residualFunctionBinding.modified) this.referentializer.referentializeBinding(residualFunctionBinding, name, instance); if (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, referencingOnlyAdditionalFunction) { 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 === referencingOnlyAdditionalFunction) { let g = this.additionalFunctionGenerators.get(f); (0, _invariant2.default)(g !== undefined); result.add(g); } else { let scopes = this.residualValues.get(f); (0, _invariant2.default)(scopes); for (let scope of scopes) if (scope instanceof _index2.FunctionValue) { activeFunctions.push(scope); } else { (0, _invariant2.default)(scope instanceof _generator.Generator); result.add(scope); } } } return Array.from(result); } // Determine if a value is effectively referenced by a single additional function. isReferencedOnlyByAdditionalFunction(val) { let scopes = this.residualValues.get(val); (0, _invariant2.default)(scopes !== undefined); let additionalFunction; for (let scope of scopes) if (scope instanceof _generator.Generator) { let f; for (let g = scope; g !== undefined && f === undefined; g = this.getGeneratorParent(g)) f = this.additionalFunctionGeneratorsInverse.get(g); if (f === undefined) return undefined; if (additionalFunction !== undefined && additionalFunction !== f) return undefined; additionalFunction = f; } else { (0, _invariant2.default)(scope instanceof _index2.FunctionValue); if (this.additionalFunctionGenerators.has(scope)) { if (additionalFunction !== undefined && additionalFunction !== scope) return undefined; additionalFunction = scope; } else { let f = this.isReferencedOnlyByAdditionalFunction(scope); if (f === undefined) return undefined; if (additionalFunction !== undefined && additionalFunction !== f) return undefined; additionalFunction = f; } } return additionalFunction; } // 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, _invariant2.default)(scopes !== undefined); // All relevant values were visited in at least one scope. (0, _invariant2.default)(scopes.size >= 1); if (trace) this._logScopes(scopes); // 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, _invariant2.default)(scope instanceof _generator.Generator); generators.push(scope); } } let referencingOnlyAdditionalFunction = this.isReferencedOnlyByAdditionalFunction(val); if (generators.length === 0) { // This value is only referenced from residual functions. if (referencingOnlyAdditionalFunction === undefined && this._options.delayInitializations && !this._options.simpleClosures) { // 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, description: "delay_initializer" }; } } if (trace) console.log(` is referenced only by additional function? ${referencingOnlyAdditionalFunction !== undefined ? "yes" : "no"}`); let getBody = s => { if (s === this.generator) { return this.mainBody; } else { return this.activeGeneratorBodies.get(s); } }; // flatten all function values into the scopes that use them generators = this._getReferencingGenerators(generators, functionValues, referencingOnlyAdditionalFunction); if (referencingOnlyAdditionalFunction === undefined) { // Remove all generators rooted in additional functions, // since we know that there's at least one root that's not in an additional function // which requires the value to be emitted outside of the additional function. generators = generators.filter(generator => { for (let g = generator; g !== undefined; g = this.getGeneratorParent(g)) if (this.additionalFunctionGeneratorsInverse.has(g)) return false; return true; }); if (generators.length === 0) { // This means that the value was referenced by multiple additional functions, 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. // TODO: Revisit for nested additional functions return { body: this.mainBody }; } } // 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, this.getGeneratorParent), generators[0]); (0, _invariant2.default)(commonAncestor !== undefined); if (trace) console.log(` common ancestor: ${commonAncestor.getName()}`); let body; while (true) { body = getBody(commonAncestor); if (body !== undefined) break; commonAncestor = this.getGeneratorParent(commonAncestor); (0, _invariant2.default)(commonAncestor !== undefined); } // So we have a (common ancestor) body now. (0, _invariant2.default)(body !== undefined); // 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, { onAbstractValueWithIdentifier: dependency => { if (trace) console.log(` depending on abstract value with identifier ${dependency.intrinsicName || "?"}`); (0, _invariant2.default)(referencingOnlyAdditionalFunction === undefined || this.emitter.emittingToAdditionalFunction()); 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 = this.getGeneratorParent(g)) { let scopeBody = getBody(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, _invariant2.default)(id); name = id.name; } return name; } serializeBinding(binding) { let record = binding.environment; (0, _invariant2.default)(record instanceof _environment.DeclarativeEnvironmentRecord, "only declarative environments has bindings"); let residualFunctionBindings = this.declarativeEnvironmentRecordsBindings.get(record); (0, _invariant2.default)(residualFunctionBindings, "all bindings that create abstract values must have at least one call emitted to the generator so the function environment should have been visited"); let residualBinding = residualFunctionBindings.get(binding.name); (0, _invariant2.default)(residualBinding, "any referenced residual binding should have been visited"); if (!residualBinding.referentialized) { let additionalFunction = residualBinding.referencedOnlyFromAdditionalFunctions; (0, _invariant2.default)(additionalFunction, "residual bindings like this are only caused by leaked bindings in pure functions"); let instance = this.residualFunctionInstances.get(additionalFunction); (0, _invariant2.default)(instance, "any serialized function must exist in the scope"); this.residualFunctions.referentializer.referentializeBinding(residualBinding, binding.name, instance); } (0, _invariant2.default)(residualBinding.serializedValue); return residualBinding.serializedValue; } _declare(emittingToResidualFunction, bindingType, id, init) { if (emittingToResidualFunction) { let declar = t.variableDeclaration(bindingType, [t.variableDeclarator(id)]); this.mainBody.entries.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, _invariant2.default)(!val.refuseSerialization); if (val instanceof _index2.AbstractValue) { if (val.kind === "widened") { this.serializedValues.add(val); let name = val.intrinsicName; (0, _invariant2.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, _invariant2.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 && _ResidualHeapInspector.ResidualHeapInspector.isLeaf(val)) { let res = this._serializeValue(val); (0, _invariant2.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, _invariant2.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, bindingType || "var", id, init); } this.statistics.valueIds++; if (target.usedOnlyByResidualFunctions) this.statistics.delayedValues++; } } else { if (init) { this.residualHeapValueIdentifiers.deleteIdentifier(val); result = init; this.statistics.valuesInlined++; } } this.emitter.endEmitting(val, oldBody); return result; } _serializeValueIntrinsic(val) { let intrinsicName = val.intrinsicName; (0, _invariant2.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, _invariant2.default)(this.emitter.getBody().type === "MainGenerator" || this.emitter.getBody().type === "AdditionalFunction" || this.emitter.getBody().type === "DelayInitializations"); return this.preludeGenerator.memoizeReference(intrinsicName); } } _getDescriptorValues(desc) { if (desc.joinCondition !== undefined) return [desc.joinCondition]; (0, _invariant2.default)(desc.value === undefined || desc.value instanceof _index2.Value); if (desc.value !== undefined) return [desc.value]; (0, _invariant2.default)(desc.get !== undefined); (0, _invariant2.default)(desc.set !== undefined); return [desc.get, desc.set]; } _deleteProperty(location) { (0, _invariant2.default)(location.type === "MemberExpression"); this.emitter.emit(t.expressionStatement(t.unaryExpression("delete", location, true))); } _assignProperty(locationFn, valueFn, mightHaveBeenDeleted, deleteIfMightHaveBeenDeleted = false) { this.emitter.emit(this._getPropertyAssignment(locationFn, valueFn, mightHaveBeenDeleted, deleteIfMightHaveBeenDeleted)); } _getPropertyAssignment(locationFn, valueFn, mightHaveBeenDeleted, deleteIfMightHaveBeenDeleted = false) { let location = locationFn(); let value = valueFn(); let assignment = t.expressionStatement(t.assignmentExpression("=", location, value)); if (mightHaveBeenDeleted) { let condition = t.binaryExpression("!==", value, this.serializeValue(this.realm.intrinsics.empty)); let deletion = null; if (deleteIfMightHaveBeenDeleted) { (0, _invariant2.default)(location.type === "MemberExpression"); deletion = t.expressionStatement(t.unaryExpression("delete", location, true)); } return t.ifStatement(condition, assignment, deletion); } else { return assignment; } } _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. if (descriptor !== undefined && descriptor.value !== undefined && this._canEmbedProperty(array, key, descriptor)) { let elemVal = descriptor.value; (0, _invariant2.default)(elemVal instanceof _index2.Value); let mightHaveBeenDeleted = elemVal.mightHaveBeenDeleted(); let 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.isHavocedObject()) { 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") { this.emitter.emitNowOrAfterWaitingForDependencies([val], () => { this._assignProperty(() => t.memberExpression(this.getSerializeObjectIdentifier(val), t.identifier("length")), () => { return this.serializeValue(lenProperty); }, false /*mightHaveBeenDeleted*/ ); }); } remainingProperties.delete("length"); } } _serializeValueArray(val) { let remainingProperties = new Map(val.properties); const indexPropertyLength = (0, _utils.getSuggestedArrayLiteralLength)(this.realm, val); // Use the serialized index properties as array initialization list. const initProperties = this._serializeArrayIndexProperties(val, indexPropertyLength, remainingProperties); this._serializeArrayLengthIfNeeded(val, indexPropertyLength, remainingProperties); this._emitObjectProperties(val, remainingProperties); return t.arrayExpression(initProperties); } _serializeValueMap(val) { let kind = val.getKind(); let elems = []; let entries; if (kind === "Map") { entries = val.$MapData; } else { (0, _invariant2.default)(kind === "WeakMap"); entries = val.$WeakMapData; } (0, _invariant2.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) 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, _invariant2.default)(key !== undefined); (0, _invariant2.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)]))); }); } 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.memoizeReference(kind), args); } _serializeValueSet(val) { let kind = val.getKind(); let elems = []; let entries; if (kind === "Set") { entries = val.$SetData; } else { (0, _invariant2.default)(kind === "WeakSet"); entries = val.$WeakSetData; } (0, _invariant2.default)(entries !== undefined); let len = entries.length; let setConstructorDoesntTakeArguments = this.realm.isCompatibleWith(this.realm.MOBILE_JSC_VERSION); for (let i = 0; i < len; i++) { let entry = entries[i]; if (entry === undefined) continue; let mightHaveBeenDeleted = entry.mightHaveBeenDeleted(); let delayReason = this.emitter.getReasonToWaitForDependencies(entry) || this.emitter.getReasonToWaitForActiveValue(val, mightHaveBeenDeleted || setConstructorDoesntTakeArguments); if (delayReason) { this.emitter.emitAfterWaiting(delayReason, [entry, val], () => { (0, _invariant2.default)(entry !== undefined); this.emitter.emit(t.expressionStatement(t.callExpression(t.memberExpression(this.residualHeapValueIdentifiers.getIdentifierAndIncrementReferenceCount(val), t.identifier("add")), [this.serializeValue(entry)]))); }); } else { let elem = this.serializeValue(entry); elems.push(elem); } } this._emitObjectProperties(val); let args = elems.length > 0 ? [t.arrayExpression(elems)] : []; return t.newExpression(this.preludeGenerator.memoizeReference(kind), args); } _serializeValueTypedArrayOrDataView(val) { let buf = val.$ViewedArrayBuffer; (0, _invariant2.default)(buf !== undefined); let outlinedArrayBuffer = this.serializeValue(buf, true); this._emitObjectProperties(val); return t.newExpression(this.preludeGenerator.memoizeReference(val.getKind()), [outlinedArrayBuffer]); } _serializeValueArrayBuffer(val) { let elems = []; let len = val.$ArrayBufferByteLength; let db = val.$ArrayBufferData; (0, _invariant2.default)(len !== undefined); (0, _invariant2.default)(db); let allzero = true; for (let i = 0; i < len; i++) { if (db[i] !== 0) { allzero = false; } let elem = t.numericLiteral(db[i]); elems.push(elem); } this._emitObjectProperties(val); if (allzero) { // if they're all zero, just emit the array buffer constructor return t.newExpression(this.preludeGenerator.memoizeReference(val.getKind()), [t.numericLiteral(len)]); } else { // initialize from a byte array otherwise let arrayValue = t.arrayExpression(elems); let consExpr = t.newExpression(this.preludeGenerator.memoizeReference("Uint8Array"), [arrayValue]); // access the Uint8Array.buffer property to extract the created buffer return t.memberExpression(consExpr, t.identifier("buffer")); } } _serializeValueFunction(val) { if (val instanceof _index2.BoundFunctionValue) { this._emitObjectProperties(val); return t.callExpression(t.memberExpression(this.serializeValue(val.$BoundTargetFunction), t.identifier("bind")), [].concat(this.serializeValue(val.$BoundThis), val.$BoundArguments.map((boundArg, i) => this.serializeValue(boundArg)))); } (0, _invariant2.default)(!(val instanceof _index2.NativeFunctionValue), "all native function values should be intrinsics"); (0, _invariant2.default)(val instanceof _index2.ECMAScriptSourceFunctionValue); let instance = this.residualFunctionInstances.get(val); (0, _invariant2.default)(instance !== undefined); let residualBindings = instance.residualFunctionBindings; let inAdditionalFunction = this.isReferencedOnlyByAdditionalFunction(val); if (inAdditionalFunction !== undefined) instance.containingAdditionalFunction = inAdditionalFunction; let delayed = 1; let undelay = () => { if (--delayed === 0) { (0, _invariant2.default)(instance); // hoist if we are in an additionalFunction if (inAdditionalFunction !== undefined && (0, _hoisting.canHoistFunction)(this.realm, val, undefined, new Set())) { instance.insertionPoint = new _types.BodyReference(this.mainBody, this.mainBody.entries.length); instance.containingAdditionalFunction = undefined; } else { instance.insertionPoint = this.emitter.getBodyReference(); } } }; for (let [boundName, residualBinding] of residualBindings) { let referencedValues = []; let serializeBindingFunc; if (!residualBinding.declarativeEnvironmentRecord) { serializeBindingFunc = () => this._serializeGlobalBinding(boundName, residualBinding); } else { serializeBindingFunc = () => { (0, _invariant2.default)(instance !== undefined); return this._serializeDeclarativeEnvironmentRecordBinding(residualBinding, boundName, instance); }; let bindingValue = residualBinding.value; (0, _invariant2.default)(bindingValue !== undefined); referencedValues.push(bindingValue); if (inAdditionalFunction !== undefined) { let bindingAdditionalFunction = this.isReferencedOnlyByAdditionalFunction(bindingValue); if (bindingAdditionalFunction !== undefined) residualBinding.referencedOnlyFromAdditionalFunctions = bindingAdditionalFunction; } } delayed++; this.emitter.emitNowOrAfterWaitingForDependencies(referencedValues, () => { serializeBindingFunc(); undelay(); }); } if (val.$FunctionKind === "classConstructor") { let homeObject = val.$HomeObject; if (homeObject instanceof _index2.ObjectValue && homeObject.$IsClassPrototype) { this._serializeClass(val, homeObject, undelay); return; } } undelay(); this._emitObjectProperties(val); } _serializeClass(classFunc, classPrototype, undelay) { let classMethodInstance = this.residualClassMethodInstances.get(classFunc); (0, _invariant2.default)(classMethodInstance !== undefined); let classProtoId; let hasSerializedClassProtoId = false; let propertiesToSerialize = new Map(); // handle class inheritance if (!(classFunc.$Prototype instanceof _index2.NativeFunctionValue)) { classMethodInstance.classSuperNode = this.serializeValue(classFunc.$Prototype); } let serializeClassPrototypeId = () => { if (!hasSerializedClassProtoId) { let classId = this.getSerializeObjectIdentifier(classFunc); classProtoId = t.identifier(this.intrinsicNameGenerator.generate()); hasSerializedClassProtoId = true; this.emitter.emit(t.variableDeclaration("var", [t.variableDeclarator(classProtoId, t.memberExpression(classId, t.identifier("prototype")))])); } }; let serializeClassMethod = (propertyNameOrSymbol, methodFunc) => { (0, _invariant2.default)(methodFunc instanceof _index2.ECMAScriptSourceFunctionValue); if (methodFunc !== classFunc) { // if the method does not have a $HomeObject, it's not a class method if (methodFunc.$HomeObject !== undefined) { this.serializedValues.add(methodFunc); this._serializeClassMethod(propertyNameOrSymbol, methodFunc); } else { // if the method is not part of the class, we have to assign it to the prototype // we can't serialize via emitting the properties as that will emit all // the prototype and we only want to mutate the prototype here serializeClassPrototypeId(); let methodId = this.serializeValue(methodFunc); let name; if (typeof propertyNameOrSymbol === "string") { name = t.identifier(propertyNameOrSymbol); } else { name = this.serializeValue(propertyNameOrSymbol); } (0, _invariant2.default)(classProtoId !== undefined); this.emitter.emit(t.expressionStatement(t.assignmentExpression("=", t.memberExpression(classProtoId, name), methodId))); } } }; let serializeClassProperty = (propertyNameOrSymbol, propertyValue) => { // we handle the prototype via class syntax if (propertyNameOrSymbol === "prototype") { this.serializedValues.add(propertyValue); } else if (propertyValue instanceof _index2.ECMAScriptSourceFunctionValue && propertyValue.$HomeObject === classFunc) { serializeClassMethod(propertyNameOrSymbol, propertyValue); } else { let prop = classFunc.properties.get(propertyNameOrSymbol); (0, _invariant2.default)(prop); propertiesToSerialize.set(propertyNameOrSymbol, prop); } }; // find the all the properties on the class that we need to serialize for (let [propertyName, method] of classFunc.properties) { if (!this.residualHeapInspector.canIgnoreProperty(classFunc, propertyName) && !_utils.ClassPropertiesToIgnore.has(propertyName) && method.descriptor !== undefined && !(propertyName === "length" && (0, _utils.canIgnoreClassLengthProperty)(classFunc, method.descriptor, this.logger))) { (0, _utils.withDescriptorValue)(propertyName, method.descriptor, serializeClassProperty); } } // pass in the properties and set it so we don't serialize the prototype undelay(); this._emitObjectProperties(classFunc, propertiesToSerialize, undefined, undefined, true); // handle non-symbol properties for (let [propertyName, method] of classPrototype.properties) { (0, _utils.withDescriptorValue)(propertyName, method.descriptor, serializeClassMethod); } // handle symbol properties for