1 /* 2 * Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.sun.tools.javac.api; 27 28 import java.io.PrintStream; 29 import java.io.Writer; 30 import java.util.ArrayDeque; 31 import java.util.ArrayList; 32 import java.util.Collection; 33 import java.util.HashMap; 34 import java.util.HashSet; 35 import java.util.List; 36 import java.util.Map; 37 import java.util.Queue; 38 import java.util.Set; 39 import java.util.stream.Collectors; 40 import java.util.stream.StreamSupport; 41 42 import javax.tools.Diagnostic; 43 import javax.tools.DiagnosticListener; 44 import javax.tools.JavaFileManager; 45 import javax.tools.JavaFileObject; 46 47 import com.sun.source.tree.ClassTree; 48 import com.sun.source.tree.CompilationUnitTree; 49 import com.sun.source.tree.Tree; 50 import com.sun.source.util.JavacTask; 51 import com.sun.source.util.TaskEvent; 52 import com.sun.source.util.TaskEvent.Kind; 53 import com.sun.source.util.TaskListener; 54 import com.sun.source.util.TreeScanner; 55 import com.sun.tools.javac.code.Kinds; 56 import com.sun.tools.javac.code.Preview; 57 import com.sun.tools.javac.code.Symbol; 58 import com.sun.tools.javac.code.Symtab; 59 import com.sun.tools.javac.code.Type; 60 import com.sun.tools.javac.code.Type.ClassType; 61 import com.sun.tools.javac.code.TypeTag; 62 import com.sun.tools.javac.code.Types; 63 import com.sun.tools.javac.comp.Annotate; 64 import com.sun.tools.javac.comp.Check; 65 import com.sun.tools.javac.comp.CompileStates; 66 import com.sun.tools.javac.comp.Enter; 67 import com.sun.tools.javac.comp.Modules; 68 import com.sun.tools.javac.main.Arguments; 69 import com.sun.tools.javac.main.JavaCompiler; 70 import com.sun.tools.javac.model.JavacElements; 71 import com.sun.tools.javac.tree.JCTree.JCClassDecl; 72 import com.sun.tools.javac.tree.JCTree.LetExpr; 73 import com.sun.tools.javac.util.Context; 74 import com.sun.tools.javac.util.DefinedBy; 75 import com.sun.tools.javac.util.DefinedBy.Api; 76 import com.sun.tools.javac.util.Log; 77 78 /** 79 * A pool of reusable JavacTasks. When a task is no valid anymore, it is returned to the pool, 80 * and its Context may be reused for future processing in some cases. The reuse is achieved 81 * by replacing some components (most notably JavaCompiler and Log) with reusable counterparts, 82 * and by cleaning up leftovers from previous compilation. 83 * <p> 84 * For each combination of options, a separate task/context is created and kept, as most option 85 * values are cached inside components themselves. 86 * <p> 87 * When the compilation redefines sensitive classes (e.g. classes in the the java.* packages), the 88 * task/context is not reused. 89 * <p> 90 * When the task is reused, then packages that were already listed won't be listed again. 91 * <p> 92 * Care must be taken to only return tasks that won't be used by the original caller. 93 * <p> 94 * Care must also be taken when custom components are installed, as those are not cleaned when the 95 * task/context is reused, and subsequent getTask may return a task based on a context with these 96 * custom components. 97 * 98 * <p><b>This is NOT part of any supported API. 99 * If you write code that depends on this, you do so at your own risk. 100 * This code and its internal interfaces are subject to change or 101 * deletion without notice.</b> 102 */ 103 public class JavacTaskPool { 104 105 private static final JavacTool systemProvider = JavacTool.create(); 106 private static final Queue<ReusableContext> EMPTY_QUEUE = new ArrayDeque<>(0); 107 108 private final int maxPoolSize; 109 private final Map<List<String>, Queue<ReusableContext>> options2Contexts = new HashMap<>(); 110 private int id; 111 112 private int statReused = 0; 113 private int statNew = 0; 114 private int statPolluted = 0; 115 private int statRemoved = 0; 116 117 /**Creates the pool. 118 * 119 * @param maxPoolSize maximum number of tasks/context that will be kept in the pool. 120 */ 121 public JavacTaskPool(int maxPoolSize) { 122 this.maxPoolSize = maxPoolSize; 123 } 124 125 /**Creates a new task as if by {@link javax.tools.JavaCompiler#getTask} and runs the provided 126 * worker with it. The task is only valid while the worker is running. The internal structures 127 * may be reused from some previous compilation. 128 * 129 * @param out a Writer for additional output from the compiler; 130 * use {@code System.err} if {@code null} 131 * @param fileManager a file manager; if {@code null} use the 132 * compiler's standard file manager 133 * @param diagnosticListener a diagnostic listener; if {@code 134 * null} use the compiler's default method for reporting 135 * diagnostics 136 * @param options compiler options, {@code null} means no options 137 * @param classes names of classes to be processed by annotation 138 * processing, {@code null} means no class names 139 * @param compilationUnits the compilation units to compile, {@code 140 * null} means no compilation units 141 * @param worker that should be run with the task 142 * @return an object representing the compilation 143 * @throws RuntimeException if an unrecoverable error 144 * occurred in a user supplied component. The 145 * {@linkplain Throwable#getCause() cause} will be the error in 146 * user code. 147 * @throws IllegalArgumentException if any of the options are invalid, 148 * or if any of the given compilation units are of other kind than 149 * {@linkplain JavaFileObject.Kind#SOURCE source} 150 */ 151 public <Z> Z getTask(Writer out, 152 JavaFileManager fileManager, 153 DiagnosticListener<? super JavaFileObject> diagnosticListener, 154 Iterable<String> options, 155 Iterable<String> classes, 156 Iterable<? extends JavaFileObject> compilationUnits, 157 Worker<Z> worker) { 158 List<String> opts = 159 StreamSupport.stream(options.spliterator(), false) 160 .collect(Collectors.toCollection(ArrayList::new)); 161 162 ReusableContext ctx; 163 164 synchronized (this) { 165 Queue<ReusableContext> cached = 166 options2Contexts.getOrDefault(opts, EMPTY_QUEUE); 167 168 if (cached.isEmpty()) { 169 ctx = new ReusableContext(opts); 170 statNew++; 171 } else { 172 ctx = cached.remove(); 173 statReused++; 174 } 175 } 176 177 ctx.useCount++; 178 179 JavacTaskImpl task = 180 (JavacTaskImpl) systemProvider.getTask(out, fileManager, diagnosticListener, 181 opts, classes, compilationUnits, ctx); 182 183 task.addTaskListener(ctx); 184 185 Z result = worker.withTask(task); 186 187 //not returning the context to the pool if task crashes with an exception 188 //the task/context may be in a broken state 189 ctx.clear(); 190 if (ctx.polluted) { 191 statPolluted++; 192 } else { 193 task.cleanup(); 194 synchronized (this) { 195 while (cacheSize() + 1 > maxPoolSize) { 196 ReusableContext toRemove = 197 options2Contexts.values() 198 .stream() 199 .flatMap(Collection::stream) 200 .sorted((c1, c2) -> c1.timeStamp < c2.timeStamp ? -1 : 1) 201 .findFirst() 202 .get(); 203 options2Contexts.get(toRemove.arguments).remove(toRemove); 204 statRemoved++; 205 } 206 options2Contexts.computeIfAbsent(ctx.arguments, x -> new ArrayDeque<>()).add(ctx); 207 ctx.timeStamp = id++; 208 } 209 } 210 211 return result; 212 } 213 //where: 214 private long cacheSize() { 215 return options2Contexts.values().stream().flatMap(Collection::stream).count(); 216 } 217 218 public void printStatistics(PrintStream out) { 219 out.println(statReused + " reused Contexts"); 220 out.println(statNew + " newly created Contexts"); 221 out.println(statPolluted + " polluted Contexts"); 222 out.println(statRemoved + " removed Contexts"); 223 } 224 225 public interface Worker<Z> { 226 public Z withTask(JavacTask task); 227 } 228 229 static class ReusableContext extends Context implements TaskListener { 230 231 Set<CompilationUnitTree> roots = new HashSet<>(); 232 233 List<String> arguments; 234 boolean polluted = false; 235 236 int useCount; 237 long timeStamp; 238 239 ReusableContext(List<String> arguments) { 240 super(); 241 this.arguments = arguments; 242 put(Log.logKey, ReusableLog.factory); 243 put(JavaCompiler.compilerKey, ReusableJavaCompiler.factory); 244 } 245 246 void clear() { 247 drop(Arguments.argsKey); 248 drop(DiagnosticListener.class); 249 drop(Log.outKey); 250 drop(Log.errKey); 251 drop(JavaFileManager.class); 252 drop(JavacTask.class); 253 drop(JavacTrees.class); 254 drop(JavacElements.class); 255 256 if (ht.get(Log.logKey) instanceof ReusableLog) { 257 //log already inited - not first round 258 ((ReusableLog)Log.instance(this)).clear(); 259 Enter.instance(this).newRound(); 260 ((ReusableJavaCompiler)ReusableJavaCompiler.instance(this)).clear(); 261 Types.instance(this).newRound(); 262 Check.instance(this).newRound(); 263 Check.instance(this).clear(); //clear mandatory warning handlers 264 Preview.instance(this).clear(); //clear mandatory warning handlers 265 Modules.instance(this).newRound(); 266 Annotate.instance(this).newRound(); 267 CompileStates.instance(this).clear(); 268 MultiTaskListener.instance(this).clear(); 269 270 //find if any of the roots have redefined java.* classes 271 Symtab syms = Symtab.instance(this); 272 pollutionScanner.scan(roots, syms); 273 roots.clear(); 274 } 275 } 276 277 /** 278 * This scanner detects as to whether the shared context has been polluted. This happens 279 * whenever a compiled program redefines a core class (in 'java.*' package) or when 280 * (typically because of cyclic inheritance) the symbol kind of a core class has been touched. 281 */ 282 TreeScanner<Void, Symtab> pollutionScanner = new TreeScanner<Void, Symtab>() { 283 @Override @DefinedBy(Api.COMPILER_TREE) 284 public Void scan(Tree tree, Symtab syms) { 285 if (tree instanceof LetExpr) { 286 LetExpr le = (LetExpr) tree; 287 scan(le.defs, syms); 288 scan(le.expr, syms); 289 return null; 290 } else { 291 return super.scan(tree, syms); 292 } 293 } 294 295 @Override @DefinedBy(Api.COMPILER_TREE) 296 public Void visitClass(ClassTree node, Symtab syms) { 297 Symbol sym = ((JCClassDecl)node).sym; 298 if (sym != null) { 299 syms.removeClass(sym.packge().modle, sym.flatName()); 300 Type sup = supertype(sym); 301 if (isCoreClass(sym) || 302 (sup != null && isCoreClass(sup.tsym) && sup.tsym.kind != Kinds.Kind.TYP)) { 303 polluted = true; 304 } 305 } 306 return super.visitClass(node, syms); 307 } 308 309 private boolean isCoreClass(Symbol s) { 310 return s.flatName().toString().startsWith("java."); 311 } 312 313 private Type supertype(Symbol s) { 314 if (s.type == null || 315 !s.type.hasTag(TypeTag.CLASS)) { 316 return null; 317 } else { 318 ClassType ct = (ClassType)s.type; 319 return ct.supertype_field; 320 } 321 } 322 }; 323 324 @Override @DefinedBy(Api.COMPILER_TREE) 325 public void finished(TaskEvent e) { 326 if (e.getKind() == Kind.PARSE) { 327 roots.add(e.getCompilationUnit()); 328 } 329 } 330 331 @Override @DefinedBy(Api.COMPILER_TREE) 332 public void started(TaskEvent e) { 333 //do nothing 334 } 335 336 <T> void drop(Key<T> k) { 337 ht.remove(k); 338 } 339 340 <T> void drop(Class<T> c) { 341 ht.remove(key(c)); 342 } 343 344 /** 345 * Reusable JavaCompiler; exposes a method to clean up the component from leftovers associated with 346 * previous compilations. 347 */ 348 static class ReusableJavaCompiler extends JavaCompiler { 349 350 final static Factory<JavaCompiler> factory = ReusableJavaCompiler::new; 351 352 ReusableJavaCompiler(Context context) { 353 super(context); 354 } 355 356 @Override 357 public void close() { 358 //do nothing 359 } 360 361 void clear() { 362 newRound(); 363 } 364 365 @Override 366 protected void checkReusable() { 367 //do nothing - it's ok to reuse the compiler 368 } 369 } 370 371 /** 372 * Reusable Log; exposes a method to clean up the component from leftovers associated with 373 * previous compilations. 374 */ 375 static class ReusableLog extends Log { 376 377 final static Factory<Log> factory = ReusableLog::new; 378 379 Context context; 380 381 ReusableLog(Context context) { 382 super(context); 383 this.context = context; 384 } 385 386 void clear() { 387 recorded.clear(); 388 sourceMap.clear(); 389 nerrors = 0; 390 nwarnings = 0; 391 //Set a fake listener that will lazily lookup the context for the 'real' listener. Since 392 //this field is never updated when a new task is created, we cannot simply reset the field 393 //or keep old value. This is a hack to workaround the limitations in the current infrastructure. 394 diagListener = new DiagnosticListener<JavaFileObject>() { 395 DiagnosticListener<JavaFileObject> cachedListener; 396 397 @Override @DefinedBy(Api.COMPILER) 398 @SuppressWarnings("unchecked") 399 public void report(Diagnostic<? extends JavaFileObject> diagnostic) { 400 if (cachedListener == null) { 401 cachedListener = context.get(DiagnosticListener.class); 402 } 403 cachedListener.report(diagnostic); 404 } 405 }; 406 } 407 } 408 } 409 }