Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make it possible to analyze the dataflow of ConstructorInitializerSyntax #57576

Merged
merged 35 commits into from
Dec 1, 2021
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
f9c581a
initial check in for AnalyzeDataFlow with ConstructorInitializerSyntax
bernd5 Nov 4, 2021
f656c66
Merge remote-tracking branch 'origin/main' into allow_analyze_data_fl…
bernd5 Nov 4, 2021
c7a5bfa
Merge remote-tracking branch 'origin/main' into allow_analyze_data_fl…
bernd5 Nov 4, 2021
ead4279
Merge remote-tracking branch 'origin/main' into allow_analyze_data_fl…
bernd5 Nov 5, 2021
1fd09cd
comment adjusted
bernd5 Nov 5, 2021
7c41613
Merge remote-tracking branch 'origin/main' into allow_analyze_data_fl…
bernd5 Nov 8, 2021
e7cfdb7
Add overload in CSharpExtensions
bernd5 Nov 8, 2021
2ae537a
Merge remote-tracking branch 'origin/main' into allow_analyze_data_fl…
bernd5 Nov 9, 2021
1f56b5c
parameter names adjusted
bernd5 Nov 9, 2021
0eee201
comment adjusted
bernd5 Nov 9, 2021
cb6e96a
parameter names adjusted
bernd5 Nov 9, 2021
53e92a8
PrimaryConstructorBaseTypeSyntax support added
bernd5 Nov 9, 2021
c616493
tests moved in its own region
bernd5 Nov 9, 2021
7ae00e7
tests added
bernd5 Nov 9, 2021
c9a912e
error message extended
bernd5 Nov 9, 2021
318ce8d
parameter names adjusted
bernd5 Nov 9, 2021
3a5e197
comment fixed
bernd5 Nov 9, 2021
2436e63
comment adjusted
bernd5 Nov 9, 2021
0b23fe3
public api tests added
bernd5 Nov 9, 2021
1d85edf
public api tests added
bernd5 Nov 9, 2021
cd89bb0
Merge remote-tracking branch 'origin/main' into allow_analyze_data_fl…
bernd5 Nov 10, 2021
ed562b7
comment improved
bernd5 Nov 12, 2021
e9beada
Merge remote-tracking branch 'origin/main' into allow_analyze_data_fl…
bernd5 Nov 13, 2021
f7628b7
Merge remote-tracking branch 'origin/main' into allow_analyze_data_fl…
bernd5 Nov 16, 2021
3cd1e46
Merge remote-tracking branch 'origin/main' into allow_analyze_data_fl…
bernd5 Nov 20, 2021
4d2cd28
Update CSharpExtensions.cs
bernd5 Nov 20, 2021
7baa89f
Merge remote-tracking branch 'origin/main' into allow_analyze_data_fl…
bernd5 Nov 30, 2021
1586276
Merge branch 'allow_analyze_data_flow_for_ctor_init' of https://githu…
bernd5 Nov 30, 2021
a2ee898
use: see cref
bernd5 Nov 30, 2021
e7c5eae
use: see cref
bernd5 Nov 30, 2021
9681f1e
exception text changed
bernd5 Nov 30, 2021
0662cd7
exception text changed
bernd5 Nov 30, 2021
b5f5ef6
make RegionAnalysisContext non generic
bernd5 Nov 30, 2021
907eb89
added two tests
bernd5 Nov 30, 2021
44ee67b
additional ExtractMethodTest
bernd5 Nov 30, 2021
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
18 changes: 18 additions & 0 deletions src/Compilers/CSharp/Portable/CSharpExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1032,6 +1032,24 @@ public static AwaitExpressionInfo GetAwaitExpressionInfo(this SemanticModel? sem
return csmodel?.AnalyzeControlFlow(statement);
}

/// <summary>
/// Analyze data-flow within a constructor initializer.
/// </summary>
public static DataFlowAnalysis? AnalyzeDataFlow(this SemanticModel? semanticModel, ConstructorInitializerSyntax constructorInitializer)
{
var csmodel = semanticModel as CSharpSemanticModel;
return csmodel?.AnalyzeDataFlow(constructorInitializer);
}

/// <summary>
/// Analyze data-flow within a primary-constructor-base-type initializer.
bernd5 marked this conversation as resolved.
Show resolved Hide resolved
/// </summary>
public static DataFlowAnalysis? AnalyzeDataFlow(this SemanticModel? semanticModel, PrimaryConstructorBaseTypeSyntax primaryConstructorBaseType)
{
var csmodel = semanticModel as CSharpSemanticModel;
return csmodel?.AnalyzeDataFlow(primaryConstructorBaseType);
}

/// <summary>
/// Analyze data-flow within an expression.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2411,6 +2411,28 @@ public virtual ControlFlowAnalysis AnalyzeControlFlow(StatementSyntax statement)
return AnalyzeControlFlow(statement, statement);
}

/// <summary>
/// Analyze data-flow within an ConstructorInitializerSyntax.
bernd5 marked this conversation as resolved.
Show resolved Hide resolved
/// </summary>
/// <param name="constructorInitializer">The ctor-init within the associated SyntaxTree to analyze.</param>
/// <returns>An object that can be used to obtain the result of the data flow analysis.</returns>
public virtual DataFlowAnalysis AnalyzeDataFlow(ConstructorInitializerSyntax constructorInitializer)
{
// Only supported on a SyntaxTreeSemanticModel.
throw new NotSupportedException();
}

/// <summary>
/// Analyze data-flow within an PrimaryConstructorBaseTypeSyntax.
bernd5 marked this conversation as resolved.
Show resolved Hide resolved
/// </summary>
/// <param name="primaryConstructorBaseType">The primary constructor base type within the associated SyntaxTree to analyze.</param>
bernd5 marked this conversation as resolved.
Show resolved Hide resolved
/// <returns>An object that can be used to obtain the result of the data flow analysis.</returns>
public virtual DataFlowAnalysis AnalyzeDataFlow(PrimaryConstructorBaseTypeSyntax primaryConstructorBaseType)
{
// Only supported on a SyntaxTreeSemanticModel.
throw new NotSupportedException();
}

/// <summary>
/// Analyze data-flow within an expression.
/// </summary>
Expand Down Expand Up @@ -5245,8 +5267,12 @@ protected sealed override DataFlowAnalysis AnalyzeDataFlowCore(SyntaxNode statem
return this.AnalyzeDataFlow(statementSyntax);
case ExpressionSyntax expressionSyntax:
return this.AnalyzeDataFlow(expressionSyntax);
case ConstructorInitializerSyntax constructorInitializer:
return this.AnalyzeDataFlow(constructorInitializer);
case PrimaryConstructorBaseTypeSyntax primaryConstructorBaseType:
return this.AnalyzeDataFlow(primaryConstructorBaseType);
default:
throw new ArgumentException("statementOrExpression is not a StatementSyntax or an ExpressionSyntax.");
throw new ArgumentException("statementOrExpression is not a StatementSyntax or an ExpressionSyntax or a ConstructorInitializerSyntax or a PrimaryConstructorBaseTypeSyntax.");
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2264,6 +2264,40 @@ public override DataFlowAnalysis AnalyzeDataFlow(ExpressionSyntax expression)
return result;
}

public override DataFlowAnalysis AnalyzeDataFlow(ConstructorInitializerSyntax constructorInitializer)
{
if (constructorInitializer == null)
{
throw new ArgumentNullException(nameof(constructorInitializer));
}

if (!IsInTree(constructorInitializer))
{
throw new ArgumentException("expression not within tree");
bernd5 marked this conversation as resolved.
Show resolved Hide resolved
}

var context = RegionAnalysisContext(constructorInitializer);
var result = new CSharpDataFlowAnalysis(context);
return result;
}

public override DataFlowAnalysis AnalyzeDataFlow(PrimaryConstructorBaseTypeSyntax primaryConstructorBaseType)
{
if (primaryConstructorBaseType == null)
{
throw new ArgumentNullException(nameof(primaryConstructorBaseType));
}

if (!IsInTree(primaryConstructorBaseType))
{
throw new ArgumentException("expression not within tree");
333fred marked this conversation as resolved.
Show resolved Hide resolved
}

var context = RegionAnalysisContext(primaryConstructorBaseType);
var result = new CSharpDataFlowAnalysis(context);
return result;
}

public override DataFlowAnalysis AnalyzeDataFlow(StatementSyntax firstStatement, StatementSyntax lastStatement)
{
ValidateStatementRange(firstStatement, lastStatement);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ private RegionAnalysisContext RegionAnalysisContext(ExpressionSyntax expression)
while (expression.Kind() == SyntaxKind.ParenthesizedExpression)
expression = ((ParenthesizedExpressionSyntax)expression).Expression;

return RegionAnalysisContext<ExpressionSyntax>(expression);
}

private RegionAnalysisContext RegionAnalysisContext<T>(T expression)
bernd5 marked this conversation as resolved.
Show resolved Hide resolved
where T : CSharpSyntaxNode
{
var memberModel = GetMemberModel(expression);
if (memberModel == null)
{
Expand Down
2 changes: 2 additions & 0 deletions src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ override Microsoft.CodeAnalysis.CSharp.Syntax.FileScopedNamespaceDeclarationSynt
override Microsoft.CodeAnalysis.CSharp.Syntax.FileScopedNamespaceDeclarationSyntax.Name.get -> Microsoft.CodeAnalysis.CSharp.Syntax.NameSyntax
override Microsoft.CodeAnalysis.CSharp.Syntax.FileScopedNamespaceDeclarationSyntax.NamespaceKeyword.get -> Microsoft.CodeAnalysis.SyntaxToken
override Microsoft.CodeAnalysis.CSharp.Syntax.FileScopedNamespaceDeclarationSyntax.Usings.get -> Microsoft.CodeAnalysis.SyntaxList<Microsoft.CodeAnalysis.CSharp.Syntax.UsingDirectiveSyntax>
static Microsoft.CodeAnalysis.CSharp.CSharpExtensions.AnalyzeDataFlow(this Microsoft.CodeAnalysis.SemanticModel semanticModel, Microsoft.CodeAnalysis.CSharp.Syntax.ConstructorInitializerSyntax constructorInitializer) -> Microsoft.CodeAnalysis.DataFlowAnalysis
static Microsoft.CodeAnalysis.CSharp.CSharpExtensions.AnalyzeDataFlow(this Microsoft.CodeAnalysis.SemanticModel semanticModel, Microsoft.CodeAnalysis.CSharp.Syntax.PrimaryConstructorBaseTypeSyntax primaryConstructorBaseType) -> Microsoft.CodeAnalysis.DataFlowAnalysis
static Microsoft.CodeAnalysis.CSharp.CSharpExtensions.GetDeclaredSymbol(this Microsoft.CodeAnalysis.SemanticModel semanticModel, Microsoft.CodeAnalysis.CSharp.Syntax.FileScopedNamespaceDeclarationSyntax declarationSyntax, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> Microsoft.CodeAnalysis.INamespaceSymbol
static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.FileScopedNamespaceDeclaration(Microsoft.CodeAnalysis.CSharp.Syntax.NameSyntax name) -> Microsoft.CodeAnalysis.CSharp.Syntax.FileScopedNamespaceDeclarationSyntax
static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.FileScopedNamespaceDeclaration(Microsoft.CodeAnalysis.SyntaxList<Microsoft.CodeAnalysis.CSharp.Syntax.AttributeListSyntax> attributeLists, Microsoft.CodeAnalysis.SyntaxTokenList modifiers, Microsoft.CodeAnalysis.CSharp.Syntax.NameSyntax name, Microsoft.CodeAnalysis.SyntaxList<Microsoft.CodeAnalysis.CSharp.Syntax.ExternAliasDirectiveSyntax> externs, Microsoft.CodeAnalysis.SyntaxList<Microsoft.CodeAnalysis.CSharp.Syntax.UsingDirectiveSyntax> usings, Microsoft.CodeAnalysis.SyntaxList<Microsoft.CodeAnalysis.CSharp.Syntax.MemberDeclarationSyntax> members) -> Microsoft.CodeAnalysis.CSharp.Syntax.FileScopedNamespaceDeclarationSyntax
Expand Down
52 changes: 52 additions & 0 deletions src/Compilers/CSharp/Test/Semantic/FlowAnalysis/FlowTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,16 @@ protected DataFlowAnalysis CompileAndAnalyzeDataFlowExpression(string program, p
return CompileAndGetModelAndExpression(program, (model, expression) => model.AnalyzeDataFlow(expression), references);
}

protected DataFlowAnalysis CompileAndAnalyzeDataFlowConstructorInitializer(string program, params MetadataReference[] references)
{
return CompileAndGetModelAndConstructorInitializer(program, (model, constructorInitializer) => model.AnalyzeDataFlow(constructorInitializer), references);
}

protected DataFlowAnalysis CompileAndAnalyzeDataFlowPrimaryConstructorInitializer(string program, params MetadataReference[] references)
{
return CompileAndGetModelAndPrimaryConstructorInitializer(program, (model, primaryConstructorInitializer) => model.AnalyzeDataFlow(primaryConstructorInitializer), references);
}

protected DataFlowAnalysis CompileAndAnalyzeDataFlowStatements(string program)
{
return CompileAndGetModelAndStatements(program, (model, stmt1, stmt2) => model.AnalyzeDataFlow(stmt1, stmt2));
Expand All @@ -93,6 +103,48 @@ protected DataFlowAnalysis CompileAndAnalyzeDataFlowStatements(string program)
return CompileAndGetModelAndStatements(program, (model, stmt1, stmt2) => (model.AnalyzeControlFlow(stmt1, stmt2), model.AnalyzeDataFlow(stmt1, stmt2)));
}

protected T CompileAndGetModelAndConstructorInitializer<T>(string program, Func<SemanticModel, ConstructorInitializerSyntax, T> analysisDelegate, params MetadataReference[] references)
{
var comp = CreateCompilation(program, parseOptions: TestOptions.RegularPreview, references: references);
var tree = comp.SyntaxTrees[0];
var model = comp.GetSemanticModel(tree);
int start = program.IndexOf(StartString, StringComparison.Ordinal) + StartString.Length;
int end = program.IndexOf(EndString, StringComparison.Ordinal);
ConstructorInitializerSyntax syntaxToBind = null;
foreach (var expr in GetSyntaxNodeList(tree).OfType<ConstructorInitializerSyntax>())
{
if (expr.SpanStart >= start && expr.Span.End <= end)
{
syntaxToBind = expr;
break;
}
}

Assert.NotNull(syntaxToBind);
return analysisDelegate(model, syntaxToBind);
}

protected T CompileAndGetModelAndPrimaryConstructorInitializer<T>(string program, Func<SemanticModel, PrimaryConstructorBaseTypeSyntax, T> analysisDelegate, params MetadataReference[] references)
{
var comp = CreateCompilation(program, parseOptions: TestOptions.RegularPreview, references: references);
var tree = comp.SyntaxTrees[0];
var model = comp.GetSemanticModel(tree);
int start = program.IndexOf(StartString, StringComparison.Ordinal) + StartString.Length;
int end = program.IndexOf(EndString, StringComparison.Ordinal);
PrimaryConstructorBaseTypeSyntax syntaxToBind = null;
foreach (var expr in GetSyntaxNodeList(tree).OfType<PrimaryConstructorBaseTypeSyntax>())
{
if (expr.SpanStart >= start && expr.Span.End <= end)
{
syntaxToBind = expr;
break;
}
}

Assert.NotNull(syntaxToBind);
return analysisDelegate(model, syntaxToBind);
}

protected T CompileAndGetModelAndExpression<T>(string program, Func<SemanticModel, ExpressionSyntax, T> analysisDelegate, params MetadataReference[] references)
{
var comp = CreateCompilation(program, parseOptions: TestOptions.RegularPreview, references: references);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2618,6 +2618,202 @@ public static void M(C? c)

#endregion

#region "constructor initalizer"
[Fact]
public void TestDataFlowsInCtorInitPublicApi()
{
var program = @"
class C
{
C(int x)
{}

C(int x, int y) /*<bind>*/ : this(x + y) /*</bind>*/
{}
}
";
var analysis = CompileAndGetModelAndConstructorInitializer(program,
(model, constructorInitializer) => CSharpExtensions.AnalyzeDataFlow(model, constructorInitializer)
);

Assert.Equal("x, y", GetSymbolNamesJoined(analysis.DataFlowsIn));
}

[Fact]
public void TestDataFlowsInCtorInitPublicApi2()
{
var program = @"
class C
{
C(int x)
{}

C(int x, int y) /*<bind>*/ : this(x + y) /*</bind>*/
{}
}
";
var analysis = CompileAndGetModelAndConstructorInitializer(program,
(model, constructorInitializer) => global::Microsoft.CodeAnalysis.ModelExtensions.AnalyzeDataFlow(model, constructorInitializer)
);

Assert.Equal("x, y", GetSymbolNamesJoined(analysis.DataFlowsIn));
}

[Fact]
public void TestDataFlowsInCtorInit()
{
var analysis = CompileAndAnalyzeDataFlowConstructorInitializer(@"
class C
{
C(int x)
{}

C(int x, int y) /*<bind>*/ : this(x + y) /*</bind>*/
{}
}");
Assert.Equal("x, y", GetSymbolNamesJoined(analysis.DataFlowsIn));
}

[Fact]
public void TestDataFlowsInCtorInitVariablesDeclared()
{
var analysis = CompileAndAnalyzeDataFlowConstructorInitializer(@"
class C
{
C(int x)
{}

C(int x)
{}

C(int x, int y) /*<bind>*/ : this((x++ + y is var b) switch { _ => b * 2}) /*</bind>*/
{}
}");
Assert.Equal("x, y", GetSymbolNamesJoined(analysis.DataFlowsIn));
Assert.Equal("x, b", GetSymbolNamesJoined(analysis.WrittenInside));
Assert.Equal("b", GetSymbolNamesJoined(analysis.VariablesDeclared));
}

[Fact]
public void TestDataFlowsInCtorInitComplex()
{
var analysis = CompileAndAnalyzeDataFlowConstructorInitializer(@"
public class BaseX
{
public BaseX(out int ix, ref string i, in int s, in int s2, out int rrr)
{
}
}

public class X : BaseX
{
public X(int r, out int ix, out int x, ref int i)/*<bind>*/ :
base(out ix, ref CalcValue(""ctor"", out x), CalcInt(""int"", out var y) + CalcInt(""int2"", out var y2),
x + y + r + y2 + i++, out var rrr)
/*</bind>*/
{
Console.WriteLine(y);
}

static string s;
static ref string CalcValue(string text, out int i)
{
Console.WriteLine($""CalcInt({text})"");
i = 42;
return ref s;
}

static int CalcInt(string text, out int i)
{
Console.WriteLine($""CalcInt({text})"");
i = 42;
return i;
}
}");
Assert.Equal("r, i", GetSymbolNamesJoined(analysis.DataFlowsIn));
Assert.Equal("ix, x, i, y", GetSymbolNamesJoined(analysis.DataFlowsOut));
Assert.Equal("r, x, i, y, y2", GetSymbolNamesJoined(analysis.ReadInside));
Assert.Equal("ix, x, i, y", GetSymbolNamesJoined(analysis.ReadOutside));
Assert.Equal("ix, x, i, y, y2, rrr", GetSymbolNamesJoined(analysis.WrittenInside));
Assert.Equal("ix, x, i, y, y2, rrr", GetSymbolNamesJoined(analysis.AlwaysAssigned));
Assert.Equal("this, r, i", GetSymbolNamesJoined(analysis.WrittenOutside));
Assert.Equal("y, y2, rrr", GetSymbolNamesJoined(analysis.VariablesDeclared));
}

[Fact]
public void TestDataFlowsInCtorInitWrite()
{
var analysis = CompileAndAnalyzeDataFlowConstructorInitializer(@"
class C
{
C(int x)
{}

C(int x, int y) /*<bind>*/ : this(x++ + y) /*</bind>*/
{}
}");
Assert.Equal("x, y", GetSymbolNamesJoined(analysis.DataFlowsIn));
Assert.Equal("x", GetSymbolNamesJoined(analysis.WrittenInside));
}
#endregion

#region "primary constructor initalizer"

[Fact]
public void TestDataFlowsInPrimaryCtorInitPublicApi()
{
var program = @"
record Base(int x)

record C(int x, int y) /*<bind>*/ : Base(x + y) /*</bind>*/;
";
var analysis = CompileAndGetModelAndPrimaryConstructorInitializer(program,
(model, primaryConstructorInitializer) => CSharpExtensions.AnalyzeDataFlow(model, primaryConstructorInitializer)
);

Assert.Equal("x, y", GetSymbolNamesJoined(analysis.DataFlowsIn));
}

[Fact]
public void TestDataFlowsInPrimaryCtorInitPublicApi2()
{
var program = @"
record Base(int x)

record C(int x, int y) /*<bind>*/ : Base(x + y) /*</bind>*/;
";
var analysis = CompileAndGetModelAndPrimaryConstructorInitializer(program,
(model, primaryConstructorInitializer) => global::Microsoft.CodeAnalysis.ModelExtensions.AnalyzeDataFlow(model, primaryConstructorInitializer)
);

Assert.Equal("x, y", GetSymbolNamesJoined(analysis.DataFlowsIn));
}

[Fact]
public void TestDataFlowsInPrimaryCtorInit()
{
var analysis = CompileAndAnalyzeDataFlowPrimaryConstructorInitializer(@"
record Base(int x)

record C(int x, int y) /*<bind>*/ : Base(x + y) /*</bind>*/;
");
Assert.Equal("x, y", GetSymbolNamesJoined(analysis.DataFlowsIn));
}

[Fact]
public void TestDataFlowsInPrimaryCtorInitVariablesDeclared()
{
var analysis = CompileAndAnalyzeDataFlowPrimaryConstructorInitializer(@"
record Base(int x);

record C(int x, int y) /*<bind>*/ : Base((x++ + y is var b) switch { _ => b * 2}) /*</bind>*/;
");
Assert.Equal("x, y", GetSymbolNamesJoined(analysis.DataFlowsIn));
Assert.Equal("x, b", GetSymbolNamesJoined(analysis.WrittenInside));
Assert.Equal("b", GetSymbolNamesJoined(analysis.VariablesDeclared));
}
#endregion

#region "Statements"

[Fact]
Expand Down
Loading