ElementStructureTest.java revision 3294:9adfb22ff08f
1/*
2 * Copyright (c) 2015, 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 8072480
27 * @summary Check the platform classpath contains the correct elements.
28 * @library /tools/lib
29 * @build ToolBox ElementStructureTest
30 * @modules jdk.compiler/com.sun.tools.javac.code
31 *          jdk.compiler/com.sun.tools.javac.api
32 *          jdk.compiler/com.sun.tools.javac.main
33 *          jdk.compiler/com.sun.tools.javac.platform
34 *          jdk.compiler/com.sun.tools.javac.util
35 *          jdk.jdeps/com.sun.tools.classfile
36 *          jdk.jdeps/com.sun.tools.javap
37 * @run main ElementStructureTest
38 */
39
40import java.io.BufferedReader;
41import java.io.ByteArrayInputStream;
42import java.io.ByteArrayOutputStream;
43import java.io.File;
44import java.io.IOException;
45import java.io.InputStream;
46import java.io.OutputStream;
47import java.io.OutputStreamWriter;
48import java.io.Reader;
49import java.io.Writer;
50import java.net.URI;
51import java.net.URL;
52import java.net.URLClassLoader;
53import java.nio.file.Files;
54import java.nio.file.Path;
55import java.nio.file.Paths;
56import java.security.MessageDigest;
57import java.util.ArrayList;
58import java.util.Arrays;
59import java.util.Collections;
60import java.util.EnumSet;
61import java.util.HashMap;
62import java.util.Iterator;
63import java.util.List;
64import java.util.Map;
65import java.util.Map.Entry;
66import java.util.ServiceLoader;
67import java.util.Set;
68import java.util.TreeMap;
69import java.util.TreeSet;
70import java.util.function.Predicate;
71import java.util.regex.Pattern;
72import java.util.stream.Stream;
73
74import javax.lang.model.element.AnnotationMirror;
75import javax.lang.model.element.AnnotationValue;
76import javax.lang.model.element.Element;
77import javax.lang.model.element.ElementVisitor;
78import javax.lang.model.element.ExecutableElement;
79import javax.lang.model.element.Modifier;
80import javax.lang.model.element.ModuleElement;
81import javax.lang.model.element.NestingKind;
82import javax.lang.model.element.PackageElement;
83import javax.lang.model.element.TypeElement;
84import javax.lang.model.element.TypeParameterElement;
85import javax.lang.model.element.VariableElement;
86import javax.lang.model.type.TypeMirror;
87import javax.tools.FileObject;
88import javax.tools.JavaCompiler;
89import javax.tools.JavaFileManager;
90import javax.tools.JavaFileObject;
91import javax.tools.JavaFileObject.Kind;
92import javax.tools.StandardLocation;
93import javax.tools.ToolProvider;
94
95import com.sun.source.util.JavacTask;
96import com.sun.tools.classfile.ClassFile;
97import com.sun.tools.classfile.ConstantPoolException;
98import com.sun.tools.javac.api.JavacTaskImpl;
99import com.sun.tools.javac.code.Symbol.CompletionFailure;
100import com.sun.tools.javac.platform.PlatformProvider;
101
102/**To generate the hash values for version N, invoke this class like:
103 *
104 *     java ElementStructureTest generate-hashes $LANGTOOLS_DIR/make/data/symbols/include.list (<classes-for-N> N)+
105 *
106 * Where <classes-for-N> is the file produced by make/src/classes/build/tools/symbolgenerator/Probe.java.
107 * So, to produce hashes for 6, 7 and 8, this command can be used:
108 *
109 *     java ElementStructureTest generate-hashes classes-6 6 classes-7 7 classes-8 8
110 *
111 * To inspect differences between the actual and expected output for version N, invoke this class like:
112 *
113 *     java ElementStructureTest generate-output $LANGTOOLS_DIR/make/data/symbols/include.list (<classes-for-N> N <actual-output-file> <expected-output-file>)+
114 *
115 * For example, to get the actual and expected output for 6 in /tmp/actual and /tmp/expected, respectively:
116 *
117 *     java ElementStructureTest generate-output $LANGTOOLS_DIR/make/data/symbols/include.list classes-6 6 /tmp/actual /tmp/expected
118 */
119public class ElementStructureTest {
120
121    static final byte[] hash6 = new byte[] {
122        (byte) 0x99, (byte) 0x34, (byte) 0x82, (byte) 0xCF,
123        (byte) 0xE0, (byte) 0x53, (byte) 0xF3, (byte) 0x13,
124        (byte) 0x4E, (byte) 0xCF, (byte) 0x49, (byte) 0x32,
125        (byte) 0xB7, (byte) 0x52, (byte) 0x0F, (byte) 0x68
126    };
127    static final byte[] hash7 = new byte[] {
128        (byte) 0x6B, (byte) 0xA2, (byte) 0xE9, (byte) 0x8E,
129        (byte) 0xE1, (byte) 0x8E, (byte) 0x60, (byte) 0xBE,
130        (byte) 0x54, (byte) 0xC4, (byte) 0x33, (byte) 0x3E,
131        (byte) 0x0C, (byte) 0x2D, (byte) 0x3A, (byte) 0x7C
132    };
133    static final byte[] hash8 = new byte[] {
134        (byte) 0x37, (byte) 0x0C, (byte) 0xBA, (byte) 0xCE,
135        (byte) 0xCF, (byte) 0x81, (byte) 0xAE, (byte) 0xA8,
136        (byte) 0x1E, (byte) 0x10, (byte) 0xAB, (byte) 0x72,
137        (byte) 0xF7, (byte) 0xE5, (byte) 0x34, (byte) 0x72
138    };
139
140    final static Map<String, byte[]> version2Hash = new HashMap<>();
141
142    static {
143        version2Hash.put("6", hash6);
144        version2Hash.put("7", hash7);
145        version2Hash.put("8", hash8);
146    }
147
148    public static void main(String... args) throws Exception {
149        if (args.length == 0) {
150            new ElementStructureTest().doTest();
151            return ;
152        }
153        switch (args[0]) {
154            case "generate-hashes":
155                new ElementStructureTest().generateHashes(args);
156                break;
157            case "generate-output":
158                new ElementStructureTest().generateOutput(args);
159                break;
160            default:
161                throw new IllegalStateException("Unrecognized request: " + args[0]);
162        }
163    }
164
165    void doTest() throws Exception {
166        for (PlatformProvider provider : ServiceLoader.load(PlatformProvider.class)) {
167            for (String ver : provider.getSupportedPlatformNames()) {
168                if (!version2Hash.containsKey(ver))
169                    continue;
170                try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); Writer output = new OutputStreamWriter(baos, "UTF-8")) {
171                    run(output, ver);
172                    output.close();
173                    byte[] actual = MessageDigest.getInstance("MD5").digest(baos.toByteArray());
174                    if (!Arrays.equals(version2Hash.get(ver), actual))
175                        throw new AssertionError("Wrong hash: " + toHex(actual) + " for version: " + ver);
176                }
177            }
178        }
179    }
180
181    void generateHashes(String... args) throws Exception {
182        Predicate<String> ignoreList = constructAcceptIgnoreList(args[1]);
183        for (int i = 2; i < args.length; i += 2) {
184            try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); Writer output = new OutputStreamWriter(baos, "UTF-8")) {
185                realClasses(args[i], ignoreList, output, args[i + 1]);
186                output.close();
187                System.err.println("version:" + args[i + 1] + "; " + toHex(MessageDigest.getInstance("MD5").digest(baos.toByteArray())));
188            }
189        }
190    }
191
192    void generateOutput(String... args) throws Exception {
193        Predicate<String> ignoreList = constructAcceptIgnoreList(args[1]);
194        for (int i = 2; i < args.length; i += 4) {
195            try (Writer actual = Files.newBufferedWriter(Paths.get(args[i + 2]));
196                 Writer expected = Files.newBufferedWriter(Paths.get(args[i + 3]))) {
197                run(actual, args[i + 1]);
198                realClasses(args[i], ignoreList, expected, args[i + 1]);
199            }
200        }
201    }
202
203    Predicate<String> constructAcceptIgnoreList(String fromFiles) throws IOException {
204        StringBuilder acceptPattern = new StringBuilder();
205        StringBuilder rejectPattern = new StringBuilder();
206        for (String file : fromFiles.split(File.pathSeparator)) {
207            try (Stream<String> lines = Files.lines(Paths.get(file))) {
208                lines.forEach(line -> {
209                    if (line.isEmpty())
210                        return;
211                    StringBuilder targetPattern;
212                    switch (line.charAt(0)) {
213                        case '+':
214                            targetPattern = acceptPattern;
215                            break;
216                        case '-':
217                            targetPattern = rejectPattern;
218                            break;
219                        default:
220                            return ;
221                    }
222                    line = line.substring(1);
223                    if (line.endsWith("/")) {
224                        line += "[^/]*";
225                    } else {
226                        line += "|" + line + "$[^/]*";
227                    }
228                    line = line.replace("/", ".");
229                    if (targetPattern.length() != 0)
230                        targetPattern.append("|");
231                    targetPattern.append(line);
232                });
233            }
234        }
235        Pattern accept = Pattern.compile(acceptPattern.toString());
236        Pattern reject = Pattern.compile(rejectPattern.toString());
237
238        return clazzName -> accept.matcher(clazzName).matches() && !reject.matcher(clazzName).matches();
239    }
240
241    private static String toHex(byte[] bytes) {
242        StringBuilder hex = new StringBuilder();
243        String delim = "";
244
245        for (byte b : bytes) {
246            hex.append(delim);
247            hex.append(String.format("(byte) 0x%02X", b));
248            delim = ", ";
249        }
250
251        return hex.toString();
252    }
253
254    void run(Writer output, String version) throws Exception {
255        List<String> options = Arrays.asList("-release", version, "-classpath", "");
256        List<ToolBox.JavaSource> files = Arrays.asList(new ToolBox.JavaSource("Test", ""));
257        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
258        JavacTaskImpl task = (JavacTaskImpl) compiler.getTask(null, null, null, options, null, files);
259
260        task.analyze();
261
262        JavaFileManager fm = task.getContext().get(JavaFileManager.class);
263
264        for (String pack : packages(fm)) {
265            PackageElement packEl = task.getElements().getPackageElement(pack);
266            if (packEl == null) {
267                throw new AssertionError("Cannot find package: " + pack);
268            }
269            new ExhaustiveElementScanner(task, output, p -> true).visit(packEl);
270        }
271    }
272
273    void realClasses(String location, Predicate<String> acceptor, Writer output, String version) throws Exception {
274        Path classes = Paths.get(location);
275        Map<String, JavaFileObject> className2File = new HashMap<>();
276        Map<JavaFileObject, String> file2ClassName = new HashMap<>();
277
278        try (BufferedReader descIn = Files.newBufferedReader(classes)) {
279            String classFileData;
280
281            while ((classFileData = descIn.readLine()) != null) {
282                ByteArrayOutputStream data = new ByteArrayOutputStream();
283                for (int i = 0; i < classFileData.length(); i += 2) {
284                    data.write(Integer.parseInt(classFileData.substring(i, i + 2), 16));
285                }
286                JavaFileObject file = new ByteArrayJavaFileObject(data.toByteArray());
287                try (InputStream in = new ByteArrayInputStream(data.toByteArray())) {
288                    String name = ClassFile.read(in).getName().replace("/", ".");
289                    className2File.put(name, file);
290                    file2ClassName.put(file, name);
291                } catch (IOException | ConstantPoolException ex) {
292                    throw new IllegalStateException(ex);
293                }
294            }
295        }
296
297        try (JavaFileManager fm = new TestFileManager(className2File, file2ClassName)) {
298            JavacTaskImpl task = (JavacTaskImpl) ToolProvider.getSystemJavaCompiler().getTask(null, fm, null, Arrays.asList("-source", version), null, Arrays.asList(new ToolBox.JavaSource("Test", "")));
299            task.parse();
300
301            PACK: for (String pack : packages(fm)) {
302                PackageElement packEl = task.getElements().getPackageElement(pack);
303                assert packEl != null;
304                new ExhaustiveElementScanner(task, output, acceptor).visit(packEl);
305            }
306        }
307    }
308
309    Set<String> packages(JavaFileManager fm) throws IOException {
310        Set<String> packages = new TreeSet<>();
311        EnumSet<Kind> kinds = EnumSet.of(JavaFileObject.Kind.CLASS, JavaFileObject.Kind.OTHER);
312
313        for (JavaFileObject file : fm.list(StandardLocation.PLATFORM_CLASS_PATH, "", kinds, true)) {
314            String binary = fm.inferBinaryName(StandardLocation.PLATFORM_CLASS_PATH, file);
315            packages.add(binary.substring(0, binary.lastIndexOf('.')));
316        }
317
318        return packages;
319    }
320
321    final class ExhaustiveElementScanner implements ElementVisitor<Void, Void> {
322
323        final JavacTask task;
324        final Writer out;
325        final Predicate<String> acceptType;
326
327        public ExhaustiveElementScanner(JavacTask task, Writer out, Predicate<String> acceptType) {
328            this.task = task;
329            this.out = out;
330            this.acceptType = acceptType;
331        }
332
333        @Override
334        public Void visit(Element e, Void p) {
335            return e.accept(this, p);
336        }
337
338        @Override
339        public Void visit(Element e) {
340            return e.accept(this, null);
341        }
342
343        private void write(TypeMirror type) throws IOException {
344            try {
345                out.write(type.toString()
346                              .replace("java.lang.invoke.MethodHandle$PolymorphicSignature", "java.lang.invoke.MethodHandle.PolymorphicSignature")
347                              .replace("javax.swing.JRootPane$DefaultAction", "javax.swing.JRootPane.DefaultAction")
348                              .replace("javax.swing.plaf.metal.MetalFileChooserUI$DirectoryComboBoxRenderer", "javax.swing.plaf.metal.MetalFileChooserUI.DirectoryComboBoxRenderer")
349                         );
350            } catch (CompletionFailure cf) {
351                out.write("cf");
352            }
353        }
354
355        private void writeTypes(Iterable<? extends TypeMirror> types) throws IOException {
356            String sep = "";
357
358            for (TypeMirror type : types) {
359                out.write(sep);
360                write(type);
361                sep = ", ";
362            }
363        }
364
365        private void writeAnnotations(Iterable<? extends AnnotationMirror> annotations) throws IOException {
366            for (AnnotationMirror ann : annotations) {
367                out.write("@");
368                write(ann.getAnnotationType());
369                if (!ann.getElementValues().isEmpty()) {
370                    out.write("(");
371                    Map<ExecutableElement, AnnotationValue> valuesMap = new TreeMap<>((a1, a2) -> a1.getSimpleName().toString().compareTo(a2.getSimpleName().toString()));
372                    valuesMap.putAll(ann.getElementValues());
373                    for (Entry<? extends ExecutableElement, ? extends AnnotationValue> ev : valuesMap.entrySet()) {
374                        out.write(ev.getKey().getSimpleName().toString());
375                        out.write(" = ");
376                        out.write(ev.getValue().toString());
377                    }
378                    out.write(")");
379                }
380            }
381        }
382
383        void analyzeElement(Element e) {
384            try {
385                write(e.asType());
386                writeAnnotations(e.getAnnotationMirrors());
387                out.write(e.getKind().toString());
388                out.write(e.getModifiers().toString());
389                out.write(e.getSimpleName().toString());
390            } catch (IOException ex) {
391                ex.printStackTrace();
392            }
393        }
394
395        boolean acceptAccess(Element e) {
396            return e.getModifiers().contains(Modifier.PUBLIC) || e.getModifiers().contains(Modifier.PROTECTED);
397        }
398
399        @Override
400        public Void visitExecutable(ExecutableElement e, Void p) {
401            if (!acceptAccess(e))
402                return null;
403            try {
404                analyzeElement(e);
405                out.write(String.valueOf(e.getDefaultValue()));
406                for (VariableElement param : e.getParameters()) {
407                    visit(param, p);
408                }
409                out.write(String.valueOf(e.getReceiverType()));
410                write(e.getReturnType());
411                out.write(e.getSimpleName().toString());
412                writeTypes(e.getThrownTypes());
413                for (TypeParameterElement param : e.getTypeParameters()) {
414                    visit(param, p);
415                }
416                out.write(String.valueOf(e.isDefault()));
417                out.write(String.valueOf(e.isVarArgs()));
418                out.write("\n");
419            } catch (IOException ex) {
420                ex.printStackTrace();
421            }
422            return null;
423        }
424
425        @Override
426        public Void visitPackage(PackageElement e, Void p) {
427            List<Element> types = new ArrayList<>(e.getEnclosedElements());
428            Collections.sort(types, (e1, e2) -> e1.getSimpleName().toString().compareTo(e2.getSimpleName().toString()));
429            for (Element encl : types) {
430                visit(encl, p);
431            }
432            return null;
433        }
434
435        @Override
436        public Void visitType(TypeElement e, Void p) {
437            if (!acceptAccess(e))
438                return null;
439            writeType(e);
440            return null;
441        }
442
443        void writeType(TypeElement e) {
444            if (!acceptType.test(task.getElements().getBinaryName(e).toString()))
445                return ;
446            try {
447                analyzeElement(e);
448                writeTypes(e.getInterfaces());
449                out.write(e.getNestingKind().toString());
450                out.write(e.getQualifiedName().toString());
451                write(e.getSuperclass());
452                for (TypeParameterElement param : e.getTypeParameters()) {
453                    visit(param, null);
454                }
455                List<Element> defs = new ArrayList<>(e.getEnclosedElements()); //XXX: forcing ordering for members - not completely correct!
456                Collections.sort(defs, (e1, e2) -> e1.toString().compareTo(e2.toString()));
457                for (Element def : defs) {
458                    visit(def, null);
459                }
460                out.write("\n");
461            } catch (IOException ex) {
462                ex.printStackTrace();
463            }
464        }
465
466        @Override
467        public Void visitVariable(VariableElement e, Void p) {
468            if (!acceptAccess(e))
469                return null;
470            try {
471                analyzeElement(e);
472                out.write(String.valueOf(e.getConstantValue()));
473                out.write("\n");
474            } catch (IOException ex) {
475                ex.printStackTrace();
476            }
477            return null;
478        }
479
480        @Override
481        public Void visitTypeParameter(TypeParameterElement e, Void p) {
482            try {
483                analyzeElement(e);
484                out.write(e.getBounds().toString());
485                out.write("\n");
486            } catch (IOException ex) {
487                ex.printStackTrace();
488            }
489            return null;
490        }
491
492        @Override
493        public Void visitModule(ModuleElement e, Void p) {
494            throw new IllegalStateException("Not supported yet.");
495        }
496
497        @Override
498        public Void visitUnknown(Element e, Void p) {
499            throw new IllegalStateException("Should not get here.");
500        }
501
502    }
503
504    final class TestFileManager implements JavaFileManager {
505
506        final Map<String, JavaFileObject> className2File;
507        final Map<JavaFileObject, String> file2ClassName;
508
509        public TestFileManager(Map<String, JavaFileObject> className2File, Map<JavaFileObject, String> file2ClassName) {
510            this.className2File = className2File;
511            this.file2ClassName = file2ClassName;
512        }
513
514        @Override
515        public ClassLoader getClassLoader(Location location) {
516            return new URLClassLoader(new URL[0]);
517        }
518
519        @Override
520        public Iterable<JavaFileObject> list(Location location, String packageName, Set<Kind> kinds, boolean recurse) throws IOException {
521            if (location != StandardLocation.PLATFORM_CLASS_PATH || !kinds.contains(Kind.CLASS))
522                return Collections.emptyList();
523
524            if (!packageName.isEmpty())
525                packageName += ".";
526
527            List<JavaFileObject> result = new ArrayList<>();
528
529            for (Entry<String, JavaFileObject> e : className2File.entrySet()) {
530                String currentPackage = e.getKey().substring(0, e.getKey().lastIndexOf(".") + 1);
531                if (recurse ? currentPackage.startsWith(packageName) : packageName.equals(currentPackage))
532                    result.add(e.getValue());
533            }
534
535            return result;
536        }
537
538        @Override
539        public String inferBinaryName(Location location, JavaFileObject file) {
540            return file2ClassName.get(file);
541        }
542
543        @Override
544        public boolean isSameFile(FileObject a, FileObject b) {
545            return a == b;
546        }
547
548        @Override
549        public boolean handleOption(String current, Iterator<String> remaining) {
550            return false;
551        }
552
553        @Override
554        public boolean hasLocation(Location location) {
555            return location == StandardLocation.PLATFORM_CLASS_PATH;
556        }
557
558        @Override
559        public JavaFileObject getJavaFileForInput(Location location, String className, Kind kind) throws IOException {
560            if (location != StandardLocation.PLATFORM_CLASS_PATH || kind != Kind.CLASS)
561                return null;
562
563            return className2File.get(className);
564        }
565
566        @Override
567        public JavaFileObject getJavaFileForOutput(Location location, String className, Kind kind, FileObject sibling) throws IOException {
568            throw new UnsupportedOperationException("");
569        }
570
571        @Override
572        public FileObject getFileForInput(Location location, String packageName, String relativeName) throws IOException {
573            return null;
574        }
575
576        @Override
577        public FileObject getFileForOutput(Location location, String packageName, String relativeName, FileObject sibling) throws IOException {
578            throw new UnsupportedOperationException("");
579        }
580
581        @Override
582        public void flush() throws IOException {
583        }
584
585        @Override
586        public void close() throws IOException {
587        }
588
589        @Override
590        public int isSupportedOption(String option) {
591            return -1;
592        }
593
594    }
595
596    static class ByteArrayJavaFileObject implements JavaFileObject {
597
598        private final byte[] data;
599
600        public ByteArrayJavaFileObject(byte[] data) {
601            this.data = data;
602        }
603
604        @Override
605        public Kind getKind() {
606            return Kind.CLASS;
607        }
608
609        @Override
610        public boolean isNameCompatible(String simpleName, Kind kind) {
611            return true;
612        }
613
614        @Override
615        public NestingKind getNestingKind() {
616            return null;
617        }
618
619        @Override
620        public Modifier getAccessLevel() {
621            return null;
622        }
623
624        @Override
625        public URI toUri() {
626            return null;
627        }
628
629        @Override
630        public String getName() {
631            return null;
632        }
633
634        @Override
635        public InputStream openInputStream() throws IOException {
636            return new ByteArrayInputStream(data);
637        }
638
639        @Override
640        public OutputStream openOutputStream() throws IOException {
641            throw new UnsupportedOperationException();
642        }
643
644        @Override
645        public Reader openReader(boolean ignoreEncodingErrors) throws IOException {
646            throw new UnsupportedOperationException();
647        }
648
649        @Override
650        public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
651            throw new UnsupportedOperationException();
652        }
653
654        @Override
655        public Writer openWriter() throws IOException {
656            throw new UnsupportedOperationException();
657        }
658
659        @Override
660        public long getLastModified() {
661            return 0;
662        }
663
664        @Override
665        public boolean delete() {
666            throw new UnsupportedOperationException();
667        }
668    }
669
670}
671