• 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.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