Skip to content

C#: Remove expanded assignments.#21452

Draft
michaelnebel wants to merge 9 commits intogithub:mainfrom
michaelnebel:csharp/expandedassignment
Draft

C#: Remove expanded assignments.#21452
michaelnebel wants to merge 9 commits intogithub:mainfrom
michaelnebel:csharp/expandedassignment

Conversation

@michaelnebel
Copy link
Contributor

@michaelnebel michaelnebel commented Mar 11, 2026

This PR has two goals, primarily to avoid making multiple upgrade/downgrade scripts:

  • Remove expanded assignments. This aligns our implementation with Java and should make adoption of the shared CFG library easier. It also simplifies expression handling and avoids caching a large predicate. Previously, when the extractor encountered a += b, it would synthesize a = a + b.
  • Swap the left- and right-hand-side child indices for assignments. This lets us remove several hacks/rotations currently used in the code.

Review on a commit-by-commit basis is encouraged.

Implementation notes

  • The implementation now “correctly” classifies a += b (e.g., when a and b are int) as an operator call, since it implicitly invokes the user-defined static operator on Int32.
  • The only compound assignment that does not call an operator is ??= as the null-coalescing operator is a built-in short-circuit like operation.
  • Added a new set of classes to represent expressions involving a specific operation (e.g., AddOperation, which can represent either a + b or a += b).

DCA notes

  • Performance appears unchanged.
  • The missing result for cs/unsynchronized-getter is due to removal of a false positive: the query did not properly account for expanded assignments.
  • The missing result for cs/web/xss looks acceptable: one alert is removed, but it is essentially a shorter path to the same underlying alert (which is here)
  • The new results for cs/useless-upcast look acceptable.
  • Remaining differences appear consistent with normal DCA background noise.

@github-actions github-actions bot added the C# label Mar 11, 2026
@michaelnebel michaelnebel force-pushed the csharp/expandedassignment branch from c94de85 to 1d8d712 Compare March 11, 2026 12:47
)
else expr_parent(child, i, parent)
}
deprecated predicate expr_parent_adjusted(Expr child, int i, ControlFlowElement parent) { none() }
@michaelnebel michaelnebel force-pushed the csharp/expandedassignment branch 9 times, most recently from abb75be to 5021ea9 Compare March 16, 2026 10:00
@michaelnebel michaelnebel changed the title C#: Cleanup expanded assignments. C#: Remove expanded assignments. Mar 16, 2026
@michaelnebel michaelnebel force-pushed the csharp/expandedassignment branch 13 times, most recently from 41fef59 to 07144c2 Compare March 20, 2026 12:09
@michaelnebel
Copy link
Contributor Author

@hvitved : I would really appreciate a review of this PR even though it is still in draft mode. The PR still needs upgrade/downgrade scripts and more DCA runs.

@michaelnebel michaelnebel requested a review from hvitved March 20, 2026 13:18
@michaelnebel michaelnebel force-pushed the csharp/expandedassignment branch from 07144c2 to d2188dd Compare March 23, 2026 09:09
* An assignment operation. Either an arithmetic assignment operation
* (`AssignArithmeticOperation`), a bitwise assignment operation
* (`AssignBitwiseOperation`), or an event assignment (`AddOrRemoveEventExpr`).
* (`AssignArithmeticOperation`), a bitwise assignment operation or
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spurious "or"

Suggested change
* (`AssignArithmeticOperation`), a bitwise assignment operation or
* (`AssignArithmeticOperation`), a bitwise assignment operation

}

/**
* An assignment operation that corresponds to an operator call, for example `x += y` corresponds to `x = x + y`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This reads somewhat off to me. Did you mean something like this instead?

Suggested change
* An assignment operation that corresponds to an operator call, for example `x += y` corresponds to `x = x + y`.
* An assignment operation that corresponds to an operator call, for example `x += y` corresponds to a call to `+=`.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some further elaboration on this piece of qldoc might also be nice to clarify exactly which compound assignments are considered OperatorCalls.

/**
* An assignment operation that corresponds to an operator call, for example `x += y` corresponds to `x = x + y`.
*/
class AssignCallOperation extends AssignOperation, OperatorCall, @assign_op_call_expr {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that most AssignOperations extend OperatorCall, then the qldoc for OperatorCall needs a tweak as it currently claims to only cover calls to user-defined operators.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we should consider changing the wording as I am not sure what a non user-defined operator is (unless it is a real built-in like &&). The operator used in a += b (when a and b are of type int) is a user-defined operator and it is declared here.

* An assignment operation that corresponds to an operator call, for example `x += y` corresponds to `x = x + y`.
*/
class AssignCallOperation extends AssignOperation, OperatorCall, @assign_op_call_expr {
override string toString() { result = "... " + this.getOperator() + " ..." }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This toString can be simplified.

Suggested change
override string toString() { result = "... " + this.getOperator() + " ..." }
override string toString() { result = AssignOperation.super.toString() }

import Expr

/** A binary operation that involves a null-coalescing operation. */
abstract private class NullCoalescingOperationImpl extends BinaryOperation { }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe all the classes in this file break some new ground wrt the AST class hierarchy design, so perhaps ought to be considered in a slightly broader bike-shedding session. Or is there something similar in other languages that I'm not aware of?
Regardless, I'm not a big fan of all the Add-prefixed names. Sure they're private, but they're still not very nice and they overload the meaning of "Add" in a context that already references "Add". Also, it's unfortunate that e.g. AddExpr and AssignAddExpr are not subclasses of AddOperation - that would be more natural. And if the latter is fixed, then I don't think we need all those addition-to-abstract-class classes that have such awkward names, which would address the former point as well.

Copy link
Contributor Author

@michaelnebel michaelnebel Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The other option that I considered was extending the DB-scheme with @add_operation = @add_expr | @assign_add_expr; and similar for the other operations, then the QL part will look more "natural". Would you prefer such a solution instead?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes.

* ```
* Use `expr_parent` instead.
*/
cached
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May as well get rid of cached.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can't, it's a public predicate in a cached module - so it has to be cached as well.

@aschackmull
Copy link
Contributor

Besides the nits, code changes generally LGTM.

Copy link
Contributor

@hvitved hvitved left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall LGTM; no further comments than what @aschackmull has already pointed out, thanks for making this change.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants