ReusableContext.java revision 3294:9adfb22ff08f
1/* 2 * Copyright (c) 2015, 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. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24package combo; 25 26import com.sun.source.tree.ClassTree; 27import com.sun.source.tree.CompilationUnitTree; 28import com.sun.source.util.JavacTask; 29import com.sun.source.util.TaskEvent; 30import com.sun.source.util.TaskEvent.Kind; 31import com.sun.source.util.TaskListener; 32import com.sun.source.util.TreeScanner; 33import com.sun.tools.javac.api.MultiTaskListener; 34import com.sun.tools.javac.code.Kinds; 35import com.sun.tools.javac.code.Symbol; 36import com.sun.tools.javac.code.Symtab; 37import com.sun.tools.javac.code.Type; 38import com.sun.tools.javac.code.Type.ClassType; 39import com.sun.tools.javac.code.TypeTag; 40import com.sun.tools.javac.code.Types; 41import com.sun.tools.javac.comp.Check; 42import com.sun.tools.javac.comp.CompileStates; 43import com.sun.tools.javac.comp.Enter; 44import com.sun.tools.javac.main.Arguments; 45import com.sun.tools.javac.main.JavaCompiler; 46import com.sun.tools.javac.tree.JCTree.JCClassDecl; 47import com.sun.tools.javac.util.Context; 48import com.sun.tools.javac.util.Log; 49 50import javax.tools.Diagnostic; 51import javax.tools.DiagnosticListener; 52import javax.tools.JavaFileManager; 53import javax.tools.JavaFileObject; 54import java.util.HashSet; 55import java.util.Set; 56 57/** 58 * A reusable context is a context that can be used safely across multiple compilation rounds 59 * arising from execution of a combo test. It achieves reuse by replacing some components 60 * (most notably JavaCompiler and Log) with reusable counterparts, and by exposing a method 61 * to cleanup leftovers from previous compilation. 62 * <p> 63 * There are, however, situations in which reusing the context is not safe: (i) when different 64 * compilations are using different sets of compiler options (as most option values are cached 65 * inside components themselves) and (ii) when the compilation unit happens to redefine classes 66 * in the java.* packages. 67 */ 68class ReusableContext extends Context implements TaskListener { 69 70 Set<CompilationUnitTree> roots = new HashSet<>(); 71 72 String opts; 73 boolean polluted = false; 74 75 ReusableContext() { 76 super(); 77 put(Log.logKey, ReusableLog.factory); 78 put(JavaCompiler.compilerKey, ReusableJavaCompiler.factory); 79 } 80 81 void clear() { 82 drop(Arguments.argsKey); 83 drop(DiagnosticListener.class); 84 drop(Log.outKey); 85 drop(JavaFileManager.class); 86 drop(JavacTask.class); 87 88 if (ht.get(Log.logKey) instanceof ReusableLog) { 89 //log already inited - not first round 90 ((ReusableLog)Log.instance(this)).clear(); 91 Enter.instance(this).newRound(); 92 ((ReusableJavaCompiler)ReusableJavaCompiler.instance(this)).clear(); 93 Types.instance(this).newRound(); 94 Check.instance(this).newRound(); 95 CompileStates.instance(this).clear(); 96 MultiTaskListener.instance(this).clear(); 97 98 //find if any of the roots have redefined java.* classes 99 Symtab syms = Symtab.instance(this); 100 pollutionScanner.scan(roots, syms); 101 roots.clear(); 102 } 103 } 104 105 /** 106 * This scanner detects as to whether the shared context has been polluted. This happens 107 * whenever a compiled program redefines a core class (in 'java.*' package) or when 108 * (typically because of cyclic inheritance) the symbol kind of a core class has been touched. 109 */ 110 TreeScanner<Void, Symtab> pollutionScanner = new TreeScanner<Void, Symtab>() { 111 @Override 112 public Void visitClass(ClassTree node, Symtab syms) { 113 Symbol sym = ((JCClassDecl)node).sym; 114 if (sym != null) { 115 syms.removeClass(sym.packge().modle, sym.flatName()); 116 Type sup = supertype(sym); 117 if (isCoreClass(sym) || 118 (sup != null && isCoreClass(sup.tsym) && sup.tsym.kind != Kinds.Kind.TYP)) { 119 polluted = true; 120 } 121 } 122 return super.visitClass(node, syms); 123 } 124 125 private boolean isCoreClass(Symbol s) { 126 return s.flatName().toString().startsWith("java."); 127 } 128 129 private Type supertype(Symbol s) { 130 if (s.type == null || 131 !s.type.hasTag(TypeTag.CLASS)) { 132 return null; 133 } else { 134 ClassType ct = (ClassType)s.type; 135 return ct.supertype_field; 136 } 137 } 138 }; 139 140 @Override 141 public void finished(TaskEvent e) { 142 if (e.getKind() == Kind.PARSE) { 143 roots.add(e.getCompilationUnit()); 144 } 145 } 146 147 @Override 148 public void started(TaskEvent e) { 149 //do nothing 150 } 151 152 <T> void drop(Key<T> k) { 153 ht.remove(k); 154 } 155 156 <T> void drop(Class<T> c) { 157 ht.remove(key(c)); 158 } 159 160 /** 161 * Reusable JavaCompiler; exposes a method to clean up the component from leftovers associated with 162 * previous compilations. 163 */ 164 static class ReusableJavaCompiler extends JavaCompiler { 165 166 static Factory<JavaCompiler> factory = ReusableJavaCompiler::new; 167 168 ReusableJavaCompiler(Context context) { 169 super(context); 170 } 171 172 @Override 173 public void close() { 174 //do nothing 175 } 176 177 void clear() { 178 newRound(); 179 } 180 181 @Override 182 protected void checkReusable() { 183 //do nothing - it's ok to reuse the compiler 184 } 185 } 186 187 /** 188 * Reusable Log; exposes a method to clean up the component from leftovers associated with 189 * previous compilations. 190 */ 191 static class ReusableLog extends Log { 192 193 static Factory<Log> factory = ReusableLog::new; 194 195 Context context; 196 197 ReusableLog(Context context) { 198 super(context); 199 this.context = context; 200 } 201 202 void clear() { 203 recorded.clear(); 204 sourceMap.clear(); 205 nerrors = 0; 206 nwarnings = 0; 207 //Set a fake listener that will lazily lookup the context for the 'real' listener. Since 208 //this field is never updated when a new task is created, we cannot simply reset the field 209 //or keep old value. This is a hack to workaround the limitations in the current infrastructure. 210 diagListener = new DiagnosticListener<JavaFileObject>() { 211 DiagnosticListener<JavaFileObject> cachedListener; 212 213 @Override 214 @SuppressWarnings("unchecked") 215 public void report(Diagnostic<? extends JavaFileObject> diagnostic) { 216 if (cachedListener == null) { 217 cachedListener = context.get(DiagnosticListener.class); 218 } 219 cachedListener.report(diagnostic); 220 } 221 }; 222 } 223 } 224} 225