Exemple #1
0
 internal BasicBlock(
     BasicBlockKind kind,
     ImmutableArray <IOperation> operations,
     IOperation branchValue,
     ControlFlowConditionKind conditionKind,
     int ordinal,
     bool isReachable,
     ControlFlowRegion region)
 {
     Kind            = kind;
     Operations      = operations;
     BranchValue     = branchValue;
     ConditionKind   = conditionKind;
     Ordinal         = ordinal;
     IsReachable     = isReachable;
     EnclosingRegion = region;
 }
Exemple #2
0
        internal ControlFlowRegion(ControlFlowRegionKind kind, int firstBlockOrdinal, int lastBlockOrdinal,
                                   ImmutableArray <ControlFlowRegion> nestedRegions,
                                   ImmutableArray <ILocalSymbol> locals,
                                   ImmutableArray <IMethodSymbol> methods,
                                   ImmutableArray <CaptureId> captureIds,
                                   ITypeSymbol exceptionType,
                                   ControlFlowRegion enclosingRegion)
        {
            Debug.Assert(firstBlockOrdinal >= 0);
            Debug.Assert(lastBlockOrdinal >= firstBlockOrdinal);

            Kind = kind;
            FirstBlockOrdinal = firstBlockOrdinal;
            LastBlockOrdinal  = lastBlockOrdinal;
            ExceptionType     = exceptionType;
            Locals            = locals.NullToEmpty();
            LocalFunctions    = methods.NullToEmpty();
            CaptureIds        = captureIds.NullToEmpty();
            NestedRegions     = nestedRegions.NullToEmpty();
            EnclosingRegion   = enclosingRegion;

            foreach (ControlFlowRegion r in NestedRegions)
            {
                Debug.Assert(r.EnclosingRegion == null && r.Kind != ControlFlowRegionKind.Root);
                r.EnclosingRegion = this;
            }
#if DEBUG
            int previousLast;

            switch (kind)
            {
            case ControlFlowRegionKind.TryAndFinally:
            case ControlFlowRegionKind.FilterAndHandler:
                Debug.Assert(NestedRegions.Length == 2);
                Debug.Assert(NestedRegions[0].Kind == (kind == ControlFlowRegionKind.TryAndFinally ? ControlFlowRegionKind.Try : ControlFlowRegionKind.Filter));
                Debug.Assert(NestedRegions[1].Kind == (kind == ControlFlowRegionKind.TryAndFinally ? ControlFlowRegionKind.Finally : ControlFlowRegionKind.Catch));
                Debug.Assert(NestedRegions[0].FirstBlockOrdinal == firstBlockOrdinal);
                Debug.Assert(NestedRegions[1].LastBlockOrdinal == lastBlockOrdinal);
                Debug.Assert(NestedRegions[0].LastBlockOrdinal + 1 == NestedRegions[1].FirstBlockOrdinal);
                break;

            case ControlFlowRegionKind.TryAndCatch:
                Debug.Assert(NestedRegions.Length >= 2);
                Debug.Assert(NestedRegions[0].Kind == ControlFlowRegionKind.Try);
                Debug.Assert(NestedRegions[0].FirstBlockOrdinal == firstBlockOrdinal);
                previousLast = NestedRegions[0].LastBlockOrdinal;

                for (int i = 1; i < NestedRegions.Length; i++)
                {
                    ControlFlowRegion r = NestedRegions[i];
                    Debug.Assert(previousLast + 1 == r.FirstBlockOrdinal);
                    previousLast = r.LastBlockOrdinal;

                    Debug.Assert(r.Kind == ControlFlowRegionKind.FilterAndHandler || r.Kind == ControlFlowRegionKind.Catch);
                }

                Debug.Assert(previousLast == lastBlockOrdinal);
                break;

            case ControlFlowRegionKind.Root:
            case ControlFlowRegionKind.LocalLifetime:
            case ControlFlowRegionKind.Try:
            case ControlFlowRegionKind.Filter:
            case ControlFlowRegionKind.Catch:
            case ControlFlowRegionKind.Finally:
            case ControlFlowRegionKind.StaticLocalInitializer:
            case ControlFlowRegionKind.ErroneousBody:
                previousLast = firstBlockOrdinal - 1;

                foreach (ControlFlowRegion r in NestedRegions)
                {
                    Debug.Assert(previousLast < r.FirstBlockOrdinal);
                    previousLast = r.LastBlockOrdinal;
                }

                Debug.Assert(previousLast <= lastBlockOrdinal);
                break;

            default:
                throw ExceptionUtilities.UnexpectedValue(kind);
            }
#endif
        }
Exemple #3
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);
            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());
                }

                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.Throw:
                case ControlFlowBranchSemantics.Rethrow:
                case ControlFlowBranchSemantics.Error:
                    Debug.Assert(branch.Destination == null);
                    return;

                case ControlFlowBranchSemantics.Regular:
                case ControlFlowBranchSemantics.Return:
                    Debug.Assert(branch.Destination != null);

                    if (StepThroughFinally(current.EnclosingRegion, branch.Destination, 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);
                            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, BasicBlock destination, ref TBlockAnalysisData currentAnalysisData)
            {
                int destinationOrdinal = destination.Ordinal;

                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);
                    }
                }
            }
        }
Exemple #4
0
        private static ArrayBuilder <ControlFlowRegion> CollectRegions(int destinationOrdinal, ControlFlowRegion source)
        {
            var builder = ArrayBuilder <ControlFlowRegion> .GetInstance();

            while (!source.ContainsBlock(destinationOrdinal))
            {
                Debug.Assert(source.Kind != ControlFlowRegionKind.Root);
                builder.Add(source);
                source = source.EnclosingRegion;
            }

            return(builder);
        }