Skip to content

Commit 49a435c

Browse files
authored
Merge pull request #21827 from michaelnebel/csharp14/userincrementdecrement
C# 14: User increment/decrement support.
2 parents 96ef59a + 7a1a90b commit 49a435c

20 files changed

Lines changed: 816 additions & 572 deletions

File tree

csharp/extractor/Semmle.Extraction.CSharp.Util/SymbolExtensions.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,13 @@ public static string GetName(this ISymbol symbol, bool useMetadataName = false)
5252
{ "op_False", "false" }
5353
});
5454

55+
/// <summary>
56+
/// The operatorname for user-defined instance increment- and decrement operators are "op_IncrementAssignment" and
57+
/// "op_DecrementAssignment" respectively.
58+
/// Thus we need to handle this explicitly to avoid postfixing them with an "=".
59+
/// </summary>
60+
private static bool IsIncrementOrDecrement(string operatorName) => operatorName == "++" || operatorName == "--";
61+
5562
/// <summary>
5663
/// Convert an operator method name in to a symbolic name.
5764
/// A return value indicates whether the conversion succeeded.
@@ -72,7 +79,7 @@ public static bool TryGetOperatorSymbol(this ISymbol symbol, out string operator
7279
if (match.Success && methodToOperator.TryGetValue($"op_{match.Groups[2]}", out var rawOperatorName))
7380
{
7481
var prefix = match.Groups[1].Success ? "checked " : "";
75-
var postfix = match.Groups[3].Success ? "=" : "";
82+
var postfix = match.Groups[3].Success && !IsIncrementOrDecrement(rawOperatorName) ? "=" : "";
7683
operatorName = $"{prefix}{rawOperatorName}{postfix}";
7784
return true;
7885
}

csharp/extractor/Semmle.Extraction.CSharp/Entities/Expression.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -234,9 +234,9 @@ type.SpecialType is SpecialType.System_IntPtr ||
234234
/// </summary>
235235
/// <param name="node">The expression syntax node.</param>
236236
/// <returns>Returns the target method symbol, or null if it cannot be resolved.</returns>
237-
protected IMethodSymbol? GetTargetSymbol(ExpressionSyntax node)
237+
protected static IMethodSymbol? GetTargetSymbol(Context cx, ExpressionSyntax node)
238238
{
239-
var si = Context.GetSymbolInfo(node);
239+
var si = cx.GetSymbolInfo(node);
240240
if (si.Symbol is ISymbol symbol)
241241
{
242242
var method = symbol as IMethodSymbol;
@@ -255,7 +255,7 @@ type.SpecialType is SpecialType.System_IntPtr ||
255255
.Where(method => method.Parameters.Length >= syntax.ArgumentList.Arguments.Count)
256256
.Where(method => method.Parameters.Count(p => !p.HasExplicitDefaultValue) <= syntax.ArgumentList.Arguments.Count);
257257

258-
return Context.ExtractionContext.IsStandalone ?
258+
return cx.ExtractionContext.IsStandalone ?
259259
candidates.FirstOrDefault() :
260260
candidates.SingleOrDefault();
261261
}
@@ -281,7 +281,7 @@ public static ExprKind UnaryOperatorKind(Context cx, ExprKind originalKind, Expr
281281
/// <param name="node">The expression.</param>
282282
public void AddOperatorCall(TextWriter trapFile, ExpressionSyntax node)
283283
{
284-
var @operator = GetTargetSymbol(node);
284+
var @operator = GetTargetSymbol(Context, node);
285285
if (@operator is IMethodSymbol method)
286286
{
287287
var callType = GetCallType(Context, node);
@@ -312,9 +312,9 @@ public enum CallType
312312
/// <returns>The call type.</returns>
313313
public static CallType GetCallType(Context cx, ExpressionSyntax node)
314314
{
315-
var @operator = cx.GetSymbolInfo(node);
315+
var @operator = GetTargetSymbol(cx, node);
316316

317-
if (@operator.Symbol is IMethodSymbol method)
317+
if (@operator is IMethodSymbol method)
318318
{
319319
if (method.ContainingSymbol is ITypeSymbol containingSymbol && containingSymbol.TypeKind == Microsoft.CodeAnalysis.TypeKind.Dynamic)
320320
{

csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Invocation.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ protected override void PopulateExpression(TextWriter trapFile)
4444

4545
var child = -1;
4646
string? memberName = null;
47-
var target = GetTargetSymbol(Syntax);
47+
var target = GetTargetSymbol(Context, Syntax);
4848
switch (Syntax.Expression)
4949
{
5050
case MemberAccessExpressionSyntax memberAccess when IsValidMemberAccessKind():
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: minorAnalysis
3+
---
4+
* C# 14: Added support for user-defined instance increment/decrement operators.

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -613,6 +613,9 @@ class UnaryOperator extends Operator {
613613
this.getNumberOfParameters() = 1 and
614614
not this instanceof ConversionOperator and
615615
not this instanceof CompoundAssignmentOperator
616+
or
617+
// Instance increment and decrement operators don't have a parameter (only a qualifier).
618+
this.getNumberOfParameters() = 0 and not this.isStatic()
616619
}
617620
}
618621

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

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,19 @@ class DispatchCall extends Internal::TDispatchCall {
7373
}
7474
}
7575

76+
abstract private class InstanceOperatorCall extends OperatorCall {
77+
abstract Expr getQualifier();
78+
}
79+
80+
private class InstanceCompoundAssignment extends InstanceOperatorCall instanceof CompoundAssignmentOperatorCall
81+
{
82+
override Expr getQualifier() { result = CompoundAssignmentOperatorCall.super.getQualifier() }
83+
}
84+
85+
private class InstanceMutator extends InstanceOperatorCall instanceof InstanceMutatorOperatorCall {
86+
override Expr getQualifier() { result = InstanceMutatorOperatorCall.super.getQualifier() }
87+
}
88+
7689
/** Internal implementation details. */
7790
private module Internal {
7891
private import OverridableCallable
@@ -101,9 +114,9 @@ private module Internal {
101114
} or
102115
TDispatchOperatorCall(OperatorCall oc) {
103116
not oc.isLateBound() and
104-
not oc instanceof CompoundAssignmentOperatorCall
117+
not oc instanceof InstanceOperatorCall
105118
} or
106-
TDispatchCompoundAssignmentOperatorCall(CompoundAssignmentOperatorCall caoc) or
119+
TDispatchInstanceOperatorCall(InstanceOperatorCall ioc) or
107120
TDispatchReflectionCall(MethodCall mc, string name, Expr object, Expr qualifier, int args) {
108121
isReflectionCall(mc, name, object, qualifier, args)
109122
} or
@@ -890,12 +903,10 @@ private module Internal {
890903
override Operator getAStaticTarget() { result = this.getCall().getTarget() }
891904
}
892905

893-
private class DispatchCompoundAssignmentOperatorCall extends DispatchOverridableCall,
894-
TDispatchCompoundAssignmentOperatorCall
906+
private class DispatchInstanceOperatorCall extends DispatchOverridableCall,
907+
TDispatchInstanceOperatorCall
895908
{
896-
override CompoundAssignmentOperatorCall getCall() {
897-
this = TDispatchCompoundAssignmentOperatorCall(result)
898-
}
909+
override InstanceOperatorCall getCall() { this = TDispatchInstanceOperatorCall(result) }
899910

900911
override Expr getArgument(int i) { result = this.getCall().getArgument(i) }
901912

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -570,6 +570,29 @@ class MutatorOperatorCall extends OperatorCall {
570570
predicate isPostfix() { mutator_invocation_mode(this, 2) }
571571
}
572572

573+
/**
574+
* A call to an instance mutator operator, for example `a++` on
575+
* line 5 in
576+
*
577+
* ```csharp
578+
* class A {
579+
* public void operator ++() { ... }
580+
*
581+
* public static void Increment(A a) {
582+
* a++;
583+
* }
584+
* }
585+
* ```
586+
*/
587+
class InstanceMutatorOperatorCall extends MutatorOperatorCall {
588+
InstanceMutatorOperatorCall() { this.getTarget().getNumberOfParameters() = 0 }
589+
590+
/** Gets the qualifier of this instance mutator operator call. */
591+
Expr getQualifier() { result = this.getChildExpr(0) }
592+
593+
override Expr getArgument(int i) { none() }
594+
}
595+
573596
/**
574597
* A call to a compound assignment operator, for example `this += other`
575598
* on line 7 in

csharp/ql/test/library-tests/dataflow/operators/Operator.cs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,3 +120,36 @@ public void M1()
120120
Sink(x.Field); // $ hasValueFlow=1
121121
}
122122
}
123+
124+
public class MutatorOperators
125+
{
126+
static void Sink(object o) { }
127+
static T Source<T>(object source) => throw null;
128+
129+
public class C1
130+
{
131+
public object Field { get; private set; }
132+
133+
public C1()
134+
{
135+
Field = new object();
136+
}
137+
138+
public C1(object o)
139+
{
140+
Field = o;
141+
}
142+
143+
public void operator ++()
144+
{
145+
Field = Source<object>(1);
146+
}
147+
148+
public void M1()
149+
{
150+
var x = new C1();
151+
x++;
152+
Sink(x.Field); // $ hasValueFlow=1
153+
}
154+
}
155+
}

csharp/ql/test/library-tests/dataflow/operators/operatorFlow.expected

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,16 @@ edges
130130
| Operator.cs:119:14:119:14 | access to local variable y : C [property Field] : Object | Operator.cs:119:9:119:9 | [post] access to local variable x : C [property Field] : Object | provenance | |
131131
| Operator.cs:120:14:120:14 | access to local variable x : C [property Field] : Object | Operator.cs:120:14:120:20 | access to property Field | provenance | |
132132
| Operator.cs:120:14:120:14 | access to local variable x : C [property Field] : Object | Operator.cs:120:14:120:20 | access to property Field | provenance | |
133+
| Operator.cs:143:30:143:31 | this [Return] : C1 [property Field] : Object | Operator.cs:151:13:151:13 | [post] access to local variable x : C1 [property Field] : Object | provenance | |
134+
| Operator.cs:143:30:143:31 | this [Return] : C1 [property Field] : Object | Operator.cs:151:13:151:13 | [post] access to local variable x : C1 [property Field] : Object | provenance | |
135+
| Operator.cs:145:13:145:17 | [post] this access : C1 [property Field] : Object | Operator.cs:143:30:143:31 | this [Return] : C1 [property Field] : Object | provenance | |
136+
| Operator.cs:145:13:145:17 | [post] this access : C1 [property Field] : Object | Operator.cs:143:30:143:31 | this [Return] : C1 [property Field] : Object | provenance | |
137+
| Operator.cs:145:21:145:37 | call to method Source<Object> : Object | Operator.cs:145:13:145:17 | [post] this access : C1 [property Field] : Object | provenance | |
138+
| Operator.cs:145:21:145:37 | call to method Source<Object> : Object | Operator.cs:145:13:145:17 | [post] this access : C1 [property Field] : Object | provenance | |
139+
| Operator.cs:151:13:151:13 | [post] access to local variable x : C1 [property Field] : Object | Operator.cs:152:18:152:18 | access to local variable x : C1 [property Field] : Object | provenance | |
140+
| Operator.cs:151:13:151:13 | [post] access to local variable x : C1 [property Field] : Object | Operator.cs:152:18:152:18 | access to local variable x : C1 [property Field] : Object | provenance | |
141+
| Operator.cs:152:18:152:18 | access to local variable x : C1 [property Field] : Object | Operator.cs:152:18:152:24 | access to property Field | provenance | |
142+
| Operator.cs:152:18:152:18 | access to local variable x : C1 [property Field] : Object | Operator.cs:152:18:152:24 | access to property Field | provenance | |
133143
nodes
134144
| Operator.cs:9:39:9:39 | x : C | semmle.label | x : C |
135145
| Operator.cs:9:39:9:39 | x : C | semmle.label | x : C |
@@ -275,6 +285,18 @@ nodes
275285
| Operator.cs:120:14:120:14 | access to local variable x : C [property Field] : Object | semmle.label | access to local variable x : C [property Field] : Object |
276286
| Operator.cs:120:14:120:20 | access to property Field | semmle.label | access to property Field |
277287
| Operator.cs:120:14:120:20 | access to property Field | semmle.label | access to property Field |
288+
| Operator.cs:143:30:143:31 | this [Return] : C1 [property Field] : Object | semmle.label | this [Return] : C1 [property Field] : Object |
289+
| Operator.cs:143:30:143:31 | this [Return] : C1 [property Field] : Object | semmle.label | this [Return] : C1 [property Field] : Object |
290+
| Operator.cs:145:13:145:17 | [post] this access : C1 [property Field] : Object | semmle.label | [post] this access : C1 [property Field] : Object |
291+
| Operator.cs:145:13:145:17 | [post] this access : C1 [property Field] : Object | semmle.label | [post] this access : C1 [property Field] : Object |
292+
| Operator.cs:145:21:145:37 | call to method Source<Object> : Object | semmle.label | call to method Source<Object> : Object |
293+
| Operator.cs:145:21:145:37 | call to method Source<Object> : Object | semmle.label | call to method Source<Object> : Object |
294+
| Operator.cs:151:13:151:13 | [post] access to local variable x : C1 [property Field] : Object | semmle.label | [post] access to local variable x : C1 [property Field] : Object |
295+
| Operator.cs:151:13:151:13 | [post] access to local variable x : C1 [property Field] : Object | semmle.label | [post] access to local variable x : C1 [property Field] : Object |
296+
| Operator.cs:152:18:152:18 | access to local variable x : C1 [property Field] : Object | semmle.label | access to local variable x : C1 [property Field] : Object |
297+
| Operator.cs:152:18:152:18 | access to local variable x : C1 [property Field] : Object | semmle.label | access to local variable x : C1 [property Field] : Object |
298+
| Operator.cs:152:18:152:24 | access to property Field | semmle.label | access to property Field |
299+
| Operator.cs:152:18:152:24 | access to property Field | semmle.label | access to property Field |
278300
subpaths
279301
| Operator.cs:29:17:29:17 | access to local variable x : C | Operator.cs:16:38:16:38 | x : C | Operator.cs:16:49:16:49 | access to parameter x : C | Operator.cs:29:17:29:21 | call to operator + : C |
280302
| Operator.cs:29:17:29:17 | access to local variable x : C | Operator.cs:16:38:16:38 | x : C | Operator.cs:16:49:16:49 | access to parameter x : C | Operator.cs:29:17:29:21 | call to operator + : C |
@@ -308,3 +330,5 @@ testFailures
308330
| Operator.cs:78:14:78:14 | (...) ... | Operator.cs:84:17:84:29 | call to method Source<C> : C | Operator.cs:78:14:78:14 | (...) ... | $@ | Operator.cs:84:17:84:29 | call to method Source<C> : C | call to method Source<C> : C |
309331
| Operator.cs:120:14:120:20 | access to property Field | Operator.cs:116:23:116:39 | call to method Source<Object> : Object | Operator.cs:120:14:120:20 | access to property Field | $@ | Operator.cs:116:23:116:39 | call to method Source<Object> : Object | call to method Source<Object> : Object |
310332
| Operator.cs:120:14:120:20 | access to property Field | Operator.cs:116:23:116:39 | call to method Source<Object> : Object | Operator.cs:120:14:120:20 | access to property Field | $@ | Operator.cs:116:23:116:39 | call to method Source<Object> : Object | call to method Source<Object> : Object |
333+
| Operator.cs:152:18:152:24 | access to property Field | Operator.cs:145:21:145:37 | call to method Source<Object> : Object | Operator.cs:152:18:152:24 | access to property Field | $@ | Operator.cs:145:21:145:37 | call to method Source<Object> : Object | call to method Source<Object> : Object |
334+
| Operator.cs:152:18:152:24 | access to property Field | Operator.cs:145:21:145:37 | call to method Source<Object> : Object | Operator.cs:152:18:152:24 | access to property Field | $@ | Operator.cs:145:21:145:37 | call to method Source<Object> : Object | call to method Source<Object> : Object |

0 commit comments

Comments
 (0)