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 }