package polyglot.ext.jl.ast; import polyglot.ast.*; import polyglot.util.*; import polyglot.types.*; import polyglot.visit.*; import java.util.*; /** * An immutable representation of a <code>try</code> block, one or more * <code>catch</code> blocks, and an optional <code>finally</code> block. */ public class Try_c extends Stmt_c implements Try { protected Block tryBlock; protected List catchBlocks; protected Block finallyBlock; public Try_c(Position pos, Block tryBlock, List catchBlocks, Block finallyBlock) { super(pos); this.tryBlock = tryBlock; this.catchBlocks = TypedList.copyAndCheck(catchBlocks, Catch.class, true); this.finallyBlock = finallyBlock; } /** Get the try block of the statement. */ public Block tryBlock() { return this.tryBlock; } /** Set the try block of the statement. */ public Try tryBlock(Block tryBlock) { Try_c n = (Try_c) copy(); n.tryBlock = tryBlock; return n; } /** Get the catch blocks of the statement. */ public List catchBlocks() { return Collections.unmodifiableList(this.catchBlocks); } /** Set the catch blocks of the statement. */ public Try catchBlocks(List catchBlocks) { Try_c n = (Try_c) copy(); n.catchBlocks = TypedList.copyAndCheck(catchBlocks, Catch.class, true); return n; } /** Get the finally block of the statement. */ public Block finallyBlock() { return this.finallyBlock; } /** Set the finally block of the statement. */ public Try finallyBlock(Block finallyBlock) { Try_c n = (Try_c) copy(); n.finallyBlock = finallyBlock; return n; } /** Reconstruct the statement. */ protected Try_c reconstruct(Block tryBlock, List catchBlocks, Block finallyBlock) { if (tryBlock != this.tryBlock || ! CollectionUtil.equals(catchBlocks, this.catchBlocks) || finallyBlock != this.finallyBlock) { Try_c n = (Try_c) copy(); n.tryBlock = tryBlock; n.catchBlocks = TypedList.copyAndCheck(catchBlocks, Catch.class, true); n.finallyBlock = finallyBlock; return n; } return this; } /** Visit the children of the statement. */ public Node visitChildren(NodeVisitor v) { Block tryBlock = (Block) visitChild(this.tryBlock, v); List catchBlocks = visitList(this.catchBlocks, v); Block finallyBlock = (Block) visitChild(this.finallyBlock, v); return reconstruct(tryBlock, catchBlocks, finallyBlock); } /** * Bypass all children when peforming an exception check. * exceptionCheck(), called from ExceptionChecker.leave(), * will handle visiting children. */ public NodeVisitor exceptionCheckEnter(ExceptionChecker ec) throws SemanticException { ec = (ExceptionChecker) super.exceptionCheckEnter(ec); return ec.bypassChildren(this); } /** * Performs exceptionChecking. This is a special method that is called * via the exceptionChecker's override method (i.e, doesn't follow the * standard model for visitation. * * @param ec The ExceptionChecker that was run against the * child node. It contains the exceptions that can be thrown by the try * block. */ public Node exceptionCheck(ExceptionChecker ec) throws SemanticException { TypeSystem ts = ec.typeSystem(); // Visit the try block. ec = ec.push(); Block tryBlock = (Block) visitChild(this.tryBlock, ec); // First, get exceptions from the try block. SubtypeSet thrown = ec.throwsSet(); SubtypeSet caught = new SubtypeSet(ts.Throwable()); ec = ec.pop(); // Add the unchecked exceptions. thrown.addAll(ts.uncheckedExceptions()); // Walk through our catch blocks, making sure that they each can // catch something. for (Iterator i = this.catchBlocks.iterator(); i.hasNext(); ) { Catch cb = (Catch) i.next(); Type catchType = cb.catchType(); // Check if the catch type is a supertype or a subtype of an // exception thrown in the try block. boolean match = false; for (Iterator j = thrown.iterator(); j.hasNext(); ) { Type ex = (Type) j.next(); if (ts.isSubtype(ex, catchType) || ts.isSubtype(catchType, ex)) { match = true; break; } } if (! match) { throw new SemanticException("The exception \"" + catchType + "\" is not thrown in the try block.", cb.position()); } // Check if the exception has already been caught. if (caught.contains(catchType)) { throw new SemanticException("The exception \"" + catchType + "\" has been caught by an earlier catch block.", cb.position()); } caught.add(catchType); } // Remove exceptions which have been caught. thrown.removeAll(caught); // "thrown" now contains any exceptions which were not caught. // We now visit the catch blocks and finallyBlock to get the // exceptions they throw. List catchBlocks = new ArrayList(this.catchBlocks.size()); for (Iterator i = this.catchBlocks.iterator(); i.hasNext(); ) { Catch cb = (Catch) i.next(); ec = ec.push(); cb = (Catch) visitChild(cb, ec); catchBlocks.add(cb); thrown.addAll(ec.throwsSet()); ec = ec.pop(); } Block finallyBlock = null; if (this.finallyBlock != null) { ec = ec.push(); finallyBlock = (Block) visitChild(this.finallyBlock, ec); // an interesting thing happens here... // if the finally block can complete normally, then all the // exceptions that the try-block and catch-blocks can throw // can be thrown by the try-catch-finally block. HOWEVER, if // the finally block can not complete normally, then the // try-catch-finally block can only throw the exceptions thrown // by the finally block. Examining the finally-block's reachability // will tell us if the finally-block can complete normally. if (!this.finallyBlock.reachable()) { // warn the user, and remove all the exceptions that have // been added by the try and catch blocks. if (false) { // don't warn the user; javac doesn't ec.errorQueue().enqueue(ErrorInfo.WARNING, "The finally block cannot complete normally", finallyBlock.position()); } thrown.clear(); } thrown.addAll(ec.throwsSet()); ec = ec.pop(); } // "thrown" now contains any exceptions which were not caught, // and any exceptions thrown by the catch blocks and finallyBlock // Add these exceptions to the exception checker's throw set. ec.throwsSet().addAll(thrown); return reconstruct(tryBlock, catchBlocks, finallyBlock).exceptions(ec.throwsSet()); } public String toString() { StringBuffer sb = new StringBuffer(); sb.append("try "); sb.append(tryBlock.toString()); int count = 0; for (Iterator it = catchBlocks.iterator(); it.hasNext(); ) { Catch cb = (Catch) it.next(); if (count++ > 2) { sb.append("..."); break; } sb.append(" "); sb.append(cb.toString()); } if (finallyBlock != null) { sb.append(" finally "); sb.append(finallyBlock.toString()); } return sb.toString(); } public void prettyPrint(CodeWriter w, PrettyPrinter tr) { w.write("try"); printSubStmt(tryBlock, w, tr); for (Iterator it = catchBlocks.iterator(); it.hasNext(); ) { Catch cb = (Catch) it.next(); w.newline(0); printBlock(cb, w, tr); } if (finallyBlock != null) { w.newline(0); w.write ("finally"); printSubStmt(finallyBlock, w, tr); } } public Term entry() { return tryBlock.entry(); } public List acceptCFG(CFGBuilder v, List succs) { // Add edges from the try entry to any catch blocks for Error and // RuntimeException. TypeSystem ts = v.typeSystem(); CFGBuilder v1 = v.push(this, false); CFGBuilder v2 = v.push(this, true); for (Iterator i = ts.uncheckedExceptions().iterator(); i.hasNext(); ) { Type type = (Type) i.next(); v1.visitThrow(tryBlock.entry(), type); } Term next; // Handle the normal return case. The throw case will be handled // specially. if (finallyBlock == null) { next = this; } else { next = finallyBlock.entry(); v.visitCFG(finallyBlock, this); } v1.visitCFG(tryBlock, next); for (Iterator it = catchBlocks.iterator(); it.hasNext(); ) { Catch cb = (Catch) it.next(); v2.visitCFG(cb, next); } return succs; } }