UNPKG

prepack

Version:

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

577 lines (512 loc) 23.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.PreludeGenerator = exports.NameGenerator = exports.Generator = undefined; var _index = require("../values/index.js"); var _errors = require("../errors.js"); var _index2 = require("../methods/index.js"); var _index3 = require("../domains/index.js"); var _babelTypes = require("babel-types"); var t = _interopRequireWildcard(_babelTypes); var _invariant = require("../invariant.js"); var _invariant2 = _interopRequireDefault(_invariant); var _completions = require("../completions.js"); var _internalizer = require("./internalizer.js"); var _singletons = require("../singletons.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)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } function serializeBody(generator, context) { let statements = context.serializeGenerator(generator); if (statements.length === 1 && statements[0].type === "BlockStatement") return statements[0]; return t.blockStatement(statements); } /** * 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 Generator { constructor(realm, name) { (0, _invariant2.default)(realm.useAbstractInterpretation); let realmPreludeGenerator = realm.preludeGenerator; (0, _invariant2.default)(realmPreludeGenerator); this.preludeGenerator = realmPreludeGenerator; this.realm = realm; this._entries = []; this.id = realm.nextGeneratorId++; this._name = name; } getName() { return `${this._name}(#${this.id})`; } getAsPropertyNameExpression(key, canBeIdentifier = true) { // If key is a non-negative numeric string literal, parse it and set it as a numeric index instead. let index = Number.parseInt(key, 10); if (index >= 0 && index.toString() === key) { return t.numericLiteral(index); } if (canBeIdentifier) { // TODO #1020: revert this when Unicode identifiers are supported by all targetted JavaScript engines let keyIsAscii = /^[\u0000-\u007f]*$/.test(key); if (t.isValidIdentifier(key) && keyIsAscii) return t.identifier(key); } return t.stringLiteral(key); } empty() { return this._entries.length === 0; } emitGlobalDeclaration(key, value) { this.preludeGenerator.declaredGlobals.add(key); if (!(value instanceof _index.UndefinedValue)) this.emitGlobalAssignment(key, value, true); } emitGlobalAssignment(key, value, strictMode) { this._addEntry({ args: [value], buildNode: ([valueNode]) => t.expressionStatement(t.assignmentExpression("=", this.preludeGenerator.globalReference(key, !strictMode), valueNode)) }); } emitConcreteModel(key, value) { this._addEntry({ args: [(0, _singletons.concretize)(this.realm, value)], buildNode: ([valueNode]) => t.expressionStatement(t.assignmentExpression("=", this.preludeGenerator.globalReference(key, false), valueNode)) }); } emitGlobalDelete(key, strictMode) { this._addEntry({ args: [], buildNode: ([]) => t.expressionStatement(t.unaryExpression("delete", this.preludeGenerator.globalReference(key, !strictMode))) }); } emitBindingAssignment(binding, value) { this._addEntry({ args: [value], buildNode: ([valueNode], context) => t.expressionStatement(t.assignmentExpression("=", context.serializeBinding(binding), valueNode)) }); } emitPropertyAssignment(object, key, value) { if (object.refuseSerialization) return; let propName = this.getAsPropertyNameExpression(key); this._addEntry({ args: [object, value], buildNode: ([objectNode, valueNode]) => t.expressionStatement(t.assignmentExpression("=", t.memberExpression(objectNode, propName, !t.isIdentifier(propName)), valueNode)) }); } emitDefineProperty(object, key, desc, isDescChanged = true) { if (object.refuseSerialization) return; if (desc.enumerable && desc.configurable && desc.writable && desc.value && !isDescChanged) { let descValue = desc.value; (0, _invariant2.default)(descValue instanceof _index.Value); this.emitPropertyAssignment(object, key, descValue); } else { desc = Object.assign({}, desc); let descValue = desc.value || object.$Realm.intrinsics.undefined; (0, _invariant2.default)(descValue instanceof _index.Value); this._addEntry({ args: [object, descValue, desc.get || object.$Realm.intrinsics.undefined, desc.set || object.$Realm.intrinsics.undefined], buildNode: (_, context) => context.emitDefinePropertyBody(object, key, desc) }); } } emitPropertyDelete(object, key) { if (object.refuseSerialization) return; let propName = this.getAsPropertyNameExpression(key); this._addEntry({ args: [object], buildNode: ([objectNode]) => t.expressionStatement(t.unaryExpression("delete", t.memberExpression(objectNode, propName, !t.isIdentifier(propName)))) }); } emitCall(createCallee, args) { this._addEntry({ args, buildNode: values => t.expressionStatement(t.callExpression(createCallee(), [...values])) }); } emitConsoleLog(method, args) { this.emitCall(() => t.memberExpression(t.identifier("console"), t.identifier(method)), args.map(v => typeof v === "string" ? new _index.StringValue(this.realm, v) : v)); } // test must be a temporal value, which means that it must have a defined intrinsicName emitDoWhileStatement(test, body) { this._addEntry({ args: [], buildNode: function ([], context) { let testId = test.intrinsicName; (0, _invariant2.default)(testId !== undefined); let statements = context.serializeGenerator(body); let block = t.blockStatement(statements); return t.doWhileStatement(t.identifier(testId), block); }, dependencies: [body] }); } emitConditionalThrow(condition, trueBranch, falseBranch) { let [args, buildfunc] = this._deconstruct(condition, trueBranch, falseBranch, completion => { this._issueThrowCompilerDiagnostic(completion.value); let serializationArgs = [completion.value]; let func = ([arg]) => t.throwStatement(arg); return [serializationArgs, func]; }, () => [[], () => t.emptyStatement()]); this.emitStatement(args, buildfunc); } getThrowOrReturn(condition, trueBranch, falseBranch) { let [args, buildfunc] = this._deconstruct(condition, trueBranch, falseBranch, completion => { return [[completion.value], ([arg]) => t.throwStatement(arg)]; }, value => [[value], ([returnValue]) => t.returnStatement(returnValue)]); return [args, buildfunc]; } _deconstruct(condition, trueBranch, falseBranch, onThrowCompletion, onNormalValue) { let targs; let tfunc; let fargs; let ffunc; if (trueBranch instanceof _completions.JoinedAbruptCompletions) { [targs, tfunc] = this._deconstruct(trueBranch.joinCondition, trueBranch.consequent, trueBranch.alternate, onThrowCompletion, onNormalValue); } else if (trueBranch instanceof _completions.ThrowCompletion) { [targs, tfunc] = onThrowCompletion(trueBranch); } else { let value = trueBranch instanceof _completions.ReturnCompletion ? trueBranch.value : trueBranch; (0, _invariant2.default)(value instanceof _index.Value); [targs, tfunc] = onNormalValue(value); } if (falseBranch instanceof _completions.JoinedAbruptCompletions) { [fargs, ffunc] = this._deconstruct(falseBranch.joinCondition, falseBranch.consequent, falseBranch.alternate, onThrowCompletion, onNormalValue); } else if (falseBranch instanceof _completions.ThrowCompletion) { [fargs, ffunc] = onThrowCompletion(falseBranch); } else { (0, _invariant2.default)(falseBranch instanceof _index.Value); [fargs, ffunc] = onNormalValue(falseBranch); } let args = [condition].concat(targs).concat(fargs); let func = nodes => { return t.ifStatement(nodes[0], tfunc(nodes.slice().splice(1, targs.length)), ffunc(nodes.slice().splice(targs.length + 1, fargs.length))); }; return [args, func]; } _issueThrowCompilerDiagnostic(value) { let message = "Program may terminate with exception"; if (value instanceof _index.ObjectValue) { let object = value; let objectMessage = this.realm.evaluateWithUndo(() => object.$Get("message", value)); if (objectMessage instanceof _index.StringValue) message += `: ${objectMessage.value}`; const objectStack = this.realm.evaluateWithUndo(() => object.$Get("stack", value)); if (objectStack instanceof _index.StringValue) message += ` ${objectStack.value}`; } const diagnostic = new _errors.CompilerDiagnostic(message, value.expressionLocation, "PP1023", "Warning"); this.realm.handleError(diagnostic); } emitThrow(value) { this._issueThrowCompilerDiagnostic(value); this.emitStatement([value], ([argument]) => t.throwStatement(argument)); } // Checks the full set of possible concrete values as well as typeof // for any AbstractValues // e.g: (obj.property !== undefined && typeof obj.property !== "object") // NB: if the type of the AbstractValue is top, skips the invariant emitFullInvariant(object, key, value) { let propertyIdentifier = this.getAsPropertyNameExpression(key); let computed = !t.isIdentifier(propertyIdentifier); let accessedPropertyOf = objectNode => t.memberExpression(objectNode, propertyIdentifier, computed); let condition; if (value instanceof _index.AbstractValue) { let isTop = false; let concreteComparisons = []; let typeComparisons = new Set(); function populateComparisonsLists(absValue) { if (absValue.kind === "abstractConcreteUnion") { // recurse for (let nestedValue of absValue.args) if (nestedValue instanceof _index.ConcreteValue) { concreteComparisons.push(nestedValue); } else { (0, _invariant2.default)(nestedValue instanceof _index.AbstractValue); populateComparisonsLists(nestedValue); } } else if (absValue.getType().isTop || absValue.getType() === _index.Value) { isTop = true; } else { typeComparisons.add(absValue.getType()); } } populateComparisonsLists(value); // No point in doing the invariant if we don't know the type // of one of the nested abstract values if (isTop) { return; } else { condition = ([valueNode]) => { // Create `object.property !== concreteValue` let checks = concreteComparisons.map(concreteValue => t.binaryExpression("!==", valueNode, t.valueToNode(concreteValue.serialize()))); // Create `typeof object.property !== typeValue` checks = checks.concat([...typeComparisons].map(typeValue => { let typeString = _singletons.Utils.typeToString(typeValue); (0, _invariant2.default)(typeString !== undefined, typeValue); return t.binaryExpression("!==", t.unaryExpression("typeof", valueNode, true), t.stringLiteral(typeString)); })); return checks.reduce((expr, newCondition) => t.logicalExpression("&&", expr, newCondition)); }; this.emitInvariant([value, value], condition, valueNode => valueNode); } } else { condition = ([objectNode, valueNode]) => t.binaryExpression("!==", accessedPropertyOf(objectNode), valueNode); this.emitInvariant([object, value, object], condition, objnode => accessedPropertyOf(objnode)); } } emitInvariant(args, violationConditionFn, appendLastToInvariantFn) { if (this.realm.omitInvariants) return; this._addEntry({ args, buildNode: nodes => { let throwString = t.stringLiteral("Prepack model invariant violation"); if (appendLastToInvariantFn) { let last = nodes.pop(); throwString = t.binaryExpression("+", t.stringLiteral("Prepack model invariant violation: "), appendLastToInvariantFn(last)); } let condition = violationConditionFn(nodes); let throwblock = t.blockStatement([t.throwStatement(t.newExpression(t.identifier("Error"), [throwString]))]); return t.ifStatement(condition, throwblock); } }); } emitCallAndCaptureResult(types, values, createCallee, args, kind) { return this.derive(types, values, args, nodes => t.callExpression(createCallee(), nodes)); } emitStatement(args, buildNode_) { this._addEntry({ args, buildNode: buildNode_ }); } emitVoidExpression(types, values, args, buildNode_) { this._addEntry({ args, buildNode: nodes => t.expressionStatement(buildNode_ instanceof Function ? buildNode_(nodes) : buildNode_) }); return this.realm.intrinsics.undefined; } emitForInStatement(o, lh, sourceObject, targetObject, boundName) { this._addEntry({ // duplicate args to ensure refcount > 1 args: [o, targetObject, sourceObject, targetObject, sourceObject], buildNode: ([obj, tgt, src, obj1, tgt1, src1]) => { return t.forInStatement(lh, obj, t.blockStatement([t.expressionStatement(t.assignmentExpression("=", t.memberExpression(tgt, boundName, true), t.memberExpression(src, boundName, true)))])); } }); } derive(types, values, args, buildNode_, optionalArgs) { (0, _invariant2.default)(buildNode_ instanceof Function || args.length === 0); let id = t.identifier(this.preludeGenerator.nameGenerator.generate("derived")); this.preludeGenerator.derivedIds.set(id.name, args); let options = {}; if (optionalArgs && optionalArgs.kind) options.kind = optionalArgs.kind; let Constructor = _index.Value.isTypeCompatibleWith(types.getType(), _index.ObjectValue) ? _index.AbstractObjectValue : _index.AbstractValue; let res = new Constructor(this.realm, types, values, (0, _index2.hashString)(id.name), [], id, options); this._addEntry({ isPure: optionalArgs ? optionalArgs.isPure : undefined, declared: res, args, buildNode: (nodes, context) => { return t.variableDeclaration("var", [t.variableDeclarator(id, buildNode_ instanceof Function ? buildNode_(nodes, context) : buildNode_)]); } }); let type = types.getType(); res.intrinsicName = id.name; if (optionalArgs && optionalArgs.skipInvariant) return res; let typeofString; if (type instanceof _index.FunctionValue) typeofString = "function";else if (type === _index.UndefinedValue) (0, _invariant2.default)(false);else if (type === _index.NullValue) (0, _invariant2.default)(false);else if (type === _index.StringValue) typeofString = "string";else if (type === _index.BooleanValue) typeofString = "boolean";else if (type === _index.NumberValue) typeofString = "number";else if (type === _index.IntegralValue) typeofString = "number";else if (type === _index.SymbolValue) typeofString = "symbol";else if (type === _index.ObjectValue) typeofString = "object"; if (typeofString !== undefined) { // Verify that the types are as expected, a failure of this invariant // should mean the model is wrong. this.emitInvariant([res, res], nodes => { (0, _invariant2.default)(typeofString !== undefined); let condition = t.binaryExpression("!==", t.unaryExpression("typeof", nodes[0]), t.stringLiteral(typeofString)); if (typeofString === "object") { condition = t.logicalExpression("&&", condition, t.binaryExpression("!==", t.unaryExpression("typeof", nodes[0]), t.stringLiteral("function"))); condition = t.logicalExpression("||", condition, t.binaryExpression("===", nodes[0], _internalizer.nullExpression)); } return condition; }, node => node); } return res; } serialize(context) { for (let entry of this._entries) { if (!entry.isPure || !entry.declared || !context.canOmit(entry.declared)) { let nodes = entry.args.map((boundArg, i) => context.serializeValue(boundArg)); if (entry.buildNode) { let node = entry.buildNode(nodes, context); if (node.type === "BlockStatement") { let block = node; let statements = block.body; if (statements.length === 0) continue; if (statements.length === 1) { node = statements[0]; } } context.emit(node); } if (entry.declared !== undefined) context.declare(entry.declared); } } } visitEntry(entry, callbacks) { if (entry.isPure && entry.declared && callbacks.canSkip(entry.declared)) { callbacks.recordDelayedEntry(this, entry); } else { if (entry.declared) callbacks.recordDeclaration(entry.declared); callbacks.visitValues(entry.args); if (entry.dependencies) for (let dependency of entry.dependencies) callbacks.visitGenerator(dependency, this); } } visit(callbacks) { for (let entry of this._entries) this.visitEntry(entry, callbacks); } _addEntry(entry) { this._entries.push(entry); } appendGenerator(other, leadingComment) { if (other.empty()) return; this._addEntry({ args: [], buildNode: function (args, context) { let statements = context.serializeGenerator(other); if (statements.length === 1) { let statement = statements[0]; if (leadingComment.length > 0) statement.leadingComments = [{ type: "BlockComment", value: leadingComment }]; return statement; } let block = t.blockStatement(statements); if (leadingComment.length > 0) block.leadingComments = [{ type: "BlockComment", value: leadingComment }]; return block; }, dependencies: [other] }); } composeGenerators(generator1, generator2) { this._addEntry({ args: [], buildNode: function ([], context) { let statements1 = generator1.empty() ? [] : context.serializeGenerator(generator1); let statements2 = generator2.empty() ? [] : context.serializeGenerator(generator2); let statements = statements1.concat(statements2); if (statements.length === 1) return statements[0]; return t.blockStatement(statements); }, dependencies: [generator1, generator2] }); } joinGenerators(joinCondition, generator1, generator2) { this._addEntry({ args: [joinCondition], buildNode: function ([cond], context) { let block1 = generator1.empty() ? null : serializeBody(generator1, context); let block2 = generator2.empty() ? null : serializeBody(generator2, context); if (block1) return t.ifStatement(cond, block1, block2); (0, _invariant2.default)(block2); return t.ifStatement(t.unaryExpression("!", cond), block2); }, dependencies: [generator1, generator2] }); } } exports.Generator = Generator; // some characters are invalid within a JavaScript identifier, // such as: . , : ( ) ' " ` [ ] - // so we replace these character instances with an underscore function replaceInvalidCharactersWithUnderscore(string) { return string.replace(/[.,:\(\)\"\'\`\[\]\-]/g, "_"); } const base62characters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; function base62encode(n) { (0, _invariant2.default)((n | 0) === n && n >= 0); if (n === 0) return "0"; let s = ""; while (n > 0) { let f = n % base62characters.length; s = base62characters[f] + s; n = (n - f) / base62characters.length; } return s; } class NameGenerator { constructor(forbiddenNames, debugNames, uniqueSuffix, prefix) { this.prefix = prefix; this.uidCounter = 0; this.debugNames = debugNames; this.forbiddenNames = forbiddenNames; this.uniqueSuffix = uniqueSuffix; } generate(debugSuffix) { let id; do { id = this.prefix + base62encode(this.uidCounter++); if (this.uniqueSuffix.length > 0) id += this.uniqueSuffix; if (this.debugNames) { if (debugSuffix) id += "_" + replaceInvalidCharactersWithUnderscore(debugSuffix);else id += "_"; } } while (this.forbiddenNames.has(id)); return id; } } exports.NameGenerator = NameGenerator; class PreludeGenerator { constructor(debugNames, uniqueSuffix) { this.prelude = []; this.derivedIds = new Map(); this.memoizedRefs = new Map(); this.nameGenerator = new NameGenerator(new Set(), !!debugNames, uniqueSuffix || "", "_$"); this.usesThis = false; this.declaredGlobals = new Set(); } createNameGenerator(prefix) { return new NameGenerator(this.nameGenerator.forbiddenNames, this.nameGenerator.debugNames, this.nameGenerator.uniqueSuffix, prefix); } convertStringToMember(str) { return str.split(".").map(name => { if (name === "global") { return this.memoizeReference(name); } else if (name === "this") { return t.thisExpression(); } else { return t.identifier(name); } }).reduce((obj, prop) => t.memberExpression(obj, prop)); } globalReference(key, globalScope = false) { if (globalScope && t.isValidIdentifier(key)) return t.identifier(key); let keyNode = t.isValidIdentifier(key) ? t.identifier(key) : t.stringLiteral(key); return t.memberExpression(this.memoizeReference("global"), keyNode, !t.isIdentifier(keyNode)); } memoizeReference(key) { let ref = this.memoizedRefs.get(key); if (ref) return ref; let init; if (key.includes("(") || key.includes("[")) { // Horrible but effective hack: // Some internal object have intrinsic names such as // ([][Symbol.iterator]().__proto__.__proto__) // and // RegExp.prototype[Symbol.match] // which get turned into a babel node here. // TODO: We should properly parse such a string, and memoize all references in it separately. // Instead, we just turn it into a funky identifier, which Babel seems to accept. init = t.identifier(key); } else if (key === "global") { this.usesThis = true; init = t.thisExpression(); } else { let i = key.lastIndexOf("."); if (i === -1) { init = t.memberExpression(this.memoizeReference("global"), t.identifier(key)); } else { init = t.memberExpression(this.memoizeReference(key.substr(0, i)), t.identifier(key.substr(i + 1))); } } ref = t.identifier(this.nameGenerator.generate(key)); this.prelude.push(t.variableDeclaration("var", [t.variableDeclarator(ref, init)])); this.memoizedRefs.set(key, ref); return ref; } } exports.PreludeGenerator = PreludeGenerator; //# sourceMappingURL=generator.js.map