Skip to content

Commit b2002a9

Browse files
committed
C++: Use the shared typeflow library to determine whether a pointer points to a buffer or an object.
1 parent c389611 commit b2002a9

File tree

3 files changed

+262
-5
lines changed

3 files changed

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

0 commit comments

Comments
 (0)