• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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