protected override void VisitFinallyBlock(BoundStatement finallyBlock, ref LocalState endState) { var oldPending1 = SavePending(); // we do not support branches into a finally block var oldPending2 = SavePending(); // track only the branches out of the finally block base.VisitFinallyBlock(finallyBlock, ref endState); RestorePending(oldPending2); // resolve branches that remain within the finally block foreach (var branch in PendingBranches.AsEnumerable()) { if (branch.Branch == null) { continue; // a tracked exception } var location = new SourceLocation(branch.Branch.Syntax.GetFirstToken()); switch (branch.Branch.Kind) { case BoundKind.YieldBreakStatement: case BoundKind.YieldReturnStatement: // ERR_BadYieldInFinally reported during initial binding break; default: Diagnostics.Add(ErrorCode.ERR_BadFinallyLeave, location); break; } } RestorePending(oldPending1); }
protected override LocalState VisitSwitchStatementDispatch(BoundSwitchStatement node) { // first, learn from any null tests in the patterns int slot = MakeSlot(node.Expression); if (slot > 0) { var originalInputType = node.Expression.Type; foreach (var section in node.SwitchSections) { foreach (var label in section.SwitchLabels) { LearnFromAnyNullPatterns(slot, originalInputType, label.Pattern); } } } // visit switch header var expressionState = VisitRvalueWithState(node.Expression); LocalState initialState = this.State.Clone(); var labelStateMap = LearnFromDecisionDag(node.Syntax, node.DecisionDag, node.Expression, expressionState, ref initialState); foreach (var section in node.SwitchSections) { foreach (var label in section.SwitchLabels) { var labelResult = labelStateMap.TryGetValue(label.Label, out var s1) ? s1 : (state : UnreachableState(), believedReachable : false); SetState(labelResult.state); PendingBranches.Add(new PendingBranch(label, this.State, label.Label)); } } labelStateMap.Free(); return(initialState); }
private void VisitPatternSwitchBlock(BoundPatternSwitchStatement node) { var initialState = this.State; var afterSwitchState = UnreachableState(); var switchSections = node.SwitchSections; var iLastSection = (switchSections.Length - 1); // simulate the dispatch (setting pattern variables and jumping to labels) to // all reachable switch labels foreach (var section in switchSections) { foreach (var label in section.SwitchLabels) { if (label.IsReachable && label != node.DefaultLabel) { SetState(initialState.Clone()); // assign pattern variables VisitPattern(null, label.Pattern); SetState(StateWhenTrue); if (label.Guard != null) { VisitCondition(label.Guard); SetState(StateWhenTrue); } PendingBranches.Add(new PendingBranch(label, this.State)); } } } // we always consider the default label reachable for flow analysis purposes // unless there was a single case that would match every input. if (node.DefaultLabel != null) { if (node.SomeLabelAlwaysMatches) { SetUnreachable(); } else { SetState(initialState.Clone()); } PendingBranches.Add(new PendingBranch(node.DefaultLabel, this.State)); } // visit switch sections for (var iSection = 0; iSection <= iLastSection; iSection++) { VisitPatternSwitchSection(switchSections[iSection], node.Expression, iSection == iLastSection); // Even though it is illegal for the end of a switch section to be reachable, in erroneous // code it may be reachable. We treat that as an implicit break (branch to afterSwitchState). Join(ref afterSwitchState, ref this.State); } SetState(afterSwitchState); }
protected virtual TLocalState VisitSwitchStatementDispatch(BoundSwitchStatement node) { // visit switch header VisitRvalue(node.Expression); TLocalState initialState = this.State.Clone(); var reachableLabels = node.DecisionDag.ReachableLabels; foreach (var section in node.SwitchSections) { foreach (var label in section.SwitchLabels) { if ( reachableLabels.Contains(label.Label) || label.HasErrors || label == node.DefaultLabel && node.Expression.ConstantValue == null && IsTraditionalSwitch(node) ) { SetState(initialState.Clone()); } else { SetUnreachable(); } VisitPattern(label.Pattern); SetState(StateWhenTrue); if (label.WhenClause != null) { VisitCondition(label.WhenClause); SetState(StateWhenTrue); } PendingBranches.Add(new PendingBranch(label, this.State, label.Label)); } } TLocalState afterSwitchState = UnreachableState(); if ( node.DecisionDag.ReachableLabels.Contains(node.BreakLabel) || ( node.DefaultLabel == null && node.Expression.ConstantValue == null && IsTraditionalSwitch(node) ) ) { Join(ref afterSwitchState, ref initialState); } return(afterSwitchState); }
protected override void LeaveRegion() { foreach (var pending in PendingBranches.AsEnumerable()) { if (pending.Branch == null || !RegionContains(pending.Branch.Syntax.Span)) { continue; } switch (pending.Branch.Kind) { case BoundKind.GotoStatement: if (_labelsInside.Contains(((BoundGotoStatement)pending.Branch).Label)) { continue; } break; case BoundKind.BreakStatement: if (_labelsInside.Contains(((BoundBreakStatement)pending.Branch).Label)) { continue; } break; case BoundKind.ContinueStatement: if (_labelsInside.Contains(((BoundContinueStatement)pending.Branch).Label)) { continue; } break; case BoundKind.YieldBreakStatement: case BoundKind.ReturnStatement: // Return statements are included break; case BoundKind.YieldReturnStatement: case BoundKind.AwaitExpression: case BoundKind.UsingStatement: case BoundKind.ForEachStatement when((BoundForEachStatement)pending.Branch).AwaitOpt != null: // We don't do anything with yield return statements, async using statement, async foreach statement, or await expressions; // they are treated as if they are not jumps. continue; default: throw ExceptionUtilities.UnexpectedValue(pending.Branch.Kind); } _branchesOutOf.Add((StatementSyntax)pending.Branch.Syntax); } base.LeaveRegion(); }
private void VisitSwitchBlock(BoundSwitchStatement node) { var initialState = State.Clone(); var reachableLabels = node.DecisionDag.ReachableLabels; foreach (var section in node.SwitchSections) { foreach (var label in section.SwitchLabels) { if (reachableLabels.Contains(label.Label) || label.HasErrors || label == node.DefaultLabel && node.Expression.ConstantValue == null && IsTraditionalSwitch(node)) { SetState(initialState.Clone()); } else { SetUnreachable(); } VisitPattern(label.Pattern); SetState(StateWhenTrue); if (label.WhenClause != null) { VisitCondition(label.WhenClause); SetState(StateWhenTrue); } PendingBranches.Add(new PendingBranch(label, this.State, label.Label)); } } // visit switch sections var afterSwitchState = UnreachableState(); var switchSections = node.SwitchSections; var iLastSection = (switchSections.Length - 1); for (var iSection = 0; iSection <= iLastSection; iSection++) { VisitSwitchSection(switchSections[iSection], iSection == iLastSection); // Even though it is illegal for the end of a switch section to be reachable, in erroneous // code it may be reachable. We treat that as an implicit break (branch to afterSwitchState). Join(ref afterSwitchState, ref this.State); } if (reachableLabels.Contains(node.BreakLabel) || node.DefaultLabel == null && IsTraditionalSwitch(node)) { Join(ref afterSwitchState, ref initialState); } ResolveBreaks(afterSwitchState, node.BreakLabel); }
public override BoundNode VisitSwitchDispatch(BoundSwitchDispatch node) { VisitRvalue(node.Expression); var state = this.State.Clone(); PendingBranches.Add(new PendingBranch(node, state, node.DefaultLabel)); foreach ((_, LabelSymbol label) in node.Cases) { PendingBranches.Add(new PendingBranch(node, state, label)); } SetUnreachable(); return(null); }
protected override LocalState VisitSwitchStatementDispatch(BoundSwitchStatement node) { // first, learn from any null tests in the patterns int slot = node.Expression.IsSuppressed ? GetOrCreatePlaceholderSlot(node.Expression) : MakeSlot(node.Expression); if (slot > 0) { var originalInputType = node.Expression.Type; foreach (var section in node.SwitchSections) { foreach (var label in section.SwitchLabels) { LearnFromAnyNullPatterns(slot, originalInputType, label.Pattern); } } } // visit switch header Visit(node.Expression); var expressionState = ResultType; var initialState = PossiblyConditionalState.Create(this); DeclareLocals(node.InnerLocals); foreach (var section in node.SwitchSections) { // locals can be alive across jumps in the switch sections, so we declare them early. DeclareLocals(section.Locals); } var labelStateMap = LearnFromDecisionDag(node.Syntax, node.DecisionDag, node.Expression, expressionState, ref initialState); foreach (var section in node.SwitchSections) { foreach (var label in section.SwitchLabels) { var labelResult = labelStateMap.TryGetValue(label.Label, out var s1) ? s1 : (state : UnreachableState(), believedReachable : false); SetState(labelResult.state); PendingBranches.Add(new PendingBranch(label, this.State, label.Label)); } } var afterSwitchState = labelStateMap.TryGetValue(node.BreakLabel, out var stateAndReachable) ? stateAndReachable.state : UnreachableState(); labelStateMap.Free(); return(afterSwitchState); }
public override BoundNode VisitReturnStatement(BoundReturnStatement node) { var result = base.VisitReturnStatement(node); // After processing a return statement, the very last pending branch is for that return statement. // If it is returning an allocated object, consider it to be disposed. if (result != null) { switch (result.Kind) { case HijackedBoundKindForValueHolder: { var holder = (BoundValueHolder)result; var returnBranch = PendingBranches.Last(); foreach (var c in holder.value.creations) { returnBranch.State.possiblyUndisposedCreations.Remove(c); if (returnBranch.State.possiblyDisposedCreations.Contains(c)) { // TODO: error: returning a value that may already have been disposed. } } break; } case BoundKind.NewT: case BoundKind.ObjectCreationExpression: PendingBranches.Last().State.possiblyUndisposedCreations.Remove((BoundExpression)result); break; default: break; } } return(result); }
protected override void LeaveRegion() { if (this.IsConditionalState) { // If the region is in a condition, then the state will be split and state.Assigned will // be null. Merge to get sensible results. _endOfRegionState = StateWhenTrue.Clone(); Join(ref _endOfRegionState, ref StateWhenFalse); } else { _endOfRegionState = this.State.Clone(); } foreach (var branch in PendingBranches.AsEnumerable()) { if (branch.Branch != null && RegionContains(branch.Branch.Syntax.Span) && !_labelsInside.Contains(branch.Label)) { Join(ref _endOfRegionState, ref branch.State); } } base.LeaveRegion(); }
public override BoundNode VisitLocalFunctionStatement(BoundLocalFunctionStatement localFunc) { 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(); if (!localFunc.WasCompilerGenerated) { EnterParameters(localFuncSymbol.Parameters); } // Captured variables are definitely assigned if they are assigned on // all branches into the local function, so we store all reads from // possibly unassigned captured variables and later report definite // assignment errors if any of the captured variables is not assigned // on a particular branch. // // Assignments to captured variables are also recorded, as calls to local functions // definitely assign captured variables if the variables are definitely assigned at // all branches out of the local function. var usages = GetOrCreateLocalFuncUsages(localFuncSymbol); var oldReads = usages.ReadVars.Clone(); usages.ReadVars.Clear(); 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 = null; if (!localFuncSymbol.Locations.IsDefaultOrEmpty) { location = localFuncSymbol.Locations[0]; } LeaveParameters(localFuncSymbol.Parameters, localFunc.Syntax, location); // Intersect the state of all branches out of the local function LocalState 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); } // Check for changes to the possibly unassigned and assigned sets // of captured variables if (RecordChangedVars(ref usages.WrittenVars, ref stateAtReturn, ref oldReads, ref usages.ReadVars) && usages.LocalFuncVisited) { // 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. The previous writes are always valid, // so they are stored in usages.WrittenVars, while the reads // may be invalidated by new writes, so we throw the results out. stateChangedAfterUse = true; usages.LocalFuncVisited = false; } this.State = savedState; this.currentSymbol = oldSymbol; return(null); }
public override BoundNode?VisitLocalFunctionStatement(BoundLocalFunctionStatement localFunc) { 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(); 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(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 = null; if (!localFuncSymbol.Locations.IsDefaultOrEmpty) { location = localFuncSymbol.Locations[0]; } 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; this.CurrentSymbol = oldSymbol; return(null); }
public override BoundNode VisitLocalFunctionStatement(BoundLocalFunctionStatement localFunc) { var oldMethodOrLambda = this.currentMethodOrLambda; var localFuncSymbol = localFunc.Symbol; this.currentMethodOrLambda = localFuncSymbol; var oldPending = SavePending(); // we do not support branches into a lambda // Local functions don't affect outer state and are analyzed // with everything unassigned and reachable var savedState = this.State; this.State = this.ReachableState(); var usages = GetOrCreateLocalFuncUsages(localFuncSymbol); var oldReads = usages.ReadVars; usages.ReadVars = BitVector.Empty; if (!localFunc.WasCompilerGenerated) { EnterParameters(localFuncSymbol.Parameters); } 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)); } VisitAlways(localFunc.Body); RestorePending(oldPending2); // process any forward branches within the lambda body ImmutableArray <PendingBranch> pendingReturns = RemoveReturns(); RestorePending(oldPending); Location location = null; if (!localFuncSymbol.Locations.IsDefaultOrEmpty) { location = localFuncSymbol.Locations[0]; } LeaveParameters(localFuncSymbol.Parameters, localFunc.Syntax, location); LocalState stateAtReturn = this.State; foreach (PendingBranch pending in pendingReturns) { this.State = pending.State; BoundNode branch = pending.Branch; LeaveParameters(localFuncSymbol.Parameters, branch?.Syntax, branch?.WasCompilerGenerated == true ? location : null); IntersectWith(ref stateAtReturn, ref this.State); } // Check for changes to the read and write sets if (RecordChangedVars(ref usages.WrittenVars, ref stateAtReturn, ref oldReads, ref usages.ReadVars) && usages.LocalFuncVisited) { stateChangedAfterUse = true; usages.LocalFuncVisited = false; } this.State = savedState; this.currentMethodOrLambda = oldMethodOrLambda; return(null); }