Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
840fcb4
C#: Extract compound operator assignments as is.
michaelnebel Mar 11, 2026
0b926d8
C#: Update the DB Scheme such that compound assignments can be consid…
michaelnebel Mar 11, 2026
8727bdd
C#: Let assign operations be operator calls.
michaelnebel Mar 11, 2026
0b79ea2
C#: Add another group of assignable definitions (corresponding to com…
michaelnebel Mar 11, 2026
998d720
C#: Remove uses of expanded assignment.
michaelnebel Mar 11, 2026
4f30ada
C#: Remove expanded assignments from tests.
michaelnebel Mar 11, 2026
d31cbb3
C#: Remove Primary QL class and toString for DynamicOperatorCall (fix…
michaelnebel Mar 11, 2026
7c248f0
C#: Update guards library as assign operations are not expanded.
michaelnebel Mar 13, 2026
8979161
C#: Update nullness expected test output.
michaelnebel Mar 13, 2026
a4e83d4
C#: Update range utils and signanalysis.
michaelnebel Mar 12, 2026
6788c9d
C#: Update sign- and modulus analysis expected test output.
michaelnebel Mar 13, 2026
30a4ca6
C#: Update the ImplicitToStringExpr class to take AssignAddExpr into …
michaelnebel Mar 13, 2026
85d88c0
C#: Update cs/useless-assignment-to-local to take AssignOperationDefi…
michaelnebel Mar 11, 2026
654ccf8
C#: Update MissingXMLValidation expected test output. Note that this …
michaelnebel Mar 12, 2026
735dea8
C#: Update test expected output.
michaelnebel Mar 11, 2026
d03fb89
C#: Update SSA tests expected output.
michaelnebel Mar 13, 2026
175bcef
C#: Update CFG expected test output.
michaelnebel Mar 13, 2026
3732be5
C#: Update the viable callables test as += now is an operator call.
michaelnebel Mar 13, 2026
d2031e5
C#: Add comment to viable callable testcase and update expected outpu…
michaelnebel Mar 13, 2026
5cb4ddc
C#: Swap left and right child of assignment expressions.
michaelnebel Mar 16, 2026
74c45ff
C#: Swap left and right child in assignments in the QL library.
michaelnebel Mar 16, 2026
e9f6f10
C#: Update structural comparison test expected output.
michaelnebel Mar 16, 2026
906e0e9
C#: Add some more tests for cs/coalesce-of-identical-expressions and …
michaelnebel Mar 17, 2026
106cb0f
C#: Add a NullCoalescingOperation class.
michaelnebel Mar 17, 2026
3c8c47d
C#: Use the NullCoalescingOperation class where needed (now that assi…
michaelnebel Mar 17, 2026
30a301d
C#: Update expected test output for UselessNullCoalescingExpression.
michaelnebel Mar 17, 2026
f3e06ef
FIXUP of C# 8 tests - controlflow for null coalesching assignment.
michaelnebel Mar 17, 2026
7def15b
FIXUP of nullness check.
michaelnebel Mar 17, 2026
d038d35
C#: Add cs/unused-collection test case.
michaelnebel Mar 17, 2026
b2b065d
C#: Update the cs/unused-collection query.
michaelnebel Mar 17, 2026
d677a07
C#: Add null coalescing data flow tests.
michaelnebel Mar 18, 2026
2a1b1ab
C#: Add operation classes.
michaelnebel Mar 18, 2026
a72f848
C#: Update data flow and taint-tracking private.
michaelnebel Mar 18, 2026
cdf37e2
C#: Update guards library implementation.
michaelnebel Mar 18, 2026
19db97f
C#: Rename unsigned right shift class.
michaelnebel Mar 18, 2026
c76f03e
C#: Use the operation classes where applicable.
michaelnebel Mar 18, 2026
b7a75ce
C#: Update expected output after right shift rename.
michaelnebel Mar 18, 2026
5218054
FIXUP of range and modulus analysis.
michaelnebel Mar 18, 2026
2949edd
FIXUP of DB wrt. which operators result in operator calls.
michaelnebel Mar 18, 2026
ab63949
FIXUP of extractor implementation. Null coalescing assignments doesn'…
michaelnebel Mar 18, 2026
9b0fe48
FIXUP of assign operation implementation.
michaelnebel Mar 18, 2026
fc5347b
FIXUP of AssignOperation expected test output.
michaelnebel Mar 18, 2026
a232f28
C#: Preserve the semantics of the cs/complex-condition query.
michaelnebel Mar 19, 2026
37e37ee
C#: Add test case for cs/unsynchronized-getter with spurious result.
michaelnebel Mar 19, 2026
6bb6714
C#: Update test expected output for cs/unsynchronized-getter.
michaelnebel Mar 19, 2026
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 @@ -22,26 +22,12 @@ public static Assignment Create(ExpressionNodeInfo info)

protected override void PopulateExpression(TextWriter trapFile)
{
var operatorKind = OperatorKind;
if (operatorKind.HasValue)
{
// Convert assignment such as `a += b` into `a = a + b`.
var simpleAssignExpr = new Expression(new ExpressionInfo(Context, Type, Location, ExprKind.SIMPLE_ASSIGN, this, 2, isCompilerGenerated: true, null));
Create(Context, Syntax.Left, simpleAssignExpr, 1);
var opexpr = new Expression(new ExpressionInfo(Context, Type, Location, operatorKind.Value, simpleAssignExpr, 0, isCompilerGenerated: true, null));
Create(Context, Syntax.Left, opexpr, 0, isCompilerGenerated: true);
Create(Context, Syntax.Right, opexpr, 1);
opexpr.OperatorCall(trapFile, Syntax);
}
else
{
Create(Context, Syntax.Left, this, 1);
Create(Context, Syntax.Right, this, 0);
Create(Context, Syntax.Left, this, 0);
Create(Context, Syntax.Right, this, 1);

if (Kind == ExprKind.ADD_EVENT || Kind == ExprKind.REMOVE_EVENT)
{
OperatorCall(trapFile, Syntax);
}
if (Kind != ExprKind.SIMPLE_ASSIGN && Kind != ExprKind.ASSIGN_COALESCE)
{
OperatorCall(trapFile, Syntax);
}
}

Expand Down Expand Up @@ -108,56 +94,5 @@ private static ExprKind GetKind(Context cx, AssignmentExpressionSyntax syntax)

return kind;
}

/// <summary>
/// Gets the kind of this assignment operator (<code>null</code> if the
/// assignment is not an assignment operator). For example, the operator
/// kind of `*=` is `*`.
/// </summary>
private ExprKind? OperatorKind
{
get
{
var kind = Kind;
if (kind == ExprKind.REMOVE_EVENT || kind == ExprKind.ADD_EVENT || kind == ExprKind.SIMPLE_ASSIGN)
return null;

if (CallType.AdjustKind(kind) == ExprKind.OPERATOR_INVOCATION)
return ExprKind.OPERATOR_INVOCATION;

switch (kind)
{
case ExprKind.ASSIGN_ADD:
return ExprKind.ADD;
case ExprKind.ASSIGN_AND:
return ExprKind.BIT_AND;
case ExprKind.ASSIGN_DIV:
return ExprKind.DIV;
case ExprKind.ASSIGN_LSHIFT:
return ExprKind.LSHIFT;
case ExprKind.ASSIGN_MUL:
return ExprKind.MUL;
case ExprKind.ASSIGN_OR:
return ExprKind.BIT_OR;
case ExprKind.ASSIGN_REM:
return ExprKind.REM;
case ExprKind.ASSIGN_RSHIFT:
return ExprKind.RSHIFT;
case ExprKind.ASSIGN_URSHIFT:
return ExprKind.URSHIFT;
case ExprKind.ASSIGN_SUB:
return ExprKind.SUB;
case ExprKind.ASSIGN_XOR:
return ExprKind.BIT_XOR;
case ExprKind.ASSIGN_COALESCE:
return ExprKind.NULL_COALESCING;
default:
Context.ModelError(Syntax, $"Couldn't unfold assignment of type {kind}");
return ExprKind.UNKNOWN;
}
}
}

public new CallType CallType => GetCallType(Context, Syntax);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -83,30 +83,31 @@ protected override void PopulateExpression(TextWriter trapFile)
{
var assignmentInfo = new ExpressionNodeInfo(Context, init, this, child++).SetKind(ExprKind.SIMPLE_ASSIGN);
var assignmentEntity = new Expression(assignmentInfo);
var typeInfoRight = Context.GetTypeInfo(assignment.Right);
if (typeInfoRight.Type is null)
// The type may be null for nested initializers such as
// ```csharp
// new ClassWithArrayField() { As = { [0] = a } }
// ```
// In this case we take the type from the assignment
// `As = { [0] = a }` instead
typeInfoRight = assignmentInfo.TypeInfo;
CreateFromNode(new ExpressionNodeInfo(Context, assignment.Right, assignmentEntity, 0, typeInfoRight));

var target = Context.GetSymbolInfo(assignment.Left);

// If the target is null, then assume that this is an array initializer (of the form `[...] = ...`)

var access = target.Symbol is null ?
new Expression(new ExpressionNodeInfo(Context, assignment.Left, assignmentEntity, 1).SetKind(ExprKind.ARRAY_ACCESS)) :
Access.Create(new ExpressionNodeInfo(Context, assignment.Left, assignmentEntity, 1), target.Symbol, false, Context.CreateEntity(target.Symbol));
new Expression(new ExpressionNodeInfo(Context, assignment.Left, assignmentEntity, 0).SetKind(ExprKind.ARRAY_ACCESS)) :
Access.Create(new ExpressionNodeInfo(Context, assignment.Left, assignmentEntity, 0), target.Symbol, false, Context.CreateEntity(target.Symbol));

if (assignment.Left is ImplicitElementAccessSyntax iea)
{
// An array/indexer initializer of the form `[...] = ...`
access.PopulateArguments(trapFile, iea.ArgumentList.Arguments, 0);
}

var typeInfoRight = Context.GetTypeInfo(assignment.Right);
if (typeInfoRight.Type is null)
{
// The type may be null for nested initializers such as
// ```csharp
// new ClassWithArrayField() { As = { [0] = a } }
// ```
// In this case we take the type from the assignment
// `As = { [0] = a }` instead
typeInfoRight = assignmentInfo.TypeInfo;
}
CreateFromNode(new ExpressionNodeInfo(Context, assignment.Right, assignmentEntity, 1, typeInfoRight));
}
else
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,11 @@ protected override void PopulateExpression(TextWriter trapFile)
var loc = Context.CreateLocation(init.GetLocation());

var assignment = new Expression(new ExpressionInfo(Context, type, loc, ExprKind.SIMPLE_ASSIGN, objectInitializer, child++, isCompilerGenerated: false, null));
Create(Context, init.Expression, assignment, 0);
Property.Create(Context, property);

var access = new Expression(new ExpressionInfo(Context, type, loc, ExprKind.PROPERTY_ACCESS, assignment, 1, isCompilerGenerated: false, null));
var access = new Expression(new ExpressionInfo(Context, type, loc, ExprKind.PROPERTY_ACCESS, assignment, 0, isCompilerGenerated: false, null));
trapFile.expr_access(access, propEntity);

Create(Context, init.Expression, assignment, 1);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,12 +94,12 @@ protected Expression DeclareRangeVariable(Context cx, IExpressionParentEntity pa
child
);

Expression.Create(cx, Expr, decl, 0);

var nameLoc = cx.CreateLocation(name.GetLocation());
var access = new Expression(new ExpressionInfo(cx, type, nameLoc, ExprKind.LOCAL_VARIABLE_ACCESS, decl, 1, isCompilerGenerated: false, null));
var access = new Expression(new ExpressionInfo(cx, type, nameLoc, ExprKind.LOCAL_VARIABLE_ACCESS, decl, 0, isCompilerGenerated: false, null));
cx.TrapWriter.Writer.expr_access(access, LocalVariable.Create(cx, variableSymbol));

Expression.Create(cx, Expr, decl, 1);

return decl;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,11 +176,11 @@ public static VariableDeclaration CreateDeclarator(Context cx, VariableDeclarato

if (d.Initializer is not null)
{
Create(cx, d.Initializer.Value, ret, 0);

// Create an access
var access = new Expression(new ExpressionInfo(cx, type, localVar.Location, ExprKind.LOCAL_VARIABLE_ACCESS, ret, 1, isCompilerGenerated: false, null));
var access = new Expression(new ExpressionInfo(cx, type, localVar.Location, ExprKind.LOCAL_VARIABLE_ACCESS, ret, 0, isCompilerGenerated: false, null));
cx.TrapWriter.Writer.expr_access(access, localVar);

Create(cx, d.Initializer.Value, ret, 1);
}

if (d.Parent is VariableDeclarationSyntax decl)
Expand Down
4 changes: 2 additions & 2 deletions csharp/extractor/Semmle.Extraction.CSharp/Entities/Field.cs
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,9 @@ private Expression AddInitializerAssignment(TextWriter trapFile, ExpressionSynta
{
var type = Symbol.GetAnnotatedType();
var simpleAssignExpr = new Expression(new ExpressionInfo(Context, type, loc, ExprKind.SIMPLE_ASSIGN, this, child++, isCompilerGenerated: true, constValue));
Expression.CreateFromNode(new ExpressionNodeInfo(Context, initializer, simpleAssignExpr, 0));
var access = new Expression(new ExpressionInfo(Context, type, Location, ExprKind.FIELD_ACCESS, simpleAssignExpr, 1, isCompilerGenerated: true, constValue));
var access = new Expression(new ExpressionInfo(Context, type, Location, ExprKind.FIELD_ACCESS, simpleAssignExpr, 0, isCompilerGenerated: true, constValue));
trapFile.expr_access(access, this);
Expression.CreateFromNode(new ExpressionNodeInfo(Context, initializer, simpleAssignExpr, 1));
return access;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,9 @@ public override void Populate(TextWriter trapFile)
var loc = Context.CreateLocation(initializer!.GetLocation());
var annotatedType = AnnotatedTypeSymbol.CreateNotAnnotated(Symbol.Type);
var simpleAssignExpr = new Expression(new ExpressionInfo(Context, annotatedType, loc, ExprKind.SIMPLE_ASSIGN, this, child++, isCompilerGenerated: true, null));
Expression.CreateFromNode(new ExpressionNodeInfo(Context, initializer.Value, simpleAssignExpr, 0));
var access = new Expression(new ExpressionInfo(Context, annotatedType, Location, ExprKind.PROPERTY_ACCESS, simpleAssignExpr, 1, isCompilerGenerated: true, null));
var access = new Expression(new ExpressionInfo(Context, annotatedType, Location, ExprKind.PROPERTY_ACCESS, simpleAssignExpr, 0, isCompilerGenerated: true, null));
trapFile.expr_access(access, this);
Expression.CreateFromNode(new ExpressionNodeInfo(Context, initializer.Value, simpleAssignExpr, 1));
if (!Symbol.IsStatic)
{
This.CreateImplicit(Context, Symbol.ContainingType, Location, access, -1);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ private predicate maybeUsedInElfHashFunction(Variable v, Operation xor, Operatio
Expr e1, Expr e2, AssignExpr addAssign, AssignExpr xorAssign, Operation notOp,
AssignExpr notAssign
|
(add instanceof AddExpr or add instanceof AssignAddExpr) and
add instanceof AddOperation and
e1.getAChild*() = add.getAnOperand() and
e1 instanceof BinaryBitwiseOperation and
e2 = e1.(BinaryBitwiseOperation).getLeftOperand() and
Expand Down
42 changes: 36 additions & 6 deletions csharp/ql/lib/semmle/code/csharp/Assignable.qll
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@
this.isRefArgument()
or
this = any(AssignableDefinitions::AddressOfDefinition def).getTargetAccess()
or
this = any(AssignableDefinitions::AssignOperationDefinition def).getTargetAccess()
) and
not nameOfChild(_, this)
}
Expand Down Expand Up @@ -271,6 +273,8 @@
def = TAddressOfDefinition(result)
or
def = TPatternDefinition(result)
or
def = TAssignOperationDefinition(result)
}

/** A local variable declaration at the top-level of a pattern. */
Expand All @@ -286,7 +290,11 @@
private module Cached {
cached
newtype TAssignableDefinition =
TAssignmentDefinition(Assignment a) { not a.getLValue() instanceof TupleExpr } or
TAssignmentDefinition(Assignment a) {
not a.getLValue() instanceof TupleExpr and
not a instanceof AssignCompoundOperation and
not a instanceof AssignCoalesceExpr
} or
TTupleAssignmentDefinition(AssignExpr ae, Expr leaf) { tupleAssignmentDefinition(ae, leaf) } or
TOutRefDefinition(AssignableAccess aa) {
aa.isOutArgument()
Expand All @@ -309,7 +317,11 @@
)
} or
TAddressOfDefinition(AddressOfExpr aoe) or
TPatternDefinition(TopLevelPatternDecl tlpd)
TPatternDefinition(TopLevelPatternDecl tlpd) or
TAssignOperationDefinition(AssignOperation ao) {

Check warning on line 321 in csharp/ql/lib/semmle/code/csharp/Assignable.qll

View workflow job for this annotation

GitHub Actions / qldoc

Missing QLdoc for newtype-branch Assignable::AssignableInternal::Cached::TAssignOperationDefinition
ao instanceof AssignCompoundOperation or
ao instanceof AssignCoalesceExpr
}

/**
* Gets the source expression assigned in tuple definition `def`, if any.
Expand Down Expand Up @@ -355,6 +367,8 @@
def = TMutationDefinition(any(MutatorOperation mo | mo.getOperand() = result))
or
def = TAddressOfDefinition(any(AddressOfExpr aoe | aoe.getOperand() = result))
or
def = TAssignOperationDefinition(any(AssignOperation ao | ao.getLeftOperand() = result))
}

/**
Expand All @@ -369,8 +383,10 @@
or
exists(Assignment ass | ac = ass.getLValue() |
result = ass.getRValue() and
not ass.(AssignOperation).hasExpandedAssignment()
not ass instanceof AssignOperation
)
or
exists(AssignOperation ao | ac = ao.getLeftOperand() | result = ao)
}
}

Expand All @@ -388,8 +404,9 @@
* a mutation update (`AssignableDefinitions::MutationDefinition`), a local variable
* declaration without an initializer (`AssignableDefinitions::LocalVariableDefinition`),
* an implicit parameter definition (`AssignableDefinitions::ImplicitParameterDefinition`),
* an address-of definition (`AssignableDefinitions::AddressOfDefinition`), or a pattern
* definition (`AssignableDefinitions::PatternDefinition`).
* an address-of definition (`AssignableDefinitions::AddressOfDefinition`), a pattern
* definition (`AssignableDefinitions::PatternDefinition`), or a compound assignment
* operation definition (`AssignableDefinitions::AssignOperationDefinition`)
*/
class AssignableDefinition extends TAssignableDefinition {
/**
Expand Down Expand Up @@ -511,7 +528,7 @@

override Expr getSource() {
result = a.getRValue() and
not a instanceof AssignOperation
not a instanceof AddOrRemoveEventExpr
}

override string toString() { result = a.toString() }
Expand Down Expand Up @@ -735,4 +752,17 @@
/** Gets the assignable (field or property) being initialized. */
Assignable getAssignable() { result = fieldOrProp }
}

/**
* A definition by a compound assignment operation, for example `x += y`.
*/
class AssignOperationDefinition extends AssignableDefinition, TAssignOperationDefinition {
AssignOperation ao;

AssignOperationDefinition() { this = TAssignOperationDefinition(ao) }

override Expr getSource() { result = ao }

override string toString() { result = ao.toString() }
}
}
58 changes: 3 additions & 55 deletions csharp/ql/lib/semmle/code/csharp/ExprOrStmtParent.qll
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

/** Gets the `i`th child expression of this element (zero-based). */
final Expr getChildExpr(int i) {
expr_parent_adjusted(result, i, this) or
expr_parent(result, i, this) or
expr_parent_top_level_adjusted(result, i, this)
}

Expand Down Expand Up @@ -119,66 +119,14 @@
}

/**
* The `expr_parent()` relation adjusted for expandable assignments. For example,
* the assignment `x += y` is extracted as
*
* ```
* +=
* |
* 2
* |
* =
* / \
* 1 0
* / \
* x +
* / \
* 1 0
* / \
* x y
* ```
*
* in order to be able to retrieve the expanded assignment `x = x + y` as the 2nd
* child. This predicate changes the diagram above into
*
* ```
* +=
* / \
* 1 0
* / \
* x y
* ```
* Use `expr_parent` instead.
*/
cached
predicate expr_parent_adjusted(Expr child, int i, ControlFlowElement parent) {
if parent instanceof AssignOperation
then
parent =
any(AssignOperation ao |
exists(AssignExpr ae | ae = ao.getExpandedAssignment() |
i = 0 and
exists(Expr right |
// right = `x + y`
expr_parent(right, 0, ae)
|
expr_parent(child, 1, right)
)
or
i = 1 and
expr_parent(child, 1, ae)
)
or
not ao.hasExpandedAssignment() and
expr_parent(child, i, parent)
)
else expr_parent(child, i, parent)
}
deprecated predicate expr_parent_adjusted(Expr child, int i, ControlFlowElement parent) { none() }

Check warning

Code scanning / CodeQL

Missing QLDoc for parameter Warning

The QLDoc has no documentation for child, or i, or parent, but the QLDoc mentions expr_parent

private Expr getAChildExpr(ExprOrStmtParent parent) {
result = parent.getAChildExpr() and
not result = parent.(DeclarationWithGetSetAccessors).getExpressionBody()
or
result = parent.(AssignOperation).getExpandedAssignment()
}

private ControlFlowElement getAChild(ExprOrStmtParent parent) {
Expand Down
Loading
Loading