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 }