Skip to content

Commit e6532cb

Browse files
authored
Merge pull request github#4695 from tamasvajk/feature/csharp9-with-expr
C#: Extract 'with' expressions
2 parents 9eed17f + f878453 commit e6532cb

File tree

28 files changed

+7345
-2762
lines changed

28 files changed

+7345
-2762
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
lgtm,codescanning
2+
* C# 9 `with` expressions are now extracted. Data flow support has been added to
3+
handle flow through `with` expressions and also from `record` constructor arguments
4+
to its properties.

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,9 @@ internal static Expression Create(ExpressionNodeInfo info)
250250
case SyntaxKind.SuppressNullableWarningExpression:
251251
return PostfixUnary.Create(info.SetKind(ExprKind.SUPPRESS_NULLABLE_WARNING), ((PostfixUnaryExpressionSyntax)info.Node).Operand);
252252

253+
case SyntaxKind.WithExpression:
254+
return WithExpression.Create(info);
255+
253256
default:
254257
info.Context.ModelError(info.Node, $"Unhandled expression '{info.Node}' of kind '{info.Node.Kind()}'");
255258
return new Unknown(info);

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ internal abstract class Initializer : Expression<InitializerExpressionSyntax>
1212
protected Initializer(ExpressionNodeInfo info) : base(info) { }
1313
}
1414

15-
internal class ArrayInitializer : Expression<InitializerExpressionSyntax>
15+
internal class ArrayInitializer : Initializer
1616
{
1717
private ArrayInitializer(ExpressionNodeInfo info) : base(info.SetType(null).SetKind(ExprKind.ARRAY_INIT)) { }
1818

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using Microsoft.CodeAnalysis.CSharp.Syntax;
2+
using Semmle.Extraction.Kinds;
3+
using System.IO;
4+
5+
namespace Semmle.Extraction.CSharp.Entities.Expressions
6+
{
7+
internal class WithExpression : Expression<WithExpressionSyntax>
8+
{
9+
private WithExpression(ExpressionNodeInfo info) : base(info.SetKind(ExprKind.WITH)) { }
10+
11+
public static Expression Create(ExpressionNodeInfo info) => new WithExpression(info).TryPopulate();
12+
13+
protected override void PopulateExpression(TextWriter trapFile)
14+
{
15+
Create(cx, Syntax.Expression, this, 0);
16+
17+
ObjectInitializer.Create(new ExpressionNodeInfo(cx, Syntax.Initializer, this, 1).SetType(Type));
18+
}
19+
}
20+
}

csharp/extractor/Semmle.Extraction.CSharp/Kinds/ExprKind.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ public enum ExprKind
124124
AND_PATTERN = 127,
125125
OR_PATTERN = 128,
126126
FUNCTION_POINTER_INVOCATION = 129,
127-
127+
WITH = 130,
128128
DEFINE_SYMBOL = 999
129129
}
130130
}

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,15 @@ class Operator extends Callable, Member, Attributable, @operator {
461461
override Parameter getRawParameter(int i) { result = getParameter(i) }
462462
}
463463

464+
/** A clone method on a record. */
465+
class RecordCloneMethod extends Method, DotNet::RecordCloneCallable {
466+
override Constructor getConstructor() {
467+
result = DotNet::RecordCloneCallable.super.getConstructor()
468+
}
469+
470+
override string toString() { result = Method.super.toString() }
471+
}
472+
464473
/**
465474
* A user-defined unary operator - an operator taking one operand.
466475
*

csharp/ql/src/semmle/code/csharp/Type.qll

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -748,6 +748,9 @@ class Class extends RefType, @class_type {
748748
class Record extends Class {
749749
Record() { this.isRecord() }
750750

751+
/** Gets the clone method of this record. */
752+
RecordCloneMethod getCloneMethod() { result = this.getAMember() }
753+
751754
override string getAPrimaryQlClass() { result = "Record" }
752755
}
753756

csharp/ql/src/semmle/code/csharp/dataflow/LibraryTypeDataFlow.qll

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2416,3 +2416,22 @@ class StringValuesFlow extends LibraryTypeDataFlow, Struct {
24162416
preservesValue = false
24172417
}
24182418
}
2419+
2420+
private class RecordConstructorFlow extends SummarizedCallable {
2421+
RecordConstructorFlow() { this = any(Record r).getAMember().(Constructor) }
2422+
2423+
override predicate propagatesFlow(
2424+
SummaryInput input, ContentList inputContents, SummaryOutput output, ContentList outputContents,
2425+
boolean preservesValue
2426+
) {
2427+
exists(int i, Property p, string name |
2428+
this.getParameter(i).getName() = name and
2429+
this.getDeclaringType().getAMember(name) = p and
2430+
input = SummaryInput::parameter(i) and
2431+
inputContents = ContentList::empty() and
2432+
output = SummaryOutput::return() and
2433+
outputContents = ContentList::property(p) and
2434+
preservesValue = true
2435+
)
2436+
}
2437+
}

csharp/ql/src/semmle/code/csharp/dataflow/internal/ControlFlowReachability.qll

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ abstract class ControlFlowReachabilityConfiguration extends string {
6161
/**
6262
* Holds if `e1` and `e2` are expressions for which we want to find a
6363
* control-flow path that follows control flow successors (resp.
64-
* predecessors, as specified by `isSuccesor`) inside the syntactic scope
64+
* predecessors, as specified by `isSuccessor`) inside the syntactic scope
6565
* `scope`. The Boolean `exactScope` indicates whether a transitive child
6666
* of `scope` is allowed (`exactScope = false`).
6767
*/
@@ -74,7 +74,7 @@ abstract class ControlFlowReachabilityConfiguration extends string {
7474
/**
7575
* Holds if `e` and `def` are elements for which we want to find a
7676
* control-flow path that follows control flow successors (resp.
77-
* predecessors, as specified by `isSuccesor`) inside the syntactic scope
77+
* predecessors, as specified by `isSuccessor`) inside the syntactic scope
7878
* `scope`. The Boolean `exactScope` indicates whether a transitive child
7979
* of `scope` is allowed (`exactScope = false`).
8080
*/

csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowPrivate.qll

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,17 @@ module LocalFlow {
209209
e1 = e2.(SwitchExpr).getACase().getBody() and
210210
scope = e2 and
211211
isSuccessor = true
212+
or
213+
exists(WithExpr we |
214+
scope = we and
215+
isSuccessor = true
216+
|
217+
e1 = we.getExpr() and
218+
e2 = we.getInitializer()
219+
or
220+
e1 = we.getInitializer() and
221+
e2 = we
222+
)
212223
)
213224
}
214225

@@ -451,10 +462,23 @@ private predicate fieldOrPropertyStore(Expr e, Content c, Expr src, Expr q, bool
451462
postUpdate = true
452463
)
453464
or
465+
// `with` expression initializer, `x with { f = src }`
466+
e =
467+
any(WithExpr we |
468+
exists(MemberInitializer mi |
469+
q = we and
470+
mi = we.getInitializer().getAMemberInitializer() and
471+
f = mi.getInitializedMember() and
472+
src = mi.getRValue() and
473+
postUpdate = false
474+
)
475+
)
476+
or
454477
// Object initializer, `new C() { f = src }`
455478
exists(MemberInitializer mi |
456479
e = q and
457480
mi = q.(ObjectInitializer).getAMemberInitializer() and
481+
q.getParent() instanceof ObjectCreation and
458482
f = mi.getInitializedMember() and
459483
src = mi.getRValue() and
460484
postUpdate = false
@@ -792,6 +816,13 @@ private module Cached {
792816
input = SummaryInput::parameter(i) and
793817
n.(ArgumentNode).argumentOf(call, i)
794818
)
819+
or
820+
exists(WithExpr we, ObjectInitializer oi, FieldOrProperty f |
821+
oi = we.getInitializer() and
822+
n.asExpr() = oi and
823+
f = oi.getAMemberInitializer().getInitializedMember() and
824+
c = f.getContent()
825+
)
795826
}
796827

797828
/**
@@ -885,6 +916,8 @@ private module Cached {
885916
n instanceof SummaryNodeImpl
886917
or
887918
n instanceof ParamsArgumentNode
919+
or
920+
n.asExpr() = any(WithExpr we).getInitializer()
888921
}
889922
}
890923

0 commit comments

Comments
 (0)