| File: FlowAnalysis\AbstractFlowPass_LocalFunctions.cs | Web Access |
| Project: src\roslyn\src\Compilers\CSharp\Portable\Microsoft.CodeAnalysis.CSharp.csproj (Microsoft.CodeAnalysis.CSharp) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.Collections.Immutable; using Microsoft.CodeAnalysis.CSharp.Symbols; namespace Microsoft.CodeAnalysis.CSharp { internal partial class AbstractFlowPass<TLocalState, TLocalFunctionState> { internal abstract class AbstractLocalFunctionState { /// <summary> /// This is the state from the local function which makes the /// current state less specific. For example, in nullable analysis /// this would be captured variables that may be nullable after /// calling the local function. When a local function is called, /// this state is <see cref="Join(ref TLocalState, ref TLocalState)"/> /// with the current state. /// </summary> public TLocalState StateFromBottom; /// <summary> /// This is the part of the local function transfer function which /// transfers knowledge additively. For example, in definite /// assignment this would be captured state which is assigned by /// the local function. When a local function is called, this /// state is <see cref="Meet(ref TLocalState, ref TLocalState)"/> /// with the current state. /// </summary> public TLocalState StateFromTop; public AbstractLocalFunctionState(TLocalState stateFromBottom, TLocalState stateFromTop) { StateFromBottom = stateFromBottom; StateFromTop = stateFromTop; } public bool Visited = false; } protected abstract TLocalFunctionState CreateLocalFunctionState(LocalFunctionSymbol symbol); private SmallDictionary<LocalFunctionSymbol, TLocalFunctionState>? _localFuncVarUsages = null; protected TLocalFunctionState GetOrCreateLocalFuncUsages(LocalFunctionSymbol localFunc) { _localFuncVarUsages ??= new SmallDictionary<LocalFunctionSymbol, TLocalFunctionState>(); if (!_localFuncVarUsages.TryGetValue(localFunc, out TLocalFunctionState? usages)) { usages = CreateLocalFunctionState(localFunc); _localFuncVarUsages[localFunc] = usages; } return usages; } protected bool HasLocalFuncUsagesCreated(LocalFunctionSymbol localFunc) { return _localFuncVarUsages?.ContainsKey(localFunc) == true; } public override BoundNode? VisitLocalFunctionStatement(BoundLocalFunctionStatement localFunc) { if (localFunc.Symbol.IsExtern) { // an extern local function is not permitted to have a body and thus shouldn't be flow analyzed return null; } var oldSymbol = this.CurrentSymbol; var localFuncSymbol = localFunc.Symbol; this.CurrentSymbol = localFuncSymbol; var oldPending = SavePending(); // we do not support branches into a lambda // SPEC: The entry point to a local function is always reachable. // Captured variables are definitely assigned if they are definitely assigned on // all branches into the local function. var savedState = this.State; this.State = this.TopState(); Optional<TLocalState> savedNonMonotonicState = NonMonotonicState; if (_nonMonotonicTransfer) { NonMonotonicState = ReachableBottomState(); } if (!localFunc.WasCompilerGenerated) EnterParameters(localFuncSymbol.Parameters); // State changes to captured variables are recorded, as calls to local functions // transition the state of captured variables if the variables have state changes // across all branches leaving the local function var localFunctionState = GetOrCreateLocalFuncUsages((LocalFunctionSymbol)localFuncSymbol); var savedLocalFunctionState = LocalFunctionStart(localFunctionState); var oldPending2 = SavePending(); // If this is an iterator, there's an implicit branch before the first statement // of the function where the enumerable is returned. if (localFuncSymbol.IsIterator) { PendingBranches.Add(new PendingBranch(null, this.State, null)); } VisitAlways(localFunc.Body); RestorePending(oldPending2); // process any forward branches within the lambda body ImmutableArray<PendingBranch> pendingReturns = RemoveReturns(); RestorePending(oldPending); Location? location = localFuncSymbol.TryGetFirstLocation(); LeaveParameters(localFuncSymbol.Parameters, localFunc.Syntax, location); // Intersect the state of all branches out of the local function var stateAtReturn = this.State; foreach (PendingBranch pending in pendingReturns) { this.State = pending.State; BoundNode branch = pending.Branch; // Pass the local function identifier as a location if the branch // is null or compiler generated. LeaveParameters(localFuncSymbol.Parameters, branch?.Syntax, branch?.WasCompilerGenerated == false ? null : location); Join(ref stateAtReturn, ref this.State); } // Record any changes to the state of captured variables if (RecordStateChange( savedLocalFunctionState, localFunctionState, ref stateAtReturn) && localFunctionState.Visited) { // If the sets have changed and we already used the results // of this local function in another computation, the previous // calculations may be invalid. We need to analyze until we // reach a fixed-point. stateChangedAfterUse = true; localFunctionState.Visited = false; } this.State = savedState; NonMonotonicState = savedNonMonotonicState; this.CurrentSymbol = oldSymbol; return null; } private bool RecordStateChange( TLocalFunctionState savedState, TLocalFunctionState currentState, ref TLocalState stateAtReturn) { bool anyChanged = LocalFunctionEnd(savedState, currentState, ref stateAtReturn); anyChanged |= Join(ref currentState.StateFromTop, ref stateAtReturn); if (NonMonotonicState.HasValue) { var value = NonMonotonicState.Value; // Since only state moving up gets stored in the non-monotonic state, // Meet with the stateAtReturn, which records all state changes. If // a state moved up, then down, the final state should be down. Meet(ref value, ref stateAtReturn); anyChanged |= Join(ref currentState.StateFromBottom, ref value); } return anyChanged; } /// <summary> /// Executed at the start of visiting a local function body. The <paramref name="state"/> /// parameter holds the current state information for the local function being visited. To /// save state information across the analysis, return an instance of <typeparamref name="TLocalFunctionState"/>. /// </summary> protected virtual TLocalFunctionState LocalFunctionStart(TLocalFunctionState state) => state; /// <summary> /// Executed after visiting a local function body. The <paramref name="savedState"/> is the /// return value from <see cref="LocalFunctionStart(TLocalFunctionState)"/>. The <paramref name="currentState"/> /// is state information for the local function that was just visited. <paramref name="stateAtReturn"/> is /// the state after visiting the method. /// </summary> protected virtual bool LocalFunctionEnd( TLocalFunctionState savedState, TLocalFunctionState currentState, ref TLocalState stateAtReturn) { return false; } } }