001package daikon.chicory; 002 003// import harpoon.ClassFile.HMethod; 004 005import static daikon.tools.nullness.NullnessUtil.castNonNull; 006 007import daikon.Chicory; 008import daikon.plumelib.bcelutil.BcelUtil; 009import daikon.plumelib.bcelutil.SimpleLog; 010import daikon.plumelib.options.Option; 011import daikon.plumelib.options.Options; 012import daikon.plumelib.util.FilesPlume; 013import java.io.BufferedReader; 014import java.io.File; 015import java.io.FileNotFoundException; 016import java.io.IOException; 017import java.io.UncheckedIOException; 018import java.lang.instrument.ClassFileTransformer; 019import java.lang.instrument.Instrumentation; 020import java.lang.reflect.Member; 021import java.net.URL; 022import java.util.ArrayList; 023import java.util.Arrays; 024import java.util.Collections; 025import java.util.Enumeration; 026import java.util.HashSet; 027import java.util.List; 028import java.util.Set; 029import java.util.jar.JarFile; 030import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf; 031import org.checkerframework.checker.nullness.qual.MonotonicNonNull; 032import org.checkerframework.checker.nullness.qual.Nullable; 033import org.checkerframework.checker.nullness.qual.RequiresNonNull; 034import org.checkerframework.checker.signature.qual.BinaryName; 035 036/** 037 * This class is the entry point for the Chicory instrumentation agent. It is the only code in 038 * ChicoryPremain.jar. 039 */ 040public class ChicoryPremain { 041 042 // These command-line options cannot be accessed from Chicory. These are internal debugging 043 // options that may be used when ChicoryPremain is invoked directly from the command line. 044 045 /** Socket port to communicate with Daikon. */ 046 @Option("socket port to communicate with Daikon") 047 public static int daikon_port = -1; 048 049 /** Turn on most Runtime debugging options. */ 050 @Option("Turn on most Runtime debugging options") 051 public static boolean debug_runtime = false; 052 053 /** Print information about the classes being transformed. */ 054 public static boolean verbose = false; 055 056 /** Set of pure methods returned by Alexandru Salcianu's purity analysis. */ 057 // Non-null if doPurity == true 058 private static @MonotonicNonNull Set<String> pureMethods = null; 059 060 /** True iff Chicory should add variables based on pure methods during instrumentation. */ 061 private static boolean doPurity = false; 062 063 /** 064 * This method is the entry point of the Java agent. Its main purpose is to set up the transformer 065 * so that when classes from the target app are loaded, they are first transformed. 066 * 067 * <p>This method also sets up some other initialization tasks: it connects to Daikon over a port 068 * if necessary, or reads in a purity analysis. 069 */ 070 public static void premain(String agentArgs, Instrumentation inst) throws IOException { 071 072 // System.out.format ("In premain, agentargs ='%s', " + 073 // "Instrumentation = '%s'%n", agentArgs, inst); 074 075 // Because Chicory started ChicoryPremain in a separate process, we must rescan 076 // the options to set up the Chicory static variables. 077 Options options = new Options(Chicory.synopsis, Chicory.class, ChicoryPremain.class); 078 String[] target_args = options.parse(true, Options.tokenize(agentArgs)); 079 if (target_args.length > 0) { 080 System.err.printf("Unexpected ChicoryPremain arguments %s%n", Arrays.toString(target_args)); 081 options.printUsage(); 082 System.exit(1); 083 } 084 085 // Turn on dumping of instrumented classes if debug was selected 086 if (Chicory.debug) { 087 Chicory.dump = true; 088 } 089 090 verbose = Chicory.verbose || Chicory.debug; 091 if (debug_runtime) { 092 Runtime.debug = true; 093 } 094 095 if (verbose) { 096 System.out.printf( 097 "In Chicory premain, agentargs ='%s', Instrumentation = '%s'", agentArgs, inst); 098 System.out.printf("Options settings: %n%s%n", options.settings()); 099 } 100 101 // Open the dtrace file 102 if (Chicory.daikon_online) { 103 Runtime.setDtraceOnlineMode(daikon_port); 104 } else if (Chicory.dtrace_file == null) { 105 File trace_file_path = new File(Chicory.output_dir, "dtrace.gz"); 106 Runtime.setDtraceMaybe(trace_file_path.toString()); 107 } else { 108 File trace_file_path = new File(Chicory.output_dir, Chicory.dtrace_file.getPath()); 109 Runtime.setDtraceMaybe(trace_file_path.toString()); 110 } 111 112 // Setup argument fields in Runtime 113 Runtime.nesting_depth = Chicory.nesting_depth; 114 // daikon.chicory.Instrument.shouldIgnore is shared by Chicory and DynComp. 115 // It uses the Runtime copy of the patterns. 116 Runtime.ppt_omit_pattern = Chicory.ppt_omit_pattern; 117 Runtime.ppt_select_pattern = Chicory.ppt_select_pattern; 118 Runtime.sample_start = Chicory.sample_start; 119 DaikonVariableInfo.std_visibility = Chicory.std_visibility; 120 DaikonVariableInfo.debug_vars.enabled = Chicory.debug_decl_print; 121 if (Chicory.comparability_file != null) { 122 Runtime.comp_info = new DeclReader(); 123 try { 124 castNonNull(Runtime.comp_info).read(castNonNull(Chicory.comparability_file)); 125 } catch (FileNotFoundException e) { 126 System.err.printf("%nCould not find comparability file: %s%n", Chicory.comparability_file); 127 Runtime.chicoryLoaderInstantiationError = true; 128 System.exit(1); 129 } 130 if (verbose) { 131 System.out.printf("Read comparability from %s%n", Chicory.comparability_file); 132 // Runtime.comp_info.dump(); 133 } 134 } 135 136 if (Chicory.doPurity()) { 137 System.err.println("Executing a purity analysis is currently disabled"); 138 System.exit(1); 139 } else if (Chicory.get_purity_file() != null) { 140 readPurityFile(Chicory.get_purity_file(), Chicory.config_dir); 141 doPurity = true; 142 } 143 144 initializeDeclAndDTraceWriters(); 145 146 String instrumenter; 147 if (BcelUtil.javaVersion >= 24) { 148 instrumenter = "daikon.chicory.Instrument24"; 149 } else { 150 instrumenter = "daikon.chicory.Instrument"; 151 } 152 153 // Setup the transformer 154 ClassFileTransformer transformer; 155 // use a special classloader to ensure correct version of BCEL is used 156 ClassLoader loader = new ChicoryLoader(); 157 try { 158 transformer = 159 (ClassFileTransformer) 160 loader.loadClass(instrumenter).getDeclaredConstructor().newInstance(); 161 } catch (Exception e) { 162 throw new RuntimeException("Unexpected error loading Instrument", e); 163 } 164 if (Chicory.debug) { 165 System.out.printf( 166 "Classloader of transformer = %s%n", transformer.getClass().getClassLoader()); 167 } 168 169 // now turn on instrumentation 170 if (verbose) { 171 System.out.println("call addTransformer"); 172 } 173 inst.addTransformer(transformer); 174 175 if (verbose) { 176 System.out.println("exit premain"); 177 } 178 } 179 180 /** Set up the declaration and dtrace writer. */ 181 // Runtime.dtrace is @GuardedBy("<self>") because in the Runtime class, 182 // the printing of final lines and then closing of dtrace only happens 183 // when the monitor of dtrace is held in order for the closing of the 184 // trace to happen only once. See Runtime.noMoreOutput() and 185 // Runtime.addShutdownHook() for more details. DeclWriter and DTraceWriter 186 // never perform this operation (print final lines and close) on the 187 // value of dtrace passed in, therefore they do not need to make use 188 // of synchronization and their references to dtrace do not need to 189 // be annotated with @GuardedBy("<self>"). 190 @SuppressWarnings("lock:argument") 191 private static void initializeDeclAndDTraceWriters() { 192 // The include/exclude filter are implemented in the transform, 193 // so they don't need to be handled here. 194 // (It looks like these can be called even if Runtime.dtrace is null...) 195 Runtime.decl_writer = new DeclWriter(Runtime.dtrace); 196 Runtime.dtrace_writer = new DTraceWriter(Runtime.dtrace); 197 } 198 199 /** 200 * Reads purity file. Each line should contain exactly one method. Care must be taken to supply 201 * the correct format. 202 * 203 * <p>From the Sun JDK API: 204 * 205 * <p>"The string is formatted as the method access modifiers, if any, followed by the method 206 * return type, followed by a space, followed by the class declaring the method, followed by a 207 * period, followed by the method name, followed by a parenthesized, comma-separated list of the 208 * method's formal parameter types. If the method throws checked exceptions, the parameter list is 209 * followed by a space, followed by the word throws followed by a comma-separated list of the 210 * thrown exception types. For example: 211 * 212 * <p>public boolean java.lang.Object.equals(java.lang.Object) 213 * 214 * <p>The access modifiers are placed in canonical order as specified by "The Java Language 215 * Specification". This is public, protected or private first, and then other modifiers in the 216 * following order: abstract, static, final, synchronized native. 217 * 218 * @param purityFileName the purity file 219 * @param pathLoc the relative path; interpret {@code purityFileName} with respect to it 220 */ 221 private static void readPurityFile(File purityFileName, @Nullable File pathLoc) { 222 pureMethods = new HashSet<String>(); 223 File purityFile = new File(pathLoc, purityFileName.getPath()); 224 String purityFileAbsolutePath = purityFile.getAbsolutePath(); 225 226 BufferedReader reader; 227 try { 228 reader = FilesPlume.newBufferedFileReader(purityFile); 229 } catch (FileNotFoundException e) { 230 System.err.printf( 231 "%nCould not find purity file %s = %s%n", purityFileName, purityFileAbsolutePath); 232 Runtime.chicoryLoaderInstantiationError = true; 233 System.exit(1); 234 throw new Error("Unreachable control flow"); 235 } catch (IOException e) { 236 throw new UncheckedIOException( 237 "Problem reading purity file " + purityFileName + " = " + purityFileAbsolutePath, e); 238 } 239 240 if (verbose) { 241 System.out.printf("Reading '%s' for pure methods %n", purityFileName); 242 } 243 244 String line = null; 245 do { 246 try { 247 line = reader.readLine(); 248 } catch (IOException e) { 249 try { 250 reader.close(); 251 } catch (IOException e2) { 252 // Do nothing 253 } 254 throw new UncheckedIOException( 255 "Error reading file " + purityFileName + " = " + purityFileAbsolutePath, e); 256 } 257 258 if (line != null) { 259 pureMethods.add(line.trim()); 260 // System.out.printf("Adding '%s' to list of pure methods%n", 261 // line); 262 } 263 } while (line != null); 264 265 try { 266 reader.close(); 267 } catch (IOException e) { 268 System.err.println("Error while closing " + purityFileName + " after reading."); 269 System.exit(1); 270 } 271 272 // System.out.printf("leaving purify file%n"); 273 274 } 275 276 /** Return true iff Chicory has run a purity analysis or read a {@code *.pure} file. */ 277 @SuppressWarnings("nullness") // dependent: pureMethods is non-null if doPurity is true 278 @EnsuresNonNullIf(result = true, expression = "pureMethods") 279 public static boolean shouldDoPurity() { 280 return doPurity; 281 } 282 283 /** 284 * Checks if member is one of the pure methods found in a purity analysis or supplied from a 285 * {@code *.pure} file. 286 * 287 * @return true iff member is a pure method 288 */ 289 // @RequiresNonNull("ChicoryPremain.pureMethods") 290 @RequiresNonNull("pureMethods") 291 public static boolean isMethodPure(Member member) { 292 assert shouldDoPurity() : "Can't query for purity if no purity analysis was executed"; 293 294 // TODO just use Set.contains(member.toString()) ? 295 for (String methName : pureMethods) { 296 if (methName.equals(member.toString())) { 297 return true; 298 } 299 } 300 301 return false; 302 } 303 304 /** Return an unmodifiable Set of the pure methods. */ 305 // @RequiresNonNull("ChicoryPremain.pureMethods") 306 @RequiresNonNull("pureMethods") 307 public static Set<String> getPureMethods() { 308 return Collections.unmodifiableSet(pureMethods); 309 } 310 311 /** 312 * Classloader for the BCEL code. Using this classloader guarantees that we get the correct 313 * version of BCEL and not a possible incompatible version from elsewhere on the user's classpath. 314 * We also load daikon.chicory.Instrument via this (since that class is the user of all of the 315 * BCEL classes). All references to BCEL must be within that class (so that all references to BCEL 316 * will get resolved by this classloader). 317 * 318 * <p>There are several versions of BCEL that have been released: 319 * 320 * <ul> 321 * <li>the original 5.2 version 322 * <li>an interim 6.0 version 323 * <li>the offical 6.0 release version 324 * <li>the offical 6.1 release version 325 * <li>the PLSE 6.1 release version (includes LocalVariableGen fix) 326 * <li>the offical 6.2 release version (includes LocalVariableGen fix) 327 * <li>the offical 6.3 release version 328 * <li>the offical 6.3.1 release version 329 * <li>the offical 6.4.1 release version 330 * <li>the PLSE 6.4.1.1 release version (includes JDK 11 support) 331 * </ul> 332 * 333 * <p>Note that both Chicory and DynComp use the ChicoryLoader to load BCEL and to verify that the 334 * version loaded is acceptable. However, the official 6.1 release version is sufficient for 335 * Chicory while DynComp requires the latest PLSE 6.4.1.1 version. Hence, this loader only checks 336 * for the official 6.1 release version (or newer). After loading BCEL, DynComp will make an 337 * additional check to verify that the 6.4.1.1 version has been loaded. 338 * 339 * <p>There are two classes present in 6.1 and subsequent releases that are not in previous 340 * versions. Thus, we can identify version 6.1 (and later) of BCEL by the presence of the class: 341 * org.apache.bcel.classfile.ConstantModule.class. 342 * 343 * <p>Earlier versions of Chicory inspected all version of BCEL found on the path and selected the 344 * correct one, if present. We now (9/15/16) simplify this to say the first BCEL found must be the 345 * correct one. This allows us to use the normal loader for all of the classes. 346 */ 347 public static class ChicoryLoader extends ClassLoader { 348 349 /** Log file if verbose is enabled. */ 350 public static final SimpleLog debug = new SimpleLog(ChicoryPremain.verbose); 351 352 /** 353 * Constructor for special BCEL class loader. 354 * 355 * @throws IOException if unable to load class 356 */ 357 @SuppressWarnings("StaticAssignmentInConstructor") // sets static variable only if aborting 358 public ChicoryLoader() throws IOException { 359 360 String bcel_classname = "org.apache.bcel.Constants"; 361 String plse_marker_classname = "org.apache.bcel.classfile.ConstantModule"; 362 363 List<URL> bcel_urls = get_resource_list(bcel_classname); 364 List<URL> plse_urls = get_resource_list(plse_marker_classname); 365 366 if (plse_urls.size() == 0) { 367 System.err.printf( 368 "%nBCEL 6.1 or newer must be on the classpath. Normally it is found in daikon.jar.%n"); 369 Runtime.chicoryLoaderInstantiationError = true; 370 System.exit(1); 371 } 372 if (bcel_urls.size() < plse_urls.size()) { 373 System.err.printf("%nCorrupted BCEL library, bcel %s, plse %s%n", bcel_urls, plse_urls); 374 Runtime.chicoryLoaderInstantiationError = true; 375 System.exit(1); 376 } 377 378 // No need to do anything if only our versions of bcel are present 379 if (bcel_urls.size() == plse_urls.size()) { 380 return; 381 } 382 383 URL bcel = bcel_urls.get(0); 384 URL plse = plse_urls.get(0); 385 if (!plse.getProtocol().equals("jar")) { 386 System.err.printf("%nDaikon BCEL must be in jar file. Found at %s%n", plse); 387 Runtime.chicoryLoaderInstantiationError = true; 388 System.exit(1); 389 } 390 if (!same_location(bcel, plse)) { 391 System.err.printf( 392 "%nDaikon BCEL (%s) is not first BCEL on the classpath (%s).%n", plse, bcel); 393 Runtime.chicoryLoaderInstantiationError = true; 394 System.exit(1); 395 } else { 396 try (JarFile bcel_jar = new JarFile(extract_jar_path(plse))) { 397 debug.log("Daikon BCEL found in jar %s%n", bcel_jar.getName()); 398 } 399 } 400 } 401 402 /** 403 * Returns whether or not the two URL represent the same location for org.apache.bcel. Two 404 * locations match if they refer to the same jar file or the same directory in the filesystem. 405 */ 406 private static boolean same_location(URL url1, URL url2) { 407 if (!url1.getProtocol().equals(url2.getProtocol())) { 408 return false; 409 } 410 411 if (url1.getProtocol().equals("jar")) { 412 // System.out.printf("url1 = %s, file=%s, path=%s, protocol=%s, %s%n", 413 // url1, url1.getFile(), url1.getPath(), 414 // url1.getProtocol(), url1.getClass()); 415 // System.out.printf("url2 = %s, file=%s, path=%s, protocol=%s, %s%n", 416 // url2, url2.getFile(), url2.getPath(), 417 // url2.getProtocol(), url1.getClass()); 418 String jar1 = extract_jar_path(url1); 419 String jar2 = extract_jar_path(url2); 420 return jar1.equals(jar2); 421 } else if (url1.getProtocol().equals("file")) { 422 String loc1 = url1.getFile().replaceFirst("org\\.apache\\.bcel\\..*$", ""); 423 String loc2 = url2.getFile().replaceFirst("org\\.apache\\.bcel\\..*$", ""); 424 return loc1.equals(loc2); 425 } else { 426 throw new Error("unexpected protocol " + url1.getProtocol()); 427 } 428 } 429 430 /** 431 * Returns the pathname of a jar file specified in the URL. The protocol must be 'jar'. Only 432 * file jars are supported. 433 */ 434 private static String extract_jar_path(URL url) { 435 assert url.getProtocol().equals("jar") : url.toString(); 436 437 // Remove the preceeding 'file:' and trailing '!filename' 438 String path = url.getFile(); 439 path = path.replaceFirst("^[^:]*:", ""); 440 path = path.replaceFirst("![^!]*$", ""); 441 442 return path; 443 } 444 445 /** 446 * Get all of the URLs that match the specified name in the classpath. The name should be in 447 * normal classname format (eg, org.apache.bcel.Const). An empty list is returned if no names 448 * match. 449 */ 450 @SuppressWarnings("JdkObsolete") // ClassLoader.getSystemResources returns an Enumeration 451 static List<URL> get_resource_list(String classname) throws IOException { 452 453 String name = classname_to_resource_name(classname); 454 Enumeration<URL> enum_urls = ClassLoader.getSystemResources(name); 455 List<URL> urls = new ArrayList<>(); 456 while (enum_urls.hasMoreElements()) { 457 urls.add(enum_urls.nextElement()); 458 } 459 return urls; 460 } 461 462 /** 463 * Changes a class name in the normal format (eg, org.apache.bcel.Const) to that used to lookup 464 * resources (eg, org/apache/bcel/Const.class). 465 */ 466 private static String classname_to_resource_name(String name) { 467 return (name.replace(".", "/") + ".class"); 468 } 469 470 @Override 471 protected Class<?> loadClass(@BinaryName String name, boolean resolve) 472 throws java.lang.ClassNotFoundException { 473 474 return super.loadClass(name, resolve); 475 } 476 } 477}