-
Notifications
You must be signed in to change notification settings - Fork 2k
Expand file tree
/
Copy pathExprHasNoEffect.qll
More file actions
204 lines (194 loc) · 6.91 KB
/
ExprHasNoEffect.qll
File metadata and controls
204 lines (194 loc) · 6.91 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
/**
* Provides classes and predicates for the 'js/useless-expression' query.
*/
overlay[local?]
module;
import javascript
import DOMProperties
/**
* Holds if `e` appears in a syntactic context where its value is discarded.
*/
predicate inVoidContext(Expr e) {
exists(ExprStmt parent |
// e is a toplevel expression in an expression statement
parent = e.getParent() and
// but it isn't an HTML attribute or a configuration object
not exists(TopLevel tl | tl = parent.getParent() |
tl instanceof CodeInAttribute
or
// if the toplevel in its entirety is of the form `({ ... })`,
// it is probably a configuration object (e.g., a require.js build configuration)
tl.getNumChildStmt() = 1 and e.stripParens() instanceof ObjectExpr
)
)
or
// propagate void context through parenthesized expressions
inVoidContext(e.getParent().(ParExpr))
or
exists(SeqExpr seq, int i, int n |
e = seq.getOperand(i) and
n = seq.getNumOperands()
|
i < n - 1 or inVoidContext(seq)
)
or
exists(ForStmt stmt | e = stmt.getUpdate())
or
exists(ForStmt stmt | e = stmt.getInit() |
// Allow the pattern `for(i; i < 10; i++)`
not e instanceof VarAccess
)
or
exists(LogicalBinaryExpr logical | e = logical.getRightOperand() and inVoidContext(logical))
or
exists(ConditionalExpr cond | e = cond.getABranch() | inVoidContext(cond))
}
/**
* Holds if `e` is of the form `x;` or `e.p;` and has a JSDoc comment containing a tag.
* In that case, it is probably meant as a declaration and shouldn't be flagged by this query.
*
* This will still flag cases where the JSDoc comment contains no tag at all (and hence carries
* no semantic information), and expression statements with an ordinary (non-JSDoc) comment
* attached to them.
*/
predicate isDeclaration(Expr e) {
(e instanceof VarAccess or e instanceof PropAccess) and
exists(e.getParent().(ExprStmt).getDocumentation().getATag())
}
/**
* Holds if there exists a getter for a property called `name` anywhere in the program.
*/
overlay[global]
predicate isGetterProperty(string name) {
// there is a call of the form `Object.defineProperty(..., name, descriptor)` ...
exists(CallToObjectDefineProperty defProp | name = defProp.getPropertyName() |
// ... where `descriptor` defines a getter
defProp.hasPropertyAttributeWrite("get", _)
or
// ... where `descriptor` may define a getter
exists(DataFlow::SourceNode descriptor | descriptor.flowsTo(defProp.getPropertyDescriptor()) |
descriptor.isIncomplete(_)
or
// minimal escape analysis for the descriptor
exists(DataFlow::InvokeNode invk |
not invk = defProp and
descriptor.flowsTo(invk.getAnArgument())
)
)
)
or
// there is an object expression with a getter property `name`
exists(ObjectExpr obj | obj.getPropertyByName(name) instanceof PropertyGetter)
}
/**
* A property access that may invoke a getter.
*/
overlay[global]
class GetterPropertyAccess extends PropAccess {
override predicate isImpure() { isGetterProperty(this.getPropertyName()) }
}
/**
* Holds if `c` is an indirect eval call of the form `(dummy, eval)(...)`, where
* `dummy` is some expression whose value is discarded, and which simply
* exists to prevent the call from being interpreted as a direct eval.
*/
predicate isIndirectEval(CallExpr c, Expr dummy) {
exists(SeqExpr seq | seq = c.getCallee().stripParens() |
dummy = seq.getOperand(0) and
seq.getOperand(1).(GlobalVarAccess).getName() = "eval" and
seq.getNumOperands() = 2
)
}
/**
* Holds if `c` is a call of the form `(dummy, e[p])(...)`, where `dummy` is
* some expression whose value is discarded, and which simply exists
* to prevent the call from being interpreted as a method call.
*/
predicate isReceiverSuppressingCall(CallExpr c, Expr dummy, PropAccess callee) {
exists(SeqExpr seq | seq = c.getCallee().stripParens() |
dummy = seq.getOperand(0) and
seq.getOperand(1) = callee and
seq.getNumOperands() = 2
)
}
/**
* Holds if evaluating `e` has no side effects (except potentially allocating
* and initializing a new object).
*
* For calls, we do not check whether their arguments have any side effects:
* even if they do, the call itself is useless and should be flagged by this
* query.
*/
overlay[global]
predicate noSideEffects(Expr e) {
e.isPure()
or
// `new Error(...)`, `new SyntaxError(...)`, etc.
forex(Function f | f = e.flow().(DataFlow::NewNode).getACallee() |
f.(ExternalType).getASupertype*().getName() = "Error"
)
}
/**
* Holds if `e` is a compound expression that may contain sub-expressions with side effects.
* We should not flag these directly as useless since we want to flag only the innermost
* expressions that actually have no effect.
*/
predicate isCompoundExpression(Expr e) {
e instanceof LogicalBinaryExpr
or
e instanceof SeqExpr
or
e instanceof ParExpr
}
/**
* Holds if the expression `e` should be reported as having no effect.
*/
overlay[global]
predicate hasNoEffect(Expr e) {
noSideEffects(e) and
inVoidContext(e) and
// disregard pure expressions wrapped in a void(...)
not e instanceof VoidExpr and
// filter out directives (unknown directives are handled by UnknownDirective.ql)
not exists(Directive d | e = d.getExpr()) and
// or about externs
not e.inExternsFile() and
// don't complain about declarations
not isDeclaration(e) and
// exclude DOM properties, which sometimes have magical auto-update properties
not isDomProperty(e.(PropAccess).getPropertyName()) and
not isCompoundExpression(e) and
// exclude xUnit.js annotations
not e instanceof XUnitAnnotation and
// exclude common patterns that are most likely intentional
not isIndirectEval(_, e) and
not isReceiverSuppressingCall(_, e, _) and
// exclude anonymous function expressions as statements; these can only arise
// from a syntax error we already flag
not exists(FunctionExpr fe, ExprStmt es | fe = e |
fe = es.getExpr() and
not exists(fe.getName())
) and
// exclude block-level flow type annotations. For example: `(name: empty)`.
not exists(ParExpr parent |
e.getParent() = parent and
e.getLastToken().getNextToken().getValue() = ":"
) and
// exclude expressions that are part of a conditional expression
not exists(ConditionalExpr cond | e = cond.getABranch() |
e instanceof NullLiteral or
e.(GlobalVarAccess).getName() = "undefined" or
e.(NumberLiteral).getIntValue() = 0 or
e instanceof VoidExpr
) and
// exclude the first statement of a try block
not e = any(TryStmt stmt).getBody().getStmt(0).(ExprStmt).getExpr() and
// exclude expressions that are alone in a file, and file doesn't contain a function.
not exists(TopLevel top |
top = e.getParent().(ExprStmt).getParent() and
top.getNumChild() = 1 and
not exists(Function fun | fun.getEnclosingContainer() = top)
) and
// ignore Angular templates
not e.getTopLevel() instanceof Angular2::TemplateTopLevel
}