/*
* Copyright 2013 Guidewire Software, Inc.
*/
package gw.internal.gosu.ir.compiler.bytecode.statement;
import gw.internal.gosu.compiler.NamedLabel;
import gw.internal.gosu.ir.compiler.bytecode.AbstractBytecodeCompiler;
import gw.internal.gosu.ir.compiler.bytecode.IRBytecodeContext;
import gw.internal.gosu.ir.compiler.bytecode.IRBytecodeCompiler;
import gw.internal.gosu.ir.compiler.bytecode.IRCompilerLocalVar;
import gw.internal.gosu.ir.compiler.bytecode.IRFinallyCodePartitioner;
import gw.lang.ir.statement.IRTryCatchFinallyStatement;
import gw.lang.ir.statement.IRCatchClause;
import gw.lang.ir.IRType;
import gw.lang.ir.IRStatement;
import gw.lang.ir.IRSymbol;
import gw.internal.gosu.ir.nodes.JavaClassIRType;
import gw.internal.ext.org.objectweb.asm.Label;
import gw.internal.ext.org.objectweb.asm.MethodVisitor;
import gw.internal.ext.org.objectweb.asm.Opcodes;
import java.util.Collections;
import java.util.List;
public class IRTryCatchFinallyStatementCompiler extends AbstractBytecodeCompiler
{
private IRFinallyCodePartitioner _finallyPartitioner;
private IRTryCatchFinallyStatement _stmt;
private IRBytecodeContext _context;
private IRTryCatchFinallyStatement _stmt() {
return _stmt;
}
public static void compile( IRTryCatchFinallyStatement stmt, IRBytecodeContext context )
{
IRTryCatchFinallyStatementCompiler compiler = new IRTryCatchFinallyStatementCompiler( stmt, context );
compiler.compile();
}
private IRTryCatchFinallyStatementCompiler( IRTryCatchFinallyStatement stmt, IRBytecodeContext context )
{
_stmt = stmt;
_context = context;
}
private void compile()
{
MethodVisitor mv = _context.getMv();
Label tryStart = new NamedLabel( "TryStart" );
Label tryEnd = new NamedLabel( "TryEnd" );
Label tryCatchFinallyEnd = new NamedLabel( "TryCatchFinallyEnd" );
List<IRCatchClause> catchStmts = _stmt().getCatchStatements();
_finallyPartitioner = pushFinallyStmt();
try
{
compileTryStatement( mv, tryStart, tryEnd, tryCatchFinallyEnd );
if( catchStmts != null && !catchStmts.isEmpty() )
{
compileCatchStatements( mv, tryStart, tryEnd, tryCatchFinallyEnd, catchStmts );
}
if( hasFinally() )
{
compileFinallyStatement( mv, tryStart );
}
mv.visitLabel( tryCatchFinallyEnd );
}
finally
{
popFinallyStmt( _finallyPartitioner );
}
}
private void compileTryStatement( MethodVisitor mv, Label tryStart, Label tryEnd, Label tryCatchEnd )
{
mv.visitLabel( tryStart );
IRBytecodeCompiler.compileIRStatement( _stmt().getTryBody(), _context );
inlineLocalFinallyStmt( _stmt().getTryBody(), tryCatchEnd );
mv.visitLabel( tryEnd );
}
private void compileCatchStatements( MethodVisitor mv, Label tryStart, Label tryEnd, Label tryCatchEnd, List<IRCatchClause> catchStmts )
{
for( int i = 0; i < catchStmts.size(); i++ )
{
IRCatchClause catchStmt = catchStmts.get( i );
_context.pushScope( ); // scope for the exception param
try
{
IRType type = catchStmt.getIdentifier().getType();
Label catchStart = new NamedLabel( "CatchStart for " + (type == null ? "(no type)" : type.getName()) );
_context.visitLabel( catchStart );
declareCatchExtents( mv, tryStart, tryEnd, catchStart, type );
assignExceptionParam( mv, catchStmt.getIdentifier() );
IRBytecodeCompiler.compileIRStatement( catchStmt.getBody(), _context );
inlineLocalFinallyStmt( catchStmt.getBody(), tryCatchEnd );
}
finally
{
_context.popScope();
}
}
}
private void declareCatchExtents( MethodVisitor mv, Label tryStart, Label coverageEnd, Label handlerStart, IRType type )
{
Label start = tryStart;
Label end;
List<Label> starts = _finallyPartitioner == null ? Collections.<Label>emptyList() : _finallyPartitioner.getFinallyStarts();
List<Label> ends = _finallyPartitioner == null ? Collections.<Label>emptyList() : _finallyPartitioner.getFinallyEnds();
for( int i = 0; i < starts.size(); i++ )
{
end = starts.get( i );
if( end.getOffset() > coverageEnd.getOffset() )
{
break;
}
insertTryCatchBlock( mv, handlerStart, type, start, end );
start = ends.get( i );
}
end = coverageEnd;
insertTryCatchBlock( mv, handlerStart, type, start, end );
}
private void insertTryCatchBlock( MethodVisitor mv, Label handlerStart, IRType type, Label start, Label end )
{
if( start.getOffset() > end.getOffset() )
{
throw new IllegalStateException( "start label must precede the end label" );
} else if (start.getOffset() != end.getOffset()) { // If start == end, then we want to ignore it and not generate any exception table entry
mv.visitTryCatchBlock( start, end, handlerStart, type.getSlashName() );
}
}
private void assignExceptionParam( MethodVisitor mv, IRSymbol exceptionSym )
{
IRCompilerLocalVar exceptionVar = _context.getLocalVar(exceptionSym);
mv.visitVarInsn( Opcodes.ASTORE, exceptionVar.getIndex() );
}
private void compileFinallyStatement( MethodVisitor mv, Label tryStart )
{
Label finallyStart = new NamedLabel( "Finally Start ");
mv.visitLabel( finallyStart );
declareCatchExtents( mv, tryStart, finallyStart, finallyStart, JavaClassIRType.get( Throwable.class ));
int iExceptionIndex = _context.getLocalVar( new IRSymbol("**temp**throwable**", JavaClassIRType.get( Throwable.class ), true ) ).getIndex();
mv.visitVarInsn( Opcodes.ASTORE, iExceptionIndex );
IRBytecodeCompiler.compileIRStatement( _stmt().getFinallyBody(), _context );
if( _stmt().getFinallyBody().getLeastSignificantTerminalStatement() == null )
{
mv.visitVarInsn( Opcodes.ALOAD, iExceptionIndex );
mv.visitInsn( Opcodes.ATHROW );
}
}
private void inlineLocalFinallyStmt( IRStatement tryOrCatchStmt, Label labelEnd )
{
MethodVisitor mv = _context.getMv();
if( tryOrCatchStmt.getLeastSignificantTerminalStatement() == null )
{
if( hasFinally() )
{
_finallyPartitioner.inlineFinally();
NamedLabel endLabel = new NamedLabel( "EndFinally" + _finallyPartitioner.getFinallyEnds().size() );
_context.visitLabel( endLabel );
_finallyPartitioner.endInlineFinally( endLabel );
}
// Also jump to end of finally
mv.visitJumpInsn( Opcodes.GOTO, labelEnd );
}
}
private void popFinallyStmt( IRFinallyCodePartitioner partition )
{
if( hasFinally() )
{
_context.popFinallyStatement( partition );
}
}
private IRFinallyCodePartitioner pushFinallyStmt()
{
if( hasFinally() )
{
return _context.pushFinallyStatement( _stmt() );
}
else
{
return null;
}
}
private boolean hasFinally()
{
return _stmt().getFinallyBody() != null;
}
}