001package daikon.dcomp; 002 003import static java.nio.charset.StandardCharsets.UTF_8; 004 005import daikon.DynComp; 006import daikon.chicory.DaikonVariableInfo; 007import daikon.chicory.DeclWriter; 008import daikon.plumelib.bcelutil.BcelUtil; 009import daikon.plumelib.options.Option; 010import daikon.plumelib.options.Options; 011import java.io.BufferedReader; 012import java.io.File; 013import java.io.IOException; 014import java.io.InputStream; 015import java.io.InputStreamReader; 016import java.io.PrintWriter; 017import java.io.UncheckedIOException; 018import java.lang.instrument.ClassDefinition; 019import java.lang.instrument.ClassFileTransformer; 020import java.lang.instrument.Instrumentation; 021import java.net.URL; 022import java.nio.file.Files; 023import java.util.Arrays; 024import java.util.HashSet; 025import java.util.Set; 026import java.util.concurrent.TimeUnit; 027 028/** 029 * This class is the entry point for the DynComp instrumentation agent. It is the only code in 030 * dcomp_premain.jar. 031 */ 032public class Premain { 033 034 // These command-line options cannot be accessed from DynComp. These are internal debugging 035 // options that may be used when Premain is invoked directly from the command line. 036 037 /** Turn on basic DCInstrument debugging options. */ 038 @Option("Turn on basic DCInstrument debugging options") 039 public static boolean debug_dcinstrument = false; 040 041 /** Turn on basic DCRuntime debugging options. */ 042 @Option("Turn on basic DCRuntime debugging options") 043 public static boolean debug_dcruntime = false; 044 045 /** Turn on most DCRuntime debugging options. */ 046 @Option("Turn on most DCRuntime debugging options") 047 public static boolean debug_dcruntime_all = false; 048 049 /** If true, print information about the classes being transformed. */ 050 public static boolean verbose = false; 051 052 /** Set of pre-instrumented JDK classes. */ 053 protected static Set<String> pre_instrumented = new HashSet<>(); 054 055 /** Set of packages known to cause problems when instrumented. */ 056 protected static Set<String> problem_packages = 057 new HashSet<>( 058 Arrays.asList( 059 // Packages to support reflection and Lambda expressions cause instrumentation 060 // problems and probably don't affect user program comparability values. 061 // JDK8 and JDK11 062 "java.lang.invoke", 063 "java.lang.reflect", 064 "sun.reflect.annotation", 065 "sun.reflect.misc", 066 // JDK8 067 "sun.reflect", 068 // JDK11 069 "jdk.internal.reflect")); 070 071 /** Set of classes known to cause problems when instrumented. */ 072 protected static Set<String> problem_classes = 073 new HashSet<>( 074 Arrays.asList( 075 // (none at present) 076 )); 077 078 /** Set of methods known to cause problems when instrumented. */ 079 protected static Set<String> problem_methods = 080 new HashSet<>( 081 Arrays.asList( 082 // (none at present) 083 )); 084 085 /** 086 * One of the last phases for DynComp is to write out the comparability values after the user 087 * program completes execution. One of the steps is to assign values to the arguments of methods 088 * that have not been executed. We use reflection to get type information about these arguments, 089 * which causes the method to be loaded; which causes the main part of DynComp to try to 090 * instrument the method. As the user program has completed execution, doing instrumentation at 091 * this point can lead to problems. The correct fix for this problem is to use BCEL to get the 092 * type information instead of reflection, thus avoiding loading the method into the JVM. This 093 * will be a large change, so a temporary fix is to indicate if the program is in shutdown mode 094 * and not instrument any methods when this flag is true. TODO: Couldn't we just call 095 * removeTransformer at the start of shutdown? 096 */ 097 protected static boolean in_shutdown = false; 098 099 // For debugging 100 // protected static Instrumentation instr; 101 102 /** 103 * This method is the entry point of the Java agent. Its main purpose is to set up the transformer 104 * so that when classes from the target app are loaded, they are first transformed in order to add 105 * comparability instrumentation. 106 * 107 * @param agentArgs string containing the arguments passed to this agent 108 * @param inst instrumentation instance to be used to transform classes 109 * @throws IOException if jdk_classes.txt cannot be read or if the correct version of BCEL cannot 110 * be found or loaded 111 */ 112 public static void premain(String agentArgs, Instrumentation inst) throws IOException { 113 // For debugging 114 // this.instr = inst; 115 116 // Because DynComp started Premain in a separate process, we must rescan 117 // the options to set up the DynComp static variables. 118 Options options = new Options(DynComp.synopsis, DynComp.class, Premain.class); 119 String[] target_args = options.parse(true, agentArgs.trim().split(" *")); 120 if (target_args.length > 0) { 121 System.err.printf("Unexpected Premain arguments %s%n", Arrays.toString(target_args)); 122 System.out.println(); 123 options.printUsage(); 124 System.exit(1); 125 } 126 127 // Turn on dumping of instrumented classes if debug was selected 128 if (DynComp.debug) { 129 DynComp.dump = true; 130 } 131 132 verbose = DynComp.verbose || DynComp.debug; 133 134 if (DynComp.rt_file != null && DynComp.rt_file.getName().equalsIgnoreCase("NONE")) { 135 DynComp.no_jdk = true; 136 DynComp.rt_file = null; 137 } 138 139 // Note that the following references to static fields have an important 140 // side effect: They cause the corresponding class to be loaded. This helps 141 // Instrument.transform() avoid ClassCircularityErrors during initialization. 142 DaikonVariableInfo.std_visibility = DynComp.std_visibility; 143 DCRuntime.depth = DynComp.nesting_depth; 144 // daikon.chicory.Instrument#shouldIgnore is shared by Chicory and DynComp. 145 // It uses the Chicory Runtime copy of the patterns. 146 daikon.chicory.Runtime.ppt_omit_pattern = DynComp.ppt_omit_pattern; 147 daikon.chicory.Runtime.ppt_select_pattern = DynComp.ppt_select_pattern; 148 149 DCInstrument.jdk_instrumented = !DynComp.no_jdk; 150 @SuppressWarnings("UnusedVariable") // loads the BcelUtil class; otherwise, Premain gives errors 151 int junk = BcelUtil.javaVersion; 152 153 // Another 'trick' to force needed classes to be loaded prior to retransformation. 154 String buffer = 155 String.format( 156 "In DynComp premain, agentargs ='%s', Instrumentation = '%s'", agentArgs, inst); 157 if (verbose) { 158 System.out.println(buffer); 159 System.out.printf("Options settings: %n%s%n", options.settings()); 160 } 161 162 // Read the list of pre-instrumented classes. 163 if (DCInstrument.jdk_instrumented) { 164 // location is: daikon/java/dcomp-rt/java/lang/jdk_classes.txt . 165 try (InputStream strm = Object.class.getResourceAsStream("jdk_classes.txt")) { 166 if (strm == null) { 167 System.err.println( 168 "Can't find jdk_classes.txt;" 169 + " see Daikon manual, section \"Instrumenting the JDK with DynComp\""); 170 System.exit(1); 171 } 172 BufferedReader reader = new BufferedReader(new InputStreamReader(strm, UTF_8)); 173 174 while (true) { 175 String line = reader.readLine(); 176 if (line == null) { 177 break; 178 } 179 // System.out.printf("adding '%s'%n", line); 180 pre_instrumented.add(line); 181 } 182 } 183 } 184 185 // Setup the shutdown hook 186 Thread shutdown_thread = new ShutdownThread(); 187 java.lang.Runtime.getRuntime().addShutdownHook(shutdown_thread); 188 189 // Setup the transformer 190 ClassFileTransformer transformer; 191 // use a special classloader to ensure correct version of BCEL is used 192 ClassLoader loader = new daikon.chicory.ChicoryPremain.ChicoryLoader(); 193 try { 194 transformer = 195 (ClassFileTransformer) 196 loader.loadClass("daikon.dcomp.Instrument").getDeclaredConstructor().newInstance(); 197 } catch (Exception e) { 198 throw new RuntimeException("Unexpected error loading Instrument", e); 199 } 200 if (verbose) { 201 // If DCInstrument.jdk_instrumented is true then the printf below will output 202 // 'null' to indicate we are using the bootstrap loader. 203 System.out.printf( 204 "Classloader of transformer = %s%n", transformer.getClass().getClassLoader()); 205 } 206 207 // Check that we got a newer version of BCEL that includes JDK 11 support. At present, 208 // this is only the PLSE 6.4.1.1 release version. We can verify this version by the 209 // presence of the method FieldGenOrMethodGen.removeAnnotationEntries(). 210 try { 211 Class<?> c = loader.loadClass("org.apache.bcel.generic.FieldGenOrMethodGen"); 212 c.getMethod("removeAnnotationEntries", (Class<?>[]) null); 213 } catch (Exception e) { 214 System.err.printf("%nBCEL jar found is not the version included with the Daikon release.%n"); 215 System.exit(1); 216 } 217 218 // now turn on instrumentation 219 if (verbose) { 220 System.out.println("call addTransformer"); 221 } 222 inst.addTransformer(transformer, true); 223 224 // See the "General Java Runtime instrumentation strategy" comments in DCInstrument.java 225 // for an explaination of how we deal with instrumenting the JDK 11 runtime. 226 // 227 // At this point in DynComp start up, we use java.lang.instrument.redefineClasses to replace the 228 // dummy java.lang.DCRuntime with a version where each method calls the corresponding method in 229 // daikon.dcomp.DCRuntime. The Java runtime does not enforce the security check in this case. 230 // 231 if (BcelUtil.javaVersion > 8 && DCInstrument.jdk_instrumented) { 232 233 // Buffer for input of our replacement java.lang.DCRuntime. 234 // The size of the current version is 6326 bytes and we do not 235 // anticipate any significant changes. 236 byte[] repClass = new byte[9999]; 237 String classname = "daikon/dcomp-transfer/DCRuntime.class"; 238 URL class_url = ClassLoader.getSystemResource(classname); 239 if (class_url != null) { 240 try (InputStream inputStream = class_url.openStream()) { 241 if (inputStream != null) { 242 int size = inputStream.read(repClass, 0, repClass.length); 243 byte[] truncated = new byte[size]; 244 System.arraycopy(repClass, 0, truncated, 0, size); 245 ClassDefinition cd = 246 new ClassDefinition(Class.forName("java.lang.DCRuntime"), truncated); 247 inst.redefineClasses(cd); 248 } else { 249 throw new Error("openStream failed for " + class_url); 250 } 251 } catch (Throwable t) { 252 throw new Error("Unexpected error reading " + class_url, t); 253 } 254 } else { 255 throw new Error("Could not locate " + classname); 256 } 257 } 258 259 // Initialize the static tag array 260 if (verbose) { 261 System.out.println("call DCRuntime.init"); 262 } 263 DCRuntime.init(); 264 265 if (verbose) { 266 System.out.println("exit premain"); 267 } 268 } 269 270 /** Shutdown thread that writes out the comparability results. */ 271 public static class ShutdownThread extends Thread { 272 273 @Override 274 public void run() { 275 276 if (Premain.verbose) { 277 System.out.println("in shutdown"); 278 } 279 in_shutdown = true; 280 281 // for debugging 282 // Class<?>[] loaded_classes = instr.getAllLoadedClasses(); 283 // for (Class<?> loaded_class : loaded_classes) { 284 // System.out.println(loaded_class + ": " + loaded_class.getClassLoader()); 285 // } 286 287 // If requested, write the comparability data to a file 288 if (DynComp.comparability_file != null) { 289 if (Premain.verbose) { 290 System.out.println("Writing comparability sets to " + DynComp.comparability_file); 291 } 292 PrintWriter compare_out = open(DynComp.comparability_file); 293 long startTime = System.nanoTime(); 294 DCRuntime.printAllComparable(compare_out); 295 compare_out.close(); 296 if (Premain.verbose) { 297 long duration = System.nanoTime() - startTime; 298 System.out.printf( 299 "Comparability sets written in %ds%n", TimeUnit.NANOSECONDS.toSeconds(duration)); 300 } 301 } 302 303 if (DynComp.trace_file != null) { 304 if (Premain.verbose) { 305 System.out.println("Writing traced comparability sets to " + DynComp.trace_file); 306 } 307 PrintWriter trace_out = open(DynComp.trace_file); 308 long startTime = System.nanoTime(); 309 DCRuntime.traceAllComparable(trace_out); 310 trace_out.close(); 311 if (Premain.verbose) { 312 long duration = System.nanoTime() - startTime; 313 System.out.printf( 314 "Traced comparability sets written in %ds%n", 315 TimeUnit.NANOSECONDS.toSeconds(duration)); 316 } 317 } else { 318 // Writing comparability sets to standard output? 319 } 320 321 if (Premain.verbose) { 322 DCRuntime.decl_stats(); 323 } 324 325 // Write the decl file out 326 @SuppressWarnings("nullness:argument") // DynComp guarantees decl_file is non null 327 File decl_file = new File(DynComp.output_dir, DynComp.decl_file); 328 if (Premain.verbose) { 329 System.out.println("Writing decl file to " + decl_file); 330 } 331 PrintWriter decl_fp = open(decl_file); 332 // Create DeclWriter so can share output code in Chicory. 333 DCRuntime.declWriter = new DeclWriter(decl_fp); 334 DCRuntime.declWriter.debug = DynComp.debug_decl_print; 335 // Used for calling ComparabilityProvider.getComparability. 336 DCRuntime.comparabilityProvider = new DCRuntime(); 337 338 long startTime = System.nanoTime(); 339 DCRuntime.printDeclFile(decl_fp); 340 decl_fp.close(); 341 if (Premain.verbose) { 342 long duration = System.nanoTime() - startTime; 343 System.out.printf("Decl file written in %ds%n", TimeUnit.NANOSECONDS.toSeconds(duration)); 344 System.out.printf("comp_list = %,d%n", DCRuntime.comp_list_ms); 345 System.out.printf("ppt name = %,d%n", DCRuntime.ppt_name_ms); 346 System.out.printf("decl vars = %,d%n", DCRuntime.decl_vars_ms); 347 System.out.printf("total = %,d%n", DCRuntime.total_ms); 348 } 349 if (Premain.verbose) { 350 System.out.println("DynComp complete"); 351 } 352 } 353 } 354 355 /** 356 * Helper method to create a PrintWriter from a File. 357 * 358 * @param filename the File to be opened 359 * @return a new PrintWriter from filename 360 */ 361 public static PrintWriter open(File filename) { 362 File canonicalFile; 363 try { 364 canonicalFile = filename.getCanonicalFile(); 365 } catch (IOException e) { 366 throw new UncheckedIOException( 367 "Can't get canonical file for " + filename + " in " + System.getProperty("user.dir"), e); 368 } 369 370 // I don't know why, but without this, the call to newBufferedWriter fails in some contexts. 371 try { 372 canonicalFile.createNewFile(); 373 } catch (IOException e) { 374 throw new UncheckedIOException("createNewFile failed for " + canonicalFile, e); 375 } 376 377 try { 378 return new PrintWriter(Files.newBufferedWriter(canonicalFile.toPath(), UTF_8)); 379 } catch (Exception e) { 380 throw new Error("Can't open " + filename + " = " + canonicalFile, e); 381 } 382 } 383}