1 /*
   2  * Copyright (c) 2017, 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 8182450
  27  * @summary Bad classfiles should not abort compilations
  28  * @library /tools/lib
  29  * @modules
  30  *      jdk.compiler/com.sun.tools.javac.api
  31  *      jdk.compiler/com.sun.tools.javac.code
  32  *      jdk.compiler/com.sun.tools.javac.comp
  33  *      jdk.compiler/com.sun.tools.javac.jvm
  34  *      jdk.compiler/com.sun.tools.javac.main
  35  *      jdk.compiler/com.sun.tools.javac.processing
  36  *      jdk.compiler/com.sun.tools.javac.util
  37  * @build toolbox.ToolBox toolbox.JavacTask
  38  * @run main NoAbortForBadClassFile
  39  */
  40 
  41 import java.nio.file.Files;
  42 import java.nio.file.Path;
  43 import java.nio.file.Paths;
  44 import java.util.ArrayList;
  45 import java.util.Arrays;
  46 import java.util.Collections;
  47 import java.util.List;
  48 import java.util.stream.Collectors;
  49 import java.util.stream.Stream;
  50 
  51 import com.sun.tools.javac.api.JavacTaskImpl;
  52 import com.sun.tools.javac.api.JavacTool;
  53 import com.sun.tools.javac.code.Flags;
  54 import com.sun.tools.javac.code.Symbol.ClassSymbol;
  55 import com.sun.tools.javac.code.Symbol.CompletionFailure;
  56 import com.sun.tools.javac.code.Symtab;
  57 import com.sun.tools.javac.jvm.ClassReader;
  58 import com.sun.tools.javac.util.Context;
  59 import com.sun.tools.javac.util.Context.Factory;
  60 import com.sun.tools.javac.util.Names;
  61 import com.sun.tools.javac.util.Options;
  62 import toolbox.Task;
  63 import toolbox.Task.Expect;
  64 
  65 import toolbox.TestRunner;
  66 import toolbox.ToolBox;
  67 
  68 public class NoAbortForBadClassFile extends TestRunner {
  69 
  70     private ToolBox tb = new ToolBox();
  71 
  72     public NoAbortForBadClassFile() {
  73         super(System.out);
  74     }
  75 
  76     public static void main(String... args) throws Exception {
  77         new NoAbortForBadClassFile().runTests(m -> new Object[] { Paths.get(m.getName()) });
  78     }
  79 
  80     @Test
  81     public void testBrokenClassFile(Path base) throws Exception {
  82         Path classes = base.resolve("classes");
  83         Path brokenClassFile = classes.resolve("test").resolve("Broken.class");
  84 
  85         Files.createDirectories(brokenClassFile.getParent());
  86         Files.newOutputStream(brokenClassFile).close();
  87 
  88         Path src = base.resolve("src");
  89         tb.writeJavaFiles(src,
  90                           "package test; public class Test { private void test() { Broken b; String.unknown(); } }");
  91         Path out = base.resolve("out");
  92         tb.createDirectories(out);
  93 
  94         List<String> log = new toolbox.JavacTask(tb)
  95                 .options("-classpath", classes.toString(),
  96                          "-XDrawDiagnostics")
  97                 .outdir(out)
  98                 .files(tb.findJavaFiles(src))
  99                 .run(Expect.FAIL)
 100                 .writeAll()
 101                 .getOutputLines(Task.OutputKind.DIRECT);
 102 
 103         List<String> expectedOut = Arrays.asList(
 104                 "Test.java:1:57: compiler.err.cant.access: test.Broken, (compiler.misc.bad.class.file.header: Broken.class, (compiler.misc.class.file.wrong.class: java.lang.AutoCloseable))",
 105                  "Test.java:1:73: compiler.err.cant.resolve.location.args: kindname.method, unknown, , , (compiler.misc.location: kindname.class, java.lang.String, null)",
 106                  "2 errors"
 107         );
 108 
 109         if (!expectedOut.equals(log))
 110             throw new Exception("expected output not found: " + log);
 111     }
 112 
 113     @Test
 114     public void testLoading(Path base) throws Exception {
 115         Path src = base.resolve("src");
 116         tb.writeJavaFiles(src,
 117                           "public class Test { static { new Object() {}; } public static class I { public static class II { } } }");
 118         Path out = base.resolve("out");
 119         tb.createDirectories(out);
 120 
 121         new toolbox.JavacTask(tb)
 122                 .outdir(out)
 123                 .files(tb.findJavaFiles(src))
 124                 .run(Expect.SUCCESS)
 125                 .writeAll()
 126                 .getOutputLines(Task.OutputKind.DIRECT);
 127 
 128         List<Path> files;
 129         try (Stream<Path> dir = Files.list(out)) {
 130             files = dir.collect(Collectors.toList());
 131         }
 132 
 133         List<List<Path>> result = new ArrayList<>();
 134 
 135         permutations(files, Collections.emptyList(), result);
 136 
 137         for (List<Path> order : result) {
 138             for (Path missing : order) {
 139                 Path test = base.resolve("test");
 140 
 141                 if (Files.exists(test)) {
 142                     tb.cleanDirectory(test);
 143                 } else {
 144                     tb.createDirectories(test);
 145                 }
 146 
 147                 for (Path p : order) {
 148                     Files.copy(p, test.resolve(p.getFileName()));
 149                 }
 150 
 151                 List<String> actual = complete(test, order, missing, true);
 152 
 153                 Files.delete(test.resolve(missing.getFileName()));
 154 
 155                 List<String> expected = complete(test, order, missing, false);
 156 
 157                 if (!actual.equals(expected)) {
 158                     throw new AssertionError("Unexpected state, actual=\n" + actual + "\nexpected=\n" + expected + "\norder=" + order + "\nmissing=" + missing);
 159                 }
 160             }
 161         }
 162     }
 163 
 164     private static void permutations(List<Path> todo, List<Path> currentList, List<List<Path>> result) {
 165         if (todo.isEmpty()) {
 166             result.add(currentList);
 167             return ;
 168         }
 169 
 170         for (Path p : todo) {
 171             List<Path> nextTODO = new ArrayList<>(todo);
 172 
 173             nextTODO.remove(p);
 174 
 175             List<Path> nextList = new ArrayList<>(currentList);
 176 
 177             nextList.add(p);
 178 
 179             permutations(nextTODO, nextList, result);
 180         }
 181     }
 182 
 183     private List<String> complete(Path test, List<Path> order, Path missing, boolean badClassFile) {
 184         Context context = new Context();
 185         if (badClassFile) {
 186             TestClassReader.preRegister(context);
 187         }
 188         JavacTool tool = JavacTool.create();
 189         JavacTaskImpl task = (JavacTaskImpl) tool.getTask(null, null, null, List.of("-classpath", test.toString(), "-XDblockClass=" + flatName(missing)), null, null, context);
 190         Symtab syms = Symtab.instance(context);
 191         Names names = Names.instance(context);
 192 
 193         task.getElements().getTypeElement("java.lang.Object");
 194 
 195         if (!badClassFile) {
 196             //to ensure the same paths taken in ClassFinder.completeEnclosing in case the file is missing:
 197             syms.enterClass(syms.unnamedModule, names.fromString(flatName(missing)));
 198         }
 199 
 200         List<String> result = new ArrayList<>();
 201 
 202         for (Path toCheck : order) {
 203             ClassSymbol sym = syms.enterClass(syms.unnamedModule, names.fromString(flatName(toCheck)));
 204 
 205             try {
 206                 sym.complete();
 207             } catch (CompletionFailure ignore) {
 208             }
 209 
 210             long flags = sym.flags_field;
 211 
 212             flags &= ~(Flags.CLASS_SEEN | Flags.SOURCE_SEEN);
 213 
 214             result.add("sym: " + sym.flatname + ", " + sym.owner.flatName() +
 215                        ", " + sym.type + ", " + sym.members_field + ", " + flags);
 216         }
 217 
 218         return result;
 219     }
 220 
 221     private String flatName(Path p) {
 222         return p.getFileName().toString().replace(".class", "");
 223     }
 224 
 225     private static class TestClassReader extends ClassReader {
 226         public static void preRegister(Context ctx) {
 227             ctx.put(classReaderKey, (Factory<ClassReader>) c -> new TestClassReader(ctx));
 228         }
 229 
 230         private final String block;
 231 
 232         public TestClassReader(Context context) {
 233             super(context);
 234             block = Options.instance(context).get("blockClass");
 235         }
 236 
 237         @Override
 238         public void readClassFile(ClassSymbol c) {
 239             super.readClassFile(c);
 240 
 241             if (c.flatname.contentEquals(block)) {
 242                 throw badClassFile("blocked");
 243             }
 244         }
 245 
 246     }
 247 
 248 }