Skip to content

Commit fcf04ab

Browse files
authored
Merge pull request #1120 from jcreedcmu/jcreed/nan
C++: Teach range analysis to pay attention to NaNs.
2 parents 886e524 + e52bbe7 commit fcf04ab

File tree

5 files changed

+102
-8
lines changed

5 files changed

+102
-8
lines changed

change-notes/1.21/analysis-cpp.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,6 @@
1515
| Memory may not be freed (`cpp/memory-may-not-be-freed`) | More correct results | Support added for more Microsoft-specific allocation functions, including `LocalAlloc`, `GlobalAlloc`, `HeapAlloc` and `CoTaskMemAlloc`. |
1616
| Memory is never freed (`cpp/memory-never-freed`) | More correct results | Support added for more Microsoft-specific allocation functions, including `LocalAlloc`, `GlobalAlloc`, `HeapAlloc` and `CoTaskMemAlloc`. |
1717
| Resource not released in destructor (`cpp/resource-not-released-in-destructor`) | Fewer false positive results | Resource allocation and deallocation functions are now determined more accurately. |
18+
| Comparison result is always the same | Fewer false positive results | The range analysis library is now more conservative about floating point values being possibly `NaN` |
1819

1920
## Changes to QL libraries
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import cpp
2+
private import semmle.code.cpp.rangeanalysis.RangeSSA
3+
4+
/**
5+
* Holds if `guard` won't return the value `polarity` when either
6+
* operand is NaN.
7+
*/
8+
predicate nanExcludingComparison(ComparisonOperation guard, boolean polarity) {
9+
polarity = true and
10+
(
11+
guard instanceof LTExpr or
12+
guard instanceof LEExpr or
13+
guard instanceof GTExpr or
14+
guard instanceof GEExpr or
15+
guard instanceof EQExpr
16+
)
17+
or
18+
polarity = false and
19+
guard instanceof NEExpr
20+
}
21+
22+
/**
23+
* Holds if `v` is a use of an SSA definition in `def` which cannot be NaN,
24+
* by virtue of the guard in `def`.
25+
*/
26+
private predicate excludesNan(RangeSsaDefinition def, VariableAccess v) {
27+
exists(VariableAccess inCond, ComparisonOperation guard, boolean branch, LocalScopeVariable lsv |
28+
def.isGuardPhi(inCond, guard, branch) and
29+
inCond.getTarget() = lsv and
30+
v = def.getAUse(lsv) and
31+
guard.getAnOperand() = inCond and
32+
nanExcludingComparison(guard, branch)
33+
)
34+
}
35+
36+
/**
37+
* A variable access which cannot be NaN.
38+
*/
39+
class NonNanVariableAccess extends VariableAccess {
40+
NonNanVariableAccess() { excludesNan(_, this) }
41+
}

cpp/ql/src/semmle/code/cpp/rangeanalysis/SimpleRangeAnalysis.qll

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import cpp
4545
private import RangeAnalysisUtils
4646
import RangeSSA
4747
import SimpleRangeAnalysisCached
48+
private import NanAnalysis
4849

4950
/**
5051
* This fixed set of lower bounds is used when the lower bounds of an
@@ -993,6 +994,25 @@ predicate unanalyzableDefBounds(
993994
ub = varMaxVal(v)
994995
}
995996

997+
/**
998+
* Holds if in the `branch` branch of a guard `guard` involving `v`,
999+
* we know that `v` is not NaN, and therefore it is safe to make range
1000+
* inferences about `v`.
1001+
*/
1002+
bindingset[guard, v, branch]
1003+
predicate nonNanGuardedVariable(ComparisonOperation guard, VariableAccess v, boolean branch) {
1004+
v.getType().getUnspecifiedType() instanceof IntegralType
1005+
or
1006+
v.getType().getUnspecifiedType() instanceof FloatingPointType and v instanceof NonNanVariableAccess
1007+
or
1008+
// The reason the following case is here is to ensure that when we say
1009+
// `if (x > 5) { ...then... } else { ...else... }`
1010+
// it is ok to conclude that `x > 5` in the `then`, (though not safe
1011+
// to conclude that x <= 5 in `else`) even if we had no prior
1012+
// knowledge of `x` not being `NaN`.
1013+
nanExcludingComparison(guard, branch)
1014+
}
1015+
9961016
/**
9971017
* If the guard is a comparison of the form `p*v + q <CMP> r`, then this
9981018
* predicate uses the bounds information for `r` to compute a lower bound
@@ -1004,10 +1024,12 @@ predicate lowerBoundFromGuard(
10041024
) {
10051025
exists (float childLB, RelationStrictness strictness
10061026
| boundFromGuard(guard, v, childLB, true, strictness, branch)
1007-
| if (strictness = Nonstrict() or
1008-
not (v.getType().getUnspecifiedType() instanceof IntegralType))
1009-
then lb = childLB
1010-
else lb = childLB+1)
1027+
| if nonNanGuardedVariable(guard, v, branch)
1028+
then (if (strictness = Nonstrict() or
1029+
not (v.getType().getUnspecifiedType() instanceof IntegralType))
1030+
then lb = childLB
1031+
else lb = childLB+1)
1032+
else lb = varMinVal(v.getTarget()))
10111033
}
10121034

10131035
/**
@@ -1021,10 +1043,12 @@ predicate upperBoundFromGuard(
10211043
) {
10221044
exists (float childUB, RelationStrictness strictness
10231045
| boundFromGuard(guard, v, childUB, false, strictness, branch)
1024-
| if (strictness = Nonstrict() or
1025-
not (v.getType().getUnspecifiedType() instanceof IntegralType))
1026-
then ub = childUB
1027-
else ub = childUB-1)
1046+
| if nonNanGuardedVariable(guard, v, branch)
1047+
then (if (strictness = Nonstrict() or
1048+
not (v.getType().getUnspecifiedType() instanceof IntegralType))
1049+
then ub = childUB
1050+
else ub = childUB-1)
1051+
else ub = varMaxVal(v.getTarget()))
10281052
}
10291053

10301054
/**

cpp/ql/test/query-tests/Likely Bugs/Arithmetic/PointlessComparison/PointlessComparison.c

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,3 +265,30 @@ int negative_zero(double dbl) {
265265
}
266266
return 0;
267267
}
268+
269+
int nan(double x) {
270+
if (x < 0.0) {
271+
return 100;
272+
}
273+
else if (x >= 0.0) { // GOOD [x could be NaN]
274+
return 200;
275+
}
276+
else {
277+
return 300;
278+
}
279+
}
280+
281+
int nan2(double x) {
282+
if (x == x) {
283+
// If x compares with anything at all, it's not NaN
284+
if (x < 0.0) {
285+
return 100;
286+
}
287+
else if (x >= 0.0) { // BAD [Always true]
288+
return 200;
289+
}
290+
else {
291+
return 300;
292+
}
293+
}
294+
}

cpp/ql/test/query-tests/Likely Bugs/Arithmetic/PointlessComparison/PointlessComparison.expected

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,6 @@
3232
| PointlessComparison.c:129:12:129:16 | ... > ... | Comparison is always false because a <= 3. |
3333
| PointlessComparison.c:197:7:197:11 | ... < ... | Comparison is always false because x >= 0. |
3434
| PointlessComparison.c:264:12:264:22 | ... >= ... | Comparison is always true because dbl >= 0 and -0 >= - .... |
35+
| PointlessComparison.c:287:14:287:21 | ... >= ... | Comparison is always true because x >= 0. |
3536
| RegressionTests.cpp:57:7:57:22 | ... <= ... | Comparison is always true because * ... <= 4294967295. |
3637
| Templates.cpp:9:10:9:24 | ... <= ... | Comparison is always true because local <= 32767. |

0 commit comments

Comments
 (0)