prepack
Version:
Execute a JS bundle, serialize global state and side effects to a snapshot that can be quickly restored.
990 lines (886 loc) • 42.6 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.ResidualHeapVisitor = undefined;
var _environment = require("../environment.js");
var _realm = require("../realm.js");
var _completions = require("../completions.js");
var _index = require("../methods/index.js");
var _index2 = require("../values/index.js");
var _Error = require("../intrinsics/ecma262/Error.js");
var _babelTypes = require("babel-types");
var t = _interopRequireWildcard(_babelTypes);
var _generator = require("../utils/generator.js");
var _babelTraverse = require("babel-traverse");
var _babelTraverse2 = _interopRequireDefault(_babelTraverse);
var _invariant = require("../invariant.js");
var _invariant2 = _interopRequireDefault(_invariant);
var _visitors = require("./visitors.js");
var _logger = require("../utils/logger.js");
var _modules = require("../utils/modules.js");
var _ResidualHeapInspector = require("./ResidualHeapInspector.js");
var _Referentializer = require("./Referentializer.js");
var _utils = require("./utils.js");
var _singletons = require("../singletons.js");
var _utils2 = require("../react/utils.js");
var _hoisting = require("../react/hoisting.js");
var _ReactElementSet = require("../react/ReactElementSet.js");
var _ReactElementSet2 = _interopRequireDefault(_ReactElementSet);
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; } }
/* 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.
*/
/**
* 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.
*/
class ResidualHeapVisitor {
constructor(realm, logger, modules, additionalFunctionValuesAndEffects,
// Referentializer is null if we're just checking what values exist
referentializer, environmentRecordIdAfterGlobalCode = 0) {
(0, _invariant2.default)(realm.useAbstractInterpretation);
this.realm = realm;
this.logger = logger;
this.modules = modules;
this.referentializer = referentializer === "NO_REFERENTIALIZE" ? undefined : referentializer;
this.declarativeEnvironmentRecordsBindings = new Map();
this.globalBindings = new Map();
this.functionInfos = new Map();
this.classMethodInstances = new Map();
this.functionInstances = new Map();
this.values = new Map();
let generator = this.realm.generator;
(0, _invariant2.default)(generator);
this.scope = this.commonScope = generator;
this.inspector = new _ResidualHeapInspector.ResidualHeapInspector(realm, logger);
this.referencedDeclaredValues = new Map();
this.delayedVisitGeneratorEntries = [];
this.someReactElement = undefined;
this.additionalFunctionValuesAndEffects = additionalFunctionValuesAndEffects;
this.equivalenceSet = new _index.HashSet();
this.reactElementEquivalenceSet = new _ReactElementSet2.default(realm, this.equivalenceSet);
this.additionalFunctionValueInfos = new Map();
this.containingAdditionalFunction = undefined;
this.additionalRoots = new Map();
this.inClass = false;
this.functionToCapturedScopes = new Map();
this.generatorParents = new Map();
this.environmentRecordIdAfterGlobalCode = environmentRecordIdAfterGlobalCode;
let environment = realm.$GlobalEnv.environmentRecord;
(0, _invariant2.default)(environment instanceof _environment.GlobalEnvironmentRecord);
this.globalEnvironmentRecord = environment;
}
// Caches that ensure one ResidualFunctionBinding exists per (record, name) pair
// Either the realm's generator or the FunctionValue of an additional function to serialize
// We only want to add to additionalRoots when we're in an additional function
// Tracks objects + functions that were visited from inside additional functions that need to be serialized in a
// parent scope of the additional function (e.g. functions/objects only used from additional functions that were
// declared outside the additional function need to be serialized in the additional function's parent scope for
// identity to work).
_registerAdditionalRoot(value) {
let additionalFunction = this.containingAdditionalFunction;
if (additionalFunction !== undefined && !this.inClass) {
let s = this.additionalRoots.get(value);
if (s === undefined) this.additionalRoots.set(value, s = new Set());
s.add(additionalFunction);
}
}
_withScope(scope, f) {
let oldScope = this.scope;
this.scope = scope;
try {
f();
} finally {
this.scope = oldScope;
}
}
visitObjectProperty(binding) {
let desc = binding.descriptor;
if (desc === undefined) return; //deleted
let obj = binding.object;
if (obj instanceof _index2.AbstractObjectValue || !this.inspector.canIgnoreProperty(obj, binding.key)) {
this.visitDescriptor(desc);
}
}
visitObjectProperties(obj, kind) {
let { skipPrototype, constructor } = (0, _utils.getObjectPrototypeMetadata)(this.realm, obj);
// visit properties
if (!(0, _utils2.isReactElement)(obj)) {
for (let [symbol, propertyBinding] of obj.symbols) {
(0, _invariant2.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.$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
(0, _invariant2.default)(propertyBindingValue);
this.visitObjectProperty(propertyBindingValue);
}
// 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.visitObjectPropertiesWithComputedNames(val);
}
}
// prototype
if (!(0, _utils2.isReactElement)(obj) && !skipPrototype) {
// we don't want to the ReactElement prototype visited
// as this is contained within the JSXElement, otherwise
// they we be need to be emitted during serialization
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, _invariant2.default)(func instanceof _index2.FunctionValue);
let prototype = _ResidualHeapInspector.ResidualHeapInspector.getPropertyValue(func, "prototype");
if (prototype instanceof _index2.ObjectValue && prototype.originalConstructor === func && !this.inspector.isDefaultPrototype(prototype)) {
this.visitValue(prototype);
}
}
visitObjectPropertiesWithComputedNames(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.visitObjectPropertiesWithComputedNames(earlier_props);
this.visitValue(P);
this.visitValue(V);
} else {
// conditional assignment
absVal.args[0] = this.visitEquivalentValue(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.visitObjectPropertiesWithComputedNames(consequent);
this.visitObjectPropertiesWithComputedNames(alternate);
}
}
visitDescriptor(desc) {
(0, _invariant2.default)(desc.value === undefined || desc.value instanceof _index2.Value);
if (desc.joinCondition !== undefined) {
desc.joinCondition = this.visitEquivalentValue(desc.joinCondition);
if (desc.descriptor1 !== undefined) this.visitDescriptor(desc.descriptor1);
if (desc.descriptor2 !== undefined) this.visitDescriptor(desc.descriptor2);
return;
}
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);
}
visitValueArray(val) {
this.visitObjectProperties(val);
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");
}
if (lenProperty instanceof _index2.AbstractValue ? lenProperty.kind !== "widened property" : _singletons.To.ToLength(realm, lenProperty) !== (0, _utils.getSuggestedArrayLiteralLength)(realm, val)) {
this.visitValue(lenProperty);
}
}
visitValueMap(val) {
let kind = val.getKind();
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;
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);
}
}
visitValueSet(val) {
let kind = val.getKind();
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;
for (let i = 0; i < len; i++) {
let entry = entries[i];
if (entry === undefined) continue;
this.visitValue(entry);
}
}
visitValueFunction(val, parentScope) {
let isClass = false;
this._registerAdditionalRoot(val);
if (val.$FunctionKind === "classConstructor") {
(0, _invariant2.default)(val instanceof _index2.ECMAScriptSourceFunctionValue);
let homeObject = val.$HomeObject;
if (homeObject instanceof _index2.ObjectValue && homeObject.$IsClassPrototype) {
isClass = true;
this.inClass = true;
}
}
this.visitObjectProperties(val);
if (isClass && this.inClass) {
this.inClass = false;
}
if (val instanceof _index2.BoundFunctionValue) {
this.visitValue(val.$BoundTargetFunction);
this.visitValue(val.$BoundThis);
for (let boundArg of val.$BoundArguments) this.visitValue(boundArg);
return;
}
(0, _invariant2.default)(!(val instanceof _index2.NativeFunctionValue), "all native function values should be intrinsics");
(0, _invariant2.default)(val instanceof _index2.ECMAScriptSourceFunctionValue);
(0, _invariant2.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 = {
unbound: new Set(),
modified: new Set(),
usesArguments: false,
usesThis: false
};
let state = {
tryQuery: this.logger.tryQuery.bind(this.logger),
val,
functionInfo,
realm: this.realm
};
(0, _babelTraverse2.default)(t.file(t.program([t.expressionStatement(t.functionExpression(null, formalParameters, code))])), _visitors.ClosureRefVisitor, null, state);
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, parentScope);
} else {
this._withScope(val, () => {
(0, _invariant2.default)(functionInfo);
for (let innerName of functionInfo.unbound) {
let environment = this.resolveBinding(val, innerName);
let residualBinding = this.visitBinding(val, environment, innerName);
(0, _invariant2.default)(residualBinding !== undefined);
residualFunctionBindings.set(innerName, residualBinding);
if (functionInfo.modified.has(innerName)) {
residualBinding.modified = true;
}
}
});
}
if (isClass && val.$HomeObject instanceof _index2.ObjectValue) {
this._visitClass(val, val.$HomeObject);
}
}
// 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.
_recordBindingVisitedAndRevisit(val, residualFunctionBinding) {
let refScope = this.containingAdditionalFunction ? this.containingAdditionalFunction : "GLOBAL";
(0, _invariant2.default)(!(refScope instanceof _generator.Generator));
let funcToScopes = (0, _utils.getOrDefault)(this.functionToCapturedScopes, refScope, () => new Map());
let envRec = residualFunctionBinding.declarativeEnvironmentRecord;
(0, _invariant2.default)(envRec !== null);
let bindingState = (0, _utils.getOrDefault)(funcToScopes, envRec, () => ({
capturedBindings: new Set(),
capturingFunctionsToCommonScope: new Map()
}));
// If the binding is new for this bindingState, have all functions capturing bindings from that scope visit it
if (!bindingState.capturedBindings.has(residualFunctionBinding)) {
if (residualFunctionBinding.value) {
(0, _invariant2.default)(this);
for (let [functionValue, functionCommonScope] of bindingState.capturingFunctionsToCommonScope) {
(0, _invariant2.default)(this);
let prevCommonScope = this.commonScope;
try {
this.commonScope = functionCommonScope;
let value = residualFunctionBinding.value;
this._withScope(functionValue, () => this.visitValue(value));
} finally {
this.commonScope = prevCommonScope;
}
}
}
bindingState.capturedBindings.add(residualFunctionBinding);
}
// If the function is new for this bindingState, visit all existent bindings in this scope
if (!bindingState.capturingFunctionsToCommonScope.has(val)) {
for (let residualBinding of bindingState.capturedBindings) {
if (residualBinding.value) this.visitValue(residualBinding.value);
}
bindingState.capturingFunctionsToCommonScope.set(val, this.commonScope);
}
}
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, _invariant2.default)(!_singletons.Environment.IsUnresolvableReference(this.realm, reference));
let referencedBase = reference.base;
let referencedName = reference.referencedName;
(0, _invariant2.default)(referencedName === name);
(0, _invariant2.default)(referencedBase instanceof _environment.DeclarativeEnvironmentRecord);
return referencedBase;
}
}
// Visits a binding, if createBinding is true, will always return a ResidualFunctionBinding
// otherwise visits + returns the binding only if one already exists.
visitBinding(val, environment, name, createBinding = true) {
if (environment === this.globalEnvironmentRecord.$DeclarativeRecord) environment = this.globalEnvironmentRecord;
let residualFunctionBinding;
let getFromMap = createBinding ? _utils.getOrDefault : (map, key, defaultFn) => map.get(key);
if (environment === this.globalEnvironmentRecord) {
// Global Binding
residualFunctionBinding = getFromMap(this.globalBindings, name, () => ({
value: this.realm.getGlobalLetBinding(name),
modified: true,
declarativeEnvironmentRecord: null
}));
} else {
(0, _invariant2.default)(environment instanceof _environment.DeclarativeEnvironmentRecord);
// DeclarativeEnvironmentRecord binding
let residualFunctionBindings = (0, _utils.getOrDefault)(this.declarativeEnvironmentRecordsBindings, environment, () => new Map());
let createdBinding = !residualFunctionBindings.has(name);
residualFunctionBinding = getFromMap(residualFunctionBindings, name, () => {
(0, _invariant2.default)(environment instanceof _environment.DeclarativeEnvironmentRecord);
let binding = environment.bindings[name];
(0, _invariant2.default)(binding !== undefined);
(0, _invariant2.default)(!binding.deletable);
return {
value: binding.initialized && binding.value || this.realm.intrinsics.undefined,
modified: false,
declarativeEnvironmentRecord: environment
};
});
if (residualFunctionBinding) {
if (this.containingAdditionalFunction && createdBinding) residualFunctionBinding.referencedOnlyFromAdditionalFunctions = this.containingAdditionalFunction;
if (!this.containingAdditionalFunction && residualFunctionBinding.referencedOnlyFromAdditionalFunctions) delete residualFunctionBinding.referencedOnlyFromAdditionalFunctions;
this._recordBindingVisitedAndRevisit(val, residualFunctionBinding);
}
}
if (residualFunctionBinding && residualFunctionBinding.value) {
residualFunctionBinding.value = this.visitEquivalentValue(residualFunctionBinding.value);
}
return residualFunctionBinding;
}
_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, _invariant2.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, _invariant2.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) {
this._registerAdditionalRoot(val);
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 "ReactElement":
if (this.realm.react.output === "create-element") {
this.someReactElement = val;
}
// check we can hoist a React Element
(0, _hoisting.canHoistReactElement)(this.realm, val, this);
return;
case "Date":
let dateValue = val.$DateValue;
(0, _invariant2.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, _invariant2.default)(buf !== undefined);
this.visitValue(buf);
return;
case "Map":
case "WeakMap":
this.visitValueMap(val);
return;
case "Set":
case "WeakSet":
this.visitValueSet(val);
return;
default:
if (kind !== "Object") this.logger.logError(val, `Object of kind ${kind} is not supported in residual heap.`);
if (this.$ParameterMap !== undefined) {
this.logger.logError(val, `Arguments object 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.visitValue(val.$ProxyTarget);
this.visitValue(val.$ProxyHandler);
}
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");
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 (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, _utils2.isReactElement)(val)) {
let equivalentReactElementValue = this.reactElementEquivalenceSet.add(val);
if (this._mark(equivalentReactElementValue)) this.visitValueObject(equivalentReactElementValue);
return equivalentReactElementValue;
}
this.visitValue(val);
return val;
}
visitValue(val) {
(0, _invariant2.default)(!val.refuseSerialization);
if (val instanceof _index2.AbstractValue) {
if (this.preProcessValue(val)) this.visitAbstractValue(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._isScopedTemplate) this.preProcessValue(val);else this._withScope(this.commonScope, () => {
this.preProcessValue(val);
});
} else if (val instanceof _index2.EmptyValue) {
this.preProcessValue(val);
} else if (_ResidualHeapInspector.ResidualHeapInspector.isLeaf(val)) {
this.preProcessValue(val);
} else if ((0, _index.IsArray)(this.realm, val)) {
(0, _invariant2.default)(val instanceof _index2.ObjectValue);
if (this.preProcessValue(val)) this.visitValueArray(val);
} else if (val instanceof _index2.ProxyValue) {
if (this.preProcessValue(val)) this.visitValueProxy(val);
} else if (val instanceof _index2.FunctionValue) {
// Function declarations should get hoisted in common scope so that instances only get allocated once
let parentScope = this.scope;
// Every function references itself through arguments, prevent the recursive double-visit
if (this.scope !== val && this.commonScope !== val) this._withScope(this.commonScope, () => {
(0, _invariant2.default)(val instanceof _index2.FunctionValue);
if (this.preProcessValue(val)) this.visitValueFunction(val, parentScope);
});
} else if (val instanceof _index2.SymbolValue) {
if (this.preProcessValue(val)) this.visitValueSymbol(val);
} else {
(0, _invariant2.default)(val instanceof _index2.ObjectValue);
// Prototypes are reachable via function declarations, and those get hoisted, so we need to move
// prototype initialization to the common scope code as well.
if (val.originalConstructor !== undefined) {
this._withScope(this.commonScope, () => {
(0, _invariant2.default)(val instanceof _index2.ObjectValue);
if (this.preProcessValue(val)) this.visitValueObject(val);
});
} else {
if (this.preProcessValue(val)) this.visitValueObject(val);
}
}
this.postProcessValue(val);
}
createGeneratorVisitCallbacks(commonScope) {
return {
visitValues: values => {
for (let i = 0, n = values.length; i < n; i++) values[i] = this.visitEquivalentValue(values[i]);
},
visitGenerator: (generator, parent) => {
// TODO: The serializer assumes that each generator has a unique parent; however, in the presence of conditional exceptions that is not actually true.
// invariant(!this.generatorParents.has(generator));
this.generatorParents.set(generator, parent);
this.visitGenerator(generator);
},
canSkip: value => {
return !this.referencedDeclaredValues.has(value) && !this.values.has(value);
},
recordDeclaration: value => {
this.referencedDeclaredValues.set(value, this.containingAdditionalFunction);
},
recordDelayedEntry: (generator, entry) => {
this.delayedVisitGeneratorEntries.push({ commonScope, generator, entry });
}
};
}
visitGenerator(generator) {
this._withScope(generator, () => {
generator.visit(this.createGeneratorVisitCallbacks(this.commonScope));
});
}
// 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
_visitEffects(additionalFunctionInfo, additionalEffects) {
let { effects, joinedEffects, returnArguments, returnBuildNode } = additionalEffects;
let functionValue = additionalFunctionInfo.functionValue;
(0, _invariant2.default)(functionValue instanceof _index2.ECMAScriptSourceFunctionValue);
let code = functionValue.$ECMAScriptCode;
let functionInfo = this.functionInfos.get(code);
(0, _invariant2.default)(functionInfo !== undefined);
let [result,, modifiedBindings, modifiedProperties, createdObjects] = effects;
for (let propertyBinding of modifiedProperties.keys()) {
let object = propertyBinding.object;
if (object instanceof _index2.ObjectValue && createdObjects.has(object)) continue; // Created Object's binding
if (object.refuseSerialization) continue; // modification to internal state
if (object.intrinsicName === "global") continue; // Avoid double-counting
this.visitObjectProperty(propertyBinding);
}
// Handling of ModifiedBindings
for (let [additionalBinding, previousValue] of modifiedBindings) {
let modifiedBinding = additionalBinding;
// TODO: Instead of looking at the environment ids, keep instead track of a createdEnvironmentRecords set,
// and only consider bindings here from environment records that already existed, or even better,
// ensure upstream that only such bindings are ever added to the modified-bindings set.
if (modifiedBinding.environment.id >= this.environmentRecordIdAfterGlobalCode) continue;
let residualBinding;
this._withScope(functionValue, () => {
// Also visit the original value of the binding
residualBinding = this.visitBinding(functionValue, modifiedBinding.environment, modifiedBinding.name);
(0, _invariant2.default)(residualBinding !== undefined);
// named functions inside an additional function that have a global binding
// can be skipped, as we don't want them to bind to the global
if (residualBinding.declarativeEnvironmentRecord === null && modifiedBinding.value instanceof _index2.ECMAScriptSourceFunctionValue) {
residualBinding = null;
return;
}
// Fixup the binding to have the correct value
// No previousValue means this is a binding for a nested function
if (previousValue && previousValue.value) residualBinding.value = this.visitEquivalentValue(previousValue.value);
(0, _invariant2.default)(functionInfo !== undefined);
if (functionInfo.modified.has(modifiedBinding.name)) residualBinding.modified;
});
if (residualBinding === null) continue;
(0, _invariant2.default)(residualBinding);
let funcInstance = additionalFunctionInfo.instance;
(0, _invariant2.default)(funcInstance !== undefined);
funcInstance.residualFunctionBindings.set(modifiedBinding.name, residualBinding);
let newValue = modifiedBinding.value;
(0, _invariant2.default)(newValue);
this.visitValue(newValue);
residualBinding.modified = true;
// This should be enforced by checkThatFunctionsAreIndependent
(0, _invariant2.default)(!residualBinding.additionalFunctionOverridesValue, "We should only have one additional function value modifying any given residual binding");
if (previousValue && previousValue.value) residualBinding.additionalFunctionOverridesValue = functionValue;
additionalFunctionInfo.modifiedBindings.set(modifiedBinding, residualBinding);
}
if (!(result instanceof _index2.UndefinedValue) && result instanceof _index2.Value) this.visitValue(result);else if (result instanceof _completions.PossiblyNormalCompletion) {
(0, _invariant2.default)(joinedEffects !== undefined);
this.realm.withEffectsAppliedInGlobalEnv(() => {
(0, _invariant2.default)(returnArguments !== undefined);
(0, _invariant2.default)(returnBuildNode !== undefined);
returnArguments.forEach(arg => this.visitValue(arg));
return null;
}, joinedEffects);
}
}
_visitAdditionalFunction(functionValue, additionalEffects, parentScope) {
// Get Instance + Info
(0, _invariant2.default)(functionValue instanceof _index2.ECMAScriptSourceFunctionValue);
let code = functionValue.$ECMAScriptCode;
let functionInfo = this.functionInfos.get(code);
(0, _invariant2.default)(functionInfo !== undefined);
let funcInstance = this.functionInstances.get(functionValue);
(0, _invariant2.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 prevCommonScope = this.commonScope;
this.commonScope = functionValue;
let oldEquivalenceSet = this.equivalenceSet;
this.equivalenceSet = new _index.HashSet();
let oldReactElementEquivalenceSet = this.reactElementEquivalenceSet;
this.reactElementEquivalenceSet = new _ReactElementSet2.default(this.realm, this.equivalenceSet);
let oldcontainingAdditionalFunction = this.containingAdditionalFunction;
this.containingAdditionalFunction = functionValue;
let prevReVisit = this.additionalRoots;
this.additionalRoots = new Map();
let modifiedBindingInfo = new Map();
let [result, generator,,, createdObjects] = additionalEffects.effects;
(0, _invariant2.default)(funcInstance !== undefined);
(0, _invariant2.default)(functionInfo !== undefined);
let additionalFunctionInfo = {
functionValue,
captures: functionInfo.unbound,
modifiedBindings: modifiedBindingInfo,
instance: funcInstance,
hasReturn: !(result instanceof _index2.UndefinedValue)
};
this.additionalFunctionValueInfos.set(functionValue, additionalFunctionInfo);
this.realm.withEffectsAppliedInGlobalEnv(effects => {
this.visitGenerator(generator);
// All modified properties and bindings should be accessible
// from its containing additional function scope.
this._withScope(functionValue, this._visitEffects.bind(this, additionalFunctionInfo, additionalEffects));
return this.realm.intrinsics.undefined;
}, additionalEffects.effects);
for (let createdObject of createdObjects) this.additionalRoots.delete(createdObject);
if (additionalEffects.joinedEffects) for (let createdObject of additionalEffects.joinedEffects[4]) this.additionalRoots.delete(createdObject);
// Cleanup
this.commonScope = prevCommonScope;
this.reactElementEquivalenceSet = oldReactElementEquivalenceSet;
this.equivalenceSet = oldEquivalenceSet;
this._withScope(parentScope,
// Re-visit any bindings corresponding to unbound values or values closed over from outside additional function
// they're serialized in the correct scope
() => {
(0, _invariant2.default)(functionInfo !== undefined);
(0, _invariant2.default)(funcInstance !== undefined);
for (let [value, additionalParentGenerators] of this.additionalRoots) {
// Populate old additionalRoots because we switched them out
prevReVisit.set(value, additionalParentGenerators);
this.visitValue(value);
}
for (let innerName of functionInfo.unbound) {
let environment = this.resolveBinding(functionValue, innerName);
let residualBinding = this.visitBinding(functionValue, environment, innerName, false);
if (residualBinding) {
funcInstance.residualFunctionBindings.set(innerName, residualBinding);
delete residualBinding.referencedOnlyFromAdditionalFunctions;
}
}
this.additionalRoots = prevReVisit;
});
this.containingAdditionalFunction = oldcontainingAdditionalFunction;
}
visitRoots() {
let generator = this.realm.generator;
(0, _invariant2.default)(generator);
this.visitGenerator(generator);
for (let moduleValue of this.modules.initializedModules.values()) this.visitValue(moduleValue);
if (this.realm.react.enabled && this.someReactElement !== undefined) {
this._visitReactLibrary(this.someReactElement);
}
// 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 oldDelayedEntries = [];
while (oldDelayedEntries.length !== this.delayedVisitGeneratorEntries.length) {
oldDelayedEntries = this.delayedVisitGeneratorEntries;
this.delayedVisitGeneratorEntries = [];
for (let _ref of oldDelayedEntries) {
let { commonScope, generator: entryGenerator, entry } = _ref;
this.commonScope = commonScope;
this._withScope(entryGenerator, () => {
entryGenerator.visitEntry(entry, this.createGeneratorVisitCallbacks(commonScope));
});
}
}
let referentializer = this.referentializer;
if (referentializer !== undefined) {
let bodyToInstances = new Map();
for (let instance of this.functionInstances.values()) {
// TODO: do something for additional functions
if (!this.additionalFunctionValuesAndEffects.has(instance.functionValue)) {
let code = instance.functionValue.$ECMAScriptCode;
(0, _invariant2.default)(code !== undefined);
(0, _utils.getOrDefault)(bodyToInstances, code, () => []).push(instance);
}
}
for (let [funcBody, instances] of bodyToInstances) {
let functionInfo = this.functionInfos.get(funcBody);
(0, _invariant2.default)(functionInfo !== undefined);
referentializer.referentialize(functionInfo.unbound, instances, instance => !this.additionalFunctionValuesAndEffects.has(instance.functionValue));
}
}
}
_visitReactLibrary(someReactElement) {
// find and visit the React library
let reactLibraryObject = this.realm.fbLibraries.react;
if (this.realm.react.output === "jsx") {
// React might not be defined in scope, i.e. another library is using JSX
// we don't throw an error as we should support JSX stand-alone
if (reactLibraryObject !== undefined) {
this.visitValue(reactLibraryObject);
}
} else if (this.realm.react.output === "create-element") {
let logError = () => {
this.logger.logError(someReactElement, "unable to visit createElement due to React not being referenced in scope");
};
// createElement output needs React in scope
if (reactLibraryObject === undefined) {
logError();
} else {
(0, _invariant2.default)(reactLibraryObject instanceof _index2.ObjectValue);
let createElement = reactLibraryObject.properties.get("createElement");
if (createElement === undefined || createElement.descriptor === undefined) {
logError();
} else {
let reactCreateElement = (0, _index.Get)(this.realm, reactLibraryObject, "createElement");
this.visitValue(reactCreateElement);
}
}
}
}
}
exports.ResidualHeapVisitor = ResidualHeapVisitor;
//# sourceMappingURL=ResidualHeapVisitor.js.map