Skip to content

Commit 1526400

Browse files
author
Dave Bartolomeo
committed
C++: Model dynamic initialization of static local variables in IR
Previously, the IR for the initialization of a static local variable ran the initialization unconditionally, every time the declaration was reached during execution. This means that we don't model the possibility that an access to the static variable fetches a value that was set on a previous execution of the function. I've added some simple modelling of the correct behavior to the IR. For each static local variable that has a dynamic initializer, we synthesize a (static) `bool` variable to hold whether the initializer for the original variable has executed. When executing a declaration, we check the value of the synthesized variable, and skip the initialization code if it is `true`. If it is `false`, we execute the initialization code as before, and then set the flag to `true`. This doesn't capture the thread-safe nature of static initialization, but I think it's more than enough to handle anything we're likely to care about for the foreseeable future. In `TranslatedDeclarationEntry.qll`, I split the translation of a static local variable declaration into two `TranslatedElement`s: one for the declaration itself, and one for the initialization. The declaration part handles the checking and setting of the flag; the initialization just does the initialization as before. I've added an IR test case that has static variables with constant, zero, and dynamic initialization. I've also verified the new IR generated for @jbj's previous test cases for constant initialization. I inverted the sense of the `hasConstantInitialization()` predicate to be `hasDynamicInitialization()`. Mostly this just made more sense to me, but I think it also fixed a potential bug where `hasConstantInitialization()` would not hold for a zero-initialized variable. Technically, constant initialization isn't the same as zero initialization, but I believe that most code really cares about the distinction between dynamic initialization and static initialization, where static initialization includes both constant and zero initialization. I've fixed up the C# side of IR generation to continue working, but it doesn't use any of the dynamic initialization stuff. In theory, it could use something similar to model the initialization of static fields.
1 parent 66fd566 commit 1526400

File tree

18 files changed

+517
-74
lines changed

18 files changed

+517
-74
lines changed

cpp/ql/src/semmle/code/cpp/Variable.qll

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -381,10 +381,10 @@ class StaticStorageDurationVariable extends Variable {
381381
}
382382

383383
/**
384-
* Holds if the initializer for this variable is evaluated at compile time.
384+
* Holds if the initializer for this variable is evaluated at runtime.
385385
*/
386-
predicate hasConstantInitialization() {
387-
not runtimeExprInStaticInitializer(this.getInitializer().getExpr())
386+
predicate hasDynamicInitialization() {
387+
runtimeExprInStaticInitializer(this.getInitializer().getExpr())
388388
}
389389
}
390390

cpp/ql/src/semmle/code/cpp/controlflow/internal/CFG.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -443,7 +443,7 @@ private Node getControlOrderChildSparse(Node n, int i) {
443443
private predicate skipInitializer(Initializer init) {
444444
exists(LocalVariable local |
445445
init = local.getInitializer() and
446-
local.(StaticStorageDurationVariable).hasConstantInitialization()
446+
not local.(StaticStorageDurationVariable).hasDynamicInitialization()
447447
)
448448
}
449449

cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/IRVariable.qll

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ class IRVariable extends TIRVariable {
2323
IRVariable() {
2424
this = TIRUserVariable(_, _, func) or
2525
this = TIRTempVariable(func, _, _, _) or
26-
this = TIRStringLiteral(func, _, _, _)
26+
this = TIRStringLiteral(func, _, _, _) or
27+
this = TIRDynamicInitializationFlag(func, _, _)
2728
}
2829

2930
string toString() { none() }
@@ -149,7 +150,8 @@ class IRGeneratedVariable extends IRVariable {
149150

150151
IRGeneratedVariable() {
151152
this = TIRTempVariable(func, ast, _, type) or
152-
this = TIRStringLiteral(func, ast, type, _)
153+
this = TIRStringLiteral(func, ast, type, _) or
154+
this = TIRDynamicInitializationFlag(func, ast, type)
153155
}
154156

155157
final override Language::LanguageType getLanguageType() { result = type }
@@ -208,7 +210,7 @@ class IRReturnVariable extends IRTempVariable {
208210
class IRThrowVariable extends IRTempVariable {
209211
IRThrowVariable() { tag = ThrowTempVar() }
210212

211-
override string getBaseString() { result = "#throw" }
213+
final override string getBaseString() { result = "#throw" }
212214
}
213215

214216
/**
@@ -226,7 +228,30 @@ class IRStringLiteral extends IRGeneratedVariable, TIRStringLiteral {
226228
result = "String: " + getLocationString() + "=" + Language::getStringLiteralText(literal)
227229
}
228230

229-
override string getBaseString() { result = "#string" }
231+
final override string getBaseString() { result = "#string" }
230232

231233
final Language::StringLiteral getLiteral() { result = literal }
232234
}
235+
236+
/**
237+
* A variable generated to track whether a specific non-stack variable has been initialized. This is
238+
* used to model the runtime initialization of static local variables in C++, as well as static
239+
* fields in C#.
240+
*/
241+
class IRDynamicInitializationFlag extends IRGeneratedVariable, TIRDynamicInitializationFlag {
242+
Language::Variable var;
243+
244+
IRDynamicInitializationFlag() {
245+
this = TIRDynamicInitializationFlag(func, var, type) and ast = var
246+
}
247+
248+
final override string toString() { result = var.toString() + "#init" }
249+
250+
final Language::Variable getVariable() { result = var }
251+
252+
final override string getUniqueId() {
253+
result = "Init: " + getVariable().toString() + " " + getVariable().getLocation().toString()
254+
}
255+
256+
final override string getBaseString() { result = "#init:" + var.toString() + ":" }
257+
}

cpp/ql/src/semmle/code/cpp/ir/implementation/internal/TIRVariable.qll

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ newtype TIRVariable =
1010
) {
1111
Construction::hasTempVariable(func, ast, tag, type)
1212
} or
13+
TIRDynamicInitializationFlag(
14+
Language::Function func, Language::Variable var, Language::LanguageType type
15+
) {
16+
Construction::hasDynamicInitializationFlag(func, var, type)
17+
} or
1318
TIRStringLiteral(
1419
Language::Function func, Language::AST ast, Language::LanguageType type,
1520
Language::StringLiteral literal

cpp/ql/src/semmle/code/cpp/ir/implementation/raw/IRVariable.qll

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ class IRVariable extends TIRVariable {
2323
IRVariable() {
2424
this = TIRUserVariable(_, _, func) or
2525
this = TIRTempVariable(func, _, _, _) or
26-
this = TIRStringLiteral(func, _, _, _)
26+
this = TIRStringLiteral(func, _, _, _) or
27+
this = TIRDynamicInitializationFlag(func, _, _)
2728
}
2829

2930
string toString() { none() }
@@ -149,7 +150,8 @@ class IRGeneratedVariable extends IRVariable {
149150

150151
IRGeneratedVariable() {
151152
this = TIRTempVariable(func, ast, _, type) or
152-
this = TIRStringLiteral(func, ast, type, _)
153+
this = TIRStringLiteral(func, ast, type, _) or
154+
this = TIRDynamicInitializationFlag(func, ast, type)
153155
}
154156

155157
final override Language::LanguageType getLanguageType() { result = type }
@@ -208,7 +210,7 @@ class IRReturnVariable extends IRTempVariable {
208210
class IRThrowVariable extends IRTempVariable {
209211
IRThrowVariable() { tag = ThrowTempVar() }
210212

211-
override string getBaseString() { result = "#throw" }
213+
final override string getBaseString() { result = "#throw" }
212214
}
213215

214216
/**
@@ -226,7 +228,30 @@ class IRStringLiteral extends IRGeneratedVariable, TIRStringLiteral {
226228
result = "String: " + getLocationString() + "=" + Language::getStringLiteralText(literal)
227229
}
228230

229-
override string getBaseString() { result = "#string" }
231+
final override string getBaseString() { result = "#string" }
230232

231233
final Language::StringLiteral getLiteral() { result = literal }
232234
}
235+
236+
/**
237+
* A variable generated to track whether a specific non-stack variable has been initialized. This is
238+
* used to model the runtime initialization of static local variables in C++, as well as static
239+
* fields in C#.
240+
*/
241+
class IRDynamicInitializationFlag extends IRGeneratedVariable, TIRDynamicInitializationFlag {
242+
Language::Variable var;
243+
244+
IRDynamicInitializationFlag() {
245+
this = TIRDynamicInitializationFlag(func, var, type) and ast = var
246+
}
247+
248+
final override string toString() { result = var.toString() + "#init" }
249+
250+
final Language::Variable getVariable() { result = var }
251+
252+
final override string getUniqueId() {
253+
result = "Init: " + getVariable().toString() + " " + getVariable().getLocation().toString()
254+
}
255+
256+
final override string getBaseString() { result = "#init:" + var.toString() + ":" }
257+
}

cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/IRConstruction.qll

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,13 @@ private module Cached {
5151
getTypeForPRValue(literal.getType()) = type
5252
}
5353

54+
cached
55+
predicate hasDynamicInitializationFlag(Function func, StaticStorageDurationVariable var, CppType type) {
56+
var.(LocalVariable).getFunction() = func and
57+
var.hasDynamicInitialization() and
58+
type = getBoolType()
59+
}
60+
5461
cached
5562
predicate hasModeledMemoryResult(Instruction instruction) { none() }
5663

cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/InstructionTag.qll

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ newtype TInstructionTag =
88
InitializerStoreTag() or
99
InitializerIndirectAddressTag() or
1010
InitializerIndirectStoreTag() or
11+
DynamicInitializationFlagAddressTag() or
12+
DynamicInitializationFlagLoadTag() or
13+
DynamicInitializationConditionalBranchTag() or
14+
DynamicInitializationFlagConstantTag() or
15+
DynamicInitializationFlagStoreTag() or
1116
ZeroPadStringConstantTag() or
1217
ZeroPadStringElementIndexTag() or
1318
ZeroPadStringElementAddressTag() or
@@ -186,4 +191,14 @@ string getInstructionTagId(TInstructionTag tag) {
186191
tag = AsmTag() and result = "Asm"
187192
or
188193
exists(int index | tag = AsmInputTag(index) and result = "AsmInputTag(" + index + ")")
194+
or
195+
tag = DynamicInitializationFlagAddressTag() and result = "DynInitFlagAddr"
196+
or
197+
tag = DynamicInitializationFlagLoadTag() and result = "DynInitFlagLoad"
198+
or
199+
tag = DynamicInitializationConditionalBranchTag() and result = "DynInitCondBranch"
200+
or
201+
tag = DynamicInitializationFlagConstantTag() and result = "DynInitFlagConst"
202+
or
203+
tag = DynamicInitializationFlagStoreTag() and result = "DynInitFlagStore"
189204
}

cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/TranslatedDeclarationEntry.qll

Lines changed: 139 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ private import semmle.code.cpp.ir.internal.IRUtilities
66
private import InstructionTag
77
private import TranslatedElement
88
private import TranslatedExpr
9+
private import TranslatedFunction
910
private import TranslatedInitialization
1011

1112
/**
@@ -66,17 +67,152 @@ abstract class TranslatedLocalVariableDeclaration extends TranslatedVariableInit
6667
}
6768

6869
/**
69-
* Represents the IR translation of a local variable declaration within a declaration statement.
70+
* The IR translation of a local variable declaration within a declaration statement.
7071
*/
71-
class TranslatedVariableDeclarationEntry extends TranslatedLocalVariableDeclaration,
72+
class TranslatedAutoVariableDeclarationEntry extends TranslatedLocalVariableDeclaration,
7273
TranslatedDeclarationEntry {
7374
LocalVariable var;
7475

75-
TranslatedVariableDeclarationEntry() { var = entry.getDeclaration() }
76+
TranslatedAutoVariableDeclarationEntry() { var = entry.getDeclaration() and not var.isStatic() }
7677

7778
override LocalVariable getVariable() { result = var }
7879
}
7980

81+
/**
82+
* The IR translation of the declaration of a static local variable.
83+
* This element generates the logic that determines whether or not the variable has already been
84+
* initialized, and if not, invokes the initializer and sets the dynamic initialization flag for the
85+
* variable. The actual initialization code is handled in
86+
* `TranslatedStaticLocalVariableInitialization`, which is a child of this element.
87+
*/
88+
class TranslatedStaticLocalVariableDeclarationEntry extends TranslatedDeclarationEntry {
89+
LocalVariable var;
90+
91+
TranslatedStaticLocalVariableDeclarationEntry() {
92+
var = entry.getDeclaration() and var.isStatic()
93+
}
94+
95+
final override TranslatedElement getChild(int id) { id = 0 and result = getInitialization() }
96+
97+
final override predicate hasInstruction(Opcode opcode, InstructionTag tag, CppType type) {
98+
tag = DynamicInitializationFlagAddressTag() and
99+
opcode instanceof Opcode::VariableAddress and
100+
type = getBoolGLValueType()
101+
or
102+
tag = DynamicInitializationFlagLoadTag() and
103+
opcode instanceof Opcode::Load and
104+
type = getBoolType()
105+
or
106+
tag = DynamicInitializationConditionalBranchTag() and
107+
opcode instanceof Opcode::ConditionalBranch and
108+
type = getVoidType()
109+
or
110+
tag = DynamicInitializationFlagConstantTag() and
111+
opcode instanceof Opcode::Constant and
112+
type = getBoolType()
113+
or
114+
tag = DynamicInitializationFlagStoreTag() and
115+
opcode instanceof Opcode::Store and
116+
type = getBoolType()
117+
}
118+
119+
final override Instruction getFirstInstruction() {
120+
result = getInstruction(DynamicInitializationFlagAddressTag())
121+
}
122+
123+
final override Instruction getInstructionSuccessor(InstructionTag tag, EdgeKind kind) {
124+
tag = DynamicInitializationFlagAddressTag() and
125+
kind instanceof GotoEdge and
126+
result = getInstruction(DynamicInitializationFlagLoadTag())
127+
or
128+
tag = DynamicInitializationFlagLoadTag() and
129+
kind instanceof GotoEdge and
130+
result = getInstruction(DynamicInitializationConditionalBranchTag())
131+
or
132+
tag = DynamicInitializationConditionalBranchTag() and
133+
(
134+
kind instanceof TrueEdge and
135+
result = getParent().getChildSuccessor(this)
136+
or
137+
kind instanceof FalseEdge and
138+
result = getInitialization().getFirstInstruction()
139+
)
140+
or
141+
tag = DynamicInitializationFlagConstantTag() and
142+
kind instanceof GotoEdge and
143+
result = getInstruction(DynamicInitializationFlagStoreTag())
144+
or
145+
tag = DynamicInitializationFlagStoreTag() and
146+
kind instanceof GotoEdge and
147+
result = getParent().getChildSuccessor(this)
148+
}
149+
150+
final override Instruction getChildSuccessor(TranslatedElement child) {
151+
child = getInitialization() and
152+
result = getInstruction(DynamicInitializationFlagConstantTag())
153+
}
154+
155+
final override IRDynamicInitializationFlag getInstructionVariable(InstructionTag tag) {
156+
tag = DynamicInitializationFlagAddressTag() and
157+
result.getVariable() = var
158+
}
159+
160+
final override string getInstructionConstantValue(InstructionTag tag) {
161+
tag = DynamicInitializationFlagConstantTag() and result = "1"
162+
}
163+
164+
final override Instruction getInstructionOperand(InstructionTag tag, OperandTag operandTag) {
165+
tag = DynamicInitializationFlagLoadTag() and
166+
(
167+
operandTag instanceof AddressOperandTag and
168+
result = getInstruction(DynamicInitializationFlagAddressTag())
169+
or
170+
operandTag instanceof LoadOperandTag and
171+
result = getTranslatedFunction(var.getFunction()).getUnmodeledDefinitionInstruction()
172+
)
173+
or
174+
tag = DynamicInitializationConditionalBranchTag() and
175+
operandTag instanceof ConditionOperandTag and
176+
result = getInstruction(DynamicInitializationFlagLoadTag())
177+
or
178+
tag = DynamicInitializationFlagStoreTag() and
179+
(
180+
operandTag instanceof AddressOperandTag and
181+
result = getInstruction(DynamicInitializationFlagAddressTag())
182+
or
183+
operandTag instanceof StoreValueOperandTag and
184+
result = getInstruction(DynamicInitializationFlagConstantTag())
185+
)
186+
}
187+
188+
private TranslatedStaticLocalVariableInitialization getInitialization() {
189+
result.getVariable() = var
190+
}
191+
}
192+
193+
/**
194+
* The initialization of a static local variable. This element will only exist for a static variable
195+
* with a dynamic initializer.
196+
*/
197+
class TranslatedStaticLocalVariableInitialization extends TranslatedElement,
198+
TranslatedLocalVariableDeclaration, TTranslatedStaticLocalVariableInitialization {
199+
VariableDeclarationEntry entry;
200+
LocalVariable var;
201+
202+
TranslatedStaticLocalVariableInitialization() {
203+
this = TTranslatedStaticLocalVariableInitialization(entry) and
204+
var = entry.getDeclaration()
205+
}
206+
207+
final override string toString() { result = "init: " + entry.toString() }
208+
209+
final override Locatable getAST() { result = entry }
210+
211+
final override LocalVariable getVariable() { result = var }
212+
213+
final override Function getFunction() { result = var.getFunction() }
214+
}
215+
80216
/**
81217
* Gets the `TranslatedRangeBasedForVariableDeclaration` that represents the declaration of
82218
* `var`.

cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/TranslatedElement.qll

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ private predicate ignoreExprAndDescendants(Expr expr) {
5454
// Otherwise the initializer does not run in function scope.
5555
exists(Initializer init, StaticStorageDurationVariable var |
5656
init = var.getInitializer() and
57-
var.hasConstantInitialization() and
57+
not var.hasDynamicInitialization() and
5858
expr = init.getExpr().getFullyConverted()
5959
)
6060
or
@@ -394,11 +394,25 @@ newtype TTranslatedElement =
394394
} or
395395
// A local declaration
396396
TTranslatedDeclarationEntry(DeclarationEntry entry) {
397-
exists(DeclStmt declStmt |
397+
exists(DeclStmt declStmt, LocalVariable var |
398398
translateStmt(declStmt) and
399399
declStmt.getADeclarationEntry() = entry and
400400
// Only declarations of local variables need to be translated to IR.
401-
entry.getDeclaration() instanceof LocalVariable
401+
var = entry.getDeclaration() and
402+
(
403+
not var.isStatic()
404+
or
405+
// Ignore static variables unless they have a dynamic initializer.
406+
var.(StaticStorageDurationVariable).hasDynamicInitialization()
407+
)
408+
)
409+
} or
410+
// The dynamic initialization of a static local variable. This is a separate object from the
411+
// declaration entry.
412+
TTranslatedStaticLocalVariableInitialization(DeclarationEntry entry) {
413+
exists(TTranslatedDeclarationEntry translatedEntry |
414+
translatedEntry = TTranslatedDeclarationEntry(entry) and
415+
entry.getDeclaration().(LocalVariable).isStatic()
402416
)
403417
} or
404418
// A compiler-generated variable to implement a range-based for loop. These don't have a

0 commit comments

Comments
 (0)