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