Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,13 @@ public static string GetName(this ISymbol symbol, bool useMetadataName = false)
{ "op_False", "false" }
});

/// <summary>
/// The operatorname for user-defined increment and decrement operators are "op_IncrementAssignment" and
/// "op_DecrementAssignment" respectively.
/// Thus we need to handle this explicitly to avoid postfixing them with an "=".
/// </summary>
private static bool isIncrementOrDecrement(string operatorName) => operatorName == "++" || operatorName == "--";

/// <summary>
/// Convert an operator method name in to a symbolic name.
/// A return value indicates whether the conversion succeeded.
Expand All @@ -72,7 +79,7 @@ public static bool TryGetOperatorSymbol(this ISymbol symbol, out string operator
if (match.Success && methodToOperator.TryGetValue($"op_{match.Groups[2]}", out var rawOperatorName))
{
var prefix = match.Groups[1].Success ? "checked " : "";
var postfix = match.Groups[3].Success ? "=" : "";
var postfix = match.Groups[3].Success && !isIncrementOrDecrement(rawOperatorName) ? "=" : "";
operatorName = $"{prefix}{rawOperatorName}{postfix}";
return true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -234,9 +234,9 @@ type.SpecialType is SpecialType.System_IntPtr ||
/// </summary>
/// <param name="node">The expression syntax node.</param>
/// <returns>Returns the target method symbol, or null if it cannot be resolved.</returns>
protected IMethodSymbol? GetTargetSymbol(ExpressionSyntax node)
protected static IMethodSymbol? GetTargetSymbol(Context cx, ExpressionSyntax node)
{
var si = Context.GetSymbolInfo(node);
var si = cx.GetSymbolInfo(node);
if (si.Symbol is ISymbol symbol)
{
var method = symbol as IMethodSymbol;
Expand All @@ -255,7 +255,7 @@ type.SpecialType is SpecialType.System_IntPtr ||
.Where(method => method.Parameters.Length >= syntax.ArgumentList.Arguments.Count)
.Where(method => method.Parameters.Count(p => !p.HasExplicitDefaultValue) <= syntax.ArgumentList.Arguments.Count);

return Context.ExtractionContext.IsStandalone ?
return cx.ExtractionContext.IsStandalone ?
candidates.FirstOrDefault() :
candidates.SingleOrDefault();
}
Expand All @@ -281,7 +281,7 @@ public static ExprKind UnaryOperatorKind(Context cx, ExprKind originalKind, Expr
/// <param name="node">The expression.</param>
public void AddOperatorCall(TextWriter trapFile, ExpressionSyntax node)
{
var @operator = GetTargetSymbol(node);
var @operator = GetTargetSymbol(Context, node);
if (@operator is IMethodSymbol method)
{
var callType = GetCallType(Context, node);
Expand Down Expand Up @@ -312,9 +312,9 @@ public enum CallType
/// <returns>The call type.</returns>
public static CallType GetCallType(Context cx, ExpressionSyntax node)
{
var @operator = cx.GetSymbolInfo(node);
var @operator = GetTargetSymbol(cx, node);

if (@operator.Symbol is IMethodSymbol method)
if (@operator is IMethodSymbol method)
{
if (method.ContainingSymbol is ITypeSymbol containingSymbol && containingSymbol.TypeKind == Microsoft.CodeAnalysis.TypeKind.Dynamic)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ protected override void PopulateExpression(TextWriter trapFile)

var child = -1;
string? memberName = null;
var target = GetTargetSymbol(Syntax);
var target = GetTargetSymbol(Context, Syntax);
switch (Syntax.Expression)
{
case MemberAccessExpressionSyntax memberAccess when IsValidMemberAccessKind():
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* C# 14: Added support for user-defined instance increment/decrement operators.
3 changes: 3 additions & 0 deletions csharp/ql/lib/semmle/code/csharp/Callable.qll
Original file line number Diff line number Diff line change
Expand Up @@ -613,6 +613,9 @@ class UnaryOperator extends Operator {
this.getNumberOfParameters() = 1 and
not this instanceof ConversionOperator and
not this instanceof CompoundAssignmentOperator
or
// Instance increment and decrement operators don't have a parameter (only a qualifier).
this.getNumberOfParameters() = 0 and not this.isStatic()
}
}

Expand Down
25 changes: 18 additions & 7 deletions csharp/ql/lib/semmle/code/csharp/dispatch/Dispatch.qll
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,19 @@ class DispatchCall extends Internal::TDispatchCall {
}
}

abstract private class InstanceOperatorCall extends OperatorCall {
abstract Expr getQualifier();
}

private class InstanceCompoundAssignment extends InstanceOperatorCall instanceof CompoundAssignmentOperatorCall
{
override Expr getQualifier() { result = CompoundAssignmentOperatorCall.super.getQualifier() }
}

private class InstanceMutator extends InstanceOperatorCall instanceof InstanceMutatorOperatorCall {
override Expr getQualifier() { result = InstanceMutatorOperatorCall.super.getQualifier() }
}

/** Internal implementation details. */
private module Internal {
private import OverridableCallable
Expand Down Expand Up @@ -101,9 +114,9 @@ private module Internal {
} or
TDispatchOperatorCall(OperatorCall oc) {
not oc.isLateBound() and
not oc instanceof CompoundAssignmentOperatorCall
not oc instanceof InstanceOperatorCall
} or
TDispatchCompoundAssignmentOperatorCall(CompoundAssignmentOperatorCall caoc) or
TDispatchInstanceOperatorCall(InstanceOperatorCall caoc) or
TDispatchReflectionCall(MethodCall mc, string name, Expr object, Expr qualifier, int args) {
isReflectionCall(mc, name, object, qualifier, args)
} or
Expand Down Expand Up @@ -890,12 +903,10 @@ private module Internal {
override Operator getAStaticTarget() { result = this.getCall().getTarget() }
}

private class DispatchCompoundAssignmentOperatorCall extends DispatchOverridableCall,
TDispatchCompoundAssignmentOperatorCall
private class DispatchInstanceOperatorCall extends DispatchOverridableCall,
TDispatchInstanceOperatorCall
{
override CompoundAssignmentOperatorCall getCall() {
this = TDispatchCompoundAssignmentOperatorCall(result)
}
override InstanceOperatorCall getCall() { this = TDispatchInstanceOperatorCall(result) }

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

Expand Down
23 changes: 23 additions & 0 deletions csharp/ql/lib/semmle/code/csharp/exprs/Call.qll
Original file line number Diff line number Diff line change
Expand Up @@ -570,6 +570,29 @@ class MutatorOperatorCall extends OperatorCall {
predicate isPostfix() { mutator_invocation_mode(this, 2) }
}

/**
* A call to an instance mutator operator, for example `a++` on
* line 5 in
*
* ```csharp
* class A {
* public void operator++() { ... }
*
* public static void Increment(A a) {
* a++;
* }
* }
* ```
*/
class InstanceMutatorOperatorCall extends MutatorOperatorCall {
InstanceMutatorOperatorCall() { this.getTarget().getNumberOfParameters() = 0 }

/** Gets the qualifier of this instance mutator operator call. */
Expr getQualifier() { result = this.getChildExpr(0) }

override Expr getArgument(int i) { none() }
}

/**
* A call to a compound assignment operator, for example `this += other`
* on line 7 in
Expand Down
33 changes: 33 additions & 0 deletions csharp/ql/test/library-tests/dataflow/operators/Operator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -120,3 +120,36 @@ public void M1()
Sink(x.Field); // $ hasValueFlow=1
}
}

public class MutatorOperators
{
static void Sink(object o) { }
static T Source<T>(object source) => throw null;

public class C1
{
public object Field { get; private set; }

public C1()
{
Field = new object();
}

public C1(object o)
{
Field = o;
}

public void operator ++()
{
Field = Source<object>(1);
}

public void M1()
{
var x = new C1();
x++;
Sink(x.Field); // $ hasValueFlow=1
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,16 @@ edges
| 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 | |
| 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 | |
| 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 | |
| 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 | |
| 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 | |
| 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 | |
| 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 | |
| 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 | |
| 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 | |
| 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 | |
| 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 | |
| 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 | |
| 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 | |
nodes
| Operator.cs:9:39:9:39 | x : C | semmle.label | x : C |
| Operator.cs:9:39:9:39 | x : C | semmle.label | x : C |
Expand Down Expand Up @@ -275,6 +285,18 @@ nodes
| 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 |
| Operator.cs:120:14:120:20 | access to property Field | semmle.label | access to property Field |
| Operator.cs:120:14:120:20 | access to property Field | semmle.label | access to property Field |
| Operator.cs:143:30:143:31 | this [Return] : C1 [property Field] : Object | semmle.label | this [Return] : C1 [property Field] : Object |
| Operator.cs:143:30:143:31 | this [Return] : C1 [property Field] : Object | semmle.label | this [Return] : C1 [property Field] : Object |
| Operator.cs:145:13:145:17 | [post] this access : C1 [property Field] : Object | semmle.label | [post] this access : C1 [property Field] : Object |
| Operator.cs:145:13:145:17 | [post] this access : C1 [property Field] : Object | semmle.label | [post] this access : C1 [property Field] : Object |
| Operator.cs:145:21:145:37 | call to method Source<Object> : Object | semmle.label | call to method Source<Object> : Object |
| Operator.cs:145:21:145:37 | call to method Source<Object> : Object | semmle.label | call to method Source<Object> : Object |
| 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 |
| 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 |
| 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 |
| 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 |
| Operator.cs:152:18:152:24 | access to property Field | semmle.label | access to property Field |
| Operator.cs:152:18:152:24 | access to property Field | semmle.label | access to property Field |
subpaths
| 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 |
| 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 |
Expand Down Expand Up @@ -308,3 +330,5 @@ testFailures
| 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 |
| 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 |
| 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 |
| 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 |
| 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 |
Loading
Loading