Skip to content

Commit 87b1260

Browse files
committed
C#: Replace initializer splitting with ObjectInitMethod.
1 parent 4d9e078 commit 87b1260

File tree

10 files changed

+159
-177
lines changed

10 files changed

+159
-177
lines changed

csharp/ql/lib/semmle/code/csharp/Callable.qll

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,6 @@ class Method extends Callable, Virtualizable, Attributable, @method {
281281
/** Holds if this method has a `params` parameter. */
282282
predicate hasParams() { exists(this.getParamsType()) }
283283

284-
// Remove when `Callable.isOverridden()` is removed
285284
override predicate fromSource() {
286285
Callable.super.fromSource() and
287286
not this.isCompilerGenerated()
@@ -317,6 +316,19 @@ class ExtensionMethod extends Method {
317316
override string getAPrimaryQlClass() { result = "ExtensionMethod" }
318317
}
319318

319+
/**
320+
* An object initializer method.
321+
*
322+
* This is an extractor-synthesized method that executes the field
323+
* initializers. Note that the AST nodes for the field initializers are nested
324+
* directly under the class, and therefore this method has no body in the AST.
325+
* On the other hand, this provides the unique enclosing callable for the field
326+
* initializers and their control flow graph.
327+
*/
328+
class ObjectInitMethod extends Method {
329+
ObjectInitMethod() { this.getName() = "<object initializer>" }
330+
}
331+
320332
/**
321333
* A constructor, for example `public C() { }` on line 2 in
322334
*
@@ -350,6 +362,9 @@ class Constructor extends Callable, Member, Attributable, @constructor {
350362
*/
351363
ConstructorInitializer getInitializer() { result = this.getChildExpr(-1) }
352364

365+
/** Gets the object initializer call of this constructor, if any. */
366+
MethodCall getObjectInitializerCall() { result = this.getChildExpr(-2) }
367+
353368
/** Holds if this constructor has an initializer. */
354369
predicate hasInitializer() { exists(this.getInitializer()) }
355370

csharp/ql/lib/semmle/code/csharp/ExprOrStmtParent.qll

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,8 @@ class TopLevelExprParent extends Element, @top_level_expr_parent {
5555
/** INTERNAL: Do not use. */
5656
Expr getExpressionBody(Callable c) {
5757
result = c.getAChildExpr() and
58-
not result = c.(Constructor).getInitializer()
58+
not result = c.(Constructor).getInitializer() and
59+
not result = c.(Constructor).getObjectInitializerCall()
5960
}
6061

6162
/** INTERNAL: Do not use. */
@@ -211,6 +212,8 @@ private module Cached {
211212
enclosingBody(cfe, getBody(c))
212213
or
213214
parent*(enclosingStart(cfe), c.(Constructor).getInitializer())
215+
or
216+
parent*(cfe, c.(Constructor).getObjectInitializerCall())
214217
}
215218

216219
/** Holds if the enclosing statement of expression `e` is `s`. */

csharp/ql/lib/semmle/code/csharp/controlflow/internal/ControlFlowGraphImpl.qll

Lines changed: 44 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ class CfgScope extends Element, @top_level_exprorstmt_parent {
1919
any(Callable c |
2020
c.(Constructor).hasInitializer()
2121
or
22-
InitializerSplitting::constructorInitializes(c, _)
22+
InitializerSplitting::obinitInitializes(c, _)
2323
or
2424
c.hasBody()
2525
)
@@ -146,14 +146,16 @@ private predicate expr_parent_top_level_adjusted2(
146146
predicate scopeFirst(CfgScope scope, AstNode first) {
147147
scope =
148148
any(Callable c |
149-
if exists(c.(Constructor).getInitializer())
150-
then first(c.(Constructor).getInitializer(), first)
149+
if exists(c.(Constructor).getObjectInitializerCall())
150+
then first(c.(Constructor).getObjectInitializerCall(), first)
151151
else
152-
if InitializerSplitting::constructorInitializes(c, _)
153-
then first(InitializerSplitting::constructorInitializeOrder(c, _, 0), first)
152+
if exists(c.(Constructor).getInitializer())
153+
then first(c.(Constructor).getInitializer(), first)
154154
else first(c.getBody(), first)
155155
)
156156
or
157+
first(InitializerSplitting::initializedInstanceMemberOrder(scope, _, 0), first)
158+
or
157159
expr_parent_top_level_adjusted2(any(Expr e | first(e, first)), _, scope) and
158160
not scope instanceof Callable
159161
}
@@ -165,14 +167,33 @@ predicate scopeLast(CfgScope scope, AstNode last, Completion c) {
165167
last(callable.getBody(), last, c) and
166168
not c instanceof GotoCompletion
167169
or
168-
last(InitializerSplitting::lastConstructorInitializer(scope, _), last, c) and
170+
last(callable.(Constructor).getInitializer(), last, c) and
169171
not callable.hasBody()
170172
)
171173
or
174+
last(InitializerSplitting::lastInitializer(scope, _), last, c)
175+
or
172176
expr_parent_top_level_adjusted2(any(Expr e | last(e, last, c)), _, scope) and
173177
not scope instanceof Callable
174178
}
175179

180+
private class ObjectInitTree extends ControlFlowTree instanceof ObjectInitMethod {
181+
final override predicate propagatesAbnormal(AstNode child) { none() }
182+
183+
final override predicate first(AstNode first) { none() }
184+
185+
final override predicate last(AstNode last, Completion c) { none() }
186+
187+
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
188+
exists(CompilationExt comp, int i |
189+
// Flow from one member initializer to the next
190+
last(InitializerSplitting::initializedInstanceMemberOrder(this, comp, i), pred, c) and
191+
c instanceof NormalCompletion and
192+
first(InitializerSplitting::initializedInstanceMemberOrder(this, comp, i + 1), succ)
193+
)
194+
}
195+
}
196+
176197
private class ConstructorTree extends ControlFlowTree instanceof Constructor {
177198
final override predicate propagatesAbnormal(AstNode child) { none() }
178199

@@ -187,18 +208,23 @@ private class ConstructorTree extends ControlFlowTree instanceof Constructor {
187208
comp = getCompilation(result.getFile())
188209
}
189210

211+
pragma[noinline]
212+
private MethodCall getObjectInitializerCall(CompilationExt comp) {
213+
result = super.getObjectInitializerCall() and
214+
comp = getCompilation(result.getFile())
215+
}
216+
217+
pragma[noinline]
218+
private ConstructorInitializer getInitializer(CompilationExt comp) {
219+
result = super.getInitializer() and
220+
comp = getCompilation(result.getFile())
221+
}
222+
190223
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
191-
exists(CompilationExt comp, int i, AssignExpr ae |
192-
ae = InitializerSplitting::constructorInitializeOrder(this, comp, i) and
193-
last(ae, pred, c) and
194-
c instanceof NormalCompletion
195-
|
196-
// Flow from one member initializer to the next
197-
first(InitializerSplitting::constructorInitializeOrder(this, comp, i + 1), succ)
198-
or
199-
// Flow from last member initializer to constructor body
200-
ae = InitializerSplitting::lastConstructorInitializer(this, comp) and
201-
first(this.getBody(comp), succ)
224+
exists(CompilationExt comp |
225+
last(this.getObjectInitializerCall(comp), pred, c) and
226+
c instanceof NormalCompletion and
227+
first(this.getInitializer(comp), succ)
202228
)
203229
}
204230
}
@@ -837,13 +863,7 @@ module Expressions {
837863
last(this, pred, c) and
838864
con = super.getConstructor() and
839865
comp = getCompilation(this.getFile()) and
840-
c instanceof NormalCompletion
841-
|
842-
// Flow from constructor initializer to first member initializer
843-
first(InitializerSplitting::constructorInitializeOrder(con, comp, 0), succ)
844-
or
845-
// Flow from constructor initializer to first element of constructor body
846-
not exists(InitializerSplitting::constructorInitializeOrder(con, comp, _)) and
866+
c instanceof NormalCompletion and
847867
first(con.getBody(comp), succ)
848868
)
849869
}

csharp/ql/lib/semmle/code/csharp/controlflow/internal/Splitting.qll

Lines changed: 13 additions & 143 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,10 @@ private module Cached {
2222
}
2323

2424
cached
25-
newtype TSplitKind =
26-
TInitializerSplitKind() or
27-
TConditionalCompletionSplitKind()
25+
newtype TSplitKind = TConditionalCompletionSplitKind()
2826

2927
cached
30-
newtype TSplit =
31-
TInitializerSplit(Constructor c) { InitializerSplitting::constructorInitializes(c, _) } or
32-
TConditionalCompletionSplit(ConditionalCompletion c)
28+
newtype TSplit = TConditionalCompletionSplit(ConditionalCompletion c)
3329
}
3430

3531
import Cached
@@ -44,8 +40,6 @@ class Split extends TSplit {
4440
}
4541

4642
module InitializerSplitting {
47-
private import semmle.code.csharp.ExprOrStmtParent
48-
4943
/**
5044
* A non-static member with an initializer, for example a field `int Field = 0`.
5145
*/
@@ -60,40 +54,28 @@ module InitializerSplitting {
6054

6155
/** Gets the initializer expression. */
6256
AssignExpr getInitializer() { expr_parent_top_level(result, _, this) }
63-
64-
/**
65-
* Gets a control flow element that is a syntactic descendant of the
66-
* initializer expression.
67-
*/
68-
AstNode getAnInitializerDescendant() {
69-
result = this.getInitializer()
70-
or
71-
result = this.getAnInitializerDescendant().getAChild()
72-
}
7357
}
7458

7559
/**
76-
* Holds if `c` is a non-static constructor that performs the initialization
60+
* Holds if `obinit` is an object initializer method that performs the initialization
7761
* of a member via assignment `init`.
7862
*/
79-
predicate constructorInitializes(InstanceConstructor c, AssignExpr init) {
63+
predicate obinitInitializes(ObjectInitMethod obinit, AssignExpr init) {
8064
exists(InitializedInstanceMember m |
81-
c.isUnboundDeclaration() and
82-
c.getDeclaringType().getAMember() = m and
83-
not c.getInitializer().isThis() and
65+
obinit.getDeclaringType().getAMember() = m and
8466
init = m.getInitializer()
8567
)
8668
}
8769

8870
/**
89-
* Gets the `i`th member initializer expression for non-static constructor `c`
71+
* Gets the `i`th member initializer expression for object initializer method `obinit`
9072
* in compilation `comp`.
9173
*/
92-
AssignExpr constructorInitializeOrder(Constructor c, CompilationExt comp, int i) {
93-
constructorInitializes(c, result) and
74+
AssignExpr initializedInstanceMemberOrder(ObjectInitMethod obinit, CompilationExt comp, int i) {
75+
obinitInitializes(obinit, result) and
9476
result =
9577
rank[i + 1](AssignExpr ae0, Location l |
96-
constructorInitializes(c, ae0) and
78+
obinitInitializes(obinit, ae0) and
9779
l = ae0.getLocation() and
9880
getCompilation(l.getFile()) = comp
9981
|
@@ -105,122 +87,12 @@ module InitializerSplitting {
10587
* Gets the last member initializer expression for non-static constructor `c`
10688
* in compilation `comp`.
10789
*/
108-
AssignExpr lastConstructorInitializer(Constructor c, CompilationExt comp) {
90+
AssignExpr lastInitializer(ObjectInitMethod obinit, CompilationExt comp) {
10991
exists(int i |
110-
result = constructorInitializeOrder(c, comp, i) and
111-
not exists(constructorInitializeOrder(c, comp, i + 1))
92+
result = initializedInstanceMemberOrder(obinit, comp, i) and
93+
not exists(initializedInstanceMemberOrder(obinit, comp, i + 1))
11294
)
11395
}
114-
115-
/**
116-
* A split for non-static member initializers belonging to a given non-static
117-
* constructor. For example, in
118-
*
119-
* ```csharp
120-
* class C
121-
* {
122-
* int Field1 = 0;
123-
* int Field2 = Field1 + 1;
124-
* int Field3;
125-
*
126-
* public C()
127-
* {
128-
* Field3 = 2;
129-
* }
130-
*
131-
* public C(int i)
132-
* {
133-
* Field3 = 3;
134-
* }
135-
* }
136-
* ```
137-
*
138-
* the initializer expressions `Field1 = 0` and `Field2 = Field1 + 1` are split
139-
* on the two constructors. This is in order to generate CFGs for the two
140-
* constructors that mimic
141-
*
142-
* ```csharp
143-
* public C()
144-
* {
145-
* Field1 = 0;
146-
* Field2 = Field1 + 1;
147-
* Field3 = 2;
148-
* }
149-
* ```
150-
*
151-
* and
152-
*
153-
* ```csharp
154-
* public C()
155-
* {
156-
* Field1 = 0;
157-
* Field2 = Field1 + 1;
158-
* Field3 = 3;
159-
* }
160-
* ```
161-
*
162-
* respectively.
163-
*/
164-
private class InitializerSplit extends Split, TInitializerSplit {
165-
private Constructor c;
166-
167-
InitializerSplit() { this = TInitializerSplit(c) }
168-
169-
/** Gets the constructor. */
170-
Constructor getConstructor() { result = c }
171-
172-
override string toString() { result = "" }
173-
}
174-
175-
private class InitializerSplitKind extends SplitKind, TInitializerSplitKind {
176-
override int getListOrder() { result = 0 }
177-
178-
override predicate isEnabled(AstNode cfe) { this.appliesTo(cfe) }
179-
180-
override string toString() { result = "Initializer" }
181-
}
182-
183-
int getNextListOrder() { result = 1 }
184-
185-
private class InitializerSplitImpl extends SplitImpl instanceof InitializerSplit {
186-
override InitializerSplitKind getKind() { any() }
187-
188-
override predicate hasEntry(AstNode pred, AstNode succ, Completion c) {
189-
exists(ConstructorInitializer ci |
190-
last(ci, pred, c) and
191-
succ(pred, succ, c) and
192-
succ = any(InitializedInstanceMember m).getAnInitializerDescendant() and
193-
super.getConstructor() = ci.getConstructor()
194-
)
195-
}
196-
197-
override predicate hasEntryScope(CfgScope scope, AstNode first) {
198-
scopeFirst(scope, first) and
199-
scope = super.getConstructor() and
200-
first = any(InitializedInstanceMember m).getAnInitializerDescendant()
201-
}
202-
203-
override predicate hasExit(AstNode pred, AstNode succ, Completion c) {
204-
this.appliesTo(pred) and
205-
succ(pred, succ, c) and
206-
not succ = any(InitializedInstanceMember m).getAnInitializerDescendant() and
207-
succ.(ControlFlowElement).getEnclosingCallable() = super.getConstructor()
208-
}
209-
210-
override predicate hasExitScope(CfgScope scope, AstNode last, Completion c) {
211-
this.appliesTo(last) and
212-
scopeLast(scope, last, c) and
213-
scope = super.getConstructor()
214-
}
215-
216-
override predicate hasSuccessor(AstNode pred, AstNode succ, Completion c) {
217-
this.appliesSucc(pred, succ, c) and
218-
succ =
219-
any(InitializedInstanceMember m |
220-
constructorInitializes(super.getConstructor(), m.getInitializer())
221-
).getAnInitializerDescendant()
222-
}
223-
}
22496
}
22597

22698
module ConditionalCompletionSplitting {
@@ -249,7 +121,7 @@ module ConditionalCompletionSplitting {
249121
}
250122

251123
private class ConditionalCompletionSplitKind_ extends SplitKind, TConditionalCompletionSplitKind {
252-
override int getListOrder() { result = InitializerSplitting::getNextListOrder() }
124+
override int getListOrder() { result = 0 }
253125

254126
override predicate isEnabled(AstNode cfe) { this.appliesTo(cfe) }
255127

@@ -312,6 +184,4 @@ module ConditionalCompletionSplitting {
312184
)
313185
}
314186
}
315-
316-
int getNextListOrder() { result = InitializerSplitting::getNextListOrder() + 1 }
317187
}

csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowDispatch.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ private import semmle.code.csharp.internal.Location
1616
*/
1717
Callable getCallableForDataFlow(Callable c) {
1818
result = c.getUnboundDeclaration() and
19-
result.hasBody() and
19+
(result.hasBody() or result instanceof ObjectInitMethod) and
2020
result.getFile().fromSource()
2121
}
2222

0 commit comments

Comments
 (0)