no-eval.js 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. /**
  2. * @fileoverview Rule to flag use of eval() statement
  3. * @author Nicholas C. Zakas
  4. */
  5. "use strict";
  6. //------------------------------------------------------------------------------
  7. // Requirements
  8. //------------------------------------------------------------------------------
  9. const astUtils = require("./utils/ast-utils");
  10. //------------------------------------------------------------------------------
  11. // Helpers
  12. //------------------------------------------------------------------------------
  13. const candidatesOfGlobalObject = Object.freeze([
  14. "global",
  15. "window",
  16. "globalThis",
  17. ]);
  18. /**
  19. * Checks a given node is a MemberExpression node which has the specified name's
  20. * property.
  21. * @param {ASTNode} node A node to check.
  22. * @param {string} name A name to check.
  23. * @returns {boolean} `true` if the node is a MemberExpression node which has
  24. * the specified name's property
  25. */
  26. function isMember(node, name) {
  27. return astUtils.isSpecificMemberAccess(node, null, name);
  28. }
  29. //------------------------------------------------------------------------------
  30. // Rule Definition
  31. //------------------------------------------------------------------------------
  32. /** @type {import('../types').Rule.RuleModule} */
  33. module.exports = {
  34. meta: {
  35. type: "suggestion",
  36. defaultOptions: [
  37. {
  38. allowIndirect: false,
  39. },
  40. ],
  41. docs: {
  42. description: "Disallow the use of `eval()`",
  43. recommended: false,
  44. url: "https://eslint.org/docs/latest/rules/no-eval",
  45. },
  46. schema: [
  47. {
  48. type: "object",
  49. properties: {
  50. allowIndirect: { type: "boolean" },
  51. },
  52. additionalProperties: false,
  53. },
  54. ],
  55. messages: {
  56. unexpected: "eval can be harmful.",
  57. },
  58. },
  59. create(context) {
  60. const [{ allowIndirect }] = context.options;
  61. const sourceCode = context.sourceCode;
  62. let funcInfo = null;
  63. /**
  64. * Pushes a `this` scope (non-arrow function, class static block, or class field initializer) information to the stack.
  65. * Top-level scopes are handled separately.
  66. *
  67. * This is used in order to check whether or not `this` binding is a
  68. * reference to the global object.
  69. * @param {ASTNode} node A node of the scope.
  70. * For functions, this is one of FunctionDeclaration, FunctionExpression.
  71. * For class static blocks, this is StaticBlock.
  72. * For class field initializers, this can be any node that is PropertyDefinition#value.
  73. * @returns {void}
  74. */
  75. function enterThisScope(node) {
  76. const strict = sourceCode.getScope(node).isStrict;
  77. funcInfo = {
  78. upper: funcInfo,
  79. node,
  80. strict,
  81. isTopLevelOfScript: false,
  82. defaultThis: false,
  83. initialized: strict,
  84. };
  85. }
  86. /**
  87. * Pops a variable scope from the stack.
  88. * @returns {void}
  89. */
  90. function exitThisScope() {
  91. funcInfo = funcInfo.upper;
  92. }
  93. /**
  94. * Reports a given node.
  95. *
  96. * `node` is `Identifier` or `MemberExpression`.
  97. * The parent of `node` might be `CallExpression`.
  98. *
  99. * The location of the report is always `eval` `Identifier` (or possibly
  100. * `Literal`). The type of the report is `CallExpression` if the parent is
  101. * `CallExpression`. Otherwise, it's the given node type.
  102. * @param {ASTNode} node A node to report.
  103. * @returns {void}
  104. */
  105. function report(node) {
  106. const parent = node.parent;
  107. const locationNode =
  108. node.type === "MemberExpression" ? node.property : node;
  109. const reportNode =
  110. parent.type === "CallExpression" && parent.callee === node
  111. ? parent
  112. : node;
  113. context.report({
  114. node: reportNode,
  115. loc: locationNode.loc,
  116. messageId: "unexpected",
  117. });
  118. }
  119. /**
  120. * Reports accesses of `eval` via the global object.
  121. * @param {eslint-scope.Scope} globalScope The global scope.
  122. * @returns {void}
  123. */
  124. function reportAccessingEvalViaGlobalObject(globalScope) {
  125. for (let i = 0; i < candidatesOfGlobalObject.length; ++i) {
  126. const name = candidatesOfGlobalObject[i];
  127. const variable = astUtils.getVariableByName(globalScope, name);
  128. if (!variable) {
  129. continue;
  130. }
  131. const references = variable.references;
  132. for (let j = 0; j < references.length; ++j) {
  133. const identifier = references[j].identifier;
  134. let node = identifier.parent;
  135. // To detect code like `window.window.eval`.
  136. while (isMember(node, name)) {
  137. node = node.parent;
  138. }
  139. // Reports.
  140. if (isMember(node, "eval")) {
  141. report(node);
  142. }
  143. }
  144. }
  145. }
  146. /**
  147. * Reports all accesses of `eval` (excludes direct calls to eval).
  148. * @param {eslint-scope.Scope} globalScope The global scope.
  149. * @returns {void}
  150. */
  151. function reportAccessingEval(globalScope) {
  152. const variable = astUtils.getVariableByName(globalScope, "eval");
  153. if (!variable) {
  154. return;
  155. }
  156. const references = variable.references;
  157. for (let i = 0; i < references.length; ++i) {
  158. const reference = references[i];
  159. const id = reference.identifier;
  160. if (id.name === "eval" && !astUtils.isCallee(id)) {
  161. // Is accessing to eval (excludes direct calls to eval)
  162. report(id);
  163. }
  164. }
  165. }
  166. if (allowIndirect) {
  167. // Checks only direct calls to eval. It's simple!
  168. return {
  169. "CallExpression:exit"(node) {
  170. const callee = node.callee;
  171. /*
  172. * Optional call (`eval?.("code")`) is not direct eval.
  173. * The direct eval is only step 6.a.vi of https://tc39.es/ecma262/#sec-function-calls-runtime-semantics-evaluation
  174. * But the optional call is https://tc39.es/ecma262/#sec-optional-chaining-chain-evaluation
  175. */
  176. if (
  177. !node.optional &&
  178. astUtils.isSpecificId(callee, "eval")
  179. ) {
  180. report(callee);
  181. }
  182. },
  183. };
  184. }
  185. return {
  186. "CallExpression:exit"(node) {
  187. const callee = node.callee;
  188. if (astUtils.isSpecificId(callee, "eval")) {
  189. report(callee);
  190. }
  191. },
  192. Program(node) {
  193. const scope = sourceCode.getScope(node),
  194. features =
  195. context.languageOptions.parserOptions.ecmaFeatures ||
  196. {},
  197. strict =
  198. scope.isStrict ||
  199. node.sourceType === "module" ||
  200. (features.globalReturn &&
  201. scope.childScopes[0].isStrict),
  202. isTopLevelOfScript =
  203. node.sourceType !== "module" && !features.globalReturn;
  204. funcInfo = {
  205. upper: null,
  206. node,
  207. strict,
  208. isTopLevelOfScript,
  209. defaultThis: true,
  210. initialized: true,
  211. };
  212. },
  213. "Program:exit"(node) {
  214. const globalScope = sourceCode.getScope(node);
  215. exitThisScope();
  216. reportAccessingEval(globalScope);
  217. reportAccessingEvalViaGlobalObject(globalScope);
  218. },
  219. FunctionDeclaration: enterThisScope,
  220. "FunctionDeclaration:exit": exitThisScope,
  221. FunctionExpression: enterThisScope,
  222. "FunctionExpression:exit": exitThisScope,
  223. "PropertyDefinition > *.value": enterThisScope,
  224. "PropertyDefinition > *.value:exit": exitThisScope,
  225. StaticBlock: enterThisScope,
  226. "StaticBlock:exit": exitThisScope,
  227. ThisExpression(node) {
  228. if (!isMember(node.parent, "eval")) {
  229. return;
  230. }
  231. /*
  232. * `this.eval` is found.
  233. * Checks whether or not the value of `this` is the global object.
  234. */
  235. if (!funcInfo.initialized) {
  236. funcInfo.initialized = true;
  237. funcInfo.defaultThis = astUtils.isDefaultThisBinding(
  238. funcInfo.node,
  239. sourceCode,
  240. );
  241. }
  242. // `this` at the top level of scripts always refers to the global object
  243. if (
  244. funcInfo.isTopLevelOfScript ||
  245. (!funcInfo.strict && funcInfo.defaultThis)
  246. ) {
  247. // `this.eval` is possible built-in `eval`.
  248. report(node.parent);
  249. }
  250. },
  251. };
  252. },
  253. };