Skip to content

Commit e4b0fa8

Browse files
committed
C#: Add various extension related classes to the QL library.
1 parent 4e63de3 commit e4b0fa8

File tree

5 files changed

+199
-13
lines changed

5 files changed

+199
-13
lines changed

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

Lines changed: 65 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,23 @@ class Callable extends Parameterizable, ExprOrStmtParent, @callable {
221221

222222
/** Gets a `Call` that has this callable as a target. */
223223
Call getACall() { this = result.getTarget() }
224+
225+
/** Holds if this callable is declared in an extension type. */
226+
predicate isInExtension() { this.getDeclaringType() instanceof ExtensionType }
227+
}
228+
229+
/**
230+
* A callable that is declared as an extension.
231+
*
232+
* Either an extension method (`ExtensionMethod`), an extension operator
233+
* (`ExtensionOperator`) or an extension accessor (`ExtensionAccessor`).
234+
*/
235+
abstract class ExtensionCallable extends Callable {
236+
/** Gets the type being extended by this method. */
237+
pragma[noinline]
238+
Type getExtendedType() { result = this.getDeclaringType().(ExtensionType).getExtendedType() }
239+
240+
override string getAPrimaryQlClass() { result = "ExtensionCallable" }
224241
}
225242

226243
/**
@@ -267,8 +284,11 @@ class Method extends Callable, Virtualizable, Attributable, @method {
267284

268285
override Location getALocation() { method_location(this.getUnboundDeclaration(), result) }
269286

287+
/** Holds if this method is a classic extension method. */
288+
predicate isClassicExtensionMethod() { this.getParameter(0).hasExtensionMethodModifier() }
289+
270290
/** Holds if this method is an extension method. */
271-
predicate isExtensionMethod() { this.getParameter(0).hasExtensionMethodModifier() }
291+
predicate isExtensionMethod() { this.isClassicExtensionMethod() or this.isInExtension() }
272292

273293
/** Gets the type of the `params` parameter of this method, if any. */
274294
Type getParamsType() {
@@ -296,24 +316,46 @@ class Method extends Callable, Virtualizable, Attributable, @method {
296316
}
297317

298318
/**
299-
* An extension method, for example
319+
* An extension method.
320+
*
321+
* Either a classic extension method (`ClassicExtensionMethod`) or an extension
322+
* type extension method (`ExtensionTypeExtensionMethod`).
323+
*/
324+
abstract class ExtensionMethod extends ExtensionCallable, Method {
325+
override string getAPrimaryQlClass() { result = "ExtensionMethod" }
326+
}
327+
328+
/**
329+
* An extension method, for example
300330
*
301331
* ```csharp
302332
* static bool IsDefined(this Widget w) {
303333
* ...
304334
* }
305335
* ```
306336
*/
307-
class ExtensionMethod extends Method {
308-
ExtensionMethod() { this.isExtensionMethod() }
309-
310-
override predicate isStatic() { any() }
337+
class ClassicExtensionMethod extends ExtensionMethod {
338+
ClassicExtensionMethod() { this.isClassicExtensionMethod() }
311339

312-
/** Gets the type being extended by this method. */
313340
pragma[noinline]
314-
Type getExtendedType() { result = this.getParameter(0).getType() }
341+
override Type getExtendedType() { result = this.getParameter(0).getType() }
315342

316-
override string getAPrimaryQlClass() { result = "ExtensionMethod" }
343+
override predicate isStatic() { any() }
344+
}
345+
346+
/**
347+
* An extension method declared in an extension type, for example `IsNullOrEmpty` in
348+
*
349+
* ```csharp
350+
* static class MyExtensions {
351+
* extension(string s) {
352+
* public bool IsNullOrEmpty() { ... }
353+
* }
354+
* }
355+
* ```
356+
*/
357+
class ExtensionTypeExtensionMethod extends ExtensionMethod {
358+
ExtensionTypeExtensionMethod() { this.isInExtension() }
317359
}
318360

319361
/**
@@ -536,6 +578,20 @@ class RecordCloneMethod extends Method {
536578
}
537579
}
538580

581+
/**
582+
* An extension operator, for example `*` in
583+
* ```csharp
584+
* static class MyExtensions {
585+
* extension(string s) {
586+
* public static string operator *(int s1, string s2) { ... }
587+
* }
588+
* }
589+
* ```
590+
*/
591+
class ExtensionOperator extends ExtensionCallable, Operator {
592+
ExtensionOperator() { this.isInExtension() }
593+
}
594+
539595
/**
540596
* A user-defined unary operator - an operator taking one operand.
541597
*

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -469,7 +469,7 @@ class Virtualizable extends Overridable, Member, @virtualizable {
469469

470470
/**
471471
* A parameterizable declaration. Either a callable (`Callable`), a delegate
472-
* type (`DelegateType`), or an indexer (`Indexer`).
472+
* type (`DelegateType`), an indexer (`Indexer`) or an extension block (`ExtensionType`).
473473
*/
474474
class Parameterizable extends Declaration, @parameterizable {
475475
/** Gets raw parameter `i`, including the `this` parameter at index 0. */

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

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,20 @@ class Property extends DeclarationWithGetSetAccessors, @property {
260260
override string getAPrimaryQlClass() { result = "Property" }
261261
}
262262

263+
/**
264+
* An extension property, for example `FirstChar` in
265+
* ```csharp
266+
* static class MyExtensions {
267+
* extension(string s) {
268+
* public char FirstChar { get { ... } }
269+
* }
270+
* }
271+
* ```
272+
*/
273+
class ExtensionProperty extends Property {
274+
ExtensionProperty() { this.getDeclaringType() instanceof ExtensionType }
275+
}
276+
263277
/**
264278
* An indexer, for example `string this[int i]` on line 2 in
265279
*
@@ -413,6 +427,21 @@ class Accessor extends Callable, Modifiable, Attributable, Overridable, @callabl
413427
override string toString() { result = this.getName() }
414428
}
415429

430+
/**
431+
* An extension accessor. Either a getter (`Getter`), a setter (`Setter`) of an
432+
* extension property, for example `get` in
433+
* ```csharp
434+
* static class MyExtensions {
435+
* extension(string s) {
436+
* public char FirstChar { get { ... } }
437+
* }
438+
* }
439+
* ```
440+
*/
441+
class ExtensionAccessor extends ExtensionCallable, Accessor {
442+
ExtensionAccessor() { this.getDeclaringType() instanceof ExtensionType }
443+
}
444+
416445
/**
417446
* A `get` accessor, for example `get { return p; }` in
418447
*

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

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ private import semmle.code.csharp.frameworks.system.runtime.CompilerServices
1717
*
1818
* Either a value or reference type (`ValueOrRefType`), the `void` type (`VoidType`),
1919
* a pointer type (`PointerType`), the arglist type (`ArglistType`), an unknown
20-
* type (`UnknownType`), or a type parameter (`TypeParameter`).
20+
* type (`UnknownType`), a type parameter (`TypeParameter`) or
21+
* an extension type (`ExtensionType`).
2122
*/
2223
class Type extends Member, TypeContainer, @type {
2324
/** Gets the name of this type without additional syntax such as `[]` or `*`. */
@@ -1326,3 +1327,28 @@ class TypeMention extends @type_mention {
13261327
/** Gets the location of this type mention. */
13271328
Location getLocation() { type_mention_location(this, result) }
13281329
}
1330+
1331+
/**
1332+
* A type extension declaration, for example `extensions(string s) { ... }` in
1333+
* ```csharp
1334+
* static class MyExtensions {
1335+
* extensions(string s) { ... }
1336+
* ```
1337+
*/
1338+
class ExtensionType extends Parameterizable, @extension_type {
1339+
/**
1340+
* Gets the receiver parameter of this extension type, if any.
1341+
*/
1342+
Parameter getReceiverParameter() { params(result, _, _, 0, _, this, _) }
1343+
1344+
/**
1345+
* Holds if this extension type has a receiver parameter.
1346+
*/
1347+
predicate hasReceiverParameter() { exists(this.getReceiverParameter()) }
1348+
1349+
/** Gets the type being extended by this extension type. */
1350+
// TODO: This doesn't handle the case where there is no parameter.
1351+
Type getExtendedType() { result = this.getReceiverParameter().getType() }
1352+
1353+
override string getAPrimaryQlClass() { result = "ExtensionType" }
1354+
}

csharp/ql/lib/semmle/code/csharp/exprs/Call.qll

Lines changed: 77 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,29 @@ class OperatorCall extends Call, LateBindableExpr, @operator_invocation_expr {
479479
override string getAPrimaryQlClass() { result = "OperatorCall" }
480480
}
481481

482+
/**
483+
* A call to an extension operator, for example `3 * s` on
484+
* line 9 in
485+
* ```csharp
486+
* static class MyExtensions {
487+
* extension(string s) {
488+
* public static string operator *(int i, string s) { ... }
489+
* }
490+
* }
491+
*
492+
* class A {
493+
* string M(string s) {
494+
* return 3 * s;
495+
* }
496+
* }
497+
* ```
498+
*/
499+
class ExtensionOperatorCall extends OperatorCall {
500+
ExtensionOperatorCall() { this.getTarget() instanceof ExtensionOperator }
501+
502+
override string getAPrimaryQlClass() { result = "ExtensionOperatorCall" }
503+
}
504+
482505
/**
483506
* A call to a user-defined mutator operator, for example `a++` on
484507
* line 7 in
@@ -577,8 +600,8 @@ class FunctionPointerCall extends DelegateLikeCall, @function_pointer_invocation
577600

578601
/**
579602
* A call to an accessor. Either a property accessor call (`PropertyCall`),
580-
* an indexer accessor call (`IndexerCall`), or an event accessor call
581-
* (`EventCall`).
603+
* an indexer accessor call (`IndexerCall`), an event accessor call
604+
* (`EventCall`) or an extension accessor call (`ExtensionAccessorCall`).
582605
*/
583606
class AccessorCall extends Call, QualifiableExpr, @call_access_expr {
584607
override Accessor getTarget() { none() }
@@ -588,6 +611,35 @@ class AccessorCall extends Call, QualifiableExpr, @call_access_expr {
588611
override Accessor getARuntimeTarget() { result = Call.super.getARuntimeTarget() }
589612
}
590613

614+
/**
615+
* A direct call to an extension accessor, for example `MyExtensions.get_FirstChar(s)`
616+
* in
617+
* ```csharp
618+
* static class MyExtensions {
619+
* extension(string s) {
620+
* public char FirstChar { get { ... } }
621+
* }
622+
* }
623+
*
624+
* class A {
625+
* char M(string s) {
626+
* return MyExtensions.get_FirstChar(s);
627+
* }
628+
* }
629+
* ```
630+
*/
631+
class ExtensionAccessorCall extends AccessorCall, @accessor_invocation_expr {
632+
override Accessor getTarget() { expr_call(this, result) }
633+
634+
override Expr getArgument(int i) { result = this.getChild(i) and i >= 0 }
635+
636+
override string toString() {
637+
result = "call to extension accessor " + concat(this.getTarget().getName())
638+
}
639+
640+
override string getAPrimaryQlClass() { result = "ExtensionAccessorCall" }
641+
}
642+
591643
/**
592644
* A call to a property accessor, for example the call to `get_P` on
593645
* line 5 in
@@ -658,6 +710,29 @@ class IndexerCall extends AccessorCall, IndexerAccessExpr {
658710
override string getAPrimaryQlClass() { result = "IndexerCall" }
659711
}
660712

713+
/**
714+
* A call to an extension property accessor (via the property), for example
715+
* `s.FirstChar` on line 9 in
716+
* ```csharp
717+
* static class MyExtensions {
718+
* extension(string s) {
719+
* public char FirstChar { get { ... } }
720+
* }
721+
* }
722+
*
723+
* class A {
724+
* char M(string s) {
725+
* return s.FirstChar;
726+
* }
727+
* }
728+
* ```
729+
*/
730+
class ExtensionPropertyCall extends PropertyCall {
731+
ExtensionPropertyCall() { this.getProperty() instanceof ExtensionProperty }
732+
733+
override string getAPrimaryQlClass() { result = "ExtensionPropertyCall" }
734+
}
735+
661736
/**
662737
* A call to an event accessor, for example the call to `add_Click`
663738
* (defined on line 5) on line 12 in

0 commit comments

Comments
 (0)