7027157: Project Coin: javac warnings for AutoCloseable.close throwing InterruptedException
authormcimadamore
Tue, 29 Mar 2011 16:41:18 +0100
changeset 9076 45c73da050e9
parent 9075 cba34854a40e
child 9077 6ee59e40b258
7027157: Project Coin: javac warnings for AutoCloseable.close throwing InterruptedException Summary: javac should warn about use/declaration of AutoCloseable subclasses that can throw InterruptedException Reviewed-by: jjg
langtools/src/share/classes/com/sun/tools/javac/code/Symtab.java
langtools/src/share/classes/com/sun/tools/javac/comp/Attr.java
langtools/src/share/classes/com/sun/tools/javac/resources/compiler.properties
langtools/test/tools/javac/TryWithResources/InterruptedExceptionTest.java
langtools/test/tools/javac/diags/examples/TryResourceThrowsInterruptedExc.java
--- a/langtools/src/share/classes/com/sun/tools/javac/code/Symtab.java	Tue Mar 29 16:40:51 2011 +0100
+++ b/langtools/src/share/classes/com/sun/tools/javac/code/Symtab.java	Tue Mar 29 16:41:18 2011 +0100
@@ -131,6 +131,7 @@
     public final Type polymorphicSignatureType;
     public final Type throwableType;
     public final Type errorType;
+    public final Type interruptedExceptionType;
     public final Type illegalArgumentExceptionType;
     public final Type exceptionType;
     public final Type runtimeExceptionType;
@@ -441,6 +442,7 @@
         polymorphicSignatureType = enterClass("java.lang.invoke.MethodHandle$PolymorphicSignature");
         errorType = enterClass("java.lang.Error");
         illegalArgumentExceptionType = enterClass("java.lang.IllegalArgumentException");
+        interruptedExceptionType = enterClass("java.lang.InterruptedException");
         exceptionType = enterClass("java.lang.Exception");
         runtimeExceptionType = enterClass("java.lang.RuntimeException");
         classNotFoundExceptionType = enterClass("java.lang.ClassNotFoundException");
@@ -480,6 +482,7 @@
                              autoCloseableType.tsym);
         trustMeType = enterClass("java.lang.SafeVarargs");
 
+        synthesizeEmptyInterfaceIfMissing(autoCloseableType);
         synthesizeEmptyInterfaceIfMissing(cloneableType);
         synthesizeEmptyInterfaceIfMissing(serializableType);
         synthesizeEmptyInterfaceIfMissing(transientPolymorphicSignatureType); // transient - 292
--- a/langtools/src/share/classes/com/sun/tools/javac/comp/Attr.java	Tue Mar 29 16:40:51 2011 +0100
+++ b/langtools/src/share/classes/com/sun/tools/javac/comp/Attr.java	Tue Mar 29 16:41:18 2011 +0100
@@ -1089,6 +1089,10 @@
             if (resource.getTag() == JCTree.VARDEF) {
                 attribStat(resource, tryEnv);
                 chk.checkType(resource, resource.type, syms.autoCloseableType, "try.not.applicable.to.type");
+
+                //check that resource type cannot throw InterruptedException
+                checkAutoCloseable(resource.pos(), localEnv, resource.type);
+
                 VarSymbol var = (VarSymbol)TreeInfo.symbolFor(resource);
                 var.setData(ElementKind.RESOURCE_VARIABLE);
             } else {
@@ -1127,6 +1131,35 @@
         result = null;
     }
 
+    void checkAutoCloseable(DiagnosticPosition pos, Env<AttrContext> env, Type resource) {
+        if (!resource.isErroneous() &&
+                types.asSuper(resource, syms.autoCloseableType.tsym) != null) {
+            Symbol close = syms.noSymbol;
+            boolean prevDeferDiags = log.deferDiagnostics;
+            Queue<JCDiagnostic> prevDeferredDiags = log.deferredDiagnostics;
+            try {
+                log.deferDiagnostics = true;
+                log.deferredDiagnostics = ListBuffer.lb();
+                close = rs.resolveQualifiedMethod(pos,
+                        env,
+                        resource,
+                        names.close,
+                        List.<Type>nil(),
+                        List.<Type>nil());
+            }
+            finally {
+                log.deferDiagnostics = prevDeferDiags;
+                log.deferredDiagnostics = prevDeferredDiags;
+            }
+            if (close.kind == MTH &&
+                    close.overrides(syms.autoCloseableClose, resource.tsym, types, true) &&
+                    chk.isHandled(syms.interruptedExceptionType, types.memberType(resource, close).getThrownTypes()) &&
+                    env.info.lint.isEnabled(LintCategory.TRY)) {
+                log.warning(LintCategory.TRY, pos, "try.resource.throws.interrupted.exc", resource);
+            }
+        }
+    }
+
     public void visitConditional(JCConditional tree) {
         attribExpr(tree.cond, env, syms.booleanType);
         attribExpr(tree.truepart, env);
@@ -3169,6 +3202,9 @@
         // method conform to the method they implement.
         chk.checkImplementations(tree);
 
+        //check that a resource implementing AutoCloseable cannot throw InterruptedException
+        checkAutoCloseable(tree.pos(), env, c.type);
+
         for (List<JCTree> l = tree.defs; l.nonEmpty(); l = l.tail) {
             // Attribute declaration
             attribStat(l.head, env);
--- a/langtools/src/share/classes/com/sun/tools/javac/resources/compiler.properties	Tue Mar 29 16:40:51 2011 +0100
+++ b/langtools/src/share/classes/com/sun/tools/javac/resources/compiler.properties	Tue Mar 29 16:41:18 2011 +0100
@@ -1244,6 +1244,10 @@
 compiler.warn.try.resource.not.referenced=\
     auto-closeable resource {0} is never referenced in body of corresponding try statement
 
+# 0: type
+compiler.warn.try.resource.throws.interrupted.exc=\
+    auto-closeable resource {0} has a member method close() that could throw InterruptedException
+
 compiler.warn.unchecked.assign=\
     unchecked assignment: {0} to {1}
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/javac/TryWithResources/InterruptedExceptionTest.java	Tue Mar 29 16:41:18 2011 +0100
@@ -0,0 +1,234 @@
+/*
+ * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 7027157
+ * @summary Project Coin: javac warnings for AutoCloseable.close throwing InterruptedException
+ */
+
+import com.sun.source.util.JavacTask;
+import java.net.URI;
+import java.util.Arrays;
+import javax.tools.Diagnostic;
+import javax.tools.JavaCompiler;
+import javax.tools.JavaFileObject;
+import javax.tools.SimpleJavaFileObject;
+import javax.tools.StandardJavaFileManager;
+import javax.tools.ToolProvider;
+
+public class InterruptedExceptionTest {
+
+    enum XlintOption {
+        NONE("none"),
+        TRY("try");
+
+        String opt;
+
+        XlintOption(String opt) {
+            this.opt = opt;
+        }
+
+        String getXlintOption() {
+            return "-Xlint:" + opt;
+        }
+    }
+
+    enum SuppressLevel {
+        NONE,
+        SUPPRESS;
+
+        String getSuppressAnno() {
+            return this == SUPPRESS ?
+                "@SuppressWarnings(\"try\")" :
+                "";
+        }
+    }
+
+    enum ClassKind {
+        ABSTRACT_CLASS("abstract class", "implements", false),
+        CLASS("class", "implements", true),
+        INTERFACE("interface", "extends", false);
+
+        String kindName;
+        String extendsClause;
+        boolean hasBody;
+
+        private ClassKind(String kindName, String extendsClause, boolean hasBody) {
+            this.kindName = kindName;
+            this.extendsClause = extendsClause;
+            this.hasBody = hasBody;
+        }
+
+        String getBody() {
+            return hasBody ? "{}" : ";";
+        }
+    }
+
+    enum ExceptionKind {
+        NONE("", false),
+        EXCEPTION("Exception", true),
+        INTERRUPTED_EXCEPTION("InterruptedException", true),
+        ILLEGAL_ARGUMENT_EXCEPTION("IllegalArgumentException", false),
+        X("X", false);
+
+        String exName;
+        boolean shouldWarn;
+
+        private ExceptionKind(String exName, boolean shouldWarn) {
+            this.exName = exName;
+            this.shouldWarn = shouldWarn;
+        }
+
+        String getThrowsClause() {
+            return this == NONE ? "" : "throws " + exName;
+        }
+
+        String getTypeArguments(ExceptionKind decl) {
+            return (decl != X || this == NONE) ? "" : "<" + exName + ">";
+        }
+
+        String getTypeParameter() {
+            return this == X ? "<X extends Exception>" : "";
+        }
+    }
+
+    public static void main(String... args) throws Exception {
+
+        //create default shared JavaCompiler - reused across multiple compilations
+        JavaCompiler comp = ToolProvider.getSystemJavaCompiler();
+        StandardJavaFileManager fm = comp.getStandardFileManager(null, null, null);
+
+        for (XlintOption xlint : XlintOption.values()) {
+            for (SuppressLevel suppress_decl : SuppressLevel.values()) {
+                for (SuppressLevel suppress_use : SuppressLevel.values()) {
+                    for (ClassKind ck : ClassKind.values()) {
+                        for (ExceptionKind ek_decl : ExceptionKind.values()) {
+                            for (ExceptionKind ek_use : ExceptionKind.values()) {
+                                new InterruptedExceptionTest(xlint, suppress_decl,
+                                        suppress_use, ck, ek_decl, ek_use).run(comp, fm);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    XlintOption xlint;
+    SuppressLevel suppress_decl;
+    SuppressLevel suppress_use;
+    ClassKind ck;
+    ExceptionKind ek_decl;
+    ExceptionKind ek_use;
+    JavaSource source;
+    DiagnosticChecker diagChecker;
+
+    InterruptedExceptionTest(XlintOption xlint, SuppressLevel suppress_decl, SuppressLevel suppress_use,
+            ClassKind ck, ExceptionKind ek_decl, ExceptionKind ek_use) {
+        this.xlint = xlint;
+        this.suppress_decl = suppress_decl;
+        this.suppress_use = suppress_use;
+        this.ck = ck;
+        this.ek_decl = ek_decl;
+        this.ek_use = ek_use;
+        this.source = new JavaSource();
+        this.diagChecker = new DiagnosticChecker();
+    }
+
+    class JavaSource extends SimpleJavaFileObject {
+
+        String template = "#S1 #CK Resource#G #EC AutoCloseable {\n" +
+                              "public void close() #TK #BK\n" +
+                          "}\n" +
+                          "class Test {\n" +
+                              "#S2 <X> void test() {\n" +
+                                 "try (Resource#PK r = null) { }\n" +
+                              "}\n" +
+                          "}\n";
+
+        String source;
+
+        public JavaSource() {
+            super(URI.create("myfo:/Test.java"), JavaFileObject.Kind.SOURCE);
+            source = template.replace("#S1", suppress_decl.getSuppressAnno())
+                    .replace("#S2", suppress_use.getSuppressAnno())
+                    .replace("#CK", ck.kindName)
+                    .replace("#EC", ck.extendsClause)
+                    .replace("#G", ek_decl.getTypeParameter())
+                    .replace("#TK", ek_decl.getThrowsClause())
+                    .replace("#BK", ck.getBody())
+                    .replace("#PK", ek_use.getTypeArguments(ek_decl));
+        }
+
+        @Override
+        public CharSequence getCharContent(boolean ignoreEncodingErrors) {
+            return source;
+        }
+    }
+
+    void run(JavaCompiler tool, StandardJavaFileManager fm) throws Exception {
+        JavacTask ct = (JavacTask)tool.getTask(null, fm, diagChecker,
+                Arrays.asList(xlint.getXlintOption()), null, Arrays.asList(source));
+        ct.analyze();
+        check();
+    }
+
+    void check() {
+
+        boolean shouldWarnDecl = ek_decl.shouldWarn &&
+                xlint == XlintOption.TRY &&
+                suppress_decl != SuppressLevel.SUPPRESS;
+
+        boolean shouldWarnUse = (ek_decl.shouldWarn ||
+                ((ek_use.shouldWarn || ek_use == ExceptionKind.NONE) && ek_decl == ExceptionKind.X)) &&
+                xlint == XlintOption.TRY &&
+                suppress_use != SuppressLevel.SUPPRESS;
+
+        int foundWarnings = 0;
+
+        if (shouldWarnDecl) foundWarnings++;
+        if (shouldWarnUse) foundWarnings++;
+
+        if (foundWarnings != diagChecker.tryWarnFound) {
+            throw new Error("invalid diagnostics for source:\n" +
+                source.getCharContent(true) +
+                "\nOptions: " + xlint.getXlintOption() +
+                "\nFound warnings: " + diagChecker.tryWarnFound +
+                "\nExpected decl warning: " + shouldWarnDecl +
+                "\nExpected use warning: " + shouldWarnUse);
+        }
+    }
+
+    static class DiagnosticChecker implements javax.tools.DiagnosticListener<JavaFileObject> {
+
+        int tryWarnFound;
+
+        public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
+            if (diagnostic.getKind() == Diagnostic.Kind.WARNING &&
+                    diagnostic.getCode().contains("try.resource.throws.interrupted.exc")) {
+                tryWarnFound++;
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/javac/diags/examples/TryResourceThrowsInterruptedExc.java	Tue Mar 29 16:41:18 2011 +0100
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+// key: compiler.warn.try.resource.throws.interrupted.exc
+// options: -Xlint:try
+
+class TryResourceThrowsInterruptedException implements AutoCloseable {
+    public void close() throws InterruptedException {}
+}