1 /*
   2  * Copyright (c) 2010, 2013, 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     6993978 7097436 8006694
  27  * @summary Project Coin: Annotation to reduce varargs warnings
  28  *  temporarily workaround combo tests are causing time out in several platforms
  29  * @author  mcimadamore
  30  * @library ../../lib
  31  * @build JavacTestingAbstractThreadedTest
  32  * @run main/othervm Warn5
  33  */
  34 
  35 // use /othervm to avoid jtreg timeout issues (CODETOOLS-7900047)
  36 // see JDK-8006746
  37 
  38 import java.net.URI;
  39 import java.util.Arrays;
  40 import java.util.EnumSet;
  41 import javax.tools.Diagnostic;
  42 import javax.tools.JavaCompiler;
  43 import javax.tools.JavaFileObject;
  44 import javax.tools.SimpleJavaFileObject;
  45 import javax.tools.ToolProvider;
  46 import com.sun.source.util.JavacTask;
  47 
  48 public class Warn5
  49     extends JavacTestingAbstractThreadedTest
  50     implements Runnable {
  51 
  52     enum XlintOption {
  53         NONE("none"),
  54         ALL("all");
  55 
  56         String opt;
  57 
  58         XlintOption(String opt) {
  59             this.opt = opt;
  60         }
  61 
  62         String getXlintOption() {
  63             return "-Xlint:" + opt;
  64         }
  65     }
  66 
  67     enum TrustMe {
  68         DONT_TRUST(""),
  69         TRUST("@java.lang.SafeVarargs");
  70 
  71         String anno;
  72 
  73         TrustMe(String anno) {
  74             this.anno = anno;
  75         }
  76     }
  77 
  78     enum SuppressLevel {
  79         NONE,
  80         VARARGS;
  81 
  82         String getSuppressAnno() {
  83             return this == VARARGS ?
  84                 "@SuppressWarnings(\"varargs\")" :
  85                 "";
  86         }
  87     }
  88 
  89     enum ModifierKind {
  90         NONE(""),
  91         FINAL("final"),
  92         STATIC("static"),
  93         PRIVATE("private");
  94 
  95         String mod;
  96 
  97         ModifierKind(String mod) {
  98             this.mod = mod;
  99         }
 100     }
 101 
 102     enum MethodKind {
 103         METHOD("void m"),
 104         CONSTRUCTOR("Test");
 105 
 106         String name;
 107 
 108         MethodKind(String name) {
 109             this.name = name;
 110         }
 111     }
 112 
 113     enum SourceLevel {
 114         JDK_6("6"),
 115         JDK_7("7"),
 116         JDK_9("9");
 117 
 118         String sourceKey;
 119 
 120         SourceLevel(String sourceKey) {
 121             this.sourceKey = sourceKey;
 122         }
 123     }
 124 
 125     enum SignatureKind {
 126         VARARGS_X("#K <X>#N(X... x)", false, true),
 127         VARARGS_STRING("#K #N(String... x)", true, true),
 128         ARRAY_X("#K <X>#N(X[] x)", false, false),
 129         ARRAY_STRING("#K #N(String[] x)", true, false);
 130 
 131         String stub;
 132         boolean isReifiableArg;
 133         boolean isVarargs;
 134 
 135         SignatureKind(String stub, boolean isReifiableArg, boolean isVarargs) {
 136             this.stub = stub;
 137             this.isReifiableArg = isReifiableArg;
 138             this.isVarargs = isVarargs;
 139         }
 140 
 141         String getSignature(ModifierKind modKind, MethodKind methKind) {
 142             return methKind != MethodKind.CONSTRUCTOR ?
 143                 stub.replace("#K", modKind.mod).replace("#N", methKind.name) :
 144                 stub.replace("#K", "").replace("#N", methKind.name);
 145         }
 146     }
 147 
 148     enum BodyKind {
 149         ASSIGN("Object o = x;", true),
 150         CAST("Object o = (Object)x;", true),
 151         METH("test(x);", true),
 152         PRINT("System.out.println(x.toString());", false),
 153         ARRAY_ASSIGN("Object[] o = x;", true),
 154         ARRAY_CAST("Object[] o = (Object[])x;", true),
 155         ARRAY_METH("testArr(x);", true);
 156 
 157         String body;
 158         boolean hasAliasing;
 159 
 160         BodyKind(String body, boolean hasAliasing) {
 161             this.body = body;
 162             this.hasAliasing = hasAliasing;
 163         }
 164     }
 165 
 166     enum WarningKind {
 167         UNSAFE_BODY,
 168         UNSAFE_DECL,
 169         MALFORMED_SAFEVARARGS,
 170         REDUNDANT_SAFEVARARGS;
 171     }
 172 
 173     public static void main(String... args) throws Exception {
 174         for (SourceLevel sourceLevel : SourceLevel.values()) {
 175             for (XlintOption xlint : XlintOption.values()) {
 176                 for (TrustMe trustMe : TrustMe.values()) {
 177                     for (SuppressLevel suppressLevel : SuppressLevel.values()) {
 178                         for (ModifierKind modKind : ModifierKind.values()) {
 179                             for (MethodKind methKind : MethodKind.values()) {
 180                                 for (SignatureKind sig : SignatureKind.values()) {
 181                                     for (BodyKind body : BodyKind.values()) {
 182                                         pool.execute(new Warn5(sourceLevel,
 183                                                 xlint, trustMe, suppressLevel,
 184                                                 modKind, methKind, sig, body));
 185                                     }
 186                                 }
 187                             }
 188                         }
 189                     }
 190                 }
 191             }
 192         }
 193 
 194         checkAfterExec(false);
 195     }
 196 
 197     final SourceLevel sourceLevel;
 198     final XlintOption xlint;
 199     final TrustMe trustMe;
 200     final SuppressLevel suppressLevel;
 201     final ModifierKind modKind;
 202     final MethodKind methKind;
 203     final SignatureKind sig;
 204     final BodyKind body;
 205     final JavaSource source;
 206     final DiagnosticChecker dc;
 207 
 208     public Warn5(SourceLevel sourceLevel, XlintOption xlint, TrustMe trustMe,
 209             SuppressLevel suppressLevel, ModifierKind modKind,
 210             MethodKind methKind, SignatureKind sig, BodyKind body) {
 211         this.sourceLevel = sourceLevel;
 212         this.xlint = xlint;
 213         this.trustMe = trustMe;
 214         this.suppressLevel = suppressLevel;
 215         this.modKind = modKind;
 216         this.methKind = methKind;
 217         this.sig = sig;
 218         this.body = body;
 219         this.source = new JavaSource();
 220         this.dc = new DiagnosticChecker();
 221     }
 222 
 223     @Override
 224     public void run() {
 225         final JavaCompiler tool = ToolProvider.getSystemJavaCompiler();
 226         JavacTask ct = (JavacTask)tool.getTask(null, fm.get(), dc,
 227                 Arrays.asList(xlint.getXlintOption(),
 228                     "-source", sourceLevel.sourceKey),
 229                 null, Arrays.asList(source));
 230         try {
 231             ct.analyze();
 232         } catch (Throwable t) {
 233             processException(t);
 234         }
 235         check();
 236     }
 237 
 238     void check() {
 239 
 240         EnumSet<WarningKind> expectedWarnings =
 241                 EnumSet.noneOf(WarningKind.class);
 242 
 243         if (sourceLevel.compareTo(SourceLevel.JDK_7) >= 0 &&
 244                 trustMe == TrustMe.TRUST &&
 245                 suppressLevel != SuppressLevel.VARARGS &&
 246                 xlint != XlintOption.NONE &&
 247                 sig.isVarargs &&
 248                 !sig.isReifiableArg &&
 249                 body.hasAliasing &&
 250                 (methKind == MethodKind.CONSTRUCTOR ||
 251                 (methKind == MethodKind.METHOD &&
 252                  modKind == ModifierKind.FINAL || modKind == ModifierKind.STATIC ||
 253                  (modKind == ModifierKind.PRIVATE && sourceLevel.compareTo(SourceLevel.JDK_9) >= 0)))) {
 254             expectedWarnings.add(WarningKind.UNSAFE_BODY);
 255         }
 256 
 257         if (sourceLevel.compareTo(SourceLevel.JDK_7) >= 0 &&
 258                 trustMe == TrustMe.DONT_TRUST &&
 259                 sig.isVarargs &&
 260                 !sig.isReifiableArg &&
 261                 xlint == XlintOption.ALL) {
 262             expectedWarnings.add(WarningKind.UNSAFE_DECL);
 263         }
 264 
 265         if (sourceLevel.compareTo(SourceLevel.JDK_7) >= 0 &&
 266                 trustMe == TrustMe.TRUST &&
 267                 (!sig.isVarargs ||
 268                  ((modKind == ModifierKind.NONE ||
 269                  modKind == ModifierKind.PRIVATE && sourceLevel.compareTo(SourceLevel.JDK_9) < 0 ) &&
 270                  methKind == MethodKind.METHOD))) {
 271             expectedWarnings.add(WarningKind.MALFORMED_SAFEVARARGS);
 272         }
 273 
 274         if (sourceLevel.compareTo(SourceLevel.JDK_7) >= 0 &&
 275                 trustMe == TrustMe.TRUST &&
 276                 xlint != XlintOption.NONE &&
 277                 suppressLevel != SuppressLevel.VARARGS &&
 278                 (modKind == ModifierKind.FINAL || modKind == ModifierKind.STATIC ||
 279                  (modKind == ModifierKind.PRIVATE && sourceLevel.compareTo(SourceLevel.JDK_9) >= 0) ||
 280                  methKind == MethodKind.CONSTRUCTOR) &&
 281                 sig.isVarargs &&
 282                 sig.isReifiableArg) {
 283             expectedWarnings.add(WarningKind.REDUNDANT_SAFEVARARGS);
 284         }
 285 
 286         if (!expectedWarnings.containsAll(dc.warnings) ||
 287                 !dc.warnings.containsAll(expectedWarnings)) {
 288             throw new Error("invalid diagnostics for source:\n" +
 289                     source.getCharContent(true) +
 290                     "\nOptions: " + xlint.getXlintOption() +
 291                     "\nSource Level: " + sourceLevel +
 292                     "\nExpected warnings: " + expectedWarnings +
 293                     "\nFound warnings: " + dc.warnings);
 294         }
 295     }
 296 
 297     class JavaSource extends SimpleJavaFileObject {
 298 
 299         String template = "import com.sun.tools.javac.api.*;\n" +
 300                           "import java.util.List;\n" +
 301                           "class Test {\n" +
 302                           "   static void test(Object o) {}\n" +
 303                           "   static void testArr(Object[] o) {}\n" +
 304                           "   #T \n #S #M { #B }\n" +
 305                           "}\n";
 306 
 307         String source;
 308 
 309         public JavaSource() {
 310             super(URI.create("myfo:/Test.java"), JavaFileObject.Kind.SOURCE);
 311             source = template.replace("#T", trustMe.anno).
 312                     replace("#S", suppressLevel.getSuppressAnno()).
 313                     replace("#M", sig.getSignature(modKind, methKind)).
 314                     replace("#B", body.body);
 315         }
 316 
 317         @Override
 318         public CharSequence getCharContent(boolean ignoreEncodingErrors) {
 319             return source;
 320         }
 321     }
 322 
 323     class DiagnosticChecker
 324         implements javax.tools.DiagnosticListener<JavaFileObject> {
 325 
 326         EnumSet<WarningKind> warnings = EnumSet.noneOf(WarningKind.class);
 327 
 328         public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
 329             if (diagnostic.getKind() == Diagnostic.Kind.WARNING) {
 330                     if (diagnostic.getCode().
 331                             contains("unsafe.use.varargs.param")) {
 332                         setWarning(WarningKind.UNSAFE_BODY);
 333                     } else if (diagnostic.getCode().
 334                             contains("redundant.trustme")) {
 335                         setWarning(WarningKind.REDUNDANT_SAFEVARARGS);
 336                     }
 337             } else if (diagnostic.getKind() == Diagnostic.Kind.MANDATORY_WARNING &&
 338                     diagnostic.getCode().
 339                         contains("varargs.non.reifiable.type")) {
 340                 setWarning(WarningKind.UNSAFE_DECL);
 341             } else if (diagnostic.getKind() == Diagnostic.Kind.ERROR &&
 342                     diagnostic.getCode().contains("invalid.trustme")) {
 343                 setWarning(WarningKind.MALFORMED_SAFEVARARGS);
 344             }
 345         }
 346 
 347         void setWarning(WarningKind wk) {
 348             if (!warnings.add(wk)) {
 349                 throw new AssertionError("Duplicate warning of kind " +
 350                         wk + " in source:\n" + source);
 351             }
 352         }
 353 
 354         boolean hasWarning(WarningKind wk) {
 355             return warnings.contains(wk);
 356         }
 357     }
 358 
 359 }