Skip to content

Commit e275e1c

Browse files
committed
C#: Add extension callable and accessor classes.
1 parent 5419b5b commit e275e1c

File tree

2 files changed

+97
-9
lines changed

2 files changed

+97
-9
lines changed

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

Lines changed: 66 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,21 @@ class RecordCloneMethod extends Method {
536578
}
537579
}
538580

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

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

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

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

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

0 commit comments

Comments
 (0)