UNPKG

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