Skip to content

Commit c0ea069

Browse files
committed
C#: Replace NullMaybe.ql implementation.
1 parent 1276648 commit c0ea069

File tree

2 files changed

+14
-306
lines changed

2 files changed

+14
-306
lines changed

csharp/ql/lib/semmle/code/csharp/dataflow/Nullness.qll

Lines changed: 10 additions & 299 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
import csharp
2121
private import ControlFlow
2222
private import internal.CallableReturns
23-
private import semmle.code.csharp.commons.Assertions
2423
private import semmle.code.csharp.controlflow.Guards as G
2524
private import semmle.code.csharp.controlflow.Guards::AbstractValues
2625
private import semmle.code.csharp.dataflow.internal.SsaImpl as SsaImpl
@@ -119,49 +118,12 @@ private predicate nonNullDef(Ssa::ExplicitDefinition def) {
119118
}
120119

121120
/**
122-
* Holds if the `i`th node of basic block `bb` is a dereference `d` of SSA
123-
* definition `def`.
121+
* Holds if the `node` is a dereference `d` of SSA definition `def`.
124122
*/
125-
private predicate dereferenceAt(BasicBlock bb, int i, Ssa::Definition def, Dereference d) {
126-
d = def.getAReadAtNode(bb.getNode(i))
127-
}
128-
129123
private predicate dereferenceAt(ControlFlow::Node node, Ssa::Definition def, Dereference d) {
130124
d = def.getAReadAtNode(node)
131125
}
132126

133-
/**
134-
* Holds if `e` having abstract value `vExpr` implies that SSA definition `def`
135-
* has abstract value `vDef`.
136-
*/
137-
private predicate exprImpliesSsaDef(
138-
G::Guard e, G::AbstractValue vExpr, Ssa::Definition def, G::AbstractValue vDef
139-
) {
140-
vExpr = e.getAValue() and
141-
vExpr = vDef and
142-
(
143-
e = def.getARead()
144-
or
145-
e = def.(Ssa::ExplicitDefinition).getADefinition().getTargetAccess()
146-
)
147-
or
148-
exists(Expr e0, G::AbstractValue vExpr0 |
149-
exprImpliesSsaDef(e0, vExpr0, def, vDef) and
150-
G::Internal::impliesStep(e, vExpr, e0, vExpr0)
151-
)
152-
}
153-
154-
/**
155-
* Gets an element that tests whether a given SSA definition, `def`, is
156-
* `null` or not.
157-
*
158-
* If the returned element takes the `s` branch, then `def` is guaranteed to be
159-
* `null` if `nv.isNull()` holds, and non-`null` otherwise.
160-
*/
161-
private ControlFlowElement getANullCheck(Ssa::Definition def, ConditionalSuccessor s, NullValue nv) {
162-
exists(Expr e, G::AbstractValue v | v.branch(result, s, e) | exprImpliesSsaDef(e, v, def, nv))
163-
}
164-
165127
private predicate isMaybeNullArgument(Ssa::ImplicitParameterDefinition def, MaybeNullExpr arg) {
166128
exists(AssignableDefinitions::ImplicitParameterDefinition pdef, Parameter p |
167129
p = def.getParameter()
@@ -263,7 +225,7 @@ private predicate defMaybeNull(
263225
)
264226
or
265227
// A variable of nullable type may be null
266-
exists(Dereference d | dereferenceAt(_, _, def, d) |
228+
exists(Dereference d | dereferenceAt(_, def, d) |
267229
node = def.getControlFlowNode() and
268230
d.hasNullableType() and
269231
not def instanceof Ssa::PhiNode and
@@ -273,219 +235,6 @@ private predicate defMaybeNull(
273235
)
274236
}
275237

276-
pragma[noinline]
277-
private predicate sourceVariableMaybeNull(Ssa::SourceVariable v) {
278-
defMaybeNull(v.getAnSsaDefinition(), _, _, _)
279-
}
280-
281-
pragma[noinline]
282-
private predicate defNullImpliesStep0(
283-
Ssa::SourceVariable v, Ssa::Definition def1, BasicBlock bb1, BasicBlock bb2
284-
) {
285-
sourceVariableMaybeNull(v) and
286-
def1.getSourceVariable() = v and
287-
def1.isLiveAtEndOfBlock(bb1) and
288-
bb2 = bb1.getASuccessor()
289-
}
290-
291-
/**
292-
* Holds if `def1` being `null` in basic block `bb1` implies that `def2` might
293-
* be `null` in basic block `bb2`. The SSA definitions share the same source
294-
* variable.
295-
*/
296-
private predicate defNullImpliesStep(
297-
Ssa::Definition def1, BasicBlock bb1, Ssa::Definition def2, BasicBlock bb2
298-
) {
299-
exists(Ssa::SourceVariable v | defNullImpliesStep0(v, def1, bb1, bb2) |
300-
def2.(Ssa::PhiNode).getAnInput() = def1 and
301-
bb2 = def2.getBasicBlock()
302-
or
303-
def2 = def1 and
304-
not exists(Ssa::PhiNode phi |
305-
phi.getSourceVariable() = v and
306-
bb2 = phi.getBasicBlock()
307-
)
308-
) and
309-
not exists(ConditionalSuccessor s, NullValue nv |
310-
bb1.getLastNode() = getANullCheck(def1, s, nv).getAControlFlowNode()
311-
|
312-
bb2 = bb1.getASuccessor(s) and
313-
nv.isNonNull()
314-
)
315-
}
316-
317-
/**
318-
* The transitive closure of `defNullImpliesStep()` originating from `defMaybeNull()`.
319-
* That is, those basic blocks for which the SSA definition is suspected of being `null`.
320-
*/
321-
private predicate defMaybeNullInBlock(Ssa::Definition def, BasicBlock bb) {
322-
defMaybeNull(def, _, _, _) and
323-
bb = def.getBasicBlock()
324-
or
325-
exists(BasicBlock mid, Ssa::Definition midDef | defMaybeNullInBlock(midDef, mid) |
326-
defNullImpliesStep(midDef, mid, def, bb)
327-
)
328-
}
329-
330-
/**
331-
* Holds if `v` is a source variable that might reach a potential `null`
332-
* dereference.
333-
*/
334-
private predicate nullDerefCandidateVariable(Ssa::SourceVariable v) {
335-
exists(Ssa::Definition def, BasicBlock bb | dereferenceAt(bb, _, def, _) |
336-
defMaybeNullInBlock(def, bb) and
337-
v = def.getSourceVariable()
338-
)
339-
}
340-
341-
private predicate succStep(PathNode pred, Ssa::Definition def, BasicBlock bb) {
342-
defNullImpliesStep(pred.getSsaDefinition(), pred.getBasicBlock(), def, bb)
343-
}
344-
345-
private predicate succNullArgument(SourcePathNode pred, Ssa::Definition def, BasicBlock bb) {
346-
pred = TSourcePathNode(def, _, _, true) and
347-
bb = def.getBasicBlock()
348-
}
349-
350-
private predicate succSourceSink(SourcePathNode source, Ssa::Definition def, BasicBlock bb) {
351-
source = TSourcePathNode(def, _, _, false) and
352-
bb = def.getBasicBlock()
353-
}
354-
355-
private newtype TPathNode =
356-
TSourcePathNode(Ssa::Definition def, string msg, Element reason, boolean isNullArgument) {
357-
nullDerefCandidateVariable(def.getSourceVariable()) and
358-
defMaybeNull(def, _, msg, reason) and
359-
if isMaybeNullArgument(def, reason) then isNullArgument = true else isNullArgument = false
360-
} or
361-
TInternalPathNode(Ssa::Definition def, BasicBlock bb) {
362-
succStep(_, def, bb)
363-
or
364-
succNullArgument(_, def, bb)
365-
} or
366-
TSinkPathNode(Ssa::Definition def, BasicBlock bb, int i, Dereference d) {
367-
dereferenceAt(bb, i, def, d) and
368-
(
369-
succStep(_, def, bb)
370-
or
371-
succNullArgument(_, def, bb)
372-
or
373-
succSourceSink(_, def, bb)
374-
)
375-
}
376-
377-
/**
378-
* An SSA definition, which may be `null`, augmented with at basic block which can
379-
* be reached without passing through a `null` check.
380-
*/
381-
abstract class PathNode extends TPathNode {
382-
/** Gets the SSA definition. */
383-
abstract Ssa::Definition getSsaDefinition();
384-
385-
/** Gets the basic block that can be reached without passing through a `null` check. */
386-
abstract BasicBlock getBasicBlock();
387-
388-
/** Gets another node that can be reached from this node. */
389-
abstract PathNode getASuccessor();
390-
391-
/** Gets a textual representation of this node. */
392-
abstract string toString();
393-
394-
/** Gets the location of this node. */
395-
abstract Location getLocation();
396-
}
397-
398-
private class SourcePathNode extends PathNode, TSourcePathNode {
399-
private Ssa::Definition def;
400-
private string msg;
401-
private Element reason;
402-
private boolean isNullArgument;
403-
404-
SourcePathNode() { this = TSourcePathNode(def, msg, reason, isNullArgument) }
405-
406-
override Ssa::Definition getSsaDefinition() { result = def }
407-
408-
override BasicBlock getBasicBlock() {
409-
isNullArgument = false and
410-
result = def.getBasicBlock()
411-
}
412-
413-
string getMessage() { result = msg }
414-
415-
Element getReason() { result = reason }
416-
417-
override PathNode getASuccessor() {
418-
succStep(this, result.getSsaDefinition(), result.getBasicBlock())
419-
or
420-
succNullArgument(this, result.getSsaDefinition(), result.getBasicBlock())
421-
or
422-
result instanceof SinkPathNode and
423-
succSourceSink(this, result.getSsaDefinition(), result.getBasicBlock())
424-
}
425-
426-
override string toString() {
427-
if isNullArgument = true then result = reason.toString() else result = def.toString()
428-
}
429-
430-
override Location getLocation() {
431-
if isNullArgument = true then result = reason.getLocation() else result = def.getLocation()
432-
}
433-
}
434-
435-
private class InternalPathNode extends PathNode, TInternalPathNode {
436-
private Ssa::Definition def;
437-
private BasicBlock bb;
438-
439-
InternalPathNode() { this = TInternalPathNode(def, bb) }
440-
441-
override Ssa::Definition getSsaDefinition() { result = def }
442-
443-
override BasicBlock getBasicBlock() { result = bb }
444-
445-
override PathNode getASuccessor() {
446-
succStep(this, result.getSsaDefinition(), result.getBasicBlock())
447-
}
448-
449-
override string toString() { result = bb.getFirstNode().toString() }
450-
451-
override Location getLocation() { result = bb.getFirstNode().getLocation() }
452-
}
453-
454-
private class SinkPathNode extends PathNode, TSinkPathNode {
455-
private Ssa::Definition def;
456-
private BasicBlock bb;
457-
private int i;
458-
private Dereference d;
459-
460-
SinkPathNode() { this = TSinkPathNode(def, bb, i, d) }
461-
462-
override Ssa::Definition getSsaDefinition() { result = def }
463-
464-
override BasicBlock getBasicBlock() { result = bb }
465-
466-
override PathNode getASuccessor() { none() }
467-
468-
Dereference getDereference() { result = d }
469-
470-
override string toString() { result = d.toString() }
471-
472-
override Location getLocation() { result = d.getLocation() }
473-
}
474-
475-
/**
476-
* Provides the query predicates needed to include a graph in a path-problem query
477-
* for `Dereference::is[First]MaybeNull()`.
478-
*/
479-
module PathGraph {
480-
query predicate nodes(PathNode n) { n.getASuccessor*() instanceof SinkPathNode }
481-
482-
query predicate edges(PathNode pred, PathNode succ) {
483-
nodes(pred) and
484-
nodes(succ) and
485-
succ = pred.getASuccessor()
486-
}
487-
}
488-
489238
private Ssa::Definition getAPseudoInput(Ssa::Definition def) {
490239
result = def.(Ssa::PhiNode).getAnInput()
491240
}
@@ -499,21 +248,15 @@ private Ssa::Definition getAnUltimateDefinition(Ssa::Definition def) {
499248

500249
/**
501250
* Holds if SSA definition `def` can reach a read at `cfn`, without passing
502-
* through an intermediate dereference that always (`always = true`) or
503-
* maybe (`always = false`) throws a null reference exception.
251+
* through an intermediate dereference that always throws a null reference
252+
* exception.
504253
*/
505-
private predicate defReaches(Ssa::Definition def, ControlFlow::Node cfn, boolean always) {
506-
exists(def.getAFirstReadAtNode(cfn)) and
507-
(always = true or always = false)
254+
private predicate defReaches(Ssa::Definition def, ControlFlow::Node cfn) {
255+
exists(def.getAFirstReadAtNode(cfn))
508256
or
509-
exists(ControlFlow::Node mid | defReaches(def, mid, always) |
257+
exists(ControlFlow::Node mid | defReaches(def, mid) |
510258
SsaImpl::adjacentReadPairSameVar(_, mid, cfn) and
511-
not mid =
512-
any(Dereference d |
513-
if always = true
514-
then d.isAlwaysNull(def.getSourceVariable())
515-
else d.isMaybeNull(def, _, _, _, _)
516-
).getAControlFlowNode()
259+
not mid = any(Dereference d | d.isAlwaysNull(def.getSourceVariable())).getAControlFlowNode()
517260
)
518261
}
519262

@@ -534,7 +277,7 @@ private module NullnessConfig implements Cf::ControlFlowReachability::ConfigSig
534277

535278
private module NullnessFlow = Cf::ControlFlowReachability::Flow<NullnessConfig>;
536279

537-
predicate debug_nullness_new(Dereference d, Ssa::SourceVariable v, string msg, Element reason) {
280+
predicate maybeNullDeref(Dereference d, Ssa::SourceVariable v, string msg, Element reason) {
538281
exists(
539282
Ssa::Definition origin, Ssa::Definition ssa, ControlFlow::Node src, ControlFlow::Node sink
540283
|
@@ -546,11 +289,6 @@ predicate debug_nullness_new(Dereference d, Ssa::SourceVariable v, string msg, E
546289
)
547290
}
548291

549-
predicate debug_nullness_orig(Dereference d, Ssa::SourceVariable v, string msg, Element reason) {
550-
d.isFirstMaybeNull(v.getAnSsaDefinition(), _, _, msg, reason) and
551-
not d instanceof NonNullExpr
552-
}
553-
554292
/**
555293
* An expression that dereferences a value. That is, an expression that may
556294
* result in a `NullReferenceException` if the value is `null`.
@@ -645,33 +383,6 @@ class Dereference extends G::DereferenceableExpr {
645383
*/
646384
predicate isFirstAlwaysNull(Ssa::SourceVariable v) {
647385
this.isAlwaysNull(v) and
648-
defReaches(v.getAnSsaDefinition(), this.getAControlFlowNode(), true)
649-
}
650-
651-
/**
652-
* Holds if this expression dereferences SSA definition `def`, which may
653-
* be `null`.
654-
*/
655-
predicate isMaybeNull(
656-
Ssa::Definition def, SourcePathNode source, SinkPathNode sink, string msg, Element reason
657-
) {
658-
source.getASuccessor*() = sink and
659-
msg = source.getMessage() and
660-
reason = source.getReason() and
661-
def = sink.getSsaDefinition() and
662-
this = sink.getDereference() and
663-
not this.isAlwaysNull(def.getSourceVariable())
664-
}
665-
666-
/**
667-
* Holds if this expression dereferences SSA definition `def`, which may
668-
* be `null`, and this expression can be reached from `def` without passing
669-
* through another such dereference.
670-
*/
671-
predicate isFirstMaybeNull(
672-
Ssa::Definition def, SourcePathNode source, SinkPathNode sink, string msg, Element reason
673-
) {
674-
this.isMaybeNull(def, source, sink, msg, reason) and
675-
defReaches(def, this.getAControlFlowNode(), false)
386+
defReaches(v.getAnSsaDefinition(), this.getAControlFlowNode())
676387
}
677388
}

csharp/ql/src/CSI/NullMaybe.ql

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* @name Dereferenced variable may be null
33
* @description Dereferencing a variable whose value may be 'null' may cause a
44
* 'NullReferenceException'.
5-
* @kind path-problem
5+
* @kind problem
66
* @problem.severity warning
77
* @precision high
88
* @id cs/dereferenced-value-may-be-null
@@ -15,10 +15,7 @@
1515

1616
import csharp
1717
import semmle.code.csharp.dataflow.Nullness
18-
import PathGraph
1918

20-
from
21-
Dereference d, PathNode source, PathNode sink, Ssa::SourceVariable v, string msg, Element reason
22-
where d.isFirstMaybeNull(v.getAnSsaDefinition(), source, sink, msg, reason)
23-
select d, source, sink, "Variable $@ may be null at this access " + msg + ".", v, v.toString(),
24-
reason, "this"
19+
from Dereference d, Ssa::SourceVariable v, string msg, Element reason
20+
where maybeNullDeref(d, v, msg, reason)
21+
select d, "Variable $@ may be null at this access " + msg + ".", v, v.toString(), reason, "this"

0 commit comments

Comments
 (0)