Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* `AnalysedExpr::isNullCheck` and `AnalysedExpr::isValidCheck` have been updated to handle variable accesses on the left-hand side of the the C++ logical and variable declarations in conditions.
9 changes: 6 additions & 3 deletions cpp/ql/lib/semmle/code/cpp/controlflow/Nullness.qll
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ predicate nullCheckExpr(Expr checkExpr, Variable var) {
or
exists(LogicalAndExpr op, AnalysedExpr child |
expr = op and
op.getRightOperand() = child and
op.getAnOperand() = child and
nullCheckExpr(child, v)
)
or
Expand Down Expand Up @@ -99,7 +99,7 @@ predicate validCheckExpr(Expr checkExpr, Variable var) {
or
exists(LogicalAndExpr op, AnalysedExpr child |
expr = op and
op.getRightOperand() = child and
op.getAnOperand() = child and
validCheckExpr(child, v)
)
or
Expand Down Expand Up @@ -169,7 +169,10 @@ class AnalysedExpr extends Expr {
*/
predicate isDef(LocalScopeVariable v) {
this.inCondition() and
this.(Assignment).getLValue() = v.getAnAccess()
(
this.(Assignment).getLValue() = v.getAnAccess() or
this.(ConditionDeclExpr).getVariableAccess() = v.getAnAccess()
)
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,14 @@
| test.cpp:15:8:15:23 | call to __builtin_expect | test.cpp:5:13:5:13 | v | is not null | is valid |
| test.cpp:16:8:16:23 | call to __builtin_expect | test.cpp:5:13:5:13 | v | is null | is not valid |
| test.cpp:17:9:17:17 | ... && ... | test.cpp:5:13:5:13 | v | is not null | is valid |
| test.cpp:18:9:18:17 | ... && ... | test.cpp:5:13:5:13 | v | is not null | is not valid |
| test.cpp:18:9:18:17 | ... && ... | test.cpp:5:13:5:13 | v | is not null | is valid |
| test.cpp:19:9:19:18 | ... && ... | test.cpp:5:13:5:13 | v | is null | is not valid |
| test.cpp:20:9:20:18 | ... && ... | test.cpp:5:13:5:13 | v | is not null | is not valid |
| test.cpp:20:9:20:18 | ... && ... | test.cpp:5:13:5:13 | v | is null | is not valid |
| test.cpp:21:9:21:14 | ... = ... | test.cpp:5:13:5:13 | v | is null | is not valid |
| test.cpp:21:9:21:14 | ... = ... | test.cpp:7:10:7:10 | b | is not null | is valid |
| test.cpp:22:17:22:17 | b | test.cpp:7:10:7:10 | b | is not null | is valid |
| test.cpp:22:9:22:14 | ... = ... | test.cpp:5:13:5:13 | v | is not null | is not valid |
| test.cpp:22:9:22:14 | ... = ... | test.cpp:7:13:7:13 | c | is not null | is not valid |
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please would you explain these two results? It looks to me like if c = !v is true, c will be true (only after the assignment) and v must be false.

If this is just a limitation of the analysis, I'm happy with that and happy for it to be documented by this test. I just want to check I'm understanding correctly.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, those are a bit weird. There's two things going on here:

  1. RHSs of assignments are not covered, as they're technically not part of a condition. So, those expressions for each variable will always result in is not null and is not valid
  2. The c case is a C++17-style if-initializer (note the c in the condition on the next line of the expected file). These also fall outside the condition that is evaluated in the if, and hence we also get is not null and is not valid there.

| test.cpp:22:17:22:17 | c | test.cpp:7:13:7:13 | c | is not null | is valid |
| test.cpp:23:21:23:21 | x | test.cpp:23:14:23:14 | x | is not null | is valid |
| test.cpp:24:9:24:18 | (condition decl) | test.cpp:5:13:5:13 | v | is not null | is not valid |
| test.cpp:24:9:24:18 | (condition decl) | test.cpp:24:14:24:14 | y | is not null | is valid |
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import cpp

from AnalysedExpr a, LocalScopeVariable v, string isNullCheck, string isValidCheck
where
a.getParent() instanceof IfStmt and
v.getAnAccess().getEnclosingStmt() = a.getParent() and
(if a.isNullCheck(v) then isNullCheck = "is null" else isNullCheck = "is not null") and
(if a.isValidCheck(v) then isValidCheck = "is valid" else isValidCheck = "is not valid")
Expand Down
6 changes: 4 additions & 2 deletions cpp/ql/test/library-tests/controlflow/nullness/test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ long __builtin_expect(long);

void f(int *v) {
int *w;
bool b;
bool b, c;

if (v) {}
if (!v) {}
Expand All @@ -19,5 +19,7 @@ void f(int *v) {
if (true && !v) {}
if (!v && true) {}
if (b = !v) {}
if (b = !v; b) {}
if (c = !v; c) {}
if (int *x = v; x) {}
if (int *y = v) {}
}