【问题标题】:How to avoid 'javassist.CannotCompileException: no method body'如何避免'javassist.CannotCompileException:没有方法体'
【发布时间】:2014-04-10 15:35:13
【问题描述】:

我正在使用 Java 工具和 Javassist 将打印语句插入方法中。这大部分都可以正常工作,但是对于某些类方法(例如java.util.TimeZone.getSystemTimeZoneID),我得到以下异常:

javassist.CannotCompileException: no method body
        at javassist.CtBehavior.insertBefore(CtBehavior.java:695)
        at javassist.CtBehavior.insertBefore(CtBehavior.java:685)

我的代码试图通过检查CtBehaviour.isEmpty() 来避免这个问题,但这并没有什么区别。有关如何避免这种情况的任何建议?

这是一个最小的例子:

public class ExceptionExample implements ClassFileTransformer {

  private static final ClassPool pool = ClassPool.getDefault();

  public static void premain(String agentArgument,
      Instrumentation instrumentation) {
    instrumentation.addTransformer(new ExceptionExample());
  }

  @Override
  public byte[] transform(final ClassLoader loader, final String className,
      final Class<?> classBeingRedefined,
      final ProtectionDomain protectionDomain, final byte[] classfileBuffer)
      throws IllegalClassFormatException {

    String dottedClassName = className.replace('/', '.');

    if (dottedClassName.startsWith("java.lang")
        || dottedClassName.startsWith("java.util")) {

      try {
        System.out.println("Instrumenting: " + dottedClassName);
        return adjustClass(dottedClassName, classBeingRedefined,
            classfileBuffer);
      } catch (Exception e) {
        e.printStackTrace();
      }
    }

    return classfileBuffer;
  }

  private byte[] adjustClass(final String className,
      final Class<?> classBeingRedefined, final byte[] classfileBuffer)
      throws IOException, RuntimeException, CannotCompileException {

    CtClass cl = null;

    try {
      cl = pool.makeClass(new java.io.ByteArrayInputStream(classfileBuffer));
      if (!cl.isInterface()) {
        CtBehavior[] methods = cl.getDeclaredBehaviors();
        for (CtBehavior method : methods) {
          if (!method.isEmpty()) {
            try {
              method
                  .insertBefore(String.format(
                      "System.out.println(\"CALLING: %s\");",
                      method.getLongName()));
            } catch (Throwable t) {
              System.out.println("Error instrumenting " + className + "."
                  + method.getName());
              t.printStackTrace();
            }
          }
        }
        return cl.toBytecode();
      }
    } finally {
      if (cl != null) {
        cl.detach();
      }
    }
    return classfileBuffer;
  }
}

这是我正在测试的一个小类:

public class Main {

  public static void main(String[] args) throws Exception {
    Calendar c = Calendar.getInstance();
    System.out.println(c.get(Calendar.YEAR));
  }
}

示例输出(缩写):

Instrumenting: java.util.Calendar
CALLING: java.util.Calendar.<clinit>()
CALLING: java.util.Calendar.getInstance()
Instrumenting: java.util.TimeZone
Error instrumenting java.util.TimeZone.getSystemTimeZoneID
javassist.CannotCompileException: no method body
        at javassist.CtBehavior.insertBefore(CtBehavior.java:695)
        at javassist.CtBehavior.insertBefore(CtBehavior.java:685)
        at com.cryptomathic.test.instrument.ExceptionExample.adjustClass(ExceptionExample.java:56)
        at com.cryptomathic.test.instrument.ExceptionExample.transform(ExceptionExample.java:34)
        at sun.instrument.TransformerManager.transform(Unknown Source)
        at sun.instrument.InstrumentationImpl.transform(Unknown Source)
        at java.util.Calendar.getInstance(Unknown Source)
        at test.Main.main(Main.java:8)
Error instrumenting java.util.TimeZone.getSystemGMTOffsetID
javassist.CannotCompileException: no method body
        at javassist.CtBehavior.insertBefore(CtBehavior.java:695)
        at javassist.CtBehavior.insertBefore(CtBehavior.java:685)
        at com.cryptomathic.test.instrument.ExceptionExample.adjustClass(ExceptionExample.java:56)
        at com.cryptomathic.test.instrument.ExceptionExample.transform(ExceptionExample.java:34)
        at sun.instrument.TransformerManager.transform(Unknown Source)
        at sun.instrument.InstrumentationImpl.transform(Unknown Source)
        at java.util.Calendar.getInstance(Unknown Source)
        at test.Main.main(Main.java:8)
CALLING: java.util.TimeZone.<clinit>()
CALLING: java.util.TimeZone.getDefaultRef()
CALLING: java.util.TimeZone.getDefaultInAppContext()
CALLING: java.util.TimeZone.setDefaultZone()
CALLING: java.util.TimeZone.getTimeZone(java.lang.String,boolean)
CALLING: java.util.TimeZone.setID(java.lang.String)
...

【问题讨论】:

    标签: java instrumentation javassist


    【解决方案1】:

    您需要首先检查它是否是本机方法,因为本机方法不支持这些方法(insertBefore()、insertAfter()..)。我写了一个小方法来检测 CtMethod 是否是原生的:

    public static boolean isNative(CtMethod method) {
        return Modifier.isNative(method.getModifiers());
    }
    

    我已经对其进行了测试,它可以工作并解决您的问题。

    注意:无法检测原生方法,因为它们没有字节码。但是,如果支持本地方法前缀 ( Transformer.isNativeMethodPrefixSupported() ),那么您可以使用 Transformer.setNativeMethodPrefix() 将本地方法调用包装在非本地调用中,然后可以对其进行检测。

    【讨论】:

    • Halil 建议您也不能修改抽象方法。要识别抽象方法,请使用以下方法:Modifier.isAbstract(method.getModifiers()).
    【解决方案2】:

    TimeZone.getSystemTimeZoneID() 是一种本机方法,我并不惊讶这不起作用(我认为你根本不能这样做,但我在 Javassist 文档中没有找到此信息)。
    要跳过本机方法,请使用 Modifier.isNative()

    【讨论】:

    • 我还不能测试这个答案(代码继续,问题不再发生等),但我怀疑它是正确的。如果我能证明的话,你现在会得到一个赞成票,还有一个闪亮的绿色勾号:-)
    • @Duncan:至少对我来说它有效。我知道这个问题已经很老了,但我认为你可以给它一个闪亮的绿色勾号:D
    猜你喜欢
    • 2011-09-13
    • 1970-01-01
    • 2019-09-07
    • 1970-01-01
    • 2019-05-29
    • 1970-01-01
    • 2020-02-06
    • 2021-12-15
    • 1970-01-01
    相关资源
    最近更新 更多