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