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

ExtractMethodMatrix.GetVariableStyle Throws exception on code with errors #17165

Closed
jmarolf opened this issue Feb 15, 2017 · 7 comments · Fixed by #20004
Closed

ExtractMethodMatrix.GetVariableStyle Throws exception on code with errors #17165

jmarolf opened this issue Feb 15, 2017 · 7 comments · Fixed by #20004
Assignees
Labels
Area-IDE Bug Resolution-Fixed The bug has been fixed and/or the requested behavior has been implemented
Milestone

Comments

@jmarolf
Copy link
Contributor

jmarolf commented Feb 15, 2017

Version Used:
image

Steps to Reproduce:

  1. Use Alt+Arrow to move members into lambda that is an argument into a method that is missing semicolon

Expected Behavior:
No Crash

Actual Behavior:

System.InvalidOperationException : Unexpected false
   at Roslyn.Utilities.Contract.ThrowIfFalse(Boolean condition,String message)
   at Microsoft.CodeAnalysis.ExtractMethod.ExtractMethodMatrix.GetVariableStyle(Boolean captured,Boolean dataFlowIn,Boolean dataFlowOut,Boolean alwaysAssigned,Boolean variableDeclared,Boolean readInside,Boolean writtenInside,Boolean readOutside,Boolean writtenOutside,Boolean unsafeAddressTaken)
   at Microsoft.CodeAnalysis.ExtractMethod.MethodExtractor.Analyzer.GetVariableStyle(Dictionary`2 symbolMap,ISymbol symbol,SemanticModel model,ITypeSymbol type,Boolean captured,Boolean dataFlowIn,Boolean dataFlowOut,Boolean alwaysAssigned,Boolean variableDeclared,Boolean readInside,Boolean writtenInside,Boolean readOutside,Boolean writtenOutside,Boolean unsafeAddressTaken)
   at Microsoft.CodeAnalysis.ExtractMethod.MethodExtractor.Analyzer.GenerateVariableInfoMap(SemanticModel model,DataFlowAnalysis dataFlowAnalysisData,Dictionary`2 symbolMap)
   at async Microsoft.CodeAnalysis.ExtractMethod.MethodExtractor.Analyzer.AnalyzeAsync(<Unknown Parameters>)
   at async Microsoft.CodeAnalysis.ExtractMethod.MethodExtractor.ExtractMethodAsync(<Unknown Parameters>)
   at async Microsoft.CodeAnalysis.ExtractMethod.AbstractExtractMethodService`3.ExtractMethodAsync[TValidator,TExtractor,TResult](<Unknown Parameters>)
   at async Microsoft.CodeAnalysis.CodeRefactorings.ExtractMethod.ExtractMethodCodeRefactoringProvider.GetCodeActionAsync(<Unknown Parameters>)
   at async Microsoft.CodeAnalysis.CodeRefactorings.ExtractMethod.ExtractMethodCodeRefactoringProvider.ComputeRefactoringsAsync(<Unknown Parameters>)
   at async Microsoft.CodeAnalysis.CodeRefactorings.CodeRefactoringService.GetRefactoringFromProviderAsync(<Unknown Parameters>)
   at Microsoft.VisualStudio.Telemetry.WindowsErrorReporting.WatsonReport.GetClrWatsonExceptionInfo(Exception exceptionObject)

Also reported at https://developercommunity.visualstudio.com/content/problem/14869/extractmethodcoderefactoringprovider-encountered-a.html

@Pilchie Pilchie added the Bug label Feb 21, 2017
@Pilchie Pilchie added this to the 2.1 milestone Feb 21, 2017
@Pilchie
Copy link
Member

Pilchie commented Feb 21, 2017

Do you have a repro or dump?

@tmat
Copy link
Member

tmat commented Feb 23, 2017

I've got a dump and a repro solution: %internal_share%\public\tomat\Bugs\17165

@tmat
Copy link
Member

tmat commented Feb 23, 2017

Repro: sync to branch https://github.com/tmat/symreader-converter/tree/ExtractMethodCrashRepro
Run restore.cmd
Open src\Microsoft.DiaSymReader.Converter\PdbConverterPortableToWindows.cs
Select code

                        foreach (var localConstantHandle in currentLocalScope.GetLocalConstants())
                        {
                            var constant = pdbReader.GetLocalConstant(localConstantHandle);
                            string name = pdbReader.GetString(constant.Name);

                            if (name.Length > MaxEntityNameLength)
                            {
                                // TODO: report warning
                                continue;
                            }

                            var (value, signature) = PortableConstantSignature.GetConstantValueAndSignature(pdbReader, localConstantHandle, pdbWriter.MetadataImport);
                            if (!metadataModel.TryGetStandaloneSignatureHandle(signature, out var constantSignatureHandle))
                            {
                                // TODO: report warning

                                // TODO: 
                                // Currently the EEs require signature to match exactly the type of the value. 
                                // We could relax that and use the type of the value regardless of the signature for primitive types.
                                // Then we could use any signature here.
                                continue;
                            }

                            pdbWriter.DefineLocalConstant(name, value, MetadataTokens.GetToken(constantSignatureHandle));
                        }

                        foreach (var localVariableHandle in currentLocalScope.GetLocalVariables())
                        {
                            var variable = pdbReader.GetLocalVariable(localVariableHandle);
                            string name = pdbReader.GetString(variable.Name);

                            if (name.Length > MaxEntityNameLength)
                            {
                                // TODO: report warning
                                continue;
                            }

                            int localSignatureToken = methodBody.LocalSignature.IsNil ? 0 : MetadataTokens.GetToken(methodBody.LocalSignature);
                            pdbWriter.DefineLocalVariable(variable.Index, name, variable.Attributes, localSignatureToken);
                        }
}

@Pilchie
Copy link
Member

Pilchie commented Mar 31, 2017

Note the repro in #18347.

@Pilchie
Copy link
Member

Pilchie commented Apr 18, 2017

@sharwell sharwell added the 4 - In Review A fix for the issue is submitted for review. label Apr 24, 2017
agocke added a commit to agocke/roslyn that referenced this issue Jun 4, 2017
By design, when data flow analyzes local functions it does so devoid of
surrounding context and records possibly unassigned variables rather
than reporting a diagnostic. Thus, all captured variables are expected
to be marked unassigned during data flow analysis without reporting a
diagnostic.

However, reporting a diagnostic is not the only side effect of running
data flow analysis. By calling virtual methods data flow analysis
informs derived types of unassigned variables. While the diagnostic is
supressed and recorded, currently the virtual method is still called.
This has negative consequences for region analysis, which considers
these calls as indications of variables flowing out of the given
region, which may not be the case for local functions.

This PR changes data flow analysis to only call the ReportUnassigned
virtual method if not inside a local function, excluding local functions
from all unassignment reporting.

Fixes dotnet#17165, dotnet#18347
@davkean
Copy link
Member

davkean commented Jun 9, 2017

@sharwell @agocke I ran into this exact stack with the following (valid) code:

    public override IDependency BeforeAdd(
            string projectPath,
            ITargetFramework targetFramework,
            IDependency dependency, 
            ImmutableDictionary<string, IDependency>.Builder worldBuilder,
            ImmutableHashSet<IDependency>.Builder topLevelBuilder,
            Dictionary<string, IProjectDependenciesSubTreeProvider> subTreeProviders,
            HashSet<string> projectItemSpecs,
            out bool filterAnyChanges)
        {
            filterAnyChanges = false;
            IDependency resultDependency = dependency;

            if (!resultDependency.TopLevel
                || resultDependency.Implicit                
                || !resultDependency.Resolved
                || !resultDependency.Flags.Contains(DependencyTreeFlags.GenericDependencyFlags))
            {
                return resultDependency;
            }

$          if (projectItemSpecs == null)   // No data
                return resultDependency;

            if$ (!projectItemSpecs.Contains(resultDependency.OriginalItemSpec))
            {
                // it is an implicit dependency
                if (!subTreeProviders.TryGetValue(resultDependency.ProviderType, out IProjectDependenciesSubTreeProvider provider))
                {
                    return resultDependency;
                }

                var internalProvider = provider as IProjectDependenciesSubTreeProviderInternal;
                if (internalProvider == null)
                {
                    return resultDependency;
                }

                resultDependency = resultDependency.SetProperties(
                    icon: internalProvider.GetImplicitIcon(),
                    isImplicit: true);
                filterAnyChanges = true;
            }

            return resultDependency;
        }

Changing the selection to the area indicated by the $, causes the crash. Will this be resolved by the changes around this?

@lukaseckert
Copy link

I also experience this issue with valid code. As soon as I have local functions within my outer method, the ExtractMethodCodeRefactoringProvider crashes and VS2017 shows the yellow notification bar that asks me if I want to enable it again.
Consider that piece of code:

 private ObjectChart RenderTreeMapChart(ChartProjectLayout layout, TreeMapChartDefinition chartDef, CollectionObjectBases objectList)
        {
            //// Formula to display caption and numeric value of a node in the treemap 
            //const string labelContent_NodeNameAndNodeValue = "LL.ChartObject.ParentNodeText0 + chr$(13) + \" (\" + CStr$(LL.ChartObject.NodeSum,\"%g\") + \")\"";  // show data row title + node value in the square

            //// Local function to set maximum node count for an axis
            //void ApplyMaxEntryCount(PropertyMaxEntryCount prop)
            //{
            //    if (chartDef.TopNLimit > 0)
            //    {
            //        prop.MaxEntries = chartDef.TopNLimit.ToString();
            //        prop.WithOthers = "True";
            //        prop.Label = _utils.TextToFormula(Resources.General_OtherValues);
            //    }
            //}

            //// Local function to disable the regular axis label (drawn over top of a node) and set a label in the center of each node that contains the caption+node value, like "Germany (12)"
            //void SetLabelForNodes(CoordinateLabelPlacement axisLabel, PropertyChartLabelCoordinateOnObject nodeLabel)
            //{
            //    axisLabel.Placement = "0";  // no label on axis
            //    nodeLabel.Contents = labelContent_NodeNameAndNodeValue;
            //    nodeLabel.Placement = "3"; // center
            //    nodeLabel.Font.Size = "36";
            //}

            ...

When I uncomment those local functions at the begin of the outer method, selecting any piece of text in the rest of the method will lead to the crash.

agocke added a commit that referenced this issue Jun 15, 2017
By design, when data flow analyzes local functions it does so devoid of
surrounding context and records possibly unassigned variables rather
than reporting a diagnostic. Thus, all captured variables are expected
to be marked unassigned during data flow analysis without reporting a
diagnostic.

However, reporting a diagnostic is not the only side effect of running
data flow analysis. By calling virtual methods data flow analysis
informs derived types of unassigned variables. While the diagnostic is
suppressed and recorded, currently the virtual method is still called.
This has negative consequences for region analysis, which considers
these calls as indications of variables flowing out of the given
region, which may not be the case for local functions.

This PR changes data flow analysis to only call the ReportUnassigned
virtual method if not inside a local function, excluding local functions
from all unassignment reporting.

Fixes #17165, #18347
@sharwell sharwell added Resolution-Fixed The bug has been fixed and/or the requested behavior has been implemented and removed 4 - In Review A fix for the issue is submitted for review. labels Jul 12, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-IDE Bug Resolution-Fixed The bug has been fixed and/or the requested behavior has been implemented
Projects
None yet
Development

Successfully merging a pull request may close this issue.

8 participants