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

403 lines
8.9 KiB

4 years ago
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.evaluateTruthy = evaluateTruthy;
  6. exports.evaluate = evaluate;
  7. const VALID_CALLEES = ["String", "Number", "Math"];
  8. const INVALID_METHODS = ["random"];
  9. function evaluateTruthy() {
  10. const res = this.evaluate();
  11. if (res.confident) return !!res.value;
  12. }
  13. function deopt(path, state) {
  14. if (!state.confident) return;
  15. state.deoptPath = path;
  16. state.confident = false;
  17. }
  18. function evaluateCached(path, state) {
  19. const {
  20. node
  21. } = path;
  22. const {
  23. seen
  24. } = state;
  25. if (seen.has(node)) {
  26. const existing = seen.get(node);
  27. if (existing.resolved) {
  28. return existing.value;
  29. } else {
  30. deopt(path, state);
  31. return;
  32. }
  33. } else {
  34. const item = {
  35. resolved: false
  36. };
  37. seen.set(node, item);
  38. const val = _evaluate(path, state);
  39. if (state.confident) {
  40. item.resolved = true;
  41. item.value = val;
  42. }
  43. return val;
  44. }
  45. }
  46. function _evaluate(path, state) {
  47. if (!state.confident) return;
  48. const {
  49. node
  50. } = path;
  51. if (path.isSequenceExpression()) {
  52. const exprs = path.get("expressions");
  53. return evaluateCached(exprs[exprs.length - 1], state);
  54. }
  55. if (path.isStringLiteral() || path.isNumericLiteral() || path.isBooleanLiteral()) {
  56. return node.value;
  57. }
  58. if (path.isNullLiteral()) {
  59. return null;
  60. }
  61. if (path.isTemplateLiteral()) {
  62. return evaluateQuasis(path, node.quasis, state);
  63. }
  64. if (path.isTaggedTemplateExpression() && path.get("tag").isMemberExpression()) {
  65. const object = path.get("tag.object");
  66. const {
  67. node: {
  68. name
  69. }
  70. } = object;
  71. const property = path.get("tag.property");
  72. if (object.isIdentifier() && name === "String" && !path.scope.getBinding(name, true) && property.isIdentifier && property.node.name === "raw") {
  73. return evaluateQuasis(path, node.quasi.quasis, state, true);
  74. }
  75. }
  76. if (path.isConditionalExpression()) {
  77. const testResult = evaluateCached(path.get("test"), state);
  78. if (!state.confident) return;
  79. if (testResult) {
  80. return evaluateCached(path.get("consequent"), state);
  81. } else {
  82. return evaluateCached(path.get("alternate"), state);
  83. }
  84. }
  85. if (path.isExpressionWrapper()) {
  86. return evaluateCached(path.get("expression"), state);
  87. }
  88. if (path.isMemberExpression() && !path.parentPath.isCallExpression({
  89. callee: node
  90. })) {
  91. const property = path.get("property");
  92. const object = path.get("object");
  93. if (object.isLiteral() && property.isIdentifier()) {
  94. const value = object.node.value;
  95. const type = typeof value;
  96. if (type === "number" || type === "string") {
  97. return value[property.node.name];
  98. }
  99. }
  100. }
  101. if (path.isReferencedIdentifier()) {
  102. const binding = path.scope.getBinding(node.name);
  103. if (binding && binding.constantViolations.length > 0) {
  104. return deopt(binding.path, state);
  105. }
  106. if (binding && path.node.start < binding.path.node.end) {
  107. return deopt(binding.path, state);
  108. }
  109. if (binding && binding.hasValue) {
  110. return binding.value;
  111. } else {
  112. if (node.name === "undefined") {
  113. return binding ? deopt(binding.path, state) : undefined;
  114. } else if (node.name === "Infinity") {
  115. return binding ? deopt(binding.path, state) : Infinity;
  116. } else if (node.name === "NaN") {
  117. return binding ? deopt(binding.path, state) : NaN;
  118. }
  119. const resolved = path.resolve();
  120. if (resolved === path) {
  121. return deopt(path, state);
  122. } else {
  123. return evaluateCached(resolved, state);
  124. }
  125. }
  126. }
  127. if (path.isUnaryExpression({
  128. prefix: true
  129. })) {
  130. if (node.operator === "void") {
  131. return undefined;
  132. }
  133. const argument = path.get("argument");
  134. if (node.operator === "typeof" && (argument.isFunction() || argument.isClass())) {
  135. return "function";
  136. }
  137. const arg = evaluateCached(argument, state);
  138. if (!state.confident) return;
  139. switch (node.operator) {
  140. case "!":
  141. return !arg;
  142. case "+":
  143. return +arg;
  144. case "-":
  145. return -arg;
  146. case "~":
  147. return ~arg;
  148. case "typeof":
  149. return typeof arg;
  150. }
  151. }
  152. if (path.isArrayExpression()) {
  153. const arr = [];
  154. const elems = path.get("elements");
  155. for (const elem of elems) {
  156. const elemValue = elem.evaluate();
  157. if (elemValue.confident) {
  158. arr.push(elemValue.value);
  159. } else {
  160. return deopt(elem, state);
  161. }
  162. }
  163. return arr;
  164. }
  165. if (path.isObjectExpression()) {
  166. const obj = {};
  167. const props = path.get("properties");
  168. for (const prop of props) {
  169. if (prop.isObjectMethod() || prop.isSpreadElement()) {
  170. return deopt(prop, state);
  171. }
  172. const keyPath = prop.get("key");
  173. let key = keyPath;
  174. if (prop.node.computed) {
  175. key = key.evaluate();
  176. if (!key.confident) {
  177. return deopt(keyPath, state);
  178. }
  179. key = key.value;
  180. } else if (key.isIdentifier()) {
  181. key = key.node.name;
  182. } else {
  183. key = key.node.value;
  184. }
  185. const valuePath = prop.get("value");
  186. let value = valuePath.evaluate();
  187. if (!value.confident) {
  188. return deopt(valuePath, state);
  189. }
  190. value = value.value;
  191. obj[key] = value;
  192. }
  193. return obj;
  194. }
  195. if (path.isLogicalExpression()) {
  196. const wasConfident = state.confident;
  197. const left = evaluateCached(path.get("left"), state);
  198. const leftConfident = state.confident;
  199. state.confident = wasConfident;
  200. const right = evaluateCached(path.get("right"), state);
  201. const rightConfident = state.confident;
  202. switch (node.operator) {
  203. case "||":
  204. state.confident = leftConfident && (!!left || rightConfident);
  205. if (!state.confident) return;
  206. return left || right;
  207. case "&&":
  208. state.confident = leftConfident && (!left || rightConfident);
  209. if (!state.confident) return;
  210. return left && right;
  211. }
  212. }
  213. if (path.isBinaryExpression()) {
  214. const left = evaluateCached(path.get("left"), state);
  215. if (!state.confident) return;
  216. const right = evaluateCached(path.get("right"), state);
  217. if (!state.confident) return;
  218. switch (node.operator) {
  219. case "-":
  220. return left - right;
  221. case "+":
  222. return left + right;
  223. case "/":
  224. return left / right;
  225. case "*":
  226. return left * right;
  227. case "%":
  228. return left % right;
  229. case "**":
  230. return Math.pow(left, right);
  231. case "<":
  232. return left < right;
  233. case ">":
  234. return left > right;
  235. case "<=":
  236. return left <= right;
  237. case ">=":
  238. return left >= right;
  239. case "==":
  240. return left == right;
  241. case "!=":
  242. return left != right;
  243. case "===":
  244. return left === right;
  245. case "!==":
  246. return left !== right;
  247. case "|":
  248. return left | right;
  249. case "&":
  250. return left & right;
  251. case "^":
  252. return left ^ right;
  253. case "<<":
  254. return left << right;
  255. case ">>":
  256. return left >> right;
  257. case ">>>":
  258. return left >>> right;
  259. }
  260. }
  261. if (path.isCallExpression()) {
  262. const callee = path.get("callee");
  263. let context;
  264. let func;
  265. if (callee.isIdentifier() && !path.scope.getBinding(callee.node.name, true) && VALID_CALLEES.indexOf(callee.node.name) >= 0) {
  266. func = global[node.callee.name];
  267. }
  268. if (callee.isMemberExpression()) {
  269. const object = callee.get("object");
  270. const property = callee.get("property");
  271. if (object.isIdentifier() && property.isIdentifier() && VALID_CALLEES.indexOf(object.node.name) >= 0 && INVALID_METHODS.indexOf(property.node.name) < 0) {
  272. context = global[object.node.name];
  273. func = context[property.node.name];
  274. }
  275. if (object.isLiteral() && property.isIdentifier()) {
  276. const type = typeof object.node.value;
  277. if (type === "string" || type === "number") {
  278. context = object.node.value;
  279. func = context[property.node.name];
  280. }
  281. }
  282. }
  283. if (func) {
  284. const args = path.get("arguments").map(arg => evaluateCached(arg, state));
  285. if (!state.confident) return;
  286. return func.apply(context, args);
  287. }
  288. }
  289. deopt(path, state);
  290. }
  291. function evaluateQuasis(path, quasis, state, raw = false) {
  292. let str = "";
  293. let i = 0;
  294. const exprs = path.get("expressions");
  295. for (const elem of quasis) {
  296. if (!state.confident) break;
  297. str += raw ? elem.value.raw : elem.value.cooked;
  298. const expr = exprs[i++];
  299. if (expr) str += String(evaluateCached(expr, state));
  300. }
  301. if (!state.confident) return;
  302. return str;
  303. }
  304. function evaluate() {
  305. const state = {
  306. confident: true,
  307. deoptPath: null,
  308. seen: new Map()
  309. };
  310. let value = evaluateCached(this, state);
  311. if (!state.confident) value = undefined;
  312. return {
  313. confident: state.confident,
  314. deopt: state.deoptPath,
  315. value: value
  316. };
  317. }