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

Display Class used when not required + related flow analysis #76573

Open
CortiWins opened this issue Dec 27, 2024 · 1 comment
Open

Display Class used when not required + related flow analysis #76573

CortiWins opened this issue Dec 27, 2024 · 1 comment
Labels
Area-Compilers untriaged Issues and PRs which have not yet been triaged by a lead

Comments

@CortiWins
Copy link

Version Used:
VisualStudio.17.Release/17.12.3+35527.113
Microsoft .NET Framework
Version 4.8.09037
C# Tools 4.12.0-3.24572.7+dfa7fc6bdea31a858a402168384192b633c811fa

There are two (related?) issues

  1. unexpected created code for lambda containing this
  2. unexpected result of SemanticModel.AnalyzeDataFlow

Description
I was wondering why c# creates a display class to capture this when it could just not do it.
My proposal for that (dotnet/csharplang#8914) was met with a comment claiming it already does what i wished for. That turned out to be true, sometimes. The compiler uses a display class for a lambda that does not need it, if the same scope also contains a lambda that needs it. That is issue 1).Example code is provided below as sharplab.io-links.

I encountered issue 2) when i tried to update a ClrHeap-Analyzer (https://github.com/sonnemaf/RoslynClrHeapAllocationAnalyzer) to not show warnings for cases when no display class is created. The source of the warnings from that analyzer is, that SemanticModel.AnalyzeDataFlow claims that a ParenthesizedLambdaExpressionSyntax with no local capture requires one even if it does not.
An example is provided as an uploaded solution.

Issue 1: Code Examples Display Class Creation

Example 1: if both the lamba and the local function only require the capture of this, they are created as member method and no display_class is created.

https://sharplab.io/#v2:EYLgxg9gTgpgtADwGwBYA+ABATARgLABQGADAAQY4oDchJ5OAdADICWAdgI41FkUMAqMBABdudCgFZuhNgEMAtjADOAB1lgYpAMIQ2SiABsYAQRUr8BAN6FSt0u2Ewocg+Sz0A7DbvWCd/6QwAG4wbML07gDybACiIWEAErJmoewA5twBdt5ZGCikALIw8sBOGBLCLLoAFADKwlDppLJQaQCUOQG+Wbk4AJzVACQARACqSsqkalAKpJYtaQC+zWwAJvZ6wrJsGqSrsltzwgAWLEoCEPWNbGnVbYvDbdJ+PYuEnf4A9J+kMQgKKiMpBwIFIADkIHszoDZABPNykMCwA4wVYMD52PKkRxKYR3DG2bo9L4/ABqTiUVTYwNBTAUwFk2OOB1IsA4AFcWLAlEyzgAaUjAdnhMDbQWaVa6TQAdxYJwgwqhqgMcLcBICJzODGicVCwiSKTYTQA1ABeUh3UimgB86v8ROJWU15yKJTKFSp1WGtQgik1N0CBgmpGZhtRAEJHplHbZFtGY+8XjHvqRyVBKbpSFhQQARCCTNgQcJszmwZpKmHwgBm7J2lV06KTjud2ti8X1yRUqQDZtITAgooM5XrbHjjqx/cHw89HSbxIdMbsLddpSg05q3t9MH9aRDndCEajdrsbznJ86p9PQA

Example 2: If one lambda requires capture of a stack variable through a display_class, even the cases where the display_class is not required are created as members of the display_class

https://sharplab.io/#v2:EYLgxg9gTgpgtADwGwBYA+ABATARgLABQGADAAQY4oDchJ5OAdADICWAdgI41FkUMAqMBABdudCgFZuhNgEMAtjADOAB1lgYpAMIQ2SiABsYAQRUr8BAN6FSt0u2Ewocg+Sz0A7DbvWCd/6QwAG4wbML07gDybACiIWEAErJmoewA5twBdt5ZGCikALIw8sBOGBLCLLoAFADKwlDppLJQaQCUOQG+Wbk4AJzVACQARACqSsqkalAKpJYtaQC+zWwAJvZ6wrJsGqSrsltzwgAWLEoCEPWNbGnVbYvDbdJ+PYuEnf4A9J+kMQgKKiMpCwIFIABEzoDZABPNykMCwA4wdbBUKkABm0FIJ006IArjtKrolHsIKQ2BBwoiDAZYWwYMj7MIAPwfOx5bHKYR3Nm2bo9L4/ABqTiUVTYpBwoKYCmAsmxxwOpFgHDxLFgJJOZwANKRgHjwpA8QZ1qVSfTSAB3FgnCAGvaQgwwty8gJa87ROKhYRJFJsJoAagAvKQ7qQgwA+V3+fkCrLuhhFEplCri6rDWoQRRam6BAwTUiKv3IgCEj0ycdsiwrlfeL0r31IIqgYt0wNBYIgkwpVJgqvVmnlq0dzvxhPFDGjdgTnviPuSKlSueDpCYEDAsgM5SJbBrcY5a43W9TNQ69bjscr09O5yTpSg27TGazMBzaULC9CpfLU6rv9I/6Ns2rYSgAzB2XYkiqaqwAqmgbiowh4rBEDoqQShbGAADWpBBC0LCyMARiAT82zrCceJQX2MGTEOI6wtgGJYji8IGBASjITA/4OM04akFI/4zrEc6+ou/rLiGYaRv+l5XrYCZ3imO7ppm2anLmIRQLCMD5poRZid+pABs0FxXOkdxPP+1aum8562bZQA=

Issue 2: Project that shows result of SemanticModel.AnalyzeDataFlow
RoslynFlowCaptureTest.zip

Sourcecode:

// Built 27.12.2024
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;

string code = @"using System;

private sealed class ThisCaptureTwo
{
    void TestFunction(int parameter)
    {
        // Make sure the lambda that does not require capturing is in its own scope
        {
            var action1 = new System.Action(() =>
            {
                this.TestFunction(66);
            });
        }

        // Make sure the lambda that DOES require capturing is in its own scope
        {
            int localVariable = 5;
            var action2 = new System.Action(() =>
            {
                this.TestFunction(localVariable);
            });
        }
    }
}
";

SyntaxTree tree = CSharpSyntaxTree.ParseText(code);

// Create a Compilation
var compilation = CSharpCompilation.Create("MyCompilation",
    syntaxTrees: [tree],
    references: [MetadataReference.CreateFromFile(typeof(object).Assembly.Location)]);

// Get the Semantic Model
var semanticModel = compilation.GetSemanticModel(tree);

SyntaxNode root = tree.GetRoot();
var lambdaExpressionSyntaxes = root.DescendantNodes().OfType<ParenthesizedLambdaExpressionSyntax>().ToList();

ParenthesizedLambdaExpressionSyntax lambdaexpNoVariable = lambdaExpressionSyntaxes[0];
var dataFlowAnalysis1 = semanticModel.AnalyzeDataFlow(lambdaexpNoVariable);

Console.WriteLine("-- CODE -------------------------------------------------");
Console.WriteLine(code);

Console.WriteLine("-- LAMBDA -------------------------------------------------");
Console.WriteLine(lambdaexpNoVariable.ToFullString());
Console.WriteLine("DataFlow Analysis Captured: " + dataFlowAnalysis1.Captured.Length);
for (int i = 0; i < dataFlowAnalysis1.Captured.Length; i++)
{
    Console.WriteLine($"{i} {dataFlowAnalysis1.Captured[i].Name}");
}

ParenthesizedLambdaExpressionSyntax lambdaexpWithLocalVariable = lambdaExpressionSyntaxes[1];
var dataFlowAnalysis2 = semanticModel.AnalyzeDataFlow(lambdaexpWithLocalVariable);

Console.WriteLine("-- LAMBDA -------------------------------------------------");
Console.WriteLine(lambdaexpWithLocalVariable.ToFullString());
Console.WriteLine("DataFlow Analysis Captured: " + dataFlowAnalysis2.Captured.Length);
for (int i = 0; i < dataFlowAnalysis2.Captured.Length; i++)
{
    Console.WriteLine($"{i} {dataFlowAnalysis2.Captured[i].Name}");
}

Console.ReadKey(true);

Console Output:

-- LAMBDA -------------------------------------------------
() =>
            {
                this.TestFunction(66);
            }
DataFlow Analysis Captured: 2
0 this
1 localVariable
-- LAMBDA -------------------------------------------------
() =>
            {
                this.TestFunction(localVariable);
            }
DataFlow Analysis Captured: 2
0 this
1 localVariable
@dotnet-issue-labeler dotnet-issue-labeler bot added Area-Compilers untriaged Issues and PRs which have not yet been triaged by a lead labels Dec 27, 2024
@CyrusNajmabadi
Copy link
Member

I believe @jaredpar has covered this in the past. Precise tracking has pros/cons. So we've opted for a simpler model which results in simpler display class layout and allocations, at the cost of this sort of thing happening in some cases.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-Compilers untriaged Issues and PRs which have not yet been triaged by a lead
Projects
None yet
Development

No branches or pull requests

2 participants