public override BoundNode VisitTryStatement(BoundTryStatement node) { BoundBlock tryBlock = (BoundBlock)this.Visit(node.TryBlock); var origSawAwait = _sawAwait; _sawAwait = false; var optimizing = _compilation.Options.OptimizationLevel == OptimizationLevel.Release; ImmutableArray<BoundCatchBlock> catchBlocks = // When optimizing and we have a try block without side-effects, we can discard the catch blocks. (optimizing && !HasSideEffects(tryBlock)) ? ImmutableArray<BoundCatchBlock>.Empty : this.VisitList(node.CatchBlocks); BoundBlock finallyBlockOpt = (BoundBlock)this.Visit(node.FinallyBlockOpt); _sawAwaitInExceptionHandler |= _sawAwait; _sawAwait |= origSawAwait; if (optimizing && !HasSideEffects(finallyBlockOpt)) { finallyBlockOpt = null; } return (catchBlocks.IsDefaultOrEmpty && finallyBlockOpt == null) ? (BoundNode)tryBlock : (BoundNode)node.Update(tryBlock, catchBlocks, finallyBlockOpt, node.PreferFaultHandler); }
public override BoundNode VisitTryStatement(BoundTryStatement node) { BoundBlock tryBlock = (BoundBlock)this.Visit(node.TryBlock); this.ExceptionHandleNesting++; ImmutableArray<BoundCatchBlock> catchBlocks = (ImmutableArray<BoundCatchBlock>)this.VisitList(node.CatchBlocks); BoundBlock finallyBlockOpt = (BoundBlock)this.Visit(node.FinallyBlockOpt); this.ExceptionHandleNesting--; return node.Update(tryBlock, catchBlocks, finallyBlockOpt, node.PreferFaultHandler); }
public override BoundNode VisitTryStatement(BoundTryStatement node) { BoundBlock tryBlock = (BoundBlock)this.Visit(node.TryBlock); var origSawAwait = _sawAwait; _sawAwait = false; ImmutableArray<BoundCatchBlock> catchBlocks = (ImmutableArray<BoundCatchBlock>)this.VisitList(node.CatchBlocks); BoundBlock finallyBlockOpt = (BoundBlock)this.Visit(node.FinallyBlockOpt); _sawAwaitInExceptionHandler |= _sawAwait; _sawAwait |= origSawAwait; return node.Update(tryBlock, catchBlocks, finallyBlockOpt, node.PreferFaultHandler); }
public override BoundNode VisitTryStatement(BoundTryStatement node) { var origSeenYield = _seenYield; var origLabels = this.currentLabels; // sibling try blocks do not see each other's yields _seenYield = false; this.currentLabels = null; base.VisitTryStatement(node); if (_seenYield) { // this try yields ! var yieldingTryLabels = _labelsInYieldingTrys; if (yieldingTryLabels == null) { _labelsInYieldingTrys = yieldingTryLabels = new Dictionary<BoundTryStatement, HashSet<LabelSymbol>>(); } yieldingTryLabels.Add(node, currentLabels); currentLabels = origLabels; } else { // this is a boring non-yielding try // currentLabels = currentLabels U origLabels ; if (currentLabels == null) { currentLabels = origLabels; } else if (origLabels != null) { currentLabels.UnionWith(origLabels); } } _seenYield = _seenYield | origSeenYield; return null; }
/// <summary> /// Returns true if given try or any of its nested try blocks contain yields /// </summary> public bool ContainsYields(BoundTryStatement statement) { return _labelsInYieldingTrys != null && _labelsInYieldingTrys.ContainsKey(statement); }
public override BoundNode VisitTryStatement(BoundTryStatement node) { var oldDispatches = _dispatches; var oldFinalizerState = _currentFinalizerState; var oldHasFinalizerState = _hasFinalizerState; _dispatches = null; _currentFinalizerState = -1; _hasFinalizerState = false; BoundBlock tryBlock = F.Block((BoundStatement)this.Visit(node.TryBlock)); GeneratedLabelSymbol dispatchLabel = null; if (_dispatches != null) { dispatchLabel = F.GenerateLabel("tryDispatch"); if (_hasFinalizerState) { // cause the current finalizer state to arrive here and then "return false" var finalizer = F.GenerateLabel("finalizer"); _dispatches.Add(finalizer, new List <int>() { _currentFinalizerState }); var skipFinalizer = F.GenerateLabel("skipFinalizer"); tryBlock = F.Block( F.HiddenSequencePoint(), Dispatch(), F.Goto(skipFinalizer), F.Label(finalizer), // code for the finalizer here GenerateSetBothStates(StateMachineStates.NotStartedStateMachine), GenerateReturn(false), F.Label(skipFinalizer), tryBlock); } else { tryBlock = F.Block( F.HiddenSequencePoint(), Dispatch(), tryBlock); } if (oldDispatches == null) { Debug.Assert(!oldHasFinalizerState); oldDispatches = new Dictionary <LabelSymbol, List <int> >(); } oldDispatches.Add(dispatchLabel, new List <int>(from kv in _dispatches.Values from n in kv orderby n select n)); } _hasFinalizerState = oldHasFinalizerState; _currentFinalizerState = oldFinalizerState; _dispatches = oldDispatches; ImmutableArray <BoundCatchBlock> catchBlocks = this.VisitList(node.CatchBlocks); BoundBlock finallyBlockOpt = node.FinallyBlockOpt == null ? null : F.Block( F.HiddenSequencePoint(), F.If( condition: ShouldEnterFinallyBlock(), thenClause: VisitFinally(node.FinallyBlockOpt) ), F.HiddenSequencePoint()); BoundStatement result = node.Update(tryBlock, catchBlocks, finallyBlockOpt, node.FinallyLabelOpt, node.PreferFaultHandler); if ((object)dispatchLabel != null) { result = F.Block( F.HiddenSequencePoint(), F.Label(dispatchLabel), result); } return(result); }
/// <summary> /// The try statement is the most complex part of the state machine transformation. /// Since the CLR will not allow a 'goto' into the scope of a try statement, we must /// generate the dispatch to the state's label stepwise. That is done by translating /// the try statements from the inside to the outside. Within a try statement, we /// start with an empty dispatch table (representing the mapping from state numbers /// to labels). During translation of the try statement's body, the dispatch table /// will be filled in with the data necessary to dispatch once we're inside the try /// block. We generate that at the head of the translated try statement. Then, we /// copy all of the states from that table into the table for the enclosing construct, /// but associate them with a label just before the translated try block. That way /// the enclosing construct will generate the code necessary to get control into the /// try block for all of those states. /// </summary> public override BoundNode VisitTryStatement(BoundTryStatement node) { var oldDispatches = dispatches; var oldFinalizerState = currentFinalizerState; var oldHasFinalizerState = hasFinalizerState; dispatches = null; currentFinalizerState = -1; hasFinalizerState = false; BoundBlock tryBlock = F.Block((BoundStatement)this.Visit(node.TryBlock)); GeneratedLabelSymbol dispatchLabel = null; if (dispatches != null) { dispatchLabel = F.GenerateLabel("tryDispatch"); if (hasFinalizerState) { // cause the current finalizer state to arrive here and then "return false" var finalizer = F.GenerateLabel("finalizer"); dispatches.Add(finalizer, new List<int>() { this.currentFinalizerState }); var skipFinalizer = F.GenerateLabel("skipFinalizer"); tryBlock = F.Block( F.HiddenSequencePoint(), Dispatch(), F.Goto(skipFinalizer), F.Label(finalizer), // code for the finalizer here F.Assignment(F.Field(F.This(), stateField), F.AssignmentExpression(F.Local(cachedState), F.Literal(StateMachineStates.NotStartedStateMachine))), GenerateReturn(false), F.Label(skipFinalizer), tryBlock ); } else { tryBlock = F.Block( F.HiddenSequencePoint(), Dispatch(), tryBlock ); } if (oldDispatches == null) { Debug.Assert(!oldHasFinalizerState); oldDispatches = new Dictionary<LabelSymbol, List<int>>(); } oldDispatches.Add(dispatchLabel, new List<int>(from kv in dispatches.Values from n in kv orderby n select n)); } hasFinalizerState = oldHasFinalizerState; currentFinalizerState = oldFinalizerState; dispatches = oldDispatches; ImmutableArray<BoundCatchBlock> catchBlocks = this.VisitList(node.CatchBlocks); BoundBlock finallyBlockOpt = node.FinallyBlockOpt == null ? null : F.Block( F.HiddenSequencePoint(), F.If( condition: F.IntLessThan(F.Local(cachedState), F.Literal(StateMachineStates.FirstUnusedState)), thenClause: (BoundBlock)this.Visit(node.FinallyBlockOpt) ), F.HiddenSequencePoint()); BoundStatement result = node.Update(tryBlock, catchBlocks, finallyBlockOpt, node.PreferFaultHandler); if ((object)dispatchLabel != null) { result = F.Block( F.HiddenSequencePoint(), F.Label(dispatchLabel), result); } return result; }
/// <summary> /// Returns true if a finally of the given try contains awaits /// </summary> public bool FinallyContainsAwaits(BoundTryStatement statement) { return _labelsInInterestingTry != null && _labelsInInterestingTry.ContainsKey(statement); }
public override BoundNode VisitTryStatement(BoundTryStatement node) { var origLabels = this.currentLabels; this.currentLabels = null; Visit(node.TryBlock); VisitList(node.CatchBlocks); var origSeenAwait = _seenAwait; _seenAwait = false; Visit(node.FinallyBlockOpt); if (_seenAwait) { // this try has awaits in the finally ! var labelsInInterestingTry = _labelsInInterestingTry; if (labelsInInterestingTry == null) { _labelsInInterestingTry = labelsInInterestingTry = new Dictionary<BoundTryStatement, HashSet<LabelSymbol>>(); } labelsInInterestingTry.Add(node, currentLabels); currentLabels = origLabels; } else { // this is a boring try without awaits in finally // currentLabels = currentLabels U origLabels ; if (currentLabels == null) { currentLabels = origLabels; } else if (origLabels != null) { currentLabels.UnionWith(origLabels); } } _seenAwait = _seenAwait | origSeenAwait; return null; }
private BoundStatement RewriteUsingStatementTryFinally(SyntaxNode syntax, BoundBlock tryBlock, BoundLocal local, SyntaxToken awaitKeywordOpt, AwaitableInfo awaitOpt, MethodSymbol methodOpt) { // SPEC: When ResourceType is a non-nullable value type, the expansion is: // SPEC: // SPEC: { // SPEC: ResourceType resource = expr; // SPEC: try { statement; } // SPEC: finally { ((IDisposable)resource).Dispose(); } // SPEC: } // SPEC: // SPEC: Otherwise, when Resource type is a nullable value type or // SPEC: a reference type other than dynamic, the expansion is: // SPEC: // SPEC: { // SPEC: ResourceType resource = expr; // SPEC: try { statement; } // SPEC: finally { if (resource != null) ((IDisposable)resource).Dispose(); } // SPEC: } // SPEC: // SPEC: Otherwise, when ResourceType is dynamic, the expansion is: // SPEC: { // SPEC: dynamic resource = expr; // SPEC: IDisposable d = (IDisposable)resource; // SPEC: try { statement; } // SPEC: finally { if (d != null) d.Dispose(); } // SPEC: } // SPEC: // SPEC: An implementation is permitted to implement a given using statement // SPEC: differently -- for example, for performance reasons -- as long as the // SPEC: behavior is consistent with the above expansion. // // In the case of using-await statement, we'll use "IAsyncDisposable" instead of "IDisposable", "await DisposeAsync()" instead of "Dispose()" // // And we do in fact generate the code slightly differently than precisely how it is // described above. // // First: if the type is a non-nullable value type then we do not do the // *boxing conversion* from the resource to IDisposable. Rather, we do // a *constrained virtual call* that elides the boxing if possible. // // Now, you might wonder if that is legal; isn't skipping the boxing producing // an observable difference? Because if the value type is mutable and the Dispose // mutates it, then skipping the boxing means that we are now mutating the original, // not the boxed copy. But this is never observable. Either (1) we have "using(R r = x){}" // and r is out of scope after the finally, so it is not possible to observe the mutation, // or (2) we have "using(x) {}". But that has the semantics of "using(R temp = x){}", // so again, we are not mutating x to begin with; we're always mutating a copy. Therefore // it doesn't matter if we skip making *a copy of the copy*. // // This is what the dev10 compiler does, and we do so as well. // // Second: if the type is a nullable value type then we can similarly elide the boxing. // We can generate // // { // ResourceType resource = expr; // try { statement; } // finally { if (resource.HasValue) resource.GetValueOrDefault().Dispose(); } // } // // Where again we do a constrained virtual call to Dispose, rather than boxing // the value to IDisposable. // // Note that this optimization is *not* what the native compiler does; in this case // the native compiler behavior is to test for HasValue, then *box* and convert // the boxed value to IDisposable. There's no need to do that. // // Third: if we have "using(x)" and x is dynamic then obviously we need not generate // "{ dynamic temp1 = x; IDisposable temp2 = (IDisposable) temp1; ... }". Rather, we elide // the completely unnecessary first temporary. Debug.Assert((awaitKeywordOpt == default) == (awaitOpt == default(AwaitableInfo))); BoundExpression disposedExpression; bool isNullableValueType = local.Type.IsNullableType(); if (isNullableValueType) { MethodSymbol getValueOrDefault = UnsafeGetNullableMethod(syntax, local.Type, SpecialMember.System_Nullable_T_GetValueOrDefault); // local.GetValueOrDefault() disposedExpression = BoundCall.Synthesized(syntax, local, getValueOrDefault); } else { // local disposedExpression = local; } BoundExpression disposeCall = GenerateDisposeCall(syntax, disposedExpression, methodOpt, awaitOpt, awaitKeywordOpt); // local.Dispose(); or await variant BoundStatement disposeStatement = new BoundExpressionStatement(syntax, disposeCall); BoundExpression ifCondition; if (isNullableValueType) { // local.HasValue ifCondition = MakeNullableHasValue(syntax, local); } else if (local.Type.IsValueType) { ifCondition = null; } else { // local != null ifCondition = MakeNullCheck(syntax, local, BinaryOperatorKind.NotEqual); } BoundStatement finallyStatement; if (ifCondition == null) { // local.Dispose(); or await variant finallyStatement = disposeStatement; } else { // if (local != null) local.Dispose(); // or // if (local.HasValue) local.GetValueOrDefault().Dispose(); // or // await variants finallyStatement = RewriteIfStatement( syntax: syntax, rewrittenCondition: ifCondition, rewrittenConsequence: disposeStatement, rewrittenAlternativeOpt: null, hasErrors: false); } // try { ... } finally { if (local != null) local.Dispose(); } // or // nullable or await variants BoundStatement tryFinally = new BoundTryStatement( syntax: syntax, tryBlock: tryBlock, catchBlocks: ImmutableArray <BoundCatchBlock> .Empty, finallyBlockOpt: BoundBlock.SynthesizedNoLocals(syntax, finallyStatement)); return(tryFinally); }
/// <summary> /// Rewrites Try/Catch part of the Try/Catch/Finally /// </summary> private BoundStatement RewriteFinalizedRegion(BoundTryStatement node) { var rewrittenTry = (BoundBlock)this.VisitBlock(node.TryBlock); var catches = node.CatchBlocks; if (catches.IsDefaultOrEmpty) { return rewrittenTry; } var origAwaitCatchFrame = _currentAwaitCatchFrame; _currentAwaitCatchFrame = null; var rewrittenCatches = this.VisitList(node.CatchBlocks); BoundStatement tryWithCatches = _F.Try(rewrittenTry, rewrittenCatches); var currentAwaitCatchFrame = _currentAwaitCatchFrame; if (currentAwaitCatchFrame != null) { var handledLabel = _F.GenerateLabel("handled"); var handlersList = currentAwaitCatchFrame.handlers; var handlers = ArrayBuilder<BoundSwitchSection>.GetInstance(handlersList.Count); for (int i = 0, l = handlersList.Count; i < l; i++) { handlers.Add(_F.SwitchSection( i + 1, _F.Block( handlersList[i], _F.Goto(handledLabel)))); } tryWithCatches = _F.Block( ImmutableArray.Create<LocalSymbol>( currentAwaitCatchFrame.pendingCaughtException, currentAwaitCatchFrame.pendingCatch). AddRange(currentAwaitCatchFrame.GetHoistedLocals()), ImmutableArray<LocalFunctionSymbol>.Empty, _F.HiddenSequencePoint(), _F.Assignment( _F.Local(currentAwaitCatchFrame.pendingCatch), _F.Default(currentAwaitCatchFrame.pendingCatch.Type)), tryWithCatches, _F.HiddenSequencePoint(), _F.Switch( _F.Local(currentAwaitCatchFrame.pendingCatch), handlers.ToImmutableAndFree()), _F.HiddenSequencePoint(), _F.Label(handledLabel)); } _currentAwaitCatchFrame = origAwaitCatchFrame; return tryWithCatches; }
/// <summary> /// Returns true if given try or any of its nested try blocks contain yields /// </summary> public bool ContainsYields(BoundTryStatement statement) { return(_labelsInYieldingTrys != null && _labelsInYieldingTrys.ContainsKey(statement)); }
/// <summary> /// Labels reachable from within this frame without invoking its finally. /// null if there are none such labels. /// </summary> internal HashSet <LabelSymbol> Labels(BoundTryStatement statement) { return(_labelsInYieldingTrys[statement]); }
/// <summary> /// Labels reachable from within this frame without invoking its finally. /// null if there are no such labels. /// </summary> internal HashSet <LabelSymbol> Labels(BoundTryStatement statement) { return(_labelsInInterestingTry[statement]); }
/// <summary> /// Returns true if a finally of the given try contains awaits /// </summary> public bool FinallyContainsAwaits(BoundTryStatement statement) { return(_labelsInInterestingTry != null && _labelsInInterestingTry.ContainsKey(statement)); }
public override BoundNode VisitTryStatement(BoundTryStatement node) { var tryStatementSyntax = node.Syntax; // If you add a syntax kind to the assertion below, please also ensure // that the scenario has been tested with Edit-and-Continue. Debug.Assert( tryStatementSyntax.IsKind(SyntaxKind.TryStatement) || tryStatementSyntax.IsKind(SyntaxKind.UsingStatement) || tryStatementSyntax.IsKind(SyntaxKind.ForEachStatement) || tryStatementSyntax.IsKind(SyntaxKind.ForEachVariableStatement) || tryStatementSyntax.IsKind(SyntaxKind.LocalDeclarationStatement)); BoundStatement finalizedRegion; BoundBlock rewrittenFinally; var finallyContainsAwaits = _analysis.FinallyContainsAwaits(node); if (!finallyContainsAwaits) { finalizedRegion = RewriteFinalizedRegion(node); rewrittenFinally = (BoundBlock)this.Visit(node.FinallyBlockOpt); if (rewrittenFinally == null) { return(finalizedRegion); } var asTry = finalizedRegion as BoundTryStatement; if (asTry != null) { // since finalized region is a try we can just attach finally to it Debug.Assert(asTry.FinallyBlockOpt == null); return(asTry.Update(asTry.TryBlock, asTry.CatchBlocks, rewrittenFinally, asTry.FinallyLabelOpt, asTry.PreferFaultHandler)); } else { // wrap finalizedRegion into a Try with a finally. return(_F.Try((BoundBlock)finalizedRegion, ImmutableArray <BoundCatchBlock> .Empty, rewrittenFinally)); } } // rewrite finalized region (try and catches) in the current frame var frame = PushFrame(node); finalizedRegion = RewriteFinalizedRegion(node); rewrittenFinally = (BoundBlock)this.VisitBlock(node.FinallyBlockOpt); PopFrame(); var exceptionType = _F.SpecialType(SpecialType.System_Object); var pendingExceptionLocal = new SynthesizedLocal(_F.CurrentFunction, TypeSymbolWithAnnotations.Create(exceptionType), SynthesizedLocalKind.TryAwaitPendingException, tryStatementSyntax); var finallyLabel = _F.GenerateLabel("finallyLabel"); var pendingBranchVar = new SynthesizedLocal(_F.CurrentFunction, TypeSymbolWithAnnotations.Create(_F.SpecialType(SpecialType.System_Int32)), SynthesizedLocalKind.TryAwaitPendingBranch, tryStatementSyntax); var catchAll = _F.Catch(_F.Local(pendingExceptionLocal), _F.Block()); var catchAndPendException = _F.Try( _F.Block( finalizedRegion, _F.HiddenSequencePoint(), _F.Goto(finallyLabel), PendBranches(frame, pendingBranchVar, finallyLabel)), ImmutableArray.Create(catchAll), finallyLabel: finallyLabel); BoundBlock syntheticFinallyBlock = _F.Block( _F.HiddenSequencePoint(), _F.Label(finallyLabel), rewrittenFinally, _F.HiddenSequencePoint(), UnpendException(pendingExceptionLocal), UnpendBranches( frame, pendingBranchVar, pendingExceptionLocal)); BoundStatement syntheticFinally = syntheticFinallyBlock; if (_F.CurrentFunction.IsAsync && _F.CurrentFunction.IsIterator) { // We wrap this block so that it can be processed as a finally block by async-iterator rewriting syntheticFinally = _F.ExtractedFinallyBlock(syntheticFinallyBlock); } var locals = ArrayBuilder <LocalSymbol> .GetInstance(); var statements = ArrayBuilder <BoundStatement> .GetInstance(); statements.Add(_F.HiddenSequencePoint()); locals.Add(pendingExceptionLocal); statements.Add(_F.Assignment(_F.Local(pendingExceptionLocal), _F.Default(pendingExceptionLocal.Type.TypeSymbol))); locals.Add(pendingBranchVar); statements.Add(_F.Assignment(_F.Local(pendingBranchVar), _F.Default(pendingBranchVar.Type.TypeSymbol))); LocalSymbol returnLocal = frame.returnValue; if (returnLocal != null) { locals.Add(returnLocal); } statements.Add(catchAndPendException); statements.Add(syntheticFinally); var completeTry = _F.Block( locals.ToImmutableAndFree(), statements.ToImmutableAndFree()); return(completeTry); }
protected virtual void VisitTryBlock(BoundStatement tryBlock, BoundTryStatement node, ref TLocalState tryState) { VisitStatement(tryBlock); }
public override BoundNode VisitTryStatement(BoundTryStatement node) { BoundBlock?tryBlock = (BoundBlock?)this.Visit(node.TryBlock); Debug.Assert(tryBlock is { });
public override BoundNode VisitTryStatement(BoundTryStatement node) { var tryStatementSyntax = node.Syntax; // If you add a syntax kind to the assertion below, please also ensure // that the scenario has been tested with Edit-and-Continue. Debug.Assert( tryStatementSyntax.IsKind(SyntaxKind.TryStatement) || tryStatementSyntax.IsKind(SyntaxKind.UsingStatement) || tryStatementSyntax.IsKind(SyntaxKind.ForEachStatement)); BoundStatement finalizedRegion; BoundBlock rewrittenFinally; var finallyContainsAwaits = _analysis.FinallyContainsAwaits(node); if (!finallyContainsAwaits) { finalizedRegion = RewriteFinalizedRegion(node); rewrittenFinally = (BoundBlock)this.Visit(node.FinallyBlockOpt); if (rewrittenFinally == null) { return finalizedRegion; } var asTry = finalizedRegion as BoundTryStatement; if (asTry != null) { // since finalized region is a try we can just attach finally to it Debug.Assert(asTry.FinallyBlockOpt == null); return asTry.Update(asTry.TryBlock, asTry.CatchBlocks, rewrittenFinally, asTry.PreferFaultHandler); } else { // wrap finalizedRegion into a Try with a finally. return _F.Try((BoundBlock)finalizedRegion, ImmutableArray<BoundCatchBlock>.Empty, rewrittenFinally); } } // rewrite finalized region (try and catches) in the current frame var frame = PushFrame(node); finalizedRegion = RewriteFinalizedRegion(node); rewrittenFinally = (BoundBlock)this.VisitBlock(node.FinallyBlockOpt); PopFrame(); var exceptionType = _F.SpecialType(SpecialType.System_Object); var pendingExceptionLocal = new SynthesizedLocal(_F.CurrentMethod, exceptionType, SynthesizedLocalKind.TryAwaitPendingException, tryStatementSyntax); var finallyLabel = _F.GenerateLabel("finallyLabel"); var pendingBranchVar = new SynthesizedLocal(_F.CurrentMethod, _F.SpecialType(SpecialType.System_Int32), SynthesizedLocalKind.TryAwaitPendingBranch, tryStatementSyntax); var catchAll = _F.Catch(_F.Local(pendingExceptionLocal), _F.Block()); var catchAndPendException = _F.Try( _F.Block( finalizedRegion, _F.HiddenSequencePoint(), _F.Goto(finallyLabel), PendBranches(frame, pendingBranchVar, finallyLabel)), ImmutableArray.Create(catchAll)); var syntheticFinally = _F.Block( _F.HiddenSequencePoint(), _F.Label(finallyLabel), rewrittenFinally, _F.HiddenSequencePoint(), UnpendException(pendingExceptionLocal), UnpendBranches( frame, pendingBranchVar, pendingExceptionLocal)); var locals = ArrayBuilder<LocalSymbol>.GetInstance(); var statements = ArrayBuilder<BoundStatement>.GetInstance(); statements.Add(_F.HiddenSequencePoint()); locals.Add(pendingExceptionLocal); statements.Add(_F.Assignment(_F.Local(pendingExceptionLocal), _F.Default(pendingExceptionLocal.Type))); locals.Add(pendingBranchVar); statements.Add(_F.Assignment(_F.Local(pendingBranchVar), _F.Default(pendingBranchVar.Type))); LocalSymbol returnLocal = frame.returnValue; if (returnLocal != null) { locals.Add(returnLocal); } statements.Add(catchAndPendException); statements.Add(syntheticFinally); var completeTry = _F.Block( locals.ToImmutableAndFree(), ImmutableArray<LocalFunctionSymbol>.Empty, statements.ToImmutableAndFree()); return completeTry; }
public override BoundNode VisitTryStatement(BoundTryStatement node) { // if node contains no yields, do regular rewrite in the current frame. if (!ContainsYields(node)) { _tryNestingLevel++; var result = node.Update( (BoundBlock)Visit(node.TryBlock), VisitList(node.CatchBlocks), (BoundBlock)Visit(node.FinallyBlockOpt), node.FinallyLabelOpt, node.PreferFaultHandler); _tryNestingLevel--; return(result); } Debug.Assert(node.CatchBlocks.IsEmpty, "try with yields must have no catches"); Debug.Assert(node.FinallyBlockOpt != null, "try with yields must have finally"); // rewrite TryBlock in a new frame. var frame = PushFrame(node); _tryNestingLevel++; var rewrittenBody = (BoundStatement)this.Visit(node.TryBlock); Debug.Assert(!frame.IsRoot()); Debug.Assert(frame.parent.knownStates.ContainsValue(frame), "parent must be aware about states in the child frame"); var finallyMethod = frame.handler; var origMethod = F.CurrentFunction; // rewrite finally block into a Finally method. F.CurrentFunction = finallyMethod; var rewrittenHandler = (BoundStatement)this.Visit(node.FinallyBlockOpt); _tryNestingLevel--; PopFrame(); // { // this.state = parentFinalizeState; // body; // return; // } Debug.Assert(frame.parent.finalizeState == _currentFinallyFrame.finalizeState); rewrittenHandler = F.Block((object)this.cachedThis != null ? ImmutableArray.Create(this.cachedThis) : ImmutableArray <LocalSymbol> .Empty, F.Assignment(F.Field(F.This(), stateField), F.Literal(frame.parent.finalizeState)), CacheThisIfNeeded(), rewrittenHandler, F.Return() ); F.CloseMethod(rewrittenHandler); F.CurrentFunction = origMethod; var bodyStatements = ArrayBuilder <BoundStatement> .GetInstance(); // add a call to the handler after the try body. // // { // this.state = finalizeState; // body; // this.Finally(); // will reset the state to the finally state of the parent. // } bodyStatements.Add(F.Assignment(F.Field(F.This(), stateField), F.Literal(frame.finalizeState))); bodyStatements.Add(rewrittenBody); bodyStatements.Add(F.ExpressionStatement(F.Call(F.This(), finallyMethod))); // handle proxy labels if have any if (frame.proxyLabels != null) { var dropThrough = F.GenerateLabel("dropThrough"); bodyStatements.Add(F.Goto(dropThrough)); var parent = frame.parent; foreach (var p in frame.proxyLabels) { var proxy = p.Value; var destination = p.Key; // branch lands here bodyStatements.Add(F.Label(proxy)); // finalize current state, proceed to destination. bodyStatements.Add(F.ExpressionStatement(F.Call(F.This(), finallyMethod))); // let the parent forward the branch appropriately var parentProxy = parent.ProxyLabelIfNeeded(destination); bodyStatements.Add(F.Goto(parentProxy)); } bodyStatements.Add(F.Label(dropThrough)); } return(F.Block(bodyStatements.ToImmutableAndFree())); }
private AwaitFinallyFrame PushFrame(BoundTryStatement statement) { var newFrame = new AwaitFinallyFrame(_currentAwaitFinallyFrame, _analysis.Labels(statement), (TryStatementSyntax)statement.Syntax); _currentAwaitFinallyFrame = newFrame; return newFrame; }
public override BoundNode VisitTryStatement(BoundTryStatement node) { var tryStatementSyntax = node.Syntax; Debug.Assert(tryStatementSyntax.IsKind(SyntaxKind.TryStatement)); BoundStatement finalizedRegion; BoundBlock rewrittenFinally; var finallyContainsAwaits = _analysis.FinallyContainsAwaits(node); if (!finallyContainsAwaits) { finalizedRegion = RewriteFinalizedRegion(node); rewrittenFinally = (BoundBlock)this.Visit(node.FinallyBlockOpt); if (rewrittenFinally == null) { return(finalizedRegion); } var asTry = finalizedRegion as BoundTryStatement; if (asTry != null) { // since finalized region is a try we can just attach finally to it Debug.Assert(asTry.FinallyBlockOpt == null); return(asTry.Update(asTry.TryBlock, asTry.CatchBlocks, rewrittenFinally, asTry.PreferFaultHandler)); } else { // wrap finalizedRegion into a Try with a finally. return(_F.Try((BoundBlock)finalizedRegion, ImmutableArray <BoundCatchBlock> .Empty, rewrittenFinally)); } } // rewrite finalized region (try and catches) in the current frame var frame = PushFrame(node); finalizedRegion = RewriteFinalizedRegion(node); rewrittenFinally = (BoundBlock)this.VisitBlock(node.FinallyBlockOpt); PopFrame(); var exceptionType = _F.SpecialType(SpecialType.System_Object); var pendingExceptionLocal = new SynthesizedLocal(_F.CurrentMethod, exceptionType, SynthesizedLocalKind.TryAwaitPendingException, tryStatementSyntax); var finallyLabel = _F.GenerateLabel("finallyLabel"); var pendingBranchVar = new SynthesizedLocal(_F.CurrentMethod, _F.SpecialType(SpecialType.System_Int32), SynthesizedLocalKind.TryAwaitPendingBranch, tryStatementSyntax); var catchAll = _F.Catch(_F.Local(pendingExceptionLocal), _F.Block()); var catchAndPendException = _F.Try( _F.Block( finalizedRegion, _F.HiddenSequencePoint(), _F.Goto(finallyLabel), PendBranches(frame, pendingBranchVar, finallyLabel)), ImmutableArray.Create(catchAll)); var syntheticFinally = _F.Block( _F.HiddenSequencePoint(), _F.Label(finallyLabel), rewrittenFinally, _F.HiddenSequencePoint(), UnpendException(pendingExceptionLocal), UnpendBranches( frame, pendingBranchVar, pendingExceptionLocal)); var locals = ArrayBuilder <LocalSymbol> .GetInstance(); var statements = ArrayBuilder <BoundStatement> .GetInstance(); statements.Add(_F.HiddenSequencePoint()); locals.Add(pendingExceptionLocal); statements.Add(_F.Assignment(_F.Local(pendingExceptionLocal), _F.Default(pendingExceptionLocal.Type))); locals.Add(pendingBranchVar); statements.Add(_F.Assignment(_F.Local(pendingBranchVar), _F.Default(pendingBranchVar.Type))); LocalSymbol returnLocal = frame.returnValue; if (returnLocal != null) { locals.Add(returnLocal); } statements.Add(catchAndPendException); statements.Add(syntheticFinally); var completeTry = _F.Block( locals.ToImmutableAndFree(), statements.ToImmutableAndFree()); return(completeTry); }
/// <summary> /// Labels reachable from within this frame without invoking its finally. /// null if there are no such labels. /// </summary> internal HashSet<LabelSymbol> Labels(BoundTryStatement statement) { return _labelsInInterestingTry[statement]; }
private IteratorFinallyFrame PushFrame(BoundTryStatement statement) { var state = _nextFinalizeState--; var finallyMethod = MakeSynthesizedFinally(state); var newFrame = new IteratorFinallyFrame(_currentFinallyFrame, state, finallyMethod, _yieldsInTryAnalysis.Labels(statement)); newFrame.AddState(state); _currentFinallyFrame = newFrame; return newFrame; }
private bool ContainsYields(BoundTryStatement statement) { return(_yieldsInTryAnalysis.ContainsYields(statement)); }
/// <summary> /// Lower a foreach loop that will enumerate a collection using an enumerator. /// /// E e = ((C)(x)).GetEnumerator() /// try { /// while (e.MoveNext()) { /// V v = (V)(T)e.Current; /// // body /// } /// } /// finally { /// // clean up e /// } /// </summary> private BoundStatement RewriteEnumeratorForEachStatement(BoundForEachStatement node) { ForEachStatementSyntax forEachSyntax = (ForEachStatementSyntax)node.Syntax; ForEachEnumeratorInfo enumeratorInfo = node.EnumeratorInfoOpt; Debug.Assert(enumeratorInfo != null); BoundExpression collectionExpression = GetUnconvertedCollectionExpression(node); BoundExpression rewrittenExpression = (BoundExpression)Visit(collectionExpression); BoundStatement rewrittenBody = (BoundStatement)Visit(node.Body); TypeSymbol enumeratorType = enumeratorInfo.GetEnumeratorMethod.ReturnType; TypeSymbol elementType = enumeratorInfo.ElementType; // E e LocalSymbol enumeratorVar = _factory.SynthesizedLocal(enumeratorType, syntax: forEachSyntax, kind: SynthesizedLocalKind.ForEachEnumerator); // Reference to e. BoundLocal boundEnumeratorVar = MakeBoundLocal(forEachSyntax, enumeratorVar, enumeratorType); // ((C)(x)).GetEnumerator() or (x).GetEnumerator(); BoundExpression enumeratorVarInitValue = SynthesizeCall(forEachSyntax, rewrittenExpression, enumeratorInfo.GetEnumeratorMethod, enumeratorInfo.CollectionConversion, enumeratorInfo.CollectionType); // E e = ((C)(x)).GetEnumerator(); BoundStatement enumeratorVarDecl = MakeLocalDeclaration(forEachSyntax, enumeratorVar, enumeratorVarInitValue); AddForEachExpressionSequencePoint(forEachSyntax, ref enumeratorVarDecl); // V v LocalSymbol iterationVar = node.IterationVariable; //(V)(T)e.Current BoundExpression iterationVarAssignValue = MakeConversion( syntax: forEachSyntax, rewrittenOperand: MakeConversion( syntax: forEachSyntax, rewrittenOperand: BoundCall.Synthesized( syntax: forEachSyntax, receiverOpt: boundEnumeratorVar, method: enumeratorInfo.CurrentPropertyGetter), conversion: enumeratorInfo.CurrentConversion, rewrittenType: elementType, @checked: node.Checked), conversion: node.ElementConversion, rewrittenType: iterationVar.Type, @checked: node.Checked); // V v = (V)(T)e.Current; BoundStatement iterationVarDecl = MakeLocalDeclaration(forEachSyntax, iterationVar, iterationVarAssignValue); AddForEachIterationVariableSequencePoint(forEachSyntax, ref iterationVarDecl); // while (e.MoveNext()) { // V v = (V)(T)e.Current; // /* node.Body */ // } var rewrittenBodyBlock = CreateBlockDeclaringIterationVariable(iterationVar, iterationVarDecl, rewrittenBody, forEachSyntax); BoundStatement whileLoop = RewriteWhileStatement( syntax: forEachSyntax, rewrittenCondition: BoundCall.Synthesized( syntax: forEachSyntax, receiverOpt: boundEnumeratorVar, method: enumeratorInfo.MoveNextMethod), conditionSequencePointSpan: forEachSyntax.InKeyword.Span, rewrittenBody: rewrittenBodyBlock, breakLabel: node.BreakLabel, continueLabel: node.ContinueLabel, hasErrors: false); BoundStatement result; MethodSymbol disposeMethod; if (enumeratorInfo.NeedsDisposeMethod && Binder.TryGetSpecialTypeMember(_compilation, SpecialMember.System_IDisposable__Dispose, forEachSyntax, _diagnostics, out disposeMethod)) { Binder.ReportDiagnosticsIfObsolete(_diagnostics, disposeMethod, forEachSyntax, hasBaseReceiver: false, containingMember: _factory.CurrentMethod, containingType: _factory.CurrentType, location: enumeratorInfo.Location); BoundBlock finallyBlockOpt; var idisposableTypeSymbol = disposeMethod.ContainingType; var conversions = new TypeConversions(_factory.CurrentMethod.ContainingAssembly.CorLibrary); HashSet <DiagnosticInfo> useSiteDiagnostics = null; var isImplicit = conversions.ClassifyImplicitConversion(enumeratorType, idisposableTypeSymbol, ref useSiteDiagnostics).IsImplicit; _diagnostics.Add(forEachSyntax, useSiteDiagnostics); if (isImplicit) { Debug.Assert(enumeratorInfo.NeedsDisposeMethod); Conversion receiverConversion = enumeratorType.IsStructType() ? Conversion.Boxing : Conversion.ImplicitReference; // ((IDisposable)e).Dispose(); or e.Dispose(); BoundStatement disposeCall = new BoundExpressionStatement(forEachSyntax, expression: SynthesizeCall(forEachSyntax, boundEnumeratorVar, disposeMethod, receiverConversion, idisposableTypeSymbol)); BoundStatement disposeStmt; if (enumeratorType.IsValueType) { // No way for the struct to be nullable and disposable. Debug.Assert(((TypeSymbol)enumeratorType.OriginalDefinition).SpecialType != SpecialType.System_Nullable_T); // For non-nullable structs, no null check is required. disposeStmt = disposeCall; } else { // NB: cast to object missing from spec. Needed to ignore user-defined operators and box type parameters. // if ((object)e != null) ((IDisposable)e).Dispose(); disposeStmt = RewriteIfStatement( syntax: forEachSyntax, rewrittenCondition: new BoundBinaryOperator(forEachSyntax, operatorKind: BinaryOperatorKind.NotEqual, left: MakeConversion( syntax: forEachSyntax, rewrittenOperand: boundEnumeratorVar, conversion: enumeratorInfo.EnumeratorConversion, rewrittenType: _compilation.GetSpecialType(SpecialType.System_Object), @checked: false), right: MakeLiteral(forEachSyntax, constantValue: ConstantValue.Null, type: null), constantValueOpt: null, methodOpt: null, resultKind: LookupResultKind.Viable, type: _compilation.GetSpecialType(SpecialType.System_Boolean)), rewrittenConsequence: disposeCall, rewrittenAlternativeOpt: null, hasErrors: false); } finallyBlockOpt = new BoundBlock(forEachSyntax, locals: ImmutableArray <LocalSymbol> .Empty, statements: ImmutableArray.Create <BoundStatement>(disposeStmt)); } else { Debug.Assert(!enumeratorType.IsSealed); // IDisposable d LocalSymbol disposableVar = _factory.SynthesizedLocal(idisposableTypeSymbol); // Reference to d. BoundLocal boundDisposableVar = MakeBoundLocal(forEachSyntax, disposableVar, idisposableTypeSymbol); BoundTypeExpression boundIDisposableTypeExpr = new BoundTypeExpression(forEachSyntax, aliasOpt: null, type: idisposableTypeSymbol); // e as IDisposable BoundExpression disposableVarInitValue = new BoundAsOperator(forEachSyntax, operand: boundEnumeratorVar, targetType: boundIDisposableTypeExpr, conversion: Conversion.ExplicitReference, // Explicit so the emitter won't optimize it away. type: idisposableTypeSymbol); // IDisposable d = e as IDisposable; BoundStatement disposableVarDecl = MakeLocalDeclaration(forEachSyntax, disposableVar, disposableVarInitValue); // if (d != null) d.Dispose(); BoundStatement ifStmt = RewriteIfStatement( syntax: forEachSyntax, rewrittenCondition: new BoundBinaryOperator(forEachSyntax, operatorKind: BinaryOperatorKind.NotEqual, // reference equality left: boundDisposableVar, right: MakeLiteral(forEachSyntax, constantValue: ConstantValue.Null, type: null), constantValueOpt: null, methodOpt: null, resultKind: LookupResultKind.Viable, type: _compilation.GetSpecialType(SpecialType.System_Boolean)), rewrittenConsequence: new BoundExpressionStatement(forEachSyntax, expression: BoundCall.Synthesized( syntax: forEachSyntax, receiverOpt: boundDisposableVar, method: disposeMethod)), rewrittenAlternativeOpt: null, hasErrors: false); // IDisposable d = e as IDisposable; // if (d != null) d.Dispose(); finallyBlockOpt = new BoundBlock(forEachSyntax, locals: ImmutableArray.Create <LocalSymbol>(disposableVar), statements: ImmutableArray.Create <BoundStatement>(disposableVarDecl, ifStmt)); } // try { // while (e.MoveNext()) { // V v = (V)(T)e.Current; // /* loop body */ // } // } // finally { // /* dispose of e */ // } BoundStatement tryFinally = new BoundTryStatement(forEachSyntax, tryBlock: new BoundBlock(forEachSyntax, locals: ImmutableArray <LocalSymbol> .Empty, statements: ImmutableArray.Create <BoundStatement>(whileLoop)), catchBlocks: ImmutableArray <BoundCatchBlock> .Empty, finallyBlockOpt: finallyBlockOpt); // E e = ((C)(x)).GetEnumerator(); // try { // /* as above */ result = new BoundBlock( syntax: forEachSyntax, locals: ImmutableArray.Create(enumeratorVar), statements: ImmutableArray.Create <BoundStatement>(enumeratorVarDecl, tryFinally)); } else { // E e = ((C)(x)).GetEnumerator(); // while (e.MoveNext()) { // V v = (V)(T)e.Current; // /* loop body */ // } result = new BoundBlock( syntax: forEachSyntax, locals: ImmutableArray.Create(enumeratorVar), statements: ImmutableArray.Create <BoundStatement>(enumeratorVarDecl, whileLoop)); } AddForEachKeywordSequencePoint(forEachSyntax, ref result); return(result); }
public override BoundNode VisitTryStatement(BoundTryStatement node) { // if node contains no yields, do regular rewrite in the current frame. if (!ContainsYields(node)) { _tryNestingLevel++; var result = node.Update( (BoundBlock)Visit(node.TryBlock), VisitList(node.CatchBlocks), (BoundBlock)Visit(node.FinallyBlockOpt), node.PreferFaultHandler); _tryNestingLevel--; return result; } Debug.Assert(node.CatchBlocks.IsEmpty, "try with yields must have no catches"); Debug.Assert(node.FinallyBlockOpt != null, "try with yields must have finally"); // rewrite TryBlock in a new frame. var frame = PushFrame(node); _tryNestingLevel++; var rewrittenBody = (BoundStatement)this.Visit(node.TryBlock); Debug.Assert(!frame.IsRoot()); Debug.Assert(frame.parent.knownStates.ContainsValue(frame), "parent must be aware about states in the child frame"); var finallyMethod = frame.handler; var origMethod = F.CurrentMethod; // rewrite finally block into a Finally method. F.CurrentMethod = finallyMethod; var rewrittenHandler = (BoundStatement)this.Visit(node.FinallyBlockOpt); _tryNestingLevel--; PopFrame(); // { // this.state = parentFinalizeState; // body; // return; // } Debug.Assert(frame.parent.finalizeState == _currentFinallyFrame.finalizeState); rewrittenHandler = F.Block( F.Assignment(F.Field(F.This(), stateField), F.Literal(frame.parent.finalizeState)), rewrittenHandler, F.Return() ); F.CloseMethod(rewrittenHandler); F.CurrentMethod = origMethod; var bodyStatements = ArrayBuilder<BoundStatement>.GetInstance(); // add a call to the handler after the try body. // // { // this.state = finalizeState; // body; // this.Finally(); // will reset the state to the finally state of the parent. // } bodyStatements.Add(F.Assignment(F.Field(F.This(), stateField), F.Literal(frame.finalizeState))); bodyStatements.Add(rewrittenBody); bodyStatements.Add(F.ExpressionStatement(F.Call(F.This(), finallyMethod))); // handle proxy labels if have any if (frame.proxyLabels != null) { var dropThrough = F.GenerateLabel("dropThrough"); bodyStatements.Add(F.Goto(dropThrough)); var parent = frame.parent; foreach (var p in frame.proxyLabels) { var proxy = p.Value; var destination = p.Key; // branch lands here bodyStatements.Add(F.Label(proxy)); // finalize current state, proceed to destination. bodyStatements.Add(F.ExpressionStatement(F.Call(F.This(), finallyMethod))); // let the parent forward the branch appropriately var parentProxy = parent.ProxyLabelIfNeeded(destination); bodyStatements.Add(F.Goto(parentProxy)); } bodyStatements.Add(F.Label(dropThrough)); } return F.Block(bodyStatements.ToImmutableAndFree()); }
public override BoundNode VisitTryStatement(BoundTryStatement node) { BoundStatement finalizedRegion; BoundBlock rewrittenFinally; var finallyContainsAwaits = analysis.FinallyContainsAwaits(node); if (!finallyContainsAwaits) { finalizedRegion = RewriteFinalizedRegion(node); rewrittenFinally = (BoundBlock)this.Visit(node.FinallyBlockOpt); if (rewrittenFinally == null) { return finalizedRegion; } var asTry = finalizedRegion as BoundTryStatement; if (asTry != null) { // since finalized region is a try we can just attach finally to it Debug.Assert(asTry.FinallyBlockOpt == null); return asTry.Update(asTry.TryBlock, asTry.CatchBlocks, rewrittenFinally, asTry.PreferFaultHandler); } else { // wrap finalizedRegion into a Try with a finally. return F.Try((BoundBlock)finalizedRegion, ImmutableArray<BoundCatchBlock>.Empty, rewrittenFinally); } } // rewrite finalized region (try and catches) in the current frame var frame = PushFrame(node); finalizedRegion = RewriteFinalizedRegion(node); rewrittenFinally = (BoundBlock)this.VisitBlock(node.FinallyBlockOpt); PopFrame(); var exceptionType = F.SpecialType(SpecialType.System_Object); var pendingExceptionLocal = F.SynthesizedLocal(exceptionType); var finallyLabel = F.GenerateLabel("finallyLabel"); var pendingBranchVar = F.SynthesizedLocal(F.SpecialType(SpecialType.System_Int32)); var catchAll = F.Catch(F.Local(pendingExceptionLocal), F.Block()); var catchAndPendException = F.Try( F.Block( finalizedRegion, F.HiddenSequencePoint(), F.Goto(finallyLabel), PendBranches(frame, pendingBranchVar, finallyLabel)), ImmutableArray.Create(catchAll)); var syntheticFinally = F.Block( F.HiddenSequencePoint(), F.Label(finallyLabel), rewrittenFinally, F.HiddenSequencePoint(), UnpendException(pendingExceptionLocal), UnpendBranches( frame, pendingBranchVar, pendingExceptionLocal)); var locals = ArrayBuilder<LocalSymbol>.GetInstance(); var statements = ArrayBuilder<BoundStatement>.GetInstance(); statements.Add(F.HiddenSequencePoint()); locals.Add(pendingExceptionLocal); statements.Add(F.Assignment(F.Local(pendingExceptionLocal), F.Default(pendingExceptionLocal.Type))); locals.Add(pendingBranchVar); statements.Add(F.Assignment(F.Local(pendingBranchVar), F.Default(pendingBranchVar.Type))); LocalSymbol returnLocal = frame.returnValue; if (returnLocal != null) { locals.Add(returnLocal); } statements.Add(catchAndPendException); statements.Add(syntheticFinally); var completeTry = F.Block( locals.ToImmutableAndFree(), statements.ToImmutableAndFree()); return completeTry; }
private bool ContainsYields(BoundTryStatement statement) { return _yieldsInTryAnalysis.ContainsYields(statement); }
private AwaitFinallyFrame PushFrame(BoundTryStatement statement) { var newFrame = new AwaitFinallyFrame(currentAwaitFinallyFrame, analysis.Labels(statement)); currentAwaitFinallyFrame = newFrame; return newFrame; }
public override BoundNode VisitTryStatement(BoundTryStatement node) { var origSeenYieldInCurrentTry = _seenYieldInCurrentTry; _seenYieldInCurrentTry = false; base.VisitTryStatement(node); _seenYieldInCurrentTry |= origSeenYieldInCurrentTry; return null; }
/// <summary> /// Lower a foreach loop that will enumerate a collection using an enumerator. /// /// E e = ((C)(x)).GetEnumerator() /// try { /// while (e.MoveNext()) { /// V v = (V)(T)e.Current; /// // body /// } /// } /// finally { /// // clean up e /// } /// </summary> private BoundStatement RewriteEnumeratorForEachStatement(BoundForEachStatement node) { ForEachStatementSyntax forEachSyntax = (ForEachStatementSyntax)node.Syntax; ForEachEnumeratorInfo enumeratorInfo = node.EnumeratorInfoOpt; Debug.Assert(enumeratorInfo != null); BoundExpression collectionExpression = GetUnconvertedCollectionExpression(node); BoundExpression rewrittenExpression = (BoundExpression)Visit(collectionExpression); BoundStatement rewrittenBody = (BoundStatement)Visit(node.Body); TypeSymbol enumeratorType = enumeratorInfo.GetEnumeratorMethod.ReturnType; TypeSymbol elementType = enumeratorInfo.ElementType; // E e LocalSymbol enumeratorVar = _factory.SynthesizedLocal(enumeratorType, syntax: forEachSyntax, kind: SynthesizedLocalKind.ForEachEnumerator); // Reference to e. BoundLocal boundEnumeratorVar = MakeBoundLocal(forEachSyntax, enumeratorVar, enumeratorType); // ((C)(x)).GetEnumerator() or (x).GetEnumerator(); BoundExpression enumeratorVarInitValue = SynthesizeCall(forEachSyntax, rewrittenExpression, enumeratorInfo.GetEnumeratorMethod, enumeratorInfo.CollectionConversion, enumeratorInfo.CollectionType); // E e = ((C)(x)).GetEnumerator(); BoundStatement enumeratorVarDecl = MakeLocalDeclaration(forEachSyntax, enumeratorVar, enumeratorVarInitValue); AddForEachExpressionSequencePoint(forEachSyntax, ref enumeratorVarDecl); // V v LocalSymbol iterationVar = node.IterationVariable; //(V)(T)e.Current BoundExpression iterationVarAssignValue = MakeConversion( syntax: forEachSyntax, rewrittenOperand: MakeConversion( syntax: forEachSyntax, rewrittenOperand: BoundCall.Synthesized( syntax: forEachSyntax, receiverOpt: boundEnumeratorVar, method: enumeratorInfo.CurrentPropertyGetter), conversion: enumeratorInfo.CurrentConversion, rewrittenType: elementType, @checked: node.Checked), conversion: node.ElementConversion, rewrittenType: iterationVar.Type, @checked: node.Checked); // V v = (V)(T)e.Current; BoundStatement iterationVarDecl = MakeLocalDeclaration(forEachSyntax, iterationVar, iterationVarAssignValue); AddForEachIterationVariableSequencePoint(forEachSyntax, ref iterationVarDecl); // while (e.MoveNext()) { // V v = (V)(T)e.Current; // /* node.Body */ // } var rewrittenBodyBlock = CreateBlockDeclaringIterationVariable(iterationVar, iterationVarDecl, rewrittenBody, forEachSyntax); BoundStatement whileLoop = RewriteWhileStatement( syntax: forEachSyntax, rewrittenCondition: BoundCall.Synthesized( syntax: forEachSyntax, receiverOpt: boundEnumeratorVar, method: enumeratorInfo.MoveNextMethod), conditionSequencePointSpan: forEachSyntax.InKeyword.Span, rewrittenBody: rewrittenBodyBlock, breakLabel: node.BreakLabel, continueLabel: node.ContinueLabel, hasErrors: false); BoundStatement result; MethodSymbol disposeMethod; if (enumeratorInfo.NeedsDisposeMethod && Binder.TryGetSpecialTypeMember(_compilation, SpecialMember.System_IDisposable__Dispose, forEachSyntax, _diagnostics, out disposeMethod)) { Binder.ReportDiagnosticsIfObsolete(_diagnostics, disposeMethod, forEachSyntax, hasBaseReceiver: false, containingMember: _factory.CurrentMethod, containingType: _factory.CurrentType, location: enumeratorInfo.Location); BoundBlock finallyBlockOpt; var idisposableTypeSymbol = disposeMethod.ContainingType; var conversions = new TypeConversions(_factory.CurrentMethod.ContainingAssembly.CorLibrary); HashSet<DiagnosticInfo> useSiteDiagnostics = null; var isImplicit = conversions.ClassifyImplicitConversion(enumeratorType, idisposableTypeSymbol, ref useSiteDiagnostics).IsImplicit; _diagnostics.Add(forEachSyntax, useSiteDiagnostics); if (isImplicit) { Debug.Assert(enumeratorInfo.NeedsDisposeMethod); Conversion receiverConversion = enumeratorType.IsStructType() ? Conversion.Boxing : Conversion.ImplicitReference; // ((IDisposable)e).Dispose(); or e.Dispose(); BoundStatement disposeCall = new BoundExpressionStatement(forEachSyntax, expression: SynthesizeCall(forEachSyntax, boundEnumeratorVar, disposeMethod, receiverConversion, idisposableTypeSymbol)); BoundStatement disposeStmt; if (enumeratorType.IsValueType) { // No way for the struct to be nullable and disposable. Debug.Assert(((TypeSymbol)enumeratorType.OriginalDefinition).SpecialType != SpecialType.System_Nullable_T); // For non-nullable structs, no null check is required. disposeStmt = disposeCall; } else { // NB: cast to object missing from spec. Needed to ignore user-defined operators and box type parameters. // if ((object)e != null) ((IDisposable)e).Dispose(); disposeStmt = RewriteIfStatement( syntax: forEachSyntax, rewrittenCondition: new BoundBinaryOperator(forEachSyntax, operatorKind: BinaryOperatorKind.NotEqual, left: MakeConversion( syntax: forEachSyntax, rewrittenOperand: boundEnumeratorVar, conversion: enumeratorInfo.EnumeratorConversion, rewrittenType: _compilation.GetSpecialType(SpecialType.System_Object), @checked: false), right: MakeLiteral(forEachSyntax, constantValue: ConstantValue.Null, type: null), constantValueOpt: null, methodOpt: null, resultKind: LookupResultKind.Viable, type: _compilation.GetSpecialType(SpecialType.System_Boolean)), rewrittenConsequence: disposeCall, rewrittenAlternativeOpt: null, hasErrors: false); } finallyBlockOpt = new BoundBlock(forEachSyntax, locals: ImmutableArray<LocalSymbol>.Empty, localFunctions: ImmutableArray<LocalFunctionSymbol>.Empty, statements: ImmutableArray.Create<BoundStatement>(disposeStmt)); } else { Debug.Assert(!enumeratorType.IsSealed); // IDisposable d LocalSymbol disposableVar = _factory.SynthesizedLocal(idisposableTypeSymbol); // Reference to d. BoundLocal boundDisposableVar = MakeBoundLocal(forEachSyntax, disposableVar, idisposableTypeSymbol); BoundTypeExpression boundIDisposableTypeExpr = new BoundTypeExpression(forEachSyntax, aliasOpt: null, type: idisposableTypeSymbol); // e as IDisposable BoundExpression disposableVarInitValue = new BoundAsOperator(forEachSyntax, operand: boundEnumeratorVar, targetType: boundIDisposableTypeExpr, conversion: Conversion.ExplicitReference, // Explicit so the emitter won't optimize it away. type: idisposableTypeSymbol); // IDisposable d = e as IDisposable; BoundStatement disposableVarDecl = MakeLocalDeclaration(forEachSyntax, disposableVar, disposableVarInitValue); // if (d != null) d.Dispose(); BoundStatement ifStmt = RewriteIfStatement( syntax: forEachSyntax, rewrittenCondition: new BoundBinaryOperator(forEachSyntax, operatorKind: BinaryOperatorKind.NotEqual, // reference equality left: boundDisposableVar, right: MakeLiteral(forEachSyntax, constantValue: ConstantValue.Null, type: null), constantValueOpt: null, methodOpt: null, resultKind: LookupResultKind.Viable, type: _compilation.GetSpecialType(SpecialType.System_Boolean)), rewrittenConsequence: new BoundExpressionStatement(forEachSyntax, expression: BoundCall.Synthesized( syntax: forEachSyntax, receiverOpt: boundDisposableVar, method: disposeMethod)), rewrittenAlternativeOpt: null, hasErrors: false); // IDisposable d = e as IDisposable; // if (d != null) d.Dispose(); finallyBlockOpt = new BoundBlock(forEachSyntax, locals: ImmutableArray.Create<LocalSymbol>(disposableVar), localFunctions: ImmutableArray<LocalFunctionSymbol>.Empty, statements: ImmutableArray.Create<BoundStatement>(disposableVarDecl, ifStmt)); } // try { // while (e.MoveNext()) { // V v = (V)(T)e.Current; // /* loop body */ // } // } // finally { // /* dispose of e */ // } BoundStatement tryFinally = new BoundTryStatement(forEachSyntax, tryBlock: new BoundBlock(forEachSyntax, locals: ImmutableArray<LocalSymbol>.Empty, localFunctions: ImmutableArray<LocalFunctionSymbol>.Empty, statements: ImmutableArray.Create<BoundStatement>(whileLoop)), catchBlocks: ImmutableArray<BoundCatchBlock>.Empty, finallyBlockOpt: finallyBlockOpt); // E e = ((C)(x)).GetEnumerator(); // try { // /* as above */ result = new BoundBlock( syntax: forEachSyntax, locals: ImmutableArray.Create(enumeratorVar), localFunctions: ImmutableArray<LocalFunctionSymbol>.Empty, statements: ImmutableArray.Create<BoundStatement>(enumeratorVarDecl, tryFinally)); } else { // E e = ((C)(x)).GetEnumerator(); // while (e.MoveNext()) { // V v = (V)(T)e.Current; // /* loop body */ // } result = new BoundBlock( syntax: forEachSyntax, locals: ImmutableArray.Create(enumeratorVar), localFunctions: ImmutableArray<LocalFunctionSymbol>.Empty, statements: ImmutableArray.Create<BoundStatement>(enumeratorVarDecl, whileLoop)); } AddForEachKeywordSequencePoint(forEachSyntax, ref result); return result; }
/// <summary> /// Labels reachable from within this frame without invoking its finally. /// null if there are none such labels. /// </summary> internal HashSet<LabelSymbol> Labels(BoundTryStatement statement) { return _labelsInYieldingTrys[statement]; }
public override BoundNode VisitTryStatement(BoundTryStatement node) { this.Visit(node.TryBlock); this.VisitList(node.CatchBlocks); this.Visit(node.FinallyBlockOpt); return null; }
private BoundStatement RewriteUsingStatementTryFinally(CSharpSyntaxNode syntax, BoundBlock tryBlock, BoundLocal local) { // SPEC: When ResourceType is a non-nullable value type, the expansion is: // SPEC: // SPEC: { // SPEC: ResourceType resource = expr; // SPEC: try { statement; } // SPEC: finally { ((IDisposable)resource).Dispose(); } // SPEC: } // SPEC: // SPEC: Otherwise, when Resource type is a nullable value type or // SPEC: a reference type other than dynamic, the expansion is: // SPEC: // SPEC: { // SPEC: ResourceType resource = expr; // SPEC: try { statement; } // SPEC: finally { if (resource != null) ((IDisposable)resource).Dispose(); } // SPEC: } // SPEC: // SPEC: Otherwise, when ResourceType is dynamic, the expansion is: // SPEC: { // SPEC: dynamic resource = expr; // SPEC: IDisposable d = (IDisposable)resource; // SPEC: try { statement; } // SPEC: finally { if (d != null) d.Dispose(); } // SPEC: } // SPEC: // SPEC: An implementation is permitted to implement a given using statement // SPEC: differently -- for example, for performance reasons -- as long as the // SPEC: behavior is consistent with the above expansion. // // And we do in fact generate the code slightly differently than precisely how it is // described above. // // First: if the type is a non-nullable value type then we do not do the // *boxing conversion* from the resource to IDisposable. Rather, we do // a *constrained virtual call* that elides the boxing if possible. // // Now, you might wonder if that is legal; isn't skipping the boxing producing // an observable difference? Because if the value type is mutable and the Dispose // mutates it, then skipping the boxing means that we are now mutating the original, // not the boxed copy. But this is never observable. Either (1) we have "using(R r = x){}" // and r is out of scope after the finally, so it is not possible to observe the mutation, // or (2) we have "using(x) {}". But that has the semantics of "using(R temp = x){}", // so again, we are not mutating x to begin with; we're always mutating a copy. Therefore // it doesn't matter if we skip making *a copy of the copy*. // // This is what the dev10 compiler does, and we do so as well. // // Second: if the type is a nullable value type then we can similarly elide the boxing. // We can generate // // { // ResourceType resource = expr; // try { statement; } // finally { if (resource.HasValue) resource.GetValueOrDefault().Dispose(); } // } // // Where again we do a constrained virtual call to Dispose, rather than boxing // the value to IDisposable. // // Note that this optimization is *not* what the native compiler does; in this case // the native compiler behavior is to test for HasValue, then *box* and convert // the boxed value to IDisposable. There's no need to do that. // // Third: if we have "using(x)" and x is dynamic then obviously we need not generate // "{ dynamic temp1 = x; IDisposable temp2 = (IDisposable) temp1; ... }". Rather, we elide // the completely unnecessary first temporary. BoundExpression disposedExpression; bool isNullableValueType = local.Type.IsNullableType(); if (isNullableValueType) { MethodSymbol getValueOrDefault = GetNullableMethod(syntax, local.Type, SpecialMember.System_Nullable_T_GetValueOrDefault); // local.GetValueOrDefault() disposedExpression = BoundCall.Synthesized(syntax, local, getValueOrDefault); } else { // local disposedExpression = local; } // local.Dispose() BoundExpression disposeCall; MethodSymbol disposeMethodSymbol; if (TryGetSpecialTypeMember(syntax, SpecialMember.System_IDisposable__Dispose, out disposeMethodSymbol)) { disposeCall = BoundCall.Synthesized(syntax, disposedExpression, disposeMethodSymbol); } else { disposeCall = new BoundBadExpression(syntax, LookupResultKind.NotInvocable, ImmutableArray<Symbol>.Empty, ImmutableArray.Create<BoundNode>(disposedExpression), ErrorTypeSymbol.UnknownResultType); } // local.Dispose(); BoundStatement disposeStatement = new BoundExpressionStatement(syntax, disposeCall); BoundExpression ifCondition; if (isNullableValueType) { MethodSymbol hasValue = GetNullableMethod(syntax, local.Type, SpecialMember.System_Nullable_T_get_HasValue); // local.HasValue ifCondition = BoundCall.Synthesized(syntax, local, hasValue); } else if (local.Type.IsValueType) { ifCondition = null; } else { // local != null ifCondition = MakeNullCheck(syntax, local, BinaryOperatorKind.NotEqual); } BoundStatement finallyStatement; if (ifCondition == null) { // local.Dispose(); finallyStatement = disposeStatement; } else { // if (local != null) local.Dispose(); // or // if (local.HasValue) local.GetValueOrDefault().Dispose(); finallyStatement = RewriteIfStatement( syntax: syntax, locals: ImmutableArray<LocalSymbol>.Empty, rewrittenCondition: ifCondition, rewrittenConsequence: disposeStatement, rewrittenAlternativeOpt: null, hasErrors: false); } // try { ... } finally { if (local != null) local.Dispose(); } BoundStatement tryFinally = new BoundTryStatement( syntax: syntax, tryBlock: tryBlock, catchBlocks: ImmutableArray<BoundCatchBlock>.Empty, finallyBlockOpt: BoundBlock.SynthesizedNoLocals(syntax, finallyStatement)); return tryFinally; }
public override BoundNode VisitTryStatement(BoundTryStatement node) { var oldPending = SavePending(); // we do not allow branches into a try statement var initialState = this.State.Clone(); // use this state to resolve all the branches introduced and internal to try/catch var pendingBeforeTry = SavePending(); VisitTryBlock(node.TryBlock, node, ref initialState); var finallyState = initialState.Clone(); var endState = this.State; foreach (var catchBlock in node.CatchBlocks) { SetState(initialState.Clone()); VisitCatchBlock(catchBlock, ref finallyState); IntersectWith(ref endState, ref this.State); } // Give a chance to branches internal to try/catch to resolve. // Carry forward unresolved branches. RestorePending(pendingBeforeTry); // NOTE: At this point all branches that are internal to try or catch blocks have been resolved. // However we have not yet restored the oldPending branches. Therefore all the branches // that are currently pending must have been introduced in try/catch and do not terminate inside those blocks. // // With exception of YieldReturn, these branches logically go through finally, if such present, // so we must Union/Intersect finally state as appropriate if (node.FinallyBlockOpt != null) { // branches from the finally block, while illegal, should still not be considered // to execute the finally block before occurring. Also, we do not handle branches // *into* the finally block. SetState(finallyState); // capture tryAndCatchPending before going into finally // we will need pending branches as they were before finally later var tryAndCatchPending = SavePending(); var unsetInFinally = AllBitsSet(); VisitFinallyBlock(node.FinallyBlockOpt, ref unsetInFinally); foreach (var pend in tryAndCatchPending.PendingBranches) { if (pend.Branch == null) { continue; // a tracked exception } if (pend.Branch.Kind != BoundKind.YieldReturnStatement) { UnionWith(ref pend.State, ref this.State); if (trackUnassignments) { IntersectWith(ref pend.State, ref unsetInFinally); } } } RestorePending(tryAndCatchPending); UnionWith(ref endState, ref this.State); if (trackUnassignments) { IntersectWith(ref endState, ref unsetInFinally); } } SetState(endState); RestorePending(oldPending); return(null); }