8218628: Add detailed message to NullPointerException describing what is null. JEP-8220715-NPE_messages
authorgoetz
Fri, 08 Feb 2019 14:15:05 +0100
branchJEP-8220715-NPE_messages
changeset 57271 1735d39dbff9
parent 54184 5f4dedb4dcf5
child 57272 472db1657c6d
8218628: Add detailed message to NullPointerException describing what is null.
make/hotspot/symbols/symbols-unix
src/hotspot/share/classfile/javaClasses.cpp
src/hotspot/share/classfile/javaClasses.hpp
src/hotspot/share/classfile/systemDictionary.hpp
src/hotspot/share/classfile/vmSymbols.hpp
src/hotspot/share/include/jvm.h
src/hotspot/share/interpreter/bytecodeUtils.cpp
src/hotspot/share/interpreter/bytecodeUtils.hpp
src/hotspot/share/prims/jvm.cpp
src/java.base/share/classes/java/lang/NullPointerException.java
src/java.base/share/native/libjava/NullPointerException.c
test/hotspot/jtreg/runtime/exceptionMsgs/NullPointerException/NullPointerExceptionTest.java
test/hotspot/jtreg/vmTestbase/jit/t/t104/t104.gold
--- a/make/hotspot/symbols/symbols-unix	Tue Mar 19 17:03:18 2019 +0800
+++ b/make/hotspot/symbols/symbols-unix	Fri Feb 08 14:15:05 2019 +0100
@@ -1,5 +1,5 @@
 #
-# Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2016, 2019, Oracle and/or its affiliates. All rights reserved.
 # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 #
 # This code is free software; you can redistribute it and/or modify it
@@ -97,6 +97,7 @@
 JVM_GetDeclaredClasses
 JVM_GetDeclaringClass
 JVM_GetEnclosingMethodInfo
+JVM_GetExtendedNPEMessage
 JVM_GetFieldIxModifiers
 JVM_GetFieldTypeAnnotations
 JVM_GetInheritedAccessControlContext
--- a/src/hotspot/share/classfile/javaClasses.cpp	Tue Mar 19 17:03:18 2019 +0800
+++ b/src/hotspot/share/classfile/javaClasses.cpp	Fri Feb 08 14:15:05 2019 +0100
@@ -1516,7 +1516,7 @@
 int  java_lang_Class::classRedefinedCount_offset = -1;
 
 #define CLASS_FIELDS_DO(macro) \
-  macro(classRedefinedCount_offset, k, "classRedefinedCount", int_signature,         false) ; \
+  macro(classRedefinedCount_offset, k, "classRedefinedCount", int_signature,         false); \
   macro(_class_loader_offset,       k, "classLoader",         classloader_signature, false); \
   macro(_component_mirror_offset,   k, "componentType",       class_signature,       false); \
   macro(_module_offset,             k, "module",              module_signature,      false); \
@@ -1961,6 +1961,38 @@
   return method != NULL && (method->constants()->version() == version);
 }
 
+bool java_lang_Throwable::get_method_and_bci(oop throwable, Method** method, int* bci) {
+  objArrayOop bt = (objArrayOop)backtrace(throwable);
+
+  if (bt == NULL) {
+    return false;
+  }
+
+  oop ms = bt->obj_at(trace_methods_offset);
+  typeArrayOop methods = typeArrayOop(ms);
+  typeArrayOop bcis = (typeArrayOop)bt->obj_at(trace_bcis_offset);
+  objArrayOop mirrors = (objArrayOop)bt->obj_at(trace_mirrors_offset);
+  if ((methods == NULL) || (bcis == NULL) || (mirrors == NULL)) {
+    return false;
+  }
+
+  oop m = mirrors->obj_at(0);
+  if (m == NULL) {
+    return false;
+  }
+
+  InstanceKlass* holder = InstanceKlass::cast(java_lang_Class::as_Klass(m));
+  int method_id = methods->short_at(0);
+  Method* act_method = holder->method_with_idnum(method_id);
+  if (act_method == NULL) {
+    return false;
+  }
+
+  int act_bci = Backtrace::bci_at(bcis->int_at(0));
+  *method = act_method;
+  *bci = act_bci;
+  return true;
+}
 
 // This class provides a simple wrapper over the internal structure of
 // exception backtrace to insulate users of the backtrace from needing
--- a/src/hotspot/share/classfile/javaClasses.hpp	Tue Mar 19 17:03:18 2019 +0800
+++ b/src/hotspot/share/classfile/javaClasses.hpp	Fri Feb 08 14:15:05 2019 +0100
@@ -572,6 +572,8 @@
   static void java_printStackTrace(Handle throwable, TRAPS);
   // Debugging
   friend class JavaClasses;
+  // Gets the method and bci of the top frame (TOS). Returns false if this failed.
+  static bool get_method_and_bci(oop throwable, Method** method, int* bci);
 };
 
 
--- a/src/hotspot/share/classfile/systemDictionary.hpp	Tue Mar 19 17:03:18 2019 +0800
+++ b/src/hotspot/share/classfile/systemDictionary.hpp	Fri Feb 08 14:15:05 2019 +0100
@@ -155,6 +155,7 @@
   do_klass(reflect_ConstantPool_klass,                  reflect_ConstantPool                                  ) \
   do_klass(reflect_UnsafeStaticFieldAccessorImpl_klass, reflect_UnsafeStaticFieldAccessorImpl                 ) \
   do_klass(reflect_CallerSensitive_klass,               reflect_CallerSensitive                               ) \
+  do_klass(reflect_NativeConstructorAccessorImpl_klass, reflect_NativeConstructorAccessorImpl                 ) \
                                                                                                                 \
   /* support for dynamic typing; it's OK if these are NULL in earlier JDKs */                                   \
   do_klass(DirectMethodHandle_klass,                    java_lang_invoke_DirectMethodHandle                   ) \
--- a/src/hotspot/share/classfile/vmSymbols.hpp	Tue Mar 19 17:03:18 2019 +0800
+++ b/src/hotspot/share/classfile/vmSymbols.hpp	Fri Feb 08 14:15:05 2019 +0100
@@ -243,6 +243,7 @@
   template(reflect_Reflection,                        "jdk/internal/reflect/Reflection")              \
   template(reflect_CallerSensitive,                   "jdk/internal/reflect/CallerSensitive")         \
   template(reflect_CallerSensitive_signature,         "Ljdk/internal/reflect/CallerSensitive;")       \
+  template(reflect_NativeConstructorAccessorImpl,     "jdk/internal/reflect/NativeConstructorAccessorImpl")\
   template(checkedExceptions_name,                    "checkedExceptions")                        \
   template(clazz_name,                                "clazz")                                    \
   template(exceptionTypes_name,                       "exceptionTypes")                           \
--- a/src/hotspot/share/include/jvm.h	Tue Mar 19 17:03:18 2019 +0800
+++ b/src/hotspot/share/include/jvm.h	Fri Feb 08 14:15:05 2019 +0100
@@ -192,6 +192,13 @@
 JVM_InitStackTraceElement(JNIEnv* env, jobject element, jobject stackFrameInfo);
 
 /*
+ * java.lang.NullPointerException
+ */
+
+JNIEXPORT jstring JNICALL
+JVM_GetExtendedNPEMessage(JNIEnv *env, jthrowable throwable);
+
+/*
  * java.lang.StackWalker
  */
 enum {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/hotspot/share/interpreter/bytecodeUtils.cpp	Fri Feb 08 14:15:05 2019 +0100
@@ -0,0 +1,1317 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2019 SAP SE. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ *
+ */
+
+#include "precompiled.hpp"
+#include "classfile/systemDictionary.hpp"
+#include "gc/shared/gcLocker.hpp"
+#include "interpreter/bytecodeUtils.hpp"
+#include "memory/resourceArea.hpp"
+#include "runtime/signature.hpp"
+#include "utilities/events.hpp"
+
+/*
+ * Returns the name of the klass that is described at constant pool
+ * index cp_index in the constant pool of method 'method'.
+ */
+char const* MethodBytecodePrinter::get_klass_name(Method* method, int cp_index) {
+  ConstantPool* cp = method->constants();
+  int class_index = cp->klass_ref_index_at(cp_index);
+  Symbol* klass = cp->klass_at_noresolve(class_index);
+
+  return klass->as_klass_external_name();
+}
+
+/*
+ * Returns the name of the method that is described at constant pool
+ * index cp_index in the constant pool of method 'method'.
+ */
+char const* MethodBytecodePrinter::get_method_name(Method* method, int cp_index) {
+  ConstantPool* cp = method->constants();
+  int class_index = cp->klass_ref_index_at(cp_index);
+  Symbol* klass = cp->klass_at_noresolve(class_index);
+
+  int name_and_type_index = cp->name_and_type_ref_index_at(cp_index);
+  int name_index = cp->name_ref_index_at(name_and_type_index);
+  int type_index = cp->signature_ref_index_at(name_and_type_index);
+  Symbol* name = cp->symbol_at(name_index);
+  Symbol* signature = cp->symbol_at(type_index);
+
+  stringStream ss;
+  ss.print("%s.%s%s", klass->as_klass_external_name(), name->as_C_string(), signature->as_C_string());
+  return ss.as_string();
+}
+
+/*
+ * Returns the name of the field that is described at constant pool
+ * index cp_index in the constant pool of method 'method'.
+ */
+char const* MethodBytecodePrinter::get_field_and_class(Method* method, int cp_index) {
+  ConstantPool* cp = method->constants();
+  int class_index = cp->klass_ref_index_at(cp_index);
+  Symbol* klass = cp->klass_at_noresolve(class_index);
+
+  int name_and_type_index = cp->name_and_type_ref_index_at(cp_index);
+  int name_index = cp->name_ref_index_at(name_and_type_index);
+  Symbol* name = cp->symbol_at(name_index);
+
+  stringStream ss;
+  ss.print("%s.%s", klass->as_klass_external_name(), name->as_C_string());
+  return ss.as_string();
+}
+
+/*
+ * Returns the name of the field that is described at constant pool
+ * index cp_index in the constant pool of method 'method'.
+ */
+char const* MethodBytecodePrinter::get_field_name(Method* method, int cp_index) {
+  ConstantPool* cp = method->constants();
+  int name_and_type_index = cp->name_and_type_ref_index_at(cp_index);
+  int name_index = cp->name_ref_index_at(name_and_type_index);
+  Symbol* name = cp->symbol_at(name_index);
+  return name->as_C_string();
+}
+
+TrackingStackEntry::TrackingStackEntry(BasicType type) : _entry(INVALID + type * SCALE) { }
+
+TrackingStackEntry::TrackingStackEntry(int bci, BasicType type) : _entry(bci + type * SCALE) {
+  assert(bci >= 0, "BCI must be >= 0");
+  assert(bci < 65536, "BCI must be < 65536");
+}
+
+int TrackingStackEntry::get_bci() {
+  return _entry % SCALE;
+}
+
+BasicType TrackingStackEntry::get_type() {
+  return BasicType (_entry / SCALE);
+}
+
+TrackingStackEntry TrackingStackEntry::merge(TrackingStackEntry other) {
+  if (get_type() != other.get_type()) {
+    if (((get_type() == T_OBJECT) || (get_type() == T_ARRAY)) &&
+        ((other.get_type() == T_OBJECT) || (other.get_type() == T_ARRAY))) {
+      if (get_bci() == other.get_bci()) {
+        return TrackingStackEntry(get_bci(), T_OBJECT);
+      } else {
+        return TrackingStackEntry(T_OBJECT);
+      }
+    } else {
+      return TrackingStackEntry(T_CONFLICT);
+    }
+  }
+
+  if (get_bci() == other.get_bci()) {
+    return *this;
+  } else {
+    return TrackingStackEntry(get_type());
+  }
+}
+
+
+TrackingStack::TrackingStack(const TrackingStack &copy) {
+  for (int i = 0; i < copy.get_size(); i++) {
+    push_raw(copy._stack.at(i));
+  }
+}
+
+void TrackingStack::push_raw(TrackingStackEntry entry) {
+  if (entry.get_type() == T_VOID) {
+    return;
+  }
+
+  _stack.push(entry);
+}
+
+void TrackingStack::push(TrackingStackEntry entry) {
+  if (type2size[entry.get_type()] == 2) {
+    push_raw(entry);
+    push_raw(entry);
+  } else {
+    push_raw(entry);
+  }
+}
+
+void TrackingStack::push(int bci, BasicType type) {
+  push(TrackingStackEntry(bci, type));
+}
+
+void TrackingStack::pop(int slots) {
+  for (int i = 0; i < slots; ++i) {
+    _stack.pop();
+  }
+
+  assert(get_size() >= 0, "Popped too many slots");
+}
+
+void TrackingStack::merge(TrackingStack const& other) {
+  assert(get_size() == other.get_size(), "Stacks not of same size");
+
+  for (int i = get_size() - 1; i >= 0; --i) {
+    _stack.at_put(i, _stack.at(i).merge(other._stack.at(i)));
+  }
+}
+
+int TrackingStack::get_size() const {
+  return _stack.length();
+}
+
+TrackingStackEntry TrackingStack::get_entry(int slot) {
+  assert(slot >= 0, "Slot < 0");
+  assert(slot < get_size(), "Slot >= size");
+
+  return _stack.at(get_size() - slot - 1);
+}
+
+
+
+static TrackingStackSource createInvalidSource(int bci) {
+  return TrackingStackSource(TrackingStackSource::INVALID, bci, "invalid");
+}
+
+static TrackingStackSource createLocalVarSource(int bci, Method* method, int slot) {
+  // We assume outermost caller has ResourceMark.
+  stringStream reason;
+
+  if (method->has_localvariable_table()) {
+    for (int i = 0; i < method->localvariable_table_length(); i++) {
+      LocalVariableTableElement* elem = method->localvariable_table_start() + i;
+      int start = elem->start_bci;
+      int end = start + elem->length;
+
+      if ((bci >= start) && (bci < end) && (elem->slot == slot)) {
+        ConstantPool* cp = method->constants();
+        char *var =  cp->symbol_at(elem->name_cp_index)->as_C_string();
+        if (strlen(var) == 4 && strcmp(var, "this") == 0) {
+          reason.print("loaded from 'this'");
+        } else {
+          reason.print("loaded from local variable '%s'", var);
+        }
+
+        return TrackingStackSource(TrackingStackSource::LOCAL_VAR, bci, reason.as_string());
+      }
+    }
+  }
+
+  // Handle at least some cases we know.
+  if (!method->is_static() && (slot == 0)) {
+    reason.print("loaded from 'this'");
+  } else {
+    int curr = method->is_static() ? 0 : 1;
+    SignatureStream ss(method->signature());
+    int param_index = 0;
+    bool found = false;
+
+    for (SignatureStream ss(method->signature()); !ss.is_done(); ss.next()) {
+      if (ss.at_return_type()) {
+        continue;
+      }
+
+      int size = type2size[ss.type()];
+
+      if ((slot >= curr) && (slot < curr + size)) {
+        found = true;
+        break;
+      }
+
+      param_index += 1;
+      curr += size;
+    }
+
+    if (found) {
+      reason.print("loaded from the parameter nr. %d of the method", 1 + param_index);
+    } else {
+      // This is the best we can do.
+      reason.print("loaded from a local variable at slot %d", slot);
+    }
+  }
+
+  return TrackingStackSource(TrackingStackSource::LOCAL_VAR, bci, reason.as_string());
+}
+
+static TrackingStackSource createMethodSource(int bci, Method* method, int cp_index) {
+  // We assume outermost caller has ResourceMark.
+  stringStream reason;
+  reason.print("returned from '%s'", MethodBytecodePrinter::get_method_name(method, cp_index));
+  return TrackingStackSource(TrackingStackSource::METHOD, bci, reason.as_string());
+}
+
+static TrackingStackSource createConstantSource(int bci) {
+  return TrackingStackSource(TrackingStackSource::CONSTANT, bci, "loaded from a constant");
+}
+
+static TrackingStackSource createArraySource(int bci, TrackingStackSource const& array_source,
+                                             TrackingStackSource const& index_source) {
+  // We assume outermost caller has ResourceMark.
+  stringStream reason;
+
+  if (array_source.get_type() != TrackingStackSource::INVALID) {
+    if (index_source.get_type() != TrackingStackSource::INVALID) {
+      reason.print("loaded from an array (which itself was %s) with an index %s",
+                   array_source.as_string(), index_source.as_string());
+    } else {
+      reason.print("loaded from an array (which itself was %s)", array_source.as_string());
+    }
+  } else {
+    if (index_source.get_type() != TrackingStackSource::INVALID) {
+      reason.print("loaded from an array with an index %s", index_source.as_string());
+    } else {
+      reason.print("loaded from an array");
+    }
+  }
+
+  return TrackingStackSource(TrackingStackSource::ARRAY_ELEM, bci, reason.as_string());
+}
+
+static TrackingStackSource createFieldSource(int bci, Method* method, int cp_index,
+                                             TrackingStackSource const& object_source) {
+  // We assume outermost caller has ResourceMark.
+  stringStream reason;
+
+  if (object_source.get_type() != TrackingStackSource::INVALID) {
+    reason.print("loaded from field '%s' of an object %s",
+                 MethodBytecodePrinter::get_field_and_class(method, cp_index),
+                 object_source.as_string());
+  } else {
+    reason.print("loaded from field '%s' of an object",
+                 MethodBytecodePrinter::get_field_and_class(method, cp_index));
+  }
+
+  return TrackingStackSource(TrackingStackSource::FIELD_ELEM, bci, reason.as_string());
+}
+
+static TrackingStackSource createStaticFieldSource(int bci, Method* method, int cp_index) {
+  // We assume outermost caller has ResourceMark.
+  stringStream reason;
+  reason.print("loaded from static field '%s'",
+               MethodBytecodePrinter::get_field_and_class(method, cp_index));
+
+  return TrackingStackSource(TrackingStackSource::FIELD_ELEM, bci, reason.as_string());
+}
+
+TrackingStackCreator::TrackingStackCreator(Method* method, int bci) : _method(method) {
+  ConstMethod* const_method = method->constMethod();
+
+  int len = const_method->code_size();
+  _nr_of_entries = 0;
+  _max_entries = 1000000;
+  _stacks = new GrowableArray<TrackingStack*> (len+1);
+
+  for (int i = 0; i <= len; ++i) {
+    _stacks->push(NULL);
+  }
+
+  // Initialize stack a bci 0.
+  _stacks->at_put(0, new TrackingStack());
+
+  // And initialize the start of all exception handlers.
+  if (const_method->has_exception_handler()) {
+    ExceptionTableElement *et = const_method->exception_table_start();
+    for (int i = 0; i < const_method->exception_table_length(); ++i) {
+      u2 index = et[i].handler_pc;
+
+      if (_stacks->at(index) == NULL) {
+        _stacks->at_put(index, new TrackingStack());
+        _stacks->at(index)->push(index, T_OBJECT);
+      }
+    }
+  }
+
+  _all_processed = false;
+  _added_one = true;
+
+  // Do this until each bytecode hash a stack or we haven't
+  // added a new stack in one iteration.
+  while (!_all_processed && _added_one) {
+    _all_processed = true;
+    _added_one = false;
+
+    for (int i = 0; i < len; ) {
+      // Analyse bytecode i. Step by size of the analyzed bytecode to next bytecode.
+      i += do_instruction(i);
+
+      // If we want the data only for a certain bci, we can possibly end early.
+      if ((bci == i) && (_stacks->at(i) != NULL)) {
+        _all_processed = true;
+        break;
+      }
+
+      if (_nr_of_entries > _max_entries) {
+        return;
+      }
+    }
+  }
+}
+
+TrackingStackCreator::~TrackingStackCreator() {
+  for (int i = 0; i < _stacks->length(); ++i) {
+    delete _stacks->at(i);
+  }
+}
+
+void TrackingStackCreator::merge(int bci, TrackingStack* stack) {
+  assert(stack != _stacks->at(bci), "Cannot merge itself");
+
+  if (_stacks->at(bci) != NULL) {
+    stack->merge(*_stacks->at(bci));
+  } else {
+    // Got a new stack, so count the entries.
+    _nr_of_entries += stack->get_size();
+  }
+
+  delete _stacks->at(bci);
+  _stacks->at_put(bci, new TrackingStack(*stack));
+}
+
+int TrackingStackCreator::do_instruction(int bci) {
+  ConstMethod* const_method = _method->constMethod();
+  address code_base = _method->constMethod()->code_base();
+
+  // We use the java code, since we don't want to cope with all the fast variants.
+  int len = Bytecodes::java_length_at(_method, code_base + bci);
+
+  // If we have no stack for this bci, we cannot process the bytecode now.
+  if (_stacks->at(bci) == NULL) {
+    _all_processed = false;
+    return len;
+  }
+
+  TrackingStack* stack = new TrackingStack(*_stacks->at(bci));
+
+  // dest_bci is != -1 if we branch.
+  int dest_bci = -1;
+
+  // This is for table and lookup switch.
+  static const int initial_length = 2;
+  GrowableArray<int> dests(initial_length);
+
+  bool flow_ended = false;
+
+  // Get the bytecode.
+  bool is_wide = false;
+  Bytecodes::Code raw_code = Bytecodes::code_at(_method, code_base + bci);
+  Bytecodes::Code code = Bytecodes::java_code_at(_method, code_base + bci);
+  int pos = bci + 1;
+
+  if (code == Bytecodes::_wide) {
+    is_wide = true;
+    code = Bytecodes::java_code_at(_method, code_base + bci + 1);
+    pos += 1;
+  }
+
+  // Now simulate the action of each bytecode.
+  switch (code) {
+    case Bytecodes::_nop:
+    case Bytecodes::_aconst_null:
+    case Bytecodes::_iconst_m1:
+    case Bytecodes::_iconst_0:
+    case Bytecodes::_iconst_1:
+    case Bytecodes::_iconst_2:
+    case Bytecodes::_iconst_3:
+    case Bytecodes::_iconst_4:
+    case Bytecodes::_iconst_5:
+    case Bytecodes::_lconst_0:
+    case Bytecodes::_lconst_1:
+    case Bytecodes::_fconst_0:
+    case Bytecodes::_fconst_1:
+    case Bytecodes::_fconst_2:
+    case Bytecodes::_dconst_0:
+    case Bytecodes::_dconst_1:
+    case Bytecodes::_bipush:
+    case Bytecodes::_sipush:
+    case Bytecodes::_iload:
+    case Bytecodes::_lload:
+    case Bytecodes::_fload:
+    case Bytecodes::_dload:
+    case Bytecodes::_aload:
+    case Bytecodes::_iload_0:
+    case Bytecodes::_iload_1:
+    case Bytecodes::_iload_2:
+    case Bytecodes::_iload_3:
+    case Bytecodes::_lload_0:
+    case Bytecodes::_lload_1:
+    case Bytecodes::_lload_2:
+    case Bytecodes::_lload_3:
+    case Bytecodes::_fload_0:
+    case Bytecodes::_fload_1:
+    case Bytecodes::_fload_2:
+    case Bytecodes::_fload_3:
+    case Bytecodes::_dload_0:
+    case Bytecodes::_dload_1:
+    case Bytecodes::_dload_2:
+    case Bytecodes::_dload_3:
+    case Bytecodes::_aload_0:
+    case Bytecodes::_aload_1:
+    case Bytecodes::_aload_2:
+    case Bytecodes::_aload_3:
+    case Bytecodes::_iinc:
+    case Bytecodes::_new:
+      stack->push(bci, Bytecodes::result_type(code));
+      break;
+
+    case Bytecodes::_ldc:
+    case Bytecodes::_ldc_w:
+    case Bytecodes::_ldc2_w: {
+      int cp_index;
+      ConstantPool* cp = _method->constants();
+
+      if (code == Bytecodes::_ldc) {
+        cp_index = *(uint8_t*) (code_base + pos);
+
+        if (raw_code == Bytecodes::_fast_aldc) {
+          cp_index = cp->object_to_cp_index(cp_index);
+        }
+      } else {
+        if (raw_code == Bytecodes::_fast_aldc_w) {
+          cp_index = Bytes::get_native_u2(code_base + pos);
+          cp_index = cp->object_to_cp_index(cp_index);
+        }
+        else {
+          cp_index = Bytes::get_Java_u2(code_base + pos);
+        }
+      }
+
+      constantTag tag = cp->tag_at(cp_index);
+      if (tag.is_klass()  || tag.is_unresolved_klass() ||
+          tag.is_method() || tag.is_interface_method() ||
+          tag.is_field()  || tag.is_string()) {
+        stack->push(bci, T_OBJECT);
+      } else if (tag.is_int()) {
+        stack->push(bci, T_INT);
+      } else if (tag.is_long()) {
+        stack->push(bci, T_LONG);
+      } else if (tag.is_float()) {
+        stack->push(bci, T_FLOAT);
+      } else if (tag.is_double()) {
+        stack->push(bci, T_DOUBLE);
+      } else {
+        assert(false, "Unexpected tag");
+      }
+      break;
+    }
+
+    case Bytecodes::_iaload:
+    case Bytecodes::_faload:
+    case Bytecodes::_aaload:
+    case Bytecodes::_baload:
+    case Bytecodes::_caload:
+    case Bytecodes::_saload:
+    case Bytecodes::_laload:
+    case Bytecodes::_daload:
+      stack->pop(2);
+      stack->push(bci, Bytecodes::result_type(code));
+      break;
+
+    case Bytecodes::_istore:
+    case Bytecodes::_lstore:
+    case Bytecodes::_fstore:
+    case Bytecodes::_dstore:
+    case Bytecodes::_astore:
+    case Bytecodes::_istore_0:
+    case Bytecodes::_istore_1:
+    case Bytecodes::_istore_2:
+    case Bytecodes::_istore_3:
+    case Bytecodes::_lstore_0:
+    case Bytecodes::_lstore_1:
+    case Bytecodes::_lstore_2:
+    case Bytecodes::_lstore_3:
+    case Bytecodes::_fstore_0:
+    case Bytecodes::_fstore_1:
+    case Bytecodes::_fstore_2:
+    case Bytecodes::_fstore_3:
+    case Bytecodes::_dstore_0:
+    case Bytecodes::_dstore_1:
+    case Bytecodes::_dstore_2:
+    case Bytecodes::_dstore_3:
+    case Bytecodes::_astore_0:
+    case Bytecodes::_astore_1:
+    case Bytecodes::_astore_2:
+    case Bytecodes::_astore_3:
+    case Bytecodes::_iastore:
+    case Bytecodes::_lastore:
+    case Bytecodes::_fastore:
+    case Bytecodes::_dastore:
+    case Bytecodes::_aastore:
+    case Bytecodes::_bastore:
+    case Bytecodes::_castore:
+    case Bytecodes::_sastore:
+    case Bytecodes::_pop:
+    case Bytecodes::_pop2:
+    case Bytecodes::_monitorenter:
+    case Bytecodes::_monitorexit:
+    case Bytecodes::_breakpoint:
+      stack->pop(-Bytecodes::depth(code));
+      break;
+
+    case Bytecodes::_dup:
+      stack->push_raw(stack->get_entry(0));
+      break;
+
+    case Bytecodes::_dup_x1: {
+      TrackingStackEntry top1 = stack->get_entry(0);
+      TrackingStackEntry top2 = stack->get_entry(1);
+      stack->pop(2);
+      stack->push_raw(top1);
+      stack->push_raw(top2);
+      stack->push_raw(top1);
+      break;
+    }
+
+    case Bytecodes::_dup_x2: {
+      TrackingStackEntry top1 = stack->get_entry(0);
+      TrackingStackEntry top2 = stack->get_entry(1);
+      TrackingStackEntry top3 = stack->get_entry(2);
+      stack->pop(3);
+      stack->push_raw(top1);
+      stack->push_raw(top3);
+      stack->push_raw(top2);
+      stack->push_raw(top1);
+      break;
+    }
+
+    case Bytecodes::_dup2:
+      stack->push_raw(stack->get_entry(1));
+      stack->push_raw(stack->get_entry(1));
+      break;
+
+    case Bytecodes::_dup2_x1: {
+      TrackingStackEntry top1 = stack->get_entry(0);
+      TrackingStackEntry top2 = stack->get_entry(1);
+      TrackingStackEntry top3 = stack->get_entry(2);
+      stack->pop(3);
+      stack->push_raw(top2);
+      stack->push_raw(top1);
+      stack->push_raw(top3);
+      stack->push_raw(top2);
+      stack->push_raw(top1);
+      break;
+    }
+
+    case Bytecodes::_dup2_x2: {
+      TrackingStackEntry top1 = stack->get_entry(0);
+      TrackingStackEntry top2 = stack->get_entry(1);
+      TrackingStackEntry top3 = stack->get_entry(2);
+      TrackingStackEntry top4 = stack->get_entry(3);
+      stack->pop(4);
+      stack->push_raw(top2);
+      stack->push_raw(top1);
+      stack->push_raw(top4);
+      stack->push_raw(top3);
+      stack->push_raw(top2);
+      stack->push_raw(top1);
+      break;
+    }
+
+    case Bytecodes::_swap: {
+      TrackingStackEntry top1 = stack->get_entry(0);
+      TrackingStackEntry top2 = stack->get_entry(1);
+      stack->pop(2);
+      stack->push(top1);
+      stack->push(top2);
+      break;
+    }
+
+    case Bytecodes::_iadd:
+    case Bytecodes::_ladd:
+    case Bytecodes::_fadd:
+    case Bytecodes::_dadd:
+    case Bytecodes::_isub:
+    case Bytecodes::_lsub:
+    case Bytecodes::_fsub:
+    case Bytecodes::_dsub:
+    case Bytecodes::_imul:
+    case Bytecodes::_lmul:
+    case Bytecodes::_fmul:
+    case Bytecodes::_dmul:
+    case Bytecodes::_idiv:
+    case Bytecodes::_ldiv:
+    case Bytecodes::_fdiv:
+    case Bytecodes::_ddiv:
+    case Bytecodes::_irem:
+    case Bytecodes::_lrem:
+    case Bytecodes::_frem:
+    case Bytecodes::_drem:
+    case Bytecodes::_iand:
+    case Bytecodes::_land:
+    case Bytecodes::_ior:
+    case Bytecodes::_lor:
+    case Bytecodes::_ixor:
+    case Bytecodes::_lxor:
+      stack->pop(2 * type2size[Bytecodes::result_type(code)]);
+      stack->push(bci, Bytecodes::result_type(code));
+      break;
+
+    case Bytecodes::_ineg:
+    case Bytecodes::_lneg:
+    case Bytecodes::_fneg:
+    case Bytecodes::_dneg:
+      stack->pop(type2size[Bytecodes::result_type(code)]);
+      stack->push(bci, Bytecodes::result_type(code));
+      break;
+
+    case Bytecodes::_ishl:
+    case Bytecodes::_lshl:
+    case Bytecodes::_ishr:
+    case Bytecodes::_lshr:
+    case Bytecodes::_iushr:
+    case Bytecodes::_lushr:
+      stack->pop(1 + type2size[Bytecodes::result_type(code)]);
+      stack->push(bci, Bytecodes::result_type(code));
+      break;
+
+    case Bytecodes::_i2l:
+    case Bytecodes::_i2f:
+    case Bytecodes::_i2d:
+    case Bytecodes::_f2i:
+    case Bytecodes::_f2l:
+    case Bytecodes::_f2d:
+    case Bytecodes::_i2b:
+    case Bytecodes::_i2c:
+    case Bytecodes::_i2s:
+      stack->pop(1);
+      stack->push(bci, Bytecodes::result_type(code));
+      break;
+
+    case Bytecodes::_l2i:
+    case Bytecodes::_l2f:
+    case Bytecodes::_l2d:
+    case Bytecodes::_d2i:
+    case Bytecodes::_d2l:
+    case Bytecodes::_d2f:
+      stack->pop(2);
+      stack->push(bci, Bytecodes::result_type(code));
+      break;
+
+    case Bytecodes::_lcmp:
+    case Bytecodes::_fcmpl:
+    case Bytecodes::_fcmpg:
+    case Bytecodes::_dcmpl:
+    case Bytecodes::_dcmpg:
+      stack->pop(1 - Bytecodes::depth(code));
+      stack->push(bci, T_INT);
+      break;
+
+    case Bytecodes::_ifeq:
+    case Bytecodes::_ifne:
+    case Bytecodes::_iflt:
+    case Bytecodes::_ifge:
+    case Bytecodes::_ifgt:
+    case Bytecodes::_ifle:
+    case Bytecodes::_if_icmpeq:
+    case Bytecodes::_if_icmpne:
+    case Bytecodes::_if_icmplt:
+    case Bytecodes::_if_icmpge:
+    case Bytecodes::_if_icmpgt:
+    case Bytecodes::_if_icmple:
+    case Bytecodes::_if_acmpeq:
+    case Bytecodes::_if_acmpne:
+    case Bytecodes::_ifnull:
+    case Bytecodes::_ifnonnull:
+      stack->pop(-Bytecodes::depth(code));
+      dest_bci = bci + (int16_t) Bytes::get_Java_u2(code_base + pos);
+      break;
+
+    case Bytecodes::_jsr:
+      // NOTE: Bytecodes has wrong depth for jsr.
+      stack->push(bci, T_ADDRESS);
+      dest_bci = bci + (int16_t) Bytes::get_Java_u2(code_base + pos);
+      flow_ended = true;
+      break;
+
+    case Bytecodes::_jsr_w: {
+      // NOTE: Bytecodes has wrong depth for jsr.
+      stack->push(bci, T_ADDRESS);
+      dest_bci = bci + (int32_t) Bytes::get_Java_u4(code_base + pos);
+      flow_ended = true;
+      break;
+    }
+
+    case Bytecodes::_ret:
+      // We don't track local variables, so we cannot know were we
+      // return. This makes the stacks imprecise, but we have to
+      // live with that.
+      flow_ended = true;
+      break;
+
+    case Bytecodes::_tableswitch: {
+      stack->pop(1);
+      pos = (pos + 3) & ~3;
+      dest_bci = bci + (int32_t) Bytes::get_Java_u4(code_base + pos);
+      int low = (int32_t) Bytes::get_Java_u4(code_base + pos + 4);
+      int high = (int32_t) Bytes::get_Java_u4(code_base + pos + 8);
+
+      for (int64_t i = low; i <= high; ++i) {
+        dests.push(bci + (int32_t) Bytes::get_Java_u4(code_base + pos + 12 + 4 * (i - low)));
+      }
+
+      break;
+    }
+
+    case Bytecodes::_lookupswitch: {
+      stack->pop(1);
+      pos = (pos + 3) & ~3;
+      dest_bci = bci + (int32_t) Bytes::get_Java_u4(code_base + pos);
+      int nr_of_dests = (int32_t) Bytes::get_Java_u4(code_base + pos + 4);
+
+      for (int i = 0; i < nr_of_dests; ++i) {
+        dests.push(bci + (int32_t) Bytes::get_Java_u4(code_base + pos + 12 + 8 * i));
+      }
+
+      break;
+    }
+
+    case Bytecodes::_ireturn:
+    case Bytecodes::_lreturn:
+    case Bytecodes::_freturn:
+    case Bytecodes::_dreturn:
+    case Bytecodes::_areturn:
+    case Bytecodes::_return:
+    case Bytecodes::_athrow:
+      stack->pop(-Bytecodes::depth(code));
+      flow_ended = true;
+      break;
+
+    case Bytecodes::_getstatic:
+    case Bytecodes::_getfield: {
+      // Find out the type of the field accessed.
+      int cp_index = Bytes::get_native_u2(code_base + pos) DEBUG_ONLY(+ ConstantPool::CPCACHE_INDEX_TAG);
+      ConstantPool* cp = _method->constants();
+      int name_and_type_index = cp->name_and_type_ref_index_at(cp_index);
+      int type_index = cp->signature_ref_index_at(name_and_type_index);
+      Symbol* signature = cp->symbol_at(type_index);
+      // Simulate the bytecode: pop the address, push the 'value' loaded
+      // from the field.
+      stack->pop(1 - Bytecodes::depth(code));
+      stack->push(bci, char2type((char) signature->char_at(0)));
+      break;
+    }
+
+    case Bytecodes::_putstatic:
+    case Bytecodes::_putfield: {
+      int cp_index = Bytes::get_native_u2(code_base + pos) DEBUG_ONLY(+ ConstantPool::CPCACHE_INDEX_TAG);
+      ConstantPool* cp = _method->constants();
+      int name_and_type_index = cp->name_and_type_ref_index_at(cp_index);
+      int type_index = cp->signature_ref_index_at(name_and_type_index);
+      Symbol* signature = cp->symbol_at(type_index);
+      ResultTypeFinder result_type(signature);
+      stack->pop(type2size[char2type((char) signature->char_at(0))] - Bytecodes::depth(code) - 1);
+      break;
+    }
+
+    case Bytecodes::_invokevirtual:
+    case Bytecodes::_invokespecial:
+    case Bytecodes::_invokestatic:
+    case Bytecodes::_invokeinterface:
+    case Bytecodes::_invokedynamic: {
+      ConstantPool* cp = _method->constants();
+      int cp_index;
+
+      if (code == Bytecodes::_invokedynamic) {
+        cp_index = ((int) Bytes::get_native_u4(code_base + pos));
+      } else {
+        cp_index = Bytes::get_native_u2(code_base + pos) DEBUG_ONLY(+ ConstantPool::CPCACHE_INDEX_TAG);
+      }
+
+      int name_and_type_index = cp->name_and_type_ref_index_at(cp_index);
+      int type_index = cp->signature_ref_index_at(name_and_type_index);
+      Symbol* signature = cp->symbol_at(type_index);
+
+      if ((code != Bytecodes::_invokestatic) && (code != Bytecodes::_invokedynamic)) {
+        // Pop class.
+        stack->pop(1);
+      }
+
+      stack->pop(ArgumentSizeComputer(signature).size());
+      ResultTypeFinder result_type(signature);
+      stack->push(bci, result_type.type());
+      break;
+    }
+
+    case Bytecodes::_newarray:
+    case Bytecodes::_anewarray:
+    case Bytecodes::_instanceof:
+      stack->pop(1);
+      stack->push(bci, Bytecodes::result_type(code));
+      break;
+
+    case Bytecodes::_arraylength:
+      // The return type of arraylength is wrong in the bytecodes table (T_VOID).
+      stack->pop(1);
+      stack->push(bci, T_INT);
+      break;
+
+    case Bytecodes::_checkcast:
+      break;
+
+    case Bytecodes::_multianewarray:
+      stack->pop(*(uint8_t*) (code_base + pos + 2));
+      stack->push(bci, T_OBJECT);
+      break;
+
+   case Bytecodes::_goto:
+      stack->pop(-Bytecodes::depth(code));
+      dest_bci = bci + (int16_t) Bytes::get_Java_u2(code_base + pos);
+      flow_ended = true;
+      break;
+
+
+   case Bytecodes::_goto_w:
+      stack->pop(-Bytecodes::depth(code));
+      dest_bci = bci + (int32_t) Bytes::get_Java_u4(code_base + pos);
+      flow_ended = true;
+      break;
+
+    default:
+      // Allow at least the bcis which have stack info to work.
+      _all_processed = false;
+      _added_one = false;
+      delete stack;
+
+      return len;
+  }
+
+  // Put new stack to the next instruction, if we might reach if from
+  // this bci.
+  if (!flow_ended) {
+    if (_stacks->at(bci + len) == NULL) {
+      _added_one = true;
+    }
+
+    merge(bci + len, stack);
+  }
+
+  // Put the stack to the branch target too.
+  if (dest_bci != -1) {
+    if (_stacks->at(dest_bci) == NULL) {
+      _added_one = true;
+    }
+
+    merge(dest_bci, stack);
+  }
+
+  // If we have more than one branch target, process these too.
+  for (int64_t i = 0; i < dests.length(); ++i) {
+    if (_stacks->at(dests.at(i)) == NULL) {
+      _added_one = true;
+    }
+
+    merge(dests.at(i), stack);
+  }
+
+  delete stack;
+
+  return len;
+}
+
+TrackingStackSource TrackingStackCreator::get_source(int bci, int slot, int max_detail) {
+  assert(bci >= 0, "BCI too low");
+  assert(bci < get_size(), "BCI to large");
+
+  if (max_detail <= 0) {
+    return createInvalidSource(bci);
+  }
+
+  if (_stacks->at(bci) == NULL) {
+    return createInvalidSource(bci);
+  }
+
+  TrackingStack* stack = _stacks->at(bci);
+  assert(slot >= 0, "Slot nr. too low");
+  assert(slot < stack->get_size(), "Slot nr. too large");
+
+  TrackingStackEntry entry = stack->get_entry(slot);
+
+  if (!entry.has_bci()) {
+    return createInvalidSource(bci);
+  }
+
+  // Get the bytecode.
+  int source_bci = entry.get_bci();
+  address code_base = _method->constMethod()->code_base();
+  Bytecodes::Code code = Bytecodes::java_code_at(_method, code_base + source_bci);
+  bool is_wide = false;
+  int pos = source_bci + 1;
+
+  if (code == Bytecodes::_wide) {
+    is_wide = true;
+    code = Bytecodes::java_code_at(_method, code_base + source_bci + 1);
+    pos += 1;
+  }
+
+  switch (code) {
+    case Bytecodes::_iload:
+    case Bytecodes::_lload:
+    case Bytecodes::_fload:
+    case Bytecodes::_dload:
+    case Bytecodes::_aload: {
+      int index;
+
+      if (is_wide) {
+        index = Bytes::get_Java_u2(code_base + source_bci + 2);
+      } else {
+        index = *(uint8_t*) (code_base + source_bci + 1);
+      }
+
+      return createLocalVarSource(source_bci, _method, index);
+    }
+
+    case Bytecodes::_iload_0:
+    case Bytecodes::_lload_0:
+    case Bytecodes::_fload_0:
+    case Bytecodes::_dload_0:
+    case Bytecodes::_aload_0:
+      return createLocalVarSource(source_bci, _method, 0);
+
+    case Bytecodes::_iload_1:
+    case Bytecodes::_lload_1:
+    case Bytecodes::_fload_1:
+    case Bytecodes::_dload_1:
+    case Bytecodes::_aload_1:
+      return createLocalVarSource(source_bci, _method, 1);
+
+    case Bytecodes::_iload_2:
+    case Bytecodes::_lload_2:
+    case Bytecodes::_fload_2:
+    case Bytecodes::_dload_2:
+    case Bytecodes::_aload_2:
+      return createLocalVarSource(source_bci, _method, 2);
+
+    case Bytecodes::_lload_3:
+    case Bytecodes::_iload_3:
+    case Bytecodes::_fload_3:
+    case Bytecodes::_dload_3:
+    case Bytecodes::_aload_3:
+      return createLocalVarSource(source_bci, _method, 3);
+
+    case Bytecodes::_aconst_null:
+    case Bytecodes::_iconst_m1:
+    case Bytecodes::_iconst_0:
+    case Bytecodes::_iconst_1:
+    case Bytecodes::_iconst_2:
+    case Bytecodes::_iconst_3:
+    case Bytecodes::_iconst_4:
+    case Bytecodes::_iconst_5:
+    case Bytecodes::_lconst_0:
+    case Bytecodes::_lconst_1:
+    case Bytecodes::_fconst_0:
+    case Bytecodes::_fconst_1:
+    case Bytecodes::_fconst_2:
+    case Bytecodes::_dconst_0:
+    case Bytecodes::_dconst_1:
+    case Bytecodes::_bipush:
+    case Bytecodes::_sipush:
+      return createConstantSource(source_bci);
+
+    case Bytecodes::_iaload:
+    case Bytecodes::_faload:
+    case Bytecodes::_aaload:
+    case Bytecodes::_baload:
+    case Bytecodes::_caload:
+    case Bytecodes::_saload:
+    case Bytecodes::_laload:
+    case Bytecodes::_daload: {
+      TrackingStackSource array_source = get_source(source_bci, 1, max_detail - 1);
+      TrackingStackSource index_source = get_source(source_bci, 0, max_detail - 1);
+      return createArraySource(source_bci, array_source, index_source);
+    }
+
+    case Bytecodes::_invokevirtual:
+    case Bytecodes::_invokespecial:
+    case Bytecodes::_invokestatic:
+    case Bytecodes::_invokeinterface: {
+        int cp_index = Bytes::get_native_u2(code_base + pos) DEBUG_ONLY(+ ConstantPool::CPCACHE_INDEX_TAG);
+        return createMethodSource(source_bci, _method, cp_index);
+    }
+
+    case Bytecodes::_getstatic:
+      return createStaticFieldSource(source_bci, _method,
+                                     Bytes::get_native_u2(code_base + pos) + ConstantPool::CPCACHE_INDEX_TAG);
+
+    case Bytecodes::_getfield: {
+      int cp_index = Bytes::get_native_u2(code_base + pos) + ConstantPool::CPCACHE_INDEX_TAG;
+      TrackingStackSource object_source = get_source(source_bci, 0, max_detail - 1);
+      return createFieldSource(source_bci, _method, cp_index, object_source);
+    }
+
+    default:
+      return createInvalidSource(bci);
+  }
+}
+
+int TrackingStackCreator::get_null_pointer_slot(int bci, char const** reason) {
+  // If this NPE was created via reflection, we have no real NPE.
+  if (_method->method_holder() == SystemDictionary::reflect_NativeConstructorAccessorImpl_klass()) {
+    return -2;
+  }
+
+  // Get the bytecode.
+  address code_base = _method->constMethod()->code_base();
+  Bytecodes::Code code = Bytecodes::java_code_at(_method, code_base + bci);
+  int pos = bci + 1;
+
+  if (code == Bytecodes::_wide) {
+    code = Bytecodes::java_code_at(_method, code_base + bci + 1);
+    pos += 1;
+  }
+
+  int result = -1;
+
+  switch (code) {
+    case Bytecodes::_iaload:
+      if (reason != NULL) {
+        *reason = "while trying to load from a null int array";
+      }
+
+      result = 1;
+      break;
+
+    case Bytecodes::_faload:
+      if (reason != NULL) {
+        *reason = "while trying to load from a null float array";
+      }
+
+      result = 1;
+      break;
+
+    case Bytecodes::_aaload:
+      if (reason != NULL) {
+        *reason = "while trying to load from a null object array";
+      }
+
+      result = 1;
+      break;
+
+    case Bytecodes::_baload:
+      if (reason != NULL) {
+        *reason = "while trying to load from a null byte (or boolean) array";
+      }
+
+      result = 1;
+      break;
+
+    case Bytecodes::_caload:
+      if (reason != NULL) {
+        *reason = "while trying to load from a null char array";
+      }
+
+      result = 1;
+      break;
+
+    case Bytecodes::_saload:
+      if (reason != NULL) {
+        *reason = "while trying to load from a null short array";
+      }
+
+      result = 1;
+      break;
+
+    case Bytecodes::_laload:
+      if (reason != NULL) {
+        *reason = "while trying to load from a null long array";
+      }
+
+      result = 1;
+      break;
+
+    case Bytecodes::_daload:
+      if (reason != NULL) {
+        *reason = "while trying to load from a null double array";
+      }
+
+      result = 1;
+      break;
+
+    case Bytecodes::_iastore:
+      if (reason != NULL) {
+        *reason = "while trying to store to a null int array";
+      }
+
+      result = 2;
+      break;
+
+    case Bytecodes::_lastore:
+      if (reason != NULL) {
+        *reason = "while trying to store to a null long array";
+      }
+
+      result = 3;
+      break;
+
+    case Bytecodes::_fastore:
+      if (reason != NULL) {
+        *reason = "while trying to store to a null float array";
+      }
+
+      result = 2;
+      break;
+
+    case Bytecodes::_dastore:
+      if (reason != NULL) {
+        *reason = "while trying to store to a null double array";
+      }
+
+      result = 3;
+      break;
+
+    case Bytecodes::_aastore:
+      if (reason != NULL) {
+        *reason = "while trying to store to a null object array";
+      }
+
+      result = 2;
+      break;
+
+    case Bytecodes::_bastore:
+      if (reason != NULL) {
+        *reason = "while trying to store to a null byte (or boolean) array";
+      }
+
+      result = 2;
+      break;
+
+    case Bytecodes::_castore:
+      if (reason != NULL) {
+        *reason = "while trying to store to a null char array";
+      }
+
+      result = 2;
+      break;
+
+    case Bytecodes::_sastore:
+      if (reason != NULL) {
+        *reason = "while trying to store to a null short array";
+      }
+
+      result = 2;
+      break;
+
+    case Bytecodes::_getfield:
+      {
+        if (reason != NULL) {
+          int cp_index = Bytes::get_native_u2(code_base + pos) DEBUG_ONLY(+ ConstantPool::CPCACHE_INDEX_TAG);
+          ConstantPool* cp = _method->constants();
+          int name_and_type_index = cp->name_and_type_ref_index_at(cp_index);
+          int name_index = cp->name_ref_index_at(name_and_type_index);
+          Symbol* name = cp->symbol_at(name_index);
+          stringStream ss;
+          ss.print("while trying to read the field '%s' of a null object", name->as_C_string());
+          *reason = ss.as_string();
+        }
+
+        result = 0;
+      }
+
+      break;
+
+    case Bytecodes::_arraylength:
+      if (reason != NULL) {
+        *reason = "while trying to get the length of a null array";
+      }
+
+      result = 0;
+      break;
+
+    case Bytecodes::_athrow:
+      if (reason != NULL) {
+        *reason = "while trying to throw a null exception object";
+      }
+
+      result = 0;
+      break;
+
+    case Bytecodes::_monitorenter:
+      if (reason != NULL) {
+        *reason = "while trying to enter a null monitor";
+      }
+
+      result = 0;
+      break;
+
+    case Bytecodes::_monitorexit:
+      if (reason != NULL) {
+        *reason = "while trying to exit a null monitor";
+      }
+
+      result = 0;
+      break;
+
+    case Bytecodes::_putfield:
+      {
+        int cp_index = Bytes::get_native_u2(code_base + pos) DEBUG_ONLY(+ ConstantPool::CPCACHE_INDEX_TAG);
+        ConstantPool* cp = _method->constants();
+        int name_and_type_index = cp->name_and_type_ref_index_at(cp_index);
+        int type_index = cp->signature_ref_index_at(name_and_type_index);
+        Symbol* signature = cp->symbol_at(type_index);
+
+        if (reason != NULL) {
+          stringStream ss;
+          ss.print("while trying to write the field '%s' of a null object",
+                   MethodBytecodePrinter::get_field_and_class(_method, cp_index));
+          *reason = ss.as_string();
+        }
+
+        result = type2size[char2type((char) signature->char_at(0))];
+      }
+
+      break;
+
+    case Bytecodes::_invokevirtual:
+    case Bytecodes::_invokespecial:
+    case Bytecodes::_invokeinterface:
+      {
+        int cp_index = Bytes::get_native_u2(code_base+ pos) DEBUG_ONLY(+ ConstantPool::CPCACHE_INDEX_TAG);
+        ConstantPool* cp = _method->constants();
+        int name_and_type_index = cp->name_and_type_ref_index_at(cp_index);
+        int name_index = cp->name_ref_index_at(name_and_type_index);
+        int type_index = cp->signature_ref_index_at(name_and_type_index);
+        Symbol* name = cp->symbol_at(name_index);
+        Symbol* signature = cp->symbol_at(type_index);
+
+        // Assume the the call of a constructor can never cause a NullPointerException
+        // (which is true in Java). This is mainly used to avoid generating wrong
+        // messages for NullPointerExceptions created explicitly by new in Java code.
+        if (name != vmSymbols::object_initializer_name()) {
+          if (reason != NULL) {
+            stringStream ss;
+            ss.print("while trying to invoke the method '%s' on a null reference",
+                     MethodBytecodePrinter::get_method_name(_method, cp_index));
+            *reason = ss.as_string();
+          }
+
+          result = ArgumentSizeComputer(signature).size();
+        }
+        else {
+          result = -2;
+        }
+      }
+
+      break;
+
+    default:
+      break;
+  }
+
+  return result;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/hotspot/share/interpreter/bytecodeUtils.hpp	Fri Feb 08 14:15:05 2019 +0100
@@ -0,0 +1,257 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2019 SAP SE. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ *
+ */
+
+#ifndef SHARE_CLASSFILE_BYTECODEUTILS_HPP
+#define SHARE_CLASSFILE_BYTECODEUTILS_HPP
+
+#include "memory/allocation.hpp"
+#include "oops/method.hpp"
+#include "utilities/growableArray.hpp"
+
+
+// A class which can be used to print out the bytecode
+// of a method.
+// NOTE: The method must already be rewritten.
+class MethodBytecodePrinter : public AllStatic {
+ public:
+
+  // Returns the external (using '.') name of the class at the given cp index in an
+  // area allocated string.
+  static char const* get_klass_name(Method* method, int cp_index);
+
+  // Returns the name of the method(including signature, but without
+  // the return type) at the given cp index in a resource area
+  // allocated string.
+  static char const* get_method_name(Method* method, int cp_index);
+
+  // Returns the name of the field at the given cp index in
+  // a resource area allocated string.
+  static char const *get_field_name(Method* method, int cp_index);
+
+  // Returns the name and class of the field at the given cp index in
+  // a resource area allocated string.
+  static char const* get_field_and_class(Method* method, int cp_index);
+};
+
+class TrackingStack;
+class TrackingStackCreator;
+
+// The entry of TrackingStack.
+class TrackingStackEntry {
+ private:
+
+  friend class TrackingStack;
+  friend class TrackingStackCreator;
+
+  // The raw entry composed of the type and the bci.
+  int _entry;
+
+  enum {
+    SCALE = 1024 * 1024
+  };
+
+  // Merges this entry with the given one and returns the result. If
+  // the bcis of the entry are different, the bci of the result will be
+  // undefined. If the types are different, the result type is T_CONFLICT.
+  // (an exception is if one type is an array and the other is object, then
+  // the result type will be T_OBJECT).
+  TrackingStackEntry merge(TrackingStackEntry other);
+public:
+  // Creates a new entry with an invalid bci and the given type.
+  TrackingStackEntry(BasicType type = T_CONFLICT);
+
+  // Creates a new entry with the given bci and type.
+  TrackingStackEntry(int bci, BasicType type);
+
+ public:
+
+  enum {
+    INVALID = SCALE - 1
+  };
+
+  // Returns the bci. If the bci is invalid, INVALID is returned.
+  int get_bci();
+
+  // Returns true, if the bci is not invalid.
+  bool has_bci() { return get_bci() != INVALID; }
+
+  // Returns the type of the entry.
+  BasicType get_type();
+};
+
+// A stack consisting of TrackingStackEntries.
+class TrackingStack: CHeapObj<mtInternal> {
+
+ private:
+
+  friend class TrackingStackCreator;
+  friend class TrackingStackEntry;
+
+  // The stack.
+  GrowableArray<TrackingStackEntry> _stack;
+
+  TrackingStack() { };
+  TrackingStack(const TrackingStack &copy);
+
+  // Pushes the given entry.
+  void push_raw(TrackingStackEntry entry);
+
+  // Like push_raw, but if the entry is long or double, we push two.
+  void push(TrackingStackEntry entry);
+
+  // Like push(entry), but using bci/type instead of entry.
+  void push(int bci, BasicType type);
+
+  // Pops the given number of entries.
+  void pop(int slots);
+
+  // Merges this with the given stack by merging all entries. The
+  // size of the stacks must be the same.
+  void merge(TrackingStack const& other);
+
+ public:
+
+  // Returns the size of the stack.
+  int get_size() const;
+
+  // Returns the entry with the given index. Top of stack is at index 0.
+  TrackingStackEntry get_entry(int slot);
+};
+
+// Defines a source of a slot of the operand stack.
+class TrackingStackSource {
+
+ public:
+
+  enum Type {
+    // If the value was loaded from a local variable.
+    LOCAL_VAR,
+
+    // If the value was returned from a method.
+    METHOD,
+
+    // If the value was loaded from an array.
+    ARRAY_ELEM,
+
+    // If the value was loaded from a field.
+    FIELD_ELEM,
+
+    // If the value was from a constant.
+    CONSTANT,
+
+    // If the source is invalid.
+    INVALID
+  };
+
+ private:
+
+  const char *_reason;
+
+  Type _type;
+  int _bci;
+
+ public:
+
+  TrackingStackSource(Type type, int bci, const char * reason) : _reason(reason), _type(type), _bci(bci) { }
+
+  // Returns the type.
+  Type get_type() const {
+    return _type;
+  }
+
+  // Returns a human readable string describing the source.
+  char const* as_string() const {
+    return _reason;
+  }
+};
+
+// Analyses the bytecodes of a method and tries to create a tracking
+// stack for each bci. The tracking stack holds the bci and type of
+// the objec on the stack. The bci (if valid) holds the bci of the
+// instruction, which put the entry on the stack.
+class TrackingStackCreator {
+
+  // The stacks.
+  GrowableArray<TrackingStack*>* _stacks;
+
+  // The method.
+  Method* _method;
+
+  // The maximum number of entries we want to use. This is used to
+  // limit the amount of memory we waste for insane methods (as they
+  // appear in JCK tests).
+  int _max_entries;
+
+  // The number of entries used (the sum of all entries of all stacks).
+  int _nr_of_entries;
+
+  // If true, we have added at least one new stack.
+  bool _added_one;
+
+  // If true, we have processed all bytecodes.
+  bool _all_processed;
+
+  // Merges the stack the the given bci with the given stack. If there
+  // is no stack at the bci, we just put the given stack there. This
+  // method doesn't takes ownership of the stack.
+  void merge(int bci, TrackingStack* stack);
+
+  // Processes the instruction at the given bci in the method. Returns
+  // the size of the instruction.
+  int do_instruction(int bci);
+
+ public:
+
+  // Creates tracking stacks for the given method (the method must be
+  // rewritten already). Note that you're not allowed to use this object
+  // when crossing a safepoint! If the bci is != -1, we only create the
+  // stacks as far as needed to get a stack for the bci.
+  TrackingStackCreator(Method* method, int bci = -1);
+
+  // Releases the resources.
+  ~TrackingStackCreator();
+
+  // Returns the number of stacks (this is the size of the method).
+  int get_size() { return _stacks->length() - 1; }
+
+  // Returns the source of the value in the given slot at the given bci.
+  // The TOS has the slot number 0, that below 1 and so on. You have to
+  // delete the returned object via 'delete'. 'max_detail' is the number
+  // of levels for which we include sources recursively (e.g. for a source
+  // which was from an array and the array was loaded from field of an
+  // object which ...). The larger the value, the more detailed the source.
+  TrackingStackSource get_source(int bci, int slot, int max_detail);
+
+  // Assuming that a NullPointerException was thrown at the given bci,
+  // we return the nr of the slot holding the null reference. If this
+  // NPE is created by hand, we return -2 as the slot. If there
+  // cannot be a NullPointerException at the bci, -1 is returned. If
+  // 'reason' is != NULL, a description is stored, which is allocated
+  // statically or in the resource area.
+  int get_null_pointer_slot(int bci, char const** reason);
+
+};
+
+#endif // SHARE_CLASSFILE_BYTECODEUTILS_HPP
--- a/src/hotspot/share/prims/jvm.cpp	Tue Mar 19 17:03:18 2019 +0800
+++ b/src/hotspot/share/prims/jvm.cpp	Fri Feb 08 14:15:05 2019 +0100
@@ -37,6 +37,7 @@
 #include "classfile/vmSymbols.hpp"
 #include "gc/shared/collectedHeap.inline.hpp"
 #include "interpreter/bytecode.hpp"
+#include "interpreter/bytecodeUtils.hpp"
 #include "jfr/jfrEvents.hpp"
 #include "logging/log.hpp"
 #include "memory/heapShared.hpp"
@@ -534,13 +535,52 @@
 
 // java.lang.Throwable //////////////////////////////////////////////////////
 
-
 JVM_ENTRY(void, JVM_FillInStackTrace(JNIEnv *env, jobject receiver))
   JVMWrapper("JVM_FillInStackTrace");
   Handle exception(thread, JNIHandles::resolve_non_null(receiver));
   java_lang_Throwable::fill_in_stack_trace(exception);
 JVM_END
 
+// java.lang.NullPointerException ///////////////////////////////////////////
+
+JVM_ENTRY(jstring, JVM_GetExtendedNPEMessage(JNIEnv *env, jthrowable throwable))
+  oop exc = JNIHandles::resolve_non_null(throwable);
+
+  Method* method;
+  int bci;
+  if (!java_lang_Throwable::get_method_and_bci(exc, &method, &bci)) {
+    return NULL;
+  }
+  if (method->is_native()) {
+    return NULL;
+  }
+
+  ResourceMark rm(THREAD);
+  TrackingStackCreator stc(method, bci);
+  char const* reason;
+  int slot = stc.get_null_pointer_slot(bci, &reason);
+
+  // Build the message.
+  stringStream ss;
+  if (slot == -2) {
+    return NULL;
+  } else if (slot == -1) {
+    ss.print("There cannot be a NullPointerException at bci %d of method %s",
+             bci, method->name_and_sig_as_C_string());
+  } else if (reason == NULL) {
+    ss.print("Cannot get the reason for the NullPointerException at bci %d of method %s",
+             bci, method->name_and_sig_as_C_string());
+  } else {
+    TrackingStackSource source = stc.get_source(bci, slot, 2);
+    ss.print("%s", reason);
+    if (source.get_type() != TrackingStackSource::INVALID) {
+      ss.print(" %s", source.as_string());
+    }
+  }
+
+  oop result = java_lang_String::create_oop_from_str(ss.as_string(), CHECK_0);
+  return (jstring) JNIHandles::make_local(env, result);
+JVM_END
 
 // java.lang.StackTraceElement //////////////////////////////////////////////
 
--- a/src/java.base/share/classes/java/lang/NullPointerException.java	Tue Mar 19 17:03:18 2019 +0800
+++ b/src/java.base/share/classes/java/lang/NullPointerException.java	Fri Feb 08 14:15:05 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1994, 2011, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1994, 2019, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -69,4 +69,29 @@
     public NullPointerException(String s) {
         super(s);
     }
+
+    /**
+     * Returns the detail message string of this throwable.
+     *
+     * If no explicit message was passed to the constructor, and as
+     * long as certain internal information is available, a verbose
+     * description of the null entity is returned. After releasing the
+     * internal information, e.g., after serialization, null will be
+     * returned in this case.
+     *
+     * @return the detail message string of this {@code NullPointerException} instance
+     *         (which may be {@code null}).
+     */
+    public String getMessage() {
+        String message = super.getMessage();
+        if (message == null) {
+            return getExtendedNPEMessage();
+        }
+        return message;
+    }
+
+    // Get an extended exception message. This returns a string describing
+    // the location and cause of the exception. It returns null for
+    // exceptions where this is not applicable.
+    private native String getExtendedNPEMessage();
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/native/libjava/NullPointerException.c	Fri Feb 08 14:15:05 2019 +0100
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2019 SAP SE. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include "jni.h"
+#include "jvm.h"
+
+#include "java_lang_NullPointerException.h"
+
+JNIEXPORT jstring JNICALL
+Java_java_lang_NullPointerException_getExtendedNPEMessage(JNIEnv *env, jobject throwable)
+{
+    return JVM_GetExtendedNPEMessage(env, throwable);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/hotspot/jtreg/runtime/exceptionMsgs/NullPointerException/NullPointerExceptionTest.java	Fri Feb 08 14:15:05 2019 +0100
@@ -0,0 +1,1236 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2019 SAP SE. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/**
+ * @test
+ * @summary Test extended NullPointerException message for class
+ *   files generated without debugging information. The message lists
+ *   detailed information about the entity that is null.
+ * @library /test/lib
+ * @compile NullPointerExceptionTest.java
+ * @run main NullPointerExceptionTest
+ */
+/**
+ * @test
+ * @summary Test extended NullPointerException message for
+ *   classfiles generated with debug information. In this case the name
+ *   of the variable containing the array is printed.
+ * @library /test/lib
+ * @compile -g NullPointerExceptionTest.java
+ * @run main/othervm -XX:+WizardMode -DhasDebugInfo NullPointerExceptionTest
+ */
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.ArrayList;
+
+import jdk.test.lib.Asserts;
+
+/**
+ * Tests NullPointerExceptions
+ */
+public class NullPointerExceptionTest {
+    // Some fields used in the test.
+    static Object nullStaticField;
+    NullPointerExceptionTest nullInstanceField;
+    static int[][][][] staticArray;
+    static long[][] staticLongArray = new long[1000][];
+    DoubleArrayGen dag;
+    ArrayList<String> names = new ArrayList<>();
+    ArrayList<String> curr;
+    static boolean hasDebugInfo = true;
+
+    static {
+        try {
+            hasDebugInfo = System.getProperty("hasDebugInfo") != null;
+        } catch (Throwable t) {
+            throw new RuntimeException(t);
+        }
+
+        staticArray       = new int[1][][][];
+        staticArray[0]    = new int[1][][];
+        staticArray[0][0] = new int[1][];
+    }
+
+    public static void checkMessage(String expression,
+                                    String obtainedMsg, String expectedMsg) {
+        System.out.println();
+        System.out.println(" source code: " + expression);
+        System.out.println("  thrown msg: " + obtainedMsg);
+        System.out.println("expected msg: " + expectedMsg);
+        Asserts.assertEquals(obtainedMsg, expectedMsg);
+    }
+
+    public static void main(String[] args) throws Exception {
+        NullPointerExceptionTest t = new NullPointerExceptionTest();
+        t.testPointerChasing();
+        t.testArrayChasing();
+        t.testMethodChasing();
+        t.testMixedChasing();
+        t.testSameMessage();
+        t.testCreationViaNew();
+        t.testCreationViaReflection();
+        t.testCreationViaSerialization();
+        t.testLoadedFromLocalVariable1();
+        t.testLoadedFromLocalVariable2();
+        t.testLoadedFromLocalVariable3();
+        t.testLoadedFromLocalVariable4();
+        t.testLoadedFromLocalVariable5();
+        t.testLoadedFromLocalVariable6();
+        t.testLoadedFromLocalVariable7();
+        t.testLoadedFromMethod1();
+        t.testLoadedFromMethod2();
+        t.testLoadedFromMethod3();
+        t.testLoadedFromMethod4();
+        t.testLoadedFromMethod5();
+        t.testLoadedFromMethod6();
+        t.testLoadedFromMethod7();
+        t.testLoadedFromStaticField1();
+        t.testLoadedFromStaticField2();
+        t.testLoadedFromStaticField3();
+        t.testLoadedFromStaticField4(0, 0);
+        t.testLoadedFromStaticField5();
+        t.testLoadedFromStaticField5a();
+        t.testLoadedFromStaticField5b();
+        t.testLoadedFromStaticField6();
+        t.testLoadedFromInstanceField1();
+        t.testLoadedFromInstanceField2();
+        t.testLoadedFromInstanceField3();
+        t.testLoadedFromInstanceField4();
+        t.testLoadedFromInstanceField5();
+        t.testLoadedFromInstanceField6();
+        t.testInNative();
+        t.testMissingLocalVariableTable();
+        t.testNullMessages();
+    }
+
+    class A {
+        public B to_b;
+        public B getB() { return to_b; }
+    }
+
+    class B {
+        public C to_c;
+        public B to_b;
+        public C getC() { return to_c; }
+        public B getBfromB() { return to_b; }
+    }
+
+    class C {
+        public D to_d;
+        public D getD() { return to_d; }
+    }
+
+    class D {
+        public int num;
+        public int[][] ar;
+    }
+
+    public void testPointerChasing() {
+        A a = null;
+        try {
+            a.to_b.to_c.to_d.num = 99;
+            Asserts.fail();
+        } catch (NullPointerException e) {
+            checkMessage("a.to_b.to_c.to_d.num = 99 // a is null", e.getMessage(),
+                         "while trying to read the field 'to_b' of a null object loaded from " +
+                         (hasDebugInfo ? "local variable 'a'" : "a local variable at slot 1"));
+        }
+        a = new A();
+        try {
+            a.to_b.to_c.to_d.num = 99;
+            Asserts.fail();
+        } catch (NullPointerException e) {
+            checkMessage("a.to_b.to_c.to_d.num = 99 // a.to_b is null", e.getMessage(),
+                         "while trying to read the field 'to_c' of a null object loaded from field 'NullPointerExceptionTest$A.to_b' of an object loaded from " +
+                         (hasDebugInfo ? "local variable 'a'" : "a local variable at slot 1"));
+        }
+        a.to_b = new B();
+        try {
+            a.to_b.to_c.to_d.num = 99;
+            Asserts.fail();
+        } catch (NullPointerException e) {
+            checkMessage("a.to_b.to_c.to_d.num = 99 // a.to_b.to_c is null", e.getMessage(),
+                         "while trying to read the field 'to_d' of a null object loaded from field 'NullPointerExceptionTest$B.to_c' of an object loaded from field 'NullPointerExceptionTest$A.to_b' of an object");
+        }
+        a.to_b.to_c = new C();
+        try {
+            a.to_b.to_c.to_d.num = 99;
+            Asserts.fail();
+        } catch (NullPointerException e) {
+            checkMessage("a.to_b.to_c.to_d.num = 99 // a.to_b.to_c.to_d is null", e.getMessage(),
+                         "while trying to write the field 'NullPointerExceptionTest$D.num' of a null object loaded from field 'NullPointerExceptionTest$C.to_d' of an object loaded from field 'NullPointerExceptionTest$B.to_c' of an object");
+        }
+    }
+
+    public void testArrayChasing() {
+        int[][][][][] a = null;
+        try {
+            a[0][0][0][0][0] = 99;
+            Asserts.fail();
+        } catch (NullPointerException e) {
+            checkMessage("int[0][0][0][0][0] = 99 // a is null", e.getMessage(),
+                         "while trying to load from a null object array loaded from " +
+                         (hasDebugInfo ? "local variable 'a'" : "a local variable at slot 1"));
+        }
+        a = new int[1][][][][];
+        try {
+            a[0][0][0][0][0] = 99;
+            Asserts.fail();
+        } catch (NullPointerException e) {
+            checkMessage("int[0][0][0][0][0] = 99 // a[0] is null", e.getMessage(),
+                         "while trying to load from a null object array loaded from an array (which itself was loaded from " +
+                         (hasDebugInfo ? "local variable 'a'" : "a local variable at slot 1") +
+                         ") with an index loaded from a constant");
+        }
+        a[0] = new int[1][][][];
+        try {
+            a[0][0][0][0][0] = 99;
+            Asserts.fail();
+        } catch (NullPointerException e) {
+            checkMessage("int[0][0][0][0][0] = 99 // a[0][0] is null", e.getMessage(),
+                         "while trying to load from a null object array loaded from an array (which itself was loaded from an array) with an index loaded from a constant");
+        }
+        a[0][0] = new int[1][][];
+        try {
+            a[0][0][0][0][0] = 99;
+            Asserts.fail();
+        } catch (NullPointerException e) {
+            checkMessage("int[0][0][0][0][0] = 99 // a[0][0][0] is null", e.getMessage(),
+                         "while trying to load from a null object array loaded from an array (which itself was loaded from an array) with an index loaded from a constant");
+        }
+        a[0][0][0] = new int[1][];
+        try {
+            a[0][0][0][0][0] = 99;
+            Asserts.fail();
+        } catch (NullPointerException e) {
+            checkMessage("int[0][0][0][0][0] = 99 // a[0][0][0][0] is null", e.getMessage(),
+                         "while trying to store to a null int array loaded from an array (which itself was loaded from an array) with an index loaded from a constant");
+        }
+        a[0][0][0][0] = new int[1];
+        try {
+            a[0][0][0][0][0] = 99;
+        } catch (NullPointerException e) {
+            Asserts.fail();
+        }
+    }
+
+    public void testMethodChasing() {
+        A a = null;
+        try {
+            a.getB().getBfromB().getC().getD().num = 99;
+            Asserts.fail();
+        } catch (NullPointerException e) {
+            checkMessage("a.getB().getBfromB().getC().getD().num = 99 // a is null", e.getMessage(),
+                         "while trying to invoke the method 'NullPointerExceptionTest$A.getB()LNullPointerExceptionTest$B;' on a null reference loaded from " +
+                         (hasDebugInfo ? "local variable 'a'" : "a local variable at slot 1"));
+        }
+        a = new A();
+        try {
+            a.getB().getBfromB().getC().getD().num = 99;
+            Asserts.fail();
+        } catch (NullPointerException e) {
+            checkMessage("a.getB().getBfromB().getC().getD().num = 99 // a.getB() is null", e.getMessage(),
+                         "while trying to invoke the method 'NullPointerExceptionTest$B.getBfromB()LNullPointerExceptionTest$B;' on a null reference returned from 'NullPointerExceptionTest$A.getB()LNullPointerExceptionTest$B;'");
+        }
+        a.to_b = new B();
+        try {
+            a.getB().getBfromB().getC().getD().num = 99;
+            Asserts.fail();
+        } catch (NullPointerException e) {
+            checkMessage("a.getB().getBfromB().getC().getD().num = 99 // a.getB().getBfromB() is null", e.getMessage(),
+                         "while trying to invoke the method 'NullPointerExceptionTest$B.getC()LNullPointerExceptionTest$C;' on a null reference returned from 'NullPointerExceptionTest$B.getBfromB()LNullPointerExceptionTest$B;'");
+        }
+        a.to_b.to_b = new B();
+        try {
+            a.getB().getBfromB().getC().getD().num = 99;
+            Asserts.fail();
+        } catch (NullPointerException e) {
+            checkMessage("a.getB().getBfromB().getC().getD().num = 99 // a.getB().getBfromB().getC() is null", e.getMessage(),
+                         "while trying to invoke the method 'NullPointerExceptionTest$C.getD()LNullPointerExceptionTest$D;' on a null reference returned from 'NullPointerExceptionTest$B.getC()LNullPointerExceptionTest$C;'");
+        }
+        a.to_b.to_b.to_c = new C();
+        try {
+            a.getB().getBfromB().getC().getD().num = 99;
+            Asserts.fail();
+        } catch (NullPointerException e) {
+            checkMessage("a.getB().getBfromB().getC().getD().num = 99 // a.getB().getBfromB().getC().getD() is null", e.getMessage(),
+                         "while trying to write the field 'NullPointerExceptionTest$D.num' of a null object returned from 'NullPointerExceptionTest$C.getD()LNullPointerExceptionTest$D;'");
+        }
+    }
+
+    public void testMixedChasing() {
+        A a = null;
+        try {
+            a.getB().getBfromB().to_c.to_d.ar[0][0] = 99;
+            Asserts.fail();
+        } catch (NullPointerException e) {
+            checkMessage("a.getB().getBfromB().to_c.to_d.ar[0][0] = 99; // a is null", e.getMessage(),
+                         "while trying to invoke the method 'NullPointerExceptionTest$A.getB()LNullPointerExceptionTest$B;' on a null reference loaded from " +
+                         (hasDebugInfo ? "local variable 'a'" : "a local variable at slot 1"));
+        }
+        a = new A();
+        try {
+            a.getB().getBfromB().to_c.to_d.ar[0][0] = 99;
+            Asserts.fail();
+        } catch (NullPointerException e) {
+            checkMessage("a.getB().getBfromB().to_c.to_d.ar[0][0] = 99; // a.getB() is null", e.getMessage(),
+                         "while trying to invoke the method 'NullPointerExceptionTest$B.getBfromB()LNullPointerExceptionTest$B;' on a null reference returned from 'NullPointerExceptionTest$A.getB()LNullPointerExceptionTest$B;'");
+        }
+        a.to_b = new B();
+        try {
+            a.getB().getBfromB().to_c.to_d.ar[0][0] = 99;
+            Asserts.fail();
+        } catch (NullPointerException e) {
+            checkMessage("a.getB().getBfromB().to_c.to_d.ar[0][0] = 99; // a.getB().getBfromB() is null", e.getMessage(),
+                         "while trying to read the field 'to_c' of a null object returned from 'NullPointerExceptionTest$B.getBfromB()LNullPointerExceptionTest$B;'");
+        }
+        a.to_b.to_b = new B();
+        try {
+            a.getB().getBfromB().to_c.to_d.ar[0][0] = 99;
+            Asserts.fail();
+        } catch (NullPointerException e) {
+            checkMessage("a.getB().getBfromB().to_c.to_d.ar[0][0] = 99; // a.getB().getBfromB().to_c is null", e.getMessage(),
+                         "while trying to read the field 'to_d' of a null object loaded from field 'NullPointerExceptionTest$B.to_c' of an object returned from 'NullPointerExceptionTest$B.getBfromB()LNullPointerExceptionTest$B;'");
+        }
+        a.to_b.to_b.to_c = new C();
+        try {
+            a.getB().getBfromB().to_c.to_d.ar[0][0] = 99;
+            Asserts.fail();
+        } catch (NullPointerException e) {
+            checkMessage("a.getB().getBfromB().to_c.to_d.ar[0][0] = 99; // a.getB().getBfromB().to_c.to_d is null", e.getMessage(),
+                         "while trying to read the field 'ar' of a null object loaded from field 'NullPointerExceptionTest$C.to_d' of an object loaded from field 'NullPointerExceptionTest$B.to_c' of an object");
+        }
+        a.to_b.to_b.to_c.to_d = new D();
+        try {
+            a.getB().getBfromB().to_c.to_d.ar[0][0] = 99;
+            Asserts.fail();
+        } catch (NullPointerException e) {
+            checkMessage("a.getB().getBfromB().to_c.to_d.ar[0][0] = 99; // a.getB().getBfromB().to_c.to_d.ar is null", e.getMessage(),
+                         "while trying to load from a null object array loaded from field 'NullPointerExceptionTest$D.ar' of an object loaded from field 'NullPointerExceptionTest$C.to_d' of an object");
+        }
+        try {
+            a.getB().getBfromB().getC().getD().ar[0][0] = 99;
+            Asserts.fail();
+        } catch (NullPointerException e) {
+            checkMessage("a.getB().getBfromB().getC().getD().ar[0][0] = 99; // a.getB().getBfromB().getC().getD().ar is null", e.getMessage(),
+                         "while trying to load from a null object array loaded from field 'NullPointerExceptionTest$D.ar' of an object returned from 'NullPointerExceptionTest$C.getD()LNullPointerExceptionTest$D;'");
+        }
+        a.to_b.to_b.to_c.to_d.ar = new int[1][];
+        try {
+            a.getB().getBfromB().to_c.to_d.ar[0][0] = 99;
+            Asserts.fail();
+        } catch (NullPointerException e) {
+            checkMessage("a.getB().getBfromB().to_c.to_d.ar[0][0] = 99; // a.getB().getBfromB().to_c.to_d.ar[0] is null", e.getMessage(),
+                         "while trying to store to a null int array loaded from an array (which itself was loaded from field 'NullPointerExceptionTest$D.ar' of an object) with an index loaded from a constant");
+        }
+        try {
+            a.getB().getBfromB().getC().getD().ar[0][0] = 99;
+            Asserts.fail();
+        } catch (NullPointerException e) {
+            checkMessage("a.getB().getBfromB().getC().getD().ar[0][0] = 99; // a.getB().getBfromB().getC().getD().ar[0] is null", e.getMessage(),
+                         "while trying to store to a null int array loaded from an array (which itself was loaded from field 'NullPointerExceptionTest$D.ar' of an object) with an index loaded from a constant");
+        }
+    }
+
+
+    // Test we get the same message calling npe.getMessage() twice.
+    public void testSameMessage() throws Exception {
+        Object null_o = null;
+        String expectedMsg =
+            "while trying to invoke the method 'java.lang.Object.hashCode()I'" +
+            " on a null reference loaded from " +
+            (hasDebugInfo ? "local variable 'null_o'" : "a local variable at slot 1");
+
+        try {
+            null_o.hashCode();
+            Asserts.fail();
+        } catch (NullPointerException npe) {
+            String msg1 = npe.getMessage();
+            checkMessage("null_o.hashCode()", msg1, expectedMsg);
+            String msg2 = npe.getMessage();
+            Asserts.assertTrue(msg1.equals(msg2));
+            // It was decided that getMessage should generate the
+            // message anew on every call, so this does not hold any more.
+            Asserts.assertFalse(msg1 == msg2);
+        }
+    }
+
+    /**
+     *
+     */
+    public void testCreationViaNew() {
+        Asserts.assertNull(new NullPointerException().getMessage());
+    }
+
+    /**
+     * @throws Exception
+     */
+    public void testCreationViaReflection() throws Exception {
+        Exception ex = NullPointerException.class.getDeclaredConstructor().newInstance();
+        Asserts.assertNull(ex.getMessage());
+    }
+
+    /**
+     * @throws Exception
+     */
+    public void testCreationViaSerialization() throws Exception {
+        // NPE without message.
+        Object o1 = new NullPointerException();
+        ByteArrayOutputStream bos1 = new ByteArrayOutputStream();
+        ObjectOutputStream oos1 = new ObjectOutputStream(bos1);
+        oos1.writeObject(o1);
+        ByteArrayInputStream bis1 = new ByteArrayInputStream(bos1.toByteArray());
+        ObjectInputStream ois1 = new ObjectInputStream(bis1);
+        Exception ex1 = (Exception) ois1.readObject();
+        Asserts.assertNull(ex1.getMessage());
+
+        // NPE with custom message.
+        String msg2 = "A useless message";
+        Object o2 = new NullPointerException(msg2);
+        ByteArrayOutputStream bos2 = new ByteArrayOutputStream();
+        ObjectOutputStream oos2 = new ObjectOutputStream(bos2);
+        oos2.writeObject(o2);
+        ByteArrayInputStream bis2 = new ByteArrayInputStream(bos2.toByteArray());
+        ObjectInputStream ois2 = new ObjectInputStream(bis2);
+        Exception ex2 = (Exception) ois2.readObject();
+        Asserts.assertEquals(ex2.getMessage(), msg2);
+
+        // NPE with generated message.
+        Object null_o3 = null;
+        Object o3 = null;
+        String msg3 = null;
+        try {
+            null_o3.hashCode();
+            Asserts.fail();
+        } catch (NullPointerException npe3) {
+            o3 = npe3;
+            msg3 = npe3.getMessage();
+            checkMessage("null_o3.hashCode()", msg3, "while trying to invoke the method 'java.lang.Object.hashCode()I'" +
+                                 " on a null reference loaded from " +
+                                 (hasDebugInfo ? "local variable 'null_o3'" : "a local variable at slot 14"));
+        }
+        ByteArrayOutputStream bos3 = new ByteArrayOutputStream();
+        ObjectOutputStream oos3 = new ObjectOutputStream(bos3);
+        oos3.writeObject(o3);
+        ByteArrayInputStream bis3 = new ByteArrayInputStream(bos3.toByteArray());
+        ObjectInputStream ois3 = new ObjectInputStream(bis3);
+        Exception ex3 = (Exception) ois3.readObject();
+        // It was decided that getMessage should not store the
+        // message in Throwable.detailMessage, thus it can not
+        // be recovered by serialization.
+        //Asserts.assertEquals(ex3.getMessage(), msg3);
+        Asserts.assertEquals(ex3.getMessage(), null);
+    }
+
+    /**
+     *
+     */
+    @SuppressWarnings("null")
+    public void testLoadedFromLocalVariable1() {
+        Object o = null;
+
+        try {
+            o.hashCode();
+            Asserts.fail();
+        } catch (NullPointerException e) {
+            checkMessage("o.hashCode()", e.getMessage(), "while trying to invoke the method 'java.lang.Object.hashCode()I' on a null reference loaded from " + (hasDebugInfo ? "local variable 'o'" : "a local variable at slot 1"));
+        }
+    }
+
+    /**
+     *
+     */
+    @SuppressWarnings("null")
+    public void testLoadedFromLocalVariable2() {
+        Exception[] myVariable = null;
+
+        try {
+            Asserts.assertNull(myVariable[0]);
+            Asserts.fail();
+        } catch (NullPointerException e) {
+            checkMessage("myVariable[0]", e.getMessage(), "while trying to load from a null object array loaded from " + (hasDebugInfo ? "local variable 'myVariable'" : "a local variable at slot 1"));
+        }
+    }
+
+    /**
+     *
+     */
+    @SuppressWarnings("null")
+    public void testLoadedFromLocalVariable3() {
+        Exception[] myVariable = null;
+
+        try {
+            myVariable[0] = null;
+            Asserts.fail();
+        } catch (NullPointerException e) {
+            checkMessage("myVariable[0] = null", e.getMessage(), "while trying to store to a null object array loaded from " + (hasDebugInfo ? "local variable 'myVariable'" : "a local variable at slot 1"));
+        }
+    }
+
+    /**
+     *
+     */
+    @SuppressWarnings("null")
+    public void testLoadedFromLocalVariable4() {
+        Exception[] myVariable\u0096 = null;
+
+        try {
+            Asserts.assertTrue(myVariable\u0096.length == 0);
+            Asserts.fail();
+        } catch (NullPointerException e) {
+            checkMessage("myVariable\u0096.length", e.getMessage(), "while trying to get the length of a null array loaded from " + (hasDebugInfo ? "local variable 'myVariable'" : "a local variable at slot 1"));
+        }
+    }
+
+    /**
+     * @throws Exception
+     */
+    @SuppressWarnings("null")
+    public void testLoadedFromLocalVariable5() throws Exception {
+        Exception myException = null;
+
+        try {
+            throw myException;
+        } catch (NullPointerException e) {
+            checkMessage("throw myException", e.getMessage(), "while trying to throw a null exception object loaded from " + (hasDebugInfo ? "local variable 'myException'" : "a local variable at slot 1"));
+        }
+    }
+
+    /**
+     *
+     */
+    @SuppressWarnings("null")
+    public void testLoadedFromLocalVariable6() {
+        byte[] myVariable = null;
+        int my_index = 1;
+
+        try {
+            Asserts.assertTrue(myVariable[my_index] == 0);
+            Asserts.fail();
+        } catch (NullPointerException e) {
+            checkMessage("myVariable[my_index]", e.getMessage(), "while trying to load from a null byte (or boolean) array loaded from " + (hasDebugInfo ? "local variable 'myVariable'" : "a local variable at slot 1"));
+        }
+    }
+
+    /**
+     *
+     */
+    @SuppressWarnings("null")
+    public void testLoadedFromLocalVariable7() {
+        byte[] myVariable = null;
+
+        try {
+            myVariable[System.out.hashCode()] = (byte) 0;
+            Asserts.fail();
+        } catch (NullPointerException e) {
+            checkMessage("myVariable[System.out.hashCode()]", e.getMessage(), "while trying to store to a null byte (or boolean) array loaded from " + (hasDebugInfo ? "local variable 'myVariable'" : "a local variable at slot 1"));
+        }
+    }
+
+    /**
+     *
+     */
+    public void testLoadedFromMethod1() {
+        try {
+            Asserts.assertTrue(((char[]) NullPointerGenerator.nullReturner(false))[0] == 'A');
+            Asserts.fail();
+        } catch (NullPointerException e) {
+            checkMessage("((char[]) NullPointerGenerator.nullReturner(false))[0]", e.getMessage(), "while trying to load from a null char array returned from 'NullPointerExceptionTest$NullPointerGenerator.nullReturner(Z)Ljava/lang/Object;'");
+        }
+    }
+
+    /**
+     *
+     */
+    public void testLoadedFromMethod2() {
+        try {
+            Asserts.assertTrue(((char[]) (new NullPointerGenerator().returnMyNull(1, 1, (short) 1)))[0] == 'a');
+            Asserts.fail();
+        } catch (NullPointerException e) {
+            checkMessage("((char[]) (new NullPointerGenerator().returnMyNull(1, 1, (short) 1)))[0]", e.getMessage(), "while trying to load from a null char array returned from 'NullPointerExceptionTest$NullPointerGenerator.returnMyNull(DJS)Ljava/lang/Object;'");
+        }
+    }
+
+    /**
+     *
+     */
+    public void testLoadedFromMethod3() {
+        try {
+            Asserts.assertTrue(((double[]) returnNull(null, null, 1f))[0] == 1.0);
+            Asserts.fail();
+        } catch (NullPointerException e) {
+            checkMessage("((double[]) returnNull(null, null, 1f))[0] ", e.getMessage(), "while trying to load from a null double array returned from 'NullPointerExceptionTest.returnNull([[Ljava/lang/String;[[[IF)Ljava/lang/Object;'");
+        }
+    }
+
+    /**
+     *
+     */
+    public void testLoadedFromMethod4() {
+        ImplTestLoadedFromMethod4(new DoubleArrayGenImpl());
+    }
+
+    /**
+     * @param gen
+     */
+    public void ImplTestLoadedFromMethod4(DoubleArrayGen gen) {
+        try {
+            (gen.getArray())[0] = 1.0;
+            Asserts.fail();
+        } catch (NullPointerException e) {
+            checkMessage("(gen.getArray())[0]", e.getMessage(), "while trying to store to a null double array returned from 'NullPointerExceptionTest$DoubleArrayGen.getArray()[D'");
+        }
+    }
+
+    /**
+     *
+     */
+    public void testLoadedFromMethod5() {
+        try {
+            returnMeAsNull(null, 1, 'A').dag = null;
+            Asserts.fail();
+        } catch (NullPointerException e) {
+            checkMessage("returnMeAsNull(null, 1, 'A').dag = null", e.getMessage(), "while trying to write the field 'NullPointerExceptionTest.dag' of a null object returned from 'NullPointerExceptionTest.returnMeAsNull(Ljava/lang/Throwable;IC)LNullPointerExceptionTest;'");
+        }
+        /*
+        try {
+            returnMeAsNull(null, 1, 'A').dag.dag = null;
+            Asserts.fail();
+        } catch (NullPointerException e) {
+            checkMessage("returnMeAsNull(null, 1, 'A').dag.dag = null", e.getMessage(), "while trying to write the field 'NullPointerExceptionTest.dag' of a null object returned from 'NullPointerExceptionTest.returnMeAsNull(Ljava/lang/Throwable;IC)LNullPointerExceptionTest;'");
+        }
+        */
+    }
+
+    /**
+     *
+     */
+    @SuppressWarnings("null")
+    public void testLoadedFromMethod6() {
+        short[] sa = null;
+
+        try {
+            Asserts.assertTrue(sa[0] == (short) 1);
+            Asserts.fail();
+        } catch (NullPointerException e) {
+            checkMessage("sa[0]", e.getMessage(), "while trying to load from a null short array loaded from " + (hasDebugInfo ? "local variable 'sa'" : "a local variable at slot 1"));
+        }
+    }
+
+    /**
+     *
+     */
+    @SuppressWarnings("null")
+    public void testLoadedFromMethod7() {
+        short[] sa = null;
+
+        try {
+            sa[0] = 1;
+            Asserts.fail();
+        } catch (NullPointerException e) {
+            checkMessage("sa[0] = 1", e.getMessage(), "while trying to store to a null short array loaded from " + (hasDebugInfo ? "local variable 'sa'" : "a local variable at slot 1"));
+        }
+    }
+
+    /**
+     *
+     */
+    public void testLoadedFromStaticField1() {
+        try {
+            Asserts.assertTrue(((float[]) nullStaticField)[0] == 1.0f);
+            Asserts.fail();
+        } catch (NullPointerException e) {
+            checkMessage("((float[]) nullStaticField)[0]", e.getMessage(), "while trying to load from a null float array loaded from static field 'NullPointerExceptionTest.nullStaticField'");
+        }
+    }
+
+    /**
+     *
+     */
+    public void testLoadedFromStaticField2() {
+        try {
+            ((float[]) nullStaticField)[0] = 1.0f;
+            Asserts.fail();
+        } catch (NullPointerException e) {
+            checkMessage("((float[]) nullStaticField)[0] = 1.0f", e.getMessage(), "while trying to store to a null float array loaded from static field 'NullPointerExceptionTest.nullStaticField'");
+        }
+    }
+
+    /**
+     *
+     */
+    public void testLoadedFromStaticField3() {
+        try {
+            Asserts.assertTrue(staticArray[0][0][0][0] == 1);
+            Asserts.fail();
+        } catch (NullPointerException e) {
+            checkMessage("staticArray[0][0][0][0] // staticArray[0][0][0] is null.", e.getMessage(), "while trying to load from a null int array loaded from an array (which itself was loaded from an array) with an index loaded from a constant");
+        }
+    }
+
+    /**
+     *
+     */
+    public void testLoadedFromStaticField4(int myIdx, int pos) {
+        try {
+            staticArray[0][0][pos][myIdx] = 2;
+            Asserts.fail();
+        } catch (NullPointerException e) {
+            checkMessage(" staticArray[0][0][pos][myIdx] = 2", e.getMessage(), "while trying to store to a null int array loaded from an array (which itself was loaded from an array) with an index loaded from " + (hasDebugInfo ? "local variable 'pos'" : "the parameter nr. 2 of the method"));
+        }
+    }
+
+    /**
+     *
+     */
+    public void testLoadedFromStaticField5() {
+        try {
+            Asserts.assertTrue(staticLongArray[0][0] == 1L);
+        } catch (NullPointerException e) {
+            checkMessage("staticLongArray[0][0]", e.getMessage(), "while trying to load from a null long array loaded from an array (which itself was loaded from static field 'NullPointerExceptionTest.staticLongArray') with an index loaded from a constant");
+        }
+    }
+
+    /**
+     * Test bipush for index.
+     */
+    public void testLoadedFromStaticField5a() {
+        try {
+            Asserts.assertTrue(staticLongArray[139 /*0x77*/][0] == 1L);
+        } catch (NullPointerException e) {
+            checkMessage("staticLongArray[139][0]", e.getMessage(), "while trying to load from a null long array loaded from an array (which itself was loaded from static field 'NullPointerExceptionTest.staticLongArray') with an index loaded from a constant");
+        }
+    }
+
+    /**
+     * Test sipush for index.
+     */
+    public void testLoadedFromStaticField5b() {
+        try {
+            Asserts.assertTrue(staticLongArray[819 /*0x333*/][0] == 1L);
+        } catch (NullPointerException e) {
+            checkMessage("staticLongArray[819][0]",  e.getMessage(), "while trying to load from a null long array loaded from an array (which itself was loaded from static field 'NullPointerExceptionTest.staticLongArray') with an index loaded from a constant");
+        }
+    }
+
+    /**
+     *
+     */
+    public void testLoadedFromStaticField6() {
+        try {
+            staticLongArray[0][0] = 2L;
+            Asserts.fail();
+        } catch (NullPointerException e) {
+            checkMessage("staticLongArray[0][0] = 2L", e.getMessage(), "while trying to store to a null long array loaded from an array (which itself was loaded from static field 'NullPointerExceptionTest.staticLongArray') with an index loaded from a constant");
+        }
+    }
+
+    /**
+     *
+     */
+    public void testLoadedFromInstanceField1() {
+        try {
+            Asserts.assertTrue(this.nullInstanceField.nullInstanceField == null);
+            Asserts.fail();
+        } catch (NullPointerException e) {
+            checkMessage("this.nullInstanceField.nullInstanceField", e.getMessage(), "while trying to read the field 'nullInstanceField' of a null object loaded from field 'NullPointerExceptionTest.nullInstanceField' of an object loaded from 'this'");
+        }
+    }
+
+    /**
+     *
+     */
+    public void testLoadedFromInstanceField2() {
+        try {
+            this.nullInstanceField.nullInstanceField = null;
+            Asserts.fail();
+        } catch (NullPointerException e) {
+            checkMessage("this.nullInstanceField.nullInstanceField = null", e.getMessage(), "while trying to write the field 'NullPointerExceptionTest.nullInstanceField' of a null object loaded from field 'NullPointerExceptionTest.nullInstanceField' of an object loaded from 'this'");
+        }
+    }
+
+    /**
+     *
+     */
+    public void testLoadedFromInstanceField3() {
+        NullPointerExceptionTest obj = this;
+
+        try {
+            Asserts.assertNull(obj.dag.getArray().clone());
+            Asserts.fail();
+        } catch (NullPointerException e) {
+            checkMessage("obj.dag.getArray().clone()", e.getMessage(), "while trying to invoke the method 'NullPointerExceptionTest$DoubleArrayGen.getArray()[D' on a null reference loaded from field 'NullPointerExceptionTest.dag' of an object loaded from " + (hasDebugInfo ? "local variable 'obj'" : "a local variable at slot 1"));
+        }
+    }
+
+    /**
+     *
+     */
+    public void testLoadedFromInstanceField4() {
+        int indexes[] = new int[1];
+
+        NullPointerExceptionTest[] objs = new NullPointerExceptionTest[] {this};
+
+        try {
+            Asserts.assertNull(objs[indexes[0]].nullInstanceField.returnNull(null, null, 1f));
+            Asserts.fail();
+        } catch (NullPointerException e) {
+            checkMessage("objs[indexes[0]].nullInstanceField.returnNull(null, null, 1f", e.getMessage(), "while trying to invoke the method 'NullPointerExceptionTest.returnNull([[Ljava/lang/String;[[[IF)Ljava/lang/Object;' on a null reference loaded from field 'NullPointerExceptionTest.nullInstanceField' of an object loaded from an array");
+        }
+    }
+
+    /**
+     *
+     */
+    public void testLoadedFromInstanceField5() {
+        int indexes[] = new int[1];
+
+        NullPointerExceptionTest[] objs = new NullPointerExceptionTest[] {this};
+
+        try {
+            Asserts.assertNull(objs[indexes[0]].nullInstanceField.toString().toCharArray().clone());
+        } catch (NullPointerException e) {
+            checkMessage("objs[indexes[0]].nullInstanceField.toString().toCharArray().clone()", e.getMessage(), "while trying to invoke the method 'java.lang.Object.toString()Ljava/lang/String;' on a null reference loaded from field 'NullPointerExceptionTest.nullInstanceField' of an object loaded from an array");
+        }
+    }
+
+    /**
+     *
+     */
+    public void testLoadedFromInstanceField6() {
+        int indexes[] = new int[1];
+
+        NullPointerExceptionTest[][] objs =
+            new NullPointerExceptionTest[][] {new NullPointerExceptionTest[] {this}};
+
+        try {
+            // Check monitorenter only, since we cannot probe monitorexit from Java.
+            synchronized (objs[indexes[0]][0].nullInstanceField) {
+                Asserts.fail();
+            }
+        } catch (NullPointerException e) {
+            checkMessage("synchronized (objs[indexes[0]][0].nullInstanceField)", e.getMessage(), "while trying to enter a null monitor loaded from field 'NullPointerExceptionTest.nullInstanceField' of an object loaded from an array");
+        }
+    }
+
+    /**
+     * @throws ClassNotFoundException
+     */
+    public void testInNative() throws ClassNotFoundException {
+        try {
+            Class.forName(null);
+            Asserts.fail();
+        } catch (NullPointerException e) {
+            Asserts.assertNull(e.getMessage());
+        }
+    }
+
+    private Object returnNull(String[][] dummy1, int[][][] dummy2, float dummy3) {
+        return null;
+    }
+
+    private NullPointerExceptionTest returnMeAsNull(Throwable dummy1, int dummy2, char dummy3){
+        return null;
+    }
+
+    static interface DoubleArrayGen {
+        public double[] getArray();
+    }
+
+    static class DoubleArrayGenImpl implements DoubleArrayGen {
+        @Override
+        public double[] getArray() {
+            return null;
+        }
+    }
+
+    static class NullPointerGenerator {
+        public static Object nullReturner(boolean dummy1) {
+            return null;
+        }
+
+        public Object returnMyNull(double dummy1, long dummy2, short dummy3) {
+            return null;
+        }
+    }
+
+    /**
+     *
+     */
+    public void testMissingLocalVariableTable() {
+        doTestMissingLocalVariableTable(names);
+
+        String[] expectedHasDebugInfoGoodNames = new String[] {
+            "while trying to invoke the method 'java.lang.Object.hashCode()I' on a null reference " +
+                "loaded from field 'NullPointerExceptionTest.nullInstanceField' " +
+                "of an object loaded from 'this'",
+            "while trying to invoke the method 'java.lang.Object.hashCode()I' on a null reference loaded from " +
+                "local variable 'a1'",
+            "while trying to invoke the method 'java.lang.Object.hashCode()I' on a null reference loaded from " +
+                "local variable 'o1'",
+            "while trying to invoke the method 'java.lang.Object.hashCode()I' on a null reference loaded from " +
+                "local variable 'aa1'"
+        };
+
+        String[] expectedNoDebugInfoGoodNames = new String[] {
+            "while trying to invoke the method 'java.lang.Object.hashCode()I' on a null reference " +
+                "loaded from field 'NullPointerExceptionTest.nullInstanceField' " +
+                "of an object loaded from 'this'",
+            "while trying to invoke the method 'java.lang.Object.hashCode()I' on a null reference loaded from " +
+                "the parameter nr. 5 of the method",
+            "while trying to invoke the method 'java.lang.Object.hashCode()I' on a null reference loaded from " +
+                "the parameter nr. 2 of the method",
+            "while trying to invoke the method 'java.lang.Object.hashCode()I' on a null reference loaded from " +
+                "the parameter nr. 9 of the method"
+        };
+
+        String[] expectedNames;
+        if (hasDebugInfo) {
+            expectedNames = expectedHasDebugInfoGoodNames;
+        } else {
+            expectedNames = expectedNoDebugInfoGoodNames;
+        }
+
+        // The two lists of messages should have the same length.
+        Asserts.assertEquals(names.size(), expectedNames.length);
+
+        for (int i = 0; i < expectedNames.length; ++i) {
+            Asserts.assertEquals(names.get(i), expectedNames[i]);
+        }
+    }
+
+    private void doTestMissingLocalVariableTable(ArrayList<String> names) {
+        curr = names;
+        doTestMissingLocalVariableTable1();
+        doTestMissingLocalVariableTable2(-1, null, false, 0.0, null, 0.1f, (byte) 0, (short) 0, null);
+    }
+
+    private void doTestMissingLocalVariableTable1() {
+        try {
+            this.nullInstanceField.hashCode();
+            Asserts.fail();
+        } catch (NullPointerException e) {
+            curr.add(e.getMessage());
+        }
+    }
+
+    private void doTestMissingLocalVariableTable2(long l1, Object o1, boolean z1, double d1, Object[] a1,
+            float f1, byte b1, short s1, Object[][] aa1) {
+        try {
+            a1.hashCode();
+            Asserts.fail();
+        }
+        catch (NullPointerException e) {
+            curr.add(e.getMessage());
+        }
+
+        try {
+            o1.hashCode();
+            Asserts.fail();
+        }
+        catch (NullPointerException e) {
+            curr.add(e.getMessage());
+        }
+
+        try {
+            aa1.hashCode();
+            Asserts.fail();
+        }
+        catch (NullPointerException e) {
+            curr.add(e.getMessage());
+        }
+    }
+
+    /**
+     *
+     */
+    @SuppressWarnings("null")
+    public void testNullMessages() {
+        boolean[] za1 = null;
+        byte[] ba1 = null;
+        short[] sa1 = null;
+        char[] ca1 = null;
+        int[] ia1 = null;
+        long[] la1 = null;
+        float[] fa1 = null;
+        double[] da1 = null;
+        Object[] oa1 = null;
+
+        Object[][] oa2 = new Object[2][];
+        oa2[1] = oa1;
+
+        try {
+            System.out.println(oa2[1][0]);
+            Asserts.fail();
+        } catch (NullPointerException e) {
+            checkMessage("oa2[1][0]", e.getMessage(),
+                                 "while trying to load from a null object array loaded from an array " +
+                                 "(which itself was loaded from " +
+                                 (hasDebugInfo ? "local variable 'oa2'" : "a local variable at slot 10") + ") " +
+                                 "with an index loaded from a constant");
+        }
+
+
+        try {
+            System.out.println(za1[0]);
+            Asserts.fail();
+        } catch (NullPointerException e) {
+            checkMessage("za1[0]", e.getMessage(),
+                                 "while trying to load from a null byte (or boolean) array loaded from " +
+                                 (hasDebugInfo ? "local variable 'za1'" : "a local variable at slot 1"));
+        }
+
+        try {
+            System.out.println(ba1[0]);
+            Asserts.fail();
+        } catch (NullPointerException e) {
+            checkMessage("ba1[0]", e.getMessage(),
+                                 "while trying to load from a null byte (or boolean) array loaded from " +
+                                 (hasDebugInfo ? "local variable 'ba1'" : "a local variable at slot 2"));
+        }
+
+        try {
+            System.out.println(sa1[0]);
+            Asserts.fail();
+        } catch (NullPointerException e) {
+            checkMessage("sa1[0]", e.getMessage(),
+                                 "while trying to load from a null short array loaded from " +
+                                 (hasDebugInfo ? "local variable 'sa1'" : "a local variable at slot 3"));
+        }
+
+        try {
+            System.out.println(ca1[0]);
+            Asserts.fail();
+        } catch (NullPointerException e) {
+            checkMessage("ca1[0]", e.getMessage(),
+                                 "while trying to load from a null char array loaded from " +
+                                 (hasDebugInfo ? "local variable 'ca1'" : "a local variable at slot 4"));
+        }
+
+        try {
+            System.out.println(ia1[0]);
+            Asserts.fail();
+        } catch (NullPointerException e) {
+            checkMessage("ia1[0]", e.getMessage(),
+                                 "while trying to load from a null int array loaded from " +
+                                 (hasDebugInfo ? "local variable 'ia1'" : "a local variable at slot 5"));
+        }
+
+        try {
+            System.out.println(la1[0]);
+            Asserts.fail();
+        } catch (NullPointerException e) {
+            checkMessage("la1[0]", e.getMessage(),
+                                 "while trying to load from a null long array loaded from " +
+                                 (hasDebugInfo ? "local variable 'la1'" : "a local variable at slot 6"));
+        }
+
+        try {
+            System.out.println(fa1[0]);
+            Asserts.fail();
+        } catch (NullPointerException e) {
+            checkMessage("fa1[0]", e.getMessage(),
+                                 "while trying to load from a null float array loaded from " +
+                                 (hasDebugInfo ? "local variable 'fa1'" : "a local variable at slot 7"));
+        }
+
+        try {
+            System.out.println(da1[0]);
+            Asserts.fail();
+        } catch (NullPointerException e) {
+            checkMessage("da1[0]", e.getMessage(),
+                                 "while trying to load from a null double array loaded from " +
+                                 (hasDebugInfo ? "local variable 'da1'" : "a local variable at slot 8"));
+        }
+
+        try {
+            System.out.println(oa1[0]);
+            Asserts.fail();
+        } catch (NullPointerException e) {
+            checkMessage("oa1[0]", e.getMessage(),
+                                 "while trying to load from a null object array loaded from " +
+                                 (hasDebugInfo ? "local variable 'oa1'" : "a local variable at slot 9"));
+        }
+
+        try {
+            System.out.println(za1[0] = false);
+            Asserts.fail();
+        } catch (NullPointerException e) {
+            checkMessage("za1[0] = false", e.getMessage(),
+                                 "while trying to store to a null byte (or boolean) array loaded from " +
+                                 (hasDebugInfo ? "local variable 'za1'" : "a local variable at slot 1"));
+        }
+
+        try {
+            System.out.println(ba1[0] = 0);
+            Asserts.fail();
+        } catch (NullPointerException e) {
+            checkMessage("ba1[0] = 0", e.getMessage(),
+                                 "while trying to store to a null byte (or boolean) array loaded from " +
+                                 (hasDebugInfo ? "local variable 'ba1'" : "a local variable at slot 2"));
+        }
+
+        try {
+            System.out.println(sa1[0] = 0);
+            Asserts.fail();
+        } catch (NullPointerException e) {
+            checkMessage("sa1[0] = 0", e.getMessage(),
+                                 "while trying to store to a null short array loaded from " +
+                                 (hasDebugInfo ? "local variable 'sa1'" : "a local variable at slot 3"));
+        }
+
+        try {
+            System.out.println(ca1[0] = 0);
+            Asserts.fail();
+        } catch (NullPointerException e) {
+            checkMessage("ca1[0] = 0", e.getMessage(),
+                                 "while trying to store to a null char array loaded from " +
+                                 (hasDebugInfo ? "local variable 'ca1'" : "a local variable at slot 4"));
+        }
+
+        try {
+            System.out.println(ia1[0] = 0);
+            Asserts.fail();
+        } catch (NullPointerException e) {
+            checkMessage("ia1[0] = 0", e.getMessage(),
+                                 "while trying to store to a null int array loaded from " +
+                                 (hasDebugInfo ? "local variable 'ia1'" : "a local variable at slot 5"));
+        }
+
+        try {
+            System.out.println(la1[0] = 0);
+            Asserts.fail();
+        } catch (NullPointerException e) {
+            checkMessage("la1[0] = 0", e.getMessage(),
+                                 "while trying to store to a null long array loaded from " +
+                                 (hasDebugInfo ? "local variable 'la1'" : "a local variable at slot 6"));
+        }
+
+        try {
+            System.out.println(fa1[0] = 0);
+            Asserts.fail();
+        } catch (NullPointerException e) {
+            checkMessage("fa1[0] = 0", e.getMessage(),
+                                 "while trying to store to a null float array loaded from " +
+                                 (hasDebugInfo ? "local variable 'fa1'" : "a local variable at slot 7"));
+        }
+
+        try {
+            System.out.println(da1[0] = 0);
+            Asserts.fail();
+        } catch (NullPointerException e) {
+            checkMessage("da1[0] = 0", e.getMessage(),
+                                 "while trying to store to a null double array loaded from " +
+                                 (hasDebugInfo ? "local variable 'da1'" : "a local variable at slot 8"));
+        }
+
+        try {
+            System.out.println(oa1[0] = null);
+            Asserts.fail();
+        } catch (NullPointerException e) {
+            checkMessage("oa1[0] = null", e.getMessage(),
+                                 "while trying to store to a null object array loaded from " +
+                                 (hasDebugInfo ? "local variable 'oa1'" : "a local variable at slot 9"));
+        }
+
+        try {
+            System.out.println(nullInstanceField.nullInstanceField);
+            Asserts.fail();
+        } catch (NullPointerException e) {
+            checkMessage("nullInstanceField.nullInstanceField", e.getMessage(),
+                                 "while trying to read the field 'nullInstanceField' of a null object loaded " +
+                                 "from field 'NullPointerExceptionTest.nullInstanceField' of an object " +
+                                 "loaded from 'this'");
+        }
+
+        try {
+            System.out.println(nullInstanceField.nullInstanceField = null);
+            Asserts.fail();
+        } catch (NullPointerException e) {
+            checkMessage("nullInstanceField.nullInstanceField = null", e.getMessage(),
+                                 "while trying to write the field 'NullPointerExceptionTest.nullInstanceField' " +
+                                 "of a null object loaded from field 'NullPointerExceptionTest.nullInstanceField' " +
+                                 "of an object loaded from 'this'");
+        }
+
+        try {
+            System.out.println(za1.length);
+            Asserts.fail();
+        } catch (NullPointerException e) {
+            checkMessage("za1.length", e.getMessage(),
+                                 "while trying to get the length of a null array loaded from " +
+                                 (hasDebugInfo ? "local variable 'za1'" : "a local variable at slot 1"));
+        }
+
+        try {
+            throw null;
+        } catch (NullPointerException e) {
+            checkMessage("throw null", e.getMessage(),
+                                 "while trying to throw a null exception object loaded " +
+                                 "from a constant");
+        }
+
+        try {
+            synchronized (nullInstanceField) {
+                // desired
+            }
+        } catch (NullPointerException e) {
+            checkMessage("synchronized (nullInstanceField)", e.getMessage(),
+                                 "while trying to enter a null monitor loaded from field " +
+                                 "'NullPointerExceptionTest.nullInstanceField' of an object loaded from " +
+                                 "'this'");
+        }
+
+        try {
+            nullInstanceField.testCreationViaNew();
+            Asserts.fail();
+        } catch (NullPointerException e) {
+            checkMessage("nullInstanceField.testCreationViaNew()", e.getMessage(),
+                                 "while trying to invoke the method 'NullPointerExceptionTest.testCreationViaNew()V' on a null reference " +
+                                 "loaded from field 'NullPointerExceptionTest.nullInstanceField' of an " +
+                                 "object loaded from 'this'");
+        }
+
+        try {
+            nullInstanceField.testNullMessages();
+            Asserts.fail();
+        } catch (NullPointerException e) {
+            checkMessage("nullInstanceField.testNullMessages()", e.getMessage(),
+                                 "while trying to invoke the method 'NullPointerExceptionTest.testNullMessages()V' on a null reference " +
+                                 "loaded from field 'NullPointerExceptionTest.nullInstanceField' of an " +
+                                 "object loaded from 'this'");
+        }
+
+        try {
+            // If we can get the value from more than one bci, we cannot know which one.
+            (Math.random() < 0.5 ? oa1 : (new Object[1])[0]).equals("");
+        } catch (NullPointerException e) {
+            checkMessage("(Math.random() < 0.5 ? oa1 : (new Object[1])[0]).equals(\"\")", e.getMessage(),
+                                 "while trying to invoke the method 'java.lang.Object.equals(Ljava/lang/Object;)Z' on a null reference");
+        }
+    }
+}
--- a/test/hotspot/jtreg/vmTestbase/jit/t/t104/t104.gold	Tue Mar 19 17:03:18 2019 +0800
+++ b/test/hotspot/jtreg/vmTestbase/jit/t/t104/t104.gold	Fri Feb 08 14:15:05 2019 +0100
@@ -1,2 +1,2 @@
 Exception thrown.
-java.lang.NullPointerException
+java.lang.NullPointerException: while trying to enter a null monitor loaded from a local variable at slot 1