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}