Merge pull request #4695 from tamasvajk/feature/csharp9-with-expr

C#: Extract 'with' expressions
This commit is contained in:
Tamás Vajk
2021-02-23 21:04:51 +01:00
committed by GitHub
28 changed files with 7626 additions and 3043 deletions

View File

@@ -0,0 +1,4 @@
lgtm,codescanning
* C# 9 `with` expressions are now extracted. Data flow support has been added to
handle flow through `with` expressions and also from `record` constructor arguments
to its properties.

View File

@@ -250,6 +250,9 @@ namespace Semmle.Extraction.CSharp.Entities.Expressions
case SyntaxKind.SuppressNullableWarningExpression:
return PostfixUnary.Create(info.SetKind(ExprKind.SUPPRESS_NULLABLE_WARNING), ((PostfixUnaryExpressionSyntax)info.Node).Operand);
case SyntaxKind.WithExpression:
return WithExpression.Create(info);
default:
info.Context.ModelError(info.Node, $"Unhandled expression '{info.Node}' of kind '{info.Node.Kind()}'");
return new Unknown(info);

View File

@@ -12,7 +12,7 @@ namespace Semmle.Extraction.CSharp.Entities.Expressions
protected Initializer(ExpressionNodeInfo info) : base(info) { }
}
internal class ArrayInitializer : Expression<InitializerExpressionSyntax>
internal class ArrayInitializer : Initializer
{
private ArrayInitializer(ExpressionNodeInfo info) : base(info.SetType(null).SetKind(ExprKind.ARRAY_INIT)) { }

View File

@@ -0,0 +1,20 @@
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Semmle.Extraction.Kinds;
using System.IO;
namespace Semmle.Extraction.CSharp.Entities.Expressions
{
internal class WithExpression : Expression<WithExpressionSyntax>
{
private WithExpression(ExpressionNodeInfo info) : base(info.SetKind(ExprKind.WITH)) { }
public static Expression Create(ExpressionNodeInfo info) => new WithExpression(info).TryPopulate();
protected override void PopulateExpression(TextWriter trapFile)
{
Create(cx, Syntax.Expression, this, 0);
ObjectInitializer.Create(new ExpressionNodeInfo(cx, Syntax.Initializer, this, 1).SetType(Type));
}
}
}

View File

@@ -124,7 +124,7 @@ namespace Semmle.Extraction.Kinds
AND_PATTERN = 127,
OR_PATTERN = 128,
FUNCTION_POINTER_INVOCATION = 129,
WITH = 130,
DEFINE_SYMBOL = 999
}
}

View File

@@ -461,6 +461,15 @@ class Operator extends Callable, Member, Attributable, @operator {
override Parameter getRawParameter(int i) { result = getParameter(i) }
}
/** A clone method on a record. */
class RecordCloneMethod extends Method, DotNet::RecordCloneCallable {
override Constructor getConstructor() {
result = DotNet::RecordCloneCallable.super.getConstructor()
}
override string toString() { result = Method.super.toString() }
}
/**
* A user-defined unary operator - an operator taking one operand.
*

View File

@@ -748,6 +748,9 @@ class Class extends RefType, @class_type {
class Record extends Class {
Record() { this.isRecord() }
/** Gets the clone method of this record. */
RecordCloneMethod getCloneMethod() { result = this.getAMember() }
override string getAPrimaryQlClass() { result = "Record" }
}

View File

@@ -2416,3 +2416,22 @@ class StringValuesFlow extends LibraryTypeDataFlow, Struct {
preservesValue = false
}
}
private class RecordConstructorFlow extends SummarizedCallable {
RecordConstructorFlow() { this = any(Record r).getAMember().(Constructor) }
override predicate propagatesFlow(
SummaryInput input, ContentList inputContents, SummaryOutput output, ContentList outputContents,
boolean preservesValue
) {
exists(int i, Property p, string name |
this.getParameter(i).getName() = name and
this.getDeclaringType().getAMember(name) = p and
input = SummaryInput::parameter(i) and
inputContents = ContentList::empty() and
output = SummaryOutput::return() and
outputContents = ContentList::property(p) and
preservesValue = true
)
}
}

View File

@@ -61,7 +61,7 @@ abstract class ControlFlowReachabilityConfiguration extends string {
/**
* Holds if `e1` and `e2` are expressions for which we want to find a
* control-flow path that follows control flow successors (resp.
* predecessors, as specified by `isSuccesor`) inside the syntactic scope
* predecessors, as specified by `isSuccessor`) inside the syntactic scope
* `scope`. The Boolean `exactScope` indicates whether a transitive child
* of `scope` is allowed (`exactScope = false`).
*/
@@ -74,7 +74,7 @@ abstract class ControlFlowReachabilityConfiguration extends string {
/**
* Holds if `e` and `def` are elements for which we want to find a
* control-flow path that follows control flow successors (resp.
* predecessors, as specified by `isSuccesor`) inside the syntactic scope
* predecessors, as specified by `isSuccessor`) inside the syntactic scope
* `scope`. The Boolean `exactScope` indicates whether a transitive child
* of `scope` is allowed (`exactScope = false`).
*/

View File

@@ -209,6 +209,17 @@ module LocalFlow {
e1 = e2.(SwitchExpr).getACase().getBody() and
scope = e2 and
isSuccessor = true
or
exists(WithExpr we |
scope = we and
isSuccessor = true
|
e1 = we.getExpr() and
e2 = we.getInitializer()
or
e1 = we.getInitializer() and
e2 = we
)
)
}
@@ -451,10 +462,23 @@ private predicate fieldOrPropertyStore(Expr e, Content c, Expr src, Expr q, bool
postUpdate = true
)
or
// `with` expression initializer, `x with { f = src }`
e =
any(WithExpr we |
exists(MemberInitializer mi |
q = we and
mi = we.getInitializer().getAMemberInitializer() and
f = mi.getInitializedMember() and
src = mi.getRValue() and
postUpdate = false
)
)
or
// Object initializer, `new C() { f = src }`
exists(MemberInitializer mi |
e = q and
mi = q.(ObjectInitializer).getAMemberInitializer() and
q.getParent() instanceof ObjectCreation and
f = mi.getInitializedMember() and
src = mi.getRValue() and
postUpdate = false
@@ -792,6 +816,13 @@ private module Cached {
input = SummaryInput::parameter(i) and
n.(ArgumentNode).argumentOf(call, i)
)
or
exists(WithExpr we, ObjectInitializer oi, FieldOrProperty f |
oi = we.getInitializer() and
n.asExpr() = oi and
f = oi.getAMemberInitializer().getInitializedMember() and
c = f.getContent()
)
}
/**
@@ -885,6 +916,8 @@ private module Cached {
n instanceof SummaryNodeImpl
or
n instanceof ParamsArgumentNode
or
n.asExpr() = any(WithExpr we).getInitializer()
}
}

View File

@@ -61,7 +61,7 @@ abstract class ControlFlowReachabilityConfiguration extends string {
/**
* Holds if `e1` and `e2` are expressions for which we want to find a
* control-flow path that follows control flow successors (resp.
* predecessors, as specified by `isSuccesor`) inside the syntactic scope
* predecessors, as specified by `isSuccessor`) inside the syntactic scope
* `scope`. The Boolean `exactScope` indicates whether a transitive child
* of `scope` is allowed (`exactScope = false`).
*/
@@ -74,7 +74,7 @@ abstract class ControlFlowReachabilityConfiguration extends string {
/**
* Holds if `e` and `def` are elements for which we want to find a
* control-flow path that follows control flow successors (resp.
* predecessors, as specified by `isSuccesor`) inside the syntactic scope
* predecessors, as specified by `isSuccessor`) inside the syntactic scope
* `scope`. The Boolean `exactScope` indicates whether a transitive child
* of `scope` is allowed (`exactScope = false`).
*/

View File

@@ -1152,3 +1152,21 @@ class DefineSymbolExpr extends Expr, @define_symbol_expr {
override string getAPrimaryQlClass() { result = "DefineSymbolExpr" }
}
/**
* A `with` expression called on a record.
*/
class WithExpr extends Expr, @with_expr {
/** Gets the object initializer of this `with` expression. */
ObjectInitializer getInitializer() { result = this.getChild(1) }
/** Gets the expression on which this `with` is called. */
Expr getExpr() { result = this.getChild(0) }
/** Gets the clone method of the `record` that is targetted by this `with` expression. */
RecordCloneMethod getCloneMethod() { result = this.getExpr().getType().(Record).getCloneMethod() }
override string toString() { result = "... with { ... }" }
override string getAPrimaryQlClass() { result = "WithExpr" }
}

View File

@@ -91,3 +91,34 @@ abstract class Constructor extends Callable { }
/** A destructor/finalizer. */
abstract class Destructor extends Callable { }
pragma[nomagic]
private ValueOrRefType getARecordBaseType(ValueOrRefType t) {
exists(Callable c |
c.hasName("<Clone>$") and
c.getNumberOfParameters() = 0 and
t = c.getDeclaringType() and
result = t
)
or
result = getARecordBaseType(t).getABaseType()
}
/** A clone method on a record. */
class RecordCloneCallable extends Callable {
RecordCloneCallable() {
this.getDeclaringType() instanceof ValueOrRefType and
this.hasName("<Clone>$") and
this.getNumberOfParameters() = 0 and
this.getReturnType() = getARecordBaseType(this.getDeclaringType()) and
this.(Member).isPublic() and
not this.(Member).isStatic()
}
/** Gets the constructor that this clone method calls. */
Constructor getConstructor() {
result.getDeclaringType() = this.getDeclaringType() and
result.getNumberOfParameters() = 1 and
result.getParameter(0).getType() = this.getDeclaringType()
}
}

View File

@@ -51,16 +51,7 @@ class ValueOrRefType extends Type, @dotnet_valueorreftype {
ValueOrRefType getABaseType() { none() }
/** Holds if this type is a `record`. */
predicate isRecord() {
exists(Callable c |
c.getDeclaringType() = this and
c.hasName("<Clone>$") and
c.getNumberOfParameters() = 0 and
c.getReturnType() = this.getABaseType*() and
c.(Member).isPublic() and
not c.(Member).isStatic()
)
}
predicate isRecord() { exists(RecordCloneCallable c | c.getDeclaringType() = this) }
}
/**

View File

@@ -1156,6 +1156,7 @@ case @expr.kind of
| 127 = @and_pattern_expr
| 128 = @or_pattern_expr
| 129 = @function_pointer_invocation_expr
| 130 = @with_expr
/* Preprocessor */
| 999 = @define_symbol_expr
;

File diff suppressed because it is too large Load Diff

View File

@@ -1020,61 +1020,158 @@ Record.cs:
# 50| 0: [MethodCall] call to method ToString
# 50| -1: [LocalVariableAccess] access to local variable s
# 50| 1: [StringLiteral] " is a dog"
# 54| [Class] Record1
# 56| 5: [Method] M1
# 56| -1: [TypeMention] Void
# 57| 4: [BlockStmt] {...}
# 58| 0: [LocalVariableDeclStmt] ... ...;
# 58| 0: [LocalVariableDeclAndInitExpr] Person person = ...
# 58| -1: [TypeMention] Person
# 58| 0: [LocalVariableAccess] access to local variable person
# 58| 1: [ObjectCreation] object creation of type Person
# 58| -1: [TypeMention] Person
# 58| 0: [StringLiteral] "Bill"
# 58| 1: [StringLiteral] "Wagner"
# 59| 1: [LocalVariableDeclStmt] ... ...;
# 59| 0: [LocalVariableDeclAndInitExpr] Student student = ...
# 59| -1: [TypeMention] Student
# 59| 0: [LocalVariableAccess] access to local variable student
# 59| 1: [ObjectCreation] object creation of type Student
# 59| -1: [TypeMention] Student
# 59| 0: [StringLiteral] "Bill"
# 59| 1: [StringLiteral] "Wagner"
# 59| 2: [IntLiteral] 11
# 61| 2: [ExprStmt] ...;
# 61| 0: [MethodCall] call to method WriteLine
# 61| -1: [TypeAccess] access to type Console
# 61| 0: [TypeMention] Console
# 61| 0: [OperatorCall] call to operator ==
# 61| 0: [LocalVariableAccess] access to local variable student
# 61| 1: [LocalVariableAccess] access to local variable person
# 64| 6: [Method] M2
# 64| -1: [TypeMention] Void
# 65| 4: [BlockStmt] {...}
# 66| 0: [LocalVariableDeclStmt] ... ...;
# 66| 0: [LocalVariableDeclAndInitExpr] Person1 person = ...
# 66| -1: [TypeMention] Person1
# 66| 0: [LocalVariableAccess] access to local variable person
# 66| 1: [ObjectCreation] object creation of type Person1
# 66| -1: [TypeMention] Person1
# 66| 0: [StringLiteral] "Bill"
# 66| 1: [StringLiteral] "Wagner"
# 68| 1: [ExprStmt] ...;
# 68| 0: [AssignExpr] ... = ...
# 68| 0: [TupleExpr] (..., ...)
# 68| 0: [LocalVariableDeclExpr] String first
# 68| 1: [LocalVariableDeclExpr] String last
# 68| 1: [LocalVariableAccess] access to local variable person
# 69| 2: [ExprStmt] ...;
# 69| 0: [MethodCall] call to method WriteLine
# 69| -1: [TypeAccess] access to type Console
# 69| 0: [TypeMention] Console
# 69| 0: [LocalVariableAccess] access to local variable first
# 70| 3: [ExprStmt] ...;
# 70| 0: [MethodCall] call to method WriteLine
# 70| -1: [TypeAccess] access to type Console
# 70| 0: [TypeMention] Console
# 70| 0: [LocalVariableAccess] access to local variable last
# 54| [Record] R1
# 54| 12: [NEOperator] !=
#-----| 2: (Parameters)
# 54| 0: [Parameter] r1
# 54| 1: [Parameter] r2
# 54| 13: [EQOperator] ==
#-----| 2: (Parameters)
# 54| 0: [Parameter] r1
# 54| 1: [Parameter] r2
# 54| 14: [Property] EqualityContract
# 54| 3: [Getter] get_EqualityContract
# 54| 15: [InstanceConstructor] R1
#-----| 2: (Parameters)
# 54| 0: [Parameter] A
# 54| -1: [TypeMention] string
# 54| 16: [Property] A
# 54| 3: [Getter] get_A
# 54| 4: [Setter] set_A
#-----| 2: (Parameters)
# 54| 0: [Parameter] value
# 56| [Record] R2
# 56| 13: [NEOperator] !=
#-----| 2: (Parameters)
# 56| 0: [Parameter] r1
# 56| 1: [Parameter] r2
# 56| 14: [EQOperator] ==
#-----| 2: (Parameters)
# 56| 0: [Parameter] r1
# 56| 1: [Parameter] r2
# 56| 15: [Property] EqualityContract
# 56| 3: [Getter] get_EqualityContract
# 56| 16: [InstanceConstructor] R2
#-----| 2: (Parameters)
# 56| 0: [Parameter] A
# 56| -1: [TypeMention] string
# 56| 1: [Parameter] B
# 56| -1: [TypeMention] string
# 56| 17: [Property] B
# 56| 3: [Getter] get_B
# 56| 4: [Setter] set_B
#-----| 2: (Parameters)
# 56| 0: [Parameter] value
# 58| [Class] Record1
# 60| 5: [Method] M1
# 60| -1: [TypeMention] Void
# 61| 4: [BlockStmt] {...}
# 62| 0: [LocalVariableDeclStmt] ... ...;
# 62| 0: [LocalVariableDeclAndInitExpr] Person person = ...
# 62| -1: [TypeMention] Person
# 62| 0: [LocalVariableAccess] access to local variable person
# 62| 1: [ObjectCreation] object creation of type Person
# 62| -1: [TypeMention] Person
# 62| 0: [StringLiteral] "Bill"
# 62| 1: [StringLiteral] "Wagner"
# 63| 1: [LocalVariableDeclStmt] ... ...;
# 63| 0: [LocalVariableDeclAndInitExpr] Student student = ...
# 63| -1: [TypeMention] Student
# 63| 0: [LocalVariableAccess] access to local variable student
# 63| 1: [ObjectCreation] object creation of type Student
# 63| -1: [TypeMention] Student
# 63| 0: [StringLiteral] "Bill"
# 63| 1: [StringLiteral] "Wagner"
# 63| 2: [IntLiteral] 11
# 65| 2: [ExprStmt] ...;
# 65| 0: [MethodCall] call to method WriteLine
# 65| -1: [TypeAccess] access to type Console
# 65| 0: [TypeMention] Console
# 65| 0: [OperatorCall] call to operator ==
# 65| 0: [LocalVariableAccess] access to local variable student
# 65| 1: [LocalVariableAccess] access to local variable person
# 68| 6: [Method] M2
# 68| -1: [TypeMention] Void
# 69| 4: [BlockStmt] {...}
# 70| 0: [LocalVariableDeclStmt] ... ...;
# 70| 0: [LocalVariableDeclAndInitExpr] Person1 p1 = ...
# 70| -1: [TypeMention] Person1
# 70| 0: [LocalVariableAccess] access to local variable p1
# 70| 1: [ObjectCreation] object creation of type Teacher1
# 70| -1: [TypeMention] Teacher1
# 70| 0: [StringLiteral] "Bill"
# 70| 1: [StringLiteral] "Wagner"
# 70| 2: [StringLiteral] "Math"
# 72| 1: [ExprStmt] ...;
# 72| 0: [AssignExpr] ... = ...
# 72| 0: [TupleExpr] (..., ...)
# 72| 0: [LocalVariableDeclExpr] String first
# 72| 1: [LocalVariableDeclExpr] String last
# 72| 1: [LocalVariableAccess] access to local variable p1
# 73| 2: [ExprStmt] ...;
# 73| 0: [MethodCall] call to method WriteLine
# 73| -1: [TypeAccess] access to type Console
# 73| 0: [TypeMention] Console
# 73| 0: [LocalVariableAccess] access to local variable first
# 75| 3: [LocalVariableDeclStmt] ... ...;
# 75| 0: [LocalVariableDeclAndInitExpr] Person1 p2 = ...
# 75| -1: [TypeMention] Person1
# 75| 0: [LocalVariableAccess] access to local variable p2
# 75| 1: [WithExpr] ... with { ... }
# 75| 0: [LocalVariableAccess] access to local variable p1
# 75| 1: [ObjectInitializer] { ..., ... }
# 75| 0: [MemberInitializer] ... = ...
# 75| 0: [PropertyCall] access to property FirstName
# 75| 1: [StringLiteral] "Paul"
# 76| 4: [LocalVariableDeclStmt] ... ...;
# 76| 0: [LocalVariableDeclAndInitExpr] Teacher1 p3 = ...
# 76| -1: [TypeMention] Teacher1
# 76| 0: [LocalVariableAccess] access to local variable p3
# 76| 1: [WithExpr] ... with { ... }
# 76| 0: [CastExpr] (...) ...
# 76| 0: [TypeAccess] access to type Teacher1
# 76| 0: [TypeMention] Teacher1
# 76| 1: [LocalVariableAccess] access to local variable p1
# 76| 1: [ObjectInitializer] { ..., ... }
# 76| 0: [MemberInitializer] ... = ...
# 76| 0: [PropertyCall] access to property FirstName
# 76| 1: [StringLiteral] "Paul"
# 76| 1: [MemberInitializer] ... = ...
# 76| 0: [PropertyCall] access to property Subject
# 76| 1: [StringLiteral] "Literature"
# 77| 5: [LocalVariableDeclStmt] ... ...;
# 77| 0: [LocalVariableDeclAndInitExpr] Person1 clone = ...
# 77| -1: [TypeMention] Person1
# 77| 0: [LocalVariableAccess] access to local variable clone
# 77| 1: [WithExpr] ... with { ... }
# 77| 0: [LocalVariableAccess] access to local variable p1
# 77| 1: [ObjectInitializer] { ..., ... }
# 80| 7: [Method] M3
# 80| -1: [TypeMention] Void
# 81| 4: [BlockStmt] {...}
# 82| 0: [LocalVariableDeclStmt] ... ...;
# 82| 0: [LocalVariableDeclAndInitExpr] R2 a = ...
# 82| -1: [TypeMention] R2
# 82| 0: [LocalVariableAccess] access to local variable a
# 82| 1: [ObjectCreation] object creation of type R2
# 82| -1: [TypeMention] R2
# 82| 0: [StringLiteral] "A"
# 82| 1: [StringLiteral] "B"
# 83| 1: [LocalVariableDeclStmt] ... ...;
# 83| 0: [LocalVariableDeclAndInitExpr] R1 b = ...
# 83| -1: [TypeMention] R1
# 83| 0: [LocalVariableAccess] access to local variable b
# 83| 1: [LocalVariableAccess] access to local variable a
# 84| 2: [LocalVariableDeclStmt] ... ...;
# 84| 0: [LocalVariableDeclAndInitExpr] R1 c = ...
# 84| -1: [TypeMention] R1
# 84| 0: [LocalVariableAccess] access to local variable c
# 84| 1: [WithExpr] ... with { ... }
# 84| 0: [LocalVariableAccess] access to local variable b
# 84| 1: [ObjectInitializer] { ..., ... }
# 84| 0: [MemberInitializer] ... = ...
# 84| 0: [PropertyCall] access to property A
# 84| 1: [StringLiteral] "C"
RelationalPattern.cs:
# 3| [Class] RelationalPattern
# 5| 5: [Method] M1

View File

@@ -51,6 +51,10 @@ public record Dog(string Name) : Pet(Name)
}
}
public abstract record R1(string A) { }
public record R2(string A, string B) : R1(A) { }
public class Record1
{
public void M1()
@@ -63,10 +67,20 @@ public class Record1
public void M2()
{
var person = new Person1("Bill", "Wagner");
Person1 p1 = new Teacher1("Bill", "Wagner", "Math");
var (first, last) = person;
var (first, last) = p1;
Console.WriteLine(first);
Console.WriteLine(last);
var p2 = p1 with { FirstName = "Paul" };
var p3 = (Teacher1)p1 with { FirstName = "Paul", Subject = "Literature" };
var clone = p1 with { };
}
public void M3()
{
R2 a = new R2("A", "B");
R1 b = a;
R1 c = b with { A = "C" };
}
}

View File

@@ -10,4 +10,6 @@
| Record.cs:29:66:29:72 | set_Subject | init |
| Record.cs:32:70:32:74 | set_Level | init |
| Record.cs:35:26:35:29 | set_Name | init |
| Record.cs:54:34:54:34 | set_A | init |
| Record.cs:56:35:56:35 | set_B | init |
| UnaryPattern.cs:5:26:5:28 | set_P1 | set |

View File

@@ -1,12 +1,14 @@
types
| Record.cs:4:1:10:1 | Person | IEquatable<Person> |
| Record.cs:12:1:18:1 | Teacher | IEquatable<Teacher> |
| Record.cs:20:1:25:1 | Student | IEquatable<Student> |
| Record.cs:27:1:27:57 | Person1 | IEquatable<Person1> |
| Record.cs:29:1:30:35 | Teacher1 | IEquatable<Teacher1> |
| Record.cs:32:1:33:35 | Student1 | IEquatable<Student1> |
| Record.cs:35:1:39:1 | Pet | IEquatable<Pet> |
| Record.cs:41:1:52:1 | Dog | IEquatable<Dog> |
records
| Record.cs:4:1:10:1 | Person | IEquatable<Person> | Record.cs:4:1:10:1 | <Clone>$ |
| Record.cs:12:1:18:1 | Teacher | IEquatable<Teacher> | Record.cs:12:1:18:1 | <Clone>$ |
| Record.cs:20:1:25:1 | Student | IEquatable<Student> | Record.cs:20:1:25:1 | <Clone>$ |
| Record.cs:27:1:27:57 | Person1 | IEquatable<Person1> | Record.cs:27:1:27:57 | <Clone>$ |
| Record.cs:29:1:30:35 | Teacher1 | IEquatable<Teacher1> | Record.cs:29:1:30:35 | <Clone>$ |
| Record.cs:32:1:33:35 | Student1 | IEquatable<Student1> | Record.cs:32:1:33:35 | <Clone>$ |
| Record.cs:35:1:39:1 | Pet | IEquatable<Pet> | Record.cs:35:1:39:1 | <Clone>$ |
| Record.cs:41:1:52:1 | Dog | IEquatable<Dog> | Record.cs:41:1:52:1 | <Clone>$ |
| Record.cs:54:1:54:39 | R1 | IEquatable<R1> | Record.cs:54:1:54:39 | <Clone>$ |
| Record.cs:56:1:56:48 | R2 | IEquatable<R2> | Record.cs:56:1:56:48 | <Clone>$ |
members
| Record.cs:4:1:10:1 | Person | Person.!=(Person, Person) | Record.cs:4:15:4:20 |
| Record.cs:4:1:10:1 | Person | Person.<Clone>$() | no location |
@@ -198,3 +200,48 @@ members
| Record.cs:41:1:52:1 | Dog | System.Object.Object() | no location |
| Record.cs:41:1:52:1 | Dog | System.Object.ReferenceEquals(object, object) | no location |
| Record.cs:41:1:52:1 | Dog | System.Object.~Object() | no location |
| Record.cs:54:1:54:39 | R1 | R1.!=(R1, R1) | Record.cs:54:24:54:25 |
| Record.cs:54:1:54:39 | R1 | R1.<Clone>$() | no location |
| Record.cs:54:1:54:39 | R1 | R1.==(R1, R1) | Record.cs:54:24:54:25 |
| Record.cs:54:1:54:39 | R1 | R1.A | Record.cs:54:34:54:34 |
| Record.cs:54:1:54:39 | R1 | R1.Deconstruct(out string) | no location |
| Record.cs:54:1:54:39 | R1 | R1.EqualityContract | Record.cs:54:24:54:25 |
| Record.cs:54:1:54:39 | R1 | R1.Equals(R1) | no location |
| Record.cs:54:1:54:39 | R1 | R1.Equals(object) | no location |
| Record.cs:54:1:54:39 | R1 | R1.GetHashCode() | no location |
| Record.cs:54:1:54:39 | R1 | R1.PrintMembers(StringBuilder) | no location |
| Record.cs:54:1:54:39 | R1 | R1.R1(R1) | no location |
| Record.cs:54:1:54:39 | R1 | R1.R1(string) | Record.cs:54:24:54:25 |
| Record.cs:54:1:54:39 | R1 | R1.ToString() | no location |
| Record.cs:54:1:54:39 | R1 | System.Object.Equals(object, object) | no location |
| Record.cs:54:1:54:39 | R1 | System.Object.GetType() | no location |
| Record.cs:54:1:54:39 | R1 | System.Object.MemberwiseClone() | no location |
| Record.cs:54:1:54:39 | R1 | System.Object.Object() | no location |
| Record.cs:54:1:54:39 | R1 | System.Object.ReferenceEquals(object, object) | no location |
| Record.cs:54:1:54:39 | R1 | System.Object.~Object() | no location |
| Record.cs:56:1:56:48 | R2 | R1.!=(R1, R1) | Record.cs:54:24:54:25 |
| Record.cs:56:1:56:48 | R2 | R1.==(R1, R1) | Record.cs:54:24:54:25 |
| Record.cs:56:1:56:48 | R2 | R1.A | Record.cs:54:34:54:34 |
| Record.cs:56:1:56:48 | R2 | R1.Deconstruct(out string) | no location |
| Record.cs:56:1:56:48 | R2 | R1.R1(R1) | no location |
| Record.cs:56:1:56:48 | R2 | R1.R1(string) | Record.cs:54:24:54:25 |
| Record.cs:56:1:56:48 | R2 | R2.!=(R2, R2) | Record.cs:56:15:56:16 |
| Record.cs:56:1:56:48 | R2 | R2.<Clone>$() | no location |
| Record.cs:56:1:56:48 | R2 | R2.==(R2, R2) | Record.cs:56:15:56:16 |
| Record.cs:56:1:56:48 | R2 | R2.B | Record.cs:56:35:56:35 |
| Record.cs:56:1:56:48 | R2 | R2.Deconstruct(out string, out string) | no location |
| Record.cs:56:1:56:48 | R2 | R2.EqualityContract | Record.cs:56:15:56:16 |
| Record.cs:56:1:56:48 | R2 | R2.Equals(R1) | no location |
| Record.cs:56:1:56:48 | R2 | R2.Equals(R2) | no location |
| Record.cs:56:1:56:48 | R2 | R2.Equals(object) | no location |
| Record.cs:56:1:56:48 | R2 | R2.GetHashCode() | no location |
| Record.cs:56:1:56:48 | R2 | R2.PrintMembers(StringBuilder) | no location |
| Record.cs:56:1:56:48 | R2 | R2.R2(R2) | no location |
| Record.cs:56:1:56:48 | R2 | R2.R2(string, string) | Record.cs:56:15:56:16 |
| Record.cs:56:1:56:48 | R2 | R2.ToString() | no location |
| Record.cs:56:1:56:48 | R2 | System.Object.Equals(object, object) | no location |
| Record.cs:56:1:56:48 | R2 | System.Object.GetType() | no location |
| Record.cs:56:1:56:48 | R2 | System.Object.MemberwiseClone() | no location |
| Record.cs:56:1:56:48 | R2 | System.Object.Object() | no location |
| Record.cs:56:1:56:48 | R2 | System.Object.ReferenceEquals(object, object) | no location |
| Record.cs:56:1:56:48 | R2 | System.Object.~Object() | no location |

View File

@@ -1,6 +1,8 @@
import csharp
query predicate types(Record t, string i) { t.getABaseInterface().toStringWithTypes() = i }
query predicate records(Record t, string i, RecordCloneMethod clone) {
t.getABaseInterface().toStringWithTypes() = i and clone = t.getCloneMethod()
}
private string getMemberName(Member m) {
result = m.getDeclaringType().getQualifiedName() + "." + m.toStringWithTypes()

View File

@@ -0,0 +1,17 @@
withExpr
| Record.cs:75:18:75:47 | ... with { ... } | Person1 | Record.cs:75:18:75:19 | access to local variable p1 | Record.cs:75:26:75:47 | { ..., ... } | Person1.<Clone>$() |
| Record.cs:76:18:76:81 | ... with { ... } | Teacher1 | Record.cs:76:18:76:29 | (...) ... | Record.cs:76:36:76:81 | { ..., ... } | Teacher1.<Clone>$() |
| Record.cs:77:21:77:31 | ... with { ... } | Person1 | Record.cs:77:21:77:22 | access to local variable p1 | Record.cs:77:29:77:31 | { ..., ... } | Person1.<Clone>$() |
| Record.cs:84:16:84:33 | ... with { ... } | R1 | Record.cs:84:16:84:16 | access to local variable b | Record.cs:84:23:84:33 | { ..., ... } | R1.<Clone>$() |
withTarget
| Record.cs:75:18:75:47 | ... with { ... } | Record.cs:27:1:27:57 | <Clone>$ | Record.cs:27:1:27:57 | Person1 |
| Record.cs:76:18:76:81 | ... with { ... } | Record.cs:29:1:30:35 | <Clone>$ | Record.cs:29:1:30:35 | Teacher1 |
| Record.cs:77:21:77:31 | ... with { ... } | Record.cs:27:1:27:57 | <Clone>$ | Record.cs:27:1:27:57 | Person1 |
| Record.cs:84:16:84:33 | ... with { ... } | Record.cs:54:1:54:39 | <Clone>$ | Record.cs:54:1:54:39 | R1 |
cloneOverrides
| Person1.<Clone>$() | Student1.<Clone>$() |
| Person1.<Clone>$() | Teacher1.<Clone>$() |
| Person.<Clone>$() | Student.<Clone>$() |
| Person.<Clone>$() | Teacher.<Clone>$() |
| Pet.<Clone>$() | Dog.<Clone>$() |
| R1.<Clone>$() | R2.<Clone>$() |

View File

@@ -0,0 +1,26 @@
import csharp
private string getSignature(Method m) {
result = m.getDeclaringType().getQualifiedName() + "." + m.toStringWithTypes()
}
query predicate withExpr(WithExpr with, string type, Expr expr, ObjectInitializer init, string clone) {
type = with.getType().toStringWithTypes() and
expr = with.getExpr() and
init = with.getInitializer() and
clone = getSignature(with.getCloneMethod())
}
query predicate withTarget(WithExpr with, RecordCloneMethod clone, Constructor ctor) {
with.getCloneMethod() = clone and
clone.getConstructor() = ctor
}
query predicate cloneOverrides(string b, string o) {
exists(RecordCloneMethod base, RecordCloneMethod overrider |
base.getDeclaringType().fromSource() and
base.(Virtualizable).getAnOverrider() = overrider and
b = getSignature(base) and
o = getSignature(overrider)
)
}

View File

@@ -238,6 +238,18 @@ edges
| I.cs:37:23:37:23 | i [Field1] : Object | I.cs:39:9:39:9 | access to parameter i [Field1] : Object |
| I.cs:39:9:39:9 | access to parameter i [Field1] : Object | I.cs:40:14:40:14 | access to parameter i [Field1] : Object |
| I.cs:40:14:40:14 | access to parameter i [Field1] : Object | I.cs:40:14:40:21 | access to field Field1 |
| J.cs:12:17:12:28 | object creation of type Object : Object | J.cs:13:29:13:29 | access to local variable o : Object |
| J.cs:12:17:12:28 | object creation of type Object : Object | J.cs:21:36:21:36 | access to local variable o : Object |
| J.cs:13:18:13:36 | object creation of type Record [Prop1] : Object | J.cs:14:14:14:15 | access to local variable r1 [Prop1] : Object |
| J.cs:13:18:13:36 | object creation of type Record [Prop1] : Object | J.cs:18:14:18:15 | access to local variable r2 [Prop1] : Object |
| J.cs:13:18:13:36 | object creation of type Record [Prop1] : Object | J.cs:22:14:22:15 | access to local variable r3 [Prop1] : Object |
| J.cs:13:29:13:29 | access to local variable o : Object | J.cs:13:18:13:36 | object creation of type Record [Prop1] : Object |
| J.cs:14:14:14:15 | access to local variable r1 [Prop1] : Object | J.cs:14:14:14:21 | access to property Prop1 |
| J.cs:18:14:18:15 | access to local variable r2 [Prop1] : Object | J.cs:18:14:18:21 | access to property Prop1 |
| J.cs:21:18:21:38 | ... with { ... } [Prop2] : Object | J.cs:23:14:23:15 | access to local variable r3 [Prop2] : Object |
| J.cs:21:36:21:36 | access to local variable o : Object | J.cs:21:18:21:38 | ... with { ... } [Prop2] : Object |
| J.cs:22:14:22:15 | access to local variable r3 [Prop1] : Object | J.cs:22:14:22:21 | access to property Prop1 |
| J.cs:23:14:23:15 | access to local variable r3 [Prop2] : Object | J.cs:23:14:23:21 | access to property Prop2 |
nodes
| A.cs:5:17:5:23 | object creation of type C : C | semmle.label | object creation of type C : C |
| A.cs:6:17:6:25 | call to method Make [c] : C | semmle.label | call to method Make [c] : C |
@@ -513,6 +525,19 @@ nodes
| I.cs:39:9:39:9 | access to parameter i [Field1] : Object | semmle.label | access to parameter i [Field1] : Object |
| I.cs:40:14:40:14 | access to parameter i [Field1] : Object | semmle.label | access to parameter i [Field1] : Object |
| I.cs:40:14:40:21 | access to field Field1 | semmle.label | access to field Field1 |
| J.cs:12:17:12:28 | object creation of type Object : Object | semmle.label | object creation of type Object : Object |
| J.cs:13:18:13:36 | object creation of type Record [Prop1] : Object | semmle.label | object creation of type Record [Prop1] : Object |
| J.cs:13:29:13:29 | access to local variable o : Object | semmle.label | access to local variable o : Object |
| J.cs:14:14:14:15 | access to local variable r1 [Prop1] : Object | semmle.label | access to local variable r1 [Prop1] : Object |
| J.cs:14:14:14:21 | access to property Prop1 | semmle.label | access to property Prop1 |
| J.cs:18:14:18:15 | access to local variable r2 [Prop1] : Object | semmle.label | access to local variable r2 [Prop1] : Object |
| J.cs:18:14:18:21 | access to property Prop1 | semmle.label | access to property Prop1 |
| J.cs:21:18:21:38 | ... with { ... } [Prop2] : Object | semmle.label | ... with { ... } [Prop2] : Object |
| J.cs:21:36:21:36 | access to local variable o : Object | semmle.label | access to local variable o : Object |
| J.cs:22:14:22:15 | access to local variable r3 [Prop1] : Object | semmle.label | access to local variable r3 [Prop1] : Object |
| J.cs:22:14:22:21 | access to property Prop1 | semmle.label | access to property Prop1 |
| J.cs:23:14:23:15 | access to local variable r3 [Prop2] : Object | semmle.label | access to local variable r3 [Prop2] : Object |
| J.cs:23:14:23:21 | access to property Prop2 | semmle.label | access to property Prop2 |
#select
| A.cs:7:14:7:16 | access to field c | A.cs:5:17:5:23 | object creation of type C : C | A.cs:7:14:7:16 | access to field c | $@ | A.cs:5:17:5:23 | object creation of type C : C | object creation of type C : C |
| A.cs:14:14:14:20 | call to method Get | A.cs:13:15:13:22 | object creation of type C1 : C1 | A.cs:14:14:14:20 | call to method Get | $@ | A.cs:13:15:13:22 | object creation of type C1 : C1 | object creation of type C1 : C1 |
@@ -566,3 +591,7 @@ nodes
| I.cs:23:14:23:21 | access to field Field1 | I.cs:7:18:7:29 | object creation of type Object : Object | I.cs:23:14:23:21 | access to field Field1 | $@ | I.cs:7:18:7:29 | object creation of type Object : Object | object creation of type Object : Object |
| I.cs:27:14:27:21 | access to field Field1 | I.cs:7:18:7:29 | object creation of type Object : Object | I.cs:27:14:27:21 | access to field Field1 | $@ | I.cs:7:18:7:29 | object creation of type Object : Object | object creation of type Object : Object |
| I.cs:40:14:40:21 | access to field Field1 | I.cs:31:13:31:24 | object creation of type Object : Object | I.cs:40:14:40:21 | access to field Field1 | $@ | I.cs:31:13:31:24 | object creation of type Object : Object | object creation of type Object : Object |
| J.cs:14:14:14:21 | access to property Prop1 | J.cs:12:17:12:28 | object creation of type Object : Object | J.cs:14:14:14:21 | access to property Prop1 | $@ | J.cs:12:17:12:28 | object creation of type Object : Object | object creation of type Object : Object |
| J.cs:18:14:18:21 | access to property Prop1 | J.cs:12:17:12:28 | object creation of type Object : Object | J.cs:18:14:18:21 | access to property Prop1 | $@ | J.cs:12:17:12:28 | object creation of type Object : Object | object creation of type Object : Object |
| J.cs:22:14:22:21 | access to property Prop1 | J.cs:12:17:12:28 | object creation of type Object : Object | J.cs:22:14:22:21 | access to property Prop1 | $@ | J.cs:12:17:12:28 | object creation of type Object : Object | object creation of type Object : Object |
| J.cs:23:14:23:21 | access to property Prop2 | J.cs:12:17:12:28 | object creation of type Object : Object | J.cs:23:14:23:21 | access to property Prop2 | $@ | J.cs:12:17:12:28 | object creation of type Object : Object | object creation of type Object : Object |

View File

@@ -0,0 +1,31 @@
namespace System.Runtime.CompilerServices
{
internal static class IsExternalInit { }
}
public record Record(object Prop1, object Prop2) { }
public class J
{
private void M1()
{
var o = new object();
var r1 = new Record(o, null);
Sink(r1.Prop1); // flow
Sink(r1.Prop2); // no flow
var r2 = r1 with { };
Sink(r2.Prop1); // flow
Sink(r2.Prop2); // no flow
var r3 = r1 with { Prop2 = o };
Sink(r3.Prop1); // flow
Sink(r3.Prop2); // flow
var r4 = r1 with { Prop1 = null };
Sink(r4.Prop1); // no flow
Sink(r4.Prop2); // no flow
}
public static void Sink(object o) { }
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,2 @@
description: Add 'with_expr' to 'expr' types.
compatibility: backwards