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