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}