• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2019 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.binder;
18 
19 import static java.util.Objects.requireNonNull;
20 
21 import com.google.auto.value.AutoValue;
22 import com.google.common.base.Function;
23 import com.google.common.base.Joiner;
24 import com.google.common.base.Stopwatch;
25 import com.google.common.base.Supplier;
26 import com.google.common.base.Throwables;
27 import com.google.common.collect.ImmutableList;
28 import com.google.common.collect.ImmutableMap;
29 import com.google.common.collect.ImmutableSet;
30 import com.google.common.collect.ImmutableSetMultimap;
31 import com.google.common.collect.Sets;
32 import com.google.turbine.binder.Binder.BindingResult;
33 import com.google.turbine.binder.Binder.Statistics;
34 import com.google.turbine.binder.bound.SourceTypeBoundClass;
35 import com.google.turbine.binder.bound.TypeBoundClass;
36 import com.google.turbine.binder.bound.TypeBoundClass.FieldInfo;
37 import com.google.turbine.binder.env.CompoundEnv;
38 import com.google.turbine.binder.env.Env;
39 import com.google.turbine.binder.env.SimpleEnv;
40 import com.google.turbine.binder.sym.ClassSymbol;
41 import com.google.turbine.binder.sym.Symbol;
42 import com.google.turbine.diag.SourceFile;
43 import com.google.turbine.diag.TurbineLog;
44 import com.google.turbine.parse.Parser;
45 import com.google.turbine.processing.ModelFactory;
46 import com.google.turbine.processing.TurbineElements;
47 import com.google.turbine.processing.TurbineFiler;
48 import com.google.turbine.processing.TurbineMessager;
49 import com.google.turbine.processing.TurbineProcessingEnvironment;
50 import com.google.turbine.processing.TurbineRoundEnvironment;
51 import com.google.turbine.processing.TurbineTypes;
52 import com.google.turbine.tree.Tree.CompUnit;
53 import com.google.turbine.type.AnnoInfo;
54 import java.net.MalformedURLException;
55 import java.net.URL;
56 import java.net.URLClassLoader;
57 import java.nio.file.Paths;
58 import java.time.Duration;
59 import java.util.ArrayList;
60 import java.util.Collection;
61 import java.util.HashSet;
62 import java.util.LinkedHashMap;
63 import java.util.LinkedHashSet;
64 import java.util.List;
65 import java.util.Map;
66 import java.util.Objects;
67 import java.util.Optional;
68 import java.util.Set;
69 import java.util.regex.Pattern;
70 import javax.annotation.processing.Processor;
71 import javax.lang.model.SourceVersion;
72 import javax.lang.model.element.TypeElement;
73 import javax.tools.Diagnostic;
74 import org.jspecify.nullness.Nullable;
75 
76 /** Top level annotation processing logic, see also {@link Binder}. */
77 public class Processing {
78 
process( TurbineLog log, final ImmutableList<CompUnit> initialSources, final ClassPath classpath, ProcessorInfo processorInfo, ClassPath bootclasspath, BindingResult result, Optional<String> moduleVersion)79   static @Nullable BindingResult process(
80       TurbineLog log,
81       final ImmutableList<CompUnit> initialSources,
82       final ClassPath classpath,
83       ProcessorInfo processorInfo,
84       ClassPath bootclasspath,
85       BindingResult result,
86       Optional<String> moduleVersion) {
87 
88     Set<String> seen = new HashSet<>();
89     for (CompUnit u : initialSources) {
90       if (u.source() != null) {
91         seen.add(u.source().path());
92       }
93     }
94 
95     TurbineFiler filer =
96         new TurbineFiler(
97             seen,
98             new Function<String, @Nullable Supplier<byte[]>>() {
99               @Override
100               public @Nullable Supplier<byte[]> apply(String input) {
101                 // TODO(cushon): should annotation processors be allowed to generate code with
102                 // dependencies between source and bytecode, or vice versa?
103                 // Currently generated classes are not available on the classpath when compiling
104                 // the compilation sources (including generated sources).
105                 return classpath.resource(input);
106               }
107             },
108             processorInfo.loader());
109 
110     Env<ClassSymbol, SourceTypeBoundClass> tenv = new SimpleEnv<>(result.units());
111     CompoundEnv<ClassSymbol, TypeBoundClass> env =
112         CompoundEnv.<ClassSymbol, TypeBoundClass>of(result.classPathEnv()).append(tenv);
113     ModelFactory factory = new ModelFactory(env, processorInfo.loader(), result.tli());
114 
115     Map<String, byte[]> statistics = new LinkedHashMap<>();
116 
117     TurbineTypes turbineTypes = new TurbineTypes(factory);
118     TurbineProcessingEnvironment processingEnv =
119         new TurbineProcessingEnvironment(
120             filer,
121             turbineTypes,
122             new TurbineElements(factory, turbineTypes),
123             new TurbineMessager(factory, log),
124             processorInfo.options(),
125             processorInfo.sourceVersion(),
126             processorInfo.loader(),
127             statistics);
128     Timers timers = new Timers();
129     for (Processor processor : processorInfo.processors()) {
130       try (Timers.Timer unused = timers.start(processor)) {
131         processor.init(processingEnv);
132       } catch (Throwable t) {
133         logProcessorCrash(log, processor, t);
134         return null;
135       }
136     }
137 
138     ImmutableMap<Processor, SupportedAnnotationTypes> wanted =
139         initializeSupportedAnnotationTypes(processorInfo);
140 
141     Set<ClassSymbol> allSymbols = new HashSet<>();
142 
143     ImmutableList.Builder<CompUnit> units =
144         ImmutableList.<CompUnit>builder().addAll(initialSources);
145 
146     Set<Processor> toRun = new LinkedHashSet<>();
147 
148     boolean errorRaised = false;
149 
150     while (true) {
151       ImmutableSet<ClassSymbol> syms =
152           Sets.difference(result.units().keySet(), allSymbols).immutableCopy();
153       allSymbols.addAll(syms);
154       if (syms.isEmpty()) {
155         break;
156       }
157       ImmutableSetMultimap<ClassSymbol, Symbol> allAnnotations = getAllAnnotations(env, syms);
158       TurbineRoundEnvironment roundEnv = null;
159       for (Map.Entry<Processor, SupportedAnnotationTypes> e : wanted.entrySet()) {
160         Processor processor = e.getKey();
161         SupportedAnnotationTypes supportedAnnotationTypes = e.getValue();
162         Set<TypeElement> annotations = new HashSet<>();
163         boolean run = supportedAnnotationTypes.everything() || toRun.contains(processor);
164         for (ClassSymbol a : allAnnotations.keys()) {
165           if (supportedAnnotationTypes.everything()
166               || supportedAnnotationTypes.pattern().matcher(a.toString()).matches()) {
167             annotations.add(factory.typeElement(a));
168             run = true;
169           }
170         }
171         if (run) {
172           toRun.add(processor);
173           if (roundEnv == null) {
174             roundEnv =
175                 new TurbineRoundEnvironment(factory, syms, false, errorRaised, allAnnotations);
176           }
177           try (Timers.Timer unused = timers.start(processor)) {
178             // discard the result of Processor#process because 'claiming' annotations is a bad idea
179             // TODO(cushon): consider disallowing this, or reporting a diagnostic
180             processor.process(annotations, roundEnv);
181           } catch (Throwable t) {
182             logProcessorCrash(log, processor, t);
183             return null;
184           }
185         }
186       }
187       Collection<SourceFile> files = filer.finishRound();
188       if (files.isEmpty()) {
189         break;
190       }
191       for (SourceFile file : files) {
192         units.add(Parser.parse(file));
193       }
194       errorRaised = log.errorRaised();
195       if (errorRaised) {
196         break;
197       }
198       log.clear();
199       result =
200           Binder.bind(
201               log,
202               units.build(),
203               filer.generatedSources(),
204               filer.generatedClasses(),
205               classpath,
206               bootclasspath,
207               moduleVersion);
208       tenv = new SimpleEnv<>(result.units());
209       env = CompoundEnv.<ClassSymbol, TypeBoundClass>of(result.classPathEnv()).append(tenv);
210       factory.round(env, result.tli());
211     }
212 
213     TurbineRoundEnvironment roundEnv = null;
214     for (Processor processor : toRun) {
215       if (roundEnv == null) {
216         roundEnv =
217             new TurbineRoundEnvironment(
218                 factory,
219                 ImmutableSet.of(),
220                 /* processingOver= */ true,
221                 errorRaised,
222                 ImmutableSetMultimap.of());
223       }
224       try (Timers.Timer unused = timers.start(processor)) {
225         processor.process(ImmutableSet.of(), roundEnv);
226       } catch (Throwable t) {
227         logProcessorCrash(log, processor, t);
228         return null;
229       }
230     }
231 
232     Collection<SourceFile> files = filer.finishRound();
233     if (!files.isEmpty()) {
234       // processors aren't supposed to generate sources on the final processing round, but javac
235       // tolerates it anyway
236       // TODO(cushon): consider disallowing this, or reporting a diagnostic
237       for (SourceFile file : files) {
238         units.add(Parser.parse(file));
239       }
240       result =
241           Binder.bind(
242               log,
243               units.build(),
244               filer.generatedSources(),
245               filer.generatedClasses(),
246               classpath,
247               bootclasspath,
248               moduleVersion);
249       if (log.anyErrors()) {
250         return null;
251       }
252     }
253 
254     if (!filer.generatedClasses().isEmpty()) {
255       // add any generated class files to the output
256       // TODO(cushon): consider handling generated classes after each round
257       result = result.withGeneratedClasses(filer.generatedClasses());
258     }
259     if (!filer.generatedSources().isEmpty()) {
260       result = result.withGeneratedSources(filer.generatedSources());
261     }
262 
263     result =
264         result.withStatistics(Statistics.create(timers.build(), ImmutableMap.copyOf(statistics)));
265 
266     return result;
267   }
268 
269   private static ImmutableMap<Processor, SupportedAnnotationTypes>
initializeSupportedAnnotationTypes(ProcessorInfo processorInfo)270       initializeSupportedAnnotationTypes(ProcessorInfo processorInfo) {
271     ImmutableMap.Builder<Processor, SupportedAnnotationTypes> result = ImmutableMap.builder();
272     for (Processor processor : processorInfo.processors()) {
273       result.put(processor, SupportedAnnotationTypes.create(processor));
274     }
275     return result.buildOrThrow();
276   }
277 
278   @AutoValue
279   abstract static class SupportedAnnotationTypes {
280 
everything()281     abstract boolean everything();
282 
pattern()283     abstract Pattern pattern();
284 
create(Processor processor)285     static SupportedAnnotationTypes create(Processor processor) {
286       List<String> patterns = new ArrayList<>();
287       boolean everything = false;
288       for (String supportedAnnotationType : processor.getSupportedAnnotationTypes()) {
289         if (supportedAnnotationType.equals("*")) {
290           everything = true;
291         } else {
292           // TODO(b/139026291): this handling of getSupportedAnnotationTypes isn't correct
293           patterns.add(supportedAnnotationType);
294         }
295       }
296       return new AutoValue_Processing_SupportedAnnotationTypes(
297           everything, Pattern.compile(Joiner.on('|').join(patterns)));
298     }
299   }
300 
logProcessorCrash(TurbineLog log, Processor processor, Throwable t)301   private static void logProcessorCrash(TurbineLog log, Processor processor, Throwable t) {
302     log.diagnostic(
303         Diagnostic.Kind.ERROR,
304         String.format(
305             "An exception occurred in %s:\n%s",
306             processor.getClass().getCanonicalName(), Throwables.getStackTraceAsString(t)));
307   }
308 
309   /** Returns a map from annotations present in the compilation to the annotated elements. */
getAllAnnotations( Env<ClassSymbol, TypeBoundClass> env, Iterable<ClassSymbol> syms)310   private static ImmutableSetMultimap<ClassSymbol, Symbol> getAllAnnotations(
311       Env<ClassSymbol, TypeBoundClass> env, Iterable<ClassSymbol> syms) {
312     ImmutableSetMultimap.Builder<ClassSymbol, Symbol> result = ImmutableSetMultimap.builder();
313     for (ClassSymbol sym : syms) {
314       TypeBoundClass info = env.getNonNull(sym);
315       for (AnnoInfo annoInfo : info.annotations()) {
316         if (sym.simpleName().equals("package-info")) {
317           addAnno(result, annoInfo, sym.owner());
318         } else {
319           addAnno(result, annoInfo, sym);
320         }
321       }
322       for (ClassSymbol inheritedAnno :
323           inheritedAnnotations(new HashSet<>(), info.superclass(), env)) {
324         result.put(inheritedAnno, sym);
325       }
326       for (TypeBoundClass.MethodInfo method : info.methods()) {
327         for (AnnoInfo annoInfo : method.annotations()) {
328           addAnno(result, annoInfo, method.sym());
329         }
330         for (TypeBoundClass.ParamInfo param : method.parameters()) {
331           for (AnnoInfo annoInfo : param.annotations()) {
332             addAnno(result, annoInfo, param.sym());
333           }
334         }
335       }
336       for (FieldInfo field : info.fields()) {
337         for (AnnoInfo annoInfo : field.annotations()) {
338           addAnno(result, annoInfo, field.sym());
339         }
340       }
341     }
342     return result.build();
343   }
344 
345   // TODO(cushon): consider memoizing this (or isAnnotationInherited) if they show up in profiles
inheritedAnnotations( Set<ClassSymbol> seen, @Nullable ClassSymbol sym, Env<ClassSymbol, TypeBoundClass> env)346   private static ImmutableSet<ClassSymbol> inheritedAnnotations(
347       Set<ClassSymbol> seen, @Nullable ClassSymbol sym, Env<ClassSymbol, TypeBoundClass> env) {
348     ImmutableSet.Builder<ClassSymbol> result = ImmutableSet.builder();
349     ClassSymbol curr = sym;
350     while (curr != null && seen.add(curr)) {
351       TypeBoundClass info = env.get(curr);
352       if (info == null) {
353         break;
354       }
355       for (AnnoInfo anno : info.annotations()) {
356         ClassSymbol annoSym = anno.sym();
357         if (annoSym == null) {
358           continue;
359         }
360         if (isAnnotationInherited(env, annoSym)) {
361           result.add(annoSym);
362         }
363       }
364       curr = info.superclass();
365     }
366     return result.build();
367   }
368 
isAnnotationInherited( Env<ClassSymbol, TypeBoundClass> env, ClassSymbol sym)369   private static boolean isAnnotationInherited(
370       Env<ClassSymbol, TypeBoundClass> env, ClassSymbol sym) {
371     TypeBoundClass annoInfo = env.get(sym);
372     if (annoInfo == null) {
373       return false;
374     }
375     for (AnnoInfo anno : annoInfo.annotations()) {
376       if (Objects.equals(anno.sym(), ClassSymbol.INHERITED)) {
377         return true;
378       }
379     }
380     return false;
381   }
382 
addAnno( ImmutableSetMultimap.Builder<ClassSymbol, Symbol> result, AnnoInfo annoInfo, Symbol owner)383   private static void addAnno(
384       ImmutableSetMultimap.Builder<ClassSymbol, Symbol> result, AnnoInfo annoInfo, Symbol owner) {
385     ClassSymbol sym = annoInfo.sym();
386     if (sym != null) {
387       result.put(sym, owner);
388     }
389   }
390 
initializeProcessors( SourceVersion sourceVersion, ImmutableList<String> javacopts, ImmutableSet<String> processorNames, ClassLoader processorLoader)391   public static ProcessorInfo initializeProcessors(
392       SourceVersion sourceVersion,
393       ImmutableList<String> javacopts,
394       ImmutableSet<String> processorNames,
395       ClassLoader processorLoader) {
396     if (processorNames.isEmpty() || javacopts.contains("-proc:none")) {
397       return ProcessorInfo.empty();
398     }
399     ImmutableList<Processor> processors = instantiateProcessors(processorNames, processorLoader);
400     ImmutableMap<String, String> processorOptions = processorOptions(javacopts);
401     return ProcessorInfo.create(processors, processorLoader, processorOptions, sourceVersion);
402   }
403 
instantiateProcessors( ImmutableSet<String> processorNames, ClassLoader processorLoader)404   private static ImmutableList<Processor> instantiateProcessors(
405       ImmutableSet<String> processorNames, ClassLoader processorLoader) {
406     ImmutableList.Builder<Processor> processors = ImmutableList.builder();
407     for (String processor : processorNames) {
408       try {
409         Class<? extends Processor> clazz =
410             Class.forName(processor, false, processorLoader).asSubclass(Processor.class);
411         processors.add(clazz.getConstructor().newInstance());
412       } catch (ReflectiveOperationException e) {
413         throw new LinkageError(e.getMessage(), e);
414       }
415     }
416     return processors.build();
417   }
418 
processorLoader( ImmutableList<String> processorPath, ImmutableSet<String> builtinProcessors)419   public static ClassLoader processorLoader(
420       ImmutableList<String> processorPath, ImmutableSet<String> builtinProcessors)
421       throws MalformedURLException {
422     if (processorPath.isEmpty()) {
423       return Processing.class.getClassLoader();
424     }
425     return new URLClassLoader(
426         toUrls(processorPath),
427         new ClassLoader(ClassLoader.getPlatformClassLoader()) {
428           @Override
429           protected Class<?> findClass(String name) throws ClassNotFoundException {
430             if (name.equals("com.google.turbine.processing.TurbineProcessingEnvironment")) {
431               return Class.forName(name);
432             }
433             if (!builtinProcessors.isEmpty()) {
434               if (name.startsWith("com.sun.source.")
435                   || name.startsWith("com.sun.tools.")
436                   || name.startsWith("com.google.common.collect.")
437                   || name.startsWith("com.google.common.base.")
438                   || name.startsWith("com.google.common.graph.")
439                   || name.startsWith("com.google.devtools.build.buildjar.javac.statistics.")
440                   || name.startsWith("dagger.model.")
441                   || name.startsWith("dagger.spi.")
442                   || builtinProcessors.contains(name)) {
443                 return Class.forName(name);
444               }
445             }
446             throw new ClassNotFoundException(name);
447           }
448         });
449   }
450 
451   private static URL[] toUrls(ImmutableList<String> processorPath) throws MalformedURLException {
452     URL[] urls = new URL[processorPath.size()];
453     int i = 0;
454     for (String path : processorPath) {
455       urls[i++] = Paths.get(path).toUri().toURL();
456     }
457     return urls;
458   }
459 
460   private static ImmutableMap<String, String> processorOptions(ImmutableList<String> javacopts) {
461     Map<String, String> result = new LinkedHashMap<>(); // ImmutableMap.Builder rejects duplicates
462     for (String javacopt : javacopts) {
463       if (javacopt.startsWith("-A")) {
464         javacopt = javacopt.substring("-A".length());
465         int idx = javacopt.indexOf('=');
466         String key;
467         String value;
468         if (idx != -1) {
469           key = javacopt.substring(0, idx);
470           value = javacopt.substring(idx + 1);
471         } else {
472           key = javacopt;
473           value = javacopt;
474         }
475         result.put(key, value);
476       }
477     }
478     return ImmutableMap.copyOf(result);
479   }
480 
481   /** Information about any annotation processing performed by this compilation. */
482   @AutoValue
483   public abstract static class ProcessorInfo {
484 
485     abstract ImmutableList<Processor> processors();
486 
487     /**
488      * The classloader to use for annotation processor implementations, and any annotations they
489      * access reflectively.
490      */
491     abstract @Nullable ClassLoader loader();
492 
493     /** Command line annotation processing options, passed to javac with {@code -Akey=value}. */
494     abstract ImmutableMap<String, String> options();
495 
496     public abstract SourceVersion sourceVersion();
497 
498     public static ProcessorInfo create(
499         ImmutableList<Processor> processors,
500         @Nullable ClassLoader loader,
501         ImmutableMap<String, String> options,
502         SourceVersion sourceVersion) {
503       return new AutoValue_Processing_ProcessorInfo(processors, loader, options, sourceVersion);
504     }
505 
506     static ProcessorInfo empty() {
507       return create(
508           /* processors= */ ImmutableList.of(),
509           /* loader= */ null,
510           /* options= */ ImmutableMap.of(),
511           /* sourceVersion= */ SourceVersion.latest());
512     }
513   }
514 
515   private static class Timers {
516     private final Map<Class<?>, Stopwatch> processorTimers = new LinkedHashMap<>();
517 
518     Timer start(Processor processor) {
519       Class<? extends Processor> clazz = processor.getClass();
520       Stopwatch sw = processorTimers.get(clazz);
521       if (sw == null) {
522         sw = Stopwatch.createUnstarted();
523         processorTimers.put(clazz, sw);
524       }
525       sw.start();
526       return new Timer(sw);
527     }
528 
529     private static class Timer implements AutoCloseable {
530 
531       private final Stopwatch sw;
532 
533       public Timer(Stopwatch sw) {
534         this.sw = sw;
535       }
536 
537       @Override
538       public void close() {
539         sw.stop();
540       }
541     }
542 
543     ImmutableMap<String, Duration> build() {
544       ImmutableMap.Builder<String, Duration> result = ImmutableMap.builder();
545       for (Map.Entry<Class<?>, Stopwatch> e : processorTimers.entrySet()) {
546         // requireNonNull is safe, barring bizarre processor implementations (e.g., anonymous class)
547         result.put(requireNonNull(e.getKey().getCanonicalName()), e.getValue().elapsed());
548       }
549       return result.buildOrThrow();
550     }
551   }
552 
553   private Processing() {}
554 }
555