/// <summary> /// Transform multiple python except handlers for a try block into a single catch body. /// </summary> /// <param name="ag"></param> /// <param name="variable">The variable for the exception in the catch block.</param> /// <returns>Null if there are no except handlers. Else the statement to go inside the catch handler</returns> private MSAst.Expression TransformHandlers(AstGenerator ag, out MSAst.ParameterExpression variable) { if (_handlers == null || _handlers.Length == 0) { variable = null; return null; } bool emittingFinally = ag._isEmittingFinally; ag._isEmittingFinally = false; try { MSAst.ParameterExpression exception = ag.GetTemporary("exception", typeof(Exception)); MSAst.ParameterExpression extracted = ag.GetTemporary("extracted", typeof(object)); // The variable where the runtime will store the exception. variable = exception; var tests = new List<Microsoft.Scripting.Ast.IfStatementTest>(_handlers.Length); MSAst.ParameterExpression converted = null; MSAst.Expression catchAll = null; for (int index = 0; index < _handlers.Length; index++) { TryStatementHandler tsh = _handlers[index]; if (tsh.Test != null) { Microsoft.Scripting.Ast.IfStatementTest ist; // translating: // except Test ... // // generate following AST for the Test (common part): // CheckException(exception, Test) MSAst.Expression test = Ast.Call( AstGenerator.GetHelperMethod("CheckException"), ag.LocalContext, extracted, ag.TransformAsObject(tsh.Test) ); if (tsh.Target != null) { // translating: // except Test, Target: // <body> // into: // if ((converted = CheckException(exception, Test)) != null) { // Target = converted; // traceback-header // <body> // } if (converted == null) { converted = ag.GetTemporary("converted"); } ist = AstUtils.IfCondition( Ast.NotEqual( Ast.Assign(converted, test), AstUtils.Constant(null) ), Ast.Block( tsh.Target.TransformSet(ag, SourceSpan.None, converted, PythonOperationKind.None), ag.AddDebugInfo( GetTracebackHeader( ag, exception, ag.Transform(tsh.Body) ), new SourceSpan(tsh.Start, tsh.Header) ), AstUtils.Empty() ) ); } else { // translating: // except Test: // <body> // into: // if (CheckException(exception, Test) != null) { // traceback-header // <body> // } ist = AstUtils.IfCondition( Ast.NotEqual( test, AstUtils.Constant(null) ), ag.AddDebugInfo( GetTracebackHeader( ag, exception, ag.Transform(tsh.Body) ), new SourceSpan(tsh.Start, tsh.Header) ) ); } // Add the test to the if statement test cascade tests.Add(ist); } else { Debug.Assert(index == _handlers.Length - 1); Debug.Assert(catchAll == null); // translating: // except: // <body> // into: // { // traceback-header // <body> // } catchAll = ag.AddDebugInfo( GetTracebackHeader(ag, exception, ag.Transform(tsh.Body)), new SourceSpan(tsh.Start, tsh.Header) ); } } MSAst.Expression body = null; if (tests.Count > 0) { // rethrow the exception if we have no catch-all block if (catchAll == null) { catchAll = Ast.Block( ag.GetSaveLineNumberExpression(true), Ast.Throw( Ast.Call( typeof(ExceptionHelpers).GetMethod("UpdateForRethrow"), exception ) ) ); } body = AstUtils.If( tests.ToArray(), catchAll ); } else { Debug.Assert(catchAll != null); body = catchAll; } if (converted != null) { ag.FreeTemp(converted); } ag.FreeTemp(exception); ag.FreeTemp(extracted); // Codegen becomes: // extracted = PythonOps.SetCurrentException(exception) // < dynamic exception analysis > return Ast.Block( Ast.Assign( extracted, Ast.Call( AstGenerator.GetHelperMethod("SetCurrentException"), ag.LocalContext, exception ) ), body, AstUtils.Empty() ); } finally { ag._isEmittingFinally = emittingFinally; } }
/// <summary> /// Surrounds the body of an except block w/ the appropriate code for maintaining the traceback. /// </summary> internal static MSAst.Expression GetTracebackHeader(AstGenerator ag, MSAst.ParameterExpression exception, MSAst.Expression body) { // we are about to enter a except block. We need to emit the line number update so we track // the line that the exception was thrown from. We then need to build exc_info() so that // it's available. Finally we clear the list of dynamic stack frames because they've all // been associated with this exception. return Ast.Block( // pass false so if we take another exception we'll add it to the frame list ag.GetSaveLineNumberExpression(false), Ast.Call( AstGenerator.GetHelperMethod("BuildExceptionInfo"), ag.LocalContext, exception ), body, AstUtils.Empty() ); }
private MSAst.Expression AddFinally(AstGenerator/*!*/ ag, MSAst.Expression/*!*/ body, MSAst.ParameterExpression nestedException) { if (_finally != null) { bool isEmitting = ag._isEmittingFinally; ag._isEmittingFinally = true; int loopId = ++ag._loopOrFinallyId; ag.LoopOrFinallyIds.Add(loopId, true); try { Debug.Assert(nestedException != null); MSAst.ParameterExpression nestedFrames = ag.GetTemporary("$nestedFrames", typeof(List<DynamicStackFrame>)); bool inFinally = ag.InFinally; ag.InFinally = true; MSAst.Expression @finally = ag.Transform(_finally); ag.InFinally = inFinally; if (@finally == null) { // error reported during compilation return null; } // lots is going on here. We need to consider: // 1. Exceptions propagating out of try/except/finally. Here we need to save the line # // from the exception block and not save the # from the finally block later. // 2. Exceptions propagating out of the finally block. Here we need to report the line number // from the finally block and leave the existing stack traces cleared. // 3. Returning from the try block: Here we need to run the finally block and not update the // line numbers. body = AstUtils.Try( // we use a fault to know when we have an exception and when control leaves normally (via // either a return or the body completing successfully). AstUtils.Try( ag.AddDebugInfo(AstUtils.Empty(), new SourceSpan(Span.Start, _header)), Ast.Assign(nestedException, AstUtils.Constant(false)), body ).Fault( // fault Ast.Assign(nestedException, AstUtils.Constant(true)) ) ).FinallyWithJumps( // if we had an exception save the line # that was last executing during the try AstUtils.If( nestedException, ag.GetSaveLineNumberExpression(false) ), // clear the frames incase thae finally throws, and allow line number // updates to proceed ag.UpdateLineUpdated(false), Ast.Assign( nestedFrames, Ast.Call(AstGenerator.GetHelperMethod("GetAndClearDynamicStackFrames")) ), // run the finally code @finally, // if the finally exits normally restore any previous exception info Ast.Call( AstGenerator.GetHelperMethod("SetDynamicStackFrames"), nestedFrames ), // if we took an exception in the try block we have saved the line number. Otherwise // we have no line number saved and will need to continue saving them if // other exceptions are thrown. AstUtils.If( nestedException, ag.UpdateLineUpdated(true) ) ); ag.FreeTemp(nestedFrames); ag.FreeTemp(nestedException); } finally { ag._isEmittingFinally = isEmitting; ag.LoopOrFinallyIds.Remove(loopId); } } return body; }