diff --git a/csharp/ql/src/semmle/code/csharp/Implements.qll b/csharp/ql/src/semmle/code/csharp/Implements.qll index 5ea4b51bfa2..8f577e98872 100644 --- a/csharp/ql/src/semmle/code/csharp/Implements.qll +++ b/csharp/ql/src/semmle/code/csharp/Implements.qll @@ -259,7 +259,7 @@ private module Gvn { private newtype TGvnType = TLeafGvnType(LeafType t) or TMethodTypeParameterGvnType(int i) { i = any(MethodTypeParameter p).getIndex() } or - TConstructedGvnType(ConstructedGvnTypeList l) + TConstructedGvnType(ConstructedGvnTypeList l) { l.isComplete() } private newtype TConstructedGvnTypeList = TConstructedGvnTypeNil(Unification::CompoundTypeKind k) or @@ -334,6 +334,8 @@ private module Gvn { ) } + predicate isComplete() { this.getKind().getNumberOfTypeParameters() - 1 = this.length() } + private GvnType getArg(int i) { exists(GvnType head, ConstructedGvnTypeList tail | this = TConstructedGvnTypeCons(head, tail) @@ -345,47 +347,71 @@ private module Gvn { ) } + private Unification::GenericType getConstructedGenericDeclaringTypeAt(int i) { + i = 0 and + result = this.getKind().getConstructedSourceDeclaration() + or + result = this.getConstructedGenericDeclaringTypeAt(i - 1).getGenericDeclaringType() + } + + private predicate isDeclaringTypeAt(int i) { + exists(this.getConstructedGenericDeclaringTypeAt(i - 1)) + } + /** - * Gets a textual representation of this constructed type, restricted - * to the prefix `t` of the underlying source declaration type. - * - * The `toString()` calculation needs to be split up into prefixes, in - * order to apply the type arguments correctly. For example, a source - * declaration type `A<>.B.C<,>` applied to types `int, string, bool` - * needs to be printed as `A.B.C`. + * Gets the `j`th `toString()` part of the `i`th nested component of this + * constructed type, if any. The nested components are sorted in reverse + * order, while the individual parts are sorted in normal order. */ language[monotonicAggregates] - private string toStringConstructed(Unification::GenericType t) { - t = this.getKind().getConstructedSourceDeclaration().getGenericDeclaringType*() and - exists(int offset, int children, string name, string nameArgs | - offset = t.getNumberOfDeclaringArguments() and - children = t.getNumberOfArgumentsSelf() and - name = Unification::getNameNested(t) and - if children = 0 - then nameArgs = name - else - exists(string offsetArgs | - offsetArgs = - concat(int i | - i in [offset .. offset + children - 1] - | - this.getArg(i).toString(), "," order by i - ) and - nameArgs = name.prefix(name.length() - children - 1) + "<" + offsetArgs + ">" + private string toStringConstructedPart(int i, int j) { + this.isComplete() and + exists(Unification::GenericType t | + t = this.getConstructedGenericDeclaringTypeAt(i) and + exists(int offset, int children, string name | + offset = t.getNumberOfDeclaringArguments() and + children = t.getNumberOfArgumentsSelf() and + name = Unification::getNameNested(t) and + if children = 0 + then + j = 0 and result = name + or + this.isDeclaringTypeAt(i) and j = 1 and result = "." + else ( + j = 0 and result = name.prefix(name.length() - children - 1) + "<" + or + j in [1 .. 2 * children - 1] and + if j % 2 = 0 + then result = "," + else result = this.getArg((j + 1) / 2 + offset - 1).toString() + or + j = 2 * children and + result = ">" + or + this.isDeclaringTypeAt(i) and + j = 2 * children + 1 and + result = "." ) - | - offset = 0 and result = nameArgs - or - result = this.toStringConstructed(t.getGenericDeclaringType()) + "." + nameArgs + ) ) } language[monotonicAggregates] string toString() { + this.isComplete() and exists(Unification::CompoundTypeKind k | k = this.getKind() | result = k.toStringBuiltin(this.getArg(0).toString()) or - result = this.toStringConstructed(k.getConstructedSourceDeclaration()) + result = + strictconcat(int i, int j | + exists(Unification::GenericType t, int children | + t = this.getConstructedGenericDeclaringTypeAt(i) and + children = t.getNumberOfArgumentsSelf() and + if children = 0 then j = 0 else j in [0 .. 2 * children] + ) + | + this.toStringConstructedPart(i, j) order by i desc, j + ) ) } diff --git a/csharp/ql/src/semmle/code/csharp/Unification.qll b/csharp/ql/src/semmle/code/csharp/Unification.qll index 2c7545c7609..795e97164f0 100644 --- a/csharp/ql/src/semmle/code/csharp/Unification.qll +++ b/csharp/ql/src/semmle/code/csharp/Unification.qll @@ -213,6 +213,8 @@ module Gvn { ) } + predicate isComplete() { this.getKind().getNumberOfTypeParameters() - 1 = this.length() } + GvnType getArg(int i) { exists(GvnType head, ConstructedGvnTypeList tail | this = TConstructedGvnTypeCons(head, tail) @@ -224,47 +226,72 @@ module Gvn { ) } + private GenericType getConstructedGenericDeclaringTypeAt(int i) { + i = 0 and + result = this.getKind().getConstructedSourceDeclaration() + or + result = this.getConstructedGenericDeclaringTypeAt(i - 1).getGenericDeclaringType() + } + + private predicate isDeclaringTypeAt(int i) { + exists(this.getConstructedGenericDeclaringTypeAt(i - 1)) + } + /** - * Gets a textual representation of this constructed type, restricted - * to the prefix `t` of the underlying source declaration type. - * - * The `toString()` calculation needs to be split up into prefixes, in - * order to apply the type arguments correctly. For example, a source - * declaration type `A<>.B.C<,>` applied to types `int, string, bool` - * needs to be printed as `A.B.C`. + * Gets the `j`th `toString()` part of the `i`th nested component of this + * constructed type, if any. The nested components are sorted in reverse + * order, while the individual parts are sorted in normal order. */ language[monotonicAggregates] - private string toStringConstructed(GenericType t) { - t = this.getKind().getConstructedSourceDeclaration().getGenericDeclaringType*() and - exists(int offset, int children, string name, string nameArgs | - offset = t.getNumberOfDeclaringArguments() and - children = t.getNumberOfArgumentsSelf() and - name = getNameNested(t) and - if children = 0 - then nameArgs = name - else - exists(string offsetArgs | - offsetArgs = - concat(int i | - i in [offset .. offset + children - 1] - | - this.getArg(i).toString(), "," order by i - ) and - nameArgs = name.prefix(name.length() - children - 1) + "<" + offsetArgs + ">" + private string toStringConstructedPart(int i, int j) { + this.isComplete() and + exists(GenericType t | + t = this.getConstructedGenericDeclaringTypeAt(i) and + exists(int offset, int children, string name | + offset = t.getNumberOfDeclaringArguments() and + children = t.getNumberOfArgumentsSelf() and + name = getNameNested(t) and + if children = 0 + then + j = 0 and result = name + or + this.isDeclaringTypeAt(i) and j = 1 and result = "." + else ( + j = 0 and result = name.prefix(name.length() - children - 1) + "<" + or + j in [1 .. 2 * children - 1] and + if j % 2 = 0 + then result = "," + else result = this.getArg((j + 1) / 2 + offset - 1).toString() + or + j = 2 * children and + result = ">" + or + this.isDeclaringTypeAt(i) and + j = 2 * children + 1 and + result = "." ) - | - offset = 0 and result = nameArgs - or - result = this.toStringConstructed(t.getGenericDeclaringType()) + "." + nameArgs + ) ) } language[monotonicAggregates] string toString() { + this.isComplete() and exists(CompoundTypeKind k | k = this.getKind() | result = k.toStringBuiltin(this.getArg(0).toString()) or - result = this.toStringConstructed(k.getConstructedSourceDeclaration()) + result = + strictconcat(int i, int j, int offset | + exists(GenericType t, int children | + t = this.getConstructedGenericDeclaringTypeAt(i) and + children = t.getNumberOfArgumentsSelf() and + (if this.isDeclaringTypeAt(i) then offset = 1 else offset = 0) and + if children = 0 then j in [0 .. offset] else j in [0 .. 2 * children + offset] + ) + | + this.toStringConstructedPart(i, j) order by i desc, j + ) ) } @@ -482,7 +509,7 @@ module Gvn { newtype TGvnType = TLeafGvnType(LeafType t) or TTypeParameterGvnType() or - TConstructedGvnType(ConstructedGvnTypeList l) + TConstructedGvnType(ConstructedGvnTypeList l) { l.isComplete() } cached newtype TConstructedGvnTypeList =