no-eval.js 9.3 KB

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