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