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