Skip to content

Commit 4c1a9b2

Browse files
committed
C#: Teach unification library about nested types
1 parent 851fc98 commit 4c1a9b2

File tree

3 files changed

+174
-48
lines changed

3 files changed

+174
-48
lines changed

csharp/ql/src/semmle/code/csharp/Unification.qll

Lines changed: 131 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,69 @@ private import Caching
1010
* equal modulo identity conversions and type parameters.
1111
*/
1212
module Gvn {
13+
/** Gets the qualified name of type `t`. */
14+
string getQualifiedName(Type t) {
15+
if not t instanceof NestedType or t.(NestedType).getDeclaringType() instanceof GenericType
16+
then result = t.getName()
17+
else result = getQualifiedName(t.(NestedType).getDeclaringType()) + "." + t.getName()
18+
}
19+
20+
/**
21+
* A generic type. This is either a type with a type parameter, a type with
22+
* a type argument, or a nested type with a generic enclosing type.
23+
*/
24+
class GenericType extends Type {
25+
GenericType() {
26+
exists(this.getChild(0))
27+
or
28+
this.(NestedType).getDeclaringType() instanceof GenericType
29+
}
30+
31+
/** Gets the generic containing type, if any. */
32+
GenericType getQualifier() { result = this.(NestedType).getDeclaringType() }
33+
34+
/**
35+
* Gets the number of children of the generic containing type, or 0 if there
36+
* is no generic containing type.
37+
*/
38+
int getNumberOfQualifierChildrenExt() {
39+
result = this.getQualifier().getNumberOfChildrenExt()
40+
or
41+
not exists(this.getQualifier()) and result = 0
42+
}
43+
44+
/** Gets the number of children of this type, not taking nested types into account. */
45+
int getNumberOfChildrenSelf() { result = count(int i | exists(this.getChild(i)) and i >= 0) }
46+
47+
/** Gets the number of children of this type, taking nested types into account. */
48+
int getNumberOfChildrenExt() {
49+
result = this.getNumberOfQualifierChildrenExt() + this.getNumberOfChildrenSelf()
50+
}
51+
52+
/** Gets the `i`th child of this type, taking nested types into account. */
53+
Type getChildExt(int i) {
54+
result = this.getQualifier().getChildExt(i)
55+
or
56+
exists(int offset |
57+
offset = this.getNumberOfQualifierChildrenExt() and
58+
result = this.getChild(i - offset) and
59+
i >= offset
60+
)
61+
}
62+
63+
/** Gets a textual representation of this type, taking nested types into account. */
64+
string toStringExt() {
65+
exists(string name | name = getQualifiedName(this) |
66+
result = this.getQualifier().toStringExt() + "." + name
67+
or
68+
not exists(this.getQualifier()) and result = name
69+
)
70+
}
71+
}
72+
1373
private class LeafType extends Type {
1474
LeafType() {
15-
not exists(this.getAChild()) and
75+
not this instanceof GenericType and
1676
not this instanceof TypeParameter and
1777
not this instanceof DynamicType
1878
}
@@ -28,29 +88,37 @@ module Gvn {
2888
or
2989
this = TArrayTypeKind(_, _) and result = 1
3090
or
31-
exists(UnboundGenericType ugt | this = TConstructedType(ugt) |
32-
result = ugt.getNumberOfTypeParameters()
91+
exists(GenericType t | this = TConstructedType(t.getSourceDeclaration()) |
92+
result = t.getNumberOfChildrenExt()
3393
)
3494
}
3595

36-
/** Gets a textual representation of this kind when applied to arguments `args`. */
96+
/** Gets the source declaration type that this kind corresponds to, if any. */
97+
GenericType getConstructedSourceDeclaration() { this = TConstructedType(result) }
98+
99+
/**
100+
* Gets a textual representation of this kind when applied to arguments `args`.
101+
*
102+
* This predicate is restricted to built-in generics (pointers, nullables, and
103+
* arrays).
104+
*/
37105
bindingset[args]
38-
string toString(string args) {
106+
string toStringBuiltin(string args) {
39107
this = TPointerTypeKind() and result = args + "*"
40108
or
41109
this = TNullableTypeKind() and result = args + "?"
42110
or
43111
exists(int rnk | this = TArrayTypeKind(_, rnk) |
44112
result = args + "[" + concat(int i | i in [0 .. rnk - 2] | ",") + "]"
45113
)
46-
or
47-
exists(UnboundGenericType ugt | this = TConstructedType(ugt) |
48-
result = ugt.getNameWithoutBrackets() + "<" + args + ">"
49-
)
50114
}
51115

52116
/** Gets a textual representation of this kind. */
53-
string toString() { result = toString("") }
117+
string toString() {
118+
result = this.toStringBuiltin("")
119+
or
120+
result = this.getConstructedSourceDeclaration().toStringExt()
121+
}
54122

55123
/** Gets the location of this kind. */
56124
Location getLocation() { result instanceof EmptyLocation }
@@ -64,11 +132,9 @@ module Gvn {
64132
or
65133
t = any(ArrayType at | result = TArrayTypeKind(at.getDimension(), at.getRank()))
66134
or
67-
result = TConstructedType(t.(ConstructedType).getUnboundGeneric())
68-
or
69-
result = TConstructedType(t.(TupleType).getUnderlyingType().getUnboundGeneric())
135+
result = TConstructedType(t.getSourceDeclaration())
70136
or
71-
result = TConstructedType(t)
137+
result = TConstructedType(t.(TupleType).getUnderlyingType().getSourceDeclaration())
72138
}
73139

74140
/**
@@ -107,7 +173,7 @@ module Gvn {
107173
override CompoundTypeKind getKind() { result = l.getKind() }
108174
}
109175

110-
private ConstructedGvnTypeList gvnConstructed(Type t, CompoundTypeKind k, int i) {
176+
private ConstructedGvnTypeList gvnConstructed(GenericType t, CompoundTypeKind k, int i) {
111177
result = TConstructedGvnTypeNil(k) and
112178
i = -1 and
113179
k = getTypeKind(t)
@@ -118,14 +184,16 @@ module Gvn {
118184
}
119185

120186
pragma[noinline]
121-
private GvnType gvnTypeChild(Type t, int i) { result = getGlobalValueNumber(t.getChild(i)) }
187+
private GvnType gvnTypeChildExt(GenericType t, int i) {
188+
result = getGlobalValueNumber(t.getChildExt(i))
189+
}
122190

123191
pragma[noinline]
124192
private predicate gvnConstructedCons(
125-
Type t, CompoundTypeKind k, int i, GvnType head, ConstructedGvnTypeList tail
193+
GenericType t, CompoundTypeKind k, int i, GvnType head, ConstructedGvnTypeList tail
126194
) {
127195
tail = gvnConstructed(t, k, i - 1) and
128-
head = gvnTypeChild(t, i)
196+
head = gvnTypeChildExt(t, i)
129197
}
130198

131199
private class ConstructedGvnTypeList extends TConstructedGvnTypeList {
@@ -150,17 +218,47 @@ module Gvn {
150218
)
151219
}
152220

221+
/**
222+
* Gets a textual representation of this constructed type, restricted
223+
* to the prefix `t` of the underlying source declaration type.
224+
*
225+
* The `toString()` calculation needs to be split up into prefixes, in
226+
* order to apply the type arguments correctly. For example, a source
227+
* declaration type `A<>.B.C<,>` applied to types `int, string, bool`
228+
* needs to be printed as `A<int>.B.C<string,bool>`.
229+
*/
230+
language[monotonicAggregates]
231+
private string toStringConstructed(GenericType t) {
232+
t = this.getKind().getConstructedSourceDeclaration().getQualifier*() and
233+
exists(int offset, int children, string name, string nameArgs |
234+
offset = t.getNumberOfQualifierChildrenExt() and
235+
children = t.getNumberOfChildrenSelf() and
236+
name = getQualifiedName(t) and
237+
if children = 0
238+
then nameArgs = name
239+
else
240+
exists(string offsetArgs |
241+
offsetArgs =
242+
concat(int i |
243+
i in [offset .. offset + children - 1]
244+
|
245+
this.getArg(i).toString(), "," order by i
246+
) and
247+
nameArgs = name.prefix(name.length() - children - 1) + "<" + offsetArgs + ">"
248+
)
249+
|
250+
offset = 0 and result = nameArgs
251+
or
252+
result = this.toStringConstructed(t.getQualifier()) + "." + nameArgs
253+
)
254+
}
255+
153256
language[monotonicAggregates]
154257
string toString() {
155-
exists(CompoundTypeKind k, string args |
156-
k = this.getKind() and
157-
args =
158-
concat(int i |
159-
i in [0 .. k.getNumberOfTypeParameters() - 1]
160-
|
161-
this.getArg(i).toString(), "," order by i
162-
) and
163-
result = k.toString(args)
258+
exists(CompoundTypeKind k | k = this.getKind() |
259+
result = k.toStringBuiltin(this.getArg(0).toString())
260+
or
261+
result = this.toStringConstructed(k.getConstructedSourceDeclaration())
164262
)
165263
}
166264

@@ -366,7 +464,12 @@ module Gvn {
366464
TArrayTypeKind(int dim, int rnk) {
367465
exists(ArrayType at | dim = at.getDimension() and rnk = at.getRank())
368466
} or
369-
TConstructedType(UnboundGenericType ugt) { exists(ugt.getATypeParameter()) }
467+
TConstructedType(GenericType sourceDecl) {
468+
sourceDecl = any(GenericType t).getSourceDeclaration() and
469+
not sourceDecl instanceof PointerType and
470+
not sourceDecl instanceof NullableType and
471+
not sourceDecl instanceof ArrayType
472+
}
370473

371474
cached
372475
newtype TGvnType =

csharp/ql/test/library-tests/dataflow/types/Types.expected

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -32,17 +32,17 @@ edges
3232
| Types.cs:74:9:74:9 | access to local variable d : D | Types.cs:16:30:16:30 | this : D |
3333
| Types.cs:77:22:77:22 | a : C | Types.cs:79:18:79:25 | SSA def(b) : C |
3434
| Types.cs:79:18:79:25 | SSA def(b) : C | Types.cs:80:18:80:18 | access to local variable b |
35-
| Types.cs:90:22:90:22 | e : E2 | Types.cs:92:26:92:26 | access to parameter e : E2 |
36-
| Types.cs:92:13:92:16 | [post] this access [Field] : E2 | Types.cs:93:13:93:16 | this access [Field] : E2 |
37-
| Types.cs:92:26:92:26 | access to parameter e : E2 | Types.cs:92:13:92:16 | [post] this access [Field] : E2 |
38-
| Types.cs:93:13:93:16 | this access [Field] : E2 | Types.cs:113:34:113:34 | this [Field] : E2 |
39-
| Types.cs:110:25:110:32 | object creation of type E2 : E2 | Types.cs:90:22:90:22 | e : E2 |
40-
| Types.cs:113:34:113:34 | this [Field] : E2 | Types.cs:115:22:115:25 | this access [Field] : E2 |
41-
| Types.cs:115:22:115:25 | this access [Field] : E2 | Types.cs:115:22:115:31 | access to field Field |
35+
| Types.cs:90:22:90:22 | e : Types.E<D>.E2 | Types.cs:92:26:92:26 | access to parameter e : Types.E<D>.E2 |
36+
| Types.cs:92:13:92:16 | [post] this access [Field] : Types.E<D>.E2 | Types.cs:93:13:93:16 | this access [Field] : Types.E<D>.E2 |
37+
| Types.cs:92:26:92:26 | access to parameter e : Types.E<D>.E2 | Types.cs:92:13:92:16 | [post] this access [Field] : Types.E<D>.E2 |
38+
| Types.cs:93:13:93:16 | this access [Field] : Types.E<D>.E2 | Types.cs:113:34:113:34 | this [Field] : Types.E<D>.E2 |
39+
| Types.cs:110:25:110:32 | object creation of type E2 : Types.E<D>.E2 | Types.cs:90:22:90:22 | e : Types.E<D>.E2 |
40+
| Types.cs:113:34:113:34 | this [Field] : Types.E<D>.E2 | Types.cs:115:22:115:25 | this access [Field] : Types.E<D>.E2 |
41+
| Types.cs:115:22:115:25 | this access [Field] : Types.E<D>.E2 | Types.cs:115:22:115:31 | access to field Field |
4242
| Types.cs:120:25:120:31 | object creation of type A : A | Types.cs:122:30:122:30 | access to local variable a : A |
43-
| Types.cs:121:26:121:33 | object creation of type E2 : E2 | Types.cs:123:30:123:31 | access to local variable e2 : E2 |
43+
| Types.cs:121:26:121:33 | object creation of type E2 : Types.E<D>.E2 | Types.cs:123:30:123:31 | access to local variable e2 : Types.E<D>.E2 |
4444
| Types.cs:122:30:122:30 | access to local variable a : A | Types.cs:122:22:122:31 | call to method Through |
45-
| Types.cs:123:30:123:31 | access to local variable e2 : E2 | Types.cs:123:22:123:32 | call to method Through |
45+
| Types.cs:123:30:123:31 | access to local variable e2 : Types.E<D>.E2 | Types.cs:123:22:123:32 | call to method Through |
4646
nodes
4747
| Types.cs:7:21:7:25 | this : D | semmle.label | this : D |
4848
| Types.cs:7:32:7:35 | this access : D | semmle.label | this access : D |
@@ -86,20 +86,20 @@ nodes
8686
| Types.cs:77:22:77:22 | a : C | semmle.label | a : C |
8787
| Types.cs:79:18:79:25 | SSA def(b) : C | semmle.label | SSA def(b) : C |
8888
| Types.cs:80:18:80:18 | access to local variable b | semmle.label | access to local variable b |
89-
| Types.cs:90:22:90:22 | e : E2 | semmle.label | e : E2 |
90-
| Types.cs:92:13:92:16 | [post] this access [Field] : E2 | semmle.label | [post] this access [Field] : E2 |
91-
| Types.cs:92:26:92:26 | access to parameter e : E2 | semmle.label | access to parameter e : E2 |
92-
| Types.cs:93:13:93:16 | this access [Field] : E2 | semmle.label | this access [Field] : E2 |
93-
| Types.cs:110:25:110:32 | object creation of type E2 : E2 | semmle.label | object creation of type E2 : E2 |
94-
| Types.cs:113:34:113:34 | this [Field] : E2 | semmle.label | this [Field] : E2 |
95-
| Types.cs:115:22:115:25 | this access [Field] : E2 | semmle.label | this access [Field] : E2 |
89+
| Types.cs:90:22:90:22 | e : Types.E<D>.E2 | semmle.label | e : Types.E<D>.E2 |
90+
| Types.cs:92:13:92:16 | [post] this access [Field] : Types.E<D>.E2 | semmle.label | [post] this access [Field] : Types.E<D>.E2 |
91+
| Types.cs:92:26:92:26 | access to parameter e : Types.E<D>.E2 | semmle.label | access to parameter e : Types.E<D>.E2 |
92+
| Types.cs:93:13:93:16 | this access [Field] : Types.E<D>.E2 | semmle.label | this access [Field] : Types.E<D>.E2 |
93+
| Types.cs:110:25:110:32 | object creation of type E2 : Types.E<D>.E2 | semmle.label | object creation of type E2 : Types.E<D>.E2 |
94+
| Types.cs:113:34:113:34 | this [Field] : Types.E<D>.E2 | semmle.label | this [Field] : Types.E<D>.E2 |
95+
| Types.cs:115:22:115:25 | this access [Field] : Types.E<D>.E2 | semmle.label | this access [Field] : Types.E<D>.E2 |
9696
| Types.cs:115:22:115:31 | access to field Field | semmle.label | access to field Field |
9797
| Types.cs:120:25:120:31 | object creation of type A : A | semmle.label | object creation of type A : A |
98-
| Types.cs:121:26:121:33 | object creation of type E2 : E2 | semmle.label | object creation of type E2 : E2 |
98+
| Types.cs:121:26:121:33 | object creation of type E2 : Types.E<D>.E2 | semmle.label | object creation of type E2 : Types.E<D>.E2 |
9999
| Types.cs:122:22:122:31 | call to method Through | semmle.label | call to method Through |
100100
| Types.cs:122:30:122:30 | access to local variable a : A | semmle.label | access to local variable a : A |
101101
| Types.cs:123:22:123:32 | call to method Through | semmle.label | call to method Through |
102-
| Types.cs:123:30:123:31 | access to local variable e2 : E2 | semmle.label | access to local variable e2 : E2 |
102+
| Types.cs:123:30:123:31 | access to local variable e2 : Types.E<D>.E2 | semmle.label | access to local variable e2 : Types.E<D>.E2 |
103103
#select
104104
| Types.cs:23:12:23:18 | object creation of type C : C | Types.cs:50:18:50:18 | access to local variable c | Types.cs:50:18:50:18 | access to local variable c | $@ | Types.cs:50:18:50:18 | access to local variable c | access to local variable c |
105105
| Types.cs:25:12:25:18 | object creation of type C : C | Types.cs:63:33:63:36 | (...) ... | Types.cs:63:33:63:36 | (...) ... | $@ | Types.cs:63:33:63:36 | (...) ... | (...) ... |
@@ -115,6 +115,6 @@ nodes
115115
| Types.cs:39:12:39:18 | object creation of type D : D | Types.cs:69:52:69:52 | access to parameter x | Types.cs:69:52:69:52 | access to parameter x | $@ | Types.cs:69:52:69:52 | access to parameter x | access to parameter x |
116116
| Types.cs:40:12:40:18 | object creation of type D : D | Types.cs:16:42:16:45 | this access | Types.cs:16:42:16:45 | this access | $@ | Types.cs:16:42:16:45 | this access | this access |
117117
| Types.cs:43:20:43:23 | null : null | Types.cs:44:14:44:14 | access to local variable o | Types.cs:44:14:44:14 | access to local variable o | $@ | Types.cs:44:14:44:14 | access to local variable o | access to local variable o |
118-
| Types.cs:110:25:110:32 | object creation of type E2 : E2 | Types.cs:115:22:115:31 | access to field Field | Types.cs:115:22:115:31 | access to field Field | $@ | Types.cs:115:22:115:31 | access to field Field | access to field Field |
118+
| Types.cs:110:25:110:32 | object creation of type E2 : Types.E<D>.E2 | Types.cs:115:22:115:31 | access to field Field | Types.cs:115:22:115:31 | access to field Field | $@ | Types.cs:115:22:115:31 | access to field Field | access to field Field |
119119
| Types.cs:120:25:120:31 | object creation of type A : A | Types.cs:122:22:122:31 | call to method Through | Types.cs:122:22:122:31 | call to method Through | $@ | Types.cs:122:22:122:31 | call to method Through | call to method Through |
120-
| Types.cs:121:26:121:33 | object creation of type E2 : E2 | Types.cs:123:22:123:32 | call to method Through | Types.cs:123:22:123:32 | call to method Through | $@ | Types.cs:123:22:123:32 | call to method Through | call to method Through |
120+
| Types.cs:121:26:121:33 | object creation of type E2 : Types.E<D>.E2 | Types.cs:123:22:123:32 | call to method Through | Types.cs:123:22:123:32 | call to method Through | $@ | Types.cs:123:22:123:32 | call to method Through | call to method Through |

0 commit comments

Comments
 (0)