private static void FixTryCatchFinally(AstTryCatchBlock node) { // this of course is all rather brittle and tied to the // internal workings of the C# compiler. if (node.FinallyBlock == null) return; var firstExpr = node.FinallyBlock.GetChildren().First() as AstExpression; if (firstExpr == null) return; if (!firstExpr.Code.IsConditionalControlFlow()) return; if (firstExpr.Arguments[0].Code != AstCode.Ldloc) // should read a local variable return; var condVar = firstExpr.Arguments[0].Operand as AstVariable; if (condVar == null) // safety check needed? return; // make sure the variable is not written to in the try block or any of the catch blocks if(IsModified(node.TryBlock, condVar) || node.CatchBlocks.Any(c=>IsModified(c, condVar))) throw new CompilerException("Unable to fix complex locking in async method."); // remove the conditional branch. firstExpr.Arguments.Clear(); firstExpr.Operand = null; firstExpr.Code = AstCode.Nop; }
/// <summary> /// Insert the finally block of the given try/catch block before every "exit" opcode in the given range. /// </summary> private void InsertFinallyBeforeReturns(Instruction first, Instruction last, AstTryCatchBlock tryCatchBlock) { if (tryCatchBlock.FinallyBlock == null) { return; } // Find all return instructions var firstIndex = first.Index; var lastIndex = last.Index; var exitInstructions = new List <Instruction>(); for (var index = firstIndex; index <= lastIndex; index++) { var ins = instructions[index]; if (ins.Code.IsReturn() /*|| (ins.Code == RCode.Leave)*/) { exitInstructions.Add(ins); } } if (exitInstructions.Count == 0) { return; } // Insert finally block before each return instruction foreach (var ins in exitInstructions) { // Clone the exit instruction var clone = new Instruction(ins); // Emit finally block var finallyStart = this.Add(tryCatchBlock.FinallyBlock.SourceLocation, RCode.Nop); using (labelManager.CreateContext()) { tryCatchBlock.FinallyBlock.Accept(this, tryCatchBlock); tryCatchBlock.FinallyBlock.CleanResults(); } // Convert instruction -> jump to finally block ins.Code = RCode.Goto; ins.Operand = finallyStart; ins.Registers.Clear(); // Append exit instruction instructions.Add(clone); } }
private static void FixTryCatchFinally(AstTryCatchBlock node) { // this of course is all rather brittle and tied to the // internal workings of the C# compiler. if (node.FinallyBlock == null) { return; } var firstExpr = node.FinallyBlock.GetChildren().First() as AstExpression; if (firstExpr == null) { return; } if (!firstExpr.Code.IsConditionalControlFlow()) { return; } if (firstExpr.Arguments[0].Code != AstCode.Ldloc) // should read a local variable { return; } var condVar = firstExpr.Arguments[0].Operand as AstVariable; if (condVar == null) // safety check needed? { return; } // make sure the variable is not written to in the try block or any of the catch blocks if (IsModified(node.TryBlock, condVar) || node.CatchBlocks.Any(c => IsModified(c, condVar))) { throw new CompilerException("Unable to fix complex locking in async method."); } // remove the conditional branch. firstExpr.Arguments.Clear(); firstExpr.Operand = null; firstExpr.Code = AstCode.Nop; }
/// <summary> /// Convert the given method to a list of Ast nodes. /// </summary> public AstBlock Build() { if ((codeAttr == null) || (codeAttr.Code.Length == 0)) { return(new AstBlock((ISourceLocation)null)); } var body = StackAnalysis(); var offset2ByteCode = new Dictionary <int, ByteCode>(); foreach (var bc in body) { offset2ByteCode[bc.Offset] = bc; } var blockStarts = SplitInBlocks(body, validExceptionHandlers); var list = ConvertToAst(body, new HashSet <ExceptionHandler>(validExceptionHandlers), blockStarts, 0, offset2ByteCode); var ast = new AstBlock(list.Select(x => x.SourceLocation).FirstOrDefault(), list); if (methodDef.IsSynchronized) { // Wrap in synchronization block var sl = ast.SourceLocation; var tryCatch = new AstTryCatchBlock(sl); // try-lock(this) var lockExpr = methodDef.IsStatic ? new AstExpression(sl, AstCode.TypeOf, declaringType) : new AstExpression(sl, AstCode.Ldthis, null); ast.Body.Insert(0, new AstExpression(sl, AstCode.Call, MonitorMethodReference("Enter"), new AstExpression(lockExpr))); tryCatch.TryBlock = ast; // finally-unlock(this) tryCatch.FinallyBlock = new AstBlock(sl, new AstExpression(sl, AstCode.Call, MonitorMethodReference("Exit"), new AstExpression(lockExpr)), new AstExpression(sl, AstCode.Endfinally, null)); // Wrap try/catch in block ast = new AstBlock(sl, tryCatch); } return(ast); }
/// <summary> /// Convert the given method to a list of Ast nodes. /// </summary> public AstBlock Build() { if ((codeAttr == null) || (codeAttr.Code.Length == 0)) return new AstBlock((ISourceLocation)null); var body = StackAnalysis(); var offset2ByteCode = new Dictionary<int, ByteCode>(); foreach (var bc in body) { offset2ByteCode[bc.Offset] = bc; } var blockStarts = SplitInBlocks(body, validExceptionHandlers); var list = ConvertToAst(body, new HashSet<ExceptionHandler>(validExceptionHandlers), blockStarts, 0, offset2ByteCode); var ast = new AstBlock(list.Select(x => x.SourceLocation).FirstOrDefault(), list); if (methodDef.IsSynchronized) { // Wrap in synchronization block var sl = ast.SourceLocation; var tryCatch = new AstTryCatchBlock(sl); // try-lock(this) var lockExpr = methodDef.IsStatic ? new AstExpression(sl, AstCode.TypeOf, declaringType) : new AstExpression(sl, AstCode.Ldthis, null); ast.Body.Insert(0, new AstExpression(sl, AstCode.Call, MonitorMethodReference("Enter"), new AstExpression(lockExpr))); tryCatch.TryBlock = ast; // finally-unlock(this) tryCatch.FinallyBlock = new AstBlock(sl, new AstExpression(sl, AstCode.Call, MonitorMethodReference("Exit"), new AstExpression(lockExpr)), new AstExpression(sl, AstCode.Endfinally, null)); // Wrap try/catch in block ast = new AstBlock(sl, tryCatch); } return ast; }
/// <summary> /// Create try/catch/finally/fault block /// </summary> public override RLRange Visit(AstTryCatchBlock node, AstNode parent) { var handler = new ExceptionHandler(); //if (node.FaultBlock != null) //{ // Debugger.Break(); //} // Setup instruction before/after my node. var first = new Instruction() { SequencePoint = node.SourceLocation }; var last = new Instruction(RCode.Nop); FinallyBlockState finState = null; FinallyBlockState outerFinState = tryCatchStack.FirstOrDefault(f => f.HasFinally); if (tryCatchStack.Count == 0) { finallyState.FinallyStacks.Add(new List <FinallyBlockState>()); } if (node.FinallyBlock != null) { // store finaly state finState = new FinallyBlockState(outerFinState, tryCatchStack.Count, this, first, last); finState.FinallyExceptionRegister = frame.AllocateTemp(new ClassReference("java.lang.Throwable")).Register; // clear the variable to make sure it isn't stale from a previous loop. // make sure this is outside the try block for edge case exceptions. this.Add(node.SourceLocation, RCode.Const, 0, finState.FinallyExceptionRegister); tryCatchStack.Push(finState); finallyState.FinallyStacks.Last().Add(finState); } else { finState = new FinallyBlockState(outerFinState, tryCatchStack.Count); tryCatchStack.Push(finState); } instructions.Add(first); // Emit try block handler.TryStart = first; node.TryBlock.AcceptOrDefault(this, node); handler.TryEnd = TryCatchGotoEnd(finState, last); var catchesStart = this.Add(AstNode.NoSource, RCode.Nop); // Emit "normal" catch blocks foreach (var catchBlock in node.CatchBlocks.Where(x => !x.IsCatchAll())) { var c = new Catch { Type = catchBlock.ExceptionType.GetReference(targetPackage) }; handler.Catches.Add(c); var catchStart = this.Add(catchBlock.SourceLocation, RCode.Nop); catchBlock.Accept(this, node); c.Instruction = catchStart; TryCatchGotoEnd(finState, last); } // Emit "catch all" (if any) var catchAllBlock = node.CatchBlocks.SingleOrDefault(x => x.IsCatchAll()); if (catchAllBlock != null) { var catchStart = this.Add(catchAllBlock.SourceLocation, RCode.Nop); catchAllBlock.Accept(this, node); handler.CatchAll = catchStart; TryCatchGotoEnd(finState, last); } var catchesEnd = this.Add(AstNode.NoSource, RCode.Nop); // clear try/catch/finally stack: we don't want to cover ourselves! tryCatchStack.Pop(); // Emit finally code if (node.FinallyBlock != null) { // preparation. var finallyStart = this.Add(node.FinallyBlock.SourceLocation, RCode.Move_exception, finState.FinallyExceptionRegister); instructions.Add(finState.NonException); // the original handler node.FinallyBlock.Accept(this, node); // prepare the routing this.Add(AstNode.NoSource, RCode.If_eqz, finState.AfterExceptionCheck, finState.FinallyExceptionRegister); this.Add(AstNode.NoSource, RCode.Throw, finState.FinallyExceptionRegister); instructions.Add(finState.AfterExceptionCheck); // Set up exception handlers. if (catchAllBlock == null) { // we need to cover the try block. handler.CatchAll = finallyStart; } if (node.CatchBlocks.Any()) { // we need to cover the catch blocks var finallyHandler = new ExceptionHandler { TryStart = catchesStart, TryEnd = catchesEnd, CatchAll = finallyStart }; body.Exceptions.Add(finallyHandler); } } // Add end instructions.Add(last); // Record catch/catch-all handler if ((handler.CatchAll != null) || handler.Catches.Any()) { body.Exceptions.Add(handler); } return(new RLRange(first, last, null)); }
/// <summary> /// Get the first expression to be excecuted if the instruction pointer is at the start of the given node. /// Try blocks may not be entered in any way. If possible, the try block is returned as the node to be executed. /// </summary> AstNode Enter(AstNode node, HashSet <AstNode> visitedNodes) { if (node == null) { throw new ArgumentNullException(); } if (!visitedNodes.Add(node)) { return(null); // Infinite loop } AstLabel label = node as AstLabel; if (label != null) { return(Exit(label, visitedNodes)); } AstExpression expr = node as AstExpression; if (expr != null) { if (expr.Code == AstCode.Br || expr.Code == AstCode.Leave) { AstLabel target = (AstLabel)expr.Operand; // Early exit - same try-block if (GetParents(expr).OfType <AstTryCatchBlock>().FirstOrDefault() == GetParents(target).OfType <AstTryCatchBlock>().FirstOrDefault()) { return(Enter(target, visitedNodes)); } // Make sure we are not entering any try-block var srcTryBlocks = GetParents(expr).OfType <AstTryCatchBlock>().Reverse().ToList(); var dstTryBlocks = GetParents(target).OfType <AstTryCatchBlock>().Reverse().ToList(); // Skip blocks that we are already in int i = 0; while (i < srcTryBlocks.Count && i < dstTryBlocks.Count && srcTryBlocks[i] == dstTryBlocks[i]) { i++; } if (i == dstTryBlocks.Count) { return(Enter(target, visitedNodes)); } else { AstTryCatchBlock dstTryBlock = dstTryBlocks[i]; // Check that the goto points to the start AstTryCatchBlock current = dstTryBlock; while (current != null) { foreach (AstNode n in current.TryBlock.Body) { if (n is AstLabel) { if (n == target) { return(dstTryBlock); } } else if (!n.Match(AstCode.Nop)) { current = n as AstTryCatchBlock; break; } } } return(null); } } else if (expr.Code == AstCode.Nop) { return(Exit(expr, visitedNodes)); } else if (expr.Code == AstCode.LoopOrSwitchBreak) { AstNode breakBlock = GetParents(expr).First(n => n is AstSwitch); return(Exit(breakBlock, new HashSet <AstNode>() { expr })); } else { return(expr); } } AstBlock block = node as AstBlock; if (block != null) { if (block.EntryGoto != null) { return(Enter(block.EntryGoto, visitedNodes)); } else if (block.Body.Count > 0) { return(Enter(block.Body[0], visitedNodes)); } else { return(Exit(block, visitedNodes)); } } AstTryCatchBlock tryCatch = node as AstTryCatchBlock; if (tryCatch != null) { return(tryCatch); } AstSwitch astSwitch = node as AstSwitch; if (astSwitch != null) { return(astSwitch.Condition); } throw new NotSupportedException(node.GetType().ToString()); }
private static void FailsafeInterlockedUsingLocking(XFieldReference field, AstExpression expr, AstExpression targetExpr, AstBlock block, int idx, AssemblyCompiler compiler, MethodSource currentMethod) { if (currentMethod.IsDotNet && !currentMethod.ILMethod.DeclaringType.HasSuppressMessageAttribute("InterlockedFallback")) { bool isStatic = field != null && field.Resolve().IsStatic; DLog.Warning(DContext.CompilerCodeGenerator, "Emulating Interlocked call using failsafe locking mechanism in {0}{1}. Consider using AtomicXXX classes instead. You can suppress this message with an [SuppressMessage(\"dot42\", \"InterlockedFallback\")] attribute on the class.", currentMethod.Method.FullName, !isStatic ? "" : " because a static field is referenced"); } // replace with: // Monitor.Enter(); // try { <original expression> } finally { Monitor.Exit(); } // // note that the lock is larger than it has to be, since it also sourrounds // the parameter evaluation. // It must sourround the storing and loading of the reference parameters though. var typeSystem = compiler.Module.TypeSystem; var monitorType = compiler.GetDot42InternalType("System.Threading", "Monitor"); var enterMethod = monitorType.Resolve().Methods.Single(p => p.Name == "Enter"); var exitMethod = monitorType.Resolve().Methods.Single(p => p.Name == "Exit"); AstExpression loadLockTarget = null; if (field != null) { if (field.Resolve().IsStatic) { // lock on the field's class typedef. // but always the element type, not on a generic instance (until Dot42 implements proper generic static field handling) loadLockTarget = new AstExpression(expr.SourceLocation, AstCode.LdClass, field.DeclaringType.GetElementType()) .SetType(typeSystem.Type); } else { // lock on the fields object loadLockTarget = targetExpr.Arguments[0]; } } if (loadLockTarget == null) { // something went wrong. use a global lock. DLog.Warning(DContext.CompilerCodeGenerator, "unable to infer target of Interlocked call. using global lock."); var interlockedType = compiler.GetDot42InternalType("System.Threading", "Interlocked"); loadLockTarget = new AstExpression(expr.SourceLocation, AstCode.LdClass, interlockedType) .SetType(typeSystem.Type); } var lockVar = new AstGeneratedVariable("lockTarget$", "") { Type = typeSystem.Object }; var storeLockVar = new AstExpression(expr.SourceLocation, AstCode.Stloc, lockVar, loadLockTarget); var loadLockVar = new AstExpression(expr.SourceLocation, AstCode.Ldloc, lockVar); var enterCall = new AstExpression(expr.SourceLocation, AstCode.Call, enterMethod, storeLockVar); var replacementBlock = new AstBlock(expr.SourceLocation); replacementBlock.Body.Add(enterCall); var tryCatch = new AstTryCatchBlock(expr.SourceLocation) { TryBlock = new AstBlock(expr.SourceLocation, expr), FinallyBlock = new AstBlock(expr.SourceLocation, new AstExpression(block.SourceLocation, AstCode.Call, exitMethod, loadLockVar)) }; replacementBlock.Body.Add(tryCatch); if (block.EntryGoto == expr) { block.EntryGoto = enterCall; } block.Body[idx] = replacementBlock; }
private static void FailsafeInterlockedUsingLocking(XFieldReference field, AstExpression expr, AstExpression targetExpr, AstBlock block, int idx, AssemblyCompiler compiler, MethodSource currentMethod) { if (currentMethod.IsDotNet && !currentMethod.ILMethod.DeclaringType.HasSuppressMessageAttribute("InterlockedFallback")) { bool isStatic = field != null && field.Resolve().IsStatic; DLog.Warning(DContext.CompilerCodeGenerator, "Emulating Interlocked call using failsafe locking mechanism in {0}{1}. Consider using AtomicXXX classes instead. You can suppress this message with an [SuppressMessage(\"dot42\", \"InterlockedFallback\")] attribute on the class.", currentMethod.Method.FullName, !isStatic ? "" : " because a static field is referenced"); } // replace with: // Monitor.Enter(); // try { <original expression> } finally { Monitor.Exit(); } // // note that the lock is larger than it has to be, since it also sourrounds // the parameter evaluation. // It must sourround the storing and loading of the reference parameters though. var typeSystem = compiler.Module.TypeSystem; var monitorType = compiler.GetDot42InternalType("System.Threading", "Monitor"); var enterMethod = monitorType.Resolve().Methods.Single(p => p.Name == "Enter"); var exitMethod = monitorType.Resolve().Methods.Single(p => p.Name == "Exit"); AstExpression loadLockTarget = null; if (field != null) { if (field.Resolve().IsStatic) { // lock on the field's class typedef. // but always the element type, not on a generic instance (until Dot42 implements proper generic static field handling) loadLockTarget = new AstExpression(expr.SourceLocation, AstCode.LdClass, field.DeclaringType.GetElementType()) .SetType(typeSystem.Type); } else { // lock on the fields object loadLockTarget = targetExpr.Arguments[0]; } } if (loadLockTarget == null) { // something went wrong. use a global lock. DLog.Warning(DContext.CompilerCodeGenerator, "unable to infer target of Interlocked call. using global lock."); var interlockedType = compiler.GetDot42InternalType("System.Threading", "Interlocked"); loadLockTarget = new AstExpression(expr.SourceLocation, AstCode.LdClass, interlockedType) .SetType(typeSystem.Type); } var lockVar = new AstGeneratedVariable("lockTarget$", "") {Type = typeSystem.Object}; var storeLockVar = new AstExpression(expr.SourceLocation, AstCode.Stloc, lockVar, loadLockTarget); var loadLockVar = new AstExpression(expr.SourceLocation, AstCode.Ldloc, lockVar); var enterCall = new AstExpression(expr.SourceLocation, AstCode.Call, enterMethod, storeLockVar); var replacementBlock = new AstBlock(expr.SourceLocation); replacementBlock.Body.Add(enterCall); var tryCatch = new AstTryCatchBlock(expr.SourceLocation) { TryBlock = new AstBlock(expr.SourceLocation, expr), FinallyBlock = new AstBlock(expr.SourceLocation, new AstExpression(block.SourceLocation, AstCode.Call, exitMethod, loadLockVar)) }; replacementBlock.Body.Add(tryCatch); if (block.EntryGoto == expr) block.EntryGoto = enterCall; block.Body[idx] = replacementBlock; }
/// <summary> /// Convert the given set of bytecodes to an Ast node list. /// Split exception handlers into Ast try/catch blocks. /// </summary> private List<AstNode> ConvertToAst(List<ByteCode> body, HashSet<ExceptionHandler> ehs, List<ByteCodeBlock> blockStarts, int nestingLevel, Dictionary<int, ByteCode> offset2ByteCode) { var ast = new List<AstNode>(); // Split body in blocks while (ehs.Any()) { var tryCatchBlock = new AstTryCatchBlock(null); // Find the first and widest scope var tryStart = ehs.Min(eh => eh.StartPc); var tryEnd = ehs.Where(eh => eh.StartPc == tryStart).Max(eh => eh.EndPc); var handlers = ehs.Where(eh => (eh.StartPc == tryStart) && (eh.EndPc == tryEnd)).OrderBy(eh => eh.HandlerPc).ToList(); // Remember that any part of the body migt have been removed due to unreachability // Cut all instructions up to the try block { var tryStartIdx = 0; while ((tryStartIdx < body.Count) && (body[tryStartIdx].Offset < tryStart)) { tryStartIdx++; } if (tryStartIdx > 0) { ast.AddRange(ConvertRangeToAst(body.CutRange(0, tryStartIdx))); // Make sure the block before the try block ends with an unconditional control flow AddUnconditionalBranchToNext(ast, body, 0); } } // Cut the try block { var nestedEHs = new HashSet<ExceptionHandler>( ehs.Where(eh => ((tryStart <= eh.StartPc) && (eh.EndPc < tryEnd)) || ((tryStart < eh.StartPc) && (eh.EndPc <= tryEnd)))); ehs.ExceptWith(nestedEHs); var tryEndIdx = 0; while ((tryEndIdx < body.Count) && (body[tryEndIdx].Offset < tryEnd)) { tryEndIdx++; } var converted = ConvertToAst(body.CutRange(0, tryEndIdx), nestedEHs, blockStarts, nestingLevel + 1, offset2ByteCode); tryCatchBlock.TryBlock = new AstBlock(converted.Select(x => x.SourceLocation).FirstOrDefault(), converted); // Make sure the try block ends with an unconditional control flow AddUnconditionalBranchToNext(tryCatchBlock.TryBlock.Body, body, 0); } // Cut all handlers tryCatchBlock.CatchBlocks.Clear(); foreach (var iterator in handlers) { var eh = iterator; var handler = offset2ByteCode[eh.HandlerPc]; // body.First(x => x.Offset == eh.HandlerPc); var catchType = eh.IsCatchAll ? typeSystem.Object : AsTypeReference(eh.CatchType, XTypeUsageFlags.CatchType); var catchBlock = new AstTryCatchBlock.CatchBlock(handler.SourceLocation, tryCatchBlock) { ExceptionType = catchType, Body = new List<AstNode>() }; // Create catch "body" (actually a jump to the handler) // Handle the automatically pushed exception on the stack var ldexception = ldexceptions[eh]; catchBlock.Body.Add(new AstExpression(handler.SourceLocation, AstCode.Br, handler.Label(true))); if (ldexception.StoreTo == null || ldexception.StoreTo.Count == 0) { // Exception is not used catchBlock.ExceptionVariable = null; } else if (ldexception.StoreTo.Count == 1) { /*var first = catchBlock.Body.FirstOrDefault() as AstExpression; if (first != null && first.Code == AstCode.Pop && first.Arguments[0].Code == AstCode.Ldloc && first.Arguments[0].Operand == ldexception.StoreTo[0]) { // The exception is just poped - optimize it all away; catchBlock.ExceptionVariable = new AstGeneratedVariable("ex_" + eh.HandlerPc.ToString("X2")); catchBlock.Body.RemoveAt(0); } else*/ { catchBlock.ExceptionVariable = ldexception.StoreTo[0]; } } else { var exTemp = new AstGeneratedVariable("ex_" + eh.HandlerPc.ToString("X2"), null); catchBlock.ExceptionVariable = exTemp; foreach (var storeTo in ldexception.StoreTo) { catchBlock.Body.Insert(0, new AstExpression(catchBlock.SourceLocation, AstCode.Stloc, storeTo, new AstExpression(catchBlock.SourceLocation, AstCode.Ldloc, exTemp))); } } tryCatchBlock.CatchBlocks.Add(catchBlock); } ehs.ExceptWith(handlers); ast.Add(tryCatchBlock); } // Add whatever is left ast.AddRange(ConvertRangeToAst(body)); return ast; }
/// <summary> /// Convert the given set of bytecodes to an Ast node list. /// Split exception handlers into Ast try/catch blocks. /// </summary> private List <AstNode> ConvertToAst(List <ByteCode> body, HashSet <ExceptionHandler> ehs, List <ByteCodeBlock> blockStarts, int nestingLevel, Dictionary <int, ByteCode> offset2ByteCode) { var ast = new List <AstNode>(); // Split body in blocks while (ehs.Any()) { var tryCatchBlock = new AstTryCatchBlock(null); // Find the first and widest scope var tryStart = ehs.Min(eh => eh.StartPc); var tryEnd = ehs.Where(eh => eh.StartPc == tryStart).Max(eh => eh.EndPc); var handlers = ehs.Where(eh => (eh.StartPc == tryStart) && (eh.EndPc == tryEnd)).OrderBy(eh => eh.HandlerPc).ToList(); // Remember that any part of the body migt have been removed due to unreachability // Cut all instructions up to the try block { var tryStartIdx = 0; while ((tryStartIdx < body.Count) && (body[tryStartIdx].Offset < tryStart)) { tryStartIdx++; } if (tryStartIdx > 0) { ast.AddRange(ConvertRangeToAst(body.CutRange(0, tryStartIdx))); // Make sure the block before the try block ends with an unconditional control flow AddUnconditionalBranchToNext(ast, body, 0); } } // Cut the try block { var nestedEHs = new HashSet <ExceptionHandler>( ehs.Where(eh => ((tryStart <= eh.StartPc) && (eh.EndPc < tryEnd)) || ((tryStart < eh.StartPc) && (eh.EndPc <= tryEnd)))); ehs.ExceptWith(nestedEHs); var tryEndIdx = 0; while ((tryEndIdx < body.Count) && (body[tryEndIdx].Offset < tryEnd)) { tryEndIdx++; } var converted = ConvertToAst(body.CutRange(0, tryEndIdx), nestedEHs, blockStarts, nestingLevel + 1, offset2ByteCode); tryCatchBlock.TryBlock = new AstBlock(converted.Select(x => x.SourceLocation).FirstOrDefault(), converted); // Make sure the try block ends with an unconditional control flow AddUnconditionalBranchToNext(tryCatchBlock.TryBlock.Body, body, 0); } // Cut all handlers tryCatchBlock.CatchBlocks.Clear(); foreach (var iterator in handlers) { var eh = iterator; var handler = offset2ByteCode[eh.HandlerPc]; // body.First(x => x.Offset == eh.HandlerPc); var catchType = eh.IsCatchAll ? typeSystem.Object : AsTypeReference(eh.CatchType, XTypeUsageFlags.CatchType); var catchBlock = new AstTryCatchBlock.CatchBlock(handler.SourceLocation, tryCatchBlock) { ExceptionType = catchType, Body = new List <AstNode>() }; // Create catch "body" (actually a jump to the handler) // Handle the automatically pushed exception on the stack var ldexception = ldexceptions[eh]; catchBlock.Body.Add(new AstExpression(handler.SourceLocation, AstCode.Br, handler.Label(true))); if (ldexception.StoreTo == null || ldexception.StoreTo.Count == 0) { // Exception is not used catchBlock.ExceptionVariable = null; } else if (ldexception.StoreTo.Count == 1) { /*var first = catchBlock.Body.FirstOrDefault() as AstExpression; * if (first != null && * first.Code == AstCode.Pop && * first.Arguments[0].Code == AstCode.Ldloc && * first.Arguments[0].Operand == ldexception.StoreTo[0]) * { * // The exception is just poped - optimize it all away; * catchBlock.ExceptionVariable = new AstGeneratedVariable("ex_" + eh.HandlerPc.ToString("X2")); * catchBlock.Body.RemoveAt(0); * } * else*/ { catchBlock.ExceptionVariable = ldexception.StoreTo[0]; } } else { var exTemp = new AstGeneratedVariable("ex_" + eh.HandlerPc.ToString("X2"), null); catchBlock.ExceptionVariable = exTemp; foreach (var storeTo in ldexception.StoreTo) { catchBlock.Body.Insert(0, new AstExpression(catchBlock.SourceLocation, AstCode.Stloc, storeTo, new AstExpression(catchBlock.SourceLocation, AstCode.Ldloc, exTemp))); } } tryCatchBlock.CatchBlocks.Add(catchBlock); } ehs.ExceptWith(handlers); ast.Add(tryCatchBlock); } // Add whatever is left ast.AddRange(ConvertRangeToAst(body)); return(ast); }
List <AstNode> ConvertToAst(List <ByteCode> body, HashSet <ExceptionHandler> ehs) { var ast = new List <AstNode>(); while (ehs.Any()) { var tryCatchBlock = new AstTryCatchBlock(null); // Find the first and widest scope var tryStart = ehs.Min(eh => eh.TryStart.Offset); var tryEnd = ehs.Where(eh => eh.TryStart.Offset == tryStart).Max(eh => eh.TryEnd.Offset); var handlers = ehs.Where(eh => eh.TryStart.Offset == tryStart && eh.TryEnd.Offset == tryEnd).OrderBy(eh => eh.TryStart.Offset).ToList(); // Remember that any part of the body migt have been removed due to unreachability // Cut all instructions up to the try block { var tryStartIdx = 0; while (tryStartIdx < body.Count && body[tryStartIdx].Offset < tryStart) { tryStartIdx++; } ast.AddRange(ConvertToAst(CollectionExtensions.CutRange(body, 0, tryStartIdx))); } // Cut the try block { var nestedEHs = new HashSet <ExceptionHandler>( ehs.Where(eh => (tryStart <= eh.TryStart.Offset && eh.TryEnd.Offset < tryEnd) || (tryStart < eh.TryStart.Offset && eh.TryEnd.Offset <= tryEnd))); ehs.ExceptWith(nestedEHs); var tryEndIdx = 0; while (tryEndIdx < body.Count && body[tryEndIdx].Offset < tryEnd) { tryEndIdx++; } var converted = ConvertToAst(CollectionExtensions.CutRange(body, 0, tryEndIdx), nestedEHs); tryCatchBlock.TryBlock = new AstBlock(converted.Select(x => x.SourceLocation).FirstOrDefault(), converted); } // Cut all handlers tryCatchBlock.CatchBlocks.Clear(); foreach (var eh in handlers) { var handlerEndOffset = eh.HandlerEnd == null ? methodDef.Body.CodeSize : eh.HandlerEnd.Offset; var startIdx = 0; while (startIdx < body.Count && body[startIdx].Offset < eh.HandlerStart.Offset) { startIdx++; } var endIdx = 0; while (endIdx < body.Count && body[endIdx].Offset < handlerEndOffset) { endIdx++; } var nestedEHs = new HashSet <ExceptionHandler>(ehs.Where(e => (eh.HandlerStart.Offset <= e.TryStart.Offset && e.TryEnd.Offset < handlerEndOffset) || (eh.HandlerStart.Offset < e.TryStart.Offset && e.TryEnd.Offset <= handlerEndOffset))); ehs.ExceptWith(nestedEHs); var handlerAst = ConvertToAst(CollectionExtensions.CutRange(body, startIdx, endIdx - startIdx), nestedEHs); if (eh.HandlerType == ExceptionHandlerType.Catch) { var catchType = eh.CatchType.IsSystemObject() ? module.TypeSystem.Exception : XBuilder.AsTypeReference(module, eh.CatchType); var catchBlock = new AstTryCatchBlock.CatchBlock(handlerAst.Select(x => x.SourceLocation).FirstOrDefault(), tryCatchBlock) { ExceptionType = catchType, Body = handlerAst }; // Handle the automatically pushed exception on the stack var ldexception = ldexceptions[eh]; if (ldexception.StoreTo == null || ldexception.StoreTo.Count == 0) { // Exception is not used catchBlock.ExceptionVariable = null; } else if (ldexception.StoreTo.Count == 1) { var first = catchBlock.Body[0] as AstExpression; if (first != null && first.Code == AstCode.Pop && first.Arguments[0].Code == AstCode.Ldloc && first.Arguments[0].Operand == ldexception.StoreTo[0]) { // The exception is just poped - optimize it all away; if (context.Settings.AlwaysGenerateExceptionVariableForCatchBlocks) { catchBlock.ExceptionVariable = new AstGeneratedVariable("ex_" + eh.HandlerStart.Offset.ToString("X2"), null); } else { catchBlock.ExceptionVariable = null; } catchBlock.Body.RemoveAt(0); } else { catchBlock.ExceptionVariable = ldexception.StoreTo[0]; } } else { var exTemp = new AstGeneratedVariable("ex_" + eh.HandlerStart.Offset.ToString("X2"), null); catchBlock.ExceptionVariable = exTemp; foreach (var storeTo in ldexception.StoreTo) { catchBlock.Body.Insert(0, new AstExpression(catchBlock.SourceLocation, AstCode.Stloc, storeTo, new AstExpression(catchBlock.SourceLocation, AstCode.Ldloc, exTemp))); } } tryCatchBlock.CatchBlocks.Add(catchBlock); } else if (eh.HandlerType == ExceptionHandlerType.Finally) { tryCatchBlock.FinallyBlock = new AstBlock(handlerAst); } else if (eh.HandlerType == ExceptionHandlerType.Fault) { tryCatchBlock.FaultBlock = new AstBlock(handlerAst); } else { // TODO: ExceptionHandlerType.Filter } } ehs.ExceptWith(handlers); ast.Add(tryCatchBlock); } // Add whatever is left ast.AddRange(ConvertToAst(body)); return(ast); }
/// <summary> /// Create code for given catch block /// </summary> public override RLRange Visit(AstTryCatchBlock.CatchBlock node, AstNode parent) { // Allocate exception register var r = frame.AllocateTemp(node.ExceptionType.GetReference(targetPackage)); var first = this.Add(node.SourceLocation, RCode.Move_exception, r); var last = first; currentExceptionRegister.Push(r); // Store exception in exception variable (if any) if (node.ExceptionVariable != null) { var rVar = frame.GetArgument(node.ExceptionVariable); this.Add(node.SourceLocation, RCode.Move_object, rVar, r); } // Generate code for actual catch block. var result = Visit((AstBlock)node, parent); if (result != null) { last = result.Last; } currentExceptionRegister.Pop(); // Combine result return new RLRange(first, last, null); }
/// <summary> /// Create try/catch/finally/fault block /// </summary> public override RLRange Visit(AstTryCatchBlock node, AstNode parent) { var handler = new ExceptionHandler(); //if (node.FaultBlock != null) //{ // Debugger.Break(); //} // Setup instruction before/after my node. var first = new Instruction() { SequencePoint = node.SourceLocation}; var last = new Instruction(RCode.Nop); FinallyBlockState finState = null; FinallyBlockState outerFinState = tryCatchStack.FirstOrDefault(f => f.HasFinally); if (tryCatchStack.Count == 0) finallyState.FinallyStacks.Add(new List<FinallyBlockState>()); if (node.FinallyBlock != null) { // store finaly state finState = new FinallyBlockState(outerFinState, tryCatchStack.Count, this, first, last); finState.FinallyExceptionRegister = frame.AllocateTemp(new ClassReference("java.lang.Throwable")).Register; // clear the variable to make sure it isn't stale from a previous loop. // make sure this is outside the try block for edge case exceptions. this.Add(node.SourceLocation, RCode.Const, 0, finState.FinallyExceptionRegister); tryCatchStack.Push(finState); finallyState.FinallyStacks.Last().Add(finState); } else { finState = new FinallyBlockState(outerFinState, tryCatchStack.Count); tryCatchStack.Push(finState); } instructions.Add(first); // Emit try block handler.TryStart = first; node.TryBlock.AcceptOrDefault(this, node); handler.TryEnd = TryCatchGotoEnd(finState, last); var catchesStart = this.Add(AstNode.NoSource, RCode.Nop); // Emit "normal" catch blocks foreach (var catchBlock in node.CatchBlocks.Where(x => !x.IsCatchAll())) { var c = new Catch { Type = catchBlock.ExceptionType.GetReference(targetPackage) }; handler.Catches.Add(c); var catchStart = this.Add(catchBlock.SourceLocation, RCode.Nop); catchBlock.Accept(this, node); c.Instruction = catchStart; TryCatchGotoEnd(finState, last); } // Emit "catch all" (if any) var catchAllBlock = node.CatchBlocks.SingleOrDefault(x => x.IsCatchAll()); if (catchAllBlock != null) { var catchStart = this.Add(catchAllBlock.SourceLocation, RCode.Nop); catchAllBlock.Accept(this, node); handler.CatchAll = catchStart; TryCatchGotoEnd(finState, last); } var catchesEnd = this.Add(AstNode.NoSource, RCode.Nop); // clear try/catch/finally stack: we don't want to cover ourselves! tryCatchStack.Pop(); // Emit finally code if (node.FinallyBlock != null) { // preparation. var finallyStart = this.Add(node.FinallyBlock.SourceLocation, RCode.Move_exception, finState.FinallyExceptionRegister); instructions.Add(finState.NonException); // the original handler node.FinallyBlock.Accept(this, node); // prepare the routing this.Add(AstNode.NoSource, RCode.If_eqz, finState.AfterExceptionCheck, finState.FinallyExceptionRegister); this.Add(AstNode.NoSource, RCode.Throw, finState.FinallyExceptionRegister); instructions.Add(finState.AfterExceptionCheck); // Set up exception handlers. if (catchAllBlock == null) { // we need to cover the try block. handler.CatchAll = finallyStart; } if (node.CatchBlocks.Any()) { // we need to cover the catch blocks var finallyHandler = new ExceptionHandler { TryStart = catchesStart, TryEnd = catchesEnd, CatchAll = finallyStart }; body.Exceptions.Add(finallyHandler); } } // Add end instructions.Add(last); // Record catch/catch-all handler if ((handler.CatchAll != null) || handler.Catches.Any()) { body.Exceptions.Add(handler); } return new RLRange(first, last, null); }
public virtual TReturn Visit(AstTryCatchBlock node, TData data) { return(default(TReturn)); }
List<AstNode> ConvertToAst(List<ByteCode> body, HashSet<ExceptionHandler> ehs) { var ast = new List<AstNode>(); while (ehs.Any()) { var tryCatchBlock = new AstTryCatchBlock(null); // Find the first and widest scope var tryStart = ehs.Min(eh => eh.TryStart.Offset); var tryEnd = ehs.Where(eh => eh.TryStart.Offset == tryStart).Max(eh => eh.TryEnd.Offset); var handlers = ehs.Where(eh => eh.TryStart.Offset == tryStart && eh.TryEnd.Offset == tryEnd).OrderBy(eh => eh.TryStart.Offset).ToList(); // Remember that any part of the body migt have been removed due to unreachability // Cut all instructions up to the try block { var tryStartIdx = 0; while (tryStartIdx < body.Count && body[tryStartIdx].Offset < tryStart) tryStartIdx++; ast.AddRange(ConvertToAst(CollectionExtensions.CutRange(body, 0, tryStartIdx))); } // Cut the try block { var nestedEHs = new HashSet<ExceptionHandler>( ehs.Where(eh => (tryStart <= eh.TryStart.Offset && eh.TryEnd.Offset < tryEnd) || (tryStart < eh.TryStart.Offset && eh.TryEnd.Offset <= tryEnd))); ehs.ExceptWith(nestedEHs); var tryEndIdx = 0; while (tryEndIdx < body.Count && body[tryEndIdx].Offset < tryEnd) tryEndIdx++; var converted = ConvertToAst(CollectionExtensions.CutRange(body, 0, tryEndIdx), nestedEHs); tryCatchBlock.TryBlock = new AstBlock(converted.Select(x => x.SourceLocation).FirstOrDefault(), converted); } // Cut all handlers tryCatchBlock.CatchBlocks.Clear(); foreach (var eh in handlers) { var handlerEndOffset = eh.HandlerEnd == null ? methodDef.Body.CodeSize : eh.HandlerEnd.Offset; var startIdx = 0; while (startIdx < body.Count && body[startIdx].Offset < eh.HandlerStart.Offset) startIdx++; var endIdx = 0; while (endIdx < body.Count && body[endIdx].Offset < handlerEndOffset) endIdx++; var nestedEHs = new HashSet<ExceptionHandler>(ehs.Where(e => (eh.HandlerStart.Offset <= e.TryStart.Offset && e.TryEnd.Offset < handlerEndOffset) || (eh.HandlerStart.Offset < e.TryStart.Offset && e.TryEnd.Offset <= handlerEndOffset))); ehs.ExceptWith(nestedEHs); var handlerAst = ConvertToAst(CollectionExtensions.CutRange(body, startIdx, endIdx - startIdx), nestedEHs); if (eh.HandlerType == ExceptionHandlerType.Catch) { var catchType = eh.CatchType.IsSystemObject() ? module.TypeSystem.Exception : XBuilder.AsTypeReference(module, eh.CatchType); var catchBlock = new AstTryCatchBlock.CatchBlock(handlerAst.Select(x => x.SourceLocation).FirstOrDefault(), tryCatchBlock) { ExceptionType = catchType, Body = handlerAst }; // Handle the automatically pushed exception on the stack var ldexception = ldexceptions[eh]; if (ldexception.StoreTo == null || ldexception.StoreTo.Count == 0) { // Exception is not used catchBlock.ExceptionVariable = null; } else if (ldexception.StoreTo.Count == 1) { var first = catchBlock.Body[0] as AstExpression; if (first != null && first.Code == AstCode.Pop && first.Arguments[0].Code == AstCode.Ldloc && first.Arguments[0].Operand == ldexception.StoreTo[0]) { // The exception is just poped - optimize it all away; if (context.Settings.AlwaysGenerateExceptionVariableForCatchBlocks) catchBlock.ExceptionVariable = new AstGeneratedVariable("ex_" + eh.HandlerStart.Offset.ToString("X2"), null); else catchBlock.ExceptionVariable = null; catchBlock.Body.RemoveAt(0); } else { catchBlock.ExceptionVariable = ldexception.StoreTo[0]; } } else { var exTemp = new AstGeneratedVariable("ex_" + eh.HandlerStart.Offset.ToString("X2"), null); catchBlock.ExceptionVariable = exTemp; foreach (var storeTo in ldexception.StoreTo) { catchBlock.Body.Insert(0, new AstExpression(catchBlock.SourceLocation, AstCode.Stloc, storeTo, new AstExpression(catchBlock.SourceLocation, AstCode.Ldloc, exTemp))); } } tryCatchBlock.CatchBlocks.Add(catchBlock); } else if (eh.HandlerType == ExceptionHandlerType.Finally) { tryCatchBlock.FinallyBlock = new AstBlock(handlerAst); } else if (eh.HandlerType == ExceptionHandlerType.Fault) { tryCatchBlock.FaultBlock = new AstBlock(handlerAst); } else { // TODO: ExceptionHandlerType.Filter } } ehs.ExceptWith(handlers); ast.Add(tryCatchBlock); } // Add whatever is left ast.AddRange(ConvertToAst(body)); return ast; }
/// <summary> /// Create try/catch/finally/fault block /// </summary> public override RLRange Visit(AstTryCatchBlock node, AstNode parent) { var handler = new ExceptionHandler(); // Setup instruction after my node. var last = new Instruction(RCode.Nop); // Emit try block handler.TryStart = this.Add(node.SourceLocation, RCode.Nop); node.TryBlock.AcceptOrDefault(this, node); handler.TryEnd = this.Add(node.SourceLocation, RCode.Nop); // Emit finally code if (node.FinallyBlock != null) { using (labelManager.CreateContext()) { node.FinallyBlock.Accept(this, node); node.FinallyBlock.CleanResults(); } } // Jump to end this.Add(node.SourceLocation, RCode.Goto, last); // Replace returns in the try block InsertFinallyBeforeReturns(handler.TryStart, handler.TryEnd, node); // Prepare for finally handler var finallyStart = (node.FinallyBlock != null) ? new Instruction(RCode.Move_exception) : null; // Emit "normal" catch blocks foreach (var catchBlock in node.CatchBlocks.Where(x => !x.IsCatchAll())) { var c = new Catch { Type = catchBlock.ExceptionType.GetReference(targetPackage) }; handler.Catches.Add(c); var catchStart = this.Add(catchBlock.SourceLocation, RCode.Nop); catchBlock.Accept(this, node); var catchEnd = this.Add(catchBlock.SourceLocation, RCode.Nop); c.Instruction = catchStart; // Add finally block if (node.FinallyBlock != null) { // Emit finally code using (labelManager.CreateContext()) { node.FinallyBlock.Accept(this, node); node.FinallyBlock.CleanResults(); } // Write catch block in finally handler var catchFinallyHandler = new ExceptionHandler { TryStart = catchStart, TryEnd = catchEnd, CatchAll = finallyStart }; body.Exceptions.Add(catchFinallyHandler); } // Jump to end this.Add(node.SourceLocation, RCode.Goto, last); // Replace returns in the catch block InsertFinallyBeforeReturns(catchStart, catchEnd, node); } // Emit "catch all" (if any) var catchAllBlock = node.CatchBlocks.SingleOrDefault(x => x.IsCatchAll()); if (catchAllBlock != null) { var catchStart = this.Add(catchAllBlock.SourceLocation, RCode.Nop); catchAllBlock.Accept(this, node); var catchEnd = this.Add(catchAllBlock.SourceLocation, RCode.Nop); handler.CatchAll = catchStart; // Add finally block if (node.FinallyBlock != null) { // Emit finally code using (labelManager.CreateContext()) { node.FinallyBlock.Accept(this, node); node.FinallyBlock.CleanResults(); } // Write catch block in finally handler var catchFinallyHandler = new ExceptionHandler { TryStart = catchStart, TryEnd = catchEnd, CatchAll = finallyStart }; body.Exceptions.Add(catchFinallyHandler); } // Jump to end this.Add(node.SourceLocation, RCode.Goto, last); // Replace returns in the catch block InsertFinallyBeforeReturns(catchStart, catchEnd, node); } // Emit finally block (if any) if (node.FinallyBlock != null) { // Create finally handler var finallyHandler = new ExceptionHandler { TryStart = handler.TryStart, TryEnd = handler.TryEnd /*body.Instructions.Last()*/, CatchAll = finallyStart }; body.Exceptions.Add(finallyHandler); // Update move_exception instructions var r = frame.AllocateTemp(new ClassReference("java.lang.Throwable")); finallyStart.Registers.Add(r); instructions.Add(finallyStart); // Emit finally code using (labelManager.CreateContext()) { node.FinallyBlock.Accept(this, node); node.FinallyBlock.CleanResults(); } // Re-throw exception this.Add(node.SourceLocation, RCode.Throw, r); } // Add end instructions.Add(last); // Record catch/catch-all handler if ((handler.CatchAll != null) || handler.Catches.Any()) { body.Exceptions.Add(handler); } return(new RLRange(handler.TryStart, last, null)); }