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