public void RethrowRewriter_Basics()
        {
            var t1 = Expression.Rethrow();
            var s1 = Expression.Empty();
            var r1 = RethrowRewriter.Rewrite(t1, s1);

            Assert.AreSame(s1, r1);

            var t2 = Expression.TryCatch(Expression.Empty(), Expression.Catch(typeof(Exception), t1));
            var s2 = Expression.Empty();
            var r2 = RethrowRewriter.Rewrite(t2, s2);

            Assert.AreSame(t2, r2);

            var t3 = Expression.Block(Expression.Negate(Expression.Constant(1)), t1, Expression.Empty());
            var s3 = Expression.Empty();
            var r3 = (BlockExpression)RethrowRewriter.Rewrite(t3, s3);

            Assert.AreSame(t3.Expressions[0], r3.Expressions[0]);
            Assert.AreSame(s3, r3.Expressions[1]);
            Assert.AreSame(t3.Expressions[2], r3.Expressions[2]);

            var t4 = Expression.Throw(Expression.Constant(new Exception()));
            var s4 = Expression.Empty();
            var r4 = RethrowRewriter.Rewrite(t4, s4);

            Assert.AreSame(t4, r4);
        }
Example #2
0
        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));
        }
Example #3
0
        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));
        }