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