Skip to content

Commit e9dba59

Browse files
authored
Merge branch 'main' into main
2 parents 1d44f45 + 317790e commit e9dba59

File tree

17 files changed

+398
-68
lines changed

17 files changed

+398
-68
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: minorAnalysis
3+
---
4+
* The "Guards" library (`semmle.code.cpp.controlflow.Guards`) now also infers guards from calls to the builtin operation `__builtin_expect`. As a result, some queries may produce fewer false positives.

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

Lines changed: 133 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,33 @@ cached
375375
class IRGuardCondition extends Instruction {
376376
Instruction branch;
377377

378+
/*
379+
* An `IRGuardCondition` supports reasoning about four different kinds of
380+
* relations:
381+
* 1. A unary equality relation of the form `e == k`
382+
* 2. A binary equality relation of the form `e1 == e2 + k`
383+
* 3. A unary inequality relation of the form `e < k`
384+
* 4. A binary inequality relation of the form `e1 < e2 + k`
385+
*
386+
* where `k` is a constant.
387+
*
388+
* Furthermore, the unary relations (i.e., case 1 and case 3) are also
389+
* inferred from `switch` statement guards: equality relations are inferred
390+
* from the unique `case` statement, if any, and inequality relations are
391+
* inferred from the [case range](https://gcc.gnu.org/onlinedocs/gcc/Case-Ranges.html)
392+
* gcc extension.
393+
*
394+
* The implementation of all four follows the same structure: Each relation
395+
* has a cached user-facing predicate that. For example,
396+
* `GuardCondition::comparesEq` calls `compares_eq`. This predicate has
397+
* several cases that recursively decompose the relation to bring it to a
398+
* canonical form (i.e., a relation of the form `e1 == e2 + k`). The base
399+
* case for this relation (i.e., `simple_comparison_eq`) handles
400+
* `CompareEQInstruction`s and `CompareNEInstruction`, and recursive
401+
* predicates (e.g., `complex_eq`) rewrites larger expressions such as
402+
* `e1 + k1 == e2 + k2` into canonical the form `e1 == e2 + (k2 - k1)`.
403+
*/
404+
378405
cached
379406
IRGuardCondition() { branch = getBranchForCondition(this) }
380407

@@ -735,6 +762,8 @@ private predicate compares_eq(
735762
exists(AbstractValue dual | value = dual.getDualValue() |
736763
compares_eq(test.(LogicalNotInstruction).getUnary(), left, right, k, areEqual, dual)
737764
)
765+
or
766+
compares_eq(test.(BuiltinExpectCallInstruction).getCondition(), left, right, k, areEqual, value)
738767
}
739768

740769
/**
@@ -776,7 +805,9 @@ private predicate unary_compares_eq(
776805
Instruction test, Operand op, int k, boolean areEqual, boolean inNonZeroCase, AbstractValue value
777806
) {
778807
/* The simple case where the test *is* the comparison so areEqual = testIsTrue xor eq. */
779-
exists(AbstractValue v | unary_simple_comparison_eq(test, op, k, inNonZeroCase, v) |
808+
exists(AbstractValue v |
809+
unary_simple_comparison_eq(test, k, inNonZeroCase, v) and op.getDef() = test
810+
|
780811
areEqual = true and value = v
781812
or
782813
areEqual = false and value = v.getDualValue()
@@ -802,6 +833,9 @@ private predicate unary_compares_eq(
802833
int_value(const) = k1 and
803834
k = k1 + k2
804835
)
836+
or
837+
unary_compares_eq(test.(BuiltinExpectCallInstruction).getCondition(), op, k, areEqual,
838+
inNonZeroCase, value)
805839
}
806840

807841
/** Rearrange various simple comparisons into `left == right + k` form. */
@@ -821,45 +855,55 @@ private predicate simple_comparison_eq(
821855
value.(BooleanValue).getValue() = false
822856
}
823857

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-
843858
/**
844859
* Rearrange various simple comparisons into `op == k` form.
845860
*/
846861
private predicate unary_simple_comparison_eq(
847-
Instruction test, Operand op, int k, boolean inNonZeroCase, AbstractValue value
862+
Instruction test, int k, boolean inNonZeroCase, AbstractValue value
848863
) {
849864
exists(SwitchInstruction switch, CaseEdge case |
850865
test = switch.getExpression() and
851-
op.getDef() = test and
852866
case = value.(MatchValue).getCase() and
853867
exists(switch.getSuccessor(case)) and
854868
case.getValue().toInt() = k and
855869
inNonZeroCase = false
856870
)
857871
or
858-
// There's no implicit CompareInstruction in files compiled as C since C
859-
// doesn't have implicit boolean conversions. So instead we check whether
860-
// there's a branch on a value of pointer or integer type.
861-
relevantUnaryComparison(test) and
862-
op.getDef() = test and
872+
// Any instruction with an integral type could potentially be part of a
873+
// check for nullness when used in a guard. So we include all integral
874+
// typed instructions here. However, since some of these instructions are
875+
// already included as guards in other cases, we exclude those here.
876+
// These are instructions that compute a binary equality or inequality
877+
// relation. For example, the following:
878+
// ```cpp
879+
// if(a == b + 42) { ... }
880+
// ```
881+
// generates the following IR:
882+
// ```
883+
// r1(glval<int>) = VariableAddress[a] :
884+
// r2(int) = Load[a] : &:r1, m1
885+
// r3(glval<int>) = VariableAddress[b] :
886+
// r4(int) = Load[b] : &:r3, m2
887+
// r5(int) = Constant[42] :
888+
// r6(int) = Add : r4, r5
889+
// r7(bool) = CompareEQ : r2, r6
890+
// v1(void) = ConditionalBranch : r7
891+
// ```
892+
// and since `r7` is an integral typed instruction this predicate could
893+
// include a case for when `r7` evaluates to true (in which case we would
894+
// infer that `r6` was non-zero, and a case for when `r7` evaluates to false
895+
// (in which case we would infer that `r6` was zero).
896+
// However, since `a == b + 42` is already supported when reasoning about
897+
// binary equalities we exclude those cases here.
898+
not test.isGLValue() and
899+
not simple_comparison_eq(test, _, _, _, _) and
900+
not simple_comparison_lt(test, _, _, _) and
901+
not test = any(SwitchInstruction switch).getExpression() and
902+
(
903+
test.getResultIRType() instanceof IRAddressType or
904+
test.getResultIRType() instanceof IRIntegerType or
905+
test.getResultIRType() instanceof IRBooleanType
906+
) and
863907
(
864908
k = 1 and
865909
value.(BooleanValue).getValue() = true and
@@ -871,12 +915,68 @@ private predicate unary_simple_comparison_eq(
871915
)
872916
}
873917

918+
/** A call to the builtin operation `__builtin_expect`. */
919+
private class BuiltinExpectCallInstruction extends CallInstruction {
920+
BuiltinExpectCallInstruction() { this.getStaticCallTarget().hasName("__builtin_expect") }
921+
922+
/** Gets the condition of this call. */
923+
Instruction getCondition() {
924+
// The first parameter of `__builtin_expect` has type `long`. So we skip
925+
// the conversion when inferring guards.
926+
result = this.getArgument(0).(ConvertInstruction).getUnary()
927+
}
928+
}
929+
930+
/**
931+
* Holds if `left == right + k` is `areEqual` if `cmp` evaluates to `value`,
932+
* and `cmp` is an instruction that compares the value of
933+
* `__builtin_expect(left == right + k, _)` to `0`.
934+
*/
935+
private predicate builtin_expect_eq(
936+
CompareInstruction cmp, Operand left, Operand right, int k, boolean areEqual, AbstractValue value
937+
) {
938+
exists(BuiltinExpectCallInstruction call, Instruction const, AbstractValue innerValue |
939+
int_value(const) = 0 and
940+
cmp.hasOperands(call.getAUse(), const.getAUse()) and
941+
compares_eq(call.getCondition(), left, right, k, areEqual, innerValue)
942+
|
943+
cmp instanceof CompareNEInstruction and
944+
value = innerValue
945+
or
946+
cmp instanceof CompareEQInstruction and
947+
value.getDualValue() = innerValue
948+
)
949+
}
950+
874951
private predicate complex_eq(
875952
CompareInstruction cmp, Operand left, Operand right, int k, boolean areEqual, AbstractValue value
876953
) {
877954
sub_eq(cmp, left, right, k, areEqual, value)
878955
or
879956
add_eq(cmp, left, right, k, areEqual, value)
957+
or
958+
builtin_expect_eq(cmp, left, right, k, areEqual, value)
959+
}
960+
961+
/**
962+
* Holds if `op == k` is `areEqual` if `cmp` evaluates to `value`, and `cmp` is
963+
* an instruction that compares the value of `__builtin_expect(op == k, _)` to `0`.
964+
*/
965+
private predicate unary_builtin_expect_eq(
966+
CompareInstruction cmp, Operand op, int k, boolean areEqual, boolean inNonZeroCase,
967+
AbstractValue value
968+
) {
969+
exists(BuiltinExpectCallInstruction call, Instruction const, AbstractValue innerValue |
970+
int_value(const) = 0 and
971+
cmp.hasOperands(call.getAUse(), const.getAUse()) and
972+
unary_compares_eq(call.getCondition(), op, k, areEqual, inNonZeroCase, innerValue)
973+
|
974+
cmp instanceof CompareNEInstruction and
975+
value = innerValue
976+
or
977+
cmp instanceof CompareEQInstruction and
978+
value.getDualValue() = innerValue
979+
)
880980
}
881981

882982
private predicate unary_complex_eq(
@@ -885,6 +985,8 @@ private predicate unary_complex_eq(
885985
unary_sub_eq(test, op, k, areEqual, inNonZeroCase, value)
886986
or
887987
unary_add_eq(test, op, k, areEqual, inNonZeroCase, value)
988+
or
989+
unary_builtin_expect_eq(test, op, k, areEqual, inNonZeroCase, value)
888990
}
889991

890992
/*
@@ -913,7 +1015,8 @@ private predicate compares_lt(
9131015

9141016
/** Holds if `op < k` evaluates to `isLt` given that `test` evaluates to `value`. */
9151017
private predicate compares_lt(Instruction test, Operand op, int k, boolean isLt, AbstractValue value) {
916-
simple_comparison_lt(test, op, k, isLt, value)
1018+
unary_simple_comparison_lt(test, k, isLt, value) and
1019+
op.getDef() = test
9171020
or
9181021
complex_lt(test, op, k, isLt, value)
9191022
or
@@ -960,12 +1063,11 @@ private predicate simple_comparison_lt(CompareInstruction cmp, Operand left, Ope
9601063
}
9611064

9621065
/** Rearrange various simple comparisons into `op < k` form. */
963-
private predicate simple_comparison_lt(
964-
Instruction test, Operand op, int k, boolean isLt, AbstractValue value
1066+
private predicate unary_simple_comparison_lt(
1067+
Instruction test, int k, boolean isLt, AbstractValue value
9651068
) {
9661069
exists(SwitchInstruction switch, CaseEdge case |
9671070
test = switch.getExpression() and
968-
op.getDef() = test and
9691071
case = value.(MatchValue).getCase() and
9701072
exists(switch.getSuccessor(case)) and
9711073
case.getMaxValue() > case.getMinValue()

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ astGuardsCompare
7474
| 34 | j >= 10+0 when ... < ... is false |
7575
| 42 | 10 < j+1 when ... < ... is false |
7676
| 42 | 10 >= j+1 when ... < ... is true |
77+
| 42 | call to getABool != 0 when call to getABool is true |
78+
| 42 | call to getABool == 0 when call to getABool is false |
7779
| 42 | j < 10+0 when ... < ... is true |
7880
| 42 | j >= 10+0 when ... < ... is false |
7981
| 44 | 0 < z+0 when ... > ... is true |
@@ -537,6 +539,8 @@ astGuardsEnsure_const
537539
| test.cpp:31:7:31:13 | ... == ... | test.cpp:31:7:31:7 | x | != | -1 | 34 | 34 |
538540
| test.cpp:31:7:31:13 | ... == ... | test.cpp:31:7:31:7 | x | == | -1 | 30 | 30 |
539541
| test.cpp:31:7:31:13 | ... == ... | test.cpp:31:7:31:7 | x | == | -1 | 31 | 32 |
542+
| test.cpp:42:13:42:20 | call to getABool | test.cpp:42:13:42:20 | call to getABool | != | 0 | 43 | 45 |
543+
| test.cpp:42:13:42:20 | call to getABool | test.cpp:42:13:42:20 | call to getABool | == | 0 | 53 | 53 |
540544
irGuards
541545
| test.c:7:9:7:13 | CompareGT: ... > ... |
542546
| test.c:17:8:17:12 | CompareLT: ... < ... |
@@ -613,6 +617,8 @@ irGuardsCompare
613617
| 34 | j >= 10+0 when CompareLT: ... < ... is false |
614618
| 42 | 10 < j+1 when CompareLT: ... < ... is false |
615619
| 42 | 10 >= j+1 when CompareLT: ... < ... is true |
620+
| 42 | call to getABool != 0 when Call: call to getABool is true |
621+
| 42 | call to getABool == 0 when Call: call to getABool is false |
616622
| 42 | j < 10 when CompareLT: ... < ... is true |
617623
| 42 | j < 10+0 when CompareLT: ... < ... is true |
618624
| 42 | j >= 10 when CompareLT: ... < ... is false |
@@ -1081,3 +1087,5 @@ irGuardsEnsure_const
10811087
| test.cpp:31:7:31:13 | CompareEQ: ... == ... | test.cpp:31:7:31:7 | Load: x | != | -1 | 34 | 34 |
10821088
| test.cpp:31:7:31:13 | CompareEQ: ... == ... | test.cpp:31:7:31:7 | Load: x | == | -1 | 30 | 30 |
10831089
| test.cpp:31:7:31:13 | CompareEQ: ... == ... | test.cpp:31:7:31:7 | Load: x | == | -1 | 32 | 32 |
1090+
| test.cpp:42:13:42:20 | Call: call to getABool | test.cpp:42:13:42:20 | Call: call to getABool | != | 0 | 44 | 44 |
1091+
| test.cpp:42:13:42:20 | Call: call to getABool | test.cpp:42:13:42:20 | Call: call to getABool | == | 0 | 53 | 53 |

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,10 @@
4242
| test.cpp:99:6:99:6 | f |
4343
| test.cpp:105:6:105:14 | ... != ... |
4444
| test.cpp:111:6:111:14 | ... != ... |
45+
| test.cpp:122:9:122:9 | b |
46+
| test.cpp:125:13:125:20 | ! ... |
47+
| test.cpp:125:14:125:17 | call to safe |
48+
| test.cpp:131:6:131:21 | call to __builtin_expect |
49+
| test.cpp:135:6:135:21 | call to __builtin_expect |
50+
| test.cpp:141:6:141:21 | call to __builtin_expect |
51+
| test.cpp:145:6:145:21 | call to __builtin_expect |

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

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@
4444
| 34 | j >= 10+0 when ... < ... is false |
4545
| 42 | 10 < j+1 when ... < ... is false |
4646
| 42 | 10 >= j+1 when ... < ... is true |
47+
| 42 | call to getABool != 0 when call to getABool is true |
48+
| 42 | call to getABool == 0 when call to getABool is false |
4749
| 42 | j < 10 when ... < ... is true |
4850
| 42 | j < 10+0 when ... < ... is true |
4951
| 42 | j >= 10 when ... < ... is false |
@@ -149,16 +151,59 @@
149151
| 111 | 0.0 == i+0 when ... != ... is false |
150152
| 111 | i != 0.0+0 when ... != ... is true |
151153
| 111 | i == 0.0+0 when ... != ... is false |
154+
| 122 | b != 0 when b is true |
155+
| 122 | b == 0 when b is false |
156+
| 125 | ! ... != 0 when ! ... is true |
157+
| 125 | ! ... == 0 when ! ... is false |
158+
| 125 | call to safe != 0 when ! ... is false |
159+
| 125 | call to safe != 0 when call to safe is true |
160+
| 125 | call to safe == 0 when call to safe is false |
152161
| 126 | 1 != 0 when 1 is true |
153162
| 126 | 1 != 0 when ... && ... is true |
154163
| 126 | 1 == 0 when 1 is false |
155164
| 126 | call to test3_condition != 0 when ... && ... is true |
156165
| 126 | call to test3_condition != 0 when call to test3_condition is true |
157166
| 126 | call to test3_condition == 0 when call to test3_condition is false |
167+
| 131 | ... + ... != a+0 when call to __builtin_expect is false |
168+
| 131 | ... + ... == a+0 when call to __builtin_expect is true |
169+
| 131 | a != ... + ...+0 when call to __builtin_expect is false |
170+
| 131 | a != b+42 when call to __builtin_expect is false |
171+
| 131 | a == ... + ...+0 when call to __builtin_expect is true |
172+
| 131 | a == b+42 when call to __builtin_expect is true |
158173
| 131 | b != 0 when b is true |
174+
| 131 | b != a+-42 when call to __builtin_expect is false |
159175
| 131 | b == 0 when b is false |
176+
| 131 | b == a+-42 when call to __builtin_expect is true |
177+
| 131 | call to __builtin_expect != 0 when call to __builtin_expect is true |
178+
| 131 | call to __builtin_expect == 0 when call to __builtin_expect is false |
179+
| 135 | ... + ... != a+0 when call to __builtin_expect is true |
180+
| 135 | ... + ... == a+0 when call to __builtin_expect is false |
181+
| 135 | a != ... + ...+0 when call to __builtin_expect is true |
182+
| 135 | a != b+42 when call to __builtin_expect is true |
183+
| 135 | a == ... + ...+0 when call to __builtin_expect is false |
184+
| 135 | a == b+42 when call to __builtin_expect is false |
185+
| 135 | b != a+-42 when call to __builtin_expect is true |
186+
| 135 | b == a+-42 when call to __builtin_expect is false |
187+
| 135 | call to __builtin_expect != 0 when call to __builtin_expect is true |
188+
| 135 | call to __builtin_expect == 0 when call to __builtin_expect is false |
160189
| 137 | 0 != 0 when 0 is true |
161190
| 137 | 0 == 0 when 0 is false |
191+
| 141 | 42 != a+0 when call to __builtin_expect is false |
192+
| 141 | 42 == a+0 when call to __builtin_expect is true |
193+
| 141 | a != 42 when call to __builtin_expect is false |
194+
| 141 | a != 42+0 when call to __builtin_expect is false |
195+
| 141 | a == 42 when call to __builtin_expect is true |
196+
| 141 | a == 42+0 when call to __builtin_expect is true |
197+
| 141 | call to __builtin_expect != 0 when call to __builtin_expect is true |
198+
| 141 | call to __builtin_expect == 0 when call to __builtin_expect is false |
199+
| 145 | 42 != a+0 when call to __builtin_expect is true |
200+
| 145 | 42 == a+0 when call to __builtin_expect is false |
201+
| 145 | a != 42 when call to __builtin_expect is true |
202+
| 145 | a != 42+0 when call to __builtin_expect is true |
203+
| 145 | a == 42 when call to __builtin_expect is false |
204+
| 145 | a == 42+0 when call to __builtin_expect is false |
205+
| 145 | call to __builtin_expect != 0 when call to __builtin_expect is true |
206+
| 145 | call to __builtin_expect == 0 when call to __builtin_expect is false |
162207
| 146 | ! ... != 0 when ! ... is true |
163208
| 146 | ! ... == 0 when ! ... is false |
164209
| 146 | x != 0 when ! ... is false |

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,3 +100,11 @@
100100
| test.cpp:99:6:99:6 | f | true | 99 | 100 |
101101
| test.cpp:105:6:105:14 | ... != ... | true | 105 | 106 |
102102
| test.cpp:111:6:111:14 | ... != ... | true | 111 | 112 |
103+
| test.cpp:122:9:122:9 | b | true | 123 | 125 |
104+
| test.cpp:122:9:122:9 | b | true | 125 | 125 |
105+
| test.cpp:125:13:125:20 | ! ... | true | 125 | 125 |
106+
| test.cpp:125:14:125:17 | call to safe | false | 125 | 125 |
107+
| test.cpp:131:6:131:21 | call to __builtin_expect | true | 131 | 132 |
108+
| test.cpp:135:6:135:21 | call to __builtin_expect | true | 135 | 136 |
109+
| test.cpp:141:6:141:21 | call to __builtin_expect | true | 141 | 142 |
110+
| test.cpp:145:6:145:21 | call to __builtin_expect | true | 145 | 146 |

0 commit comments

Comments
 (0)