001package daikon.dcomp; 002 003import daikon.DynComp; 004import daikon.plumelib.bcelutil.BcelUtil; 005import daikon.plumelib.bcelutil.SimpleLog; 006import java.io.ByteArrayInputStream; 007import java.io.File; 008import java.lang.instrument.ClassFileTransformer; 009import java.lang.instrument.IllegalClassFormatException; 010import java.security.ProtectionDomain; 011import org.apache.bcel.classfile.ClassParser; 012import org.apache.bcel.classfile.JavaClass; 013import org.checkerframework.checker.nullness.qual.Nullable; 014import org.checkerframework.checker.signature.qual.InternalForm; 015import org.checkerframework.dataflow.qual.Pure; 016 017public class Instrument implements ClassFileTransformer { 018 019 /** Directory for debug output. */ 020 File debug_dir; 021 022 /** Directory for debug instrumented class output. */ 023 File debug_bin_dir; 024 025 /** Directory for debug original class output. */ 026 File debug_orig_dir; 027 028 /** Have we seen a class member of a known transformer? */ 029 static boolean transformer_seen = false; 030 031 /** 032 * Debug information about which classes and/or methods are transformed and why. Use 033 * debugInstrument for actual instrumentation details. 034 */ 035 private static SimpleLog debug_transform = new SimpleLog(false); 036 037 /** Instrument class constructor. Setup debug directories, if needed. */ 038 public Instrument() { 039 debug_transform.enabled = 040 DynComp.debug || DynComp.debug_transform || Premain.debug_dcinstrument; 041 daikon.chicory.Instrument.debug_transform.enabled = debug_transform.enabled; 042 043 debug_dir = DynComp.debug_dir; 044 debug_bin_dir = new File(debug_dir, "bin"); 045 debug_orig_dir = new File(debug_dir, "orig"); 046 047 if (DynComp.dump) { 048 debug_bin_dir.mkdirs(); 049 debug_orig_dir.mkdirs(); 050 } 051 } 052 053 /** Debug code for printing the current run-time call stack. */ 054 public static void print_call_stack() { 055 StackTraceElement[] stack_trace; 056 stack_trace = Thread.currentThread().getStackTrace(); 057 // [0] is getStackTrace 058 // [1] is print_call_stack 059 for (int i = 2; i < stack_trace.length; i++) { 060 System.out.printf("call stack: %s%n", stack_trace[i]); 061 } 062 System.out.println(); 063 } 064 065 @Override 066 @SuppressWarnings("nullness") // bug: java.lang.instrument is not yet annotated 067 public byte @Nullable [] transform( 068 ClassLoader loader, 069 @InternalForm String className, 070 Class<?> classBeingRedefined, 071 ProtectionDomain protectionDomain, 072 byte[] classfileBuffer) 073 throws IllegalClassFormatException { 074 075 debug_transform.log( 076 "In dcomp.Instrument.transform(): class = %s, loader: %s%n", className, loader); 077 078 if (className == null) { 079 /* 080 // debug code to display unnamed class 081 try { 082 // Parse the bytes of the classfile, die on any errors 083 ClassParser parser = new ClassParser(new ByteArrayInputStream(classfileBuffer), className); 084 JavaClass c = parser.parse(); 085 System.out.println(c.toString()); 086 } catch (Throwable e) { 087 System.out.printf("Unexpected Error: %s%n", e); 088 e.printStackTrace(); 089 throw new RuntimeException("Unexpected error", e); 090 } 091 */ 092 // most likely a lambda related class 093 return null; 094 } 095 096 // See comments in Premain.java about meaning and use of in_shutdown. 097 if (Premain.in_shutdown) { 098 debug_transform.log("Skipping in_shutdown class %s%n", className); 099 return null; 100 } 101 102 // If already instrumented, nothing to do 103 // (This set will be empty if DCInstrument.jdk_instrumented is false) 104 if (Premain.pre_instrumented.contains(className)) { 105 debug_transform.log("Skipping pre_instrumented JDK class %s%n", className); 106 return null; 107 } 108 109 boolean in_jdk = false; 110 111 // Check if class is in JDK 112 if (BcelUtil.inJdkInternalform(className)) { 113 // If we are not using an instrumented JDK, then skip this class. 114 if (!DCInstrument.jdk_instrumented) { 115 debug_transform.log("Skipping JDK class %s%n", className); 116 return null; 117 } 118 119 int lastSlashPos = className.lastIndexOf('/'); 120 if (lastSlashPos > 0) { 121 String packageName = className.substring(0, lastSlashPos).replace('/', '.'); 122 if (Premain.problem_packages.contains(packageName)) { 123 debug_transform.log("Skipping problem package %s%n", packageName); 124 return null; 125 } 126 } 127 128 if (BcelUtil.javaVersion > 8) { 129 if (Premain.problem_classes.contains(className.replace('/', '.'))) { 130 debug_transform.log("Skipping problem class %s%n", className); 131 return null; 132 } 133 } 134 135 if (className.equals("java/lang/DCRuntime")) { 136 debug_transform.log("Skipping special DynComp runtime class %s%n", className); 137 return null; 138 } 139 140 in_jdk = true; 141 debug_transform.log("Instrumenting JDK class %s%n", className); 142 } else { 143 144 // We're not in a JDK class 145 // Don't instrument our own classes 146 if (is_dcomp(className)) { 147 debug_transform.log("Skipping is_dcomp class %s%n", className); 148 return null; 149 } 150 151 // Don't instrument other byte code transformers 152 if (is_transformer(className)) { 153 debug_transform.log("Skipping is_transformer class %s%n", className); 154 if (!transformer_seen) { 155 transformer_seen = true; 156 System.err.printf( 157 "DynComp warning: This program uses a Java byte code transformer: %s%n", className); 158 System.err.printf( 159 "This may interfere with the DynComp transformer and cause DynComp to fail.%n"); 160 } 161 return null; 162 } 163 } 164 165 if (loader == null) { 166 debug_transform.log("transforming class %s, loader %s%n", className, loader); 167 } else { 168 debug_transform.log( 169 "transforming class %s, loader %s - %s%n", className, loader, loader.getParent()); 170 } 171 172 // Parse the bytes of the classfile, die on any errors 173 try (ByteArrayInputStream bais = new ByteArrayInputStream(classfileBuffer)) { 174 ClassParser parser = new ClassParser(bais, className); 175 176 JavaClass c = parser.parse(); 177 178 if (DynComp.dump) { 179 c.dump(new File(debug_orig_dir, c.getClassName() + ".class")); 180 } 181 182 // Transform the file 183 DCInstrument dci = new DCInstrument(c, in_jdk, loader); 184 JavaClass njc = dci.instrument(); 185 186 if (njc == null) { 187 debug_transform.log("Didn't instrument %s%n", c.getClassName()); 188 return null; 189 } else { 190 if (DynComp.dump) { 191 System.out.printf("Dumping %s to %s%n", njc.getClassName(), debug_bin_dir); 192 // write .class file 193 njc.dump(new File(debug_bin_dir, njc.getClassName() + ".class")); 194 // write .bcel file 195 BcelUtil.dump(njc, debug_bin_dir); 196 } 197 return njc.getBytes(); 198 } 199 } catch (Throwable e) { 200 System.err.printf("Unexpected Error: %s%n", e); 201 e.printStackTrace(); 202 throw new RuntimeException("Unexpected error", e); 203 } 204 } 205 206 /** 207 * Returns whether or not the specified class is part of dcomp itself (and thus should not be 208 * instrumented). Some Daikon classes that are used by DynComp are included here as well. 209 * 210 * @param classname class to be checked 211 * @return true if classname is a part of DynComp 212 */ 213 @Pure 214 private static boolean is_dcomp(String classname) { 215 216 if (classname.startsWith("daikon/dcomp/") && !classname.startsWith("daikon/dcomp/DcompTest")) { 217 return true; 218 } 219 if (classname.startsWith("daikon/chicory/") 220 && !classname.equals("daikon/chicory/ChicoryTest")) { 221 return true; 222 } 223 if (classname.equals("daikon/PptTopLevel$PptType")) { 224 return true; 225 } 226 if (classname.startsWith("daikon/plumelib")) { 227 return true; 228 } 229 return false; 230 } 231 232 /** 233 * Returns whether or not the specified class is part of a tool known to do Java byte code 234 * transformation. We need to warn the user that this may not work correctly. 235 * 236 * @param classname class to be checked 237 * @return true if classname is a known transformer 238 */ 239 @Pure 240 protected static boolean is_transformer(String classname) { 241 242 if (classname.startsWith("org/codehaus/groovy")) { 243 return true; 244 } 245 if (classname.startsWith("groovy/lang")) { 246 return true; 247 } 248 if (classname.startsWith("org/mockito")) { 249 return true; 250 } 251 if (classname.startsWith("org/objenesis")) { 252 return true; 253 } 254 if (classname.contains("ByMockito")) { 255 return true; 256 } 257 return false; 258 } 259}