Skip to content

Commit 8b5733a

Browse files
committed
Refactor ReadOfUninitializedMemory to separate library into usable pieces
previously importing the shared query lib to other queries does not work bc it introduces a select stmt and a problems predicate into the same scope this is now more modular and usable
1 parent a559f7a commit 8b5733a

File tree

5 files changed

+264
-260
lines changed

5 files changed

+264
-260
lines changed
Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
1+
/**
2+
* Provides classes and predicates for identifying uninitialized variables.
3+
*/
4+
5+
import cpp
6+
import semmle.code.cpp.controlflow.Guards
7+
import semmle.code.cpp.controlflow.SubBasicBlocks
8+
import codingstandards.cpp.InitializationFunctions
9+
10+
private newtype TInitializationContext =
11+
/** No specific context - for functions where conditional initialization doesn't play a role */
12+
NoContext(UninitializedVariable uv) { count(uv.getACorrelatedConditionVariable()) = 0 } or
13+
/**
14+
* A context where the given `LocalScopeVariable` is identified as a correlated variable with the
15+
* given state value.
16+
*/
17+
CorrelatedVariable(UninitializedVariable uv, LocalScopeVariable correlatedVariable, boolean state) {
18+
uv.getACorrelatedConditionVariable() = correlatedVariable and state = [true, false]
19+
}
20+
21+
/**
22+
* A context to apply when determining whether a given variable is uninitialized at a particular
23+
* `SubBasicBlock` in the control flow graph.
24+
*
25+
* If no suitable context is found for an `UninitializedVariable`, `NoContext` is used.
26+
*
27+
* If one or more correlated variables are found, a `CorrelatedVariable` context is provided, with
28+
* `true` and `false` states.
29+
*/
30+
class InitializationContext extends TInitializationContext {
31+
/**
32+
* Gets the `UninitializedVariable` for this context.
33+
*/
34+
UninitializedVariable getUninitializedVariable() {
35+
this = NoContext(result) or
36+
this = CorrelatedVariable(result, _, _)
37+
}
38+
39+
/**
40+
* Gets the correlated variable, if any.
41+
*/
42+
LocalScopeVariable getCorrelatedVariable() { this = CorrelatedVariable(_, result, _) }
43+
44+
string toString() {
45+
if this instanceof CorrelatedVariable
46+
then
47+
result =
48+
"Uninitialized variable " + getUninitializedVariable().getName() + " where location " +
49+
getCorrelatedVariable().getName() + " is " +
50+
any(boolean b | this = CorrelatedVariable(_, _, b))
51+
else result = "Uninitialized variable " + getUninitializedVariable().getName()
52+
}
53+
}
54+
55+
/**
56+
* A `SubBasicBlockCutNode` that ensures that any uninitialized variable definitions appear at the
57+
* start of a `SubBasicBlock`.
58+
*/
59+
class InitializationSubBasicBlock extends SubBasicBlockCutNode {
60+
InitializationSubBasicBlock() { this = any(UninitializedVariable uv).getADefinitionAccess() }
61+
}
62+
63+
/**
64+
* Holds if the `gc` dictates the state of variable `lv`, i.e. the state is heuristically
65+
* identified to be different under different branches.
66+
*/
67+
private predicate guardDictatesLocalVariableState(
68+
GuardCondition gc, LocalScopeVariable lv, boolean lvStateOnTrueBranch
69+
) {
70+
// Condition is a boolean check on the variable
71+
gc = lv.getAnAccess() and lvStateOnTrueBranch = true
72+
or
73+
// Condition is a negated boolean check on the variable
74+
gc.(NotExpr).getOperand() = lv.getAnAccess() and lvStateOnTrueBranch = false
75+
or
76+
// Condition controls a block which assigns to `lv`
77+
gc.controls(lv.getAnAssignedValue().getBasicBlock(), lvStateOnTrueBranch)
78+
}
79+
80+
/**
81+
* Catches `new int;` as an expression that doesn't initialize its value. Note that the pointer returned has been initialized (ie it is a valid pointer),
82+
* but the pointee/value has not. In our analysis, we simply count `x` as uninitialized in `x = new int` for now, though a more thorough analysis might track the initialization of `x` and `*x` separately.
83+
*/
84+
class NewNotInit extends NewExpr {
85+
NewNotInit() {
86+
this.getAllocatedType() instanceof BuiltInType and
87+
not exists(this.getAChild())
88+
}
89+
}
90+
91+
class NonInitAssignment extends Assignment {
92+
NonInitAssignment() { this.getRValue() instanceof NewNotInit }
93+
}
94+
95+
/**
96+
* A local variable without an initializer which is amenable to initialization analysis.
97+
*/
98+
class UninitializedVariable extends LocalVariable {
99+
UninitializedVariable() {
100+
// Not initialized at declaration
101+
(
102+
not exists(getInitializer().getExpr())
103+
or
104+
getInitializer().getExpr() instanceof NewNotInit
105+
) and
106+
// Not static or thread local, because they are not initialized with indeterminate values
107+
not isStatic() and
108+
not isThreadLocal() and
109+
// Not atomic, which have special initialization rules
110+
not getType().hasSpecifier("atomic") and
111+
// Not a class type, because default initialization of a class calls the default constructor
112+
// The default constructor may leave certain fields uninitialized, but that would be a separate
113+
// field-wise analysis
114+
not this.getType().getUnspecifiedType() instanceof Class and
115+
// An analysis of an array entry also requires a field wise analysis
116+
not this.getType().getUnspecifiedType() instanceof ArrayType and
117+
// Ignore variables in uninstantiated templates, because we often do not have enough semantic
118+
// information to accurately determine initialization state.
119+
not isFromUninstantiatedTemplate(_) and
120+
// Ignore `va_list`, that is part of the mechanism for
121+
not getType().hasName("va_list") and
122+
// Ignore variable defined in a block with an `asm` statement, as that may initialized the variables
123+
not exists(AsmStmt asm | asm.getEnclosingBlock() = getParentScope()) and
124+
// Ignore variables generated for `RangeBasedForStmt` e.g. `for(auto x : y)`
125+
not this = any(RangeBasedForStmt f).getAChild().(DeclStmt).getADeclaration()
126+
}
127+
128+
/** Gets a variable correlated with at least one use of `this` uninitialized variable. */
129+
private LocalScopeVariable getAUseCorrelatedConditionVariable() {
130+
/* Extracted to improve join order of getACorrelatedConditionVariable(). */
131+
// The use is guarded by the access of a variable
132+
exists(GuardCondition gc |
133+
gc.controls(getAUse().getBasicBlock(), _) and
134+
gc = result.getAnAccess()
135+
)
136+
}
137+
138+
/** Find another variable which looks like it may be correlated with the initialization of this variable. */
139+
pragma[noinline]
140+
LocalScopeVariable getACorrelatedConditionVariable() {
141+
result = getAUseCorrelatedConditionVariable() and
142+
(
143+
// Definition is guarded by an access of the same variable
144+
exists(GuardCondition gc |
145+
gc.controls(getADefinitionAccess().getBasicBlock(), _) and
146+
gc = result.getAnAccess()
147+
)
148+
or
149+
// The variable is assigned in the same basic block as one of our definitions
150+
result.getAnAssignedValue().getBasicBlock() = getADefinitionAccess().getBasicBlock()
151+
)
152+
}
153+
154+
/**
155+
* Get a access of the variable that is assumed to initialize the variable.
156+
* This approximates that any access in the lvalue category may be a definition.
157+
*/
158+
VariableAccess getADefinitionAccess() {
159+
result = getAnAccess() and
160+
result.isLValueCategory() and
161+
// Not a pointless read
162+
not result = any(ExprStmt es).getExpr() and
163+
// not involved in a new expr assignment since that does not define
164+
not result = any(NonInitAssignment a).getLValue()
165+
}
166+
167+
/**
168+
* Gets an access of the this variable which is not used as an lvalue, and not used as an argument
169+
* to an initialization function.
170+
*/
171+
VariableAccess getAUse() {
172+
result = this.getAnAccess() and
173+
(
174+
//count rvalue x (or *x) as a use if not new int
175+
result.isRValue() and
176+
not this.getInitializer().getExpr() instanceof NewNotInit
177+
or
178+
//count lvalue x as a use if used in *x and not new int
179+
result.isLValue() and
180+
exists(PointerDereferenceExpr e | result = e.getAChild()) and
181+
exists(this.getInitializer()) and
182+
not this.getInitializer().getExpr() instanceof NewNotInit
183+
or
184+
//count rvalue *x as a use if has new int
185+
result.isRValue() and
186+
this.getInitializer().getExpr() instanceof NewNotInit and
187+
exists(PointerDereferenceExpr e | result = e.getAChild())
188+
) and
189+
// Not passed to another initialization function
190+
not exists(Call c, int j | j = c.getTarget().(InitializationFunction).initializedParameter() |
191+
result = c.getArgument(j).(AddressOfExpr).getOperand()
192+
) and
193+
// Not a pointless read
194+
not result = any(ExprStmt es).getExpr() and
195+
// sizeof operators are not real uses
196+
not result.getParent+() instanceof SizeofOperator
197+
}
198+
199+
/** Get a read of the variable that may occur while the variable is uninitialized. */
200+
VariableAccess getAnUnitializedUse() {
201+
exists(SubBasicBlock useSbb |
202+
result = getAUse() and
203+
useSbb.getANode() = result and
204+
// This sbb is considered uninitialized in all the contexts we identified
205+
forex(InitializationContext ct | ct.getUninitializedVariable() = this |
206+
useSbb = getAnUninitializedSubBasicBlock(ct)
207+
)
208+
)
209+
}
210+
211+
/**
212+
* Gets a `SubBasicBlock` where this variable is uninitialized under the conditions specified by
213+
* `InitializationContext`.
214+
*/
215+
private SubBasicBlock getAnUninitializedSubBasicBlock(InitializationContext ic) {
216+
ic.getUninitializedVariable() = this and
217+
(
218+
// Base case - this SBB is the one that declares the variable
219+
exists(DeclStmt ds |
220+
ds.getADeclaration() = this and
221+
result.getANode() = ds
222+
)
223+
or
224+
// Recursive case - SBB is a successor of an SBB where this variable is uninitialized
225+
exists(SubBasicBlock mid |
226+
// result is the successor of an SBB where this is considered to be uninitialized under the
227+
// context ic
228+
mid = getAnUninitializedSubBasicBlock(ic) and
229+
result = mid.getASuccessor() and
230+
// Result is not an SBB where this variable is initialized
231+
not getADefinitionAccess() = result and
232+
// Determine if this is a branch at __builtin_expect where the initialization occurs inside
233+
// the checked argument, and exclude it if so, because the CFG is known to be broken here.
234+
not exists(FunctionCall fc |
235+
mid.getEnd() = fc and
236+
fc.getTarget().hasName("__builtin_expect") and
237+
fc.getArgument(0).getAChild*() = getADefinitionAccess()
238+
)
239+
|
240+
// If this is an analysis with no context
241+
ic = NoContext(this)
242+
or
243+
exists(LocalScopeVariable lv | lv = ic.getCorrelatedVariable() |
244+
// If the final node in `mid` SBB is a guard condition that affects our tracked correlated
245+
// variable
246+
guardDictatesLocalVariableState(mid.getEnd(), lv, _)
247+
implies
248+
// Then our context must match the inferred state of the correlated variable after the branch
249+
exists(boolean lvStateOnTrueBranch |
250+
guardDictatesLocalVariableState(mid.getEnd(), lv, lvStateOnTrueBranch)
251+
|
252+
result = mid.getATrueSuccessor() and
253+
ic = CorrelatedVariable(this, lv, lvStateOnTrueBranch)
254+
or
255+
result = mid.getAFalseSuccessor() and
256+
ic = CorrelatedVariable(this, lv, lvStateOnTrueBranch.booleanNot())
257+
)
258+
)
259+
)
260+
)
261+
}
262+
}

cpp/common/src/codingstandards/cpp/rules/readofuninitializedmemory/InitializationFunctions.qll renamed to cpp/common/src/codingstandards/cpp/InitializationFunctions.qll

File renamed without changes.

0 commit comments

Comments
 (0)