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