UNPKG

8.6 kBJavaScriptView Raw
1/**
2 * @fileoverview Rule to flag use of eval() statement
3 * @author Nicholas C. Zakas
4 * @copyright 2015 Toru Nagashima. All rights reserved.
5 * @copyright 2015 Mathias Schreck. All rights reserved.
6 * @copyright 2013 Nicholas C. Zakas. All rights reserved.
7 */
8
9"use strict";
10
11//------------------------------------------------------------------------------
12// Requirements
13//------------------------------------------------------------------------------
14
15var astUtils = require("../ast-utils");
16
17//------------------------------------------------------------------------------
18// Helpers
19//------------------------------------------------------------------------------
20
21var candidatesOfGlobalObject = Object.freeze([
22 "global",
23 "window"
24]);
25
26/**
27 * Checks a given node is a Identifier node of the specified name.
28 *
29 * @param {ASTNode} node - A node to check.
30 * @param {string} name - A name to check.
31 * @returns {boolean} `true` if the node is a Identifier node of the name.
32 */
33function isIdentifier(node, name) {
34 return node.type === "Identifier" && node.name === name;
35}
36
37/**
38 * Checks a given node is a Literal node of the specified string value.
39 *
40 * @param {ASTNode} node - A node to check.
41 * @param {string} name - A name to check.
42 * @returns {boolean} `true` if the node is a Literal node of the name.
43 */
44function isConstant(node, name) {
45 switch (node.type) {
46 case "Literal":
47 return node.value === name;
48
49 case "TemplateLiteral":
50 return (
51 node.expressions.length === 0 &&
52 node.quasis[0].value.cooked === name
53 );
54
55 default:
56 return false;
57 }
58}
59
60/**
61 * Checks a given node is a MemberExpression node which has the specified name's
62 * property.
63 *
64 * @param {ASTNode} node - A node to check.
65 * @param {string} name - A name to check.
66 * @returns {boolean} `true` if the node is a MemberExpression node which has
67 * the specified name's property
68 */
69function isMember(node, name) {
70 return (
71 node.type === "MemberExpression" &&
72 (node.computed ? isConstant : isIdentifier)(node.property, name)
73 );
74}
75
76//------------------------------------------------------------------------------
77// Rule Definition
78//------------------------------------------------------------------------------
79
80module.exports = function(context) {
81 var allowIndirect = Boolean(
82 context.options[0] &&
83 context.options[0].allowIndirect
84 );
85 var sourceCode = context.getSourceCode();
86 var funcInfo = null;
87
88 /**
89 * Pushs a variable scope (Program or Function) information to the stack.
90 *
91 * This is used in order to check whether or not `this` binding is a
92 * reference to the global object.
93 *
94 * @param {ASTNode} node - A node of the scope. This is one of Program,
95 * FunctionDeclaration, FunctionExpression, and ArrowFunctionExpression.
96 * @returns {void}
97 */
98 function enterVarScope(node) {
99 var strict = context.getScope().isStrict;
100
101 funcInfo = {
102 upper: funcInfo,
103 node: node,
104 strict: strict,
105 defaultThis: false,
106 initialized: strict
107 };
108 }
109
110 /**
111 * Pops a variable scope from the stack.
112 *
113 * @returns {void}
114 */
115 function exitVarScope() {
116 funcInfo = funcInfo.upper;
117 }
118
119 /**
120 * Reports a given node.
121 *
122 * `node` is `Identifier` or `MemberExpression`.
123 * The parent of `node` might be `CallExpression`.
124 *
125 * The location of the report is always `eval` `Identifier` (or possibly
126 * `Literal`). The type of the report is `CallExpression` if the parent is
127 * `CallExpression`. Otherwise, it's the given node type.
128 *
129 * @param {ASTNode} node - A node to report.
130 * @returns {void}
131 */
132 function report(node) {
133 var locationNode = node;
134 var parent = node.parent;
135
136 if (node.type === "MemberExpression") {
137 locationNode = node.property;
138 }
139 if (parent.type === "CallExpression" && parent.callee === node) {
140 node = parent;
141 }
142
143 context.report({
144 node: node,
145 loc: locationNode.loc.start,
146 message: "eval can be harmful."
147 });
148 }
149
150 /**
151 * Reports accesses of `eval` via the global object.
152 *
153 * @param {escope.Scope} globalScope - The global scope.
154 * @returns {void}
155 */
156 function reportAccessingEvalViaGlobalObject(globalScope) {
157 for (var i = 0; i < candidatesOfGlobalObject.length; ++i) {
158 var name = candidatesOfGlobalObject[i];
159 var variable = astUtils.getVariableByName(globalScope, name);
160 if (!variable) {
161 continue;
162 }
163
164 var references = variable.references;
165 for (var j = 0; j < references.length; ++j) {
166 var identifier = references[j].identifier;
167 var node = identifier.parent;
168
169 // To detect code like `window.window.eval`.
170 while (isMember(node, name)) {
171 node = node.parent;
172 }
173
174 // Reports.
175 if (isMember(node, "eval")) {
176 report(node);
177 }
178 }
179 }
180 }
181
182 /**
183 * Reports all accesses of `eval` (excludes direct calls to eval).
184 *
185 * @param {escope.Scope} globalScope - The global scope.
186 * @returns {void}
187 */
188 function reportAccessingEval(globalScope) {
189 var variable = astUtils.getVariableByName(globalScope, "eval");
190 if (!variable) {
191 return;
192 }
193
194 var references = variable.references;
195 for (var i = 0; i < references.length; ++i) {
196 var reference = references[i];
197 var id = reference.identifier;
198
199 if (id.name === "eval" && !astUtils.isCallee(id)) {
200 // Is accessing to eval (excludes direct calls to eval)
201 report(id);
202 }
203 }
204 }
205
206 if (allowIndirect) {
207 // Checks only direct calls to eval.
208 // It's simple!
209 return {
210 "CallExpression:exit": function(node) {
211 var callee = node.callee;
212 if (isIdentifier(callee, "eval")) {
213 report(callee);
214 }
215 }
216 };
217 }
218
219 return {
220 "CallExpression:exit": function(node) {
221 var callee = node.callee;
222 if (isIdentifier(callee, "eval")) {
223 report(callee);
224 }
225 },
226
227 "Program": function(node) {
228 var scope = context.getScope(),
229 features = context.parserOptions.ecmaFeatures || {},
230 strict =
231 scope.isStrict ||
232 node.sourceType === "module" ||
233 (features.globalReturn && scope.childScopes[0].isStrict);
234
235 funcInfo = {
236 upper: null,
237 node: node,
238 strict: strict,
239 defaultThis: true,
240 initialized: true
241 };
242 },
243
244 "Program:exit": function() {
245 var globalScope = context.getScope();
246
247 exitVarScope();
248 reportAccessingEval(globalScope);
249 reportAccessingEvalViaGlobalObject(globalScope);
250 },
251
252 "FunctionDeclaration": enterVarScope,
253 "FunctionDeclaration:exit": exitVarScope,
254 "FunctionExpression": enterVarScope,
255 "FunctionExpression:exit": exitVarScope,
256 "ArrowFunctionExpression": enterVarScope,
257 "ArrowFunctionExpression:exit": exitVarScope,
258
259 "ThisExpression": function(node) {
260 if (!isMember(node.parent, "eval")) {
261 return;
262 }
263
264 // `this.eval` is found.
265 // Checks whether or not the value of `this` is the global object.
266 if (!funcInfo.initialized) {
267 funcInfo.initialized = true;
268 funcInfo.defaultThis = astUtils.isDefaultThisBinding(
269 funcInfo.node,
270 sourceCode
271 );
272 }
273 if (!funcInfo.strict && funcInfo.defaultThis) {
274 // `this.eval` is possible built-in `eval`.
275 report(node.parent);
276 }
277 }
278 };
279
280};
281
282module.exports.schema = [
283 {
284 "type": "object",
285 "properties": {
286 "allowIndirect": {"type": "boolean"}
287 },
288 "additionalProperties": false
289 }
290];