Skip to content

Commit b8d2896

Browse files
committed
C++: Convert 'cpp/redundant-null-check-simple' to a path-problem query and assigned it precision high.
1 parent 52bc43b commit b8d2896

File tree

1 file changed

+151
-8
lines changed

1 file changed

+151
-8
lines changed

cpp/ql/src/Likely Bugs/RedundantNullCheckSimple.ql

Lines changed: 151 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,30 +3,32 @@
33
* @description Checking a pointer for nullness after dereferencing it is
44
* likely to be a sign that either the check can be removed, or
55
* it should be moved before the dereference.
6-
* @kind problem
6+
* @kind path-problem
77
* @problem.severity error
8+
* @precision high
89
* @id cpp/redundant-null-check-simple
910
* @tags reliability
1011
* correctness
1112
* external/cwe/cwe-476
1213
*/
1314

14-
/*
15-
* Note: this query is not assigned a precision yet because we don't want it
16-
* to be included in query suites until its performance is well understood.
17-
*/
18-
1915
import cpp
2016
import semmle.code.cpp.ir.IR
2117
import semmle.code.cpp.ir.ValueNumbering
18+
import PathGraph
2219

20+
/** An instruction that represents a null pointer. */
2321
class NullInstruction extends ConstantValueInstruction {
2422
NullInstruction() {
2523
this.getValue() = "0" and
2624
this.getResultIRType() instanceof IRAddressType
2725
}
2826
}
2927

28+
/**
29+
* Holds if `checked` is an instruction that is checked against a null value,
30+
* and `bool` is the instruction that represents the result of the comparison
31+
*/
3032
predicate explicitNullTestOfInstruction(Instruction checked, Instruction bool) {
3133
bool =
3234
any(CompareInstruction cmp |
@@ -49,14 +51,155 @@ predicate explicitNullTestOfInstruction(Instruction checked, Instruction bool) {
4951
)
5052
}
5153

52-
pragma[noinline]
54+
pragma[nomagic]
5355
predicate candidateResult(LoadInstruction checked, ValueNumber value, IRBlock dominator) {
5456
explicitNullTestOfInstruction(checked, _) and
5557
not checked.getAst().isInMacroExpansion() and
5658
value.getAnInstruction() = checked and
5759
dominator.dominates(checked.getBlock())
5860
}
5961

62+
/**
63+
* This module constructs a pretty edges relation out of the results produced by
64+
* the `candidateResult` predicate: We create a path using the instruction successor-
65+
* relation from the dereference to the null check. To avoid generating very long paths,
66+
* we compact the edges relation so that `edges(i1, i2)` only holds when `i2` is the first
67+
* instruction that is control-flow reachable from `i1` with the same global value number
68+
* as `i1`.
69+
*/
70+
module PathGraph {
71+
/**
72+
* Holds if `deref` is a load instruction that loads a value
73+
* from the address `address`. This predicate is restricted to
74+
* those pairs for which we will end up reporting a result.
75+
*/
76+
private predicate isSource(Instruction address, LoadInstruction deref) {
77+
exists(ValueNumber sourceValue |
78+
candidateResult(_, sourceValue, deref.getBlock()) and
79+
sourceValue.getAnInstruction() = address and
80+
deref.getSourceAddress() = address
81+
)
82+
}
83+
84+
/**
85+
* Holds if `checked` has global value number `vn` and is an instruction that is
86+
* used in a check against a null value.
87+
*/
88+
private predicate isSink(LoadInstruction checked, ValueNumber vn) {
89+
candidateResult(checked, vn, _)
90+
}
91+
92+
/** Holds if `i` is control-flow reachable from a relevant `LoadInstruction`. */
93+
private predicate fwdFlow(Instruction i) {
94+
isSource(i, _)
95+
or
96+
exists(Instruction mid |
97+
fwdFlow(mid) and
98+
mid.getASuccessor() = i
99+
)
100+
}
101+
102+
/**
103+
* Holds if `i` is part of a path from a relevant `LoadInstruction` to a
104+
* check against a null value that compares a value against an instruction
105+
* with global value number `vn`.
106+
*/
107+
private predicate revFlow(Instruction i, ValueNumber vn) {
108+
fwdFlow(i) and
109+
(
110+
isSink(i, vn)
111+
or
112+
exists(Instruction mid |
113+
revFlow(mid, vn) and
114+
i.getASuccessor() = mid
115+
)
116+
)
117+
}
118+
119+
/**
120+
* Gets a first control-flow successor of `i` that has the same
121+
* global value number as `i`.
122+
*/
123+
private Instruction getASuccessor(Instruction i) {
124+
exists(ValueNumber vn |
125+
vn.getAnInstruction() = i and
126+
result = getASuccessorWithValueNumber(i, vn)
127+
)
128+
}
129+
130+
/**
131+
* Gets a first control-flow successor of `i` that has the same
132+
* global value number as `i`. Furthermore, `i` has global value
133+
* number `vn`.
134+
*/
135+
private Instruction getASuccessorWithValueNumber(Instruction i, ValueNumber vn) {
136+
revFlow(i, vn) and
137+
result = getASuccessorWithValueNumber0(vn, i.getASuccessor()) and
138+
vn.getAnInstruction() = i
139+
}
140+
141+
pragma[nomagic]
142+
private Instruction getASuccessorWithValueNumber0(ValueNumber vn, Instruction i) {
143+
result = getASuccessorIfDifferentValueNumberTC(vn, i) and
144+
vn.getAnInstruction() = result
145+
}
146+
147+
/**
148+
* Computes the reflextive transitive closure of `getASuccessorIfDifferentValueNumber`.
149+
*/
150+
private Instruction getASuccessorIfDifferentValueNumberTC(ValueNumber vn, Instruction i) {
151+
revFlow(i, vn) and
152+
(
153+
i = result and
154+
vn.getAnInstruction() = i
155+
or
156+
exists(Instruction mid |
157+
mid = getASuccessorIfDifferentValueNumber(vn, i) and
158+
result = getASuccessorIfDifferentValueNumberTC(vn, mid)
159+
)
160+
)
161+
}
162+
163+
/**
164+
* Gets an instruction that is a control-flow successor of `i` and which is not assigned
165+
* the global value number `vn`.
166+
*/
167+
private Instruction getASuccessorIfDifferentValueNumber(ValueNumber vn, Instruction i) {
168+
revFlow(i, vn) and
169+
revFlow(result, vn) and
170+
not vn.getAnInstruction() = i and
171+
pragma[only_bind_into](result) = pragma[only_bind_into](i).getASuccessor()
172+
}
173+
174+
query predicate nodes(Instruction i, string key, string val) {
175+
revFlow(i, _) and
176+
key = "semmle.label" and
177+
val = i.getAst().toString()
178+
}
179+
180+
/**
181+
* The control-flow successor relation, compacted by stepping
182+
* over instruction that don't preserve the global value number.
183+
*
184+
* There is one exception to the above preservation rule: The
185+
* initial step from the `LoadInstruction` (that is, the sink)
186+
* steps to the first control-flow reachable instruction that
187+
* has the same value number as the load instruction's address
188+
* operand.
189+
*/
190+
query predicate edges(Instruction i1, Instruction i2) {
191+
getASuccessor(i1) = i2
192+
or
193+
// We could write `isSource(i2, i1)` here, but that would
194+
// include a not-very-informative step from `*p` to `p`.
195+
// So we collapse `*p` -> `p` -> `q` to `*p` -> `q`.
196+
exists(Instruction mid |
197+
isSource(mid, i1) and
198+
getASuccessor(mid) = i2
199+
)
200+
}
201+
}
202+
60203
from LoadInstruction checked, LoadInstruction deref, ValueNumber sourceValue, IRBlock dominator
61204
where
62205
candidateResult(checked, sourceValue, dominator) and
@@ -67,5 +210,5 @@ where
67210
// the pointer was null. To follow this idea to its full generality, we
68211
// should also give an alert when `check` post-dominates `deref`.
69212
deref.getBlock() = dominator
70-
select checked, "This null check is redundant because $@ in any case.", deref,
213+
select checked, deref, checked, "This null check is redundant because $@ in any case.", deref,
71214
"the value is dereferenced"

0 commit comments

Comments
 (0)