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