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;
     }
 }
Example #3
0
        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();
        }
Example #6
0
        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;
        }
Example #7
0
#pragma warning restore

        public DataFlowAnalysisResultBuilder()
        {
            _info = PooledDictionary <BasicBlock, TAnalysisData?> .GetInstance();
        }
Example #8
0
 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;
 }
Example #9
0
 private AwaitExpressionSpiller(MethodSymbol method, SyntaxNode syntaxNode, TypeCompilationState compilationState, PooledDictionary <LocalSymbol, LocalSymbol> tempSubstitution, DiagnosticBag diagnostics)
 {
     _F = new SyntheticBoundNodeFactory(method, syntaxNode, compilationState, diagnostics);
     _tempSubstitution = tempSubstitution;
 }
Example #10
0
        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);
        }
Example #11
0
 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));
        }
Example #14
0
        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
                           ));
            }
        }
Example #15
0
        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;
Example #16
0
 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()));
        }
Example #20
0
        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();
            }
        }
Example #22
0
        /// <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)})");
                }
            }
        }
Example #23
0
        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());
        }
Example #24
0
        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();
        }
Example #25
0
 private LocalSubstituter(PooledDictionary <LocalSymbol, LocalSymbol> tempSubstitution, int recursionDepth = 0)
     : base(recursionDepth)
 {
     _tempSubstitution = tempSubstitution;
 }
Example #26
0
        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()));
        }
Example #27
0
 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()));
        }
Example #29
0
        /// <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);
                }
            }
        }
Example #30
0
        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());
            }
        }
Example #31
0
        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));
        }
Example #32
0
        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);
            }
        }