001 package edu.rice.cs.cunit;
002
003 import edu.rice.cs.cunit.classFile.ClassFile;
004 import edu.rice.cs.cunit.classFile.ClassFileTools;
005 import edu.rice.cs.cunit.classFile.attributes.AAnnotationsAttributeInfo;
006 import edu.rice.cs.cunit.classFile.attributes.AAttributeInfo;
007 import edu.rice.cs.cunit.classFile.attributes.InstrumentationAttributeInfo;
008 import edu.rice.cs.cunit.classFile.attributes.RuntimeInvisibleAnnotationsAttributeInfo;
009 import edu.rice.cs.cunit.classFile.constantPool.APoolInfo;
010 import edu.rice.cs.cunit.classFile.constantPool.ClassPoolInfo;
011 import edu.rice.cs.cunit.classFile.constantPool.AUTFPoolInfo;
012 import edu.rice.cs.cunit.classFile.constantPool.visitors.ADefaultPoolInfoVisitor;
013 import edu.rice.cs.cunit.classFile.constantPool.visitors.CheckUTFVisitor;
014 import edu.rice.cs.cunit.instrumentors.DoNotInstrument;
015 import edu.rice.cs.cunit.instrumentors.IInstrumentationStrategy;
016 import edu.rice.cs.cunit.util.Debug;
017 import edu.rice.cs.cunit.util.FileOps;
018
019 import java.io.*;
020 import java.util.ArrayList;
021 import java.util.HashMap;
022 import java.util.List;
023
024 /**
025 * Class loader that instruments loaded classes by applying IInstrumentors to them.
026 *
027 * @author Mathias Ricken
028 */
029 public class InstrumentingClassLoader extends ClassLoader {
030 /**
031 * Should the instrumented classes be saved?
032 */
033 protected boolean _saveInstrumented = true;
034
035 /**
036 * Make backups of original classes?
037 */
038 protected boolean _makeBackups = false;
039
040 /**
041 * List of instrumentors that should be applied to a class.
042 */
043 protected ArrayList<IInstrumentationStrategy> _instrumentors = new ArrayList<IInstrumentationStrategy>();
044
045 /**
046 * Class path to consider when loading classes.
047 */
048 protected ArrayList<String> _classPath = new ArrayList<String>();
049
050 /**
051 * Hash map of classes already processed. Once processing of a class has begun, it is added with a value of null.
052 * When processing is done, this value is updated to the actual Class object.If a class has not been processed, it
053 * is not present.
054 */
055 protected HashMap<String, Class<?>> _processedClasses = new HashMap<String, Class<?>>();
056
057 /**
058 * Creates a new instrumenting class loader using specified instrumentors.
059 *
060 * @param instrumentors array of instrumentors
061 */
062 public InstrumentingClassLoader(IInstrumentationStrategy[] instrumentors) {
063 for(IInstrumentationStrategy itor : instrumentors) {
064 _instrumentors.add(itor);
065 }
066 String[] paths = System.getProperty("java.class.path").split(System.getProperty("path.separator"));
067 for(String path: paths) { _classPath.add(path); }
068 }
069
070 /**
071 * Accessor for the save instrumented flag
072 *
073 * @return flag
074 */
075 public boolean isSaveInstrumented() {
076 return _saveInstrumented;
077 }
078
079 /**
080 * Mutator for the save instrumented flag
081 *
082 * @param saveInstrumented new flag
083 */
084 public void setSaveInstrumented(boolean saveInstrumented) {
085 _saveInstrumented = saveInstrumented;
086 }
087
088 /**
089 * Mutator for the make backups flag
090 *
091 * @param makeBackups new flag
092 */
093 public void setMakeBackups(boolean makeBackups) {
094 _makeBackups = makeBackups;
095 }
096
097 /**
098 * Load and instrument the class with the specified name.
099 *
100 * @param name class name
101 * @param resolve true if class should be resolved
102 *
103 * @return Class instance for the loaded class
104 *
105 * @throws ClassNotFoundException
106 */
107 public Class loadClass(String name, boolean resolve)
108 throws ClassNotFoundException {
109 SyncPointBuffer.setRecording(true);
110 try {
111 ClassFile cf = null;
112 Class c;
113
114 Debug.out.print(name);
115 c = findLoadedClass(name);
116 if ((c == null) && (!_processedClasses.containsKey(name))) {
117 try {
118 _processedClasses.put(name, null);
119
120 // find class on class path
121 ClassFileTools.ClassLocation cl = ClassFileTools.findClassFile(name, _classPath);
122 if (cl!=null) {
123 boolean fromJAR = (cl.getJarFile()!=null);
124 File origFile = cl.getFile();
125 byte[] barr = null;
126 cf = cl.getClassFile();
127
128 try {
129 if (fromJAR) {
130 Debug.out.print(" from jar file "+cl.getJarFile().getName());
131 }
132 else {
133 Debug.out.print(" from file "+cl.getFile().getPath());
134 }
135
136 // go ahead and close the file now
137 try {
138 cl.close();
139 }
140 catch(IOException e) { /* ignore */ }
141 cl = null;
142
143 // find out if we should instrument this class, or if it has already been instrumented
144 boolean doInstrument = cf.getAttribute(InstrumentationAttributeInfo.NAME) == null;
145 if (doInstrument) {
146 String[] doNotInstrumentPatterns = new String[0];
147 AAttributeInfo attr = cf.getAttribute(
148 RuntimeInvisibleAnnotationsAttributeInfo.getAttributeName());
149 if (null != attr) {
150 RuntimeInvisibleAnnotationsAttributeInfo ann
151 = (RuntimeInvisibleAnnotationsAttributeInfo)attr;
152 for(AAnnotationsAttributeInfo.Annotation a : ann.getAnnotations()) {
153 String typeString = ClassFileTools.getTypeString(a.getType(), "");
154 if (typeString.substring(0, typeString.length() - 1).equals(
155 DoNotInstrument.class.getName())) {
156 for(AAnnotationsAttributeInfo.Annotation.NameValuePair nvp : a.getPairs()) {
157 if (nvp.getName().toString().equals("instrumentors")) {
158 AAnnotationsAttributeInfo.Annotation.ConstantMemberValue cmv = nvp.getValue().execute(
159 AAnnotationsAttributeInfo.Annotation.CheckConstantMemberVisitor.singleton(),null);
160 AUTFPoolInfo patternString = cmv.getConstValue().execute(
161 CheckUTFVisitor.singleton(), null);
162 doNotInstrumentPatterns = patternString.toString().split(";");
163 break;
164 }
165 }
166 break;
167 }
168 }
169 }
170 List<IInstrumentationStrategy> appliedInstrumentors
171 = new ArrayList<IInstrumentationStrategy>();
172 // apply all instrumentors
173 for(IInstrumentationStrategy itor : _instrumentors) {
174 if (!ClassFileTools.classNameMatches(itor.getClass().getName(),
175 doNotInstrumentPatterns)) {
176 itor.instrument(cf);
177 appliedInstrumentors.add(itor);
178 }
179 else {
180 Debug.out.println("Class " + cf.getThisClassName() + " not instrumented with "
181 + itor.getClass().getName()
182 + " because it matched @DoNotInstrument instrumentors");
183 }
184 }
185 InstrumentationAttributeInfo.addInstrumentationAttributeInfo(cf, appliedInstrumentors);
186 ByteArrayOutputStream baos = new ByteArrayOutputStream();
187 cf.write(baos);
188 barr = baos.toByteArray();
189
190 if (_saveInstrumented && !fromJAR) {
191 // we can only save and make backups if this is coming from a file, not a jar
192 if (_makeBackups) {
193 File backupFile = new File(origFile.getPath().replace(".class", ".class~"));
194 FileOps.copyFile(origFile, backupFile);
195 }
196 FileOutputStream fos = new FileOutputStream(origFile);
197 fos.write(barr);
198 fos.close();
199 }
200 Debug.out.println(" <instrumented>");
201 }
202 else {
203 Debug.out.println(" <skipped: attribute/annotation>");
204 ByteArrayOutputStream baos = new ByteArrayOutputStream();
205 cf.write(baos);
206 barr = baos.toByteArray();
207 }
208
209 c = defineClass(name, barr, 0, barr.length);
210 }
211 catch(IOException e) {
212 e.printStackTrace();
213 }
214 }
215 else {
216 // not found on classpath
217 // try to load the class from the system
218 c = findSystemClass(name);
219 Debug.out.println(" from system");
220 }
221 }
222 catch(ClassFormatError e) {
223 System.err.println(e.getMessage());
224 throw new ClassNotFoundException(name + ":" + e.getMessage(), e);
225 }
226 }
227 else {
228 Debug.out.println(" already processed");
229 }
230 if (resolve) {
231 resolveClass(c);
232 }
233
234 _processedClasses.put(name, c);
235
236 return c;
237 }
238 finally {
239 SyncPointBuffer.setRecording(false);
240 }
241 }
242
243 /**
244 * Private exception used to transport an exception out of the inner class.
245 */
246 private static class RecurIntoClassesException extends RuntimeException {
247 public RecurIntoClassesException(Throwable cause) {
248 super(cause);
249 }
250 }
251
252 /**
253 * Recur into all the classes defined in the class.
254 *
255 * @param cf class file
256 * @param resolve resolve classes?
257 *
258 * @throws ClassNotFoundException
259 */
260 protected void recurIntoClasses(ClassFile cf, final boolean resolve) throws ClassNotFoundException {
261 for(APoolInfo cpi : cf.getConstantPool()) {
262 try {
263 cpi.execute(new ADefaultPoolInfoVisitor<Object, Object>() {
264 public Object defaultCase(APoolInfo host, Object o) {
265 // do nothing
266 return null;
267 }
268
269 public Object classCase(ClassPoolInfo host, Object o) {
270 String className = ClassFileTools.getClassName(host.getName().toString().replace('/', '.'));
271 int space = className.indexOf(' ');
272 if (space >= 0) {
273 className = className.substring(0, space);
274 }
275
276 if ((!ClassFileTools.isPrimitive(host.getName().toString())) &&
277 (!className.startsWith("edu.rice.cs.cunit.")) &&
278 (!_processedClasses.containsKey(className))) {
279 // load this class
280 //Debug.out.println("Recur into class "+className);
281 try {
282 Class c = loadClass(className, resolve);
283 c = null;
284 }
285 catch(ClassNotFoundException e) {
286 throw new RecurIntoClassesException(e);
287 }
288 }
289 return null;
290 }
291 }, null);
292 }
293 catch(RecurIntoClassesException e) {
294 throw (ClassNotFoundException)(e.getCause());
295 }
296 }
297 }
298 }
299