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
41import java.nio.file.Files;
42import java.nio.file.Path;
43import java.nio.file.Paths;
44import java.util.ArrayList;
45import java.util.Arrays;
46import java.util.Collections;
47import java.util.List;
48import java.util.stream.Collectors;
49import java.util.stream.Stream;
50
51import com.sun.tools.javac.api.JavacTaskImpl;
52import com.sun.tools.javac.api.JavacTool;
53import com.sun.tools.javac.code.Flags;
54import com.sun.tools.javac.code.Symbol.ClassSymbol;
55import com.sun.tools.javac.code.Symbol.CompletionFailure;
56import com.sun.tools.javac.code.Symtab;
57import com.sun.tools.javac.jvm.ClassReader;
58import com.sun.tools.javac.util.Context;
59import com.sun.tools.javac.util.Context.Factory;
60import com.sun.tools.javac.util.Names;
61import com.sun.tools.javac.util.Options;
62import toolbox.Task;
63import toolbox.Task.Expect;
64
65import toolbox.TestRunner;
66import toolbox.ToolBox;
67
68public 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}
249