Skip to content

Commit 6601327

Browse files
authored
Merge pull request #894 from jbj/ir-RedundantNullCheckSimple
C++: IR query for redundant null check
2 parents e6ddf7f + 9f2fdbb commit 6601327

File tree

6 files changed

+149
-0
lines changed

6 files changed

+149
-0
lines changed

cpp/config/suites/c/experimental

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
+ semmlecode-cpp-queries/Likely Bugs/RedundantNullCheckSimple.ql: /Correctness/Common Errors

cpp/config/suites/cpp/experimental

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
+ semmlecode-cpp-queries/Likely Bugs/RedundantNullCheckSimple.ql: /Correctness/Common Errors
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/**
2+
* @name Redundant null check due to previous dereference
3+
* @description Checking a pointer for nullness after dereferencing it is
4+
* likely to be a sign that either the check can be removed, or
5+
* it should be moved before the dereference.
6+
* @kind problem
7+
* @problem.severity error
8+
* @id cpp/redundant-null-check-simple
9+
* @tags reliability
10+
* correctness
11+
* external/cwe/cwe-476
12+
*/
13+
14+
/*
15+
* Note: this query is not assigned a precision yet because we don't want it on
16+
* LGTM until its performance is well understood. It's also lacking qhelp.
17+
*/
18+
19+
import semmle.code.cpp.ir.IR
20+
21+
class NullInstruction extends ConstantValueInstruction {
22+
NullInstruction() {
23+
this.getValue() = "0" and
24+
this.getResultType().getUnspecifiedType() instanceof PointerType
25+
}
26+
}
27+
28+
/**
29+
* An instruction that will never have slicing on its result.
30+
*/
31+
class SingleValuedInstruction extends Instruction {
32+
SingleValuedInstruction() {
33+
this.getResultMemoryAccess() instanceof IndirectMemoryAccess
34+
or
35+
not this.hasMemoryResult()
36+
}
37+
}
38+
39+
predicate explicitNullTestOfInstruction(Instruction checked, Instruction bool) {
40+
bool = any(CompareInstruction cmp |
41+
exists(NullInstruction null |
42+
cmp.getLeft() = null and cmp.getRight() = checked
43+
or
44+
cmp.getLeft() = checked and cmp.getRight() = null
45+
|
46+
cmp instanceof CompareEQInstruction
47+
or
48+
cmp instanceof CompareNEInstruction
49+
)
50+
)
51+
or
52+
bool = any(ConvertInstruction convert |
53+
checked = convert.getUnary() and
54+
convert.getResultType() instanceof BoolType and
55+
checked.getResultType() instanceof PointerType
56+
)
57+
}
58+
59+
from LoadInstruction checked, LoadInstruction deref, SingleValuedInstruction sourceValue
60+
where
61+
explicitNullTestOfInstruction(checked, _) and
62+
sourceValue = deref.getSourceAddress().(LoadInstruction).getSourceValue() and
63+
sourceValue = checked.getSourceValue() and
64+
// This also holds if the blocks are equal, meaning that the check could come
65+
// before the deref. That's still not okay because when they're in the same
66+
// basic block then the deref is unavoidable even if the check concluded that
67+
// the pointer was null. To follow this idea to its full generality, we
68+
// should also give an alert when `check` post-dominates `deref`.
69+
deref.getBlock().dominates(checked.getBlock()) and
70+
not checked.getAST().isInMacroExpansion()
71+
select checked, "This null check is redundant because the value is $@ in any case", deref,
72+
"dereferenced here"
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
void test_simple_bad(int *p) {
2+
int x;
3+
x = *p;
4+
if (p == nullptr) { // BAD
5+
return;
6+
}
7+
}
8+
9+
void test_not_same_basic_block(int *p) {
10+
int x = *p;
11+
if (x > 100)
12+
return;
13+
if (!p) // BAD
14+
return;
15+
}
16+
17+
void test_indirect(int **p) {
18+
int x;
19+
x = **p;
20+
if (*p == nullptr) { // BAD [NOT DETECTED]
21+
return;
22+
}
23+
}
24+
25+
struct ContainsIntPtr {
26+
int **intPtr;
27+
};
28+
29+
bool check_curslist(ContainsIntPtr *cip) {
30+
// both the deref and the null check come from the same instruction, but it's
31+
// an AliasedDefinition instruction.
32+
return *cip->intPtr != nullptr; // GOOD
33+
}
34+
35+
void test_no_single_dominator(int *p, bool b) {
36+
int x;
37+
if (b) {
38+
x = *p;
39+
} else {
40+
x = *p;
41+
}
42+
if (p == nullptr) { // BAD [NOT DETECTED]
43+
return;
44+
}
45+
}
46+
47+
int test_postdominator_same_bb(int *p) {
48+
int b = (p == nullptr); // BAD
49+
// This dereference is a postdominator of the null check, meaning that all
50+
// paths from the check to the function exit will pass through it.
51+
return *p + b;
52+
}
53+
54+
int test_postdominator(int *p) {
55+
int b = (p == nullptr); // BAD [NOT DETECTED]
56+
57+
if (b) b++; // This line breaks up the basic block
58+
59+
// This dereference is a postdominator of the null check, meaning that all
60+
// paths from the check to the function exit will pass through it.
61+
return *p + b;
62+
}
63+
64+
int test_inverted_logic(int *p) {
65+
if (p == nullptr) { // BAD [NOT DETECTED]
66+
// The check above should probably have been `!=` instead of `==`.
67+
return *p;
68+
} else {
69+
return 0;
70+
}
71+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
| RedundantNullCheckSimple.cpp:4:7:4:7 | Load: p | This null check is redundant because the value is $@ in any case | RedundantNullCheckSimple.cpp:3:7:3:8 | Load: * ... | dereferenced here |
2+
| RedundantNullCheckSimple.cpp:13:8:13:8 | Load: p | This null check is redundant because the value is $@ in any case | RedundantNullCheckSimple.cpp:10:11:10:12 | Load: * ... | dereferenced here |
3+
| RedundantNullCheckSimple.cpp:48:12:48:12 | Load: p | This null check is redundant because the value is $@ in any case | RedundantNullCheckSimple.cpp:51:10:51:11 | Load: * ... | dereferenced here |
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Likely Bugs/RedundantNullCheckSimple.ql

0 commit comments

Comments
 (0)