public int AssignLocalOrdinal(SynthesizedLocalKind localKind, int syntaxOffset) { #if !DEBUG // Optimization (avoid growing the dictionary below): // User-defined locals have to have a distinct syntax offset, thus ordinal is always 0. if (localKind == SynthesizedLocalKind.UserDefined) { return 0; } #endif int ordinal; long key = MakeKey(localKind, syntaxOffset); // Group by syntax offset and kind. // Variables associated with the same syntax and kind will be assigned different ordinals. if (_lazyMap == null) { _lazyMap = PooledDictionary<long, int>.GetInstance(); ordinal = 0; } else if (!_lazyMap.TryGetValue(key, out ordinal)) { ordinal = 0; } _lazyMap[key] = ordinal + 1; Debug.Assert(ordinal == 0 || localKind != SynthesizedLocalKind.UserDefined); return ordinal; }
public void Free() { if (_lazyMap != null) { _lazyMap.Free(); _lazyMap = null; } }
public static DiagnosticAnalysisResult CreateFromBuild(Project project, ImmutableArray <DiagnosticData> diagnostics) { // we can't distinguish locals and non locals from build diagnostics nor determine right snapshot version for the build. // so we put everything in as semantic local with default version. this lets us to replace those to live diagnostics when needed easily. var version = VersionStamp.Default; var documentIds = ImmutableHashSet.CreateBuilder <DocumentId>(); var diagnosticsWithDocumentId = PooledDictionary <DocumentId, ArrayBuilder <DiagnosticData> > .GetInstance(); var diagnosticsWithoutDocumentId = ArrayBuilder <DiagnosticData> .GetInstance(); foreach (var data in diagnostics) { var documentId = data.DocumentId; if (documentId != null) { documentIds.Add(documentId); diagnosticsWithDocumentId.MultiAdd(documentId, data); } else { diagnosticsWithoutDocumentId.Add(data); } } var result = new DiagnosticAnalysisResult( project.Id, version, documentIds: documentIds.ToImmutable(), syntaxLocals: ImmutableDictionary <DocumentId, ImmutableArray <DiagnosticData> > .Empty, semanticLocals: diagnosticsWithDocumentId.ToImmutableMultiDictionaryAndFree(), nonLocals: ImmutableDictionary <DocumentId, ImmutableArray <DiagnosticData> > .Empty, others: diagnosticsWithoutDocumentId.ToImmutableAndFree(), fromBuild: true); return(result); }
/// <summary> /// Runs dataflow analysis for the given <paramref name="analyzer"/> on the given <paramref name="controlFlowGraph"/>. /// </summary> /// <param name="controlFlowGraph">Control flow graph on which to execute analysis.</param> /// <param name="analyzer">Dataflow analyzer.</param> /// <returns>Block analysis data at the end of the exit block.</returns> /// <remarks> /// Algorithm for this CFG walker has been forked from <see cref="ControlFlowGraphBuilder"/>'s internal /// implementation for basic block reachability computation: "MarkReachableBlocks", /// we should keep them in sync as much as possible. /// </remarks> public static TBlockAnalysisData Run(ControlFlowGraph controlFlowGraph, DataFlowAnalyzer<TBlockAnalysisData> analyzer, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); var blocks = controlFlowGraph.Blocks; var continueDispatchAfterFinally = PooledDictionary<ControlFlowRegion, bool>.GetInstance(); var dispatchedExceptionsFromRegions = PooledHashSet<ControlFlowRegion>.GetInstance(); var firstBlockOrdinal = 0; var lastBlockOrdinal = blocks.Length - 1; var unreachableBlocksToVisit = ArrayBuilder<BasicBlock>.GetInstance(); if (analyzer.AnalyzeUnreachableBlocks) { for (var i = firstBlockOrdinal; i <= lastBlockOrdinal; i++) { if (!blocks[i].IsReachable) { unreachableBlocksToVisit.Add(blocks[i]); } } } var initialAnalysisData = analyzer.GetCurrentAnalysisData(blocks[0]); var result = RunCore(blocks, analyzer, firstBlockOrdinal, lastBlockOrdinal, initialAnalysisData, unreachableBlocksToVisit, outOfRangeBlocksToVisit: null, continueDispatchAfterFinally, dispatchedExceptionsFromRegions, cancellationToken); Debug.Assert(unreachableBlocksToVisit.Count == 0); unreachableBlocksToVisit.Free(); continueDispatchAfterFinally.Free(); dispatchedExceptionsFromRegions.Free(); return result; }
internal static void OrderAllDependencies( this SourceFieldSymbolWithSyntaxReference field, ArrayBuilder <FieldInfo> order, bool earlyDecodingWellKnownAttributes ) { Debug.Assert(order.Count == 0); var graph = PooledDictionary < SourceFieldSymbolWithSyntaxReference, Node <SourceFieldSymbolWithSyntaxReference> > .GetInstance(); CreateGraph(graph, field, earlyDecodingWellKnownAttributes); Debug.Assert(graph.Count >= 1); CheckGraph(graph); #if DEBUG var fields = ArrayBuilder <SourceFieldSymbolWithSyntaxReference> .GetInstance(); fields.AddRange(graph.Keys); #endif OrderGraph(graph, order); #if DEBUG // Verify all entries in the graph are in the ordered list. var map = new HashSet <SourceFieldSymbolWithSyntaxReference>( order.Select(o => o.Field).Distinct() ); Debug.Assert(fields.All(f => map.Contains(f))); fields.Free(); #endif graph.Free(); }
internal EEMethodSymbol( EENamedTypeSymbol container, string name, Location location, MethodSymbol sourceMethod, ImmutableArray <LocalSymbol> sourceLocals, ImmutableArray <LocalSymbol> sourceLocalsForBinding, ImmutableDictionary <string, DisplayClassVariable> sourceDisplayClassVariables, GenerateMethodBody generateMethodBody) { Debug.Assert(sourceMethod.IsDefinition); Debug.Assert(sourceMethod.ContainingSymbol == container.SubstitutedSourceType.OriginalDefinition); Debug.Assert(sourceLocals.All(l => l.ContainingSymbol == sourceMethod)); _container = container; _name = name; _locations = ImmutableArray.Create(location); // What we want is to map all original type parameters to the corresponding new type parameters // (since the old ones have the wrong owners). Unfortunately, we have a circular dependency: // 1) Each new type parameter requires the entire map in order to be able to construct its constraint list. // 2) The map cannot be constructed until all new type parameters exist. // Our solution is to pass each new type parameter a lazy reference to the type map. We then // initialize the map as soon as the new type parameters are available - and before they are // handed out - so that there is never a period where they can require the type map and find // it uninitialized. var sourceMethodTypeParameters = sourceMethod.TypeParameters; var allSourceTypeParameters = container.SourceTypeParameters.Concat(sourceMethodTypeParameters); var getTypeMap = new Func <TypeMap>(() => this.TypeMap); _typeParameters = sourceMethodTypeParameters.SelectAsArray( (tp, i, arg) => (TypeParameterSymbol) new EETypeParameterSymbol(this, tp, i, getTypeMap), (object)null); _allTypeParameters = container.TypeParameters.Concat(_typeParameters); this.TypeMap = new TypeMap(allSourceTypeParameters, _allTypeParameters); EENamedTypeSymbol.VerifyTypeParameters(this, _typeParameters); var substitutedSourceType = container.SubstitutedSourceType; this.SubstitutedSourceMethod = sourceMethod.AsMember(substitutedSourceType); if (sourceMethod.Arity > 0) { this.SubstitutedSourceMethod = this.SubstitutedSourceMethod.Construct(_typeParameters.As <TypeSymbol>()); } TypeParameterChecker.Check(this.SubstitutedSourceMethod, _allTypeParameters); // Create a map from original parameter to target parameter. var parameterBuilder = ArrayBuilder <ParameterSymbol> .GetInstance(); var substitutedSourceThisParameter = this.SubstitutedSourceMethod.ThisParameter; var substitutedSourceHasThisParameter = (object)substitutedSourceThisParameter != null; if (substitutedSourceHasThisParameter) { _thisParameter = MakeParameterSymbol(0, GeneratedNames.ThisProxyFieldName(), substitutedSourceThisParameter); Debug.Assert(_thisParameter.Type == this.SubstitutedSourceMethod.ContainingType); parameterBuilder.Add(_thisParameter); } var ordinalOffset = (substitutedSourceHasThisParameter ? 1 : 0); foreach (var substitutedSourceParameter in this.SubstitutedSourceMethod.Parameters) { var ordinal = substitutedSourceParameter.Ordinal + ordinalOffset; Debug.Assert(ordinal == parameterBuilder.Count); var parameter = MakeParameterSymbol(ordinal, substitutedSourceParameter.Name, substitutedSourceParameter); parameterBuilder.Add(parameter); } _parameters = parameterBuilder.ToImmutableAndFree(); var localsBuilder = ArrayBuilder <LocalSymbol> .GetInstance(); var localsMap = PooledDictionary <LocalSymbol, LocalSymbol> .GetInstance(); foreach (var sourceLocal in sourceLocals) { var local = sourceLocal.ToOtherMethod(this, this.TypeMap); localsMap.Add(sourceLocal, local); localsBuilder.Add(local); } this.Locals = localsBuilder.ToImmutableAndFree(); localsBuilder = ArrayBuilder <LocalSymbol> .GetInstance(); foreach (var sourceLocal in sourceLocalsForBinding) { LocalSymbol local; if (!localsMap.TryGetValue(sourceLocal, out local)) { local = sourceLocal.ToOtherMethod(this, this.TypeMap); localsMap.Add(sourceLocal, local); } localsBuilder.Add(local); } this.LocalsForBinding = localsBuilder.ToImmutableAndFree(); // Create a map from variable name to display class field. var displayClassVariables = PooledDictionary <string, DisplayClassVariable> .GetInstance(); foreach (var pair in sourceDisplayClassVariables) { var variable = pair.Value; var oldDisplayClassInstance = variable.DisplayClassInstance; // Note: we don't call ToOtherMethod in the local case because doing so would produce // a new LocalSymbol that would not be ReferenceEquals to the one in this.LocalsForBinding. var oldDisplayClassInstanceFromLocal = oldDisplayClassInstance as DisplayClassInstanceFromLocal; var newDisplayClassInstance = (oldDisplayClassInstanceFromLocal == null) ? oldDisplayClassInstance.ToOtherMethod(this, this.TypeMap) : new DisplayClassInstanceFromLocal((EELocalSymbol)localsMap[oldDisplayClassInstanceFromLocal.Local]); variable = variable.SubstituteFields(newDisplayClassInstance, this.TypeMap); displayClassVariables.Add(pair.Key, variable); } _displayClassVariables = displayClassVariables.ToImmutableDictionary(); displayClassVariables.Free(); localsMap.Free(); _generateMethodBody = generateMethodBody; }
#pragma warning restore public DataFlowAnalysisResultBuilder() { _info = PooledDictionary <BasicBlock, TAnalysisData?> .GetInstance(); }
private SpillSequenceSpiller(MethodSymbol method, SyntaxNode syntaxNode, TypeCompilationState compilationState, PooledDictionary <LocalSymbol, LocalSymbol> tempSubstitution, DiagnosticBag diagnostics) { _F = new SyntheticBoundNodeFactory(method, syntaxNode, compilationState, diagnostics); _F.CurrentFunction = method; _tempSubstitution = tempSubstitution; }
private AwaitExpressionSpiller(MethodSymbol method, SyntaxNode syntaxNode, TypeCompilationState compilationState, PooledDictionary <LocalSymbol, LocalSymbol> tempSubstitution, DiagnosticBag diagnostics) { _F = new SyntheticBoundNodeFactory(method, syntaxNode, compilationState, diagnostics); _tempSubstitution = tempSubstitution; }
public static Dictionary <K, ImmutableArray <V> > ToMultiDictionaryAndFree <K, V>(this PooledDictionary <K, ArrayBuilder <V> > builders) { var dictionary = new Dictionary <K, ImmutableArray <V> >(builders.Count); foreach (var(key, items) in builders) { dictionary.Add(key, items.ToImmutableAndFree()); } builders.Free(); return(dictionary); }
private FlowGraphAnalysisData( ControlFlowGraph controlFlowGraph, ISymbol owningSymbol, ImmutableArray <IParameterSymbol> parameters, PooledDictionary <BasicBlock, BasicBlockAnalysisData> analysisDataByBasicBlockMap, PooledDictionary <(ISymbol symbol, IOperation operation), bool> symbolsWriteMap,
public void Dictionary_Generic_ContainsValue_DefaultValueNotPresent(int count) { PooledDictionary <TKey, TValue> dictionary = (PooledDictionary <TKey, TValue>)GenericIDictionaryFactory(count); Assert.False(dictionary.ContainsValue(default));
public SyntaxAndDeclarationManager RemoveSyntaxTrees(HashSet <SyntaxTree> trees) { var state = _lazyState; var newExternalSyntaxTrees = this.ExternalSyntaxTrees.RemoveAll(t => trees.Contains(t)); if (state == null) { return(this.WithExternalSyntaxTrees(newExternalSyntaxTrees)); } var syntaxTrees = state.SyntaxTrees; var loadDirectiveMap = state.LoadDirectiveMap; var loadedSyntaxTreeMap = state.LoadedSyntaxTreeMap; var removeSet = PooledHashSet <SyntaxTree> .GetInstance(); foreach (var tree in trees) { int unused1; ImmutableArray <DeclarationLoadDirective> unused2; GetRemoveSet( tree, includeLoadedTrees: true, syntaxTrees: syntaxTrees, syntaxTreeOrdinalMap: state.OrdinalMap, loadDirectiveMap: loadDirectiveMap, loadedSyntaxTreeMap: loadedSyntaxTreeMap, removeSet: removeSet, totalReferencedTreeCount: out unused1, oldLoadDirectives: out unused2); } var treesBuilder = ArrayBuilder <SyntaxTree> .GetInstance(); var ordinalMapBuilder = PooledDictionary <SyntaxTree, int> .GetInstance(); var declMapBuilder = state.RootNamespaces.ToBuilder(); var declTable = state.DeclarationTable; foreach (var tree in syntaxTrees) { if (removeSet.Contains(tree)) { loadDirectiveMap = loadDirectiveMap.Remove(tree); loadedSyntaxTreeMap = loadedSyntaxTreeMap.Remove(tree.FilePath); RemoveSyntaxTreeFromDeclarationMapAndTable(tree, declMapBuilder, ref declTable); } else if (!IsLoadedSyntaxTree(tree, loadedSyntaxTreeMap)) { UpdateSyntaxTreesAndOrdinalMapOnly( treesBuilder, tree, ordinalMapBuilder, loadDirectiveMap, loadedSyntaxTreeMap); } } removeSet.Free(); state = new State( treesBuilder.ToImmutableAndFree(), ordinalMapBuilder.ToImmutableDictionaryAndFree(), loadDirectiveMap, loadedSyntaxTreeMap, declMapBuilder.ToImmutableDictionary(), declTable); return(new SyntaxAndDeclarationManager( newExternalSyntaxTrees, this.ScriptClassName, this.Resolver, this.Language, this.IsSubmission, state)); }
internal void SubstituteConstraintTypesDistinctWithoutModifiers( TypeParameterSymbol owner, ImmutableArray <TypeWithAnnotations> original, ArrayBuilder <TypeWithAnnotations> result, HashSet <TypeParameterSymbol> ignoreTypesDependentOnTypeParametersOpt ) { DynamicTypeEraser dynamicEraser = null; if (original.Length == 0) { return; } else if (original.Length == 1) { var type = original[0]; if ( ignoreTypesDependentOnTypeParametersOpt == null || !type.Type.ContainsTypeParameters(ignoreTypesDependentOnTypeParametersOpt) ) { result.Add(substituteConstraintType(type)); } } else { var map = PooledDictionary <TypeSymbol, int> .GetInstance(); foreach (var type in original) { if ( ignoreTypesDependentOnTypeParametersOpt == null || !type.Type.ContainsTypeParameters( ignoreTypesDependentOnTypeParametersOpt ) ) { var substituted = substituteConstraintType(type); if (!map.TryGetValue(substituted.Type, out int mergeWith)) { map.Add(substituted.Type, result.Count); result.Add(substituted); } else { result[mergeWith] = ConstraintsHelper.ConstraintWithMostSignificantNullability( result[mergeWith], substituted ); } } } map.Free(); } TypeWithAnnotations substituteConstraintType(TypeWithAnnotations type) { if (dynamicEraser == null) { dynamicEraser = new DynamicTypeEraser( owner.ContainingAssembly.CorLibrary.GetSpecialType( SpecialType.System_Object ) ); } TypeWithAnnotations substituted = SubstituteType(type); return(substituted.WithTypeAndModifiers( dynamicEraser.EraseDynamic(substituted.Type), substituted.CustomModifiers )); } }
public sealed override void Initialize(AnalysisContext context) { context.EnableConcurrentExecution(); context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); context.RegisterSymbolStartAction(visitEnumSymbol, SymbolKind.NamedType); void visitEnumSymbol(SymbolStartAnalysisContext context) { if (context.Symbol is not INamedTypeSymbol { TypeKind : TypeKind.Enum } enumSymbol) { return; } // This dictionary is populated by this thread and then read concurrently. // https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.dictionary-2?view=net-5.0#thread-safety var membersByValue = PooledDictionary <object, IFieldSymbol> .GetInstance(); var duplicates = PooledConcurrentSet <IFieldSymbol> .GetInstance(SymbolEqualityComparer.Default); foreach (var member in enumSymbol.GetMembers()) { if (member is not IFieldSymbol { IsImplicitlyDeclared: false, HasConstantValue: true } field) { continue; } var constantValue = field.ConstantValue; if (membersByValue.ContainsKey(constantValue)) { // This field is a duplicate. We need to first check // if its initializer is another field on this enum, // and if not give a diagnostic on it. var added = duplicates.Add(field); Debug.Assert(added); } else { membersByValue[constantValue] = field; } } context.RegisterOperationAction(visitFieldInitializer, OperationKind.FieldInitializer); context.RegisterSymbolEndAction(endVisitEnumSymbol); void visitFieldInitializer(OperationAnalysisContext context) { var initializer = (IFieldInitializerOperation)context.Operation; if (initializer.InitializedFields.Length != 1) { return; } var field = initializer.InitializedFields[0]; if (duplicates.Remove(field)) { var duplicatedField = membersByValue[field.ConstantValue]; if (initializer.Value is not IConversionOperation { Operand : IFieldReferenceOperation { Field : IFieldSymbol referencedField } } || !SymbolEqualityComparer.Default.Equals(referencedField, duplicatedField)) { context.ReportDiagnostic(field.CreateDiagnostic(RuleDuplicatedValue, field.Name, field.ConstantValue, duplicatedField.Name)); } } // Check for duplicate usages of an enum field in an initializer consisting of '|' expressions var referencedSymbols = PooledHashSet <IFieldSymbol> .GetInstance(SymbolEqualityComparer.Default); var containingType = field.ContainingType; visitInitializerValue(initializer.Value); referencedSymbols.Free(context.CancellationToken); void visitInitializerValue(IOperation operation) { switch (operation) { case IBinaryOperation { OperatorKind: not BinaryOperatorKind.Or } : // only descend into '|' binary operators, not into '&', '+', ... break;
public LocalSubstituter(PooledDictionary <LocalSymbol, LocalSymbol> tempSubstitution) { _tempSubstitution = tempSubstitution; }
/// <exception cref="InvalidOperationException">Bad data.</exception> private static void GetCSharpDynamicLocalInfo( byte[] customDebugInfo, IEnumerable <ISymUnmanagedScope> scopes, out ImmutableDictionary <int, ImmutableArray <bool> > dynamicLocalMap, out ImmutableDictionary <string, ImmutableArray <bool> > dynamicLocalConstantMap) { dynamicLocalMap = null; dynamicLocalConstantMap = null; var record = CustomDebugInfoReader.TryGetCustomDebugInfoRecord(customDebugInfo, CustomDebugInfoKind.DynamicLocals); if (record.IsDefault) { return; } var localKindsByName = PooledDictionary <string, LocalKind> .GetInstance(); GetLocalKindByName(localKindsByName, scopes); ImmutableDictionary <int, ImmutableArray <bool> > .Builder localBuilder = null; ImmutableDictionary <string, ImmutableArray <bool> > .Builder constantBuilder = null; var dynamicLocals = CustomDebugInfoReader.DecodeDynamicLocalsRecord(record); foreach (var dynamicLocal in dynamicLocals) { int slot = dynamicLocal.SlotId; var flags = dynamicLocal.Flags; if (slot == 0) { LocalKind kind; var name = dynamicLocal.LocalName; localKindsByName.TryGetValue(name, out kind); switch (kind) { case LocalKind.DuplicateName: // Drop locals with ambiguous names. continue; case LocalKind.ConstantName: constantBuilder = constantBuilder ?? ImmutableDictionary.CreateBuilder <string, ImmutableArray <bool> >(); constantBuilder[name] = CreateBoolArray(flags); continue; } } localBuilder = localBuilder ?? ImmutableDictionary.CreateBuilder <int, ImmutableArray <bool> >(); localBuilder[slot] = CreateBoolArray(flags); } if (localBuilder != null) { dynamicLocalMap = localBuilder.ToImmutable(); } if (constantBuilder != null) { dynamicLocalConstantMap = constantBuilder.ToImmutable(); } localKindsByName.Free(); }
public SyntaxAndDeclarationManager ReplaceSyntaxTree(SyntaxTree oldTree, SyntaxTree newTree) { var state = _lazyState; var newExternalSyntaxTrees = this.ExternalSyntaxTrees.Replace(oldTree, newTree); if (state == null) { return(this.WithExternalSyntaxTrees(newExternalSyntaxTrees)); } var newLoadDirectivesSyntax = newTree.GetCompilationUnitRoot().GetLoadDirectives(); var loadDirectivesHaveChanged = !oldTree.GetCompilationUnitRoot().GetLoadDirectives().SequenceEqual(newLoadDirectivesSyntax); var syntaxTrees = state.SyntaxTrees; var ordinalMap = state.OrdinalMap; var loadDirectiveMap = state.LoadDirectiveMap; var loadedSyntaxTreeMap = state.LoadedSyntaxTreeMap; var removeSet = PooledHashSet <SyntaxTree> .GetInstance(); int totalReferencedTreeCount; ImmutableArray <DeclarationLoadDirective> oldLoadDirectives; GetRemoveSet( oldTree, loadDirectivesHaveChanged, syntaxTrees, ordinalMap, loadDirectiveMap, loadedSyntaxTreeMap, removeSet, out totalReferencedTreeCount, out oldLoadDirectives); var loadDirectiveMapBuilder = loadDirectiveMap.ToBuilder(); var loadedSyntaxTreeMapBuilder = loadedSyntaxTreeMap.ToBuilder(); var declMapBuilder = state.RootNamespaces.ToBuilder(); var declTable = state.DeclarationTable; foreach (var tree in removeSet) { loadDirectiveMapBuilder.Remove(tree); loadedSyntaxTreeMapBuilder.Remove(tree.FilePath); RemoveSyntaxTreeFromDeclarationMapAndTable(tree, declMapBuilder, ref declTable); } removeSet.Free(); var oldOrdinal = ordinalMap[oldTree]; ImmutableArray <SyntaxTree> newTrees; if (loadDirectivesHaveChanged) { // Should have been removed above... Debug.Assert(!loadDirectiveMapBuilder.ContainsKey(oldTree)); Debug.Assert(!loadDirectiveMapBuilder.ContainsKey(newTree)); // If we're inserting new #load'ed trees, we'll rebuild // the whole syntaxTree array and the ordinalMap. var treesBuilder = ArrayBuilder <SyntaxTree> .GetInstance(); var ordinalMapBuilder = PooledDictionary <SyntaxTree, int> .GetInstance(); for (var i = 0; i <= (oldOrdinal - totalReferencedTreeCount); i++) { var tree = syntaxTrees[i]; treesBuilder.Add(tree); ordinalMapBuilder.Add(tree, i); } AppendAllSyntaxTrees( treesBuilder, newTree, this.ScriptClassName, this.Resolver, this.Language, this.IsSubmission, ordinalMapBuilder, loadDirectiveMapBuilder, loadedSyntaxTreeMapBuilder, declMapBuilder, ref declTable); for (var i = oldOrdinal + 1; i < syntaxTrees.Length; i++) { var tree = syntaxTrees[i]; if (!IsLoadedSyntaxTree(tree, loadedSyntaxTreeMap)) { UpdateSyntaxTreesAndOrdinalMapOnly( treesBuilder, tree, ordinalMapBuilder, loadDirectiveMap, loadedSyntaxTreeMap); } } newTrees = treesBuilder.ToImmutableAndFree(); ordinalMap = ordinalMapBuilder.ToImmutableDictionaryAndFree(); Debug.Assert(newTrees.Length == ordinalMap.Count); } else { AddSyntaxTreeToDeclarationMapAndTable(newTree, this.ScriptClassName, this.IsSubmission, declMapBuilder, ref declTable); if (newLoadDirectivesSyntax.Any()) { // If load directives have not changed and there are new directives, // then there should have been (matching) old directives as well. Debug.Assert(!oldLoadDirectives.IsDefault); Debug.Assert(!oldLoadDirectives.IsEmpty); Debug.Assert(oldLoadDirectives.Length == newLoadDirectivesSyntax.Count); loadDirectiveMapBuilder[newTree] = oldLoadDirectives; } Debug.Assert(ordinalMap.ContainsKey(oldTree)); // Checked by RemoveSyntaxTreeFromDeclarationMapAndTable newTrees = syntaxTrees.SetItem(oldOrdinal, newTree); ordinalMap = ordinalMap.Remove(oldTree); ordinalMap = ordinalMap.SetItem(newTree, oldOrdinal); } state = new State( newTrees, ordinalMap, loadDirectiveMapBuilder.ToImmutable(), loadedSyntaxTreeMapBuilder.ToImmutable(), declMapBuilder.ToImmutable(), declTable); return(new SyntaxAndDeclarationManager( newExternalSyntaxTrees, this.ScriptClassName, this.Resolver, this.Language, this.IsSubmission, state)); }
private static TaintedDataConfig Create(Compilation compilation) { WellKnownTypeProvider wellKnownTypeProvider = WellKnownTypeProvider.GetOrCreate(compilation); using PooledDictionary <SinkKind, Lazy <TaintedDataSymbolMap <SourceInfo> > > sourceSymbolMapBuilder = PooledDictionary <SinkKind, Lazy <TaintedDataSymbolMap <SourceInfo> > > .GetInstance(); using PooledDictionary <SinkKind, Lazy <TaintedDataSymbolMap <SanitizerInfo> > > sanitizerSymbolMapBuilder = PooledDictionary <SinkKind, Lazy <TaintedDataSymbolMap <SanitizerInfo> > > .GetInstance(); using PooledDictionary <SinkKind, Lazy <TaintedDataSymbolMap <SinkInfo> > > sinkSymbolMapBuilder = PooledDictionary <SinkKind, Lazy <TaintedDataSymbolMap <SinkInfo> > > .GetInstance(); // For tainted data rules with the same set of sources, we'll reuse the same TaintedDataSymbolMap<SourceInfo> instance. // Same for sanitizers. using PooledDictionary <ImmutableHashSet <SourceInfo>, Lazy <TaintedDataSymbolMap <SourceInfo> > > sourcesToSymbolMap = PooledDictionary <ImmutableHashSet <SourceInfo>, Lazy <TaintedDataSymbolMap <SourceInfo> > > .GetInstance(); using PooledDictionary <ImmutableHashSet <SanitizerInfo>, Lazy <TaintedDataSymbolMap <SanitizerInfo> > > sanitizersToSymbolMap = PooledDictionary <ImmutableHashSet <SanitizerInfo>, Lazy <TaintedDataSymbolMap <SanitizerInfo> > > .GetInstance(); // Build a mapping of (sourceSet, sanitizerSet) -> (sinkKinds, sinkSet), so we'll reuse the same TaintedDataSymbolMap<SinkInfo> instance. using PooledDictionary <(ImmutableHashSet <SourceInfo> SourceInfos, ImmutableHashSet <SanitizerInfo> SanitizerInfos), (ImmutableHashSet <SinkKind> .Builder SinkKinds, ImmutableHashSet <SinkInfo> .Builder SinkInfos)> sourceSanitizersToSinks = PooledDictionary <(ImmutableHashSet <SourceInfo> SourceInfos, ImmutableHashSet <SanitizerInfo> SanitizerInfos), (ImmutableHashSet <SinkKind> .Builder SinkKinds, ImmutableHashSet <SinkInfo> .Builder SinkInfos)> .GetInstance(); // Using LazyThreadSafetyMode.ExecutionAndPublication to avoid instantiating multiple times. foreach (SinkKind sinkKind in Enum.GetValues(typeof(SinkKind))) { ImmutableHashSet <SourceInfo> sources = GetSourceInfos(sinkKind); if (!sourcesToSymbolMap.TryGetValue(sources, out Lazy <TaintedDataSymbolMap <SourceInfo> > lazySourceSymbolMap)) { lazySourceSymbolMap = new Lazy <TaintedDataSymbolMap <SourceInfo> >( () => { return(new TaintedDataSymbolMap <SourceInfo>(wellKnownTypeProvider, sources)); }, LazyThreadSafetyMode.ExecutionAndPublication); sourcesToSymbolMap.Add(sources, lazySourceSymbolMap); } sourceSymbolMapBuilder.Add(sinkKind, lazySourceSymbolMap); ImmutableHashSet <SanitizerInfo> sanitizers = GetSanitizerInfos(sinkKind); if (!sanitizersToSymbolMap.TryGetValue(sanitizers, out Lazy <TaintedDataSymbolMap <SanitizerInfo> > lazySanitizerSymbolMap)) { lazySanitizerSymbolMap = new Lazy <TaintedDataSymbolMap <SanitizerInfo> >( () => { return(new TaintedDataSymbolMap <SanitizerInfo>(wellKnownTypeProvider, sanitizers)); }, LazyThreadSafetyMode.ExecutionAndPublication); sanitizersToSymbolMap.Add(sanitizers, lazySanitizerSymbolMap); } sanitizerSymbolMapBuilder.Add(sinkKind, lazySanitizerSymbolMap); ImmutableHashSet <SinkInfo> sinks = GetSinkInfos(sinkKind); if (!sourceSanitizersToSinks.TryGetValue((sources, sanitizers), out (ImmutableHashSet <SinkKind> .Builder SinkKinds, ImmutableHashSet <SinkInfo> .Builder SinkInfos)sinksPair)) { sinksPair = (ImmutableHashSet.CreateBuilder <SinkKind>(), ImmutableHashSet.CreateBuilder <SinkInfo>()); sourceSanitizersToSinks.Add((sources, sanitizers), sinksPair); } sinksPair.SinkKinds.Add(sinkKind); sinksPair.SinkInfos.UnionWith(sinks); } foreach (KeyValuePair <(ImmutableHashSet <SourceInfo> SourceInfos, ImmutableHashSet <SanitizerInfo> SanitizerInfos), (ImmutableHashSet <SinkKind> .Builder SinkKinds, ImmutableHashSet <SinkInfo> .Builder SinkInfos)> kvp in sourceSanitizersToSinks) { ImmutableHashSet <SinkInfo> sinks = kvp.Value.SinkInfos.ToImmutable(); Lazy <TaintedDataSymbolMap <SinkInfo> > lazySinkSymbolMap = new Lazy <TaintedDataSymbolMap <SinkInfo> >( () => { return(new TaintedDataSymbolMap <SinkInfo>(wellKnownTypeProvider, sinks)); }, LazyThreadSafetyMode.ExecutionAndPublication); foreach (SinkKind sinkKind in kvp.Value.SinkKinds) { sinkSymbolMapBuilder.Add(sinkKind, lazySinkSymbolMap); } } return(new TaintedDataConfig( wellKnownTypeProvider, sourceSymbolMapBuilder.ToImmutableDictionary(), sanitizerSymbolMapBuilder.ToImmutableDictionary(), sinkSymbolMapBuilder.ToImmutableDictionary())); }
private static TBlockAnalysisData RunCore( ImmutableArray <BasicBlock> blocks, DataFlowAnalyzer <TBlockAnalysisData> analyzer, int firstBlockOrdinal, int lastBlockOrdinal, TBlockAnalysisData initialAnalysisData, ArrayBuilder <BasicBlock> unreachableBlocksToVisit, SortedSet <int> outOfRangeBlocksToVisit, PooledDictionary <ControlFlowRegion, bool> continueDispatchAfterFinally, PooledHashSet <ControlFlowRegion> dispatchedExceptionsFromRegions, CancellationToken cancellationToken) { var toVisit = new SortedSet <int>(); var firstBlock = blocks[firstBlockOrdinal]; analyzer.SetCurrentAnalysisData(firstBlock, initialAnalysisData, cancellationToken); toVisit.Add(firstBlock.Ordinal); var processedBlocks = PooledHashSet <BasicBlock> .GetInstance(); TBlockAnalysisData resultAnalysisData = default; do { cancellationToken.ThrowIfCancellationRequested(); BasicBlock current; if (toVisit.Count > 0) { var min = toVisit.Min; toVisit.Remove(min); current = blocks[min]; } else { int index; current = null; for (index = 0; index < unreachableBlocksToVisit.Count; index++) { var unreachableBlock = unreachableBlocksToVisit[index]; if (unreachableBlock.Ordinal >= firstBlockOrdinal && unreachableBlock.Ordinal <= lastBlockOrdinal) { current = unreachableBlock; break; } } if (current == null) { continue; } unreachableBlocksToVisit.RemoveAt(index); if (processedBlocks.Contains(current)) { // Already processed from a branch from another unreachable block. continue; } analyzer.SetCurrentAnalysisData(current, analyzer.GetEmptyAnalysisData(), cancellationToken); } if (current.Ordinal < firstBlockOrdinal || current.Ordinal > lastBlockOrdinal) { outOfRangeBlocksToVisit.Add(current.Ordinal); continue; } if (current.Ordinal == current.EnclosingRegion.FirstBlockOrdinal) { // We are revisiting first block of a region, so we need to again dispatch exceptions from region. dispatchedExceptionsFromRegions.Remove(current.EnclosingRegion); } TBlockAnalysisData fallThroughAnalysisData = analyzer.AnalyzeBlock(current, cancellationToken); bool fallThroughSuccessorIsReachable = true; if (current.ConditionKind != ControlFlowConditionKind.None) { TBlockAnalysisData conditionalSuccessorAnalysisData; (fallThroughAnalysisData, conditionalSuccessorAnalysisData) = analyzer.AnalyzeConditionalBranch(current, fallThroughAnalysisData, cancellationToken); bool conditionalSuccesorIsReachable = true; if (current.BranchValue.ConstantValue.HasValue && current.BranchValue.ConstantValue.Value is bool constant) { if (constant == (current.ConditionKind == ControlFlowConditionKind.WhenTrue)) { fallThroughSuccessorIsReachable = false; } else { conditionalSuccesorIsReachable = false; } } if (conditionalSuccesorIsReachable || analyzer.AnalyzeUnreachableBlocks) { FollowBranch(current, current.ConditionalSuccessor, conditionalSuccessorAnalysisData); } } else { fallThroughAnalysisData = analyzer.AnalyzeNonConditionalBranch(current, fallThroughAnalysisData, cancellationToken); } if (fallThroughSuccessorIsReachable || analyzer.AnalyzeUnreachableBlocks) { ControlFlowBranch branch = current.FallThroughSuccessor; FollowBranch(current, branch, fallThroughAnalysisData); if (current.EnclosingRegion.Kind == ControlFlowRegionKind.Finally && current.Ordinal == lastBlockOrdinal) { continueDispatchAfterFinally[current.EnclosingRegion] = branch.Semantics != ControlFlowBranchSemantics.Throw && branch.Semantics != ControlFlowBranchSemantics.Rethrow && current.FallThroughSuccessor.Semantics == ControlFlowBranchSemantics.StructuredExceptionHandling; } } if (current.Ordinal == lastBlockOrdinal) { resultAnalysisData = fallThroughAnalysisData; } // We are using very simple approach: // If try block is reachable, we should dispatch an exception from it, even if it is empty. // To simplify implementation, we dispatch exception from every reachable basic block and rely // on dispatchedExceptionsFromRegions cache to avoid doing duplicate work. DispatchException(current.EnclosingRegion); processedBlocks.Add(current); }while (toVisit.Count != 0 || unreachableBlocksToVisit.Count != 0); return(resultAnalysisData); // Local functions. void FollowBranch(BasicBlock current, ControlFlowBranch branch, TBlockAnalysisData currentAnalsisData) { if (branch == null) { return; } switch (branch.Semantics) { case ControlFlowBranchSemantics.None: case ControlFlowBranchSemantics.ProgramTermination: case ControlFlowBranchSemantics.StructuredExceptionHandling: case ControlFlowBranchSemantics.Error: Debug.Assert(branch.Destination == null); return; case ControlFlowBranchSemantics.Throw: case ControlFlowBranchSemantics.Rethrow: Debug.Assert(branch.Destination == null); StepThroughFinally(current.EnclosingRegion, destinationOrdinal: lastBlockOrdinal, ref currentAnalsisData); return; case ControlFlowBranchSemantics.Regular: case ControlFlowBranchSemantics.Return: Debug.Assert(branch.Destination != null); if (StepThroughFinally(current.EnclosingRegion, branch.Destination.Ordinal, ref currentAnalsisData)) { var destination = branch.Destination; var currentDestinationData = analyzer.GetCurrentAnalysisData(destination); var mergedAnalysisData = analyzer.Merge(currentDestinationData, currentAnalsisData, cancellationToken); // We need to analyze the destination block if both the following conditions are met: // 1. Either the current block is reachable both destination and current are non-reachable // 2. Either the new analysis data for destination has changed or destination block hasn't // been processed. if ((current.IsReachable || !destination.IsReachable) && (!analyzer.IsEqual(currentDestinationData, mergedAnalysisData) || !processedBlocks.Contains(destination))) { analyzer.SetCurrentAnalysisData(destination, mergedAnalysisData, cancellationToken); toVisit.Add(branch.Destination.Ordinal); } } return; default: throw ExceptionUtilities.UnexpectedValue(branch.Semantics); } } // Returns whether we should proceed to the destination after finallies were taken care of. bool StepThroughFinally(ControlFlowRegion region, int destinationOrdinal, ref TBlockAnalysisData currentAnalysisData) { while (!region.ContainsBlock(destinationOrdinal)) { Debug.Assert(region.Kind != ControlFlowRegionKind.Root); ControlFlowRegion enclosing = region.EnclosingRegion; if (region.Kind == ControlFlowRegionKind.Try && enclosing.Kind == ControlFlowRegionKind.TryAndFinally) { Debug.Assert(enclosing.NestedRegions[0] == region); Debug.Assert(enclosing.NestedRegions[1].Kind == ControlFlowRegionKind.Finally); if (!StepThroughSingleFinally(enclosing.NestedRegions[1], ref currentAnalysisData)) { // The point that continues dispatch is not reachable. Cancel the dispatch. return(false); } } region = enclosing; } return(true); } // Returns whether we should proceed with dispatch after finally was taken care of. bool StepThroughSingleFinally(ControlFlowRegion @finally, ref TBlockAnalysisData currentAnalysisData) { Debug.Assert(@finally.Kind == ControlFlowRegionKind.Finally); var previousAnalysisData = analyzer.GetCurrentAnalysisData(blocks[@finally.FirstBlockOrdinal]); var mergedAnalysisData = analyzer.Merge(previousAnalysisData, currentAnalysisData, cancellationToken); if (!analyzer.IsEqual(previousAnalysisData, mergedAnalysisData)) { // For simplicity, we do a complete walk of the finally/filter region in isolation // to make sure that the resume dispatch point is reachable from its beginning. // It could also be reachable through invalid branches into the finally and we don't want to consider // these cases for regular finally handling. currentAnalysisData = RunCore(blocks, analyzer, @finally.FirstBlockOrdinal, @finally.LastBlockOrdinal, mergedAnalysisData, unreachableBlocksToVisit, outOfRangeBlocksToVisit: toVisit, continueDispatchAfterFinally, dispatchedExceptionsFromRegions, cancellationToken); } if (!continueDispatchAfterFinally.TryGetValue(@finally, out bool dispatch)) { dispatch = false; continueDispatchAfterFinally.Add(@finally, false); } return(dispatch); } void DispatchException(ControlFlowRegion fromRegion) { do { if (!dispatchedExceptionsFromRegions.Add(fromRegion)) { return; } ControlFlowRegion enclosing = fromRegion.Kind == ControlFlowRegionKind.Root ? null : fromRegion.EnclosingRegion; if (fromRegion.Kind == ControlFlowRegionKind.Try) { switch (enclosing.Kind) { case ControlFlowRegionKind.TryAndFinally: Debug.Assert(enclosing.NestedRegions[0] == fromRegion); Debug.Assert(enclosing.NestedRegions[1].Kind == ControlFlowRegionKind.Finally); var currentAnalysisData = analyzer.GetCurrentAnalysisData(blocks[fromRegion.FirstBlockOrdinal]); if (!StepThroughSingleFinally(enclosing.NestedRegions[1], ref currentAnalysisData)) { // The point that continues dispatch is not reachable. Cancel the dispatch. return; } break; case ControlFlowRegionKind.TryAndCatch: Debug.Assert(enclosing.NestedRegions[0] == fromRegion); DispatchExceptionThroughCatches(enclosing, startAt: 1); break; default: throw ExceptionUtilities.UnexpectedValue(enclosing.Kind); } } else if (fromRegion.Kind == ControlFlowRegionKind.Filter) { // If filter throws, dispatch is resumed at the next catch with an original exception Debug.Assert(enclosing.Kind == ControlFlowRegionKind.FilterAndHandler); ControlFlowRegion tryAndCatch = enclosing.EnclosingRegion; Debug.Assert(tryAndCatch.Kind == ControlFlowRegionKind.TryAndCatch); int index = tryAndCatch.NestedRegions.IndexOf(enclosing, startIndex: 1); if (index > 0) { DispatchExceptionThroughCatches(tryAndCatch, startAt: index + 1); fromRegion = tryAndCatch; continue; } throw ExceptionUtilities.Unreachable; } fromRegion = enclosing; }while (fromRegion != null); } void DispatchExceptionThroughCatches(ControlFlowRegion tryAndCatch, int startAt) { // For simplicity, we do not try to figure out whether a catch clause definitely // handles all exceptions. Debug.Assert(tryAndCatch.Kind == ControlFlowRegionKind.TryAndCatch); Debug.Assert(startAt > 0); Debug.Assert(startAt <= tryAndCatch.NestedRegions.Length); for (int i = startAt; i < tryAndCatch.NestedRegions.Length; i++) { ControlFlowRegion @catch = tryAndCatch.NestedRegions[i]; switch (@catch.Kind) { case ControlFlowRegionKind.Catch: toVisit.Add(@catch.FirstBlockOrdinal); break; case ControlFlowRegionKind.FilterAndHandler: BasicBlock entryBlock = blocks[@catch.FirstBlockOrdinal]; Debug.Assert(@catch.NestedRegions[0].Kind == ControlFlowRegionKind.Filter); Debug.Assert(entryBlock.Ordinal == @catch.NestedRegions[0].FirstBlockOrdinal); toVisit.Add(entryBlock.Ordinal); break; default: throw ExceptionUtilities.UnexpectedValue(@catch.Kind); } } } }
private void ResolveAndBindMissingAssemblies( TCompilation compilation, ImmutableArray <AssemblyData> explicitAssemblies, ImmutableArray <PEModule> explicitModules, ImmutableArray <MetadataReference> explicitReferences, ImmutableArray <ResolvedReference> explicitReferenceMap, MetadataReferenceResolver resolver, MetadataImportOptions importOptions, bool supersedeLowerVersions, [In, Out] ArrayBuilder <AssemblyReferenceBinding[]> referenceBindings, [In, Out] Dictionary <string, List <ReferencedAssemblyIdentity> > assemblyReferencesBySimpleName, out ImmutableArray <AssemblyData> allAssemblies, out ImmutableArray <MetadataReference> metadataReferences, out ImmutableArray <ResolvedReference> resolvedReferences, DiagnosticBag resolutionDiagnostics) { Debug.Assert(explicitAssemblies[0] is AssemblyDataForAssemblyBeingBuilt); Debug.Assert(referenceBindings.Count == explicitAssemblies.Length); Debug.Assert(explicitReferences.Length == explicitReferenceMap.Length); // -1 for assembly being built: int totalReferencedAssemblyCount = explicitAssemblies.Length - 1; var implicitAssemblies = ArrayBuilder <AssemblyData> .GetInstance(); // tracks identities we already asked the resolver to resolve: var requestedIdentities = PooledHashSet <AssemblyIdentity> .GetInstance(); PooledDictionary <AssemblyIdentity, PortableExecutableReference> previouslyResolvedAssembliesOpt = null; // Avoid resolving previously resolved missing references. If we call to the resolver again we would create new assembly symbols for them, // which would not match the previously created ones. As a result we would get duplicate PE types and conversion errors. var previousScriptCompilation = compilation.ScriptCompilationInfo?.PreviousScriptCompilation; if (previousScriptCompilation != null) { previouslyResolvedAssembliesOpt = PooledDictionary <AssemblyIdentity, PortableExecutableReference> .GetInstance(); foreach (var entry in previousScriptCompilation.GetBoundReferenceManager().GetImplicitlyResolvedAssemblyReferences()) { previouslyResolvedAssembliesOpt.Add(entry.Key, entry.Value); } } var metadataReferencesBuilder = ArrayBuilder <MetadataReference> .GetInstance(); Dictionary <MetadataReference, MergedAliases> lazyAliasMap = null; // metadata references and corresponding bindings of their references, used to calculate a fixed point: var referenceBindingsToProcess = ArrayBuilder <(MetadataReference, ArraySegment <AssemblyReferenceBinding>)> .GetInstance(); // collect all missing identities, resolve the assemblies and bind their references against explicit definitions: GetInitialReferenceBindingsToProcess(explicitModules, explicitReferences, explicitReferenceMap, referenceBindings, totalReferencedAssemblyCount, referenceBindingsToProcess); // NB: includes the assembly being built: int explicitAssemblyCount = explicitAssemblies.Length; try { while (referenceBindingsToProcess.Count > 0) { var referenceAndBindings = referenceBindingsToProcess.Pop(); var requestingReference = referenceAndBindings.Item1; var bindings = referenceAndBindings.Item2; foreach (var binding in bindings) { // only attempt to resolve unbound references (regardless of version difference of the bound ones) if (binding.IsBound) { continue; } if (!requestedIdentities.Add(binding.ReferenceIdentity)) { continue; } PortableExecutableReference resolvedReference; if (previouslyResolvedAssembliesOpt == null || !previouslyResolvedAssembliesOpt.TryGetValue(binding.ReferenceIdentity, out resolvedReference)) { resolvedReference = resolver.ResolveMissingAssembly(requestingReference, binding.ReferenceIdentity); if (resolvedReference == null) { continue; } } var data = ResolveMissingAssembly(binding.ReferenceIdentity, resolvedReference, importOptions, resolutionDiagnostics); if (data == null) { continue; } // The resolver may return different version than we asked for, so it may happen that // it returns the same identity for two different input identities (e.g. if a higher version // of an assembly is available than what the assemblies reference: "A, v1" -> "A, v3" and "A, v2" -> "A, v3"). // If such case occurs merge the properties (aliases) of the resulting references in the same way we do // during initial explicit references resolution. // -1 for assembly being built: int index = explicitAssemblyCount - 1 + metadataReferencesBuilder.Count; var existingReference = TryAddAssembly(data.Identity, resolvedReference, index, resolutionDiagnostics, Location.None, assemblyReferencesBySimpleName, supersedeLowerVersions); if (existingReference != null) { MergeReferenceProperties(existingReference, resolvedReference, resolutionDiagnostics, ref lazyAliasMap); continue; } metadataReferencesBuilder.Add(resolvedReference); implicitAssemblies.Add(data); var referenceBinding = data.BindAssemblyReferences(explicitAssemblies, IdentityComparer); referenceBindings.Add(referenceBinding); referenceBindingsToProcess.Push((resolvedReference, new ArraySegment <AssemblyReferenceBinding>(referenceBinding))); } } if (implicitAssemblies.Count == 0) { Debug.Assert(lazyAliasMap == null); resolvedReferences = ImmutableArray <ResolvedReference> .Empty; metadataReferences = ImmutableArray <MetadataReference> .Empty; allAssemblies = explicitAssemblies; return; } // Rebind assembly references that were initially missing. All bindings established above // are against explicitly specified references. allAssemblies = explicitAssemblies.AddRange(implicitAssemblies); for (int bindingsIndex = 0; bindingsIndex < referenceBindings.Count; bindingsIndex++) { var referenceBinding = referenceBindings[bindingsIndex]; for (int i = 0; i < referenceBinding.Length; i++) { var binding = referenceBinding[i]; // We don't rebind references bound to a non-matching version of a reference that was explicitly // specified, even if we have a better version now. if (binding.IsBound) { continue; } // We only need to resolve against implicitly resolved assemblies, // since we already resolved against explicitly specified ones. referenceBinding[i] = ResolveReferencedAssembly( binding.ReferenceIdentity, allAssemblies, explicitAssemblyCount, IdentityComparer); } } UpdateBindingsOfAssemblyBeingBuilt(referenceBindings, explicitAssemblyCount, implicitAssemblies); metadataReferences = metadataReferencesBuilder.ToImmutable(); resolvedReferences = ToResolvedAssemblyReferences(metadataReferences, lazyAliasMap, explicitAssemblyCount); } finally { implicitAssemblies.Free(); requestedIdentities.Free(); referenceBindingsToProcess.Free(); metadataReferencesBuilder.Free(); previouslyResolvedAssembliesOpt?.Free(); } }
/// <summary> /// Starting with `this` state, produce a human-readable description of the state tables. /// This is very useful for debugging and optimizing the dag state construction. /// </summary> internal new string Dump() { var allStates = this.TopologicallySortedNodes; var stateIdentifierMap = PooledDictionary <BoundDecisionDagNode, int> .GetInstance(); for (int i = 0; i < allStates.Length; i++) { stateIdentifierMap.Add(allStates[i], i); } int nextTempNumber = 0; var tempIdentifierMap = PooledDictionary <BoundDagEvaluation, int> .GetInstance(); int tempIdentifier(BoundDagEvaluation e) { return((e == null) ? 0 : tempIdentifierMap.TryGetValue(e, out int value) ? value : tempIdentifierMap[e] = ++nextTempNumber); } string tempName(BoundDagTemp t) { return($"t{tempIdentifier(t.Source)}{(t.Index != 0 ? $".{t.Index.ToString()}" : "")}"); } var resultBuilder = PooledStringBuilder.GetInstance(); var result = resultBuilder.Builder; foreach (var state in allStates) { result.AppendLine($"State " + stateIdentifierMap[state]); switch (state) { case BoundTestDecisionDagNode node: result.AppendLine($" Test: {dumpDagTest(node.Test)}"); if (node.WhenTrue != null) { result.AppendLine($" WhenTrue: {stateIdentifierMap[node.WhenTrue]}"); } if (node.WhenFalse != null) { result.AppendLine($" WhenFalse: {stateIdentifierMap[node.WhenFalse]}"); } break; case BoundEvaluationDecisionDagNode node: result.AppendLine($" Test: {dumpDagTest(node.Evaluation)}"); if (node.Next != null) { result.AppendLine($" Next: {stateIdentifierMap[node.Next]}"); } break; case BoundWhenDecisionDagNode node: result.AppendLine($" WhenClause: " + node.WhenExpression?.Syntax); if (node.WhenTrue != null) { result.AppendLine($" WhenTrue: {stateIdentifierMap[node.WhenTrue]}"); } if (node.WhenFalse != null) { result.AppendLine($" WhenFalse: {stateIdentifierMap[node.WhenFalse]}"); } break; case BoundLeafDecisionDagNode node: result.AppendLine($" Case: " + node.Syntax); break; default: throw ExceptionUtilities.UnexpectedValue(state); } } stateIdentifierMap.Free(); tempIdentifierMap.Free(); return(resultBuilder.ToStringAndFree()); string dumpDagTest(BoundDagTest d) { switch (d) { case BoundDagTypeEvaluation a: return($"t{tempIdentifier(a)}={a.Kind}(t{tempIdentifier(a)} as {a.Type})"); case BoundDagEvaluation e: return($"t{tempIdentifier(e)}={e.Kind}(t{tempIdentifier(e)})"); case BoundDagTypeTest b: return($"?{d.Kind}({tempName(d.Input)} is {b.Type})"); case BoundDagValueTest v: return($"?{d.Kind}({tempName(d.Input)} == {v.Value})"); case BoundDagRelationalTest r: var operatorName = r.Relation.Operator() switch { BinaryOperatorKind.LessThan => "<", BinaryOperatorKind.LessThanOrEqual => "<=", BinaryOperatorKind.GreaterThan => ">", BinaryOperatorKind.GreaterThanOrEqual => ">=", _ => "??" }; return($"?{d.Kind}({tempName(d.Input)} {operatorName} {r.Value})"); default: return($"?{d.Kind}({tempName(d.Input)})"); } } }
public static ImmutableDictionary <K, ImmutableArray <V> > ToImmutableMultiDictionaryAndFree <K, V>(this PooledDictionary <K, ArrayBuilder <V> > builders) { var result = ImmutableDictionary.CreateBuilder <K, ImmutableArray <V> >(); foreach (var(key, items) in builders) { result.Add(key, items.ToImmutableAndFree()); } builders.Free(); return(result.ToImmutable()); }
private static void ProcessBody <TSyntaxKind>( SemanticModel semanticModel, ImmutableArray <IDiagnosticAnalyzer> analyzers, ICodeBlockStartedAnalyzer[] bodyAnalyzers, ISymbol symbol, SyntaxNode syntax, CancellationToken cancellationToken, Action <Diagnostic> addDiagnostic, AnalyzerOptions analyzerOptions, bool continueOnError, Func <SyntaxNode, TSyntaxKind> getKind) { var endedAnalyzers = ArrayBuilder <ICodeBlockEndedAnalyzer> .GetInstance(); PooledDictionary <TSyntaxKind, ArrayBuilder <ISyntaxNodeAnalyzer <TSyntaxKind> > > nodeAnalyzersByKind = null; foreach (var a in bodyAnalyzers) { // Catch Exception from a.OnCodeBlockStarted ExecuteAndCatchIfThrows(a, addDiagnostic, continueOnError, cancellationToken, () => { var analyzer = a.OnCodeBlockStarted(syntax, symbol, semanticModel, addDiagnostic, analyzerOptions, cancellationToken); if (analyzer != null && analyzer != a) { endedAnalyzers.Add(analyzer); } }); } foreach (var nodeAnalyzer in endedAnalyzers.Concat(analyzers).OfType <ISyntaxNodeAnalyzer <TSyntaxKind> >()) { // Catch Exception from nodeAnalyzer.SyntaxKindsOfInterest try { foreach (var kind in nodeAnalyzer.SyntaxKindsOfInterest) { if (nodeAnalyzersByKind == null) { nodeAnalyzersByKind = PooledDictionary <TSyntaxKind, ArrayBuilder <ISyntaxNodeAnalyzer <TSyntaxKind> > > .GetInstance(); } ArrayBuilder <ISyntaxNodeAnalyzer <TSyntaxKind> > analyzersForKind; if (!nodeAnalyzersByKind.TryGetValue(kind, out analyzersForKind)) { nodeAnalyzersByKind.Add(kind, analyzersForKind = ArrayBuilder <ISyntaxNodeAnalyzer <TSyntaxKind> > .GetInstance()); } analyzersForKind.Add(nodeAnalyzer); } } catch (Exception e) { // Create a info diagnostic saying that the analyzer failed addDiagnostic(GetAnalyzerDiagnostic(nodeAnalyzer, e)); } } if (nodeAnalyzersByKind != null) { foreach (var child in syntax.DescendantNodesAndSelf()) { ArrayBuilder <ISyntaxNodeAnalyzer <TSyntaxKind> > analyzersForKind; if (nodeAnalyzersByKind.TryGetValue(getKind(child), out analyzersForKind)) { foreach (var analyzer in analyzersForKind) { // Catch Exception from analyzer.AnalyzeNode ExecuteAndCatchIfThrows(analyzer, addDiagnostic, continueOnError, cancellationToken, () => { analyzer.AnalyzeNode(child, semanticModel, addDiagnostic, analyzerOptions, cancellationToken); }); } } } foreach (var b in nodeAnalyzersByKind.Values) { b.Free(); } nodeAnalyzersByKind.Free(); } foreach (var a in endedAnalyzers.Concat(analyzers.OfType <ICodeBlockEndedAnalyzer>())) { // Catch Exception from a.OnCodeBlockEnded ExecuteAndCatchIfThrows(a, addDiagnostic, continueOnError, cancellationToken, () => { a.OnCodeBlockEnded(syntax, symbol, semanticModel, addDiagnostic, analyzerOptions, cancellationToken); }); } endedAnalyzers.Free(); }
private LocalSubstituter(PooledDictionary <LocalSymbol, LocalSymbol> tempSubstitution, int recursionDepth = 0) : base(recursionDepth) { _tempSubstitution = tempSubstitution; }
private ActiveStatementsMap CreateActiveStatementsMap(Solution solution, ImmutableArray <ActiveStatementDebugInfo> debugInfos) { var byDocument = PooledDictionary <DocumentId, ArrayBuilder <ActiveStatement> > .GetInstance(); var byInstruction = PooledDictionary <ActiveInstructionId, ActiveStatement> .GetInstance(); bool SupportsEditAndContinue(DocumentId documentId) => solution.GetProject(documentId.ProjectId).LanguageServices.GetService <IEditAndContinueAnalyzer>() != null; foreach (var debugInfo in debugInfos) { var documentName = debugInfo.DocumentNameOpt; if (documentName == null) { // Ignore active statements that do not have a source location. continue; } var documentIds = solution.GetDocumentIdsWithFilePath(documentName); var firstDocumentId = documentIds.FirstOrDefault(SupportsEditAndContinue); if (firstDocumentId == null) { // Ignore active statements that don't belong to the solution or language that supports EnC service. continue; } if (!byDocument.TryGetValue(firstDocumentId, out var primaryDocumentActiveStatements)) { byDocument.Add(firstDocumentId, primaryDocumentActiveStatements = ArrayBuilder <ActiveStatement> .GetInstance()); } var activeStatement = new ActiveStatement( ordinal: byInstruction.Count, primaryDocumentOrdinal: primaryDocumentActiveStatements.Count, documentIds: documentIds, flags: debugInfo.Flags, span: GetUpToDateSpan(debugInfo), instructionId: debugInfo.InstructionId, threadIds: debugInfo.ThreadIds); primaryDocumentActiveStatements.Add(activeStatement); // TODO: associate only those documents that are from a project with the right module id // https://github.com/dotnet/roslyn/issues/24320 for (var i = 1; i < documentIds.Length; i++) { var documentId = documentIds[i]; if (!SupportsEditAndContinue(documentId)) { continue; } if (!byDocument.TryGetValue(documentId, out var linkedDocumentActiveStatements)) { byDocument.Add(documentId, linkedDocumentActiveStatements = ArrayBuilder <ActiveStatement> .GetInstance()); } linkedDocumentActiveStatements.Add(activeStatement); } try { byInstruction.Add(debugInfo.InstructionId, activeStatement); } catch (ArgumentException) { throw new InvalidOperationException($"Multiple active statements with the same instruction id returned by " + $"{_activeStatementProvider.GetType()}.{nameof(IActiveStatementProvider.GetActiveStatementsAsync)}"); } } return(new ActiveStatementsMap(byDocument.ToDictionaryAndFree(), byInstruction.ToDictionaryAndFree())); }
public TwoWayDictionary(int capacity = 0) { ToValue = new PooledDictionary <TKey, TValue>(capacity); ToKey = new PooledDictionary <TValue, TKey>(capacity); }
#pragma warning disable CA1000 // Do not declare static members on generic types public static SymbolNamesWithValueOption <TValue> Create(ImmutableArray <string> symbolNames, Compilation compilation, string?optionalPrefix, #pragma warning restore CA1000 // Do not declare static members on generic types Func <string, NameParts>?getSymbolNamePartsFunc = null) { if (symbolNames.IsEmpty) { return(Empty); } var namesBuilder = PooledDictionary <string, TValue> .GetInstance(); var symbolsBuilder = PooledDictionary <ISymbol, TValue> .GetInstance(); foreach (var symbolName in symbolNames) { var parts = getSymbolNamePartsFunc != null ? getSymbolNamePartsFunc(symbolName) : new NameParts(symbolName); if (parts.SymbolName.Equals(".ctor", StringComparison.Ordinal) || parts.SymbolName.Equals(".cctor", StringComparison.Ordinal) || !parts.SymbolName.Contains(".") && !parts.SymbolName.Contains(":")) { if (!namesBuilder.ContainsKey(parts.SymbolName)) { namesBuilder.Add(parts.SymbolName, parts.AssociatedValue); } } else { var nameWithPrefix = (string.IsNullOrEmpty(optionalPrefix) || parts.SymbolName.StartsWith(optionalPrefix, StringComparison.Ordinal)) ? parts.SymbolName : optionalPrefix + parts.SymbolName; #pragma warning disable CA1307 // Specify StringComparison - https://github.com/dotnet/roslyn-analyzers/issues/1552 // Documentation comment ID for constructors uses '#ctor', but '#' is a comment start token for editorconfig. // We instead search for a '..ctor' in editorconfig and replace it with a '.#ctor' here. // Similarly, handle static constructors ".cctor" nameWithPrefix = nameWithPrefix.Replace("..ctor", ".#ctor"); nameWithPrefix = nameWithPrefix.Replace("..cctor", ".#cctor"); #pragma warning restore foreach (var symbol in DocumentationCommentId.GetSymbolsForDeclarationId(nameWithPrefix, compilation)) { if (symbol == null) { continue; } if (symbol is INamespaceSymbol namespaceSymbol && namespaceSymbol.ConstituentNamespaces.Length > 1) { foreach (var constituentNamespace in namespaceSymbol.ConstituentNamespaces) { if (!symbolsBuilder.ContainsKey(constituentNamespace)) { symbolsBuilder.Add(constituentNamespace, parts.AssociatedValue); } } } if (!symbolsBuilder.ContainsKey(symbol)) { symbolsBuilder.Add(symbol, parts.AssociatedValue); } } } } if (namesBuilder.Count == 0 && symbolsBuilder.Count == 0) { return(Empty); } return(new SymbolNamesWithValueOption <TValue>(namesBuilder.ToImmutableDictionaryAndFree(), symbolsBuilder.ToImmutableDictionaryAndFree())); }
/// <summary> /// Starting with `this` state, produce a human-readable description of the state tables. /// This is very useful for debugging and optimizing the dag state construction. /// </summary> internal new string Dump() { var allStates = this.TopologicallySortedNodes; var stateIdentifierMap = PooledDictionary <BoundDecisionDagNode, int> .GetInstance(); for (int i = 0; i < allStates.Length; i++) { stateIdentifierMap.Add(allStates[i], i); } int nextTempNumber = 0; var tempIdentifierMap = PooledDictionary <BoundDagEvaluation, int> .GetInstance(); int tempIdentifier(BoundDagEvaluation e) { return((e == null) ? 0 : tempIdentifierMap.TryGetValue(e, out int value) ? value : tempIdentifierMap[e] = ++nextTempNumber); } string tempName(BoundDagTemp t) { return($"t{tempIdentifier(t.Source)}{(t.Index != 0 ? $".{t.Index.ToString()}" : "")}"); } var resultBuilder = PooledStringBuilder.GetInstance(); var result = resultBuilder.Builder; foreach (var state in allStates) { result.AppendLine($"State " + stateIdentifierMap[state]); switch (state) { case BoundTestDecisionDagNode node: result.AppendLine($" Test: {dump(node.Test)}"); if (node.WhenTrue != null) { result.AppendLine($" WhenTrue: {stateIdentifierMap[node.WhenTrue]}"); } if (node.WhenFalse != null) { result.AppendLine($" WhenFalse: {stateIdentifierMap[node.WhenFalse]}"); } break; case BoundEvaluationDecisionDagNode node: result.AppendLine($" Test: {dump(node.Evaluation)}"); if (node.Next != null) { result.AppendLine($" Next: {stateIdentifierMap[node.Next]}"); } break; case BoundWhenDecisionDagNode node: result.AppendLine($" WhenClause: " + node.WhenExpression?.Syntax); if (node.WhenTrue != null) { result.AppendLine($" WhenTrue: {stateIdentifierMap[node.WhenTrue]}"); } if (node.WhenFalse != null) { result.AppendLine($" WhenFalse: {stateIdentifierMap[node.WhenFalse]}"); } break; case BoundLeafDecisionDagNode node: result.AppendLine($" Case: " + node.Syntax); break; default: throw ExceptionUtilities.UnexpectedValue(state); } } stateIdentifierMap.Free(); tempIdentifierMap.Free(); return(resultBuilder.ToStringAndFree()); string dump(BoundDagTest d) { switch (d) { case BoundDagTypeEvaluation a: return($"t{tempIdentifier(a)}={a.Kind} {tempName(d.Input)} as {a.Type.ToString()}"); case BoundDagPropertyEvaluation e: return($"t{tempIdentifier(e)}={e.Kind} {tempName(d.Input)}.{e.Property.Name}"); case BoundDagIndexEvaluation i: return($"t{tempIdentifier(i)}={i.Kind} {tempName(d.Input)}[{i.Index}]"); case BoundDagEvaluation e: return($"t{tempIdentifier(e)}={e.Kind}, {tempName(d.Input)}"); case BoundDagTypeTest b: return($"?{d.Kind} {tempName(d.Input)} is {b.Type.ToString()}"); case BoundDagValueTest v: return($"?{d.Kind} {v.Value.ToString()} == {tempName(d.Input)}"); case BoundDagNonNullTest t: return($"?{d.Kind} {tempName(d.Input)} != null"); case BoundDagExplicitNullTest t: return($"?{d.Kind} {tempName(d.Input)} == null"); default: throw ExceptionUtilities.UnexpectedValue(d); } } }
private static void GetFlowGraph(System.Text.StringBuilder stringBuilder, Compilation compilation, ControlFlowGraph graph, ControlFlowRegion enclosing, string idSuffix, int indent) { ImmutableArray <BasicBlock> blocks = graph.Blocks; var visitor = TestOperationVisitor.Singleton; ControlFlowRegion currentRegion = graph.Root; bool lastPrintedBlockIsInCurrentRegion = true; PooledDictionary <ControlFlowRegion, int> regionMap = buildRegionMap(); var localFunctionsMap = PooledDictionary <IMethodSymbol, ControlFlowGraph> .GetInstance(); var anonymousFunctionsMap = PooledDictionary <IFlowAnonymousFunctionOperation, ControlFlowGraph> .GetInstance(); for (int i = 0; i < blocks.Length; i++) { var block = blocks[i]; Assert.Equal(i, block.Ordinal); switch (block.Kind) { case BasicBlockKind.Block: Assert.NotEqual(0, i); Assert.NotEqual(blocks.Length - 1, i); break; case BasicBlockKind.Entry: Assert.Equal(0, i); Assert.Empty(block.Operations); Assert.Empty(block.Predecessors); Assert.Null(block.BranchValue); Assert.NotNull(block.FallThroughSuccessor); Assert.NotNull(block.FallThroughSuccessor.Destination); Assert.Null(block.ConditionalSuccessor); Assert.Same(graph.Root, currentRegion); Assert.Same(currentRegion, block.EnclosingRegion); Assert.Equal(0, currentRegion.FirstBlockOrdinal); Assert.Same(enclosing, currentRegion.EnclosingRegion); Assert.Null(currentRegion.ExceptionType); Assert.Empty(currentRegion.Locals); Assert.Empty(currentRegion.LocalFunctions); Assert.Equal(ControlFlowRegionKind.Root, currentRegion.Kind); Assert.True(block.IsReachable); break; case BasicBlockKind.Exit: Assert.Equal(blocks.Length - 1, i); Assert.Empty(block.Operations); Assert.Null(block.FallThroughSuccessor); Assert.Null(block.ConditionalSuccessor); Assert.Null(block.BranchValue); Assert.Same(graph.Root, currentRegion); Assert.Same(currentRegion, block.EnclosingRegion); Assert.Equal(i, currentRegion.LastBlockOrdinal); break; default: Assert.False(true, $"Unexpected block kind {block.Kind}"); break; } if (block.EnclosingRegion != currentRegion) { enterRegions(block.EnclosingRegion, block.Ordinal); } if (!lastPrintedBlockIsInCurrentRegion) { stringBuilder.AppendLine(); } appendLine($"Block[{getBlockId(block)}] - {block.Kind}{(block.IsReachable ? "" : " [UnReachable]")}"); var predecessors = block.Predecessors; if (!predecessors.IsEmpty) { appendIndent(); stringBuilder.Append(" Predecessors:"); int previousPredecessorOrdinal = -1; for (var predecessorIndex = 0; predecessorIndex < predecessors.Length; predecessorIndex++) { var predecessorBranch = predecessors[predecessorIndex]; Assert.Same(block, predecessorBranch.Destination); var predecessor = predecessorBranch.Source; Assert.True(previousPredecessorOrdinal < predecessor.Ordinal); previousPredecessorOrdinal = predecessor.Ordinal; Assert.Same(blocks[predecessor.Ordinal], predecessor); if (predecessorBranch.IsConditionalSuccessor) { Assert.Same(predecessor.ConditionalSuccessor, predecessorBranch); Assert.NotEqual(ControlFlowConditionKind.None, predecessor.ConditionKind); } else { Assert.Same(predecessor.FallThroughSuccessor, predecessorBranch); } stringBuilder.Append($" [{getBlockId(predecessor)}"); if (predecessorIndex < predecessors.Length - 1 && predecessors[predecessorIndex + 1].Source == predecessor) { // Multiple branches from same predecessor - one must be conditional and other fall through. Assert.True(predecessorBranch.IsConditionalSuccessor); predecessorIndex++; predecessorBranch = predecessors[predecessorIndex]; Assert.Same(predecessor.FallThroughSuccessor, predecessorBranch); Assert.False(predecessorBranch.IsConditionalSuccessor); stringBuilder.Append("*2"); } stringBuilder.Append("]"); } stringBuilder.AppendLine(); } else if (block.Kind != BasicBlockKind.Entry) { appendLine(" Predecessors (0)"); } var statements = block.Operations; appendLine($" Statements ({statements.Length})"); foreach (var statement in statements) { validateRoot(statement); stringBuilder.AppendLine(getOperationTree(statement)); } ControlFlowBranch conditionalBranch = block.ConditionalSuccessor; if (block.ConditionKind != ControlFlowConditionKind.None) { Assert.NotNull(conditionalBranch); Assert.True(conditionalBranch.IsConditionalSuccessor); Assert.Same(block, conditionalBranch.Source); if (conditionalBranch.Destination != null) { Assert.Same(blocks[conditionalBranch.Destination.Ordinal], conditionalBranch.Destination); } Assert.NotEqual(ControlFlowBranchSemantics.Return, conditionalBranch.Semantics); Assert.NotEqual(ControlFlowBranchSemantics.Throw, conditionalBranch.Semantics); Assert.NotEqual(ControlFlowBranchSemantics.StructuredExceptionHandling, conditionalBranch.Semantics); Assert.True(block.ConditionKind == ControlFlowConditionKind.WhenTrue || block.ConditionKind == ControlFlowConditionKind.WhenFalse); string jumpIfTrue = block.ConditionKind == ControlFlowConditionKind.WhenTrue ? "True" : "False"; appendLine($" Jump if {jumpIfTrue} ({conditionalBranch.Semantics}) to Block[{getDestinationString(ref conditionalBranch)}]"); IOperation value = block.BranchValue; Assert.NotNull(value); validateRoot(value); stringBuilder.Append(getOperationTree(value)); validateBranch(block, conditionalBranch); stringBuilder.AppendLine(); } else { Assert.Null(conditionalBranch); Assert.Equal(ControlFlowConditionKind.None, block.ConditionKind); } ControlFlowBranch nextBranch = block.FallThroughSuccessor; if (block.Kind == BasicBlockKind.Exit) { Assert.Null(nextBranch); Assert.Null(block.BranchValue); } else { Assert.NotNull(nextBranch); Assert.False(nextBranch.IsConditionalSuccessor); Assert.Same(block, nextBranch.Source); if (nextBranch.Destination != null) { Assert.Same(blocks[nextBranch.Destination.Ordinal], nextBranch.Destination); } if (nextBranch.Semantics == ControlFlowBranchSemantics.StructuredExceptionHandling) { Assert.Null(nextBranch.Destination); Assert.Equal(block.EnclosingRegion.LastBlockOrdinal, block.Ordinal); Assert.True(block.EnclosingRegion.Kind == ControlFlowRegionKind.Filter || block.EnclosingRegion.Kind == ControlFlowRegionKind.Finally); } appendLine($" Next ({nextBranch.Semantics}) Block[{getDestinationString(ref nextBranch)}]"); IOperation value = block.ConditionKind == ControlFlowConditionKind.None ? block.BranchValue : null; if (value != null) { Assert.True(ControlFlowBranchSemantics.Return == nextBranch.Semantics || ControlFlowBranchSemantics.Throw == nextBranch.Semantics); validateRoot(value); stringBuilder.Append(getOperationTree(value)); } else { Assert.NotEqual(ControlFlowBranchSemantics.Return, nextBranch.Semantics); Assert.NotEqual(ControlFlowBranchSemantics.Throw, nextBranch.Semantics); } validateBranch(block, nextBranch); } validateLocalsAndMethodsLifetime(block); if (currentRegion.LastBlockOrdinal == block.Ordinal && i != blocks.Length - 1) { leaveRegions(block.EnclosingRegion, block.Ordinal); } else { lastPrintedBlockIsInCurrentRegion = true; } } foreach (IMethodSymbol m in graph.LocalFunctions) { ControlFlowGraph g = localFunctionsMap[m]; Assert.Same(g, graph.GetLocalFunctionControlFlowGraph(m)); } Assert.Equal(graph.LocalFunctions.Length, localFunctionsMap.Count); foreach (KeyValuePair <IFlowAnonymousFunctionOperation, ControlFlowGraph> pair in anonymousFunctionsMap) { Assert.Same(pair.Value, graph.GetAnonymousFunctionControlFlowGraph(pair.Key)); } regionMap.Free(); localFunctionsMap.Free(); anonymousFunctionsMap.Free(); return; string getDestinationString(ref ControlFlowBranch branch) { return(branch.Destination != null?getBlockId(branch.Destination) : "null"); } PooledObjects.PooledDictionary <ControlFlowRegion, int> buildRegionMap() { var result = PooledObjects.PooledDictionary <ControlFlowRegion, int> .GetInstance(); int ordinal = 0; visit(graph.Root); void visit(ControlFlowRegion region) { result.Add(region, ordinal++); foreach (ControlFlowRegion r in region.NestedRegions) { visit(r); } } return(result); } void appendLine(string line) { appendIndent(); stringBuilder.AppendLine(line); } void appendIndent() { stringBuilder.Append(' ', indent); } void printLocals(ControlFlowRegion region) { if (!region.Locals.IsEmpty) { appendIndent(); stringBuilder.Append("Locals:"); foreach (ILocalSymbol local in region.Locals) { stringBuilder.Append($" [{local.ToTestDisplayString()}]"); } stringBuilder.AppendLine(); } if (!region.LocalFunctions.IsEmpty) { appendIndent(); stringBuilder.Append("Methods:"); foreach (IMethodSymbol method in region.LocalFunctions) { stringBuilder.Append($" [{method.ToTestDisplayString()}]"); } stringBuilder.AppendLine(); } } void enterRegions(ControlFlowRegion region, int firstBlockOrdinal) { if (region.FirstBlockOrdinal != firstBlockOrdinal) { Assert.Same(currentRegion, region); if (lastPrintedBlockIsInCurrentRegion) { stringBuilder.AppendLine(); } return; } enterRegions(region.EnclosingRegion, firstBlockOrdinal); currentRegion = region; lastPrintedBlockIsInCurrentRegion = true; switch (region.Kind) { case ControlFlowRegionKind.Filter: Assert.Empty(region.Locals); Assert.Empty(region.LocalFunctions); Assert.Equal(firstBlockOrdinal, region.EnclosingRegion.FirstBlockOrdinal); Assert.Same(region.ExceptionType, region.EnclosingRegion.ExceptionType); enterRegion($".filter {{{getRegionId(region)}}}"); break; case ControlFlowRegionKind.Try: Assert.Null(region.ExceptionType); Assert.Equal(firstBlockOrdinal, region.EnclosingRegion.FirstBlockOrdinal); enterRegion($".try {{{getRegionId(region.EnclosingRegion)}, {getRegionId(region)}}}"); break; case ControlFlowRegionKind.FilterAndHandler: enterRegion($".catch {{{getRegionId(region)}}} ({region.ExceptionType?.ToTestDisplayString() ?? "null"})"); break; case ControlFlowRegionKind.Finally: Assert.Null(region.ExceptionType); enterRegion($".finally {{{getRegionId(region)}}}"); break; case ControlFlowRegionKind.Catch: switch (region.EnclosingRegion.Kind) { case ControlFlowRegionKind.FilterAndHandler: Assert.Same(region.ExceptionType, region.EnclosingRegion.ExceptionType); enterRegion($".handler {{{getRegionId(region)}}}"); break; case ControlFlowRegionKind.TryAndCatch: enterRegion($".catch {{{getRegionId(region)}}} ({region.ExceptionType?.ToTestDisplayString() ?? "null"})"); break; default: Assert.False(true, $"Unexpected region kind {region.EnclosingRegion.Kind}"); break; } break; case ControlFlowRegionKind.LocalLifetime: Assert.Null(region.ExceptionType); Assert.False(region.Locals.IsEmpty && region.LocalFunctions.IsEmpty); enterRegion($".locals {{{getRegionId(region)}}}"); break; case ControlFlowRegionKind.TryAndCatch: case ControlFlowRegionKind.TryAndFinally: Assert.Empty(region.Locals); Assert.Empty(region.LocalFunctions); Assert.Null(region.ExceptionType); break; case ControlFlowRegionKind.StaticLocalInitializer: Assert.Null(region.ExceptionType); Assert.Empty(region.Locals); enterRegion($".static initializer {{{getRegionId(region)}}}"); break; case ControlFlowRegionKind.ErroneousBody: Assert.Null(region.ExceptionType); enterRegion($".erroneous body {{{getRegionId(region)}}}"); break; default: Assert.False(true, $"Unexpected region kind {region.Kind}"); break; } void enterRegion(string header) { appendLine(header); appendLine("{"); indent += 4; printLocals(region); } } void leaveRegions(ControlFlowRegion region, int lastBlockOrdinal) { if (region.LastBlockOrdinal != lastBlockOrdinal) { currentRegion = region; lastPrintedBlockIsInCurrentRegion = false; return; } string regionId = getRegionId(region); for (var i = 0; i < region.LocalFunctions.Length; i++) { var method = region.LocalFunctions[i]; appendLine(""); appendLine("{ " + method.ToTestDisplayString()); appendLine(""); var g = graph.GetLocalFunctionControlFlowGraph(method); localFunctionsMap.Add(method, g); Assert.Equal(OperationKind.LocalFunction, g.OriginalOperation.Kind); GetFlowGraph(stringBuilder, compilation, g, region, $"#{i}{regionId}", indent + 4); appendLine("}"); } switch (region.Kind) { case ControlFlowRegionKind.LocalLifetime: case ControlFlowRegionKind.Filter: case ControlFlowRegionKind.Try: case ControlFlowRegionKind.Finally: case ControlFlowRegionKind.FilterAndHandler: case ControlFlowRegionKind.StaticLocalInitializer: case ControlFlowRegionKind.ErroneousBody: indent -= 4; appendLine("}"); break; case ControlFlowRegionKind.Catch: switch (region.EnclosingRegion.Kind) { case ControlFlowRegionKind.FilterAndHandler: case ControlFlowRegionKind.TryAndCatch: goto endRegion; default: Assert.False(true, $"Unexpected region kind {region.EnclosingRegion.Kind}"); break; } break; endRegion: goto case ControlFlowRegionKind.Filter; case ControlFlowRegionKind.TryAndCatch: case ControlFlowRegionKind.TryAndFinally: break; default: Assert.False(true, $"Unexpected region kind {region.Kind}"); break; } leaveRegions(region.EnclosingRegion, lastBlockOrdinal); } void validateBranch(BasicBlock fromBlock, ControlFlowBranch branch) { if (branch.Destination == null) { Assert.Empty(branch.FinallyRegions); Assert.Empty(branch.LeavingRegions); Assert.Empty(branch.EnteringRegions); Assert.True(ControlFlowBranchSemantics.None == branch.Semantics || ControlFlowBranchSemantics.Throw == branch.Semantics || ControlFlowBranchSemantics.Rethrow == branch.Semantics || ControlFlowBranchSemantics.StructuredExceptionHandling == branch.Semantics || ControlFlowBranchSemantics.ProgramTermination == branch.Semantics || ControlFlowBranchSemantics.Error == branch.Semantics); return; } Assert.True(ControlFlowBranchSemantics.Regular == branch.Semantics || ControlFlowBranchSemantics.Return == branch.Semantics); Assert.True(branch.Destination.Predecessors.Contains(p => p.Source == fromBlock)); if (!branch.FinallyRegions.IsEmpty) { appendLine($" Finalizing:" + buildList(branch.FinallyRegions)); } ControlFlowRegion remainedIn1 = fromBlock.EnclosingRegion; if (!branch.LeavingRegions.IsEmpty) { appendLine($" Leaving:" + buildList(branch.LeavingRegions)); foreach (ControlFlowRegion r in branch.LeavingRegions) { Assert.Same(remainedIn1, r); remainedIn1 = r.EnclosingRegion; } } ControlFlowRegion remainedIn2 = branch.Destination.EnclosingRegion; if (!branch.EnteringRegions.IsEmpty) { appendLine($" Entering:" + buildList(branch.EnteringRegions)); for (int j = branch.EnteringRegions.Length - 1; j >= 0; j--) { ControlFlowRegion r = branch.EnteringRegions[j]; Assert.Same(remainedIn2, r); remainedIn2 = r.EnclosingRegion; } } Assert.Same(remainedIn1.EnclosingRegion, remainedIn2.EnclosingRegion); string buildList(ImmutableArray <ControlFlowRegion> list) { var builder = PooledObjects.PooledStringBuilder.GetInstance(); foreach (ControlFlowRegion r in list) { builder.Builder.Append($" {{{getRegionId(r)}}}"); } return(builder.ToStringAndFree()); } } void validateRoot(IOperation root) { visitor.Visit(root); Assert.Null(root.Parent); Assert.Null(((Operation)root).SemanticModel); Assert.True(CanBeInControlFlowGraph(root), $"Unexpected node kind OperationKind.{root.Kind}"); foreach (var operation in root.Descendants()) { visitor.Visit(operation); Assert.NotNull(operation.Parent); Assert.Null(((Operation)operation).SemanticModel); Assert.True(CanBeInControlFlowGraph(operation), $"Unexpected node kind OperationKind.{operation.Kind}"); } } void validateLocalsAndMethodsLifetime(BasicBlock block) { ISymbol[] localsOrMethodsInBlock = Enumerable.Concat(block.Operations, new[] { block.BranchValue }). Where(o => o != null). SelectMany(o => o.DescendantsAndSelf(). Select(node => { IMethodSymbol method; switch (node.Kind) { case OperationKind.LocalReference: return(((ILocalReferenceOperation)node).Local); case OperationKind.MethodReference: method = ((IMethodReferenceOperation)node).Method; return(method.MethodKind == MethodKind.LocalFunction ? method.OriginalDefinition : null); case OperationKind.Invocation: method = ((IInvocationOperation)node).TargetMethod; return(method.MethodKind == MethodKind.LocalFunction ? method.OriginalDefinition : null); default: return((ISymbol)null); } }). Where(s => s != null)). Distinct().ToArray(); if (localsOrMethodsInBlock.Length == 0) { return; } var localsAndMethodsInRegions = PooledHashSet <ISymbol> .GetInstance(); ControlFlowRegion region = block.EnclosingRegion; do { foreach (ILocalSymbol l in region.Locals) { Assert.True(localsAndMethodsInRegions.Add(l)); } foreach (IMethodSymbol m in region.LocalFunctions) { Assert.True(localsAndMethodsInRegions.Add(m)); } region = region.EnclosingRegion; }while (region != null); foreach (ISymbol l in localsOrMethodsInBlock) { Assert.False(localsAndMethodsInRegions.Add(l), $"Local/method without owning region {l.ToTestDisplayString()} in [{getBlockId(block)}]"); } localsAndMethodsInRegions.Free(); } string getBlockId(BasicBlock block) { return($"B{block.Ordinal}{idSuffix}"); } string getRegionId(ControlFlowRegion region) { return($"R{regionMap[region]}{idSuffix}"); } string getOperationTree(IOperation operation) { var walker = new OperationTreeSerializer(graph, currentRegion, idSuffix, anonymousFunctionsMap, compilation, operation, initialIndent: 8 + indent); walker.Visit(operation); return(walker.Builder.ToString()); } }
private Tuple <NamedTypeSymbol, ImmutableArray <NamedTypeSymbol> > MakeDeclaredBases(ConsList <TypeSymbol> basesBeingResolved, DiagnosticBag diagnostics) { if (this.TypeKind == TypeKind.Enum) { // Handled by GetEnumUnderlyingType(). return(new Tuple <NamedTypeSymbol, ImmutableArray <NamedTypeSymbol> >(null, ImmutableArray <NamedTypeSymbol> .Empty)); } var reportedPartialConflict = false; Debug.Assert(basesBeingResolved == null || !basesBeingResolved.ContainsReference(this.OriginalDefinition)); var newBasesBeingResolved = basesBeingResolved.Prepend(this.OriginalDefinition); var baseInterfaces = ArrayBuilder <NamedTypeSymbol> .GetInstance(); NamedTypeSymbol baseType = null; SourceLocation baseTypeLocation = null; var interfaceLocations = PooledDictionary <NamedTypeSymbol, SourceLocation> .GetInstance(); foreach (var decl in this.declaration.Declarations) { Tuple <NamedTypeSymbol, ImmutableArray <NamedTypeSymbol> > one = MakeOneDeclaredBases(newBasesBeingResolved, decl, diagnostics); if ((object)one == null) { continue; } var partBase = one.Item1; var partInterfaces = one.Item2; if (!reportedPartialConflict) { if ((object)baseType == null) { baseType = partBase; baseTypeLocation = decl.NameLocation; } else if (baseType.TypeKind == TypeKind.Error && (object)partBase != null) { // if the old base was an error symbol, copy it to the interfaces list so it doesn't get lost partInterfaces = partInterfaces.Add(baseType); baseType = partBase; baseTypeLocation = decl.NameLocation; } else if ((object)partBase != null && !TypeSymbol.Equals(partBase, baseType, TypeCompareKind.ConsiderEverything2) && partBase.TypeKind != TypeKind.Error) { // the parts do not agree var info = diagnostics.Add(ErrorCode.ERR_PartialMultipleBases, Locations[0], this); baseType = new ExtendedErrorTypeSymbol(baseType, LookupResultKind.Ambiguous, info); baseTypeLocation = decl.NameLocation; reportedPartialConflict = true; } } foreach (var t in partInterfaces) { if (!interfaceLocations.ContainsKey(t)) { baseInterfaces.Add(t); interfaceLocations.Add(t, decl.NameLocation); } } } HashSet <DiagnosticInfo> useSiteDiagnostics = null; if ((object)baseType != null) { Debug.Assert(baseTypeLocation != null); if (baseType.IsStatic) { // '{1}': cannot derive from static class '{0}' diagnostics.Add(ErrorCode.ERR_StaticBaseClass, baseTypeLocation, baseType, this); } if (!this.IsNoMoreVisibleThan(baseType, ref useSiteDiagnostics)) { // Inconsistent accessibility: base class '{1}' is less accessible than class '{0}' diagnostics.Add(ErrorCode.ERR_BadVisBaseClass, baseTypeLocation, this, baseType); } } var baseInterfacesRO = baseInterfaces.ToImmutableAndFree(); if (DeclaredAccessibility != Accessibility.Private && IsInterface) { foreach (var i in baseInterfacesRO) { if (!i.IsAtLeastAsVisibleAs(this, ref useSiteDiagnostics)) { // Inconsistent accessibility: base interface '{1}' is less accessible than interface '{0}' diagnostics.Add(ErrorCode.ERR_BadVisBaseInterface, interfaceLocations[i], this, i); } } } interfaceLocations.Free(); diagnostics.Add(Locations[0], useSiteDiagnostics); return(new Tuple <NamedTypeSymbol, ImmutableArray <NamedTypeSymbol> >(baseType, baseInterfacesRO)); }
private async Task FixAllValueAssignedIsUnusedDiagnosticsAsync( IOrderedEnumerable <Diagnostic> diagnostics, Document document, SemanticModel semanticModel, SyntaxNode root, SyntaxNode containingMemberDeclaration, UnusedValuePreference preference, bool removeAssignments, UniqueVariableNameGenerator nameGenerator, SyntaxEditor editor, ISyntaxFactsService syntaxFacts, CancellationToken cancellationToken) { // This method applies the code fix for diagnostics reported for unused value assignments to local/parameter. // The actual code fix depends on whether or not the right hand side of the assignment has side effects. // For example, if the right hand side is a constant or a reference to a local/parameter, then it has no side effects. // The lack of side effects is indicated by the "removeAssignments" parameter for this function. // If the right hand side has no side effects, then we can replace the assignments with variable declarations that have no initializer // or completely remove the statement. // If the right hand side does have side effects, we replace the identifier token for unused value assignment with // a new identifier token (either discard '_' or new unused local variable name). // For both the above cases, if the original diagnostic was reported on a local declaration, i.e. redundant initialization // at declaration, then we also add a new variable declaration statement without initializer for this local. var nodeReplacementMap = PooledDictionary <SyntaxNode, SyntaxNode> .GetInstance(); var nodesToRemove = PooledHashSet <SyntaxNode> .GetInstance(); var nodesToAdd = PooledHashSet <(TLocalDeclarationStatementSyntax declarationStatement, SyntaxNode node)> .GetInstance(); // Indicates if the node's trivia was processed. var processedNodes = PooledHashSet <SyntaxNode> .GetInstance(); var candidateDeclarationStatementsForRemoval = PooledHashSet <TLocalDeclarationStatementSyntax> .GetInstance(); var hasAnyUnusedLocalAssignment = false; try { foreach (var(node, isUnusedLocalAssignment) in GetNodesToFix()) { hasAnyUnusedLocalAssignment |= isUnusedLocalAssignment; var declaredLocal = semanticModel.GetDeclaredSymbol(node, cancellationToken) as ILocalSymbol; if (declaredLocal == null && node.Parent is TCatchStatementSyntax) { declaredLocal = semanticModel.GetDeclaredSymbol(node.Parent, cancellationToken) as ILocalSymbol; } string newLocalNameOpt = null; if (removeAssignments) { // Removable assignment or initialization, such that right hand side has no side effects. if (declaredLocal != null) { // Redundant initialization. // For example, "int a = 0;" var variableDeclarator = node.FirstAncestorOrSelf <TVariableDeclaratorSyntax>(); Debug.Assert(variableDeclarator != null); nodesToRemove.Add(variableDeclarator); // Local declaration statement containing the declarator might be a candidate for removal if all its variables get marked for removal. candidateDeclarationStatementsForRemoval.Add(variableDeclarator.GetAncestor <TLocalDeclarationStatementSyntax>()); } else { // Redundant assignment or increment/decrement. if (syntaxFacts.IsOperandOfIncrementOrDecrementExpression(node)) { // For example, C# increment operation "a++;" Debug.Assert(node.Parent.Parent is TExpressionStatementSyntax); nodesToRemove.Add(node.Parent.Parent); } else { Debug.Assert(syntaxFacts.IsLeftSideOfAnyAssignment(node)); if (node.Parent is TStatementSyntax) { // For example, VB simple assignment statement "a = 0" nodesToRemove.Add(node.Parent); } else if (node.Parent is TExpressionSyntax && node.Parent.Parent is TExpressionStatementSyntax) { // For example, C# simple assignment statement "a = 0;" nodesToRemove.Add(node.Parent.Parent); } else { // For example, C# nested assignment statement "a = b = 0;", where assignment to 'b' is redundant. // We replace the node with "a = 0;" nodeReplacementMap.Add(node.Parent, syntaxFacts.GetRightHandSideOfAssignment(node.Parent)); } } } } else { // Value initialization/assignment where the right hand side may have side effects, // and hence needs to be preserved in fixed code. // For example, "x = MethodCall();" is replaced with "_ = MethodCall();" or "var unused = MethodCall();" // Replace the flagged variable's indentifier token with new named, based on user's preference. var newNameToken = preference == UnusedValuePreference.DiscardVariable ? editor.Generator.Identifier(AbstractRemoveUnusedParametersAndValuesDiagnosticAnalyzer.DiscardVariableName) : nameGenerator.GenerateUniqueNameAtSpanStart(node); newLocalNameOpt = newNameToken.ValueText; var newNameNode = TryUpdateNameForFlaggedNode(node, newNameToken); if (newNameNode == null) { continue; } // Is this is compound assignment? if (syntaxFacts.IsLeftSideOfAnyAssignment(node) && !syntaxFacts.IsLeftSideOfAssignment(node)) { // Compound assignment is changed to simple assignment. // For example, "x += MethodCall();", where assignment to 'x' is redundant // is replaced with "_ = MethodCall();" or "var unused = MethodCall();" nodeReplacementMap.Add(node.Parent, GetReplacementNodeForCompoundAssignment(node.Parent, newNameNode, editor, syntaxFacts)); } else { nodeReplacementMap.Add(node, newNameNode); } } if (declaredLocal != null) { // We have a dead initialization for a local declaration. // Introduce a new local declaration statement without an initializer for this local. var declarationStatement = CreateLocalDeclarationStatement(declaredLocal.Type, declaredLocal.Name); if (isUnusedLocalAssignment) { declarationStatement = declarationStatement.WithAdditionalAnnotations(s_unusedLocalDeclarationAnnotation); } nodesToAdd.Add((declarationStatement, node)); } else { // We have a dead assignment to a local/parameter, which is not at the declaration site. // Create a new local declaration for the unused local if both following conditions are met: // 1. User prefers unused local variables for unused value assignment AND // 2. Assignment value has side effects and hence cannot be removed. if (preference == UnusedValuePreference.UnusedLocalVariable && !removeAssignments) { var type = semanticModel.GetTypeInfo(node, cancellationToken).Type; Debug.Assert(type != null); Debug.Assert(newLocalNameOpt != null); var declarationStatement = CreateLocalDeclarationStatement(type, newLocalNameOpt); nodesToAdd.Add((declarationStatement, node)); } } } // Process candidate declaration statements for removal. foreach (var localDeclarationStatement in candidateDeclarationStatementsForRemoval) { // If all the variable declarators for the local declaration statement are being removed, // we can remove the entire local declaration statement. if (ShouldRemoveStatement(localDeclarationStatement, out var variables)) { nodesToRemove.Add(localDeclarationStatement); nodesToRemove.RemoveRange(variables); } } foreach (var nodeToAdd in nodesToAdd) { InsertLocalDeclarationStatement(nodeToAdd.declarationStatement, nodeToAdd.node); } if (hasAnyUnusedLocalAssignment) { // Local declaration statements with no initializer, but non-zero references are candidates for removal // if the code fix removes all these references. // We annotate such declaration statements with no initializer abd non-zero references here // and remove them in post process document pass later, if the code fix did remove all these references. foreach (var localDeclarationStatement in containingMemberDeclaration.DescendantNodes().OfType <TLocalDeclarationStatementSyntax>()) { var variables = syntaxFacts.GetVariablesOfLocalDeclarationStatement(localDeclarationStatement); if (variables.Count == 1 && syntaxFacts.GetInitializerOfVariableDeclarator(variables[0]) == null && !(await IsLocalDeclarationWithNoReferencesAsync(localDeclarationStatement, document, cancellationToken).ConfigureAwait(false))) { nodeReplacementMap.Add(localDeclarationStatement, localDeclarationStatement.WithAdditionalAnnotations(s_existingLocalDeclarationWithoutInitializerAnnotation)); } } } foreach (var node in nodesToRemove) { var removeOptions = SyntaxGenerator.DefaultRemoveOptions; // If the leading trivia was not added to a new node, process it now. if (!processedNodes.Contains(node)) { // Don't keep trivia if the node is part of a multiple declaration statement. // e.g. int x = 0, y = 0, z = 0; any white space left behind can cause problems if the declaration gets split apart. var containingDeclaration = node.GetAncestor <TLocalDeclarationStatementSyntax>(); if (containingDeclaration != null && candidateDeclarationStatementsForRemoval.Contains(containingDeclaration)) { removeOptions = SyntaxRemoveOptions.KeepNoTrivia; } else { removeOptions |= SyntaxRemoveOptions.KeepLeadingTrivia; } } editor.RemoveNode(node, removeOptions); } foreach (var kvp in nodeReplacementMap) { editor.ReplaceNode(kvp.Key, kvp.Value.WithAdditionalAnnotations(Formatter.Annotation)); } } finally { nodeReplacementMap.Free(); nodesToRemove.Free(); nodesToAdd.Free(); processedNodes.Free(); } return; // Local functions. IEnumerable <(SyntaxNode node, bool isUnusedLocalAssignment)> GetNodesToFix() { foreach (var diagnostic in diagnostics) { var node = root.FindNode(diagnostic.Location.SourceSpan, getInnermostNodeForTie: true); var isUnusedLocalAssignment = AbstractRemoveUnusedParametersAndValuesDiagnosticAnalyzer.GetIsUnusedLocalDiagnostic(diagnostic); yield return(node, isUnusedLocalAssignment); } } // Mark generated local declaration statement with: // 1. "s_newLocalDeclarationAnnotation" for post processing in "MoveNewLocalDeclarationsNearReference" below. // 2. Simplifier annotation so that 'var'/explicit type is correctly added based on user options. TLocalDeclarationStatementSyntax CreateLocalDeclarationStatement(ITypeSymbol type, string name) => (TLocalDeclarationStatementSyntax)editor.Generator.LocalDeclarationStatement(type, name) .WithLeadingTrivia(editor.Generator.ElasticCarriageReturnLineFeed) .WithAdditionalAnnotations(s_newLocalDeclarationStatementAnnotation, Simplifier.Annotation); void InsertLocalDeclarationStatement(TLocalDeclarationStatementSyntax declarationStatement, SyntaxNode node) { // Find the correct place to insert the given declaration statement based on the node's ancestors. var insertionNode = node.FirstAncestorOrSelf <SyntaxNode>(n => n.Parent is TSwitchCaseBlockSyntax || syntaxFacts.IsExecutableBlock(n.Parent) && !(n is TCatchStatementSyntax) && !(n is TCatchBlockSyntax)); if (insertionNode is TSwitchCaseLabelOrClauseSyntax) { InsertAtStartOfSwitchCaseBlockForDeclarationInCaseLabelOrClause(insertionNode.GetAncestor <TSwitchCaseBlockSyntax>(), editor, declarationStatement); } else if (insertionNode is TStatementSyntax) { // If the insertion node is being removed, keep the leading trivia with the new declaration. if (nodesToRemove.Contains(insertionNode) && !processedNodes.Contains(insertionNode)) { declarationStatement = declarationStatement.WithLeadingTrivia(insertionNode.GetLeadingTrivia()); // Mark the node as processed so that the trivia only gets added once. processedNodes.Add(insertionNode); } editor.InsertBefore(insertionNode, declarationStatement); } } bool ShouldRemoveStatement(TLocalDeclarationStatementSyntax localDeclarationStatement, out SeparatedSyntaxList <SyntaxNode> variables) { Debug.Assert(removeAssignments); // We should remove the entire local declaration statement if all its variables are marked for removal. variables = syntaxFacts.GetVariablesOfLocalDeclarationStatement(localDeclarationStatement); foreach (var variable in variables) { if (!nodesToRemove.Contains(variable)) { return(false); } } return(true); } }