1 /*
   2  * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 /**
  25  * @test
  26  * @bug     6945418 6993978
  27  * @summary Project Coin: Simplified Varargs Method Invocation
  28  * @author  mcimadamore
  29  * @run main Warn4
  30  */
  31 import com.sun.source.util.JavacTask;
  32 import com.sun.tools.javac.api.JavacTool;
  33 import java.net.URI;
  34 import java.util.Arrays;
  35 import java.util.Set;
  36 import java.util.HashSet;
  37 import javax.tools.Diagnostic;
  38 import javax.tools.JavaCompiler;
  39 import javax.tools.JavaFileObject;
  40 import javax.tools.SimpleJavaFileObject;
  41 import javax.tools.StandardJavaFileManager;
  42 import javax.tools.ToolProvider;
  43 
  44 public class Warn4 {
  45 
  46     final static Warning[] error = null;
  47     final static Warning[] none = new Warning[] {};
  48     final static Warning[] vararg = new Warning[] { Warning.VARARGS };
  49     final static Warning[] unchecked = new Warning[] { Warning.UNCHECKED };
  50     final static Warning[] both = new Warning[] { Warning.VARARGS, Warning.UNCHECKED };
  51 
  52     enum Warning {
  53         UNCHECKED("generic.array.creation"),
  54         VARARGS("varargs.non.reifiable.type");
  55 
  56         String key;
  57 
  58         Warning(String key) {
  59             this.key = key;
  60         }
  61 
  62         boolean isSuppressed(TrustMe trustMe, SourceLevel source, SuppressLevel suppressLevelClient,
  63                 SuppressLevel suppressLevelDecl, ModifierKind modKind) {
  64             switch(this) {
  65                 case VARARGS:
  66                     return source == SourceLevel.JDK_6 ||
  67                             suppressLevelDecl == SuppressLevel.UNCHECKED ||
  68                             trustMe == TrustMe.TRUST;
  69                 case UNCHECKED:
  70                     return suppressLevelClient == SuppressLevel.UNCHECKED ||
  71                         (trustMe == TrustMe.TRUST && modKind != ModifierKind.NONE && source == SourceLevel.JDK_7);
  72             }
  73 
  74             SuppressLevel supLev = this == VARARGS ?
  75                 suppressLevelDecl :
  76                 suppressLevelClient;
  77             return supLev == SuppressLevel.UNCHECKED ||
  78                     (trustMe == TrustMe.TRUST && modKind != ModifierKind.NONE);
  79         }
  80     }
  81 
  82     enum SourceLevel {
  83         JDK_6("6"),
  84         JDK_7("7");
  85 
  86         String sourceKey;
  87 
  88         SourceLevel(String sourceKey) {
  89             this.sourceKey = sourceKey;
  90         }
  91     }
  92 
  93     enum TrustMe {
  94         DONT_TRUST(""),
  95         TRUST("@java.lang.SafeVarargs");
  96 
  97         String anno;
  98 
  99         TrustMe(String anno) {
 100             this.anno = anno;
 101         }
 102     }
 103 
 104     enum ModifierKind {
 105         NONE(" "),
 106         FINAL("final "),
 107         STATIC("static ");
 108 
 109         String mod;
 110 
 111         ModifierKind(String mod) {
 112             this.mod = mod;
 113         }
 114     }
 115 
 116     enum SuppressLevel {
 117         NONE(""),
 118         UNCHECKED("unchecked");
 119 
 120         String lint;
 121 
 122         SuppressLevel(String lint) {
 123             this.lint = lint;
 124         }
 125 
 126         String getSuppressAnno() {
 127             return "@SuppressWarnings(\"" + lint + "\")";
 128         }
 129     }
 130 
 131     enum Signature {
 132         UNBOUND("void #name(List<?>#arity arg) { #body }",
 133             new Warning[][] {none, none, none, none, error}),
 134         INVARIANT_TVAR("<Z> void #name(List<Z>#arity arg) { #body }",
 135             new Warning[][] {both, both, error, both, error}),
 136         TVAR("<Z> void #name(Z#arity arg) { #body }",
 137             new Warning[][] {both, both, both, both, vararg}),
 138         INVARIANT("void #name(List<String>#arity arg) { #body }",
 139             new Warning[][] {error, error, error, both, error}),
 140         UNPARAMETERIZED("void #name(String#arity arg) { #body }",
 141             new Warning[][] {error, error, error, error, none});
 142 
 143         String template;
 144         Warning[][] warnings;
 145 
 146         Signature(String template, Warning[][] warnings) {
 147             this.template = template;
 148             this.warnings = warnings;
 149         }
 150 
 151         boolean isApplicableTo(Signature other) {
 152             return warnings[other.ordinal()] != null;
 153         }
 154 
 155         boolean giveUnchecked(Signature other) {
 156             return warnings[other.ordinal()] == unchecked ||
 157                     warnings[other.ordinal()] == both;
 158         }
 159 
 160         boolean giveVarargs(Signature other) {
 161             return warnings[other.ordinal()] == vararg ||
 162                     warnings[other.ordinal()] == both;
 163         }
 164     }
 165 
 166     public static void main(String... args) throws Exception {
 167         for (SourceLevel sourceLevel : SourceLevel.values()) {
 168             for (TrustMe trustMe : TrustMe.values()) {
 169                 for (SuppressLevel suppressLevelClient : SuppressLevel.values()) {
 170                     for (SuppressLevel suppressLevelDecl : SuppressLevel.values()) {
 171                         for (ModifierKind modKind : ModifierKind.values()) {
 172                             for (Signature vararg_meth : Signature.values()) {
 173                                 for (Signature client_meth : Signature.values()) {
 174                                     if (vararg_meth.isApplicableTo(client_meth)) {
 175                                         test(sourceLevel,
 176                                                 trustMe,
 177                                                 suppressLevelClient,
 178                                                 suppressLevelDecl,
 179                                                 modKind,
 180                                                 vararg_meth,
 181                                                 client_meth);
 182                                     }
 183                                 }
 184                             }
 185                         }
 186                     }
 187                 }
 188             }
 189         }
 190     }
 191 
 192     // Create a single file manager and reuse it for each compile to save time.
 193     static StandardJavaFileManager fm = JavacTool.create().getStandardFileManager(null, null, null);
 194 
 195     static void test(SourceLevel sourceLevel, TrustMe trustMe, SuppressLevel suppressLevelClient,
 196             SuppressLevel suppressLevelDecl, ModifierKind modKind, Signature vararg_meth, Signature client_meth) throws Exception {
 197         final JavaCompiler tool = ToolProvider.getSystemJavaCompiler();
 198         JavaSource source = new JavaSource(trustMe, suppressLevelClient, suppressLevelDecl, modKind, vararg_meth, client_meth);
 199         DiagnosticChecker dc = new DiagnosticChecker();
 200         JavacTask ct = (JavacTask)tool.getTask(null, fm, dc,
 201                 Arrays.asList("-Xlint:unchecked", "-source", sourceLevel.sourceKey),
 202                 null, Arrays.asList(source));
 203         ct.generate(); //to get mandatory notes
 204         check(dc.warnings, sourceLevel,
 205                 new boolean[] {vararg_meth.giveUnchecked(client_meth),
 206                                vararg_meth.giveVarargs(client_meth)},
 207                 source, trustMe, suppressLevelClient, suppressLevelDecl, modKind);
 208     }
 209 
 210     static void check(Set<Warning> warnings, SourceLevel sourceLevel, boolean[] warnArr, JavaSource source,
 211             TrustMe trustMe, SuppressLevel suppressLevelClient, SuppressLevel suppressLevelDecl, ModifierKind modKind) {
 212         boolean badOutput = false;
 213         for (Warning wkind : Warning.values()) {
 214             boolean isSuppressed = wkind.isSuppressed(trustMe, sourceLevel,
 215                     suppressLevelClient, suppressLevelDecl, modKind);
 216             System.out.println("SUPPRESSED = " + isSuppressed);
 217             badOutput |= (warnArr[wkind.ordinal()] && !isSuppressed) != warnings.contains(wkind);
 218         }
 219         if (badOutput) {
 220             throw new Error("invalid diagnostics for source:\n" +
 221                     source.getCharContent(true) +
 222                     "\nExpected unchecked warning: " + warnArr[0] +
 223                     "\nExpected unsafe vararg warning: " + warnArr[1] +
 224                     "\nWarnings: " + warnings +
 225                     "\nSource level: " + sourceLevel);
 226         }
 227     }
 228 
 229     static class JavaSource extends SimpleJavaFileObject {
 230 
 231         String source;
 232 
 233         public JavaSource(TrustMe trustMe, SuppressLevel suppressLevelClient, SuppressLevel suppressLevelDecl,
 234                 ModifierKind modKind, Signature vararg_meth, Signature client_meth) {
 235             super(URI.create("myfo:/Test.java"), JavaFileObject.Kind.SOURCE);
 236             String meth1 = vararg_meth.template.replace("#arity", "...");
 237             meth1 = meth1.replace("#name", "m");
 238             meth1 = meth1.replace("#body", "");
 239             meth1 = trustMe.anno + "\n" + suppressLevelDecl.getSuppressAnno() + modKind.mod + meth1;
 240             String meth2 = client_meth.template.replace("#arity", "");
 241             meth2 = meth2.replace("#name", "test");
 242             meth2 = meth2.replace("#body", "m(arg);");
 243             meth2 = suppressLevelClient.getSuppressAnno() + meth2;
 244             source = "import java.util.List;\n" +
 245                      "class Test {\n" + meth1 +
 246                      "\n" + meth2 + "\n}\n";
 247         }
 248 
 249         @Override
 250         public CharSequence getCharContent(boolean ignoreEncodingErrors) {
 251             return source;
 252         }
 253     }
 254 
 255     static class DiagnosticChecker implements javax.tools.DiagnosticListener<JavaFileObject> {
 256 
 257         Set<Warning> warnings = new HashSet<>();
 258 
 259         public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
 260             if (diagnostic.getKind() == Diagnostic.Kind.MANDATORY_WARNING ||
 261                     diagnostic.getKind() == Diagnostic.Kind.WARNING) {
 262                 if (diagnostic.getCode().contains(Warning.VARARGS.key)) {
 263                     warnings.add(Warning.VARARGS);
 264                 } else if(diagnostic.getCode().contains(Warning.UNCHECKED.key)) {
 265                     warnings.add(Warning.UNCHECKED);
 266                 }
 267             }
 268         }
 269     }
 270 }