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