diff --git a/csharp/ql/lib/semmle/code/cil/Declaration.qll b/csharp/ql/lib/semmle/code/cil/Declaration.qll index 9d35eeba9f9..559703e1707 100644 --- a/csharp/ql/lib/semmle/code/cil/Declaration.qll +++ b/csharp/ql/lib/semmle/code/cil/Declaration.qll @@ -5,6 +5,7 @@ import CIL private import dotnet private import semmle.code.csharp.Member as CS +private import semmle.code.csharp.commons.QualifiedName /** * A declaration. Either a member (`Member`) or a variable (`Variable`). @@ -23,6 +24,14 @@ class Declaration extends DotNet::Declaration, Element, @cil_declaration { } override Declaration getUnboundDeclaration() { result = this } + + override predicate hasQualifiedName(string qualifier, string name) { + exists(string dqualifier, string dname | + this.getDeclaringType().hasQualifiedName(dqualifier, dname) and + qualifier = getQualifiedName(dqualifier, dname) + ) and + name = this.getName() + } } private CS::Declaration toCSharpNonTypeParameter(Declaration d) { result.matchesHandle(d) } diff --git a/csharp/ql/lib/semmle/code/csharp/Callable.qll b/csharp/ql/lib/semmle/code/csharp/Callable.qll index 6c9fd828132..20999215d0d 100644 --- a/csharp/ql/lib/semmle/code/csharp/Callable.qll +++ b/csharp/ql/lib/semmle/code/csharp/Callable.qll @@ -472,16 +472,6 @@ class Operator extends Callable, Member, Attributable, Overridable, @operator { override string toString() { result = Callable.super.toString() } override Parameter getRawParameter(int i) { result = this.getParameter(i) } - - override predicate hasQualifiedName(string qualifier, string name) { - super.hasQualifiedName(qualifier, _) and - name = this.getFunctionName() - } - - override predicate hasQualifiedName(string namespace, string type, string name) { - super.hasQualifiedName(namespace, type, _) and - name = this.getFunctionName() - } } /** A clone method on a record. */ @@ -1113,14 +1103,6 @@ class LocalFunction extends Callable, Modifiable, Attributable, @local_function override Callable getEnclosingCallable() { result = this.getStatement().getEnclosingCallable() } - override predicate hasQualifiedName(string qualifier, string name) { - exists(string cqualifier, string type | - this.getEnclosingCallable().hasQualifiedName(cqualifier, type) and - qualifier = getQualifiedName(cqualifier, type) - ) and - name = this.getName() - } - override Location getALocation() { result = this.getStatement().getALocation() } override Parameter getRawParameter(int i) { result = this.getParameter(i) } diff --git a/csharp/ql/lib/semmle/code/csharp/Element.qll b/csharp/ql/lib/semmle/code/csharp/Element.qll index 390a7b16632..e2f14379472 100644 --- a/csharp/ql/lib/semmle/code/csharp/Element.qll +++ b/csharp/ql/lib/semmle/code/csharp/Element.qll @@ -3,9 +3,9 @@ */ import Location -import Member private import semmle.code.csharp.ExprOrStmtParent private import dotnet +private import commons.QualifiedName /** * A program element. Either a control flow element (`ControlFlowElement`), an diff --git a/csharp/ql/lib/semmle/code/csharp/Generics.qll b/csharp/ql/lib/semmle/code/csharp/Generics.qll index ce9c94daaab..82efe78c96d 100644 --- a/csharp/ql/lib/semmle/code/csharp/Generics.qll +++ b/csharp/ql/lib/semmle/code/csharp/Generics.qll @@ -101,20 +101,6 @@ private string getTypeArgumentsNames(ConstructedGeneric cg) { result = strictconcat(Type t, int i | t = cg.getTypeArgument(i) | t.getName(), "," order by i) } -bindingset[t] -private string getFullName(Type t) { - exists(string qualifier, string name | - t.hasQualifiedName(qualifier, name) and - result = getQualifiedName(qualifier, name) - ) -} - -/** Gets the concatenation of the `getFullName` of type arguments. */ -language[monotonicAggregates] -private string getTypeArgumentsQualifiedNames(ConstructedGeneric cg) { - result = strictconcat(Type t, int i | t = cg.getTypeArgument(i) | getFullName(t), "," order by i) -} - /** * An unbound generic type. This is a generic type with type parameters * (for example `List`) or elided type parameters (for example `List<>`). @@ -161,19 +147,6 @@ class UnboundGenericType extends ValueOrRefType, UnboundGeneric { final override string getName() { result = this.getUndecoratedName() + "<" + getTypeParameterCommas(this) + ">" } - - final override predicate hasQualifiedName(string qualifier, string name) { - exists(string name0 | name = name0 + "<" + getTypeParameterCommas(this) + ">" | - exists(string enclosing | - this.getDeclaringType().hasQualifiedName(qualifier, enclosing) and - name0 = enclosing + "+" + this.getUndecoratedName() - ) - or - not exists(this.getDeclaringType()) and - qualifier = this.getNamespace().getFullName() and - name0 = this.getUndecoratedName() - ) - } } /** @@ -240,11 +213,6 @@ class TypeParameter extends DotNet::TypeParameter, Type, @type_parameter { /** Gets the generic that defines this type parameter. */ UnboundGeneric getGeneric() { type_parameters(this, _, result, _) } - final override predicate hasQualifiedName(string qualifier, string name) { - qualifier = "" and - name = this.getName() - } - override string getAPrimaryQlClass() { result = "TypeParameter" } } @@ -440,19 +408,6 @@ class ConstructedType extends ValueOrRefType, ConstructedGeneric { final override string getName() { result = this.getUndecoratedName() + "<" + getTypeArgumentsNames(this) + ">" } - - override predicate hasQualifiedName(string qualifier, string name) { - exists(string name0 | name = name0 + "<" + getTypeArgumentsQualifiedNames(this) + ">" | - exists(string enclosing | - this.getDeclaringType().hasQualifiedName(qualifier, enclosing) and - name0 = enclosing + "+" + this.getUndecoratedName() - ) - or - not exists(this.getDeclaringType()) and - qualifier = this.getNamespace().getFullName() and - name0 = this.getUndecoratedName() - ) - } } /** @@ -624,11 +579,6 @@ class ConstructedMethod extends Method, ConstructedGeneric { result = this.getUndecoratedName() + "<" + getTypeArgumentsNames(this) + ">" } - override predicate hasQualifiedName(string namespace, string type, string name) { - this.getDeclaringType().hasQualifiedName(namespace, type) and - name = this.getUndecoratedName() + "<" + getTypeArgumentsQualifiedNames(this) + ">" - } - final override string getUndecoratedName() { methods(this, result, _, _, _) } } diff --git a/csharp/ql/lib/semmle/code/csharp/Member.qll b/csharp/ql/lib/semmle/code/csharp/Member.qll index a884f314c15..5c555cbdd88 100644 --- a/csharp/ql/lib/semmle/code/csharp/Member.qll +++ b/csharp/ql/lib/semmle/code/csharp/Member.qll @@ -7,6 +7,9 @@ import Variable private import dotnet private import Implements private import TypeRef +private import commons.QualifiedName + +private module QualifiedNameInput implements QualifiedNameInputSig { } /** * A declaration. @@ -21,6 +24,10 @@ class Declaration extends DotNet::Declaration, Element, @declaration { override string toString() { result = this.getName() } + override predicate hasQualifiedName(string qualifier, string name) { + QualifiedName::hasQualifiedName(this, qualifier, name) + } + /** * Gets the fully qualified name of this declaration, including types, for example * the fully qualified name with types of `M` on line 3 is `N.C.M(int, string)` in @@ -199,6 +206,10 @@ class Member extends DotNet::Member, Modifiable, @member { override predicate isRequired() { Modifiable.super.isRequired() } override predicate isFile() { Modifiable.super.isFile() } + + final override predicate hasQualifiedName(string namespace, string type, string name) { + QualifiedName::hasQualifiedName(this, namespace, type, name) + } } private class TOverridable = @virtualizable or @callable_accessor; diff --git a/csharp/ql/lib/semmle/code/csharp/Namespace.qll b/csharp/ql/lib/semmle/code/csharp/Namespace.qll index 6812eada0a5..f91a9c3c7e6 100644 --- a/csharp/ql/lib/semmle/code/csharp/Namespace.qll +++ b/csharp/ql/lib/semmle/code/csharp/Namespace.qll @@ -7,7 +7,7 @@ private import dotnet /** * A type container. Either a namespace (`Namespace`) or a type (`Type`). */ -class TypeContainer extends DotNet::NamedElement, Element, @type_container { } +class TypeContainer extends Declaration, @type_container { } /** * A namespace, for example @@ -30,6 +30,10 @@ class Namespace extends DotNet::Namespace, TypeContainer, Declaration, @namespac parent_namespace(result, this) } + override predicate hasQualifiedName(string qualifier, string name) { + DotNet::Namespace.super.hasQualifiedName(qualifier, name) + } + /** * Gets a type directly declared in this namespace, if any. * For example, the class `File` in diff --git a/csharp/ql/lib/semmle/code/csharp/Type.qll b/csharp/ql/lib/semmle/code/csharp/Type.qll index f262688d277..561f6d5338b 100644 --- a/csharp/ql/lib/semmle/code/csharp/Type.qll +++ b/csharp/ql/lib/semmle/code/csharp/Type.qll @@ -56,23 +56,6 @@ private predicate isObjectClass(Class c) { c instanceof ObjectType } * Either a value type (`ValueType`) or a reference type (`RefType`). */ class ValueOrRefType extends DotNet::ValueOrRefType, Type, Attributable, @value_or_ref_type { - /** - * Holds if this type has the qualified name `qualifier`.`name`. - * - * For example the class `System.IO.IOException` has - * `qualifier`=`System.IO` and `name`=`IOException`. - */ - override predicate hasQualifiedName(string qualifier, string name) { - exists(string enclosing | - this.getDeclaringType().hasQualifiedName(qualifier, enclosing) and - name = enclosing + "+" + this.getUndecoratedName() - ) - or - not exists(this.getDeclaringType()) and - qualifier = this.getNamespace().getFullName() and - name = this.getUndecoratedName() - } - /** Gets the namespace containing this type. */ Namespace getNamespace() { if exists(this.getDeclaringType()) @@ -409,11 +392,6 @@ class NonNestedType extends ValueOrRefType { * The `void` type. */ class VoidType extends ValueOrRefType, @void_type { - override predicate hasQualifiedName(string qualifier, string name) { - qualifier = "System" and - name = "Void" - } - final override string getName() { result = "Void" } final override string getUndecoratedName() { result = "Void" } @@ -1028,11 +1006,6 @@ class NullableType extends ValueType, ConstructedType, @nullable_type { override Type getTypeArgument(int p) { p = 0 and result = this.getUnderlyingType() } override string getAPrimaryQlClass() { result = "NullableType" } - - final override predicate hasQualifiedName(string qualifier, string name) { - qualifier = "System" and - name = "Nullable<" + this.getUnderlyingType().getQualifiedName() + ">" - } } /** @@ -1102,13 +1075,6 @@ class ArrayType extends DotNet::ArrayType, RefType, @array_type { not type_location(this, _) and result = this.getElementType().getALocation() } - - final override predicate hasQualifiedName(string qualifier, string name) { - exists(Type elementType, string name0 | - elementType.hasQualifiedName(qualifier, name0) and - name = name0 + this.getDimensionString(elementType) - ) - } } /** @@ -1137,13 +1103,6 @@ class PointerType extends DotNet::PointerType, Type, @pointer_type { override string toString() { result = DotNet::PointerType.super.toString() } override string getAPrimaryQlClass() { result = "PointerType" } - - final override predicate hasQualifiedName(string qualifier, string name) { - exists(string name0 | - this.getReferentType().hasQualifiedName(qualifier, name0) and - name = name0 + "*" - ) - } } /** @@ -1230,10 +1189,6 @@ class TupleType extends ValueType, @tuple_type { override Type getChild(int i) { result = this.getUnderlyingType().getChild(i) } - final override predicate hasQualifiedName(string qualifier, string name) { - this.getUnderlyingType().hasQualifiedName(qualifier, name) - } - override string getAPrimaryQlClass() { result = "TupleType" } } diff --git a/csharp/ql/lib/semmle/code/csharp/Variable.qll b/csharp/ql/lib/semmle/code/csharp/Variable.qll index 2057ef7da50..be52bcbadaf 100644 --- a/csharp/ql/lib/semmle/code/csharp/Variable.qll +++ b/csharp/ql/lib/semmle/code/csharp/Variable.qll @@ -75,8 +75,6 @@ class LocalScopeVariable extends Variable, @local_scope_variable { * Holds if this local variable or parameter is `scoped`. */ predicate isScoped() { scoped_annotation(this, _) } - - override predicate hasQualifiedName(string qualifier, string name) { none() } } /** diff --git a/csharp/ql/lib/semmle/code/csharp/commons/QualifiedName.qll b/csharp/ql/lib/semmle/code/csharp/commons/QualifiedName.qll index 4955d8db8f2..3c6b2393815 100644 --- a/csharp/ql/lib/semmle/code/csharp/commons/QualifiedName.qll +++ b/csharp/ql/lib/semmle/code/csharp/commons/QualifiedName.qll @@ -1,6 +1,201 @@ +/** Provides predicates for working with fully qualified names. */ + +private import csharp +private import dotnet + /** - * Provides predicates related to C# qualified names. + * Holds if namespace `n` has the qualified name `qualifier`.`name`. + * + * For example if the qualified name is `System.Collections.Generic`, then + * `qualifier`=`System.Collections` and `name`=`Generic`. */ +predicate namespaceHasQualifiedName(DotNet::Namespace n, string qualifier, string name) { + if n instanceof DotNet::GlobalNamespace + then qualifier = "" and name = "" + else ( + exists(string pqualifier, string pname | + namespaceHasQualifiedName(n.getParentNamespace(), pqualifier, pname) and + qualifier = getQualifiedName(pqualifier, pname) + ) and + name = n.getName() + ) +} + +/** Gets a string of `N` commas where `N + 1` is the number of type parameters of this unbound generic. */ +private string getTypeParameterCommas(UnboundGeneric ug) { + result = strictconcat(int i | exists(ug.getTypeParameter(i)) | "", ",") +} + +/** Provides the input to `QualifiedName`. */ +signature module QualifiedNameInputSig { + /** Gets the suffix to print after unbound generic `ug`. */ + default string getUnboundGenericSuffix(UnboundGeneric ug) { + result = "<" + getTypeParameterCommas(ug) + ">" + } +} + +/** Provides predicates for computing fully qualified names. */ +module QualifiedName { + private string getDimensionString(ArrayType at, Type elementType) { + exists(Type et, string res | + et = at.getElementType() and + res = at.getArraySuffix() and + if et instanceof ArrayType + then result = res + getDimensionString(et, elementType) + else ( + result = res and elementType = et + ) + ) + } + + bindingset[t] + private string getFullName(Type t) { + exists(string qualifier, string name | + hasQualifiedName(t, qualifier, name) and + result = getQualifiedName(qualifier, name) + ) + } + + /** Gets the concatenation of the `getFullName` of type arguments. */ + language[monotonicAggregates] + private string getTypeArgumentsQualifiedNames(ConstructedGeneric cg) { + result = + strictconcat(Type t, int i | t = cg.getTypeArgument(i) | getFullName(t), "," order by i) + } + + /** Holds if declaration `d` has the qualified name `qualifier`.`name`. */ + predicate hasQualifiedName(Declaration d, string qualifier, string name) { + d = + any(ValueOrRefType vort | + vort instanceof VoidType and + qualifier = "System" and + name = "Void" + or + vort = + any(ArrayType at | + exists(Type elementType, string name0 | + hasQualifiedName(elementType, qualifier, name0) and + name = name0 + getDimensionString(at, elementType) + ) + ) + or + hasQualifiedName(vort.(TupleType).getUnderlyingType(), qualifier, name) + or + qualifier = "System" and + name = "Nullable<" + getFullName(vort.(NullableType).getUnderlyingType()) + ">" + or + vort = + any(UnboundGenericType ugt | + exists(string name0 | name = name0 + Input::getUnboundGenericSuffix(ugt) | + exists(string enclosing | + hasQualifiedName(ugt.getDeclaringType(), qualifier, enclosing) and + name0 = enclosing + "+" + ugt.getUndecoratedName() + ) + or + not exists(ugt.getDeclaringType()) and + qualifier = ugt.getNamespace().getFullName() and + name0 = ugt.getUndecoratedName() + ) + ) + or + vort = + any(ConstructedType ct | + exists(string name0 | name = name0 + "<" + getTypeArgumentsQualifiedNames(ct) + ">" | + exists(string enclosing | + hasQualifiedName(ct.getDeclaringType(), qualifier, enclosing) and + name0 = enclosing + "+" + ct.getUndecoratedName() + ) + or + not exists(ct.getDeclaringType()) and + qualifier = ct.getNamespace().getFullName() and + name0 = ct.getUndecoratedName() + ) + ) + or + not vort instanceof VoidType and + not vort instanceof ArrayType and + not vort instanceof TupleType and + not vort instanceof NullableType and + not vort instanceof UnboundGenericType and + not vort instanceof ConstructedType and + ( + exists(string enclosing | + hasQualifiedName(vort.getDeclaringType(), qualifier, enclosing) and + name = enclosing + "+" + vort.getUndecoratedName() + ) + or + not exists(vort.getDeclaringType()) and + qualifier = vort.getNamespace().getFullName() and + name = vort.getUndecoratedName() + ) + ) + or + exists(string name0 | + hasQualifiedName(d.(PointerType).getReferentType(), qualifier, name0) and + name = name0 + "*" + ) + or + qualifier = "" and + name = d.(TypeParameter).getName() + or + d = + any(LocalFunction lf | + exists(string cqualifier, string type | + hasQualifiedName(lf.getEnclosingCallable(), cqualifier, type) and + qualifier = getQualifiedName(cqualifier, type) + ) and + name = lf.getName() + ) + or + // no case for `LocalScopeVariable` + namespaceHasQualifiedName(d, qualifier, name) + or + not d instanceof ValueOrRefType and + not d instanceof PointerType and + not d instanceof TypeParameter and + not d instanceof LocalFunction and + not d instanceof LocalScopeVariable and + not d instanceof Namespace and + exists(string dqualifier, string dname | + hasQualifiedName(d.getDeclaringType(), dqualifier, dname) and + qualifier = getQualifiedName(dqualifier, dname) + ) and + ( + name = d.(Operator).getFunctionName() + or + not d instanceof Operator and + name = d.getName() + ) + } + + /** + * Holds if member `m` has name `name` and is defined in type `type` + * with namespace `namespace`. + */ + predicate hasQualifiedName(Member m, string namespace, string type, string name) { + m = + any(ConstructedMethod cm | + hasQualifiedName(cm.getDeclaringType(), namespace, type) and + name = cm.getUndecoratedName() + "<" + getTypeArgumentsQualifiedNames(cm) + ">" + ) + or + m = + any(UnboundGenericMethod ugm | + hasQualifiedName(ugm.getDeclaringType(), namespace, type) and + name = ugm.getUndecoratedName() + Input::getUnboundGenericSuffix(ugm) + ) + or + not m instanceof ConstructedMethod and + not m instanceof UnboundGenericMethod and + hasQualifiedName(m.getDeclaringType(), namespace, type) and + ( + name = m.(Operator).getFunctionName() + or + not m instanceof Operator and + name = m.getName() + ) + } +} /** * Returns the concatenation of `qualifier` and `name`, separated by a dot. diff --git a/csharp/ql/lib/semmle/code/dotnet/Declaration.qll b/csharp/ql/lib/semmle/code/dotnet/Declaration.qll index 573a690c0ec..639ab99c040 100644 --- a/csharp/ql/lib/semmle/code/dotnet/Declaration.qll +++ b/csharp/ql/lib/semmle/code/dotnet/Declaration.qll @@ -8,14 +8,6 @@ private import semmle.code.csharp.commons.QualifiedName /** A declaration. */ class Declaration extends NamedElement, @dotnet_declaration { - override predicate hasQualifiedName(string qualifier, string name) { - exists(string dqualifier, string dname | - this.getDeclaringType().hasQualifiedName(dqualifier, dname) and - qualifier = getQualifiedName(dqualifier, dname) - ) and - name = this.getName() - } - /** Gets the name of this declaration, without additional decoration such as `<...>`. */ string getUndecoratedName() { none() } diff --git a/csharp/ql/lib/semmle/code/dotnet/Namespace.qll b/csharp/ql/lib/semmle/code/dotnet/Namespace.qll index 31a625de062..6bf6e3dbc92 100644 --- a/csharp/ql/lib/semmle/code/dotnet/Namespace.qll +++ b/csharp/ql/lib/semmle/code/dotnet/Namespace.qll @@ -26,11 +26,7 @@ class Namespace extends Declaration, @namespace { * `qualifier`=`System.Collections` and `name`=`Generic`. */ override predicate hasQualifiedName(string qualifier, string name) { - exists(string pqualifier, string pname | - this.getParentNamespace().hasQualifiedName(pqualifier, pname) and - qualifier = getQualifiedName(pqualifier, pname) - ) and - name = this.getName() + namespaceHasQualifiedName(this, qualifier, name) } /** Gets a textual representation of this namespace. */ @@ -51,7 +47,7 @@ class Namespace extends Declaration, @namespace { */ string getFullName() { exists(string namespace, string name | - this.hasQualifiedName(namespace, name) and + namespaceHasQualifiedName(this, namespace, name) and result = getQualifiedName(namespace, name) ) } @@ -60,8 +56,4 @@ class Namespace extends Declaration, @namespace { /** The global namespace. */ class GlobalNamespace extends Namespace { GlobalNamespace() { this.getName() = "" } - - override predicate hasQualifiedName(string qualifier, string name) { - qualifier = "" and name = "" - } }