diff --git a/csharp/ql/lib/semmle/code/csharp/Callable.qll b/csharp/ql/lib/semmle/code/csharp/Callable.qll index 49a2271b27c..1bdfb008144 100644 --- a/csharp/ql/lib/semmle/code/csharp/Callable.qll +++ b/csharp/ql/lib/semmle/code/csharp/Callable.qll @@ -221,6 +221,23 @@ class Callable extends Parameterizable, ExprOrStmtParent, @callable { /** Gets a `Call` that has this callable as a target. */ Call getACall() { this = result.getTarget() } + + /** Holds if this callable is declared in an extension type. */ + predicate isInExtension() { this.getDeclaringType() instanceof ExtensionType } +} + +/** + * A callable that is declared as an extension. + * + * Either an extension method (`ExtensionMethod`), an extension operator + * (`ExtensionOperator`) or an extension accessor (`ExtensionAccessor`). + */ +abstract class ExtensionCallable extends Callable { + /** Gets the type being extended by this method. */ + pragma[noinline] + Type getExtendedType() { result = this.getDeclaringType().(ExtensionType).getExtendedType() } + + override string getAPrimaryQlClass() { result = "ExtensionCallable" } } /** @@ -267,8 +284,11 @@ class Method extends Callable, Virtualizable, Attributable, @method { override Location getALocation() { method_location(this.getUnboundDeclaration(), result) } + /** Holds if this method is a classic extension method. */ + predicate isClassicExtensionMethod() { this.getParameter(0).hasExtensionMethodModifier() } + /** Holds if this method is an extension method. */ - predicate isExtensionMethod() { this.getParameter(0).hasExtensionMethodModifier() } + predicate isExtensionMethod() { this.isClassicExtensionMethod() or this.isInExtension() } /** Gets the type of the `params` parameter of this method, if any. */ Type getParamsType() { @@ -296,7 +316,17 @@ class Method extends Callable, Virtualizable, Attributable, @method { } /** - * An extension method, for example + * An extension method. + * + * Either a classic extension method (`ClassicExtensionMethod`) or an extension + * type extension method (`ExtensionTypeExtensionMethod`). + */ +abstract class ExtensionMethod extends ExtensionCallable, Method { + override string getAPrimaryQlClass() { result = "ExtensionMethod" } +} + +/** + * An extension method, for example * * ```csharp * static bool IsDefined(this Widget w) { @@ -304,16 +334,28 @@ class Method extends Callable, Virtualizable, Attributable, @method { * } * ``` */ -class ExtensionMethod extends Method { - ExtensionMethod() { this.isExtensionMethod() } +class ClassicExtensionMethod extends ExtensionMethod { + ClassicExtensionMethod() { this.isClassicExtensionMethod() } + + pragma[noinline] + override Type getExtendedType() { result = this.getParameter(0).getType() } override predicate isStatic() { any() } +} - /** Gets the type being extended by this method. */ - pragma[noinline] - Type getExtendedType() { result = this.getParameter(0).getType() } - - override string getAPrimaryQlClass() { result = "ExtensionMethod" } +/** + * An extension method declared in an extension type, for example `IsNullOrEmpty` in + * + * ```csharp + * static class MyExtensions { + * extension(string s) { + * public bool IsNullOrEmpty() { ... } + * } + * } + * ``` + */ +class ExtensionTypeExtensionMethod extends ExtensionMethod { + ExtensionTypeExtensionMethod() { this.isInExtension() } } /** @@ -536,6 +578,21 @@ class RecordCloneMethod extends Method { } } +/** + * An extension operator, for example `*` in + * + * ```csharp + * static class MyExtensions { + * extension(string s) { + * public static string operator *(int s1, string s2) { ... } + * } + * } + * ``` + */ +class ExtensionOperator extends ExtensionCallable, Operator { + ExtensionOperator() { this.isInExtension() } +} + /** * A user-defined unary operator - an operator taking one operand. * diff --git a/csharp/ql/lib/semmle/code/csharp/Property.qll b/csharp/ql/lib/semmle/code/csharp/Property.qll index e651639b631..d3e65def671 100644 --- a/csharp/ql/lib/semmle/code/csharp/Property.qll +++ b/csharp/ql/lib/semmle/code/csharp/Property.qll @@ -260,6 +260,21 @@ class Property extends DeclarationWithGetSetAccessors, @property { override string getAPrimaryQlClass() { result = "Property" } } +/** + * An extension property, for example `FirstChar` in + * + * ```csharp + * static class MyExtensions { + * extension(string s) { + * public char FirstChar { get { ... } } + * } + * } + * ``` + */ +class ExtensionProperty extends Property { + ExtensionProperty() { this.getDeclaringType() instanceof ExtensionType } +} + /** * An indexer, for example `string this[int i]` on line 2 in * @@ -413,6 +428,22 @@ class Accessor extends Callable, Modifiable, Attributable, Overridable, @callabl override string toString() { result = this.getName() } } +/** + * An extension accessor. Either a getter (`Getter`) or a setter (`Setter`) of an + * extension property, for example `get` in + * + * ```csharp + * static class MyExtensions { + * extension(string s) { + * public char FirstChar { get { ... } } + * } + * } + * ``` + */ +class ExtensionAccessor extends ExtensionCallable, Accessor { + ExtensionAccessor() { this.getDeclaringType() instanceof ExtensionType } +} + /** * A `get` accessor, for example `get { return p; }` in *