UNPKG

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