1 /* 2 * Copyright 2016 Google Inc. All Rights Reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.google.turbine.lower; 18 19 import static com.google.common.collect.ImmutableList.toImmutableList; 20 import static com.google.common.io.MoreFiles.getFileExtension; 21 import static com.google.turbine.testing.TestClassPaths.TURBINE_BOOTCLASSPATH; 22 import static java.nio.charset.StandardCharsets.UTF_8; 23 import static java.util.Objects.requireNonNull; 24 import static java.util.stream.Collectors.joining; 25 import static java.util.stream.Collectors.toCollection; 26 import static java.util.stream.Collectors.toList; 27 import static java.util.stream.Collectors.toSet; 28 import static org.junit.Assert.fail; 29 30 import com.google.common.base.Joiner; 31 import com.google.common.base.Splitter; 32 import com.google.common.collect.ImmutableList; 33 import com.google.common.io.MoreFiles; 34 import com.google.common.jimfs.Configuration; 35 import com.google.common.jimfs.Jimfs; 36 import org.objectweb.asm.Opcodes; 37 import com.google.turbine.binder.Binder; 38 import com.google.turbine.binder.Binder.BindingResult; 39 import com.google.turbine.binder.ClassPath; 40 import com.google.turbine.binder.ClassPathBinder; 41 import com.google.turbine.diag.SourceFile; 42 import com.google.turbine.options.LanguageVersion; 43 import com.google.turbine.parse.Parser; 44 import com.google.turbine.testing.AsmUtils; 45 import com.google.turbine.tree.Tree.CompUnit; 46 import com.sun.source.util.JavacTask; 47 import com.sun.tools.javac.api.JavacTool; 48 import com.sun.tools.javac.file.JavacFileManager; 49 import com.sun.tools.javac.util.Context; 50 import java.io.BufferedWriter; 51 import java.io.IOException; 52 import java.io.OutputStreamWriter; 53 import java.io.PrintWriter; 54 import java.nio.file.FileSystem; 55 import java.nio.file.FileVisitResult; 56 import java.nio.file.Files; 57 import java.nio.file.Path; 58 import java.nio.file.SimpleFileVisitor; 59 import java.nio.file.attribute.BasicFileAttributes; 60 import java.util.ArrayDeque; 61 import java.util.ArrayList; 62 import java.util.Collection; 63 import java.util.Collections; 64 import java.util.Comparator; 65 import java.util.Deque; 66 import java.util.HashMap; 67 import java.util.HashSet; 68 import java.util.LinkedHashMap; 69 import java.util.List; 70 import java.util.Map; 71 import java.util.Optional; 72 import java.util.Set; 73 import javax.tools.DiagnosticCollector; 74 import javax.tools.JavaFileObject; 75 import javax.tools.StandardLocation; 76 import org.objectweb.asm.ClassReader; 77 import org.objectweb.asm.ClassWriter; 78 import org.objectweb.asm.Opcodes; 79 import org.objectweb.asm.Type; 80 import org.objectweb.asm.signature.SignatureReader; 81 import org.objectweb.asm.signature.SignatureVisitor; 82 import org.objectweb.asm.tree.AnnotationNode; 83 import org.objectweb.asm.tree.ClassNode; 84 import org.objectweb.asm.tree.FieldNode; 85 import org.objectweb.asm.tree.InnerClassNode; 86 import org.objectweb.asm.tree.MethodNode; 87 import org.objectweb.asm.tree.RecordComponentNode; 88 import org.objectweb.asm.tree.TypeAnnotationNode; 89 90 /** Support for bytecode diffing-integration tests. */ 91 public final class IntegrationTestSupport { 92 93 /** 94 * Normalizes order of members, attributes, and constant pool entries, to allow diffing bytecode. 95 */ sortMembers(Map<String, byte[]> in)96 public static Map<String, byte[]> sortMembers(Map<String, byte[]> in) { 97 List<ClassNode> classes = toClassNodes(in); 98 for (ClassNode n : classes) { 99 sortAttributes(n); 100 } 101 return toByteCode(classes); 102 } 103 104 /** 105 * Canonicalizes bytecode produced by javac to match the expected output of turbine. Includes the 106 * same normalization as {@link #sortMembers}, as well as removing everything not produced by the 107 * header compiler (code, debug info, etc.) 108 */ canonicalize(Map<String, byte[]> in)109 public static Map<String, byte[]> canonicalize(Map<String, byte[]> in) { 110 List<ClassNode> classes = toClassNodes(in); 111 112 // drop local and anonymous classes 113 classes = 114 classes.stream() 115 .filter(n -> !isAnonymous(n) && !isLocal(n)) 116 .collect(toCollection(ArrayList::new)); 117 118 // collect all inner classes attributes 119 Map<String, InnerClassNode> infos = new HashMap<>(); 120 for (ClassNode n : classes) { 121 for (InnerClassNode innerClassNode : n.innerClasses) { 122 infos.put(innerClassNode.name, innerClassNode); 123 } 124 } 125 126 HashSet<String> all = classes.stream().map(n -> n.name).collect(toCollection(HashSet::new)); 127 for (ClassNode n : classes) { 128 removeImplementation(n); 129 removeUnusedInnerClassAttributes(infos, n); 130 makeEnumsFinal(all, n); 131 sortAttributes(n); 132 undeprecate(n); 133 } 134 135 return toByteCode(classes); 136 } 137 isLocal(ClassNode n)138 private static boolean isLocal(ClassNode n) { 139 return n.outerMethod != null; 140 } 141 isAnonymous(ClassNode n)142 private static boolean isAnonymous(ClassNode n) { 143 // JVMS 4.7.6: if C is anonymous, the value of the inner_name_index item must be zero 144 return n.innerClasses.stream().anyMatch(i -> i.name.equals(n.name) && i.innerName == null); 145 } 146 147 // ASM sets ACC_DEPRECATED for elements with the Deprecated attribute; 148 // unset it if the @Deprecated annotation is not also present. 149 // This can happen if the @deprecated javadoc tag was present but the 150 // annotation wasn't. undeprecate(ClassNode n)151 private static void undeprecate(ClassNode n) { 152 if (!isDeprecated(n.visibleAnnotations)) { 153 n.access &= ~Opcodes.ACC_DEPRECATED; 154 } 155 n.methods.stream() 156 .filter(m -> !isDeprecated(m.visibleAnnotations)) 157 .forEach(m -> m.access &= ~Opcodes.ACC_DEPRECATED); 158 n.fields.stream() 159 .filter(f -> !isDeprecated(f.visibleAnnotations)) 160 .forEach(f -> f.access &= ~Opcodes.ACC_DEPRECATED); 161 } 162 isDeprecated(List<AnnotationNode> visibleAnnotations)163 private static boolean isDeprecated(List<AnnotationNode> visibleAnnotations) { 164 return visibleAnnotations != null 165 && visibleAnnotations.stream().anyMatch(a -> a.desc.equals("Ljava/lang/Deprecated;")); 166 } 167 makeEnumsFinal(Set<String> all, ClassNode n)168 private static void makeEnumsFinal(Set<String> all, ClassNode n) { 169 n.innerClasses.forEach( 170 x -> { 171 if (all.contains(x.name) && (x.access & Opcodes.ACC_ENUM) == Opcodes.ACC_ENUM) { 172 x.access &= ~Opcodes.ACC_ABSTRACT; 173 x.access |= Opcodes.ACC_FINAL; 174 } 175 }); 176 if ((n.access & Opcodes.ACC_ENUM) == Opcodes.ACC_ENUM) { 177 n.access &= ~Opcodes.ACC_ABSTRACT; 178 n.access |= Opcodes.ACC_FINAL; 179 } 180 } 181 toByteCode(List<ClassNode> classes)182 private static Map<String, byte[]> toByteCode(List<ClassNode> classes) { 183 Map<String, byte[]> out = new LinkedHashMap<>(); 184 for (ClassNode n : classes) { 185 ClassWriter cw = new ClassWriter(0); 186 n.accept(cw); 187 out.put(n.name, cw.toByteArray()); 188 } 189 return out; 190 } 191 toClassNodes(Map<String, byte[]> in)192 private static List<ClassNode> toClassNodes(Map<String, byte[]> in) { 193 List<ClassNode> classes = new ArrayList<>(); 194 for (byte[] f : in.values()) { 195 ClassNode n = new ClassNode(); 196 new ClassReader(f).accept(n, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); 197 198 classes.add(n); 199 } 200 return classes; 201 } 202 203 /** Remove elements that are omitted by turbine, e.g. private and synthetic members. */ removeImplementation(ClassNode n)204 private static void removeImplementation(ClassNode n) { 205 n.innerClasses = 206 n.innerClasses.stream() 207 .filter(x -> (x.access & Opcodes.ACC_SYNTHETIC) == 0 && x.innerName != null) 208 .collect(toList()); 209 210 n.methods = 211 n.methods.stream() 212 .filter(x -> (x.access & (Opcodes.ACC_SYNTHETIC | Opcodes.ACC_PRIVATE)) == 0) 213 .filter(x -> !x.name.equals("<clinit>")) 214 .collect(toList()); 215 216 n.fields = 217 n.fields.stream() 218 .filter(x -> (x.access & (Opcodes.ACC_SYNTHETIC | Opcodes.ACC_PRIVATE)) == 0) 219 .collect(toList()); 220 } 221 222 /** Apply a standard sort order to attributes. */ sortAttributes(ClassNode n)223 private static void sortAttributes(ClassNode n) { 224 225 n.innerClasses.sort( 226 Comparator.comparing((InnerClassNode x) -> x.name) 227 .thenComparing(x -> x.outerName) 228 .thenComparing(x -> x.innerName) 229 .thenComparing(x -> x.access)); 230 231 sortAnnotations(n.visibleAnnotations); 232 sortAnnotations(n.invisibleAnnotations); 233 sortTypeAnnotations(n.visibleTypeAnnotations); 234 sortTypeAnnotations(n.invisibleTypeAnnotations); 235 236 for (MethodNode m : n.methods) { 237 sortParameterAnnotations(m.visibleParameterAnnotations); 238 sortParameterAnnotations(m.invisibleParameterAnnotations); 239 240 sortAnnotations(m.visibleAnnotations); 241 sortAnnotations(m.invisibleAnnotations); 242 sortTypeAnnotations(m.visibleTypeAnnotations); 243 sortTypeAnnotations(m.invisibleTypeAnnotations); 244 } 245 246 for (FieldNode f : n.fields) { 247 sortAnnotations(f.visibleAnnotations); 248 sortAnnotations(f.invisibleAnnotations); 249 sortTypeAnnotations(f.visibleTypeAnnotations); 250 sortTypeAnnotations(f.invisibleTypeAnnotations); 251 } 252 253 if (n.recordComponents != null) { 254 for (RecordComponentNode r : n.recordComponents) { 255 sortAnnotations(r.visibleAnnotations); 256 sortAnnotations(r.invisibleAnnotations); 257 sortTypeAnnotations(r.visibleTypeAnnotations); 258 sortTypeAnnotations(r.invisibleTypeAnnotations); 259 } 260 } 261 262 if (n.nestMembers != null) { 263 Collections.sort(n.nestMembers); 264 } 265 } 266 sortParameterAnnotations(List<AnnotationNode>[] parameters)267 private static void sortParameterAnnotations(List<AnnotationNode>[] parameters) { 268 if (parameters == null) { 269 return; 270 } 271 for (List<AnnotationNode> annos : parameters) { 272 sortAnnotations(annos); 273 } 274 } 275 sortTypeAnnotations(List<TypeAnnotationNode> annos)276 private static void sortTypeAnnotations(List<TypeAnnotationNode> annos) { 277 if (annos == null) { 278 return; 279 } 280 annos.sort( 281 Comparator.comparing((TypeAnnotationNode a) -> a.desc) 282 .thenComparing(a -> String.valueOf(a.typeRef)) 283 .thenComparing(a -> String.valueOf(a.typePath)) 284 .thenComparing(a -> String.valueOf(a.values))); 285 } 286 sortAnnotations(List<AnnotationNode> annos)287 private static void sortAnnotations(List<AnnotationNode> annos) { 288 if (annos == null) { 289 return; 290 } 291 annos.sort( 292 Comparator.comparing((AnnotationNode a) -> a.desc) 293 .thenComparing(a -> String.valueOf(a.values))); 294 } 295 296 /** 297 * Remove InnerClass attributes that are no longer needed after member pruning. This requires 298 * visiting all descriptors and signatures in the bytecode to find references to inner classes. 299 */ removeUnusedInnerClassAttributes( Map<String, InnerClassNode> infos, ClassNode n)300 private static void removeUnusedInnerClassAttributes( 301 Map<String, InnerClassNode> infos, ClassNode n) { 302 Set<String> types = new HashSet<>(); 303 { 304 types.add(n.name); 305 collectTypesFromSignature(types, n.signature); 306 if (n.superName != null) { 307 types.add(n.superName); 308 } 309 types.addAll(n.interfaces); 310 311 addTypesInAnnotations(types, n.visibleAnnotations); 312 addTypesInAnnotations(types, n.invisibleAnnotations); 313 addTypesInTypeAnnotations(types, n.visibleTypeAnnotations); 314 addTypesInTypeAnnotations(types, n.invisibleTypeAnnotations); 315 } 316 for (MethodNode m : n.methods) { 317 collectTypesFromSignature(types, m.desc); 318 collectTypesFromSignature(types, m.signature); 319 types.addAll(m.exceptions); 320 321 addTypesInAnnotations(types, m.visibleAnnotations); 322 addTypesInAnnotations(types, m.invisibleAnnotations); 323 addTypesInTypeAnnotations(types, m.visibleTypeAnnotations); 324 addTypesInTypeAnnotations(types, m.invisibleTypeAnnotations); 325 326 addTypesFromParameterAnnotations(types, m.visibleParameterAnnotations); 327 addTypesFromParameterAnnotations(types, m.invisibleParameterAnnotations); 328 329 collectTypesFromAnnotationValue(types, m.annotationDefault); 330 } 331 for (FieldNode f : n.fields) { 332 collectTypesFromSignature(types, f.desc); 333 collectTypesFromSignature(types, f.signature); 334 335 addTypesInAnnotations(types, f.visibleAnnotations); 336 addTypesInAnnotations(types, f.invisibleAnnotations); 337 addTypesInTypeAnnotations(types, f.visibleTypeAnnotations); 338 addTypesInTypeAnnotations(types, f.invisibleTypeAnnotations); 339 } 340 if (n.recordComponents != null) { 341 for (RecordComponentNode r : n.recordComponents) { 342 collectTypesFromSignature(types, r.descriptor); 343 collectTypesFromSignature(types, r.signature); 344 345 addTypesInAnnotations(types, r.visibleAnnotations); 346 addTypesInAnnotations(types, r.invisibleAnnotations); 347 addTypesInTypeAnnotations(types, r.visibleTypeAnnotations); 348 addTypesInTypeAnnotations(types, r.invisibleTypeAnnotations); 349 } 350 } 351 352 if (n.nestMembers != null) { 353 for (String member : n.nestMembers) { 354 InnerClassNode i = infos.get(member); 355 if (i.outerName != null) { 356 types.add(member); 357 } 358 } 359 } 360 361 List<InnerClassNode> used = new ArrayList<>(); 362 for (InnerClassNode i : n.innerClasses) { 363 if (i.outerName != null && i.outerName.equals(n.name)) { 364 // keep InnerClass attributes for any member classes 365 used.add(i); 366 } else if (types.contains(i.name)) { 367 // otherwise, keep InnerClass attributes that were referenced in class or member signatures 368 addInnerChain(infos, used, i.name); 369 } 370 } 371 addInnerChain(infos, used, n.name); 372 n.innerClasses = used; 373 374 if (n.nestMembers != null) { 375 Set<String> members = used.stream().map(i -> i.name).collect(toSet()); 376 n.nestMembers = n.nestMembers.stream().filter(members::contains).collect(toList()); 377 } 378 } 379 addTypesFromParameterAnnotations( Set<String> types, List<AnnotationNode>[] parameterAnnotations)380 private static void addTypesFromParameterAnnotations( 381 Set<String> types, List<AnnotationNode>[] parameterAnnotations) { 382 if (parameterAnnotations == null) { 383 return; 384 } 385 for (List<AnnotationNode> annos : parameterAnnotations) { 386 addTypesInAnnotations(types, annos); 387 } 388 } 389 addTypesInTypeAnnotations(Set<String> types, List<TypeAnnotationNode> annos)390 private static void addTypesInTypeAnnotations(Set<String> types, List<TypeAnnotationNode> annos) { 391 if (annos == null) { 392 return; 393 } 394 annos.forEach(a -> collectTypesFromAnnotation(types, a)); 395 } 396 addTypesInAnnotations(Set<String> types, List<AnnotationNode> annos)397 private static void addTypesInAnnotations(Set<String> types, List<AnnotationNode> annos) { 398 if (annos == null) { 399 return; 400 } 401 annos.forEach(a -> collectTypesFromAnnotation(types, a)); 402 } 403 collectTypesFromAnnotation(Set<String> types, AnnotationNode a)404 private static void collectTypesFromAnnotation(Set<String> types, AnnotationNode a) { 405 collectTypesFromSignature(types, a.desc); 406 collectTypesFromAnnotationValues(types, a.values); 407 } 408 collectTypesFromAnnotationValues(Set<String> types, List<?> values)409 private static void collectTypesFromAnnotationValues(Set<String> types, List<?> values) { 410 if (values == null) { 411 return; 412 } 413 for (Object v : values) { 414 collectTypesFromAnnotationValue(types, v); 415 } 416 } 417 collectTypesFromAnnotationValue(Set<String> types, Object v)418 private static void collectTypesFromAnnotationValue(Set<String> types, Object v) { 419 if (v instanceof List) { 420 collectTypesFromAnnotationValues(types, (List<?>) v); 421 } else if (v instanceof Type) { 422 collectTypesFromSignature(types, ((Type) v).getDescriptor()); 423 } else if (v instanceof AnnotationNode) { 424 collectTypesFromAnnotation(types, (AnnotationNode) v); 425 } else if (v instanceof String[]) { 426 String[] enumValue = (String[]) v; 427 collectTypesFromSignature(types, enumValue[0]); 428 } 429 } 430 431 /** 432 * For each preserved InnerClass attribute, keep any information about transitive enclosing 433 * classes of the inner class. 434 */ addInnerChain( Map<String, InnerClassNode> infos, List<InnerClassNode> used, String i)435 private static void addInnerChain( 436 Map<String, InnerClassNode> infos, List<InnerClassNode> used, String i) { 437 while (infos.containsKey(i)) { 438 InnerClassNode info = infos.get(i); 439 used.add(info); 440 i = info.outerName; 441 } 442 } 443 444 /** Save all class types referenced in a signature. */ collectTypesFromSignature(Set<String> classes, String signature)445 private static void collectTypesFromSignature(Set<String> classes, String signature) { 446 if (signature == null) { 447 return; 448 } 449 // signatures for qualified generic class types are visited as name and type argument pieces, 450 // so stitch them back together into a binary class name 451 final Set<String> classes1 = classes; 452 new SignatureReader(signature) 453 .accept( 454 new SignatureVisitor(Opcodes.ASM9) { 455 private final Set<String> classes = classes1; 456 // class signatures may contain type arguments that contain class signatures 457 Deque<List<String>> pieces = new ArrayDeque<>(); 458 459 @Override 460 public void visitInnerClassType(String name) { 461 pieces.element().add(name); 462 } 463 464 @Override 465 public void visitClassType(String name) { 466 List<String> classType = new ArrayList<>(); 467 classType.add(name); 468 pieces.push(classType); 469 } 470 471 @Override 472 public void visitEnd() { 473 classes.add(Joiner.on('$').join(pieces.pop())); 474 super.visitEnd(); 475 } 476 }); 477 } 478 runTurbine( Map<String, String> input, ImmutableList<Path> classpath)479 public static Map<String, byte[]> runTurbine( 480 Map<String, String> input, ImmutableList<Path> classpath) throws IOException { 481 return runTurbine(input, classpath, ImmutableList.of()); 482 } 483 runTurbine( Map<String, String> input, ImmutableList<Path> classpath, ImmutableList<String> javacopts)484 public static Map<String, byte[]> runTurbine( 485 Map<String, String> input, ImmutableList<Path> classpath, ImmutableList<String> javacopts) 486 throws IOException { 487 return runTurbine( 488 input, classpath, TURBINE_BOOTCLASSPATH, /* moduleVersion= */ Optional.empty(), javacopts); 489 } 490 runTurbine( Map<String, String> input, ImmutableList<Path> classpath, ClassPath bootClassPath, Optional<String> moduleVersion, ImmutableList<String> javacopts)491 static Map<String, byte[]> runTurbine( 492 Map<String, String> input, 493 ImmutableList<Path> classpath, 494 ClassPath bootClassPath, 495 Optional<String> moduleVersion, 496 ImmutableList<String> javacopts) 497 throws IOException { 498 BindingResult bound = turbineAnalysis(input, classpath, bootClassPath, moduleVersion); 499 return Lower.lowerAll( 500 Lower.LowerOptions.builder() 501 .languageVersion(LanguageVersion.fromJavacopts(javacopts)) 502 .build(), 503 bound.units(), 504 bound.modules(), 505 bound.classPathEnv()) 506 .bytes(); 507 } 508 turbineAnalysis( Map<String, String> input, ImmutableList<Path> classpath, ClassPath bootClassPath, Optional<String> moduleVersion)509 public static BindingResult turbineAnalysis( 510 Map<String, String> input, 511 ImmutableList<Path> classpath, 512 ClassPath bootClassPath, 513 Optional<String> moduleVersion) 514 throws IOException { 515 ImmutableList<CompUnit> units = 516 input.entrySet().stream() 517 .map(e -> new SourceFile(e.getKey(), e.getValue())) 518 .map(Parser::parse) 519 .collect(toImmutableList()); 520 521 return Binder.bind( 522 units, ClassPathBinder.bindClasspath(classpath), bootClassPath, moduleVersion); 523 } 524 runJavacAnalysis( Map<String, String> sources, Collection<Path> classpath, ImmutableList<String> options)525 public static JavacTask runJavacAnalysis( 526 Map<String, String> sources, Collection<Path> classpath, ImmutableList<String> options) 527 throws Exception { 528 return runJavacAnalysis(sources, classpath, options, new DiagnosticCollector<>()); 529 } 530 runJavacAnalysis( Map<String, String> sources, Collection<Path> classpath, ImmutableList<String> options, DiagnosticCollector<JavaFileObject> collector)531 public static JavacTask runJavacAnalysis( 532 Map<String, String> sources, 533 Collection<Path> classpath, 534 ImmutableList<String> options, 535 DiagnosticCollector<JavaFileObject> collector) 536 throws Exception { 537 FileSystem fs = Jimfs.newFileSystem(Configuration.unix()); 538 Path out = fs.getPath("out"); 539 return setupJavac(sources, classpath, options, collector, fs, out); 540 } 541 runJavac( Map<String, String> sources, Collection<Path> classpath)542 public static Map<String, byte[]> runJavac( 543 Map<String, String> sources, Collection<Path> classpath) throws Exception { 544 return runJavac( 545 sources, classpath, ImmutableList.of("-parameters", "-source", "8", "-target", "8")); 546 } 547 runJavac( Map<String, String> sources, Collection<Path> classpath, ImmutableList<String> options)548 public static Map<String, byte[]> runJavac( 549 Map<String, String> sources, Collection<Path> classpath, ImmutableList<String> options) 550 throws Exception { 551 552 DiagnosticCollector<JavaFileObject> collector = new DiagnosticCollector<>(); 553 FileSystem fs = Jimfs.newFileSystem(Configuration.unix()); 554 Path out = fs.getPath("out"); 555 556 JavacTask task = setupJavac(sources, classpath, options, collector, fs, out); 557 558 if (!task.call()) { 559 fail(collector.getDiagnostics().stream().map(d -> d.toString()).collect(joining("\n"))); 560 } 561 562 List<Path> classes = new ArrayList<>(); 563 Files.walkFileTree( 564 out, 565 new SimpleFileVisitor<Path>() { 566 @Override 567 public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) 568 throws IOException { 569 if (getFileExtension(path).equals("class")) { 570 classes.add(path); 571 } 572 return FileVisitResult.CONTINUE; 573 } 574 }); 575 Map<String, byte[]> result = new LinkedHashMap<>(); 576 for (Path path : classes) { 577 String r = out.relativize(path).toString(); 578 result.put(r.substring(0, r.length() - ".class".length()), Files.readAllBytes(path)); 579 } 580 return result; 581 } 582 setupJavac( Map<String, String> sources, Collection<Path> classpath, ImmutableList<String> options, DiagnosticCollector<JavaFileObject> collector, FileSystem fs, Path out)583 private static JavacTask setupJavac( 584 Map<String, String> sources, 585 Collection<Path> classpath, 586 ImmutableList<String> options, 587 DiagnosticCollector<JavaFileObject> collector, 588 FileSystem fs, 589 Path out) 590 throws IOException { 591 Path srcs = fs.getPath("srcs"); 592 593 Files.createDirectories(out); 594 595 ArrayList<Path> inputs = new ArrayList<>(); 596 for (Map.Entry<String, String> entry : sources.entrySet()) { 597 Path path = srcs.resolve(entry.getKey()); 598 if (path.getParent() != null) { 599 Files.createDirectories(path.getParent()); 600 } 601 MoreFiles.asCharSink(path, UTF_8).write(entry.getValue()); 602 inputs.add(path); 603 } 604 605 JavacTool compiler = JavacTool.create(); 606 JavacFileManager fileManager = new JavacFileManager(new Context(), true, UTF_8); 607 fileManager.setLocationFromPaths(StandardLocation.CLASS_OUTPUT, ImmutableList.of(out)); 608 fileManager.setLocationFromPaths(StandardLocation.CLASS_PATH, classpath); 609 fileManager.setLocationFromPaths(StandardLocation.locationFor("MODULE_PATH"), classpath); 610 if (inputs.stream() 611 .filter(i -> requireNonNull(i.getFileName()).toString().equals("module-info.java")) 612 .count() 613 > 1) { 614 // multi-module mode 615 fileManager.setLocationFromPaths( 616 StandardLocation.locationFor("MODULE_SOURCE_PATH"), ImmutableList.of(srcs)); 617 } 618 619 return compiler.getTask( 620 new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.err, UTF_8)), true), 621 fileManager, 622 collector, 623 options, 624 ImmutableList.of(), 625 fileManager.getJavaFileObjectsFromPaths(inputs)); 626 } 627 628 /** Normalizes and stringifies a collection of class files. */ dump(Map<String, byte[]> compiled)629 public static String dump(Map<String, byte[]> compiled) throws Exception { 630 StringBuilder sb = new StringBuilder(); 631 List<String> keys = new ArrayList<>(compiled.keySet()); 632 Collections.sort(keys); 633 for (String key : keys) { 634 String na = key; 635 if (na.startsWith("/")) { 636 na = na.substring(1); 637 } 638 sb.append(String.format("=== %s ===\n", na)); 639 sb.append(AsmUtils.textify(compiled.get(key), /* skipDebug= */ true)); 640 } 641 return sb.toString(); 642 } 643 644 public static class TestInput { 645 646 public final Map<String, String> sources; 647 public final Map<String, String> classes; 648 TestInput(Map<String, String> sources, Map<String, String> classes)649 public TestInput(Map<String, String> sources, Map<String, String> classes) { 650 this.sources = sources; 651 this.classes = classes; 652 } 653 parse(String text)654 public static TestInput parse(String text) { 655 Map<String, String> sources = new LinkedHashMap<>(); 656 Map<String, String> classes = new LinkedHashMap<>(); 657 String className = null; 658 String sourceName = null; 659 List<String> lines = new ArrayList<>(); 660 for (String line : Splitter.on('\n').split(text)) { 661 if (line.startsWith("===")) { 662 if (sourceName != null) { 663 sources.put(sourceName, Joiner.on('\n').join(lines) + "\n"); 664 } 665 if (className != null) { 666 classes.put(className, Joiner.on('\n').join(lines) + "\n"); 667 } 668 lines.clear(); 669 sourceName = line.substring(3, line.length() - 3).trim(); 670 className = null; 671 } else if (line.startsWith("%%%")) { 672 if (className != null) { 673 classes.put(className, Joiner.on('\n').join(lines) + "\n"); 674 } 675 if (sourceName != null) { 676 sources.put(sourceName, Joiner.on('\n').join(lines) + "\n"); 677 } 678 className = line.substring(3, line.length() - 3).trim(); 679 lines.clear(); 680 sourceName = null; 681 } else { 682 lines.add(line); 683 } 684 } 685 if (sourceName != null) { 686 sources.put(sourceName, Joiner.on('\n').join(lines) + "\n"); 687 } 688 if (className != null) { 689 classes.put(className, Joiner.on('\n').join(lines) + "\n"); 690 } 691 lines.clear(); 692 return new TestInput(sources, classes); 693 } 694 } 695 getMajor()696 public static int getMajor() { 697 return Runtime.version().feature(); 698 } 699 IntegrationTestSupport()700 private IntegrationTestSupport() {} 701 } 702