public static TryStatement TryCatchFinally(SourceSpan span, SourceLocation header, Statement body, CatchBlock[] handlers, Statement @finally) { return new TryStatement( span, header, body, CollectionUtils.ToReadOnlyCollection(handlers), @finally ); }
public Expression ToExpression() { // // We can't emit a real filter or fault because they don't // work in DynamicMethods. Instead we do a simple transformation: // fault -> catch (Exception) { ...; rethrow } // filter -> catch (ExceptionType) { if (!filter) rethrow; ... } // // Note that the filter transformation is not quite equivalent to // real CLR semantics, but it's what IronPython and IronRuby // expect. If we get CLR support we'll switch over to real filter // and fault blocks. // var handlers = new List <CatchBlock>(_catchBlocks); for (int i = 0, n = handlers.Count; i < n; i++) { CatchBlock handler = handlers[i]; if (handler.Filter != null) { handlers[i] = Expression.MakeCatchBlock( handler.Test, handler.Variable, Expression.Condition( handler.Filter, handler.Body, Expression.Rethrow(handler.Body.Type) ), null ); } } if (_fault != null) { ContractUtils.Requires(handlers.Count == 0, "fault cannot be used with catch or finally clauses"); handlers.Add(Expression.Catch(typeof(Exception), Expression.Block(_fault, Expression.Rethrow(_try.Type)))); } var result = Expression.MakeTry(null, _try, _finally, null, handlers); if (_enableJumpsFromFinally) { return(Utils.FinallyFlowControl(result)); } return(result); }
// TryStatement private Statement Rewrite(TryStatement node) { Statement body = RewriteStatement(node.Body); ReadOnlyCollection <CatchBlock> handlers = node.Handlers; CatchBlock[] clone = null; if (handlers != null) { for (int i = 0; i < handlers.Count; i++) { CatchBlock handler = handlers[i]; CatchBlock rhandler = Rewrite(handler); if (((object)rhandler != (object)handler) && (clone == null)) { clone = Clone(handlers, i); } if (clone != null) { clone[i] = rhandler; } } } Statement @finally = RewriteStatement(node.FinallyStatement); if ((clone != null) || ((object)body != (object)node.Body) || ((object)@finally != (object)node.FinallyStatement)) { if (clone != null) { handlers = CollectionUtils.ToReadOnlyCollection(clone); } return(new TryStatement(node.Span, node.Header, body, handlers, @finally)); } else { return(node); } }
// This is copied from the base implementation. // Just want to make sure we disallow yield in filters protected override CatchBlock VisitCatchBlock(CatchBlock node) { ParameterExpression v = VisitAndConvert(node.Variable, "VisitCatchBlock"); int yields = _yields.Count; Expression f = Visit(node.Filter); if (yields != _yields.Count) { // No one needs this yet, and it's not clear what it should even do throw new NotSupportedException("yield in filter is not allowed"); } Expression b = Visit(node.Body); if (v == node.Variable && b == node.Body && f == node.Filter) { return(node); } return(Expression.MakeCatchBlock(node.Test, v, b, f)); }
private CatchBlock[] VisitHandlers(TryExpression node, bool realCatch) { CatchBlock[] handlers = new CatchBlock[node.Handlers.Count]; for (int i = 0; i < node.Handlers.Count; i++) { var handler = node.Handlers[i]; ParameterExpression oldRethrow = _rethrow; try { if (handler.Variable == null) { ParameterExpression rethrow = _rethrow = Expression.Parameter(handler.Test, "$exception"); handlers[i] = Expression.Catch( rethrow, TrackCatch(Visit(handler.Body), rethrow, realCatch), Visit(handler.Filter) ); } else { ParameterExpression rethrow = _rethrow = Expression.Parameter(typeof(Exception), "$exception"); handlers[i] = Expression.Catch( handler.Variable, Expression.Block( new[] { rethrow }, Expression.Assign(rethrow, handler.Variable), TrackCatch(Visit(handler.Body), rethrow, realCatch) ), Visit(handler.Filter) ); } } finally { _rethrow = oldRethrow; } } return(handlers); }
internal TargetLabel AddCatchYieldTarget(TargetLabel label, int index, int handler) { // Yields inside catch blocks are hoisted out of the catch. // If the catch block has a finally, though, it will get wrapped in // another try block, in which case the direct jump is not possible // and code must route through the top target. Debug.Assert(_handlers != null && handler < _handlers.Count); CatchBlock cb = _handlers[handler]; cb.Yield = true; _yieldInCatch = true; if (_finally != null) { AddYieldTarget(ref _catchYields, label, index); return(EnsureTopTarget()); } else { return(label); } }
public CatchRecord(Slot slot, CatchBlock block) { _slot = slot; _block = block; }
protected override Expression VisitTry(TryExpression node) { int startYields = _yields.Count; bool savedInTryWithFinally = _inTryWithFinally; if (node.Finally != null || node.Fault != null) { _inTryWithFinally = true; } Expression @try = Visit(node.Body); int tryYields = _yields.Count; IList <CatchBlock> handlers = Visit(node.Handlers, VisitCatchBlock); int catchYields = _yields.Count; // push a new return label in case the finally block yields _returnLabels.Push(Expression.Label()); // only one of these can be non-null Expression @finally = Visit(node.Finally); Expression fault = Visit(node.Fault); LabelTarget finallyReturn = _returnLabels.Pop(); int finallyYields = _yields.Count; _inTryWithFinally = savedInTryWithFinally; if (@try == node.Body && handlers == node.Handlers && @finally == node.Finally && fault == node.Fault) { return(node); } // No yields, just return if (startYields == _yields.Count) { return(Expression.MakeTry(null, @try, @finally, fault, handlers)); } if (fault != null && finallyYields != catchYields) { // No one needs this yet, and it's not clear how we should get back to // the fault throw new NotSupportedException("yield in fault block is not supported"); } // If try has yields, we need to build a new try body that // dispatches to the yield labels var tryStart = Expression.Label(); if (tryYields != startYields) { @try = Expression.Block(MakeYieldRouter(node.Body.Type, startYields, tryYields, tryStart), @try); } // Transform catches with yield to deferred handlers if (catchYields != tryYields) { var block = new List <Expression>(); block.Add(MakeYieldRouter(node.Body.Type, tryYields, catchYields, tryStart)); block.Add(null); // empty slot to fill in later for (int i = 0, n = handlers.Count; i < n; i++) { CatchBlock c = handlers[i]; if (c == node.Handlers[i]) { continue; } if (handlers.IsReadOnly) { handlers = handlers.ToArray(); } // the variable that will be scoped to the catch block var exceptionVar = Expression.Variable(c.Test, null); // the variable that the catch block body will use to // access the exception. We reuse the original variable if // the catch block had one. It needs to be hoisted because // the catch might contain yields. var deferredVar = c.Variable ?? Expression.Variable(c.Test, null); _vars.Add(deferredVar); // We need to ensure that filters can access the exception // variable Expression filter = c.Filter; if (filter != null && c.Variable != null) { filter = Expression.Block(new[] { c.Variable }, Expression.Assign(c.Variable, exceptionVar), filter); } // catch (ExceptionType exceptionVar) { // deferredVar = exceptionVar; // } handlers[i] = Expression.Catch( exceptionVar, Expression.Block( Expression.Assign(deferredVar, exceptionVar), Expression.Default(node.Body.Type) ), filter ); // We need to rewrite rethrows into "throw deferredVar" var catchBody = new RethrowRewriter { Exception = deferredVar }.Visit(c.Body); // if (deferredVar != null) { // ... catch body ... // } block.Add( Expression.Condition( Expression.NotEqual(deferredVar, AstUtils.Constant(null, deferredVar.Type)), catchBody, Expression.Default(node.Body.Type) ) ); } block[1] = Expression.MakeTry(null, @try, null, null, new ReadOnlyCollection <CatchBlock>(handlers)); @try = Expression.Block(block); handlers = new CatchBlock[0]; // so we don't reuse these } if (finallyYields != catchYields) { // We need to add a catch block to save the exception, so we // can rethrow in case there is a yield in the finally. Also, // add logic for returning. It looks like this: // // try { ... } catch (Exception all) { saved = all; } // finally { // if (_finallyReturnVar) goto finallyReturn; // ... // if (saved != null) throw saved; // finallyReturn: // } // if (_finallyReturnVar) goto _return; // We need to add a catch(Exception), so if we have catches, // wrap them in a try if (handlers.Count > 0) { @try = Expression.MakeTry(null, @try, null, null, handlers); handlers = new CatchBlock[0]; } // NOTE: the order of these routers is important // The first call changes the labels to all point at "tryEnd", // so the second router will jump to "tryEnd" var tryEnd = Expression.Label(); Expression inFinallyRouter = MakeYieldRouter(node.Body.Type, catchYields, finallyYields, tryEnd); Expression inTryRouter = MakeYieldRouter(node.Body.Type, catchYields, finallyYields, tryStart); var all = Expression.Variable(typeof(Exception), "e"); var saved = Expression.Variable(typeof(Exception), "$saved$" + _temps.Count); _temps.Add(saved); @try = Expression.Block( Expression.TryCatchFinally( Expression.Block( inTryRouter, @try, Expression.Assign(saved, AstUtils.Constant(null, saved.Type)), Expression.Label(tryEnd) ), Expression.Block( MakeSkipFinallyBlock(finallyReturn), inFinallyRouter, @finally, Expression.Condition( Expression.NotEqual(saved, AstUtils.Constant(null, saved.Type)), Expression.Throw(saved), Utils.Empty() ), Expression.Label(finallyReturn) ), Expression.Catch(all, Utils.Void(Expression.Assign(saved, all))) ), Expression.Condition( Expression.Equal(_gotoRouter, AstUtils.Constant(GotoRouterYielding)), Expression.Goto(_returnLabels.Peek()), Utils.Empty() ) ); @finally = null; } else if (@finally != null) { // try or catch had a yield, modify finally so we can skip over it @finally = Expression.Block( MakeSkipFinallyBlock(finallyReturn), @finally, Expression.Label(finallyReturn) ); } // Make the outer try, if needed if (handlers.Count > 0 || @finally != null || fault != null) { @try = Expression.MakeTry(null, @try, @finally, fault, handlers); } return(Expression.Block(Expression.Label(tryStart), @try)); }
protected override Expression VisitTry(TryExpression node) { int startYields = _yields.Count; bool savedInTryWithFinally = _inTryWithFinally; if (node.Finally != null || node.Fault != null) { _inTryWithFinally = true; } Expression @try = Visit(node.Body); int tryYields = _yields.Count; IList <CatchBlock> handlers = Visit(node.Handlers, VisitCatchBlock); int catchYields = _yields.Count; // push a new return label in case the finally block yields _returnLabels.Push(Expression.Label()); // only one of these can be non-null Expression @finally = Visit(node.Finally); Expression fault = Visit(node.Fault); LabelTarget finallyReturn = _returnLabels.Pop(); int finallyYields = _yields.Count; _inTryWithFinally = savedInTryWithFinally; if (@try == node.Body && handlers == node.Handlers && @finally == node.Finally && fault == node.Fault) { return(node); } // No yields, just return if (startYields == _yields.Count) { return(Expression.MakeTry(@try, @finally, fault, handlers)); } if (fault != null && finallyYields != catchYields) { // No one needs this yet, and it's not clear how we should get back to // the fault throw new NotSupportedException("yield in fault block is not supported"); } // If try has yields, we need to build a new try body that // dispatches to the yield labels var tryStart = Expression.Label(); if (tryYields != startYields) { @try = Expression.Block(MakeYieldRouter(startYields, tryYields, tryStart), @try); } // Transform catches with yield to deferred handlers if (catchYields != tryYields) { // Temps which are only needed temporarily, so they can go into // a transient scope (contents will be lost each yield) var temps = new List <ParameterExpression>(); var block = new List <Expression>(); block.Add(MakeYieldRouter(tryYields, catchYields, tryStart)); block.Add(null); // empty slot to fill in later for (int i = 0, n = handlers.Count; i < n; i++) { CatchBlock c = handlers[i]; if (c == node.Handlers[i]) { continue; } if (handlers.IsReadOnly) { handlers = handlers.ToArray(); } // TODO: when CatchBlock's variable is scoped properly, this // implementation will need to be different var deferredVar = Expression.Variable(c.Test, null); temps.Add(deferredVar); handlers[i] = Expression.Catch(deferredVar, Expression.Empty(), c.Filter); // We need to rewrite rethrows into "throw deferredVar" var catchBody = new RethrowRewriter { Exception = deferredVar }.Visit(c.Body); if (c.Variable != null) { catchBody = Expression.Block(Expression.Assign(c.Variable, deferredVar), catchBody); } else { catchBody = Expression.Block(catchBody); } block.Add( Expression.Condition( Expression.NotEqual(deferredVar, Expression.Constant(null, deferredVar.Type)), Expression.Void(catchBody), Expression.Empty() ) ); } block[1] = Expression.MakeTry(@try, null, null, new ReadOnlyCollection <CatchBlock>(handlers)); @try = Expression.Block(temps, block); handlers = new CatchBlock[0]; // so we don't reuse these } if (finallyYields != catchYields) { // We need to add a catch block to save the exception, so we // can rethrow in case there is a yield in the finally. Also, // add logic for returning. It looks like this: // try { ... } catch (Exception e) {} // finally { // if (_finallyReturnVar) goto finallyReturn; // ... // if (e != null) throw e; // finallyReturn: // } // if (_finallyReturnVar) goto _return; // We need to add a catch(Exception), so if we have catches, // wrap them in a try if (handlers.Count > 0) { @try = Expression.MakeTry(@try, null, null, handlers); handlers = new CatchBlock[0]; } // NOTE: the order of these routers is important // The first call changes the labels to all point at "tryEnd", // so the second router will jump to "tryEnd" var tryEnd = Expression.Label(); Expression inFinallyRouter = MakeYieldRouter(catchYields, finallyYields, tryEnd); Expression inTryRouter = MakeYieldRouter(catchYields, finallyYields, tryStart); var exception = Expression.Variable(typeof(Exception), "$temp$" + _temps.Count); _temps.Add(exception); @try = Expression.Block( Expression.TryCatchFinally( Expression.Block( inTryRouter, @try, Expression.Assign(exception, Expression.Constant(null, exception.Type)), Expression.Label(tryEnd) ), Expression.Block( MakeSkipFinallyBlock(finallyReturn), inFinallyRouter, @finally, Expression.Condition( Expression.NotEqual(exception, Expression.Constant(null, exception.Type)), Expression.Throw(exception), Expression.Empty() ), Expression.Label(finallyReturn) ), Expression.Catch(exception, Expression.Empty()) ), Expression.Condition( Expression.Equal(_gotoRouter, Expression.Constant(GotoRouterYielding)), Expression.Goto(_returnLabels.Peek()), Expression.Empty() ) ); @finally = null; } else if (@finally != null) { // try or catch had a yield, modify finally so we can skip over it @finally = Expression.Block( MakeSkipFinallyBlock(finallyReturn), @finally, Expression.Label(finallyReturn) ); } // Make the outer try, if needed if (handlers.Count > 0 || @finally != null || fault != null) { @try = Expression.MakeTry(@try, @finally, fault, handlers); } return(Expression.Block(Expression.Label(tryStart), @try)); }
protected internal override bool Walk(CatchBlock node) { // CatchBlock is not required to have target variable if (node.Variable != null) { node.Ref = Reference(node.Variable); } return true; }
private static void EmitSaveExceptionOrPop(CodeGen cg, CatchBlock cb) { if (cb.Variable != null) { Debug.Assert(cb.Slot != null); // If the variable is present, store the exception // in the variable. cb.Slot.EmitSet(cg); } else { // Otherwise, pop it off the stack. cg.Emit(OpCodes.Pop); } }
// CatchBlock private void DefaultWalk(CatchBlock node) { if (Walk(node)) { WalkNode(node.Body); } PostWalk(node); }
protected internal override bool Walk(CatchBlock node) { if (node.Variable != null) { node.Ref = GetOrMakeRef(node.Variable); } return true; }