是否有可能追踪导致NPE的表达? [英] Is it possible to track down which expression caused an NPE?

查看:140
本文介绍了是否有可能追踪导致NPE的表达?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

当我得到一个NPE时,我会得到一个带行号的堆栈跟踪。
这很有帮助,但是如果行非常密集和/或包含嵌套表达式,那么仍然无法确定哪个引用为空。

When I get an NPE, I'll get a stack trace with line number. That's helpful, but if the line is very dense and/or contains nested expression, it's still impossible to figure out which reference was null.

当然,这个信息必须在某处可用。
有没有办法解决这个问题? (如果不是java表达式,那么至少导致NPE的字节码指令也会有所帮助)

Surely, this information must've been available somewhere. Is there a way to figure this out? (If not java expression, then at least the bytecode instruction that caused NPE would be helpful as well)

编辑#1:我看到一些评论暗示分手这条线等,没有任何冒犯,实际上是非建设性和无关紧要的。如果我能做到这一点,我会的!我只想说修改源是不可能的。

Edit #1: I've seen a few comments suggesting breaking up the line, etc, which, no offence, is really non-constructive and irrelevant. If I could do that, I would have ! Let just say this modifying the source is out of the question.

编辑#2:apangin在下面发布了一个很好的答案,我接受了。但是,对于那些不想自己尝试的人来说,我必须把输出包括在内! ;)

Edit #2: apangin has posted an excellent answer below, which I accepted. But it's SOOO COOL that I had to include the output here for anyone who doesn't want to try it out themselves! ;)

假设我有这个驱动程序TestNPE.java

So suppose I have this driver program TestNPE.java

 1  public class TestNPE {
 2      public static void main(String[] args) {
 3          int n = 0;
 4          String st = null;
 5  
 6          System.out.println("about to throw NPE");
 7          if (n >= 0 && st.isEmpty()){
 8              System.out.println("empty");
 9          }
10          else {
11              System.out.println("othereise");
12          }
13      }
14      
15  }

字节码看起来像这样(仅显示main()方法并省略其他不相关的部分)

The bytecode looks like this (showing only the main() method and omitting other irrelevant parts)

Code:
  stack=2, locals=3, args_size=1
     0: iconst_0
     1: istore_1
     2: aconst_null
     3: astore_2
     4: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;                                              
     7: ldc           #3                  // String about to throw NPE                                                                     
     9: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V                                      
    12: iload_1
    13: iflt          34
    16: aload_2
    17: invokevirtual #5                  // Method java/lang/String.isEmpty:()Z                                                           
    20: ifeq          34
    23: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;                                              
    26: ldc           #6                  // String empty                                                                                  
    28: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V                                      
    31: goto          42
    34: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;                                              
    37: ldc           #7                  // String othereise                                                                              
    39: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V                                      
    42: return

现在当您使用代理运行TestNPE驱动程序时,您将获得此

Now when you run the TestNPE driver with the agent, you'll get this

$ java -agentpath:libRichNPE.o TestNPE
about to throw NPE
Exception in thread "main" java.lang.NullPointerException: location=17
    at TestNPE.main(TestNPE.java:7)

这样指向偏移17处的invokevirtual#5!只是这是怎么回事?

So that points to the invokevirtual #5 at offset 17! Just HOW COOL IS THAT?

推荐答案

当发生异常时,JVM知道导致的原始字节码例外。但是, StackTraceElement 不跟踪字节码索引。

When an exception happens, JVM knows the original bytecode that caused the exception. However, StackTraceElement does not track bytecode indices.

解决方案是使用 JVMTI

以下示例JVMTI代理将拦截所有异常,如果异常类型为 NullPointerException ,代理将替换其 detailMessage 使用字节码位置信息。

The following sample JVMTI agent will intercept all exceptions, and if exception type is NullPointerException, the agent will replace its detailMessage with the bytecode location information.

#include <jvmti.h>
#include <stdio.h>

static jclass NullPointerException;
static jfieldID detailMessage;

void JNICALL VMInit(jvmtiEnv* jvmti, JNIEnv* env, jthread thread) {
    jclass localNPE = env->FindClass("java/lang/NullPointerException");
    NullPointerException = (jclass) env->NewGlobalRef(localNPE);

    jclass Throwable = env->FindClass("java/lang/Throwable");
    detailMessage = env->GetFieldID(Throwable, "detailMessage", "Ljava/lang/String;");
}

void JNICALL ExceptionCallback(jvmtiEnv* jvmti, JNIEnv* env, jthread thread,
                               jmethodID method, jlocation location, jobject exception,
                               jmethodID catch_method, jlocation catch_location) {
    if (env->IsInstanceOf(exception, NullPointerException)) {
        char buf[32];
        sprintf(buf, "location=%ld", (long)location);
        env->SetObjectField(exception, detailMessage, env->NewStringUTF(buf));
    }
}

JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM* vm, char* options, void* reserved) {
    jvmtiEnv* jvmti;
    vm->GetEnv((void**)&jvmti, JVMTI_VERSION_1_0);

    jvmtiCapabilities capabilities = {0};
    capabilities.can_generate_exception_events = 1;
    jvmti->AddCapabilities(&capabilities);

    jvmtiEventCallbacks callbacks = {0};
    callbacks.VMInit = VMInit;
    callbacks.Exception = ExceptionCallback;
    jvmti->SetEventCallbacks(&callbacks, sizeof(callbacks));
    jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_VM_INIT, NULL);
    jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_EXCEPTION, NULL);

    return 0;
}

将其编译成共享库并使用运行java -agentpath 选项:

Compile this into a shared library and run java with -agentpath option:

java -agentpath:/pato/to/libRichNPE.so Main

这篇关于是否有可能追踪导致NPE的表达?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆