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