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