-
Notifications
You must be signed in to change notification settings - Fork 2k
Expand file tree
/
Copy pathNullness.qll
More file actions
384 lines (358 loc) · 12.1 KB
/
Nullness.qll
File metadata and controls
384 lines (358 loc) · 12.1 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
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
/**
* Provides predicates for performing nullness analyses.
*
* Nullness analyses are used to identify places in a program where
* a `null` pointer exception (`NullReferenceException`) may be thrown.
* Example:
*
* ```csharp
* void M(string s) {
* if (s != null) {
* ...
* }
* ...
* var i = s.IndexOf('a'); // s may be null
* ...
* }
* ```
*/
import csharp
private import ControlFlow
private import internal.CallableReturns
private import semmle.code.csharp.controlflow.Guards as G
private import semmle.code.csharp.dataflow.internal.SsaImpl as SsaImpl
private import semmle.code.csharp.frameworks.System
private import semmle.code.csharp.frameworks.Test
private import semmle.code.csharp.controlflow.ControlFlowReachability
private Expr maybeNullExpr(Expr reason) {
G::Internal::nullValue(result) and reason = result
or
result instanceof AsExpr and reason = result
or
result.(AssignExpr).getRightOperand() = maybeNullExpr(reason)
or
result.(CastExpr).getExpr() = maybeNullExpr(reason)
or
result =
any(ConditionalExpr ce |
ce.getThen() = maybeNullExpr(reason)
or
ce.getElse() = maybeNullExpr(reason)
)
or
result.(NullCoalescingOperation).getRightOperand() = maybeNullExpr(reason)
or
result =
any(QualifiableExpr qe |
qe.isConditional() and
reason = qe.getQualifier() and
not qe instanceof AssignableWrite
)
}
/** An expression that may be `null`. */
class MaybeNullExpr extends Expr {
MaybeNullExpr() { this = maybeNullExpr(_) }
}
/** An expression that is always `null`. */
class AlwaysNullExpr extends Expr {
AlwaysNullExpr() {
G::Internal::nullValue(this)
or
exists(AlwaysNullExpr e | G::Internal::nullValueImpliedUnary(e, this))
or
exists(AlwaysNullExpr e1, AlwaysNullExpr e2 | G::Internal::nullValueImpliedBinary(e1, e2, this))
or
this =
any(Ssa::Definition def |
forex(Ssa::Definition u | u = def.getAnUltimateDefinition() | nullDef(u))
).getARead()
or
exists(Callable target |
this.(Call).getTarget() = target and
not target.(Virtualizable).isVirtual() and
alwaysNullCallable(target)
)
}
}
/** Holds if SSA definition `def` is always `null`. */
private predicate nullDef(Ssa::ExplicitDefinition def) {
def.getADefinition().getSource() instanceof AlwaysNullExpr
}
/** An expression that is never `null`. */
class NonNullExpr extends Expr {
NonNullExpr() {
G::Internal::nonNullValue(this)
or
exists(NonNullExpr mid | G::Internal::nonNullValueImpliedUnary(mid, this))
or
this instanceof G::NullGuardedExpr
or
this =
any(Ssa::Definition def |
forex(Ssa::Definition u | u = def.getAnUltimateDefinition() | nonNullDef(u))
).getARead()
or
exists(Callable target |
this.(Call).getTarget() = target and
not target.(Virtualizable).isVirtual() and
alwaysNotNullCallable(target) and
not this.(QualifiableExpr).isConditional()
)
}
}
/** Holds if SSA definition `def` is never `null`. */
private predicate nonNullDef(Ssa::ExplicitDefinition def) {
def.getADefinition().getSource() instanceof NonNullExpr
or
exists(AssignableDefinition ad | ad = def.getADefinition() |
ad instanceof AssignableDefinitions::PatternDefinition
or
ad =
any(AssignableDefinitions::LocalVariableDefinition d |
d.getExpr() = any(SpecificCatchClause scc).getVariableDeclExpr()
or
d.getExpr() = any(ForeachStmt fs).getAVariableDeclExpr()
)
)
}
/**
* Holds if `node` is a dereference `d` of SSA definition `def`.
*/
private predicate dereferenceAt(ControlFlowNode node, Ssa::Definition def, Dereference d) {
d = def.getAReadAtNode(node)
}
private predicate isMaybeNullArgument(Ssa::ParameterDefinition def, MaybeNullExpr arg) {
exists(AssignableDefinitions::ImplicitParameterDefinition pdef, Parameter p |
p = def.getParameter()
|
p = pdef.getParameter().getUnboundDeclaration() and
arg = p.getAnAssignedArgument() and
not arg.getEnclosingCallable().getEnclosingCallable*() instanceof TestMethod and
(
p.isParams()
implies
(
isValidExplicitParamsType(p, arg.getType()) and
not exists(Call c | c.getAnArgument() = arg and hasMultipleParamsArguments(c))
)
)
)
}
/**
* Holds if the type `t` is a valid argument type for passing an explicit array
* to the `params` parameter `p`. For example, the types `object[]` and `string[]`
* of the arguments on lines 4 and 5, respectively, are valid for the parameter
* `args` on line 1 in
*
* ```csharp
* void M(params object[] args) { ... }
*
* void CallM(object[] os, string[] ss, string s) {
* M(os);
* M(ss);
* M(s);
* }
* ```
*/
pragma[nomagic]
private predicate isValidExplicitParamsType(Parameter p, Type t) {
p.isParams() and
t.isImplicitlyConvertibleTo(p.getType())
}
/**
* Holds if call `c` has multiple arguments for a `params` parameter
* of the targeted callable.
*/
private predicate hasMultipleParamsArguments(Call c) {
exists(Parameter p | p = c.getTarget().getAParameter() |
p.isParams() and
exists(c.getArgument(any(int i | i > p.getPosition())))
)
}
private predicate isNullDefaultArgument(Ssa::ParameterDefinition def, AlwaysNullExpr arg) {
exists(AssignableDefinitions::ImplicitParameterDefinition pdef, Parameter p |
p = def.getParameter()
|
p = pdef.getParameter().getUnboundDeclaration() and
arg = p.getDefaultValue() and
not arg.getEnclosingCallable().getEnclosingCallable*() instanceof TestMethod
)
}
/** Holds if `def` is an SSA definition that may be `null`. */
private predicate defMaybeNull(Ssa::Definition def, ControlFlowNode node, string msg, Element reason) {
not nonNullDef(def) and
(
// A variable compared to `null` might be `null`
exists(G::DereferenceableExpr de | de = def.getARead() |
de.guardSuggestsMaybeNull(reason) and
msg = "as suggested by $@ null check" and
node = def.getControlFlowNode() and
not de = any(Ssa::PhiNode phi).getARead() and
// Don't use a check as reason if there is a `null` assignment
// or argument
not def.(Ssa::ExplicitDefinition).getADefinition().getSource() instanceof MaybeNullExpr and
not isMaybeNullArgument(def, _)
)
or
// A parameter might be `null` if there is a `null` argument somewhere
isMaybeNullArgument(def, reason) and
node = def.getControlFlowNode() and
(
if reason instanceof AlwaysNullExpr
then msg = "because of $@ null argument"
else msg = "because of $@ potential null argument"
)
or
// If the source of a variable is `null` then the variable may be `null`
exists(AssignableDefinition adef | adef = def.(Ssa::ExplicitDefinition).getADefinition() |
adef.getSource() = maybeNullExpr(node.asExpr()) and
reason = adef.getExpr() and
msg = "because of $@ assignment"
)
or
// A variable of nullable type may be null
exists(Dereference d | dereferenceAt(_, def, d) |
node = def.getControlFlowNode() and
d.hasNullableType() and
not def instanceof Ssa::PhiNode and
reason = def.getSourceVariable().getAssignable() and
msg = "because it has a nullable type"
)
)
}
private Ssa::Definition getAPseudoInput(Ssa::Definition def) {
result = def.(Ssa::PhiNode).getAnInput()
}
// `def.getAnUltimateDefinition()` includes inputs into uncertain
// definitions, but we only want inputs into pseudo nodes
private Ssa::Definition getAnUltimateDefinition(Ssa::Definition def) {
result = getAPseudoInput*(def) and
not result instanceof Ssa::PhiNode
}
/**
* Holds if SSA definition `def` can reach a read at `cfn`, without passing
* through an intermediate dereference that always throws a null reference
* exception.
*/
private predicate defReaches(Ssa::Definition def, ControlFlowNode cfn) {
exists(def.getAFirstReadAtNode(cfn))
or
exists(ControlFlowNode mid | defReaches(def, mid) |
SsaImpl::adjacentReadPairSameVar(_, mid, cfn) and
not mid = any(Dereference d | d.isAlwaysNull(def.getSourceVariable())).getControlFlowNode()
)
}
private module NullnessConfig implements ControlFlowReachability::ConfigSig {
predicate source(ControlFlowNode node, Ssa::Definition def) { defMaybeNull(def, node, _, _) }
predicate sink(ControlFlowNode node, Ssa::Definition def) {
exists(Dereference d |
dereferenceAt(node, def, d) and
not d instanceof NonNullExpr
)
}
predicate barrierValue(G::GuardValue gv) { gv.isNullness(false) }
predicate uncertainFlow() { none() }
}
private module NullnessFlow = ControlFlowReachability::Flow<NullnessConfig>;
predicate maybeNullDeref(Dereference d, Ssa::SourceVariable v, string msg, Element reason) {
exists(Ssa::Definition origin, Ssa::Definition ssa, ControlFlowNode src, ControlFlowNode sink |
defMaybeNull(origin, src, msg, reason) and
NullnessFlow::flow(src, origin, sink, ssa) and
ssa.getSourceVariable() = v and
dereferenceAt(sink, ssa, d) and
not d.isAlwaysNull(v)
)
}
/**
* An expression that dereferences a value. That is, an expression that may
* result in a `NullReferenceException` if the value is `null`.
*/
class Dereference extends G::DereferenceableExpr {
Dereference() {
if this.hasNullableType()
then (
// Strictly speaking, these throw `InvalidOperationException`s and not
// `NullReferenceException`s
this = any(PropertyAccess pa | pa.getTarget().hasName("Value")).getQualifier()
or
exists(Type underlyingType |
this = any(CastExpr ce | ce.getTargetType() = underlyingType).getExpr()
|
underlyingType = this.getType().(NullableType).getUnderlyingType()
or
underlyingType = this.getType() and
not underlyingType instanceof NullableType
)
) else (
this =
any(QualifiableExpr qe |
not qe.isConditional() and
not qe.(MethodCall).isImplicit()
).getQualifier() and
not this instanceof ThisAccess and
not this instanceof BaseAccess and
not this instanceof TypeAccess
or
this = any(LockStmt stmt).getExpr()
or
this = any(ForeachStmt stmt).getIterableExpr()
or
exists(ExtensionMethodCall emc, Parameter p |
this = emc.getArgumentForParameter(p) and
p.hasExtensionMethodModifier() and
not emc.isConditional()
|
// Assume all non-source extension methods on
// (1) nullable types are null-safe
// (2) non-nullable types are doing a dereference.
p.fromLibrary() and
not p.getAnnotatedType().isNullableRefType()
or
p.fromSource() and
exists(
Ssa::ParameterDefinition def, AssignableDefinitions::ImplicitParameterDefinition pdef
|
p = def.getParameter()
|
p.getUnboundDeclaration() = pdef.getParameter() and
def.getARead() instanceof Dereference
)
)
)
}
private predicate isAlwaysNull0(Ssa::Definition def) {
forall(Ssa::Definition input | input = getAnUltimateDefinition(def) |
input.(Ssa::ExplicitDefinition).getADefinition().getSource() instanceof AlwaysNullExpr
) and
not nonNullDef(def) and
this = def.getARead() and
not this instanceof G::NullGuardedExpr
}
/**
* Holds if this expression dereferences SSA source variable `v`, which is
* always `null`.
*/
predicate isAlwaysNull(Ssa::SourceVariable v) {
this = v.getAnAccess() and
// Exclude fields and properties, as they may not have an accurate SSA representation
v.getAssignable() instanceof LocalScopeVariable and
(
forex(Ssa::Definition def0 | this = def0.getARead() | this.isAlwaysNull0(def0))
or
exists(G::GuardValue nv |
this.(G::GuardedExpr).mustHaveValue(nv) and
nv.isNullValue()
)
) and
not this instanceof G::NullGuardedExpr
}
/**
* Holds if this expression dereferences SSA source variable `v`, which is
* always `null`, and this expression can be reached from an SSA definition
* for `v` without passing through another such dereference.
*/
predicate isFirstAlwaysNull(Ssa::SourceVariable v) {
this.isAlwaysNull(v) and
defReaches(v.getAnSsaDefinition(), this.getControlFlowNode())
}
}