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