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