Adds the remaining (and more complex) Java 7 new language features. Adds the remaining Java 7 new language features: namely, multiexception catch and try-with-resources. Fixes issue 7999, issue 6960. Change-Id: If973b8c847d1202aca794221f32ae4b33b616f9c
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JClassType.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JClassType.java index 6c12533..d149f4f 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/ast/JClassType.java +++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JClassType.java
@@ -50,7 +50,7 @@ /** * Construct a bare-bones deserialized external class. */ - private JClassType(String name) { + JClassType(String name) { super(SourceOrigin.UNKNOWN, name); isAbstract = false; setExternal(true);
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JMethod.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JMethod.java index dede802..f11627e 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/ast/JMethod.java +++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JMethod.java
@@ -47,7 +47,7 @@ } private Object readResolve() { - return new JMethod(signature, enclosingType); + return new JMethod(signature, enclosingType, false); } } @@ -141,15 +141,30 @@ } /** + * Creates an externalized representation for a method that needs to be resolved. + * Useful to refer to methods of magic classes during GwtAstBuilder execution. + * + * @param fullClassName the class where the method is defined. + * @param signature the signature of the method (including its name). + * + */ + public static JMethod getExternalizedMethod(String fullClassName, String signature, + boolean isStatic) { + + JClassType cls = new JClassType(fullClassName); + return new JMethod(signature, cls, isStatic); + } + + /** * Construct a bare-bones deserialized external method. */ - private JMethod(String signature, JDeclaredType enclosingType) { + private JMethod(String signature, JDeclaredType enclosingType, boolean isStatic) { super(SourceOrigin.UNKNOWN); this.name = signature.substring(0, signature.indexOf('(')); this.enclosingType = enclosingType; this.signature = signature; this.isAbstract = false; - this.isStatic = false; + this.isStatic = isStatic; this.access = AccessModifier.PUBLIC.ordinal(); }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JMethodCall.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JMethodCall.java index 9afa7be..ee89165 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/ast/JMethodCall.java +++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JMethodCall.java
@@ -100,7 +100,8 @@ * allows us to preserve type information during the latter phases of * compilation. */ - public JMethodCall(SourceInfo info, JExpression instance, JMethod method, JType overrideReturnType) { + public JMethodCall(SourceInfo info, JExpression instance, JMethod method, + JType overrideReturnType) { super(info); assert (method != null); assert (instance != null || method.isStatic());
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JTryStatement.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JTryStatement.java index 1f2d0ca..1c2a707 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/ast/JTryStatement.java +++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JTryStatement.java
@@ -17,6 +17,7 @@ import com.google.gwt.dev.jjs.SourceInfo; +import java.io.Serializable; import java.util.List; /** @@ -24,27 +25,58 @@ */ public class JTryStatement extends JStatement { - private final List<JLocalRef> catchArgs; - private final List<JBlock> catchBlocks; + /** + * Represents the catch clause parts of the try statement. + */ + public static class CatchClause implements Serializable { + private final List<JType> catchTypes; + private final JLocalRef arg; + private final JBlock block; + + public CatchClause(List<JType> catchTypes, JLocalRef arg, JBlock block) { + this.catchTypes = catchTypes; + this.arg = arg; + this.block = block; + } + + public List<JType> getTypes() { + return catchTypes; + } + + public JLocalRef getArg() { + return arg; + } + + public JBlock getBlock() { + return block; + } + } + + private final List<CatchClause> catchClauses; private final JBlock finallyBlock; private final JBlock tryBlock; - public JTryStatement(SourceInfo info, JBlock tryBlock, List<JLocalRef> catchArgs, - List<JBlock> catchBlocks, JBlock finallyBlock) { + /** + * Construct a Java try statement. + * + * Parameters catchTypes, catchArgs and catchBlocks must agree on size. Each element of each + * of these lists corresponds to a catch statement. + * + * @param info the source information. + * @param tryBlock the statement block inside the try construct. + * @param catchClauses each element of this list contains a catch clause. + * @param finallyBlock the statement block corresponding to the finally construct. + */ + public JTryStatement(SourceInfo info, JBlock tryBlock, List<CatchClause> catchClauses, + JBlock finallyBlock) { super(info); - assert (catchArgs.size() == catchBlocks.size()); this.tryBlock = tryBlock; - this.catchArgs = catchArgs; - this.catchBlocks = catchBlocks; + this.catchClauses = catchClauses; this.finallyBlock = finallyBlock; } - public List<JLocalRef> getCatchArgs() { - return catchArgs; - } - - public List<JBlock> getCatchBlocks() { - return catchBlocks; + public List<CatchClause> getCatchClauses() { + return catchClauses; } public JBlock getFinallyBlock() { @@ -58,8 +90,11 @@ public void traverse(JVisitor visitor, Context ctx) { if (visitor.visit(this, ctx)) { visitor.accept(tryBlock); - visitor.accept(catchArgs); - visitor.accept(catchBlocks); + + for (CatchClause clause : catchClauses) { + visitor.accept(clause.getArg()); + visitor.accept(clause.getBlock()); + } // TODO: normalize this so it's never null? if (finallyBlock != null) { visitor.accept(finallyBlock);
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/CatchBlockNormalizer.java b/dev/core/src/com/google/gwt/dev/jjs/impl/CatchBlockNormalizer.java index c055674..f903e8b 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/impl/CatchBlockNormalizer.java +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/CatchBlockNormalizer.java
@@ -17,6 +17,8 @@ import com.google.gwt.dev.jjs.SourceInfo; import com.google.gwt.dev.jjs.ast.Context; +import com.google.gwt.dev.jjs.ast.JBinaryOperation; +import com.google.gwt.dev.jjs.ast.JBinaryOperator; import com.google.gwt.dev.jjs.ast.JBlock; import com.google.gwt.dev.jjs.ast.JDeclarationStatement; import com.google.gwt.dev.jjs.ast.JExpression; @@ -28,11 +30,13 @@ import com.google.gwt.dev.jjs.ast.JMethodBody; import com.google.gwt.dev.jjs.ast.JMethodCall; import com.google.gwt.dev.jjs.ast.JModVisitor; +import com.google.gwt.dev.jjs.ast.JPrimitiveType; import com.google.gwt.dev.jjs.ast.JProgram; import com.google.gwt.dev.jjs.ast.JReferenceType; import com.google.gwt.dev.jjs.ast.JStatement; import com.google.gwt.dev.jjs.ast.JThrowStatement; import com.google.gwt.dev.jjs.ast.JTryStatement; +import com.google.gwt.dev.jjs.ast.JType; import java.util.ArrayList; import java.util.List; @@ -58,11 +62,11 @@ // @Override @Override public void endVisit(JTryStatement x, Context ctx) { - if (x.getCatchBlocks().isEmpty()) { + if (x.getCatchClauses().isEmpty()) { return; } - SourceInfo catchInfo = x.getCatchBlocks().get(0).getSourceInfo(); + SourceInfo catchInfo = x.getCatchClauses().get(0).getBlock().getSourceInfo(); JLocal exVar = popTempLocal(); JBlock newCatchBlock = new JBlock(catchInfo); @@ -77,19 +81,34 @@ /* * Build up a series of if, else if statements to test the type of the - * exception object against the type of the user's catch block. + * exception object against the types of the user's catch block. Each catch block might have + * multiple types in Java 7. * * Go backwards so we can nest the else statements in the correct order! */ - // rethrow the current exception if no one caught it + // rethrow the current exception if no one caught it. JStatement cur = new JThrowStatement(catchInfo, new JLocalRef(catchInfo, exVar)); - for (int i = x.getCatchBlocks().size() - 1; i >= 0; --i) { - JBlock block = x.getCatchBlocks().get(i); - JLocalRef arg = x.getCatchArgs().get(i); + for (int i = x.getCatchClauses().size() - 1; i >= 0; i--) { + JTryStatement.CatchClause clause = x.getCatchClauses().get(i); + JBlock block = clause.getBlock(); + JLocalRef arg = clause.getArg(); + List<JType> exceptionsTypes = clause.getTypes(); catchInfo = block.getSourceInfo(); - JReferenceType argType = (JReferenceType) arg.getType(); - // if ($e instanceof ArgType) { var userVar = $e; <user code> } - JExpression ifTest = new JInstanceOf(catchInfo, argType, new JLocalRef(catchInfo, exVar)); + + // if ($e instanceof ArgType1 or $e instanceof ArgType2 ...) { + // var userVar = $e; <user code> + // } + + // Handle the first Exception type. + JExpression ifTest = new JInstanceOf(catchInfo, (JReferenceType) exceptionsTypes.get(0), + new JLocalRef(catchInfo, exVar)); + // Handle the rest of the Exception types if any. + for (int j = 1; j < exceptionsTypes.size(); j++) { + JExpression orExp = new JInstanceOf(catchInfo, (JReferenceType) exceptionsTypes.get(j), + new JLocalRef(catchInfo, exVar)); + ifTest = new JBinaryOperation(catchInfo, JPrimitiveType.BOOLEAN, JBinaryOperator.OR, + ifTest, orExp); + } JDeclarationStatement declaration = new JDeclarationStatement(catchInfo, arg, new JLocalRef(catchInfo, exVar)); block.addStmt(0, declaration); @@ -98,10 +117,13 @@ } newCatchBlock.addStmt(cur); - x.getCatchArgs().clear(); - x.getCatchArgs().add(new JLocalRef(newCatchBlock.getSourceInfo(), exVar)); - x.getCatchBlocks().clear(); - x.getCatchBlocks().add(newCatchBlock); + + // Replace with a single catch block. + x.getCatchClauses().clear(); + List<JType> newCatchTypes = new ArrayList<JType>(1); + newCatchTypes.add(exVar.getType()); + x.getCatchClauses().add(new JTryStatement.CatchClause(newCatchTypes, + new JLocalRef(newCatchBlock.getSourceInfo(), exVar), newCatchBlock)); } // @Override @@ -115,7 +137,7 @@ // @Override @Override public boolean visit(JTryStatement x, Context ctx) { - if (!x.getCatchBlocks().isEmpty()) { + if (!x.getCatchClauses().isEmpty()) { pushTempLocal(x.getSourceInfo()); } return true;
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/DeadCodeElimination.java b/dev/core/src/com/google/gwt/dev/jjs/impl/DeadCodeElimination.java index 0907c57..2c778cf 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/impl/DeadCodeElimination.java +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/DeadCodeElimination.java
@@ -579,24 +579,31 @@ @Override public void endVisit(JTryStatement x, Context ctx) { // 1) Remove catch blocks whose exception type is not instantiable. - List<JLocalRef> catchArgs = x.getCatchArgs(); - List<JBlock> catchBlocks = x.getCatchBlocks(); - Iterator<JLocalRef> itA = catchArgs.iterator(); - Iterator<JBlock> itB = catchBlocks.iterator(); - while (itA.hasNext()) { - JLocalRef localRef = itA.next(); - itB.next(); - JReferenceType type = (JReferenceType) localRef.getType(); - if (!program.typeOracle.isInstantiatedType(type) || type == program.getTypeNull()) { - itA.remove(); - itB.remove(); + List<JTryStatement.CatchClause> catchClauses = x.getCatchClauses(); + + Iterator<JTryStatement.CatchClause> itClauses = catchClauses.iterator(); + while (itClauses.hasNext()) { + JTryStatement.CatchClause clause = itClauses.next(); + // Go over the types in the multiexception and remove the ones that are not instantiable. + Iterator<JType> itTypes = clause.getTypes().iterator(); + while (itTypes.hasNext()) { + JReferenceType type = (JReferenceType) itTypes.next(); + if (!program.typeOracle.isInstantiatedType(type) || type == program.getTypeNull()) { + itTypes.remove(); + madeChanges(); + } + } + + // if all exception types are gone then remove whole clause. + if (clause.getTypes().isEmpty()) { + itClauses.remove(); madeChanges(); } } // Compute properties regarding the state of this try statement boolean noTry = Simplifier.isEmpty(x.getTryBlock()); - boolean noCatch = catchArgs.size() == 0; + boolean noCatch = catchClauses.size() == 0; boolean noFinally = Simplifier.isEmpty(x.getFinallyBlock()); if (noTry) {
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java index 81e13ee..c0e1423 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java
@@ -432,12 +432,9 @@ @Override public boolean visit(JTryStatement x, Context ctx) { accept(x.getTryBlock()); - - List<JLocalRef> catchArgs = x.getCatchArgs(); - List<JBlock> catchBlocks = x.getCatchBlocks(); - for (int i = 0, c = catchArgs.size(); i < c; ++i) { - JLocalRef arg = catchArgs.get(i); - JBlock catchBlock = catchBlocks.get(i); + for (JTryStatement.CatchClause clause : x.getCatchClauses()) { + JLocalRef arg = clause.getArg(); + JBlock catchBlock = clause.getBlock(); JsCatch jsCatch = new JsCatch(x.getSourceInfo(), peek(), arg.getTarget().getName()); JsParameter jsParam = jsCatch.getParameter(); names.put(arg.getTarget(), jsParam.getName()); @@ -1466,12 +1463,12 @@ } } - int size = x.getCatchArgs().size(); - assert (size < 2 && size == x.getCatchBlocks().size()); + int size = x.getCatchClauses().size(); + assert (size < 2); if (size == 1) { JsBlock catchBlock = (JsBlock) pop(); // catchBlocks pop(); // catchArgs - JsCatch jsCatch = catchMap.get(x.getCatchBlocks().get(0)); + JsCatch jsCatch = catchMap.get(x.getCatchClauses().get(0).getBlock()); jsCatch.setBody(catchBlock); jsTry.getCatches().add(jsCatch); }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/GwtAstBuilder.java b/dev/core/src/com/google/gwt/dev/jjs/impl/GwtAstBuilder.java index c6c75e5..efa5c90 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/impl/GwtAstBuilder.java +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/GwtAstBuilder.java
@@ -177,7 +177,9 @@ import org.eclipse.jdt.internal.compiler.ast.TrueLiteral; import org.eclipse.jdt.internal.compiler.ast.TryStatement; import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; +import org.eclipse.jdt.internal.compiler.ast.TypeReference; import org.eclipse.jdt.internal.compiler.ast.UnaryExpression; +import org.eclipse.jdt.internal.compiler.ast.UnionTypeReference; import org.eclipse.jdt.internal.compiler.ast.WhileStatement; import org.eclipse.jdt.internal.compiler.impl.Constant; import org.eclipse.jdt.internal.compiler.lookup.BaseTypeBinding; @@ -286,7 +288,8 @@ } } - private void processClassLiteral(JsNameRef nameRef, SourceInfo info, JType type, JsContext ctx) { + private void processClassLiteral(JsNameRef nameRef, SourceInfo info, JType type, + JsContext ctx) { assert !ctx.isLvalue(); JsniClassLiteral classLiteral = new JsniClassLiteral(info, nameRef.getIdent(), type); nativeMethodBody.addClassRef(classLiteral); @@ -1423,19 +1426,162 @@ List<JBlock> catchBlocks = pop(x.catchBlocks); JBlock tryBlock = pop(x.tryBlock); - List<JLocalRef> catchArgs = new ArrayList<JLocalRef>(); + if (x.resources.length > 0) { + tryBlock = normalizeTryWithResources(info, x, tryBlock, scope); + } + List<JTryStatement.CatchClause> catchClauses = + new ArrayList<JTryStatement.CatchClause>(); if (x.catchBlocks != null) { - for (Argument argument : x.catchArguments) { + for (int i = 0; i < x.catchArguments.length; i++) { + Argument argument = x.catchArguments[i]; JLocal local = (JLocal) curMethod.locals.get(argument.binding); - catchArgs.add(new JLocalRef(info, local)); + + List<JType> catchTypes = new ArrayList<JType>(); + if (argument.type instanceof UnionTypeReference) { + // This is a multiexception + for (TypeReference type : ((UnionTypeReference) argument.type).typeReferences) { + catchTypes.add(typeMap.get(type.resolvedType)); + } + } else { + // Regular exception + catchTypes.add(local.getType()); + } + catchClauses.add(new JTryStatement.CatchClause(catchTypes, new JLocalRef(info, local), + catchBlocks.get(i))); } } - push(new JTryStatement(info, tryBlock, catchArgs, catchBlocks, finallyBlock)); + push(new JTryStatement(info, tryBlock, catchClauses, finallyBlock)); } catch (Throwable e) { throw translateException(x, e); } } + private JBlock normalizeTryWithResources(SourceInfo info, TryStatement x, JBlock tryBlock, + BlockScope scope) { + /** + * Apply the following source transformation: + * + * try (A1 a1 = new A1(); ... ; An an = new An()) { + * ... tryBlock... + * } ...catch/finally blocks + * + * to + * + * try { + * A1 a1 = new A1();... ; An an = new An(); + * Throwable $exception = null; + * try { + * ... tryBlock... + * } catch (Throwable t) { + * $exception = t; + * throw t; + * } finally { + * $exception = Exceptions.safeClose(an, $exception); + * ... + * $exception = Exceptions.safeClose(a1, $exception); + * if ($exception != null) { + * throw $exception; + * } + * } ...catch/finally blocks + * + */ + + JBlock innerBlock = new JBlock(info); + // add resource variables + List<JLocal> resourceVariables = new ArrayList<JLocal>(); + for (int i = x.resources.length - 1; i >= 0; i--) { + // Needs to iterate back to front to be inline with the contents of the stack. + + JDeclarationStatement resourceDecl = pop(x.resources[i]); + + JLocal resourceVar = (JLocal) curMethod.locals.get(x.resources[i].binding); + resourceVariables.add(0, resourceVar); + innerBlock.addStmt(0, resourceDecl); + } + + // add exception variable + JLocal exceptionVar = + createTempLocal(info, "$primary_ex", javaLangThrowable, false, curMethod.body); + + innerBlock.addStmt(makeDeclaration(info, exceptionVar, JNullLiteral.INSTANCE)); + + // create catch block + List<JTryStatement.CatchClause> catchClauses = new ArrayList<JTryStatement.CatchClause>(1); + + List<JType> clauseTypes = new ArrayList<JType>(1); + clauseTypes.add(javaLangThrowable); + + // add catch exception variable. + JLocal catchVar = + createTempLocal(info, "$caught_ex", javaLangThrowable, false, curMethod.body); + + JBlock catchBlock = new JBlock(info); + catchBlock.addStmt(createAssignment(info, javaLangThrowable, exceptionVar, catchVar)); + catchBlock.addStmt(new JThrowStatement(info, new JLocalRef(info, exceptionVar))); + + catchClauses.add(new JTryStatement.CatchClause(clauseTypes, new JLocalRef(info, catchVar), + catchBlock)); + + // create finally block + JBlock finallyBlock = new JBlock(info); + for (int i = x.resources.length - 1; i >= 0; i--) { + finallyBlock.addStmt(createCloseBlockFor(info, x.resources[i], + resourceVariables.get(i), exceptionVar, scope)); + } + + // if (exception != null) throw exception + JExpression exceptionNotNull = new JBinaryOperation(info, JPrimitiveType.BOOLEAN, + JBinaryOperator.NEQ, new JLocalRef(info, exceptionVar), JNullLiteral.INSTANCE); + finallyBlock.addStmt(new JIfStatement(info, exceptionNotNull, + new JThrowStatement(info, new JLocalRef(info, exceptionVar)), null)); + + // Stitch all together into a inner try block + innerBlock.addStmt(new JTryStatement(info, tryBlock, catchClauses, + finallyBlock)); + return innerBlock; + } + + private JLocal createTempLocal(SourceInfo info, String prefix, JType type, boolean isFinal, + JMethodBody enclosingMethodBody) { + int index = curMethod.body.getLocals().size() + 1; + return JProgram.createLocal(info, prefix + "_" + index, + javaLangThrowable, false, curMethod.body); + } + + private JStatement createCloseBlockFor(final SourceInfo info, final LocalDeclaration resource, + JLocal resourceVar, JLocal exceptionVar, BlockScope scope) { + /** + * Create the following code: + * + * $ex = Exceptions.safeClose(resource, $ex); + * + * which is equivalent to + * + * if (resource != null) { + * try { + * resource.close(); + * } catch (Throwable t) { + * if ($ex == null) { + * $ex = t; + * } else { + * $ex.addSuppressed(t); + * } + * } + */ + + JMethodCall safeCloseCall = new JMethodCall(info, null, SAFE_CLOSE_METHOD); + safeCloseCall.addArg(0, new JLocalRef(info, resourceVar)); + safeCloseCall.addArg(1, new JLocalRef(info, exceptionVar)); + + return new JBinaryOperation(info, javaLangThrowable, JBinaryOperator.ASG, new JLocalRef(info, + exceptionVar), safeCloseCall).makeStatement(); + } + + private JStatement createAssignment(SourceInfo info, JType type, JLocal lhs, JLocal rhs) { + return new JBinaryOperation(info, type, JBinaryOperator.ASG, new JLocalRef(info, lhs), + new JLocalRef(info, rhs)).makeStatement(); + } + @Override public void endVisit(TypeDeclaration x, ClassScope scope) { endVisit(x); @@ -2784,6 +2930,8 @@ private static final char[] VALUE = "Value".toCharArray(); private static final char[] VALUE_OF = "valueOf".toCharArray(); private static final char[] VALUES = "values".toCharArray(); + private static final char[] CLOSE = "close".toCharArray(); + private static final char[] ADDSUPRESSED = "addSuppressed".toCharArray(); static { InternalCompilerException.preload(); @@ -2896,6 +3044,8 @@ JClassType javaLangString = null; + JClassType javaLangThrowable = null; + Map<MethodDeclaration, JsniMethod> jsniMethods; Map<String, Binding> jsniRefs; @@ -2909,6 +3059,16 @@ private String sourceMapPath; /** + * Externalized class and method form for Exceptions.safeClose() to provide support + * for try-with-resources. + * + * The externalized form will be resolved during AST stitching. + */ + static JMethod SAFE_CLOSE_METHOD = JMethod.getExternalizedMethod("com.google.gwt.lang.Exceptions", + "safeClose(Ljava/lang/AutoCloseable;Ljava/lang/Throwable;)Ljava/lang/Throwable;", true); + + + /** * Builds all the GWT AST nodes that correspond to one Java source file. * * @param cud The compiled form of the Java source from the JDT. @@ -2939,6 +3099,7 @@ javaLangObject = (JClassType) typeMap.get(cud.scope.getJavaLangObject()); javaLangString = (JClassType) typeMap.get(cud.scope.getJavaLangString()); javaLangClass = (JClassType) typeMap.get(cud.scope.getJavaLangClass()); + javaLangThrowable = (JClassType) typeMap.get(cud.scope.getJavaLangThrowable()); for (TypeDeclaration typeDecl : cud.types) { // Resolve super type / interface relationships. @@ -2964,7 +3125,7 @@ javaLangObject = null; javaLangString = null; javaLangClass = null; - + javaLangThrowable = null; return result; }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/ToStringGenerationVisitor.java b/dev/core/src/com/google/gwt/dev/jjs/impl/ToStringGenerationVisitor.java index 691752a..d2c69de 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/impl/ToStringGenerationVisitor.java +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/ToStringGenerationVisitor.java
@@ -132,6 +132,7 @@ protected static final char[] CHARS_NATIVE = "native ".toCharArray(); protected static final char[] CHARS_NEW = "new ".toCharArray(); protected static final char[] CHARS_NULL = "null".toCharArray(); + protected static final char[] CHARS_PIPE = " | ".toCharArray(); protected static final char[] CHARS_PRIVATE = "private ".toCharArray(); protected static final char[] CHARS_PROTECTED = "protected ".toCharArray(); protected static final char[] CHARS_PUBLIC = "public ".toCharArray(); @@ -887,15 +888,22 @@ public boolean visit(JTryStatement x, Context ctx) { print(CHARS_TRY); accept(x.getTryBlock()); - for (int i = 0, c = x.getCatchArgs().size(); i < c; ++i) { + for (JTryStatement.CatchClause clause : x.getCatchClauses()) { print(CHARS_CATCH); lparen(); - JLocalRef localRef = x.getCatchArgs().get(i); - accept(localRef.getTarget()); + + Iterator<JType> it = clause.getTypes().iterator(); + printTypeName(it.next()); + while (it.hasNext()) { + print(CHARS_PIPE); + printTypeName(it.next()); + } + space(); + + printName(clause.getArg().getTarget()); rparen(); space(); - JBlock block = x.getCatchBlocks().get(i); - accept(block); + accept(clause.getBlock()); } if (x.getFinallyBlock() != null) { print(CHARS_FINALLY); @@ -1150,11 +1158,15 @@ } protected void visitCollectionWithCommas(Iterator<? extends JNode> iter) { + visitCollectionWith(CHARS_COMMA, iter); + } + + protected void visitCollectionWith(char[] ch, Iterator<? extends JNode> iter) { if (iter.hasNext()) { accept(iter.next()); } while (iter.hasNext()) { - print(CHARS_COMMA); + print(ch); accept(iter.next()); } }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/TypeTightener.java b/dev/core/src/com/google/gwt/dev/jjs/impl/TypeTightener.java index ae6205c..5ac6ea7 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/impl/TypeTightener.java +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/TypeTightener.java
@@ -34,7 +34,6 @@ import com.google.gwt.dev.jjs.ast.JInstanceOf; import com.google.gwt.dev.jjs.ast.JInterfaceType; import com.google.gwt.dev.jjs.ast.JLocal; -import com.google.gwt.dev.jjs.ast.JLocalRef; import com.google.gwt.dev.jjs.ast.JMethod; import com.google.gwt.dev.jjs.ast.JMethodCall; import com.google.gwt.dev.jjs.ast.JModVisitor; @@ -291,8 +290,8 @@ public void endVisit(JTryStatement x, Context ctx) { // Never tighten args to catch blocks // Fake an assignment-to-self to prevent tightening - for (JLocalRef arg : x.getCatchArgs()) { - addAssignment(arg.getTarget(), arg); + for (JTryStatement.CatchClause clause : x.getCatchClauses()) { + addAssignment(clause.getArg().getTarget(), clause.getArg()); } }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/UnifyAst.java b/dev/core/src/com/google/gwt/dev/jjs/impl/UnifyAst.java index 5aa134c..346eff1 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/impl/UnifyAst.java +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/UnifyAst.java
@@ -61,6 +61,7 @@ import com.google.gwt.dev.jjs.ast.JReturnStatement; import com.google.gwt.dev.jjs.ast.JStringLiteral; import com.google.gwt.dev.jjs.ast.JThisRef; +import com.google.gwt.dev.jjs.ast.JTryStatement; import com.google.gwt.dev.jjs.ast.JType; import com.google.gwt.dev.jjs.ast.JVariable; import com.google.gwt.dev.jjs.ast.js.JsniFieldRef; @@ -318,6 +319,20 @@ } @Override + public void endVisit(JTryStatement x, Context ctx) { + // Needs to resolve the Exceptions Types explicitly they are multiple in Java 7 and + // potentially different from the one in the exception variable. + for (JTryStatement.CatchClause clause : x.getCatchClauses()) { + List<JType> types = clause.getTypes(); + for (int i = 0; i < types.size(); i++) { + JReferenceType resolvedType = translate((JReferenceType) types.get(i)); + assert resolvedType.replaces(types.get(i)); + types.set(i, resolvedType); + } + } + } + + @Override public void endVisit(JVariable x, Context ctx) { x.setType(translate(x.getType())); }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgBuilder.java b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgBuilder.java index f643eb7..9b7f6d3 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgBuilder.java +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgBuilder.java
@@ -763,7 +763,8 @@ List<Integer> catchBlockPos = new ArrayList<Integer>(); List<List<Exit>> catchExits = new ArrayList<List<Exit>>(); - for (JBlock b : x.getCatchBlocks()) { + for (JTryStatement.CatchClause clause : x.getCatchClauses()) { + JBlock b = clause.getBlock(); catchBlockPos.add(nodes.size()); accept(b); catchExits.add(removeCurrentExits()); @@ -790,12 +791,16 @@ if (e.isThrow()) { // If execution of the try block completes abruptly because of a // throw of a value V, then there is a choice: - nextCatchBlock : for (int i = 0; i < x.getCatchArgs().size(); ++i) { + nextCatchBlock : for (int i = 0; i < x.getCatchClauses().size(); ++i) { // If the run-time type of V is assignable (�5.2) to the // Parameter of any catch clause of the try statement, then // the first (leftmost) such catch clause is selected. - JClassType catchType = - (JClassType) x.getCatchArgs().get(i).getType(); + + // TODO(rluble): we are safely overapproximating the exception + // caught in this block by the type of the exception variable. + // We could do better in multiexceptions. + JClassType catchType = + (JClassType) x.getCatchClauses().get(i).getArg().getType(); JType exceptionType = e.getExceptionType(); boolean canCatch = false; @@ -806,7 +811,12 @@ // Catch clause fully covers exception type. We'll land // here for sure. canCatch = true; - fullCatch = true; + // Safe approximation. If it is a multi exception with only one + // exception declared in the clause then the variable is of the + // the same type as the exception declared and the approximation + // is exact hence and this is a full catch. + fullCatch = x.getCatchClauses().get(i).getTypes().size() == 1 && + x.getCatchClauses().get(i).getTypes().get(0) == catchType; } else if (typeOracle.canTriviallyCast(catchType, exceptionType)) { // We can land here if we throw some subclass of // exceptionType @@ -864,12 +874,16 @@ // If execution of the try block completes abruptly because of a // throw of a value V, then there is a choice: - nextCatchBlock : for (int i = 0; i < x.getCatchArgs().size(); ++i) { + nextCatchBlock : for (int i = 0; i < x.getCatchClauses().size(); ++i) { // If the run-time type of V is assignable to the parameter of any // catch clause of the try statement, then the first // (leftmost) such catch clause is selected. - JClassType catchType = - (JClassType) x.getCatchArgs().get(i).getType(); + + // TODO(rluble): we are safely overapproximating the exception + // caught in this block by the type of the exception variable. + // We could do better in multiexceptions. + JClassType catchType = + (JClassType) x.getCatchClauses().get(i).getArg().getType(); JType exceptionType = e.getExceptionType(); boolean canCatch = false; @@ -880,7 +894,12 @@ // Catch clause fully covers exception type. We'll land // here for sure. canCatch = true; - fullCatch = true; + // Safe approximation. If it is a multi exception with only one + // exception declared in the clause then the variable is of the + // the same type as the exception declared and the approximation + // is exact hence and this is a full catch. + fullCatch = x.getCatchClauses().get(i).getTypes().size() == 1 && + x.getCatchClauses().get(i).getTypes().get(0) == catchType; } else if (typeOracle.canTriviallyCast(catchType, exceptionType)) { // We can land here if we throw some subclass of // exceptionType
diff --git a/dev/core/super/com/google/gwt/dev/jjs/intrinsic/com/google/gwt/lang/Exceptions.java b/dev/core/super/com/google/gwt/dev/jjs/intrinsic/com/google/gwt/lang/Exceptions.java index c437453..09e4bab 100644 --- a/dev/core/super/com/google/gwt/dev/jjs/intrinsic/com/google/gwt/lang/Exceptions.java +++ b/dev/core/super/com/google/gwt/dev/jjs/intrinsic/com/google/gwt/lang/Exceptions.java
@@ -65,5 +65,29 @@ static boolean throwAssertionError_Object(Object message) { throw new AssertionError(message); } + + /** + * Use by the try-with-resources construct. Look at + * {@link com.google.gwt.dev.jjs.impl.GwtAstBuilder.createCloseBlockFor}. + * + * @param resource a resource implementing the AutoCloseable interface. + * @param mainException an exception being propagated. + * @return an exception to propagate or {@code null} if none. + */ + static Throwable safeClose(AutoCloseable resource, Throwable mainException) { + if (resource == null) { + return mainException; + } + + try { + resource.close(); + } catch (Throwable e) { + if (mainException == null) { + return e; + } + mainException.addSuppressed(e); + } + return mainException; + } // CHECKSTYLE_ON }
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/JJSTestBase.java b/dev/core/test/com/google/gwt/dev/jjs/impl/JJSTestBase.java index 747ff4a..a17a51e 100644 --- a/dev/core/test/com/google/gwt/dev/jjs/impl/JJSTestBase.java +++ b/dev/core/test/com/google/gwt/dev/jjs/impl/JJSTestBase.java
@@ -255,12 +255,29 @@ } }); + sourceOracle.addOrReplace(new MockJavaResource("java.lang.AutoCloseable") { + @Override + public CharSequence getContent() { + return "" + + "package java.lang;" + + "public interface AutoCloseable { " + + " void close() throws Exception;" + + "}"; + } + }); + sourceOracle.addOrReplace(new MockJavaResource("com.google.gwt.lang.Exceptions") { @Override public CharSequence getContent() { - return "package com.google.gwt.lang;" + - "public class Exceptions { static boolean throwAssertionError() { throw new RuntimeException(); } }"; - } + return "" + + "package com.google.gwt.lang;" + + "public class Exceptions { " + + " static boolean throwAssertionError() { throw new RuntimeException(); }" + + " static Throwable safeClose(AutoCloseable resource, Throwable mainException) {" + + " return mainException;" + + " } " + + "}"; + } }); sourceOracle.addOrReplace(new MockJavaResource("java.lang.String") {
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/Java7AstTest.java b/dev/core/test/com/google/gwt/dev/jjs/impl/Java7AstTest.java index 5e261ea..3ed0dbf 100644 --- a/dev/core/test/com/google/gwt/dev/jjs/impl/Java7AstTest.java +++ b/dev/core/test/com/google/gwt/dev/jjs/impl/Java7AstTest.java
@@ -32,10 +32,14 @@ */ public class Java7AstTest extends JJSTestBase { + // TODO(rluble): add similar tests to ensure that the AST construction is correct for all types + // of nodes. @Override public void setUp() { sourceLevel = SourceLevel.JAVA7; - addAll(Java7MockResources.LIST_T, Java7MockResources.ARRAYLIST_T); + addAll(Java7MockResources.LIST_T, Java7MockResources.ARRAYLIST_T, + JavaResourceBase.AUTOCLOSEABLE, Java7MockResources.TEST_RESOURCE, + Java7MockResources.EXCEPTION1, Java7MockResources.EXCEPTION2); } public void testCompileNewStyleLiterals() throws Exception { @@ -115,5 +119,4 @@ JMethodBody body = (JMethodBody) mainMethod.getBody(); return body.getBlock(); } - }
diff --git a/user/src/com/google/gwt/core/shared/SerializableThrowable.java b/user/src/com/google/gwt/core/shared/SerializableThrowable.java index 82ccaae..cbbd612 100644 --- a/user/src/com/google/gwt/core/shared/SerializableThrowable.java +++ b/user/src/com/google/gwt/core/shared/SerializableThrowable.java
@@ -22,6 +22,7 @@ * A serializable copy of a {@link Throwable}, including its causes and stack trace. It overrides * {@code #toString} to mimic original {@link Throwable#toString()} so that {@link #printStackTrace} * will work as if it is coming from the original exception. + * * <p> * This class is especially useful for logging and testing as the emulated Throwable class does not * serialize recursively and does not serialize the stack trace. This class, as an alternative, can @@ -30,6 +31,8 @@ * <p> * Please note that, to get more useful stack traces from client side, this class needs to be used * in conjunction with {@link com.google.gwt.core.server.StackTraceDeobfuscator}. + * <p> + * NOTE: Does not serialize suppressed exceptions to remain compatible with Java 6 and below. */ public final class SerializableThrowable extends Throwable {
diff --git a/user/super/com/google/gwt/emul/java/lang/AutoCloseable.java b/user/super/com/google/gwt/emul/java/lang/AutoCloseable.java new file mode 100644 index 0000000..7bf4144 --- /dev/null +++ b/user/super/com/google/gwt/emul/java/lang/AutoCloseable.java
@@ -0,0 +1,29 @@ +/* + * Copyright 2013 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package java.lang; + +/** + * See <a + * href="http://docs.oracle.com/javase/7/docs/api/java/lang/AutoCloseable.html">the + * official Java API doc</a> for details. + */ +public interface AutoCloseable { + + /** + * Closes this resource. + */ + void close() throws Exception; +}
diff --git a/user/super/com/google/gwt/emul/java/lang/Throwable.java b/user/super/com/google/gwt/emul/java/lang/Throwable.java index dd9ecb9..e929e59 100644 --- a/user/super/com/google/gwt/emul/java/lang/Throwable.java +++ b/user/super/com/google/gwt/emul/java/lang/Throwable.java
@@ -37,30 +37,73 @@ * to ensure that only the detailMessage field is serialized. Changing the * field modifiers below may necessitate a change to the server's * SerializabilityUtil.fieldQualifiesForSerialization(Field) method. + * + * TODO(rluble): Add remaining functionality for suppressed Exceptions (e.g. + * printing). Also review the class for missing Java 7 compatibility. */ private transient Throwable cause; private String detailMessage; + private transient Throwable[] suppressedExceptions; private transient StackTraceElement[] stackTrace; - - { - fillInStackTrace(); - } + private transient boolean disableSuppression; public Throwable() { + fillInStackTrace(); } public Throwable(String message) { this.detailMessage = message; + fillInStackTrace(); } public Throwable(String message, Throwable cause) { this.cause = cause; this.detailMessage = message; + fillInStackTrace(); } public Throwable(Throwable cause) { this.detailMessage = (cause == null) ? null : cause.toString(); this.cause = cause; + fillInStackTrace(); + } + + /** + * Constructor that allows subclasses disabling exception suppression and stack traces. + * Those features should only be disabled in very specific cases. + */ + protected Throwable(String message, Throwable cause, boolean enableSuppression, + boolean writetableStackTrace) { + if (writetableStackTrace) { + fillInStackTrace(); + } + this.cause = cause; + this.detailMessage = message; + this.disableSuppression = !enableSuppression; + } + + /** + * Call to add an exception that was suppressed. Used by try-with-resources. + */ + public final void addSuppressed(Throwable exception) { + if (exception == null) { + throw new NullPointerException("Cannot suppress a null exception."); + } + if (exception == this) { + throw new IllegalArgumentException("Exception can not suppress itself."); + } + + if (disableSuppression) { + return; + } + + if (suppressedExceptions == null) { + suppressedExceptions = new Throwable[] { exception }; + } else { + // TRICK: This is not correct Java (would give an OOBE, but it works in JS and + // this code will only be executed in JS. + suppressedExceptions[suppressedExceptions.length] = exception; + } } /** @@ -97,6 +140,17 @@ return stackTrace; } + /** + * Returns the array of Exception that this one suppressedExceptions. + */ + public final Throwable[] getSuppressed() { + if (suppressedExceptions == null) { + suppressedExceptions = new Throwable[0]; + } + + return suppressedExceptions; + } + public Throwable initCause(Throwable cause) { if (this.cause != null) { throw new IllegalStateException("Can't overwrite cause");
diff --git a/user/test-super/com/google/gwt/dev/jjs/super/com/google/gwt/dev/jjs/test/Java7Test.java b/user/test-super/com/google/gwt/dev/jjs/super/com/google/gwt/dev/jjs/test/Java7Test.java index f849634..26a32ac 100644 --- a/user/test-super/com/google/gwt/dev/jjs/super/com/google/gwt/dev/jjs/test/Java7Test.java +++ b/user/test-super/com/google/gwt/dev/jjs/super/com/google/gwt/dev/jjs/test/Java7Test.java
@@ -63,4 +63,245 @@ } assertEquals(1, result); } + + final List<String> log = new ArrayList<String>(); + + public class Resource implements AutoCloseable { + + String name; + public Resource(String name) { + this.name = name; + log.add("Open " + name); + } + + public void doSomething() { + log.add("doSomething " + name); + } + + public void throwException(String text) throws E1 { + throw new E1(text + " in " + name); + } + + public void close() throws Exception { + log.add("Close " + name); + } + } + + public class ResourceWithExceptionOnClose extends Resource { + + + public ResourceWithExceptionOnClose(String name) { + super(name); + } + + public void close() throws Exception { + throw new E1("Exception in close " + name); + } + } + + public void testResource() throws Exception{ + log.clear(); + try (Resource c = new Resource("A")) { + + c.doSomething(); + } + + assertContentsInOrder(log, + "Open A", + "doSomething A", + "Close A"); + } + + public void test3Resources() throws Exception{ + log.clear(); + try (Resource rA = new Resource("A"); + Resource rB = new Resource("B"); + Resource rC = new Resource("C")) { + + rA.doSomething(); + rB.doSomething(); + rC.doSomething(); + } + + assertContentsInOrder(log, + "Open A", + "Open B", + "Open C", + "doSomething A", + "doSomething B", + "doSomething C", + "Close C", + "Close B", + "Close A" + ); + } + + public void testResourcesWithExceptions() throws Exception{ + log.clear(); + try (Resource rA = new Resource("A"); + Resource rB = new ResourceWithExceptionOnClose("B"); + Resource rC = new Resource("C")) { + + rA.doSomething(); + rB.doSomething(); + rC.doSomething(); + } catch (Exception e) { + log.add(e.getMessage()); + } finally { + log.add("finally"); + } + + assertContentsInOrder(log, + "Open A", + "Open B", + "Open C", + "doSomething A", + "doSomething B", + "doSomething C", + "Close C", + "Close A", + "Exception in close B", + "finally" + ); + } + + public void testResourcesWithSuppressedExceptions() throws Exception{ + log.clear(); + try (Resource rA = new ResourceWithExceptionOnClose("A"); + Resource rB = new Resource("B"); + Resource rC = new ResourceWithExceptionOnClose("C")) { + + rA.doSomething(); + rB.doSomething(); + rC.doSomething(); + } catch (Exception e) { + log.add(e.getMessage()); + for (Throwable t : e.getSuppressed()) { + log.add("Suppressed: " + t.getMessage()); + } + } + + assertContentsInOrder(log, + "Open A", + "Open B", + "Open C", + "doSomething A", + "doSomething B", + "doSomething C", + "Close B", + "Exception in close C", + "Suppressed: Exception in close A" + ); + + log.clear(); + try (Resource rA = new Resource("A"); + Resource rB = new ResourceWithExceptionOnClose("B"); + Resource rC = new Resource("C")) { + + rA.doSomething(); + rB.throwException("E1 here"); + rC.doSomething(); + } catch (Exception e) { + log.add(e.getMessage()); + for (Throwable t : e.getSuppressed()) { + log.add("Suppressed: " + t.getMessage()); + } + } finally { + log.add("finally"); + } + + assertContentsInOrder(log, + "Open A", + "Open B", + "Open C", + "doSomething A", + "Close C", + "Close A", + "E1 here in B", + "Suppressed: Exception in close B", + "finally" + ); + } + + public void testAddSuppressedExceptions() { + Throwable throwable = new Throwable("primary"); + assertNotNull(throwable.getSuppressed()); + assertEquals(0, throwable.getSuppressed().length); + Throwable suppressed1 = new Throwable("suppressed1"); + throwable.addSuppressed(suppressed1); + assertEquals(1, throwable.getSuppressed().length); + assertEquals(suppressed1, throwable.getSuppressed()[0]); + Throwable suppressed2 = new Throwable("suppressed2"); + throwable.addSuppressed(suppressed2); + assertEquals(2, throwable.getSuppressed().length); + assertEquals(suppressed1, throwable.getSuppressed()[0]); + assertEquals(suppressed2, throwable.getSuppressed()[1]); + } + + private void assertContentsInOrder(Iterable<String> contents, String... elements ) { + int sz = elements.length; + Iterator<String> it = contents.iterator(); + for (int i = 0; i < sz; i++) { + assertTrue(it.hasNext()); + String expected = it.next(); + assertEquals(elements[i], expected); + } + assertFalse(it.hasNext()); + } + + public static class E1 extends Exception { + String name; + public E1(String name) { + this.name = name; + } + + public int methodE1() { + return 0; + } + + @Override + public String getMessage() { + return name; + } + } + + public static class E2 extends E1 { + public E2(String name) { + super(name); + } + + public int methodE2() { + return 1; + } + } + + public static class E3 extends E1 { + public E3(String name) { + super(name); + } + + public int methodE3() { + return 2; + } + } + + public void testMultiExceptions() { + + int choose = 0; + + try { + if (choose == 0) { + throw new E1("e1"); + } else if (choose ==1) { + throw new E2("e2"); + } + + fail("Exception was not trown"); + } catch (E2 | E3 x) { + // The compiler will assign x a common supertype/superinterface of E2 and E3. + // Here we make sure that this clause is not entered when the supertype is thrown. + fail("Caught E1 instead of E2|E3"); + } catch (E1 x) { + } + } }
diff --git a/user/test/com/google/gwt/dev/jjs/test/Java7Test.java b/user/test/com/google/gwt/dev/jjs/test/Java7Test.java index 2ef1c47..e463279 100644 --- a/user/test/com/google/gwt/dev/jjs/test/Java7Test.java +++ b/user/test/com/google/gwt/dev/jjs/test/Java7Test.java
@@ -49,4 +49,22 @@ public void testSwitchOnString() { } + + public void testResource() throws Exception { + } + + public void test3Resources() throws Exception { + } + + public void testResourcesWithExceptions() throws Exception { + } + + public void testResourcesWithSuppressedExceptions() throws Exception { + } + + public void testMultiExceptions() { + } + + public void testAddSuppressedExceptions() { + } }