Skip to content

Commit df61eaf

Browse files
authored
Merge pull request github#13565 from hvitved/csharp/gvn-blowup
C#: Avoid combinatorial explosions in GVN construction for types
2 parents e9102bb + 160771e commit df61eaf

File tree

7 files changed

+61
-36
lines changed

7 files changed

+61
-36
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import csharp
2+
import semmle.code.csharp.Unification
3+
4+
query predicate missingGvn(Type t, string cls) {
5+
not exists(Gvn::getGlobalValueNumber(t)) and
6+
cls = t.getPrimaryQlClasses()
7+
}
8+
9+
query predicate multipleGvn(Type t, Gvn::GvnType g, string cls) {
10+
g = Gvn::getGlobalValueNumber(t) and
11+
strictcount(Gvn::getGlobalValueNumber(t)) > 1 and
12+
cls = t.getPrimaryQlClasses()
13+
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,8 @@ class AnnotatedArrayType extends AnnotatedType {
401401
class AnnotatedConstructedType extends AnnotatedType {
402402
override ConstructedType type;
403403

404+
AnnotatedConstructedType() { not type instanceof NullableType }
405+
404406
/** Gets the `i`th type argument of this constructed type. */
405407
AnnotatedType getTypeArgument(int i) {
406408
result.getType() = type.getTypeArgument(i) and

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

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ private import TypeRef
2626
class Generic extends DotNet::Generic, Declaration, @generic {
2727
Generic() {
2828
type_parameters(_, _, this, _) or
29-
type_arguments(_, _, this)
29+
type_arguments(_, _, this) or
30+
nullable_underlying_type(this, _)
3031
}
3132
}
3233

@@ -39,7 +40,7 @@ class Generic extends DotNet::Generic, Declaration, @generic {
3940
class UnboundGeneric extends DotNet::UnboundGeneric, Generic {
4041
UnboundGeneric() { type_parameters(_, _, this, _) }
4142

42-
override TypeParameter getTypeParameter(int n) { type_parameters(result, n, this, _) }
43+
final override TypeParameter getTypeParameter(int n) { type_parameters(result, n, this, _) }
4344

4445
override ConstructedGeneric getAConstructedGeneric() { result.getUnboundGeneric() = this }
4546

@@ -67,16 +68,18 @@ private string getTypeParameterCommas(UnboundGeneric ug) {
6768
* generic method (`ConstructedMethod`).
6869
*/
6970
class ConstructedGeneric extends DotNet::ConstructedGeneric, Generic {
70-
ConstructedGeneric() { type_arguments(_, _, this) }
71+
ConstructedGeneric() {
72+
type_arguments(_, _, this)
73+
or
74+
nullable_underlying_type(this, _)
75+
}
7176

7277
override UnboundGeneric getUnboundGeneric() { constructed_generic(this, result) }
7378

7479
override UnboundGeneric getUnboundDeclaration() {
7580
result = this.getUnboundGeneric().getUnboundDeclaration()
7681
}
7782

78-
override int getNumberOfTypeArguments() { result = count(int i | type_arguments(_, i, this)) }
79-
8083
override Type getTypeArgument(int i) { none() }
8184

8285
override Type getATypeArgument() { result = this.getTypeArgument(_) }
@@ -410,21 +413,21 @@ class ConstructedType extends ValueOrRefType, ConstructedGeneric {
410413

411414
override Location getALocation() { result = this.getUnboundDeclaration().getALocation() }
412415

413-
override Type getTypeArgument(int n) { type_arguments(getTypeRef(result), n, getTypeRef(this)) }
416+
override Type getTypeArgument(int n) { type_arguments(getTypeRef(result), n, this) }
414417

415418
override UnboundGenericType getUnboundGeneric() { constructed_generic(this, getTypeRef(result)) }
416419

417420
final override Type getChild(int n) { result = this.getTypeArgument(n) }
418421

419-
final override string toStringWithTypes() {
422+
override string toStringWithTypes() {
420423
result = this.getUndecoratedName() + "<" + getTypeArgumentsToString(this) + ">"
421424
}
422425

423426
final override string getName() {
424427
result = this.getUndecoratedName() + "<" + getTypeArgumentsNames(this) + ">"
425428
}
426429

427-
final override predicate hasQualifiedName(string qualifier, string name) {
430+
override predicate hasQualifiedName(string qualifier, string name) {
428431
exists(string name0 | name = name0 + "<" + getTypeArgumentsQualifiedNames(this) + ">" |
429432
exists(string enclosing |
430433
this.getDeclaringType().hasQualifiedName(qualifier, enclosing) and

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

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -974,29 +974,27 @@ class NullType extends RefType, @null_type {
974974
/**
975975
* A nullable type, for example `int?`.
976976
*/
977-
class NullableType extends ValueType, DotNet::ConstructedGeneric, @nullable_type {
977+
class NullableType extends ValueType, ConstructedType, @nullable_type {
978978
/**
979979
* Gets the underlying value type of this nullable type.
980980
* For example `int` in `int?`.
981981
*/
982982
Type getUnderlyingType() { nullable_underlying_type(this, getTypeRef(result)) }
983983

984+
override UnboundGenericStruct getUnboundGeneric() {
985+
result.hasQualifiedName("System", "Nullable<>")
986+
}
987+
984988
override string toStringWithTypes() {
985989
result = this.getUnderlyingType().toStringWithTypes() + "?"
986990
}
987991

988-
override Type getChild(int n) { result = this.getUnderlyingType() and n = 0 }
989-
990992
override Location getALocation() { result = this.getUnderlyingType().getALocation() }
991993

992994
override Type getTypeArgument(int p) { p = 0 and result = this.getUnderlyingType() }
993995

994996
override string getAPrimaryQlClass() { result = "NullableType" }
995997

996-
final override string getName() {
997-
result = "Nullable<" + this.getUnderlyingType().getName() + ">"
998-
}
999-
1000998
final override predicate hasQualifiedName(string qualifier, string name) {
1001999
qualifier = "System" and
10021000
name = "Nullable<" + this.getUnderlyingType().getQualifiedName() + ">"
@@ -1126,7 +1124,10 @@ class ArglistType extends Type, @arglist_type {
11261124
* A type that could not be resolved. This could happen if an indirect reference
11271125
* is not available at compilation time.
11281126
*/
1129-
class UnknownType extends Type, @unknown_type { }
1127+
class UnknownType extends Type, @unknown_type {
1128+
/** Holds if this is the canonical unknown type, and not a type that failed to extract properly. */
1129+
predicate isCanonical() { types(this, _, "<unknown type>") }
1130+
}
11301131

11311132
/**
11321133
* A type representing a tuple. For example, `(int, bool, string)`.

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ private class TypeRef extends @typeref {
1616
typeref_type(this, result)
1717
or
1818
not typeref_type(this, _) and
19-
result instanceof UnknownType
19+
result.(UnknownType).isCanonical()
2020
}
2121
}
2222

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

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@ module Gvn {
1515
* but only if the enclosing type is not a `GenericType`.
1616
*/
1717
string getNameNested(Type t) {
18-
if not t instanceof NestedType or t.(NestedType).getDeclaringType() instanceof GenericType
19-
then result = t.getName()
20-
else result = getNameNested(t.(NestedType).getDeclaringType()) + "+" + t.getName()
18+
exists(string name | name = t.getName() |
19+
if not t instanceof NestedType or t.(NestedType).getDeclaringType() instanceof GenericType
20+
then result = name
21+
else result = getNameNested(t.(NestedType).getDeclaringType()) + "+" + name
22+
)
2123
}
2224

2325
/**
@@ -47,8 +49,22 @@ module Gvn {
4749
not exists(this.getGenericDeclaringType()) and result = 0
4850
}
4951

52+
/**
53+
* Same as `getChild`, but safe-guards against potential extractor issues where
54+
* multiple children exist at the same index, which may result in a combinatorial
55+
* explosion.
56+
*/
57+
private Type getChildUnique(int i) {
58+
result = unique(Type t | t = this.getChild(i) | t)
59+
or
60+
strictcount(this.getChild(i)) > 1 and
61+
result.(UnknownType).isCanonical()
62+
}
63+
5064
/** Gets the number of arguments of this type, not taking nested types into account. */
51-
int getNumberOfArgumentsSelf() { result = count(int i | exists(this.getChild(i)) and i >= 0) }
65+
int getNumberOfArgumentsSelf() {
66+
result = count(int i | exists(this.getChildUnique(i)) and i >= 0)
67+
}
5268

5369
/** Gets the number of arguments of this type, taking nested types into account. */
5470
int getNumberOfArguments() {
@@ -61,7 +77,7 @@ module Gvn {
6177
or
6278
exists(int offset |
6379
offset = this.getNumberOfDeclaringArguments() and
64-
result = this.getChild(i - offset) and
80+
result = this.getChildUnique(i - offset) and
6581
i >= offset
6682
)
6783
}
@@ -91,13 +107,9 @@ module Gvn {
91107
int getNumberOfTypeParameters() {
92108
this = TPointerTypeKind() and result = 1
93109
or
94-
this = TNullableTypeKind() and result = 1
95-
or
96110
this = TArrayTypeKind(_, _) and result = 1
97111
or
98-
exists(GenericType t | this = TConstructedType(t.getUnboundDeclaration()) |
99-
result = t.getNumberOfArguments()
100-
)
112+
exists(GenericType t | this = TConstructedType(t) | result = t.getNumberOfArguments())
101113
}
102114

103115
/** Gets the unbound declaration type that this kind corresponds to, if any. */
@@ -106,15 +118,12 @@ module Gvn {
106118
/**
107119
* Gets a textual representation of this kind when applied to arguments `args`.
108120
*
109-
* This predicate is restricted to built-in generics (pointers, nullables, and
110-
* arrays).
121+
* This predicate is restricted to built-in generics (pointers and arrays).
111122
*/
112123
bindingset[args]
113124
string toStringBuiltin(string args) {
114125
this = TPointerTypeKind() and result = args + "*"
115126
or
116-
this = TNullableTypeKind() and result = args + "?"
117-
or
118127
exists(int rnk | this = TArrayTypeKind(_, rnk) |
119128
result = args + "[" + concat(int i | i in [0 .. rnk - 2] | ",") + "]"
120129
)
@@ -135,8 +144,6 @@ module Gvn {
135144
CompoundTypeKind getTypeKind(Type t) {
136145
result = TPointerTypeKind() and t instanceof PointerType
137146
or
138-
result = TNullableTypeKind() and t instanceof NullableType
139-
or
140147
t = any(ArrayType at | result = TArrayTypeKind(at.getDimension(), at.getRank()))
141148
or
142149
result = TConstructedType(t.getUnboundDeclaration())
@@ -280,6 +287,7 @@ module Gvn {
280287

281288
pragma[noinline]
282289
private predicate toStringPart(int i, int j) {
290+
this.isFullyConstructed() and
283291
exists(int offset |
284292
exists(GenericType t, int children |
285293
t = this.getConstructedGenericDeclaringTypeAt(i) and
@@ -449,14 +457,12 @@ module Gvn {
449457
cached
450458
newtype TCompoundTypeKind =
451459
TPointerTypeKind() { Stages::UnificationStage::forceCachingInSameStage() } or
452-
TNullableTypeKind() or
453460
TArrayTypeKind(int dim, int rnk) {
454461
exists(ArrayType at | dim = at.getDimension() and rnk = at.getRank())
455462
} or
456463
TConstructedType(GenericType unboundDecl) {
457464
unboundDecl = any(GenericType t).getUnboundDeclaration() and
458465
not unboundDecl instanceof PointerType and
459-
not unboundDecl instanceof NullableType and
460466
not unboundDecl instanceof ArrayType and
461467
not unboundDecl instanceof TupleType
462468
}

csharp/ql/lib/semmle/code/dotnet/Generics.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ abstract class ConstructedGeneric extends Generic {
4141
UnboundGeneric getUnboundGeneric() { none() }
4242

4343
/** Gets the total number of type arguments. */
44-
int getNumberOfTypeArguments() { result = count(int i | exists(this.getTypeArgument(i))) }
44+
final int getNumberOfTypeArguments() { result = count(int i | exists(this.getTypeArgument(i))) }
4545
}
4646

4747
/**

0 commit comments

Comments
 (0)