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