You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

348 lines
9.8 KiB
JavaScript

"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.evaluate = evaluate;
exports.evaluateTruthy = evaluateTruthy;
const VALID_OBJECT_CALLEES = ["Number", "String", "Math"];
const VALID_IDENTIFIER_CALLEES = ["isFinite", "isNaN", "parseFloat", "parseInt", "decodeURI", "decodeURIComponent", "encodeURI", "encodeURIComponent", null, null];
const INVALID_METHODS = ["random"];
function isValidObjectCallee(val) {
return VALID_OBJECT_CALLEES.includes(val);
}
function isValidIdentifierCallee(val) {
return VALID_IDENTIFIER_CALLEES.includes(val);
}
function isInvalidMethod(val) {
return INVALID_METHODS.includes(val);
}
function evaluateTruthy() {
const res = this.evaluate();
if (res.confident) return !!res.value;
}
function deopt(path, state) {
if (!state.confident) return;
state.deoptPath = path;
state.confident = false;
}
const Globals = new Map([["undefined", undefined], ["Infinity", Infinity], ["NaN", NaN]]);
function evaluateCached(path, state) {
const {
node
} = path;
const {
seen
} = state;
if (seen.has(node)) {
const existing = seen.get(node);
if (existing.resolved) {
return existing.value;
} else {
deopt(path, state);
return;
}
} else {
const item = {
resolved: false
};
seen.set(node, item);
const val = _evaluate(path, state);
if (state.confident) {
item.resolved = true;
item.value = val;
}
return val;
}
}
function _evaluate(path, state) {
if (!state.confident) return;
if (path.isSequenceExpression()) {
const exprs = path.get("expressions");
return evaluateCached(exprs[exprs.length - 1], state);
}
if (path.isStringLiteral() || path.isNumericLiteral() || path.isBooleanLiteral()) {
return path.node.value;
}
if (path.isNullLiteral()) {
return null;
}
if (path.isTemplateLiteral()) {
return evaluateQuasis(path, path.node.quasis, state);
}
if (path.isTaggedTemplateExpression() && path.get("tag").isMemberExpression()) {
const object = path.get("tag.object");
const {
node: {
name
}
} = object;
const property = path.get("tag.property");
if (object.isIdentifier() && name === "String" && !path.scope.getBinding(name) && property.isIdentifier() && property.node.name === "raw") {
return evaluateQuasis(path, path.node.quasi.quasis, state, true);
}
}
if (path.isConditionalExpression()) {
const testResult = evaluateCached(path.get("test"), state);
if (!state.confident) return;
if (testResult) {
return evaluateCached(path.get("consequent"), state);
} else {
return evaluateCached(path.get("alternate"), state);
}
}
if (path.isExpressionWrapper()) {
return evaluateCached(path.get("expression"), state);
}
if (path.isMemberExpression() && !path.parentPath.isCallExpression({
callee: path.node
})) {
const property = path.get("property");
const object = path.get("object");
if (object.isLiteral()) {
const value = object.node.value;
const type = typeof value;
let key = null;
if (path.node.computed) {
key = evaluateCached(property, state);
if (!state.confident) return;
} else if (property.isIdentifier()) {
key = property.node.name;
}
if ((type === "number" || type === "string") && key != null && (typeof key === "number" || typeof key === "string")) {
return value[key];
}
}
}
if (path.isReferencedIdentifier()) {
const binding = path.scope.getBinding(path.node.name);
if (binding) {
if (binding.constantViolations.length > 0 || path.node.start < binding.path.node.end) {
deopt(binding.path, state);
return;
}
if (binding.hasValue) {
return binding.value;
}
}
const name = path.node.name;
if (Globals.has(name)) {
if (!binding) {
return Globals.get(name);
}
deopt(binding.path, state);
return;
}
const resolved = path.resolve();
if (resolved === path) {
deopt(path, state);
return;
} else {
return evaluateCached(resolved, state);
}
}
if (path.isUnaryExpression({
prefix: true
})) {
if (path.node.operator === "void") {
return undefined;
}
const argument = path.get("argument");
if (path.node.operator === "typeof" && (argument.isFunction() || argument.isClass())) {
return "function";
}
const arg = evaluateCached(argument, state);
if (!state.confident) return;
switch (path.node.operator) {
case "!":
return !arg;
case "+":
return +arg;
case "-":
return -arg;
case "~":
return ~arg;
case "typeof":
return typeof arg;
}
}
if (path.isArrayExpression()) {
const arr = [];
const elems = path.get("elements");
for (const elem of elems) {
const elemValue = elem.evaluate();
if (elemValue.confident) {
arr.push(elemValue.value);
} else {
deopt(elemValue.deopt, state);
return;
}
}
return arr;
}
if (path.isObjectExpression()) {
const obj = {};
const props = path.get("properties");
for (const prop of props) {
if (prop.isObjectMethod() || prop.isSpreadElement()) {
deopt(prop, state);
return;
}
const keyPath = prop.get("key");
let key;
if (prop.node.computed) {
key = keyPath.evaluate();
if (!key.confident) {
deopt(key.deopt, state);
return;
}
key = key.value;
} else if (keyPath.isIdentifier()) {
key = keyPath.node.name;
} else {
key = keyPath.node.value;
}
const valuePath = prop.get("value");
let value = valuePath.evaluate();
if (!value.confident) {
deopt(value.deopt, state);
return;
}
value = value.value;
obj[key] = value;
}
return obj;
}
if (path.isLogicalExpression()) {
const wasConfident = state.confident;
const left = evaluateCached(path.get("left"), state);
const leftConfident = state.confident;
state.confident = wasConfident;
const right = evaluateCached(path.get("right"), state);
const rightConfident = state.confident;
switch (path.node.operator) {
case "||":
state.confident = leftConfident && (!!left || rightConfident);
if (!state.confident) return;
return left || right;
case "&&":
state.confident = leftConfident && (!left || rightConfident);
if (!state.confident) return;
return left && right;
case "??":
state.confident = leftConfident && (left != null || rightConfident);
if (!state.confident) return;
return left != null ? left : right;
}
}
if (path.isBinaryExpression()) {
const left = evaluateCached(path.get("left"), state);
if (!state.confident) return;
const right = evaluateCached(path.get("right"), state);
if (!state.confident) return;
switch (path.node.operator) {
case "-":
return left - right;
case "+":
return left + right;
case "/":
return left / right;
case "*":
return left * right;
case "%":
return left % right;
case "**":
return Math.pow(left, right);
case "<":
return left < right;
case ">":
return left > right;
case "<=":
return left <= right;
case ">=":
return left >= right;
case "==":
return left == right;
case "!=":
return left != right;
case "===":
return left === right;
case "!==":
return left !== right;
case "|":
return left | right;
case "&":
return left & right;
case "^":
return left ^ right;
case "<<":
return left << right;
case ">>":
return left >> right;
case ">>>":
return left >>> right;
}
}
if (path.isCallExpression()) {
const callee = path.get("callee");
let context;
let func;
if (callee.isIdentifier() && !path.scope.getBinding(callee.node.name) && (isValidObjectCallee(callee.node.name) || isValidIdentifierCallee(callee.node.name))) {
func = global[callee.node.name];
}
if (callee.isMemberExpression()) {
const object = callee.get("object");
const property = callee.get("property");
if (object.isIdentifier() && property.isIdentifier() && isValidObjectCallee(object.node.name) && !isInvalidMethod(property.node.name)) {
context = global[object.node.name];
const key = property.node.name;
if (Object.hasOwnProperty.call(context, key)) {
func = context[key];
}
}
if (object.isLiteral() && property.isIdentifier()) {
const type = typeof object.node.value;
if (type === "string" || type === "number") {
context = object.node.value;
func = context[property.node.name];
}
}
}
if (func) {
const args = path.get("arguments").map(arg => evaluateCached(arg, state));
if (!state.confident) return;
return func.apply(context, args);
}
}
deopt(path, state);
}
function evaluateQuasis(path, quasis, state, raw = false) {
let str = "";
let i = 0;
const exprs = path.isTemplateLiteral() ? path.get("expressions") : path.get("quasi.expressions");
for (const elem of quasis) {
if (!state.confident) break;
str += raw ? elem.value.raw : elem.value.cooked;
const expr = exprs[i++];
if (expr) str += String(evaluateCached(expr, state));
}
if (!state.confident) return;
return str;
}
function evaluate() {
const state = {
confident: true,
deoptPath: null,
seen: new Map()
};
let value = evaluateCached(this, state);
if (!state.confident) value = undefined;
return {
confident: state.confident,
deopt: state.deoptPath,
value: value
};
}
//# sourceMappingURL=evaluation.js.map