prepack
Version:
Execute a JS bundle, serialize global state and side effects to a snapshot that can be quickly restored.
1,304 lines (1,053 loc) • 54 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.ResidualHeapVisitor = void 0;
var _environment = require("../environment.js");
var _errors = require("../errors.js");
var _realm = require("../realm.js");
var _singletons = require("../singletons.js");
var _index = require("../methods/index.js");
var _index2 = require("../values/index.js");
var _Error = require("../intrinsics/ecma262/Error.js");
var t = _interopRequireWildcard(require("@babel/types"));
var _generator = require("../utils/generator.js");
var _traverse = _interopRequireDefault(require("@babel/traverse"));
var _invariant = _interopRequireDefault(require("../invariant.js"));
var _visitors = require("./visitors.js");
var _logger = require("../utils/logger.js");
var _modules = require("../utils/modules.js");
var _HeapInspector = require("../utils/HeapInspector.js");
var _utils = require("./utils.js");
var _utils2 = require("../react/utils.js");
var _ResidualReactElementVisitor = require("./ResidualReactElementVisitor.js");
var _GeneratorTree = require("./GeneratorTree.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.
*/
/* This class visits all values that are reachable in the residual heap.
In particular, this "filters out" values that are:
- captured by a DeclarativeEnvironmentRecord, but not actually used by any closure.
- Unmodified prototype objects
TODO #680: Figure out minimal set of values that need to be kept alive for WeakSet and WeakMap instances.
*/
class ResidualHeapVisitor {
constructor(realm, logger, modules, additionalFunctionValuesAndEffects) {
(0, _invariant.default)(realm.useAbstractInterpretation);
this.realm = realm;
this.logger = logger;
this.modules = modules;
this.declarativeEnvironmentRecordsBindings = new Map();
this.globalBindings = new Map();
this.functionInfos = new Map();
this.classMethodInstances = new Map();
this.functionInstances = new Map();
this.values = new Map();
this.conditionalFeasibility = new Map();
let generator = this.realm.generator;
(0, _invariant.default)(generator);
this.scope = this.globalGenerator = generator;
this.inspector = new _HeapInspector.HeapInspector(realm, logger);
this.referencedDeclaredValues = new Map();
this.delayedActions = [];
this.additionalFunctionValuesAndEffects = additionalFunctionValuesAndEffects;
this.equivalenceSet = new _index.HashSet();
this.additionalFunctionValueInfos = new Map();
this.functionToCapturedScopes = new Map();
let environment = realm.$GlobalEnv.environmentRecord;
(0, _invariant.default)(environment instanceof _environment.GlobalEnvironmentRecord);
this.globalEnvironmentRecord = environment;
this.additionalGeneratorRoots = new Map();
this.residualReactElementVisitor = new _ResidualReactElementVisitor.ResidualReactElementVisitor(this.realm, this);
this.generatorTree = new _GeneratorTree.GeneratorTree();
}
// Going backwards from the current scope, find either the containing
// additional function, or if there isn't one, return the global generator.
_getCommonScope() {
let s = this.scope;
while (true) {
if (s instanceof _generator.Generator) s = this.generatorTree.getParent(s);else if (s instanceof _index2.FunctionValue) {
// Did we find an additional function?
if (this.additionalFunctionValuesAndEffects.has(s)) return s; // Did the function itself get created by a generator we can chase?
s = this.generatorTree.getCreator(s) || "GLOBAL";
} else {
(0, _invariant.default)(s === "GLOBAL");
let generator = this.globalGenerator;
(0, _invariant.default)(generator);
return generator;
}
}
(0, _invariant.default)(false);
} // If the current scope has a containing additional function, retrieve it.
_getAdditionalFunctionOfScope() {
let s = this._getCommonScope();
return s instanceof _index2.FunctionValue ? s : undefined;
} // When a value has been created by some generator that is unrelated
// to the current common scope, visit the value in the scope it was
// created --- this causes the value later to be serialized in its
// creation scope, ensuring that the value has the right creation / life time.
_registerAdditionalRoot(value) {
let creationGenerator = this.generatorTree.getCreator(value) || this.globalGenerator;
let additionalFunction = this._getAdditionalFunctionOfScope() || "GLOBAL";
let targetAdditionalFunction;
if (creationGenerator === this.globalGenerator) {
targetAdditionalFunction = "GLOBAL";
} else {
let s = creationGenerator;
while (s instanceof _generator.Generator) {
s = this.generatorTree.getParent(s);
(0, _invariant.default)(s !== undefined);
}
(0, _invariant.default)(s === "GLOBAL" || s instanceof _index2.FunctionValue);
targetAdditionalFunction = s;
}
let usageScope;
if (additionalFunction === targetAdditionalFunction) {
usageScope = this.scope;
} else {
// Object was created outside of current additional function scope
(0, _invariant.default)(additionalFunction instanceof _index2.FunctionValue);
let additionalFVEffects = this.additionalFunctionValuesAndEffects.get(additionalFunction);
(0, _invariant.default)(additionalFVEffects !== undefined);
additionalFVEffects.additionalRoots.add(value);
this._visitInUnrelatedScope(creationGenerator, value);
usageScope = this.generatorTree.getCreator(value) || this.globalGenerator;
}
usageScope = this.scope;
if (usageScope instanceof _generator.Generator) {
// Also check if object is used in some nested generator scope that involved
// applying effects; if so, store additional information that the serializer
// can use to proactive serialize such objects from within the right generator
let anyRelevantEffects = false;
for (let g = usageScope; g instanceof _generator.Generator; g = this.generatorTree.getParent(g)) {
if (g === creationGenerator) {
if (anyRelevantEffects) {
let s = this.additionalGeneratorRoots.get(g);
if (s === undefined) this.additionalGeneratorRoots.set(g, s = new Set());
if (!s.has(value)) {
s.add(value);
this._visitInUnrelatedScope(g, value);
}
}
break;
}
let effectsToApply = g.effectsToApply;
if (effectsToApply) for (let pb of effectsToApply.modifiedProperties.keys()) if (pb.object === value) {
anyRelevantEffects = true;
break;
}
}
}
} // Careful!
// Only use _withScope when you know that the currently applied effects makes sense for the given (nested) scope!
_withScope(scope, f) {
let oldScope = this.scope;
this.scope = scope;
try {
f();
} finally {
this.scope = oldScope;
}
} // Queues up an action to be later processed in some arbitrary scope.
_enqueueWithUnrelatedScope(scope, action) {
// If we are in a zone with a non-default equivalence set (we are wrapped in a `withCleanEquivalenceSet` call) then
// we need to save our equivalence set so that we may load it before running our action.
if (this.residualReactElementVisitor.defaultEquivalenceSet === false) {
const save = this.residualReactElementVisitor.saveEquivalenceSet();
const originalAction = action;
action = () => this.residualReactElementVisitor.loadEquivalenceSet(save, originalAction);
}
this.delayedActions.push({
scope,
action
});
} // Queues up visiting a value in some arbitrary scope.
_visitInUnrelatedScope(scope, val) {
let scopes = this.values.get(val);
if (scopes !== undefined && scopes.has(scope)) return;
this._enqueueWithUnrelatedScope(scope, () => this.visitValue(val));
}
visitObjectProperty(binding) {
let desc = binding.descriptor;
let obj = binding.object;
(0, _invariant.default)(binding.key !== undefined, "Undefined keys should never make it here.");
if (obj instanceof _index2.AbstractObjectValue || !(typeof binding.key === "string" && this.inspector.canIgnoreProperty(obj, binding.key))) {
if (desc !== undefined) this.visitDescriptor(desc);
}
if (binding.key instanceof _index2.Value) this.visitValue(binding.key);
}
visitObjectProperties(obj, kind) {
// In non-instant render mode, properties of leaked objects are generated via assignments
let {
skipPrototype,
constructor
} = (0, _utils.getObjectPrototypeMetadata)(this.realm, obj);
if (obj.temporalAlias !== undefined) return; // visit properties
for (let [symbol, propertyBinding] of obj.symbols) {
(0, _invariant.default)(propertyBinding);
let desc = propertyBinding.descriptor;
if (desc === undefined) continue; //deleted
this.visitDescriptor(desc);
this.visitValue(symbol);
} // visit properties
for (let [propertyBindingKey, propertyBindingValue] of obj.properties) {
// we don't want to visit these as we handle the serialization ourselves
// via a different logic route for classes
let descriptor = propertyBindingValue.descriptor;
if (obj instanceof _index2.ECMAScriptFunctionValue && obj.$FunctionKind === "classConstructor" && (_utils.ClassPropertiesToIgnore.has(propertyBindingKey) || propertyBindingKey === "length" && (0, _utils.canIgnoreClassLengthProperty)(obj, descriptor, this.logger))) {
continue;
}
if (propertyBindingValue.pathNode !== undefined) continue; // property is written to inside a loop
// Leaked object. Properties are set via assignments
// TODO #2259: Make deduplication in the face of leaking work for custom accessors
if (!(obj instanceof _index2.ArrayValue) && !obj.mightNotBeLeakedObject() && descriptor instanceof _descriptors.PropertyDescriptor && descriptor.get === undefined && descriptor.set === undefined) {
continue;
}
this.visitObjectProperty(propertyBindingValue);
} // inject properties with computed names
if (obj.unknownProperty !== undefined) {
this.visitObjectPropertiesWithComputedNamesDescriptor(obj.unknownProperty.descriptor);
} // prototype
if (!skipPrototype) {
this.visitObjectPrototype(obj);
}
if (obj instanceof _index2.FunctionValue) {
this.visitConstructorPrototype(constructor ? constructor : obj);
} else if (obj instanceof _index2.ObjectValue && skipPrototype && constructor) {
this.visitValue(constructor);
}
}
visitObjectPrototype(obj) {
let proto = obj.$Prototype;
let kind = obj.getKind();
if (proto === this.realm.intrinsics[kind + "Prototype"]) return;
if (!obj.$IsClassPrototype || proto !== this.realm.intrinsics.null) {
this.visitValue(proto);
}
}
visitConstructorPrototype(func) {
// If the original prototype object was mutated,
// request its serialization here as this might be observable by
// residual code.
(0, _invariant.default)(func instanceof _index2.FunctionValue);
let prototype = _HeapInspector.HeapInspector.getPropertyValue(func, "prototype");
if (prototype instanceof _index2.ObjectValue && prototype.originalConstructor === func && !this.inspector.isDefaultPrototype(prototype)) {
this.visitValue(prototype);
}
}
visitObjectPropertiesWithComputedNamesDescriptor(desc) {
if (desc !== undefined) {
if (desc instanceof _descriptors.PropertyDescriptor) {
let val = desc.value;
(0, _invariant.default)(val instanceof _index2.AbstractValue);
this.visitObjectPropertiesWithComputedNames(val);
} else if (desc instanceof _descriptors.AbstractJoinedDescriptor) {
this.visitValue(desc.joinCondition);
this.visitObjectPropertiesWithComputedNamesDescriptor(desc.descriptor1);
this.visitObjectPropertiesWithComputedNamesDescriptor(desc.descriptor2);
} else {
(0, _invariant.default)(false, "unknown descriptor");
}
}
}
visitObjectPropertiesWithComputedNames(absVal) {
if (absVal.kind === "widened property") return;
if (absVal.kind === "template for prototype member expression") return;
if (absVal.kind === "conditional") {
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.visitObjectPropertiesWithComputedNames(earlier_props);
this.visitValue(P);
this.visitValue(V);
} else {
// conditional assignment
absVal.args[0] = this.visitEquivalentValue(cond);
let consequent = absVal.args[1];
if (consequent instanceof _index2.AbstractValue) {
this.visitObjectPropertiesWithComputedNames(consequent);
}
let alternate = absVal.args[2];
if (alternate instanceof _index2.AbstractValue) {
this.visitObjectPropertiesWithComputedNames(alternate);
}
}
} else {
this.visitValue(absVal);
}
}
visitDescriptor(desc) {
if (desc === undefined) {} else if (desc instanceof _descriptors.PropertyDescriptor) {
if (desc.value !== undefined) desc.value = this.visitEquivalentValue(desc.value);
if (desc.get !== undefined) this.visitValue(desc.get);
if (desc.set !== undefined) this.visitValue(desc.set);
} else if (desc instanceof _descriptors.AbstractJoinedDescriptor) {
desc.joinCondition = this.visitEquivalentValue(desc.joinCondition);
if (desc.descriptor1 !== undefined) this.visitDescriptor(desc.descriptor1);
if (desc.descriptor2 !== undefined) this.visitDescriptor(desc.descriptor2);
} else {
(0, _invariant.default)(false, "unknown descriptor");
}
}
visitValueArray(val) {
this._registerAdditionalRoot(val);
this.visitObjectProperties(val);
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");
}
let [initialLength, lengthAssignmentNotNeeded] = (0, _utils.getSuggestedArrayLiteralLength)(realm, val);
if (lengthAssignmentNotNeeded) return;
if (lenProperty instanceof _index2.AbstractValue ? lenProperty.kind !== "widened property" : _singletons.To.ToLength(realm, lenProperty) !== initialLength) {
this.visitValue(lenProperty);
}
}
visitValueMap(val) {
(0, _invariant.default)(val.getKind() === "Map");
let entries = val.$MapData;
(0, _invariant.default)(entries !== undefined);
let len = entries.length;
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;
this.visitValue(key);
this.visitValue(value);
}
}
visitValueWeakMap(val) {
(0, _invariant.default)(val.getKind() === "WeakMap");
let entries = val.$WeakMapData;
(0, _invariant.default)(entries !== undefined);
let len = entries.length;
for (let i = 0; i < len; i++) {
let entry = entries[i];
let key = entry.$Key;
let value = entry.$Value;
if (key !== undefined && value !== undefined) {
let fixpoint_rerun = () => {
let progress;
if (this.values.has(key)) {
progress = true;
this.visitValue(key);
this.visitValue(value);
} else {
progress = false;
this._enqueueWithUnrelatedScope(this.scope, fixpoint_rerun);
}
return progress;
};
fixpoint_rerun();
}
}
}
visitValueSet(val) {
(0, _invariant.default)(val.getKind() === "Set");
let entries = val.$SetData;
(0, _invariant.default)(entries !== undefined);
let len = entries.length;
for (let i = 0; i < len; i++) {
let entry = entries[i];
if (entry === undefined) continue;
this.visitValue(entry);
}
}
visitValueWeakSet(val) {
(0, _invariant.default)(val.getKind() === "WeakSet");
let entries = val.$WeakSetData;
(0, _invariant.default)(entries !== undefined);
let len = entries.length;
for (let i = 0; i < len; i++) {
let entry = entries[i];
if (entry !== undefined) {
let fixpoint_rerun = () => {
let progress;
if (this.values.has(entry)) {
progress = true;
this.visitValue(entry);
} else {
progress = false;
this._enqueueWithUnrelatedScope(this.scope, fixpoint_rerun);
}
return progress;
};
fixpoint_rerun();
}
}
}
visitValueFunction(val) {
let isClass = false;
this._registerAdditionalRoot(val);
if (val instanceof _index2.ECMAScriptFunctionValue && val.$FunctionKind === "classConstructor") {
(0, _invariant.default)(val instanceof _index2.ECMAScriptSourceFunctionValue);
let homeObject = val.$HomeObject;
if (homeObject instanceof _index2.ObjectValue && homeObject.$IsClassPrototype) {
isClass = true;
}
}
this.visitObjectProperties(val);
if (val instanceof _index2.BoundFunctionValue) {
this.visitValue(val.$BoundTargetFunction);
this.visitValue(val.$BoundThis);
for (let boundArg of val.$BoundArguments) this.visitValue(boundArg);
return;
}
(0, _invariant.default)(!(val instanceof _index2.NativeFunctionValue), "all native function values should be intrinsics");
(0, _invariant.default)(val instanceof _index2.ECMAScriptSourceFunctionValue);
(0, _invariant.default)(val.constructor === _index2.ECMAScriptSourceFunctionValue);
let formalParameters = val.$FormalParameters;
let code = val.$ECMAScriptCode;
let functionInfo = this.functionInfos.get(code);
let residualFunctionBindings = new Map();
this.functionInstances.set(val, {
residualFunctionBindings,
initializationStatements: [],
functionValue: val,
scopeInstances: new Map()
});
if (!functionInfo) {
functionInfo = {
depth: 0,
lexicalDepth: 0,
unbound: new Map(),
requireCalls: new Map(),
modified: new Set(),
usesArguments: false,
usesThis: false
};
let state = {
functionInfo,
realm: this.realm,
getModuleIdIfNodeIsRequireFunction: this.modules.getGetModuleIdIfNodeIsRequireFunction(formalParameters, [val])
};
(0, _traverse.default)(t.file(t.program([t.expressionStatement(t.functionExpression(null, formalParameters, code))])), _visitors.ClosureRefVisitor, null, state);
_traverse.default.cache.clear();
this.functionInfos.set(code, functionInfo);
if (val.isResidual && functionInfo.unbound.size) {
if (!val.isUnsafeResidual) {
this.logger.logError(val, `residual function ${(0, _Error.describeLocation)(this.realm, val, undefined, code.loc) || "(unknown)"} refers to the following identifiers defined outside of the local scope: ${Object.keys(functionInfo.unbound).join(", ")}`);
}
}
}
let additionalFunctionEffects = this.additionalFunctionValuesAndEffects.get(val);
if (additionalFunctionEffects) {
this._visitAdditionalFunction(val, additionalFunctionEffects);
} else {
this._enqueueWithUnrelatedScope(val, () => {
(0, _invariant.default)(this.scope === val);
(0, _invariant.default)(functionInfo);
for (let innerName of functionInfo.unbound.keys()) {
let environment = this.resolveBinding(val, innerName);
let residualBinding = this.getBinding(environment, innerName);
this.visitBinding(val, residualBinding);
residualFunctionBindings.set(innerName, residualBinding);
if (functionInfo.modified.has(innerName)) residualBinding.modified = true;
}
});
}
if (isClass && val.$HomeObject instanceof _index2.ObjectValue) {
this._visitClass(val, val.$HomeObject);
}
}
_visitBindingHelper(residualFunctionBinding) {
if (residualFunctionBinding.hasLeaked) return;
let environment = residualFunctionBinding.declarativeEnvironmentRecord;
(0, _invariant.default)(environment !== null);
if (residualFunctionBinding.value === undefined) {
// The first time we visit, we need to initialize the value to its equivalent value
(0, _invariant.default)(environment instanceof _environment.DeclarativeEnvironmentRecord);
let binding = environment.bindings[residualFunctionBinding.name];
(0, _invariant.default)(binding !== undefined);
(0, _invariant.default)(!binding.deletable);
let value = binding.initialized && binding.value || this.realm.intrinsics.undefined;
residualFunctionBinding.value = this.visitEquivalentValue(value);
} else {
// Subsequently, we just need to visit the value.
this.visitValue(residualFunctionBinding.value);
}
} // Addresses the case:
// let x = [];
// let y = [];
// function a() { x.push("hi"); }
// function b() { y.push("bye"); }
// function c() { return x.length + y.length; }
// Here we need to make sure that a and b both initialize x and y because x and y will be in the same
// captured scope because c captures both x and y.
visitBinding(scope, residualFunctionBinding) {
let environment = residualFunctionBinding.declarativeEnvironmentRecord;
if (environment === null) return;
(0, _invariant.default)(this.scope === scope);
let refScope = this._getAdditionalFunctionOfScope() || "GLOBAL";
residualFunctionBinding.potentialReferentializationScopes.add(refScope);
(0, _invariant.default)(!(refScope instanceof _generator.Generator));
let funcToScopes = (0, _utils.getOrDefault)(this.functionToCapturedScopes, refScope, () => new Map());
let envRec = residualFunctionBinding.declarativeEnvironmentRecord;
(0, _invariant.default)(envRec !== null);
let bindingState = (0, _utils.getOrDefault)(funcToScopes, envRec, () => ({
capturedBindings: new Set(),
capturingScopes: new Set()
})); // If the binding is new for this bindingState, have all functions capturing bindings from that scope visit it
if (!bindingState.capturedBindings.has(residualFunctionBinding)) {
for (let capturingScope of bindingState.capturingScopes) {
this._enqueueWithUnrelatedScope(capturingScope, () => this._visitBindingHelper(residualFunctionBinding));
}
bindingState.capturedBindings.add(residualFunctionBinding);
} // If the function is new for this bindingState, visit all existent bindings in this scope
if (!bindingState.capturingScopes.has(scope)) {
(0, _invariant.default)(this.scope === scope);
for (let residualBinding of bindingState.capturedBindings) this._visitBindingHelper(residualBinding);
bindingState.capturingScopes.add(scope);
}
}
resolveBinding(val, name) {
let doesNotMatter = true;
let reference = this.logger.tryQuery(() => _singletons.Environment.ResolveBinding(this.realm, name, doesNotMatter, val.$Environment), undefined);
if (reference === undefined || _singletons.Environment.IsUnresolvableReference(this.realm, reference) || reference.base === this.globalEnvironmentRecord || reference.base === this.globalEnvironmentRecord.$DeclarativeRecord) {
return this.globalEnvironmentRecord;
} else {
(0, _invariant.default)(!_singletons.Environment.IsUnresolvableReference(this.realm, reference));
let referencedBase = reference.base;
let referencedName = reference.referencedName;
(0, _invariant.default)(referencedName === name);
(0, _invariant.default)(referencedBase instanceof _environment.DeclarativeEnvironmentRecord);
return referencedBase;
}
}
hasBinding(environment, name) {
if (environment === this.globalEnvironmentRecord.$DeclarativeRecord) environment = this.globalEnvironmentRecord;
if (environment === this.globalEnvironmentRecord) {
// Global Binding
return this.globalBindings.get(name) !== undefined;
} else {
(0, _invariant.default)(environment instanceof _environment.DeclarativeEnvironmentRecord); // DeclarativeEnvironmentRecord binding
let residualFunctionBindings = this.declarativeEnvironmentRecordsBindings.get(environment);
if (residualFunctionBindings === undefined) return false;
return residualFunctionBindings.get(name) !== undefined;
}
} // Visits a binding, returns a ResidualFunctionBinding
getBinding(environment, name) {
if (environment === this.globalEnvironmentRecord.$DeclarativeRecord) environment = this.globalEnvironmentRecord;
if (environment === this.globalEnvironmentRecord) {
// Global Binding
return (0, _utils.getOrDefault)(this.globalBindings, name, () => {
let residualFunctionBinding = {
name,
value: undefined,
modified: true,
hasLeaked: false,
declarativeEnvironmentRecord: null,
potentialReferentializationScopes: new Set()
}; // Queue up visiting of global binding exactly once in the globalGenerator scope.
this._enqueueWithUnrelatedScope(this.globalGenerator, () => {
let value = this.realm.getGlobalLetBinding(name);
if (value !== undefined) residualFunctionBinding.value = this.visitEquivalentValue(value);
});
return residualFunctionBinding;
});
} else {
(0, _invariant.default)(environment instanceof _environment.DeclarativeEnvironmentRecord); // DeclarativeEnvironmentRecord binding
let residualFunctionBindings = (0, _utils.getOrDefault)(this.declarativeEnvironmentRecordsBindings, environment, () => new Map());
return (0, _utils.getOrDefault)(residualFunctionBindings, name, () => {
(0, _invariant.default)(environment instanceof _environment.DeclarativeEnvironmentRecord);
return {
name,
value: undefined,
modified: false,
hasLeaked: false,
declarativeEnvironmentRecord: environment,
potentialReferentializationScopes: new Set()
};
}); // Note that we don't yet visit the binding (and its value) here,
// as that should be done by a call to visitBinding, in the right scope,
// if the binding's incoming value is relevant.
}
}
_visitClass(classFunc, classPrototype) {
let visitClassMethod = (propertyNameOrSymbol, methodFunc, methodType, isStatic) => {
if (methodFunc instanceof _index2.ECMAScriptSourceFunctionValue) {
// if the method does not have a $HomeObject, it's not a class method
if (methodFunc.$HomeObject !== undefined) {
if (methodFunc !== classFunc) {
this._visitClassMethod(methodFunc, methodType, classPrototype, !!isStatic);
}
}
}
};
for (let [propertyName, method] of classPrototype.properties) {
(0, _utils.withDescriptorValue)(propertyName, method.descriptor, visitClassMethod);
}
for (let [symbol, method] of classPrototype.symbols) {
(0, _utils.withDescriptorValue)(symbol, method.descriptor, visitClassMethod);
} // handle class inheritance
if (!(classFunc.$Prototype instanceof _index2.NativeFunctionValue)) {
this.visitValue(classFunc.$Prototype);
}
if (classPrototype.properties.has("constructor")) {
let constructor = classPrototype.properties.get("constructor");
(0, _invariant.default)(constructor !== undefined); // check if the constructor was deleted, as it can't really be deleted
// it just gets set to empty (the default again)
if (constructor.descriptor === undefined) {
classFunc.$HasEmptyConstructor = true;
} else {
let visitClassProperty = (propertyNameOrSymbol, methodFunc, methodType) => {
visitClassMethod(propertyNameOrSymbol, methodFunc, methodType, true);
}; // check if we have any static methods we need to include
let constructorFunc = (0, _index.Get)(this.realm, classPrototype, "constructor");
(0, _invariant.default)(constructorFunc instanceof _index2.ObjectValue);
for (let [propertyName, method] of constructorFunc.properties) {
if (!_utils.ClassPropertiesToIgnore.has(propertyName) && method.descriptor !== undefined && !(propertyName === "length" && (0, _utils.canIgnoreClassLengthProperty)(constructorFunc, method.descriptor, this.logger))) {
(0, _utils.withDescriptorValue)(propertyName, method.descriptor, visitClassProperty);
}
}
}
}
this.classMethodInstances.set(classFunc, {
classPrototype,
methodType: "constructor",
classSuperNode: undefined,
classMethodIsStatic: false,
classMethodKeyNode: undefined,
classMethodComputed: false
});
}
_visitClassMethod(methodFunc, methodType, classPrototype, isStatic) {
this.classMethodInstances.set(methodFunc, {
classPrototype,
methodType: methodType === "value" ? "method" : methodType,
classSuperNode: undefined,
classMethodIsStatic: isStatic,
classMethodKeyNode: undefined,
classMethodComputed: !!methodFunc.$HasComputedName
});
}
visitValueObject(val) {
(0, _invariant.default)(val.isValid());
this._registerAdditionalRoot(val);
if ((0, _utils2.isReactElement)(val)) {
this.residualReactElementVisitor.visitReactElement(val);
return;
}
let kind = val.getKind();
this.visitObjectProperties(val, kind); // If this object is a prototype object that was implicitly created by the runtime
// for a constructor, then we can obtain a reference to this object
// in a special way that's handled alongside function serialization.
let constructor = val.originalConstructor;
if (constructor !== undefined) {
this.visitValue(constructor);
return;
}
switch (kind) {
case "RegExp":
case "Number":
case "String":
case "Boolean":
case "ArrayBuffer":
return;
case "Date":
let dateValue = val.$DateValue;
(0, _invariant.default)(dateValue !== undefined);
this.visitValue(dateValue);
return;
case "Float32Array":
case "Float64Array":
case "Int8Array":
case "Int16Array":
case "Int32Array":
case "Uint8Array":
case "Uint16Array":
case "Uint32Array":
case "Uint8ClampedArray":
case "DataView":
let buf = val.$ViewedArrayBuffer;
(0, _invariant.default)(buf !== undefined);
this.visitValue(buf);
return;
case "Map":
this.visitValueMap(val);
return;
case "WeakMap":
this.visitValueWeakMap(val);
return;
case "Set":
this.visitValueSet(val);
return;
case "WeakSet":
this.visitValueWeakSet(val);
return;
default:
if (kind !== "Object") this.logger.logError(val, `Object of kind ${kind} is not supported in residual heap.`);
if (this.realm.react.enabled && (0, _utils2.valueIsReactLibraryObject)(this.realm, val, this.logger)) {
this.realm.fbLibraries.react = val;
}
return;
}
}
visitValueSymbol(val) {
if (val.$Description) this.visitValue(val.$Description);
}
visitValueProxy(val) {
this._registerAdditionalRoot(val);
this.visitValue(val.$ProxyTarget);
this.visitValue(val.$ProxyHandler);
}
_visitAbstractValueConditional(val) {
let condition = val.args[0];
(0, _invariant.default)(condition instanceof _index2.AbstractValue);
let cf = this.conditionalFeasibility.get(val);
if (cf === undefined) this.conditionalFeasibility.set(val, cf = {
t: false,
f: false
});
let feasibleT, feasibleF;
let savedPath = this.realm.pathConditions;
try {
this.realm.pathConditions = this.scope instanceof _generator.Generator ? this.scope.pathConditions : (0, _singletons.createPathConditions)();
let impliesT = _singletons.Path.implies(condition);
let impliesF = _singletons.Path.impliesNot(condition);
(0, _invariant.default)(!(impliesT && impliesF));
if (!impliesT && !impliesF) {
feasibleT = feasibleF = true;
} else {
feasibleT = impliesT;
feasibleF = impliesF;
}
} finally {
this.realm.pathConditions = savedPath;
}
let visitedT = false,
visitedF = false;
if (!cf.t && feasibleT) {
val.args[1] = this.visitEquivalentValue(val.args[1]);
cf.t = true;
if (cf.f) val.args[0] = this.visitEquivalentValue(val.args[0]);
visitedT = true;
}
if (!cf.f && feasibleF) {
val.args[2] = this.visitEquivalentValue(val.args[2]);
cf.f = true;
if (cf.t) val.args[0] = this.visitEquivalentValue(val.args[0]);
visitedF = true;
}
if (!visitedT || !visitedF) {
let fixpoint_rerun = () => {
let progress = false;
(0, _invariant.default)(cf !== undefined);
if (cf.f && cf.t) {
(0, _invariant.default)(!visitedT || !visitedF);
this.visitValue(val.args[0]);
}
if (cf.t && !visitedT) {
this.visitValue(val.args[1]);
progress = visitedT = true;
}
(0, _invariant.default)(cf.t === visitedT);
if (cf.f && !visitedF) {
this.visitValue(val.args[2]);
progress = visitedF = true;
}
(0, _invariant.default)(cf.f === visitedF); // When not all possible outcomes are assumed to be feasible yet after visiting some scopes,
// it might be that they do become assumed to be feasible when later visiting some other scopes.
// In that case, we should also re-visit the corresponding cases in this scope.
// To this end, calling _enqueueWithUnrelatedScope enqueues this function for later re-execution if
// any other visiting progress was made.
if (!visitedT || !visitedF) this._enqueueWithUnrelatedScope(this.scope, fixpoint_rerun);
return progress;
};
fixpoint_rerun();
}
}
visitAbstractValue(val) {
if (val.kind === "sentinel member expression") {
this.logger.logError(val, "expressions of type o[p] are not yet supported for partially known o and unknown p");
} else if (val.kind === "environment initialization expression") {
this.logger.logError(val, "reads during environment initialization should never leak to serialization");
} else if (val.kind === "conditional") {
this._visitAbstractValueConditional(val);
return;
}
for (let i = 0, n = val.args.length; i < n; i++) {
val.args[i] = this.visitEquivalentValue(val.args[i]);
}
} // Overridable hook for pre-visiting the value.
// Return false will tell visitor to skip visiting children of this node.
preProcessValue(val) {
return this._mark(val);
} // Overridable hook for post-visiting the value.
postProcessValue(val) {}
_mark(val) {
let scopes = this.values.get(val);
if (scopes === undefined) this.values.set(val, scopes = new Set());
if (this.scope instanceof _generator.Generator && this.scope.effectsToApply === undefined) {
// If we've already marked this value for any simple parent (non-effect carrying) generator,
// then we don't need to re-mark it, as such a set of generators is reduced to the
// parent generator in all uses of the scopes set.
for (let g = this.scope; g instanceof _generator.Generator && g.effectsToApply === undefined; g = this.generatorTree.getParent(g)) {
if (scopes.has(g)) return false;
}
} else if (scopes.has(this.scope)) return false;
scopes.add(this.scope);
return true;
}
visitEquivalentValue(val) {
if (val instanceof _index2.AbstractValue) {
let equivalentValue = this.equivalenceSet.add(val);
if (this.preProcessValue(equivalentValue)) this.visitAbstractValue(equivalentValue);
this.postProcessValue(equivalentValue);
return equivalentValue;
}
if (val instanceof _index2.ObjectValue) {
(0, _invariant.default)(val.isValid());
if ((0, _utils2.isReactElement)(val)) {
if (val.temporalAlias !== undefined) {
return this.visitEquivalentValue(val.temporalAlias);
}
let equivalentReactElementValue = this.residualReactElementVisitor.reactElementEquivalenceSet.add(val);
if (this._mark(equivalentReactElementValue)) this.visitValueObject(equivalentReactElementValue);
return equivalentReactElementValue;
} else if ((0, _utils2.isReactPropsObject)(val)) {
let equivalentReactPropsValue = this.residualReactElementVisitor.reactPropsEquivalenceSet.add(val);
if (this._mark(equivalentReactPropsValue)) this.visitValueObject(equivalentReactPropsValue);
return equivalentReactPropsValue;
}
}
this.visitValue(val);
return val;
}
visitValue(val) {
(0, _invariant.default)(val !== undefined);
(0, _invariant.default)(!(val instanceof _index2.ObjectValue && val.refuseSerialization));
if (val instanceof _index2.AbstractValue) {
if (this.preProcessValue(val)) this.visitAbstractValue(val);
this.postProcessValue(val);
} else if (val.isIntrinsic()) {
// All intrinsic values exist from the beginning of time...
// ...except for a few that come into existence as templates for abstract objects via executable code.
if (val instanceof _index2.ObjectValue && val.isScopedTemplate) {
this.preProcessValue(val);
this.postProcessValue(val);
} else this._enqueueWithUnrelatedScope(this._getCommonScope(), () => {
this.preProcessValue(val);
this.postProcessValue(val);
});
} else if (val instanceof _index2.EmptyValue) {
this.preProcessValue(val);
this.postProcessValue(val);
} else if (_HeapInspector.HeapInspector.isLeaf(val)) {
this.preProcessValue(val);
this.postProcessValue(val);
} else if ((0, _index.IsArray)(this.realm, val)) {
(0, _invariant.default)(val instanceof _index2.ObjectValue);
if (this.preProcessValue(val)) this.visitValueArray(val);
this.postProcessValue(val);
} else if (val instanceof _index2.ProxyValue) {
if (this.preProcessValue(val)) this.visitValueProxy(val);
this.postProcessValue(val);
} else if (val instanceof _index2.FunctionValue) {
let creationGenerator = this.generatorTree.getCreator(val) || this.globalGenerator; // 1. Visit function in its creation scope
this._enqueueWithUnrelatedScope(creationGenerator, () => {
(0, _invariant.default)(val instanceof _index2.FunctionValue);
if (this.preProcessValue(val)) this.visitValueFunction(val);
this.postProcessValue(val);
}); // 2. If current scope is not related to creation scope,
// and if this is not a recursive visit, mark the usage of this function
// in the common scope as well.
let commonScope = this._getCommonScope();
if (commonScope !== creationGenerator && commonScope !== val) {
this._enqueueWithUnrelatedScope(commonScope, () => {
this.preProcessValue(val);
this.postProcessValue(val);
});
}
} else if (val instanceof _index2.SymbolValue) {
if (this.preProcessValue(val)) this.visitValueSymbol(val);
this.postProcessValue(val);
} else {
(0, _invariant.default)(val instanceof _index2.ObjectValue);
if (this.preProcessValue(val)) this.visitValueObject(val);
this.postProcessValue(val);
}
}
createGeneratorVisitCallbacks(additionalFunctionInfo) {
let callbacks = {
visitEquivalentValue: this.visitEquivalentValue.bind(this),
visitGenerator: (generator, parent) => {
(0, _invariant.default)(this.generatorTree.getParent(generator) === parent);
this.visitGenerator(generator, additionalFunctionInfo);
},
canOmit: value => {
let canOmit = !this.referencedDeclaredValues.has(value) && !this.values.has(value);
if (!canOmit) {
return false;
}
if (value instanceof _index2.ObjectValue && value.temporalAlias !== undefined) {
let temporalAlias = value.temporalAlias;
return !this.referencedDeclaredValues.has(temporalAlias) && !this.values.has(temporalAlias);
}
return canOmit;
},
recordDeclaration: value => {
this.referencedDeclaredValues.set(value, this._getAdditionalFunctionOfScope());
},
recordDelayedEntry: (generator, entry) => {
this._enqueueWithUnrelatedScope(generator, () => entry.visit(callbacks, generator));
},
visitModifiedProperty: binding => {
let fixpoint_rerun = () => {
if (this.values.has(binding.object)) {
if (binding.internalSlot) {
(0, _invariant.default)(typeof binding.key === "string");
let error = new _errors.CompilerDiagnostic(`Internal slot ${binding.key} modified in a nested context. This is not yet supported.`, binding.object.expressionLocation, "PP1006", "FatalError");
this.realm.handleError(error) === "Fail";
throw new _errors.FatalError();
}
this.visitValue(binding.object);
if (binding.key instanceof _index2.Value) this.visitValue(binding.key);
this.visitObjectProperty(binding);
return true;
} else {
this._enqueueWithUnrelatedScope(this.scope, fixpoint_rerun);
return false;
}
};
fixpoint_rerun();
},
visitModifiedBinding: modifiedBinding => {
let fixpoint_rerun = () => {
if (this.hasBinding(modifiedBinding.environment, modifiedBinding.name)) {
(0, _invariant.default)(additionalFunctionInfo);
let {
functionValue
} = additionalFunctionInfo;
(0, _invariant.default)(functionValue instanceof _index2.ECMAScriptSourceFunctionValue);
let residualBinding = this.getBinding(modifiedBinding.environment, modifiedBinding.name);
let funcInstance = additionalFunctionInfo.instance;
(0, _invariant.default)(funcInstance !== undefined);
funcInstance.residualFunctionBindings.set(modifiedBinding.name, residualBinding);
let newValue = modifiedBinding.value;
(0, _invariant.default)(newValue);
this.visitValue(newValue);
residualBinding.modified = true;
let otherFunc = residualBinding.additionalFunctionOverridesValue;
if (otherFunc !== undefined && otherFunc !== functionValue) {
let otherNameVal = otherFunc._SafeGetDataPropertyValue("name");
let otherNameStr = otherNameVal instanceof _index2.StringValue ? otherNameVal.value : "unknown function";
let funcNameVal = functionValue._SafeGetDataPropertyValue("name");
let funNameStr = funcNameVal instanceof _index2.StringValue ? funcNameVal.value : "unknown function";
let error = new _errors.CompilerDiagnostic(`Variable ${modifiedBinding.name} written to in optimized function ${funNameStr} conflicts with write in another optimized function ${otherNameStr}`, funcNameVal.expressionLocation, "PP1001", "RecoverableError");
if (functionValue.$Realm.handleError(error) === "Fail") throw new _errors.FatalError();
}
residualBinding.additionalFunctionOverridesValue = functionValue;
additionalFunctionInfo.modifiedBindings.set(modifiedBinding, residualBinding); // TODO #2430 nested optimized functions: revisit adding GLOBAL as outer optimized function
residualBinding.potentialReferentializationScopes.add("GLOBAL");
return true;
} else {
this._enqueueWithUnrelatedScope(this.scope, fixpoint_rerun);
return false;
}
};
fixpoint_rerun();
},
visitBindingAssignment: (binding, value) => {
let residualBinding = this.getBinding(binding.environment, binding.name);
residualBinding.modified = true;
residualBinding.hasLeaked = true; // This may not have been referentialized if the binding is a local of an optimized function.
// in that case, we need to figure out which optimized function it is, and referentialize it in that scope.
let commonScope = this._getCommonScope();
if (residualBinding.potentialReferentializationScopes.size === 0) {
this._enqueueWithUnrelatedScope(commonScope, () => {
if (additionalFunctionInfo !== undefined) {
let funcInstance = additionalFunctionInfo.instance;
(0, _invariant.default)(funcInstance !== undefined);
funcInstance.residualFunctionBindings.set(residualBinding.name, residualBinding);
}
this.visitBinding(commonScope, residualBinding);
});
}
return this.visitEquivalentValue(value);
}
};
return callbacks;
}
visitGenerator(generator, additionalFunctionInfo) {
this._withScope(generator, () => {
generator.visit(this.createGeneratorVisitCallbacks(additionalFunctionInfo));
}); // We don't bother purging created objects
} // result -- serialized as a return statement
// Generator -- visit all entries
// Bindings -- (modifications to named variables) only need to serialize bindings if they're
// captured by a residual function
// -- need to apply them and maybe need to revisit functions in ancestors to make sure
// we don't overwrite anything they capture
// PropertyBindings -- (property modifications) visit any property bindings to pre-existing objects
// CreatedObjects -- should take care of itself
_visitAdditionalFunction(functionValue, additionalEffects) {
// Get Instance + Info
(0, _invariant.default)(functionValue instanceof _index2.ECMAScriptSourceFunctionValue);
let code = functionValue.$ECMAScriptCode;
let functionInfo = this.functionInfos.get(code);
(0, _invariant.default)(functionInfo !== undefined);
let funcInstance = this.functionInstances.get(functionValue);
(0, _invariant.default)(funcInstance !== undefined); // Set Visitor state
// Allows us to emit function declarations etc. inside of this additional
// function instead of adding them at global scope
let visitor = () => {
(0, _invariant.default)(funcInstance !== undefined);
(0, _invariant.default)(functionInfo !== undefined);
let additionalFunctionInfo = {
modifiedBindings: new Map(),
functionValue,
instance: funcInstance,
prelude: []
};
this.additionalFunctionValueInfos.set(functionValue, additionalFunctionInfo);
let effectsGenerator = additionalEffects.generator;
this.generatorTree.add(functionValue, effectsGenerator);
this.visitGenerator(effectsGenerator, additionalFunctionInfo);
};
if (this.realm.react.enabled) {
this.residualReactElementVisitor.withCleanEquivalenceSet(visitor);
} else {
visitor();
}
}
visitRoots() {
this.generatorTree.add("GLOBAL", this.globalGenerator);
this.visitGenerator(this.globalGenerator);
for (let moduleValue of this.modules.initializedModules.values()) this.visitValue(moduleValue);
this._visitUntilFixpoint();
}
_visitUntilFixpoint() {
if (this.realm.react.verbose) {
this.logger.logInformation(`Computing fixed point...`);
} // Do a fixpoint over all pure generator entries to make sure that we visit
// arguments of only BodyEntries that are required by some other residual value
let progress = true;
while (progress) {
// Let's partition the actions by their generators,
// as applying effects is expensive, and so we don't want to do it
// more often than necessary.
let actionsByGenerator = new Map();
let expected = 0;
for (let _ref of this.delayedActions) {
let {
scope,
action
} = _ref;
let generator;
if (scope instanceof _index2.FunctionValue) generator = this.generatorTree.getCreator(scope) || this.globalGenerator;else if (scope === "GLOBAL") generator = this.globalG