Skip to content

Commit a9e5b34

Browse files
authored
Merge pull request github#12200 from michaelnebel/csharp/viablestatic
C#: Support for virtual dispatch for operators.
2 parents d02a50a + 41b2273 commit a9e5b34

File tree

6 files changed

+162
-19
lines changed

6 files changed

+162
-19
lines changed

csharp/ql/lib/semmle/code/csharp/dispatch/Dispatch.qll

Lines changed: 45 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ private module Internal {
7272
not mc.isLateBound()
7373
} or
7474
TDispatchAccessorCall(AccessorCall ac) or
75+
TDispatchOperatorCall(OperatorCall oc) { not oc.isLateBound() } or
7576
TDispatchReflectionCall(MethodCall mc, string name, Expr object, Expr qualifier, int args) {
7677
isReflectionCall(mc, name, object, qualifier, args)
7778
} or
@@ -90,8 +91,6 @@ private module Internal {
9091
or
9192
c instanceof ConstructorInitializer
9293
or
93-
c = any(OperatorCall oc | not oc.isLateBound())
94-
or
9594
c instanceof LocalFunctionCall
9695
}
9796

@@ -113,12 +112,12 @@ private module Internal {
113112
}
114113

115114
cached
116-
predicate mayBenefitFromCallContext(DispatchMethodOrAccessorCall dc) {
115+
predicate mayBenefitFromCallContext(DispatchOverridableCall dc) {
117116
dc.mayBenefitFromCallContext(_, _)
118117
}
119118

120119
cached
121-
RuntimeCallable getADynamicTargetInCallContext(DispatchMethodOrAccessorCall dc, DispatchCall ctx) {
120+
RuntimeCallable getADynamicTargetInCallContext(DispatchOverridableCall dc, DispatchCall ctx) {
122121
result = dc.getADynamicTargetInCallContext(ctx)
123122
}
124123
}
@@ -214,6 +213,9 @@ private module Internal {
214213
/** Gets the qualifier of this call, if any. */
215214
abstract Expr getQualifier();
216215

216+
/** Gets the qualifier or another expression that can be used for typing purposes, if any. */
217+
Expr getSyntheticQualifier() { result = this.getQualifier() }
218+
217219
/** Gets a static (compile-time) target of this call. */
218220
abstract Callable getAStaticTarget();
219221

@@ -241,7 +243,7 @@ private module Internal {
241243
private predicate hasCallable0(Gvn::GvnType t, OverridableCallable c, OverridableCallable source) {
242244
c.getUnboundDeclaration() = source and
243245
any(ValueOrRefType t0 | Gvn::getGlobalValueNumber(t0) = t).hasCallable(c) and
244-
source = any(DispatchMethodOrAccessorCall call).getAStaticTargetExt()
246+
source = any(DispatchOverridableCall call).getAStaticTargetExt()
245247
}
246248

247249
pragma[noinline]
@@ -250,7 +252,7 @@ private module Internal {
250252
hasOverrider(t, c)
251253
}
252254

253-
abstract private class DispatchMethodOrAccessorCall extends DispatchCallImpl {
255+
abstract private class DispatchOverridableCall extends DispatchCallImpl {
254256
pragma[noinline]
255257
OverridableCallable getAStaticTargetExt() {
256258
exists(OverridableCallable target | this.getAStaticTarget() = target |
@@ -261,13 +263,15 @@ private module Internal {
261263
}
262264

263265
pragma[nomagic]
264-
predicate hasQualifierTypeInherited(Type t) { t = getAPossibleType(this.getQualifier(), _) }
266+
predicate hasQualifierTypeInherited(Type t) {
267+
t = getAPossibleType(this.getSyntheticQualifier(), _)
268+
}
265269

266270
pragma[noinline]
267271
private predicate hasSubsumedQualifierType(Gvn::GvnType t) {
268272
hasOverrider(t, _) and
269273
exists(Type t0 |
270-
t0 = getAPossibleType(this.getQualifier(), false) and
274+
t0 = getAPossibleType(this.getSyntheticQualifier(), false) and
271275
not t0 instanceof TypeParameter
272276
|
273277
t = Gvn::getGlobalValueNumber(t0)
@@ -280,12 +284,12 @@ private module Internal {
280284
private predicate hasConstrainedTypeParameterQualifierType(
281285
Unification::ConstrainedTypeParameter tp
282286
) {
283-
tp = getAPossibleType(this.getQualifier(), false)
287+
tp = getAPossibleType(this.getSyntheticQualifier(), false)
284288
}
285289

286290
pragma[noinline]
287291
private predicate hasUnconstrainedTypeParameterQualifierType() {
288-
getAPossibleType(this.getQualifier(), false) instanceof
292+
getAPossibleType(this.getSyntheticQualifier(), false) instanceof
289293
Unification::UnconstrainedTypeParameter
290294
}
291295

@@ -313,7 +317,7 @@ private module Internal {
313317
|
314318
pdef = def.getDefinition() and
315319
p = pdef.getTarget() and
316-
this.getQualifier() = def.getARead() and
320+
this.getSyntheticQualifier() = def.getARead() and
317321
p.getPosition() = i and
318322
c.getAParameter() = p and
319323
not p.isParams()
@@ -446,7 +450,7 @@ private module Internal {
446450

447451
pragma[noinline]
448452
NonConstructedOverridableCallable getAViableOverrider0() {
449-
getAPossibleType(this.getQualifier(), false) instanceof TypeParameter and
453+
getAPossibleType(this.getSyntheticQualifier(), false) instanceof TypeParameter and
450454
result.getAConstructingCallableOrSelf() = this.getAStaticTargetExt()
451455
}
452456

@@ -497,9 +501,10 @@ private module Internal {
497501
result = c.getAnOverrider(t)
498502
)
499503
or
500-
exists(NonConstructedOverridableCallable c |
504+
exists(NonConstructedOverridableCallable c, NonConstructedOverridableCallable mid |
501505
c = this.getAViableOverrider0() and
502-
result = c.getAnOverrider(_)
506+
c = mid.getUnboundDeclaration() and
507+
result = mid.getAnOverrider(_)
503508
|
504509
this.hasUnconstrainedTypeParameterQualifierType()
505510
or
@@ -774,7 +779,7 @@ private module Internal {
774779
* The set of viable targets is determined by taking virtual dispatch
775780
* into account.
776781
*/
777-
private class DispatchMethodCall extends DispatchMethodOrAccessorCall, TDispatchMethodCall {
782+
private class DispatchMethodCall extends DispatchOverridableCall, TDispatchMethodCall {
778783
override MethodCall getCall() { this = TDispatchMethodCall(result) }
779784

780785
override Expr getArgument(int i) {
@@ -789,13 +794,35 @@ private module Internal {
789794
override Method getAStaticTarget() { result = this.getCall().getTarget() }
790795
}
791796

797+
/**
798+
* An ordinary operator call.
799+
*
800+
* The set of viable targets is determined by taking virtual dispatch
801+
* into account.
802+
*/
803+
private class DispatchOperatorCall extends DispatchOverridableCall, TDispatchOperatorCall {
804+
override OperatorCall getCall() { this = TDispatchOperatorCall(result) }
805+
806+
override Expr getArgument(int i) { result = this.getCall().getArgument(i) }
807+
808+
/**
809+
* Gets the first child expression of an operator call, which can be considered the qualifier
810+
* expression for the dispatch call use-cases.
811+
*/
812+
override Expr getSyntheticQualifier() { result = this.getCall().getChildExpr(0) }
813+
814+
override Expr getQualifier() { none() }
815+
816+
override Operator getAStaticTarget() { result = this.getCall().getTarget() }
817+
}
818+
792819
/**
793820
* A call to an accessor.
794821
*
795822
* The set of viable targets is determined by taking virtual dispatch
796823
* into account.
797824
*/
798-
private class DispatchAccessorCall extends DispatchMethodOrAccessorCall, TDispatchAccessorCall {
825+
private class DispatchAccessorCall extends DispatchOverridableCall, TDispatchAccessorCall {
799826
override AccessorCall getCall() { this = TDispatchAccessorCall(result) }
800827

801828
override Expr getArgument(int i) { result = this.getCall().getArgument(i) }
@@ -805,7 +832,7 @@ private module Internal {
805832
override Accessor getAStaticTarget() { result = this.getCall().getTarget() }
806833

807834
override RuntimeAccessor getADynamicTarget() {
808-
result = DispatchMethodOrAccessorCall.super.getADynamicTarget() and
835+
result = DispatchOverridableCall.super.getADynamicTarget() and
809836
// Calls to accessors may have `dynamic` expression arguments,
810837
// so we need to check that the types match
811838
forall(Type argumentType, int i | this.hasDynamicArg(i, argumentType) |
@@ -829,7 +856,7 @@ private module Internal {
829856

830857
pragma[nomagic]
831858
private predicate hasQualifierType(Type qualifierType, boolean isExactType) {
832-
exists(Type t | t = getAPossibleType(this.getQualifier(), isExactType) |
859+
exists(Type t | t = getAPossibleType(this.getSyntheticQualifier(), isExactType) |
833860
qualifierType = t and
834861
not t instanceof TypeParameter
835862
or

csharp/ql/test/library-tests/dispatch/CallContext.expected

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,6 @@ mayBenefitFromCallContext
2323
| ViableCallable.cs:411:9:411:18 | call to method M<Int32> |
2424
| ViableCallable.cs:455:9:455:30 | call to method M2<T> |
2525
| ViableCallable.cs:461:9:461:30 | call to method M2<T> |
26+
| ViableCallable.cs:563:18:563:22 | call to operator / |
27+
| ViableCallable.cs:566:26:566:30 | call to operator checked / |
28+
| ViableCallable.cs:572:9:572:15 | call to method M12 |

csharp/ql/test/library-tests/dispatch/CallGraph.expected

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,3 +242,17 @@
242242
| ViableCallable.cs:492:10:492:12 | Run | ViableCallable.cs:488:40:488:40 | checked + |
243243
| ViableCallable.cs:492:10:492:12 | Run | ViableCallable.cs:489:28:489:35 | explicit conversion |
244244
| ViableCallable.cs:492:10:492:12 | Run | ViableCallable.cs:490:28:490:35 | checked explicit conversion |
245+
| ViableCallable.cs:542:10:542:15 | Run<> | ViableCallable.cs:514:39:514:39 | checked - |
246+
| ViableCallable.cs:542:10:542:15 | Run<> | ViableCallable.cs:516:31:516:31 | * |
247+
| ViableCallable.cs:542:10:542:15 | Run<> | ViableCallable.cs:517:39:517:39 | checked * |
248+
| ViableCallable.cs:542:10:542:15 | Run<> | ViableCallable.cs:519:31:519:31 | / |
249+
| ViableCallable.cs:542:10:542:15 | Run<> | ViableCallable.cs:520:39:520:39 | checked / |
250+
| ViableCallable.cs:542:10:542:15 | Run<> | ViableCallable.cs:524:18:524:20 | M12 |
251+
| ViableCallable.cs:542:10:542:15 | Run<> | ViableCallable.cs:526:18:526:20 | M13 |
252+
| ViableCallable.cs:542:10:542:15 | Run<> | ViableCallable.cs:531:32:531:32 | + |
253+
| ViableCallable.cs:542:10:542:15 | Run<> | ViableCallable.cs:532:40:532:40 | checked + |
254+
| ViableCallable.cs:542:10:542:15 | Run<> | ViableCallable.cs:534:32:534:32 | - |
255+
| ViableCallable.cs:542:10:542:15 | Run<> | ViableCallable.cs:536:32:536:32 | / |
256+
| ViableCallable.cs:542:10:542:15 | Run<> | ViableCallable.cs:537:40:537:40 | checked / |
257+
| ViableCallable.cs:542:10:542:15 | Run<> | ViableCallable.cs:539:17:539:19 | M11 |
258+
| ViableCallable.cs:542:10:542:15 | Run<> | ViableCallable.cs:540:17:540:19 | M12 |

csharp/ql/test/library-tests/dispatch/GetADynamicTarget.expected

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -475,3 +475,17 @@
475475
| ViableCallable.cs:498:26:498:30 | call to operator checked + | C19.checked +(C19, C19) |
476476
| ViableCallable.cs:501:18:501:23 | call to operator explicit conversion | C19.explicit conversion(C19) |
477477
| ViableCallable.cs:504:26:504:31 | call to operator checked explicit conversion | C19.checked explicit conversion(C19) |
478+
| ViableCallable.cs:545:18:545:22 | call to operator + | C20.+(C20, C20) |
479+
| ViableCallable.cs:548:26:548:30 | call to operator checked + | C20.checked +(C20, C20) |
480+
| ViableCallable.cs:551:18:551:22 | call to operator - | C20.-(C20, C20) |
481+
| ViableCallable.cs:554:26:554:30 | call to operator checked - | I3<T>.checked -(T, T) |
482+
| ViableCallable.cs:557:18:557:22 | call to operator * | I3<T>.*(T, T) |
483+
| ViableCallable.cs:560:26:560:30 | call to operator checked * | I3<T>.checked *(T, T) |
484+
| ViableCallable.cs:563:18:563:22 | call to operator / | C20./(C20, C20) |
485+
| ViableCallable.cs:563:18:563:22 | call to operator / | I3<T>./(T, T) |
486+
| ViableCallable.cs:566:26:566:30 | call to operator checked / | C20.checked /(C20, C20) |
487+
| ViableCallable.cs:566:26:566:30 | call to operator checked / | I3<T>.checked /(T, T) |
488+
| ViableCallable.cs:569:9:569:15 | call to method M11 | C20.M11() |
489+
| ViableCallable.cs:572:9:572:15 | call to method M12 | C20.M12() |
490+
| ViableCallable.cs:572:9:572:15 | call to method M12 | I3<T>.M12() |
491+
| ViableCallable.cs:575:9:575:15 | call to method M13 | I3<T>.M13() |

csharp/ql/test/library-tests/dispatch/ViableCallable.cs

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -503,4 +503,75 @@ void Run(C19 c)
503503
// Viable callables: C19.op_CheckedExplicit()
504504
var n2 = checked((int)c);
505505
}
506-
}
506+
}
507+
508+
public interface I3<T> where T : I3<T>
509+
{
510+
static abstract T operator +(T x, T y);
511+
static abstract T operator checked +(T x, T y);
512+
513+
static abstract T operator -(T x, T y);
514+
static virtual T operator checked -(T x, T y) => throw null;
515+
516+
static virtual T operator *(T x, T y) => throw null;
517+
static virtual T operator checked *(T x, T y) => throw null;
518+
519+
static virtual T operator /(T x, T y) => throw null;
520+
static virtual T operator checked /(T x, T y) => throw null;
521+
522+
void M11();
523+
524+
virtual void M12() => throw null;
525+
526+
virtual void M13() => throw null;
527+
}
528+
529+
public class C20 : I3<C20>
530+
{
531+
public static C20 operator +(C20 x, C20 y) => throw null;
532+
public static C20 operator checked +(C20 x, C20 y) => throw null;
533+
534+
public static C20 operator -(C20 x, C20 y) => throw null;
535+
536+
public static C20 operator /(C20 x, C20 y) => throw null;
537+
public static C20 operator checked /(C20 x, C20 y) => throw null;
538+
539+
public void M11() { }
540+
public void M12() { }
541+
542+
void Run<T>(T c) where T : I3<T>
543+
{
544+
// Viable callables: C20.op_Addition()
545+
var c1 = c + c;
546+
547+
// Viable callables: C20.op_CheckedAddition()
548+
var c2 = checked(c + c);
549+
550+
// Viable callables: C20.op_Subtraction()
551+
var c3 = c - c;
552+
553+
// Viable callables: I3<C20>.op_CheckedSubtraction().
554+
var c4 = checked(c - c);
555+
556+
// Viable callables: I3<C20>.op_Multiply().
557+
var c5 = c * c;
558+
559+
// Viable callables: I3<C20>.op_CheckedMultiply().
560+
var c6 = checked(c * c);
561+
562+
// Viable callables: {C20,I3<C20>}.op_Division()
563+
var c7 = c / c;
564+
565+
// Viable callables: {C20,I3<C20>}.op_CheckedDivision()
566+
var c8 = checked(c / c);
567+
568+
// Viable callables: C20.M11.
569+
c.M11();
570+
571+
// Viable callables: {C20,I3<C20>}.M12().
572+
c.M12();
573+
574+
// Viable callables: I3<C20>.M13()
575+
c.M13();
576+
}
577+
}

csharp/ql/test/library-tests/dispatch/viableCallable.expected

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,3 +272,17 @@
272272
| ViableCallable.cs:498:26:498:30 | call to operator checked + | checked + | C19 |
273273
| ViableCallable.cs:501:18:501:23 | call to operator explicit conversion | explicit conversion | C19 |
274274
| ViableCallable.cs:504:26:504:31 | call to operator checked explicit conversion | checked explicit conversion | C19 |
275+
| ViableCallable.cs:545:18:545:22 | call to operator + | + | C20 |
276+
| ViableCallable.cs:548:26:548:30 | call to operator checked + | checked + | C20 |
277+
| ViableCallable.cs:551:18:551:22 | call to operator - | - | C20 |
278+
| ViableCallable.cs:554:26:554:30 | call to operator checked - | checked - | I3<> |
279+
| ViableCallable.cs:557:18:557:22 | call to operator * | * | I3<> |
280+
| ViableCallable.cs:560:26:560:30 | call to operator checked * | checked * | I3<> |
281+
| ViableCallable.cs:563:18:563:22 | call to operator / | / | C20 |
282+
| ViableCallable.cs:563:18:563:22 | call to operator / | / | I3<> |
283+
| ViableCallable.cs:566:26:566:30 | call to operator checked / | checked / | C20 |
284+
| ViableCallable.cs:566:26:566:30 | call to operator checked / | checked / | I3<> |
285+
| ViableCallable.cs:569:9:569:15 | call to method M11 | M11 | C20 |
286+
| ViableCallable.cs:572:9:572:15 | call to method M12 | M12 | C20 |
287+
| ViableCallable.cs:572:9:572:15 | call to method M12 | M12 | I3<> |
288+
| ViableCallable.cs:575:9:575:15 | call to method M13 | M13 | I3<> |

0 commit comments

Comments
 (0)