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 | ;
|
10 |
|
11 | //------------------------------------------------------------------------------
|
12 | // Requirements
|
13 | //------------------------------------------------------------------------------
|
14 |
|
15 | var astUtils = require("../ast-utils");
|
16 |
|
17 | //------------------------------------------------------------------------------
|
18 | // Helpers
|
19 | //------------------------------------------------------------------------------
|
20 |
|
21 | var 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 | */
|
33 | function 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 | */
|
44 | function 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 | */
|
69 | function 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 |
|
80 | module.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 |
|
282 | module.exports.schema = [
|
283 | {
|
284 | "type": "object",
|
285 | "properties": {
|
286 | "allowIndirect": {"type": "boolean"}
|
287 | },
|
288 | "additionalProperties": false
|
289 | }
|
290 | ];
|