Skip to content

Commit 09357e1

Browse files
authored
Merge pull request github#16533 from MathiasVP/better-negation-guards-in-c
C++: Make `IRGuardCondition` handle `p` in `if(p)` and `if(!p)` better in C programs
2 parents a992b67 + 5893e38 commit 09357e1

File tree

4 files changed

+124
-39
lines changed

4 files changed

+124
-39
lines changed

cpp/ql/lib/semmle/code/cpp/controlflow/IRGuards.qll

Lines changed: 104 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -565,7 +565,7 @@ class IRGuardCondition extends Instruction {
565565
/** Holds if (determined by this guard) `op == k` evaluates to `areEqual` if this expression evaluates to `value`. */
566566
cached
567567
predicate comparesEq(Operand op, int k, boolean areEqual, AbstractValue value) {
568-
compares_eq(this, op, k, areEqual, value)
568+
unary_compares_eq(this, op, k, areEqual, false, value)
569569
}
570570

571571
/**
@@ -586,7 +586,7 @@ class IRGuardCondition extends Instruction {
586586
cached
587587
predicate ensuresEq(Operand op, int k, IRBlock block, boolean areEqual) {
588588
exists(AbstractValue value |
589-
compares_eq(this, op, k, areEqual, value) and this.valueControls(block, value)
589+
unary_compares_eq(this, op, k, areEqual, false, value) and this.valueControls(block, value)
590590
)
591591
}
592592

@@ -611,7 +611,7 @@ class IRGuardCondition extends Instruction {
611611
cached
612612
predicate ensuresEqEdge(Operand op, int k, IRBlock pred, IRBlock succ, boolean areEqual) {
613613
exists(AbstractValue value |
614-
compares_eq(this, op, k, areEqual, value) and
614+
unary_compares_eq(this, op, k, areEqual, false, value) and
615615
this.valueControlsEdge(pred, succ, value)
616616
)
617617
}
@@ -737,26 +737,66 @@ private predicate compares_eq(
737737
)
738738
}
739739

740-
/** Holds if `op == k` is `areEqual` given that `test` is equal to `value`. */
741-
private predicate compares_eq(
742-
Instruction test, Operand op, int k, boolean areEqual, AbstractValue value
740+
/**
741+
* Holds if `op == k` is `areEqual` given that `test` is equal to `value`.
742+
*
743+
* Many internal predicates in this file have a `inNonZeroCase` column.
744+
* Ideally, the `k` column would be a type such as `Option<int>::Option`, to
745+
* represent whether we have a concrete value `k` such that `op == k`, or whether
746+
* we only know that `op != 0`.
747+
* However, cannot instantiate `Option` with an infinite type. Thus the boolean
748+
* `inNonZeroCase` is used to distinquish the `Some` (where we have a concrete
749+
* value `k`) and `None` cases (where we only know that `op != 0`).
750+
*
751+
* Thus, if `inNonZeroCase = true` then `op != 0` and the value of `k` is
752+
* meaningless.
753+
*
754+
* To see why `inNonZeroCase` is needed consider the following C program:
755+
* ```c
756+
* char* p = ...;
757+
* if(p) {
758+
* use(p);
759+
* }
760+
* ```
761+
* in C++ there would be an int-to-bool conversion on `p`. However, since C
762+
* does not have booleans there is no conversion. We want to be able to
763+
* conclude that `p` is non-zero in the true branch, so we need to give `k`
764+
* some value. However, simply setting `k = 1` would make the rest of the
765+
* analysis think that `k == 1` holds inside the branch. So we distinquish
766+
* between the above case and
767+
* ```c
768+
* if(p == 1) {
769+
* use(p)
770+
* }
771+
* ```
772+
* by setting `inNonZeroCase` to `true` in the former case, but not in the
773+
* latter.
774+
*/
775+
private predicate unary_compares_eq(
776+
Instruction test, Operand op, int k, boolean areEqual, boolean inNonZeroCase, AbstractValue value
743777
) {
744778
/* The simple case where the test *is* the comparison so areEqual = testIsTrue xor eq. */
745-
exists(AbstractValue v | simple_comparison_eq(test, op, k, v) |
779+
exists(AbstractValue v | unary_simple_comparison_eq(test, op, k, inNonZeroCase, v) |
746780
areEqual = true and value = v
747781
or
748782
areEqual = false and value = v.getDualValue()
749783
)
750784
or
751-
complex_eq(test, op, k, areEqual, value)
785+
unary_complex_eq(test, op, k, areEqual, inNonZeroCase, value)
752786
or
753787
/* (x is true => (op == k)) => (!x is false => (op == k)) */
754-
exists(AbstractValue dual | value = dual.getDualValue() |
755-
compares_eq(test.(LogicalNotInstruction).getUnary(), op, k, areEqual, dual)
788+
exists(AbstractValue dual, boolean inNonZeroCase0 |
789+
value = dual.getDualValue() and
790+
unary_compares_eq(test.(LogicalNotInstruction).getUnary(), op, k, inNonZeroCase0, areEqual, dual)
791+
|
792+
k = 0 and inNonZeroCase = inNonZeroCase0
793+
or
794+
k != 0 and inNonZeroCase = true
756795
)
757796
or
758797
// ((test is `areEqual` => op == const + k2) and const == `k1`) =>
759798
// test is `areEqual` => op == k1 + k2
799+
inNonZeroCase = false and
760800
exists(int k1, int k2, ConstantInstruction const |
761801
compares_eq(test, op, const.getAUse(), k2, areEqual, value) and
762802
int_value(const) = k1 and
@@ -781,35 +821,53 @@ private predicate simple_comparison_eq(
781821
value.(BooleanValue).getValue() = false
782822
}
783823

784-
/** Rearrange various simple comparisons into `op == k` form. */
785-
private predicate simple_comparison_eq(Instruction test, Operand op, int k, AbstractValue value) {
824+
/**
825+
* Holds if `test` is an instruction that is part of test that eventually is
826+
* used in a conditional branch.
827+
*/
828+
private predicate relevantUnaryComparison(Instruction test) {
829+
not test instanceof CompareInstruction and
830+
exists(IRType type, ConditionalBranchInstruction branch |
831+
type instanceof IRAddressType or type instanceof IRIntegerType
832+
|
833+
type = test.getResultIRType() and
834+
branch.getCondition() = test
835+
)
836+
or
837+
exists(LogicalNotInstruction logicalNot |
838+
relevantUnaryComparison(logicalNot) and
839+
test = logicalNot.getUnary()
840+
)
841+
}
842+
843+
/**
844+
* Rearrange various simple comparisons into `op == k` form.
845+
*/
846+
private predicate unary_simple_comparison_eq(
847+
Instruction test, Operand op, int k, boolean inNonZeroCase, AbstractValue value
848+
) {
786849
exists(SwitchInstruction switch, CaseEdge case |
787850
test = switch.getExpression() and
788851
op.getDef() = test and
789852
case = value.(MatchValue).getCase() and
790853
exists(switch.getSuccessor(case)) and
791-
case.getValue().toInt() = k
854+
case.getValue().toInt() = k and
855+
inNonZeroCase = false
792856
)
793857
or
794858
// There's no implicit CompareInstruction in files compiled as C since C
795859
// doesn't have implicit boolean conversions. So instead we check whether
796860
// there's a branch on a value of pointer or integer type.
797-
exists(ConditionalBranchInstruction branch, IRType type |
798-
not test instanceof CompareInstruction and
799-
type = test.getResultIRType() and
800-
(type instanceof IRAddressType or type instanceof IRIntegerType) and
801-
test = branch.getCondition() and
802-
op.getDef() = test
803-
|
804-
// We'd like to also include a case such as:
805-
// ```
806-
// k = 1 and
807-
// value.(BooleanValue).getValue() = true
808-
// ```
809-
// but all we know is that the value is non-zero in the true branch.
810-
// So we can only conclude something in the false branch.
861+
relevantUnaryComparison(test) and
862+
op.getDef() = test and
863+
(
864+
k = 1 and
865+
value.(BooleanValue).getValue() = true and
866+
inNonZeroCase = true
867+
or
811868
k = 0 and
812-
value.(BooleanValue).getValue() = false
869+
value.(BooleanValue).getValue() = false and
870+
inNonZeroCase = false
813871
)
814872
}
815873

@@ -821,12 +879,12 @@ private predicate complex_eq(
821879
add_eq(cmp, left, right, k, areEqual, value)
822880
}
823881

824-
private predicate complex_eq(
825-
Instruction test, Operand op, int k, boolean areEqual, AbstractValue value
882+
private predicate unary_complex_eq(
883+
Instruction test, Operand op, int k, boolean areEqual, boolean inNonZeroCase, AbstractValue value
826884
) {
827-
sub_eq(test, op, k, areEqual, value)
885+
unary_sub_eq(test, op, k, areEqual, inNonZeroCase, value)
828886
or
829-
add_eq(test, op, k, areEqual, value)
887+
unary_add_eq(test, op, k, areEqual, inNonZeroCase, value)
830888
}
831889

832890
/*
@@ -1090,16 +1148,20 @@ private predicate sub_eq(
10901148
}
10911149

10921150
// op - x == c => op == (c+x)
1093-
private predicate sub_eq(Instruction test, Operand op, int k, boolean areEqual, AbstractValue value) {
1151+
private predicate unary_sub_eq(
1152+
Instruction test, Operand op, int k, boolean areEqual, boolean inNonZeroCase, AbstractValue value
1153+
) {
1154+
inNonZeroCase = false and
10941155
exists(SubInstruction sub, int c, int x |
1095-
compares_eq(test, sub.getAUse(), c, areEqual, value) and
1156+
unary_compares_eq(test, sub.getAUse(), c, areEqual, inNonZeroCase, value) and
10961157
op = sub.getLeftOperand() and
10971158
x = int_value(sub.getRight()) and
10981159
k = c + x
10991160
)
11001161
or
1162+
inNonZeroCase = false and
11011163
exists(PointerSubInstruction sub, int c, int x |
1102-
compares_eq(test, sub.getAUse(), c, areEqual, value) and
1164+
unary_compares_eq(test, sub.getAUse(), c, areEqual, inNonZeroCase, value) and
11031165
op = sub.getLeftOperand() and
11041166
x = int_value(sub.getRight()) and
11051167
k = c + x
@@ -1153,11 +1215,13 @@ private predicate add_eq(
11531215
}
11541216

11551217
// left + x == right + c => left == right + (c-x)
1156-
private predicate add_eq(
1157-
Instruction test, Operand left, int k, boolean areEqual, AbstractValue value
1218+
private predicate unary_add_eq(
1219+
Instruction test, Operand left, int k, boolean areEqual, boolean inNonZeroCase,
1220+
AbstractValue value
11581221
) {
1222+
inNonZeroCase = false and
11591223
exists(AddInstruction lhs, int c, int x |
1160-
compares_eq(test, lhs.getAUse(), c, areEqual, value) and
1224+
unary_compares_eq(test, lhs.getAUse(), c, areEqual, inNonZeroCase, value) and
11611225
(
11621226
left = lhs.getLeftOperand() and x = int_value(lhs.getRight())
11631227
or
@@ -1166,8 +1230,9 @@ private predicate add_eq(
11661230
k = c - x
11671231
)
11681232
or
1233+
inNonZeroCase = false and
11691234
exists(PointerAddInstruction lhs, int c, int x |
1170-
compares_eq(test, lhs.getAUse(), c, areEqual, value) and
1235+
unary_compares_eq(test, lhs.getAUse(), c, areEqual, inNonZeroCase, value) and
11711236
(
11721237
left = lhs.getLeftOperand() and x = int_value(lhs.getRight())
11731238
or

cpp/ql/test/library-tests/controlflow/guards-ir/tests.expected

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,9 @@ astGuardsCompare
160160
| 137 | 0 == 0 when 0 is false |
161161
| 146 | ! ... != 0 when ! ... is true |
162162
| 146 | ! ... == 0 when ! ... is false |
163+
| 146 | x != 0 when ! ... is false |
164+
| 146 | x != 0 when x is true |
165+
| 146 | x == 0 when x is false |
163166
| 152 | x != 0 when ... && ... is true |
164167
| 152 | x != 0 when x is true |
165168
| 152 | x == 0 when x is false |
@@ -518,6 +521,7 @@ astGuardsEnsure_const
518521
| test.c:131:7:131:7 | b | test.c:131:7:131:7 | b | != | 0 | 131 | 132 |
519522
| test.c:137:7:137:7 | 0 | test.c:137:7:137:7 | 0 | == | 0 | 142 | 136 |
520523
| test.c:146:7:146:8 | ! ... | test.c:146:7:146:8 | ! ... | != | 0 | 146 | 147 |
524+
| test.c:146:8:146:8 | x | test.c:146:8:146:8 | x | == | 0 | 146 | 147 |
521525
| test.c:152:10:152:10 | x | test.c:152:10:152:10 | x | != | 0 | 151 | 152 |
522526
| test.c:152:10:152:10 | x | test.c:152:10:152:10 | x | != | 0 | 152 | 152 |
523527
| test.c:152:10:152:15 | ... && ... | test.c:152:10:152:10 | x | != | 0 | 151 | 152 |
@@ -689,6 +693,9 @@ irGuardsCompare
689693
| 137 | 0 == 0 when Constant: 0 is false |
690694
| 146 | ! ... != 0 when LogicalNot: ! ... is true |
691695
| 146 | ! ... == 0 when LogicalNot: ! ... is false |
696+
| 146 | x != 0 when Load: x is true |
697+
| 146 | x != 0 when LogicalNot: ! ... is false |
698+
| 146 | x == 0 when Load: x is false |
692699
| 152 | x != 0 when Load: x is true |
693700
| 152 | x == 0 when Load: x is false |
694701
| 152 | y != 0 when Load: y is true |
@@ -1063,6 +1070,7 @@ irGuardsEnsure_const
10631070
| test.c:131:7:131:7 | Load: b | test.c:131:7:131:7 | Load: b | != | 0 | 132 | 132 |
10641071
| test.c:137:7:137:7 | Constant: 0 | test.c:137:7:137:7 | Constant: 0 | == | 0 | 142 | 142 |
10651072
| test.c:146:7:146:8 | LogicalNot: ! ... | test.c:146:7:146:8 | LogicalNot: ! ... | != | 0 | 147 | 147 |
1073+
| test.c:146:8:146:8 | Load: x | test.c:146:8:146:8 | Load: x | == | 0 | 147 | 147 |
10661074
| test.c:152:10:152:10 | Load: x | test.c:152:10:152:10 | Load: x | != | 0 | 152 | 152 |
10671075
| test.c:152:15:152:15 | Load: y | test.c:152:15:152:15 | Load: y | != | 0 | 152 | 152 |
10681076
| test.c:175:13:175:32 | CompareEQ: ... == ... | test.c:175:13:175:15 | Call: call to foo | != | 0 | 175 | 175 |

cpp/ql/test/library-tests/controlflow/guards/GuardsCompare.expected

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,11 +161,20 @@
161161
| 137 | 0 == 0 when 0 is false |
162162
| 146 | ! ... != 0 when ! ... is true |
163163
| 146 | ! ... == 0 when ! ... is false |
164+
| 146 | x != 0 when ! ... is false |
165+
| 146 | x != 0 when x is true |
166+
| 146 | x == 0 when x is false |
164167
| 152 | p != 0 when p is true |
165168
| 152 | p == 0 when p is false |
166169
| 158 | ! ... != 0 when ! ... is true |
167170
| 158 | ! ... == 0 when ! ... is false |
171+
| 158 | p != 0 when ! ... is false |
172+
| 158 | p != 0 when p is true |
173+
| 158 | p == 0 when p is false |
168174
| 164 | s != 0 when s is true |
169175
| 164 | s == 0 when s is false |
170176
| 170 | ! ... != 0 when ! ... is true |
171177
| 170 | ! ... == 0 when ! ... is false |
178+
| 170 | s != 0 when ! ... is false |
179+
| 170 | s != 0 when s is true |
180+
| 170 | s == 0 when s is false |

cpp/ql/test/library-tests/controlflow/guards/GuardsEnsure.expected

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,10 +245,13 @@ unary
245245
| test.c:131:7:131:7 | b | test.c:131:7:131:7 | b | != | 0 | 131 | 132 |
246246
| test.c:137:7:137:7 | 0 | test.c:137:7:137:7 | 0 | == | 0 | 142 | 136 |
247247
| test.c:146:7:146:8 | ! ... | test.c:146:7:146:8 | ! ... | != | 0 | 146 | 147 |
248+
| test.c:146:8:146:8 | x | test.c:146:8:146:8 | x | == | 0 | 146 | 147 |
248249
| test.c:152:8:152:8 | p | test.c:152:8:152:8 | p | != | 0 | 152 | 154 |
249250
| test.c:158:8:158:9 | ! ... | test.c:158:8:158:9 | ! ... | != | 0 | 158 | 160 |
251+
| test.c:158:9:158:9 | p | test.c:158:9:158:9 | p | == | 0 | 158 | 160 |
250252
| test.c:164:8:164:8 | s | test.c:164:8:164:8 | s | != | 0 | 164 | 166 |
251253
| test.c:170:8:170:9 | ! ... | test.c:170:8:170:9 | ! ... | != | 0 | 170 | 172 |
254+
| test.c:170:9:170:9 | s | test.c:170:9:170:9 | s | == | 0 | 170 | 172 |
252255
| test.cpp:18:8:18:10 | call to get | test.cpp:18:8:18:10 | call to get | != | 0 | 19 | 19 |
253256
| test.cpp:31:7:31:13 | ... == ... | test.cpp:31:7:31:7 | x | != | -1 | 30 | 30 |
254257
| test.cpp:31:7:31:13 | ... == ... | test.cpp:31:7:31:7 | x | != | -1 | 34 | 34 |

0 commit comments

Comments
 (0)