UNPKG

prepack

Version:

Execute a JS bundle, serialize global state and side effects to a snapshot that can be quickly restored.

423 lines (335 loc) 16.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Modules = exports.ModuleTracer = void 0; var _environment = require("../environment.js"); var _errors = require("../errors.js"); var _realm = require("../realm.js"); var _index = require("../methods/index.js"); var _singletons = require("../singletons.js"); var _index2 = require("../values/index.js"); var t = _interopRequireWildcard(require("@babel/types")); var _invariant = _interopRequireDefault(require("../invariant.js")); var _logger = require("./logger.js"); var _statistics = require("../serializer/statistics.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. */ function downgradeErrorsToWarnings(realm, f) { let savedHandler = realm.errorHandler; function handler(e) { e.severity = "Warning"; realm.errorHandler = savedHandler; try { return realm.handleError(e); } finally { realm.errorHandler = handler; } } realm.errorHandler = handler; try { return f(); } finally { realm.errorHandler = savedHandler; } } class ModuleTracer extends _realm.Tracer { constructor(modules, logModules) { super(); this.modules = modules; this.evaluateForEffectsNesting = 0; this.requireStack = []; this.requireSequence = []; this.logModules = logModules; this.uninitializedModuleIdsRequiredInEvaluateForEffects = new Set(); } getStatistics() { return this.modules.getStatistics(); } log(message) { if (this.logModules) console.log(`[modules] ${this.requireStack.map(_ => " ").join("")}${message}`); } beginEvaluateForEffects(state) { if (state !== this) { this.log(">evaluate for effects"); this.evaluateForEffectsNesting++; this.requireStack.push(undefined); } } endEvaluateForEffects(state, effects) { if (state !== this) { let popped = this.requireStack.pop(); (0, _invariant.default)(popped === undefined); this.evaluateForEffectsNesting--; this.log("<evaluate for effects"); } } _callRequireAndRecord(moduleIdValue, performCall) { if (this.requireStack.length === 0 || this.requireStack[this.requireStack.length - 1] !== moduleIdValue) { this.requireStack.push(moduleIdValue); try { let value = performCall(); if (this.modules.moduleIds.has(moduleIdValue)) this.modules.recordModuleInitialized(moduleIdValue, value); return value; } finally { (0, _invariant.default)(this.requireStack.pop() === moduleIdValue); } } return undefined; } _tryExtractDependencies(value) { if (value === undefined || value instanceof _index2.NullValue || value instanceof _index2.UndefinedValue) return []; if (value instanceof _index2.ArrayValue) { const realm = this.modules.realm; const lengthValue = (0, _index.Get)(realm, value, "length"); if (lengthValue instanceof _index2.NumberValue) { const dependencies = []; const logger = this.modules.logger; for (let i = 0; i < lengthValue.value; i++) { const elementValue = logger.tryQuery(() => (0, _index.Get)(realm, value, "" + i), realm.intrinsics.undefined); dependencies.push(elementValue); } return dependencies; } } return undefined; } detourCall(F, thisArgument, argumentsList, newTarget, performCall) { let requireInfo = this.modules.getRequireInfo(); if (requireInfo !== undefined && F === requireInfo.value && argumentsList.length === 1) { // Here, we handle calls of the form // require(42) let moduleId = argumentsList[0]; let moduleIdValue; // Do some sanity checks and request require(...) calls with bad arguments if (moduleId instanceof _index2.NumberValue || moduleId instanceof _index2.StringValue) moduleIdValue = moduleId.value;else return performCall(); // call require(...); this might cause calls to the define function let res = this._callRequireAndRecord(moduleIdValue, performCall); if (F.$Realm.eagerlyRequireModuleDependencies) { // all dependencies of the required module should now be known let dependencies = this.modules.moduleDependencies.get(moduleIdValue); if (dependencies === undefined) this.modules.logger.logError(moduleId, `Cannot resolve module dependencies for ${moduleIdValue.toString()}.`);else for (let dependency of dependencies) { // We'll try to initialize module dependency on a best-effort basis, // ignoring any errors. Note that tryInitializeModule applies effects on success. if (dependency instanceof _index2.NumberValue || dependency instanceof _index2.StringValue) this.modules.tryInitializeModule(dependency.value, `Eager initialization of module ${dependency.value}`); } } return res; } else if (F === this.modules.getDefine()) { // Here, we handle calls of the form // __d(factoryFunction, moduleId, dependencyArray) let moduleId = argumentsList[1]; if (moduleId instanceof _index2.NumberValue || moduleId instanceof _index2.StringValue) { let moduleIdValue = moduleId.value; let factoryFunction = argumentsList[0]; if (factoryFunction instanceof _index2.FunctionValue) { let dependencies = this._tryExtractDependencies(argumentsList[2]); if (dependencies !== undefined) { this.modules.moduleDependencies.set(moduleIdValue, dependencies); this.modules.factoryFunctionDependencies.set(factoryFunction, dependencies); } else this.modules.logger.logError(argumentsList[2], "Third argument to define function is present but not a concrete array."); } else this.modules.logger.logError(factoryFunction, "First argument to define function is not a function value."); this.modules.moduleIds.add(moduleIdValue); } else this.modules.logger.logError(moduleId, "Second argument to define function is not a number or string value."); } return undefined; } } exports.ModuleTracer = ModuleTracer; class Modules { constructor(realm, logger, logModules) { this.realm = realm; this.logger = logger; this._define = realm.intrinsics.undefined; this.factoryFunctionDependencies = new Map(); this.moduleDependencies = new Map(); this.moduleIds = new Set(); this.initializedModules = new Map(); realm.tracers.push(this.moduleTracer = new ModuleTracer(this, logModules)); } getStatistics() { (0, _invariant.default)(this.realm.statistics instanceof _statistics.SerializerStatistics, "serialization requires SerializerStatistics"); return this.realm.statistics; } resolveInitializedModules() { let globalInitializedModulesMap = this._getGlobalProperty("__initializedModules"); (0, _invariant.default)(globalInitializedModulesMap instanceof _index2.ObjectValue); for (let moduleId of globalInitializedModulesMap.properties.keys()) { let property = globalInitializedModulesMap.properties.get(moduleId); (0, _invariant.default)(property); if (property.descriptor instanceof _descriptors.PropertyDescriptor) { let moduleValue = property.descriptor && property.descriptor.value; if (moduleValue instanceof _index2.Value && !moduleValue.mightHaveBeenDeleted()) { this.initializedModules.set(moduleId, moduleValue); } } } this.getStatistics().initializedModules = this.initializedModules.size; this.getStatistics().totalModules = this.moduleIds.size; } _getGlobalProperty(name) { if (this.active) return this.realm.intrinsics.undefined; this.active = true; try { let realm = this.realm; return this.logger.tryQuery(() => (0, _index.Get)(realm, realm.$GlobalObject, name), realm.intrinsics.undefined); } finally { this.active = false; } } getRequireInfo() { if (this._requireInfo === undefined) for (let globalName of ["require", "__r"]) { let value = this._getGlobalProperty(globalName); if (value instanceof _index2.FunctionValue) { this._requireInfo = { value, globalName }; break; } } return this._requireInfo; } getDefine() { if (!(this._define instanceof _index2.FunctionValue)) this._define = this._getGlobalProperty("__d"); return this._define; } // Returns a function that checks if a call node represents a call to a // known require function, and if so, what module id that call indicates. // A known require function call is either of the form // ... require(42) ... // where require resolves to the global require function, or // factoryFunction(, require, , , dependencies) { // ... // ... require(dependencies[3]) ... // where factoryFunction and dependencies were announced as part of the // global code execution via a global module declaration call such as // global.__d(factoryFunction, , [0,2,4,6,8]) getGetModuleIdIfNodeIsRequireFunction(formalParameters, functions) { let realm = this.realm; let logger = this.logger; let modules = this; return (scope, node) => { // Are we calling a function that has a single name and a single argument? if (!t.isIdentifier(node.callee) || node.arguments.length !== 1) return undefined; let argument = node.arguments[0]; if (!argument) return undefined; if (!t.isNumericLiteral(argument) && !t.isStringLiteral(argument) && !t.isMemberExpression(argument)) return undefined; (0, _invariant.default)(node.callee); let innerName = node.callee.name; let moduleId; // Helper function used to give up if we ever come up with different module ids for different functions let updateModuleId = newModuleId => { if (moduleId !== undefined && moduleId !== newModuleId) return false; moduleId = newModuleId; return true; }; // Helper function that retrieves module id from call argument, possibly chasing dependency array indirection const getModuleId = dependencies => { if (t.isMemberExpression(argument)) { if (dependencies !== undefined) { let memberExpression = argument; if (t.isIdentifier(memberExpression.object)) { let scopedBinding = scope.getBinding(memberExpression.object.name); if (scopedBinding && formalParameters[4] === scopedBinding.path.node) { if (t.isNumericLiteral(memberExpression.property)) { let dependencyIndex = memberExpression.property.value; if (Number.isInteger(dependencyIndex) && dependencyIndex >= 0 && dependencyIndex < dependencies.length) { let dependency = dependencies[dependencyIndex]; if (dependency instanceof _index2.NumberValue || dependency instanceof _index2.StringValue) return dependency.value; } } } } } } else { return argument.value; } }; // Let's consider each of the function instances (closures for the same code) for (let f of functions) { // 1. Let's check if we have a match for a factory function like // factoryFunction(, require, , , [dependencies]) // which is used with the Metro bundler let scopedBinding = scope.getBinding(innerName); if (scopedBinding) { let dependencies = modules.factoryFunctionDependencies.get(f); if (dependencies !== undefined && formalParameters[1] === scopedBinding.path.node) { (0, _invariant.default)(scopedBinding.kind === "param"); let newModuleId = getModuleId(dependencies); if (newModuleId !== undefined && !updateModuleId(newModuleId)) return undefined; continue; } // The name binds to some local entity, but nothing we'd know what exactly it is return undefined; } // 2. Let's check if we can resolve the called function just by looking at the // function instance environment. // TODO: We should not do this if the current node is in a nested function! // We won't have a dependency map here, so this only works for literal arguments. if (!t.isNumericLiteral(argument) && !t.isStringLiteral(argument)) return undefined; let doesNotMatter = true; let reference = logger.tryQuery(() => _singletons.Environment.ResolveBinding(realm, innerName, doesNotMatter, f.$Environment), undefined); if (reference === undefined) { // We couldn't resolve as we came across some behavior that we cannot deal with abstractly return undefined; } if (_singletons.Environment.IsUnresolvableReference(realm, reference)) return undefined; let referencedBase = reference.base; let referencedName = reference.referencedName; if (typeof referencedName !== "string") return undefined; let value; if (reference.base instanceof _environment.GlobalEnvironmentRecord) { value = logger.tryQuery(() => (0, _index.Get)(realm, realm.$GlobalObject, innerName), realm.intrinsics.undefined); } else { (0, _invariant.default)(referencedBase instanceof _environment.DeclarativeEnvironmentRecord); let binding = referencedBase.bindings[referencedName]; if (!binding.initialized) return undefined; value = binding.value; } let requireInfo = modules.getRequireInfo(); if (requireInfo === undefined || value !== requireInfo.value) return undefined; const newModuleId = getModuleId(); (0, _invariant.default)(newModuleId !== undefined); if (!updateModuleId(newModuleId)) return undefined; } return moduleId; }; } recordModuleInitialized(moduleId, value) { this.realm.assignToGlobal(t.memberExpression(t.memberExpression(t.identifier("global"), t.identifier("__initializedModules")), t.identifier("" + moduleId)), value); } tryInitializeModule(moduleId, message) { let realm = this.realm; let requireInfo = this.getRequireInfo(); if (requireInfo === undefined) return undefined; return downgradeErrorsToWarnings(realm, () => { try { let node = t.callExpression(t.identifier(requireInfo.globalName), [t.valueToNode(moduleId)]); let effects = realm.evaluateNodeForEffectsInGlobalEnv(node); realm.applyEffects(effects, message); return effects; } catch (err) { if (err instanceof _errors.FatalError) return undefined;else throw err; } }); } initializeMoreModules(modulesToInitialize) { // partially evaluate all factory methods by calling require let count = 0; for (let moduleId of this.moduleIds) { if (modulesToInitialize !== "ALL" && !modulesToInitialize.has("" + moduleId)) continue; if (this.initializedModules.has(moduleId)) continue; let effects = this.tryInitializeModule(moduleId, `Speculative initialization of module ${moduleId}`); if (effects === undefined) continue; let result = effects.result; if (!(result instanceof _index2.Value)) continue; // module might throw count++; this.initializedModules.set(moduleId, result); } if (count > 0) console.log(`=== speculatively initialized ${count} additional modules`); } } exports.Modules = Modules; //# sourceMappingURL=modules.js.map