Skip to content

Commit 59936c8

Browse files
authored
Merge pull request #16151 from MathiasVP/use-shared-typeflow-lib
C++: Use the shared typeflow library
2 parents 996f535 + a53ef49 commit 59936c8

File tree

6 files changed

+313
-6
lines changed

6 files changed

+313
-6
lines changed

cpp/ql/lib/qlpack.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ dependencies:
99
codeql/dataflow: ${workspace}
1010
codeql/rangeanalysis: ${workspace}
1111
codeql/ssa: ${workspace}
12+
codeql/typeflow: ${workspace}
1213
codeql/tutorial: ${workspace}
1314
codeql/util: ${workspace}
1415
codeql/xml: ${workspace}

cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/SsaInternalsCommon.qll

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ private import DataFlowImplCommon as DataFlowImplCommon
66
private import DataFlowUtil
77
private import semmle.code.cpp.models.interfaces.PointerWrapper
88
private import DataFlowPrivate
9+
private import TypeFlow
910
private import semmle.code.cpp.ir.ValueNumbering
1011

1112
/**
@@ -955,11 +956,7 @@ private module Cached {
955956
* Holds if the address computed by `operand` is guaranteed to write
956957
* to a specific address.
957958
*/
958-
private predicate isCertainAddress(Operand operand) {
959-
valueNumberOfOperand(operand).getAnInstruction() instanceof VariableAddressInstruction
960-
or
961-
operand.getType() instanceof Cpp::ReferenceType
962-
}
959+
private predicate isCertainAddress(Operand operand) { isPointerToSingleObject(operand.getDef()) }
963960

964961
/**
965962
* Holds if `address` is a use of an SSA variable rooted at `base`, and the
Lines changed: 278 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
1+
private import cpp
2+
private import semmle.code.cpp.ir.IR
3+
private import codeql.typeflow.TypeFlow
4+
5+
private module Input implements TypeFlowInput<Location> {
6+
/** Holds if `alloc` dynamically allocates a single object. */
7+
private predicate isSingleObjectAllocation(AllocationExpr alloc) {
8+
// i.e., `new int`;
9+
alloc instanceof NewExpr
10+
or
11+
// i.e., `malloc(sizeof(int))`
12+
exists(SizeofTypeOperator sizeOf | sizeOf = alloc.getSizeExpr() |
13+
not sizeOf.getTypeOperand().getUnspecifiedType() instanceof ArrayType
14+
)
15+
}
16+
17+
/**
18+
* Holds if `i` is the result of a dynamic allocation.
19+
*
20+
* `isObject` is `true` if the allocation allocated a single object,
21+
* and `false` otherwise.
22+
*/
23+
private predicate isAllocation(Instruction i, boolean isObject) {
24+
exists(AllocationExpr alloc | alloc = i.getUnconvertedResultExpression() |
25+
if isSingleObjectAllocation(alloc) then isObject = true else isObject = false
26+
)
27+
}
28+
29+
private predicate hasExactSingleType(Instruction i) {
30+
// The address of a variable is always a single object
31+
i instanceof VariableAddressInstruction
32+
or
33+
// A reference always points to a single object
34+
i.getResultLanguageType().hasUnspecifiedType(any(ReferenceType rt), false)
35+
or
36+
// `this` is never an array
37+
i instanceof InitializeThisInstruction
38+
or
39+
// An allocation of a non-array object
40+
isAllocation(i, true)
41+
}
42+
43+
private predicate hasExactBufferType(Instruction i) {
44+
// Anything with an array type is a buffer
45+
i.getResultLanguageType().hasUnspecifiedType(any(ArrayType at), false)
46+
or
47+
// An allocation expression that we couldn't conclude allocated a single
48+
// expression is assigned a buffer type.
49+
isAllocation(i, false)
50+
}
51+
52+
private newtype TTypeFlowNode =
53+
TInstructionNode(Instruction i) or
54+
TFunctionNode(IRFunction func)
55+
56+
abstract class TypeFlowNode extends TTypeFlowNode {
57+
/** Gets a textual representation of this node. */
58+
abstract string toString();
59+
60+
/**
61+
* Gets the type of this node. This type may not be the most precise
62+
* possible type, but will be used as a starting point of the analysis.
63+
*/
64+
abstract Type getType();
65+
66+
/** Gets the location of this node. */
67+
abstract Location getLocation();
68+
69+
/** Gets the underlying `Instruction` of this node, if any. */
70+
Instruction asInstruction() { none() }
71+
72+
/** Gets the underlying `IRFunction` of this node, if any. */
73+
IRFunction asFunction() { none() }
74+
75+
/** Holds if the value of this node is always null. */
76+
abstract predicate isNullValue();
77+
}
78+
79+
private class InstructionNode extends TypeFlowNode, TInstructionNode {
80+
Instruction instr;
81+
82+
InstructionNode() { this = TInstructionNode(instr) }
83+
84+
override string toString() { result = instr.toString() }
85+
86+
override Type getType() {
87+
if hasExactSingleType(instr) then result.isSingle() else result.isBuffer()
88+
}
89+
90+
override Location getLocation() { result = instr.getLocation() }
91+
92+
override Instruction asInstruction() { result = instr }
93+
94+
override predicate isNullValue() {
95+
instr.(ConstantInstruction).getValue() = "0" and
96+
instr.getResultIRType() instanceof IRAddressType
97+
}
98+
}
99+
100+
/** Gets the `TypeFlowNode` corresponding to `i`. */
101+
additional InstructionNode instructionNode(Instruction i) { result.asInstruction() = i }
102+
103+
private class FunctionNode extends TypeFlowNode, TFunctionNode {
104+
IRFunction func;
105+
106+
FunctionNode() { this = TFunctionNode(func) }
107+
108+
override string toString() { result = func.toString() }
109+
110+
Instruction getReturnValueInstruction() {
111+
result = func.getReturnInstruction().(ReturnValueInstruction).getReturnValue()
112+
}
113+
114+
override Type getType() { result = instructionNode(this.getReturnValueInstruction()).getType() }
115+
116+
override Location getLocation() { result = func.getLocation() }
117+
118+
override IRFunction asFunction() { result = func }
119+
120+
override predicate isNullValue() {
121+
instructionNode(this.getReturnValueInstruction()).isNullValue()
122+
}
123+
}
124+
125+
/**
126+
* Gets an ultimiate definition of `phi`. That is, an input to `phi` that is
127+
* not itself a `PhiInstruction`.
128+
*/
129+
private Instruction getAnUltimateLocalDefinition(PhiInstruction phi) {
130+
result = phi.getAnInput*() and not result instanceof PhiInstruction
131+
}
132+
133+
/**
134+
* Holds if this function is private (i.e., cannot be accessed outside its
135+
* compilation unit). This means we can use a closed-world assumption about
136+
* calls to this function.
137+
*/
138+
private predicate isPrivate(Function func) {
139+
// static functions have internal linkage
140+
func.isStatic()
141+
or
142+
// anonymous namespaces have internal linkage
143+
func.getNamespace().getParentNamespace*().isAnonymous()
144+
or
145+
// private member functions are only called internally from inside the class
146+
func.(MemberFunction).isPrivate()
147+
}
148+
149+
/**
150+
* Holds if `arg` is an argument for the parameter `p` in a private callable.
151+
*/
152+
pragma[nomagic]
153+
private predicate privateParamArg(InitializeParameterInstruction p, Instruction arg) {
154+
exists(CallInstruction call, int i, Function func |
155+
call.getArgument(pragma[only_bind_into](i)) = arg and
156+
func = call.getStaticCallTarget() and
157+
func.getParameter(pragma[only_bind_into](i)) = p.getParameter() and
158+
isPrivate(func)
159+
)
160+
}
161+
162+
predicate joinStep(TypeFlowNode n1, TypeFlowNode n2) {
163+
// instruction -> phi
164+
getAnUltimateLocalDefinition(n2.asInstruction()) = n1.asInstruction()
165+
or
166+
// return value -> function
167+
n2.(FunctionNode).getReturnValueInstruction() = n1.asInstruction()
168+
or
169+
// function -> call
170+
exists(Function func | func = n1.asFunction().getFunction() |
171+
not func.isVirtual() and
172+
n2.asInstruction().(CallInstruction).getStaticCallTarget() = func
173+
)
174+
or
175+
// Argument -> parameter where the parameter's enclosing function
176+
// is "private".
177+
exists(Instruction arg, Instruction p |
178+
privateParamArg(p, arg) and
179+
n1.asInstruction() = arg and
180+
n2.asInstruction() = p
181+
)
182+
}
183+
184+
/**
185+
* Holds if knowing whether `i1` points to a single object or buffer implies
186+
* knowing whether `i2` points to a single object or buffer.
187+
*/
188+
private predicate instructionStep(Instruction i1, Instruction i2) {
189+
i2.(CopyInstruction).getSourceValue() = i1
190+
or
191+
i2.(CopyValueInstruction).getSourceValue() = i1
192+
or
193+
i2.(ConvertInstruction).getUnary() = i1
194+
or
195+
i2.(CheckedConvertOrNullInstruction).getUnary() = i1
196+
or
197+
i2.(InheritanceConversionInstruction).getUnary() = i1
198+
or
199+
i2.(PointerArithmeticInstruction).getLeft() = i1
200+
}
201+
202+
predicate step(TypeFlowNode n1, TypeFlowNode n2) {
203+
instructionStep(n1.asInstruction(), n2.asInstruction())
204+
}
205+
206+
predicate isNullValue(TypeFlowNode n) { n.isNullValue() }
207+
208+
private newtype TType =
209+
TSingle() or
210+
TBuffer()
211+
212+
class Type extends TType {
213+
string toString() {
214+
this.isSingle() and
215+
result = "Single"
216+
or
217+
this.isBuffer() and
218+
result = "Buffer"
219+
}
220+
221+
/** Holds if this type is the type that represents a single object. */
222+
predicate isSingle() { this = TSingle() }
223+
224+
/** Holds if this type is the type that represents a buffer. */
225+
predicate isBuffer() { this = TBuffer() }
226+
227+
/**
228+
* Gets a super type of this type, if any.
229+
*
230+
* The type relation is `Single <: Buffer`.
231+
*/
232+
Type getASupertype() {
233+
this.isSingle() and
234+
result.isBuffer()
235+
}
236+
}
237+
238+
predicate exactTypeBase(TypeFlowNode n, Type t) {
239+
exists(Instruction instr | instr = n.asInstruction() |
240+
hasExactSingleType(instr) and t.isSingle()
241+
or
242+
hasExactBufferType(instr) and t.isBuffer()
243+
)
244+
}
245+
246+
pragma[nomagic]
247+
private predicate upcastCand(TypeFlowNode n, Type t1, Type t2) {
248+
exists(TypeFlowNode next |
249+
step(n, next)
250+
or
251+
joinStep(n, next)
252+
|
253+
n.getType() = t1 and
254+
next.getType() = t2 and
255+
t1 != t2
256+
)
257+
}
258+
259+
private predicate upcast(TypeFlowNode n, Type t1) {
260+
exists(Type t2 | upcastCand(n, t1, t2) |
261+
// No need for transitive closure since the subtyping relation is just `Single <: Buffer`
262+
t1.getASupertype() = t2
263+
)
264+
}
265+
266+
predicate typeFlowBaseCand(TypeFlowNode n, Type t) { upcast(n, t) }
267+
}
268+
269+
private module TypeFlow = Make<Location, Input>;
270+
271+
/**
272+
* Holds if `i` is an instruction that computes an address that points to a
273+
* single object (as opposed to pointing into a buffer).
274+
*/
275+
pragma[nomagic]
276+
predicate isPointerToSingleObject(Instruction i) {
277+
TypeFlow::bestTypeFlow(Input::instructionNode(i), any(Input::Type t | t.isSingle()), _)
278+
}

cpp/ql/test/library-tests/dataflow/dataflow-tests/dataflow-consistency.expected

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ argHasPostUpdate
3333
| test.cpp:67:29:67:35 | source1 | ArgumentNode is missing PostUpdateNode. |
3434
| test.cpp:813:19:813:35 | * ... | ArgumentNode is missing PostUpdateNode. |
3535
| test.cpp:848:23:848:25 | rpx | ArgumentNode is missing PostUpdateNode. |
36+
| test.cpp:1057:19:1057:21 | * ... | ArgumentNode is missing PostUpdateNode. |
3637
postWithInFlow
3738
| BarrierGuard.cpp:49:6:49:6 | x [post update] | PostUpdateNode should not be the target of local flow. |
3839
| BarrierGuard.cpp:60:7:60:7 | x [post update] | PostUpdateNode should not be the target of local flow. |
@@ -168,6 +169,13 @@ postWithInFlow
168169
| test.cpp:1045:9:1045:11 | ref arg buf | PostUpdateNode should not be the target of local flow. |
169170
| test.cpp:1051:5:1051:11 | content [post update] | PostUpdateNode should not be the target of local flow. |
170171
| test.cpp:1052:9:1052:9 | a [inner post update] | PostUpdateNode should not be the target of local flow. |
172+
| test.cpp:1056:5:1056:7 | * ... [post update] | PostUpdateNode should not be the target of local flow. |
173+
| test.cpp:1056:6:1056:7 | pp [inner post update] | PostUpdateNode should not be the target of local flow. |
174+
| test.cpp:1062:53:1062:53 | p [inner post update] | PostUpdateNode should not be the target of local flow. |
175+
| test.cpp:1072:3:1072:4 | * ... [post update] | PostUpdateNode should not be the target of local flow. |
176+
| test.cpp:1072:4:1072:4 | p [inner post update] | PostUpdateNode should not be the target of local flow. |
177+
| test.cpp:1073:3:1073:4 | * ... [post update] | PostUpdateNode should not be the target of local flow. |
178+
| test.cpp:1073:4:1073:4 | p [inner post update] | PostUpdateNode should not be the target of local flow. |
171179
viableImplInCallContextTooLarge
172180
uniqueParameterNodeAtPosition
173181
uniqueParameterNodePosition

cpp/ql/test/library-tests/dataflow/dataflow-tests/test.cpp

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1050,4 +1050,26 @@ void flow_out_of_address_with_local_flow() {
10501050
MyStruct a;
10511051
a.content = nullptr;
10521052
sink(&a); // $ SPURIOUS: ast
1053-
}
1053+
}
1054+
1055+
static void static_func_that_reassigns_pointer_before_sink(char** pp) { // $ ast-def=pp ir-def=*pp ir-def=**pp
1056+
*pp = "";
1057+
indirect_sink(*pp); // clean
1058+
}
1059+
1060+
void test_static_func_that_reassigns_pointer_before_sink() {
1061+
char* p = (char*)indirect_source();
1062+
static_func_that_reassigns_pointer_before_sink(&p);
1063+
}
1064+
1065+
void single_object_in_both_cases(bool b, int x, int y) {
1066+
int* p;
1067+
if(b) {
1068+
p = &x;
1069+
} else {
1070+
p = &y;
1071+
}
1072+
*p = source();
1073+
*p = 0;
1074+
sink(*p); // clean
1075+
}

cpp/ql/test/library-tests/dataflow/dataflow-tests/type-bugs.expected

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,5 @@ incorrectBaseType
1515
| test.cpp:848:23:848:25 | (reference dereference) | Expected 'Node.getType()' to be int, but it was int * |
1616
| test.cpp:854:10:854:36 | * ... | Expected 'Node.getType()' to be const int, but it was int |
1717
| test.cpp:867:10:867:30 | * ... | Expected 'Node.getType()' to be const int, but it was int |
18+
| test.cpp:1062:52:1062:53 | *& ... | Expected 'Node.getType()' to be char, but it was char * |
1819
failures

0 commit comments

Comments
 (0)