1 /*
  2  * Copyright (c) 2017, 2018, 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.bad.class.truncated.at.offset: 0))",
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                 .options("-source", "10", "-target", "10")
125                 .files(tb.findJavaFiles(src))
126                 .run(Expect.SUCCESS)
127                 .writeAll()
128                 .getOutputLines(Task.OutputKind.DIRECT);
129 
130         List<Path> files;
131         try (Stream<Path> dir = Files.list(out)) {
132             files = dir.collect(Collectors.toList());
133         }
134 
135         List<List<Path>> result = new ArrayList<>();
136 
137         permutations(files, Collections.emptyList(), result);
138 
139         int testNum = 0;
140 
141         for (List<Path> order : result) {
142             for (Path missing : order) {
143                 Path test = base.resolve(String.valueOf(testNum++)).resolve("test");
144 
145                 tb.createDirectories(test);
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         DeferredCompletionFailureHandler dcfh = DeferredCompletionFailureHandler.instance(context);
194 
195         dcfh.setHandler(dcfh.javacCodeHandler);
196 
197         task.getElements().getTypeElement("java.lang.Object");
198 
199         if (!badClassFile) {
200             //to ensure the same paths taken in ClassFinder.completeEnclosing in case the file is missing:
201             syms.enterClass(syms.unnamedModule, names.fromString(flatName(missing)));
202         }
203 
204         List<String> result = new ArrayList<>();
205 
206         for (Path toCheck : order) {
207             ClassSymbol sym = syms.enterClass(syms.unnamedModule, names.fromString(flatName(toCheck)));
208 
209             try {
210                 sym.complete();
211             } catch (CompletionFailure ignore) {
212             }
213 
214             long flags = sym.flags_field;
215 
216             flags &= ~(Flags.CLASS_SEEN | Flags.SOURCE_SEEN | Flags.IDENTITY_TYPE); // earlier ACC_SUPER was dropped by javac.
217 
218             result.add("sym: " + sym.flatname + ", " + sym.owner.flatName() +
219                        ", " + sym.type + ", " + sym.members_field + ", " + flags);
220         }
221 
222         return result;
223     }
224 
225     private String flatName(Path p) {
226         return p.getFileName().toString().replace(".class", "");
227     }
228 
229     private static class TestClassReader extends ClassReader {
230         public static void preRegister(Context ctx) {
231             ctx.put(classReaderKey, (Factory<ClassReader>) c -> new TestClassReader(ctx));
232         }
233 
234         private final String block;
235 
236         public TestClassReader(Context context) {
237             super(context);
238             block = Options.instance(context).get("blockClass");
239         }
240 
241         @Override
242         public void readClassFile(ClassSymbol c) {
243             super.readClassFile(c);
244 
245             if (c.flatname.contentEquals(block)) {
246                 throw badClassFile("blocked");
247             }
248         }
249 
250     }
251 
252 }