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