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