# HG changeset patch # User goetz # Date 1569310810 -7200 # Node ID 8b5c80e057cfd0a699f7c4a52d28995b56116860 # Parent ef8c8cf9256abfc3e6620741dd156783e444b978 8218628: Add detailed message to NullPointerException describing what is null. Summary: This is the implementation of JEP 358: Helpful NullPointerExceptions. Reviewed-by: coleenp, clanger, rschmelter, rriggs, forax diff --git a/make/hotspot/symbols/symbols-unix b/make/hotspot/symbols/symbols-unix --- a/make/hotspot/symbols/symbols-unix +++ b/make/hotspot/symbols/symbols-unix @@ -97,6 +97,7 @@ JVM_GetDeclaredClasses JVM_GetDeclaringClass JVM_GetEnclosingMethodInfo +JVM_GetExtendedNPEMessage JVM_GetFieldIxModifiers JVM_GetFieldTypeAnnotations JVM_GetInheritedAccessControlContext diff --git a/src/hotspot/share/classfile/javaClasses.cpp b/src/hotspot/share/classfile/javaClasses.cpp --- a/src/hotspot/share/classfile/javaClasses.cpp +++ b/src/hotspot/share/classfile/javaClasses.cpp @@ -1541,7 +1541,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); \ @@ -1934,7 +1934,6 @@ return method != NULL && (method->constants()->version() == version); } - // This class provides a simple wrapper over the internal structure of // exception backtrace to insulate users of the backtrace from needing // to know what it looks like. @@ -1946,7 +1945,11 @@ typeArrayOop _methods; typeArrayOop _bcis; objArrayOop _mirrors; - typeArrayOop _names; // needed to insulate method name against redefinition + typeArrayOop _names; // Needed to insulate method name against redefinition. + // This is set to a java.lang.Boolean(true) if the top frame + // of the backtrace is omitted because it shall be hidden. + // Else it is null. + oop _has_hidden_top_frame; int _index; NoSafepointVerifier _nsv; @@ -1956,6 +1959,7 @@ trace_mirrors_offset = java_lang_Throwable::trace_mirrors_offset, trace_names_offset = java_lang_Throwable::trace_names_offset, trace_next_offset = java_lang_Throwable::trace_next_offset, + trace_hidden_offset = java_lang_Throwable::trace_hidden_offset, trace_size = java_lang_Throwable::trace_size, trace_chunk_size = java_lang_Throwable::trace_chunk_size }; @@ -1981,11 +1985,15 @@ assert(names != NULL, "names array should be initialized in backtrace"); return names; } + static oop get_has_hidden_top_frame(objArrayHandle chunk) { + oop hidden = chunk->obj_at(trace_hidden_offset); + return hidden; + } public: // constructor for new backtrace - BacktraceBuilder(TRAPS): _head(NULL), _methods(NULL), _bcis(NULL), _mirrors(NULL), _names(NULL) { + BacktraceBuilder(TRAPS): _head(NULL), _methods(NULL), _bcis(NULL), _mirrors(NULL), _names(NULL), _has_hidden_top_frame(NULL) { expand(CHECK); _backtrace = Handle(THREAD, _head); _index = 0; @@ -1996,6 +2004,7 @@ _bcis = get_bcis(backtrace); _mirrors = get_mirrors(backtrace); _names = get_names(backtrace); + _has_hidden_top_frame = get_has_hidden_top_frame(backtrace); assert(_methods->length() == _bcis->length() && _methods->length() == _mirrors->length() && _mirrors->length() == _names->length(), @@ -2033,6 +2042,7 @@ new_head->obj_at_put(trace_bcis_offset, new_bcis()); new_head->obj_at_put(trace_mirrors_offset, new_mirrors()); new_head->obj_at_put(trace_names_offset, new_names()); + new_head->obj_at_put(trace_hidden_offset, NULL); _head = new_head(); _methods = new_methods(); @@ -2073,6 +2083,16 @@ _index++; } + void set_has_hidden_top_frame(TRAPS) { + if (_has_hidden_top_frame == NULL) { + jvalue prim; + prim.z = 1; + PauseNoSafepointVerifier pnsv(&_nsv); + _has_hidden_top_frame = java_lang_boxing_object::create(T_BOOLEAN, &prim, CHECK); + _head->obj_at_put(trace_hidden_offset, _has_hidden_top_frame); + } + } + }; struct BacktraceElement : public StackObj { @@ -2402,7 +2422,13 @@ } } if (method->is_hidden()) { - if (skip_hidden) continue; + if (skip_hidden) { + if (total_count == 0) { + // The top frame will be hidden from the stack trace. + bt.set_has_hidden_top_frame(CHECK); + } + continue; + } } bt.push(method, bci, CHECK); total_count++; @@ -2519,6 +2545,37 @@ } } +bool java_lang_Throwable::get_top_method_and_bci(oop throwable, Method** method, int* bci) { + Thread* THREAD = Thread::current(); + objArrayHandle result(THREAD, objArrayOop(backtrace(throwable))); + BacktraceIterator iter(result, THREAD); + // No backtrace available. + if (!iter.repeat()) return false; + + // If the exception happened in a frame that has been hidden, i.e., + // omitted from the back trace, we can not compute the message. + oop hidden = ((objArrayOop)backtrace(throwable))->obj_at(trace_hidden_offset); + if (hidden != NULL) { + return false; + } + + // Get first backtrace element. + BacktraceElement bte = iter.next(THREAD); + + InstanceKlass* holder = InstanceKlass::cast(java_lang_Class::as_Klass(bte._mirror())); + assert(holder != NULL, "first element should be non-null"); + Method* m = holder->method_with_orig_idnum(bte._method_id, bte._version); + + // Original version is no longer available. + if (m == NULL || !version_matches(m, bte._version)) { + return false; + } + + *method = m; + *bci = bte._bci; + return true; +} + oop java_lang_StackTraceElement::create(const methodHandle& method, int bci, TRAPS) { // Allocate java.lang.StackTraceElement instance InstanceKlass* k = SystemDictionary::StackTraceElement_klass(); diff --git a/src/hotspot/share/classfile/javaClasses.hpp b/src/hotspot/share/classfile/javaClasses.hpp --- a/src/hotspot/share/classfile/javaClasses.hpp +++ b/src/hotspot/share/classfile/javaClasses.hpp @@ -519,7 +519,8 @@ trace_mirrors_offset = 2, trace_names_offset = 3, trace_next_offset = 4, - trace_size = 5, + trace_hidden_offset = 5, + trace_size = 6, trace_chunk_size = 32 }; @@ -569,6 +570,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_top_method_and_bci(oop throwable, Method** method, int* bci); }; diff --git a/src/hotspot/share/classfile/systemDictionary.hpp b/src/hotspot/share/classfile/systemDictionary.hpp --- a/src/hotspot/share/classfile/systemDictionary.hpp +++ b/src/hotspot/share/classfile/systemDictionary.hpp @@ -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 ) \ diff --git a/src/hotspot/share/classfile/vmSymbols.hpp b/src/hotspot/share/classfile/vmSymbols.hpp --- a/src/hotspot/share/classfile/vmSymbols.hpp +++ b/src/hotspot/share/classfile/vmSymbols.hpp @@ -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") \ diff --git a/src/hotspot/share/include/jvm.h b/src/hotspot/share/include/jvm.h --- a/src/hotspot/share/include/jvm.h +++ b/src/hotspot/share/include/jvm.h @@ -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 { diff --git a/src/hotspot/share/interpreter/bytecodeUtils.cpp b/src/hotspot/share/interpreter/bytecodeUtils.cpp new file mode 100644 --- /dev/null +++ b/src/hotspot/share/interpreter/bytecodeUtils.cpp @@ -0,0 +1,1481 @@ +/* + * 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 "runtime/safepointVerifiers.hpp" +#include "utilities/events.hpp" +#include "utilities/ostream.hpp" + +class SimulatedOperandStack; +class ExceptionMessageBuilder; + +// The entries of a SimulatedOperandStack. They carry the analysis +// information gathered for the slot. +class StackSlotAnalysisData { + private: + + friend class SimulatedOperandStack; + friend class ExceptionMessageBuilder; + + unsigned int _bci:17; // The bci of the bytecode that pushed the current value on the operand stack. + // INVALID if ambiguous, e.g. after a control flow merge. + // 16 bits for bci (max bytecode size) and one for INVALID. + unsigned int _type:15; // The BasicType of the value on the operand stack. + + // Merges this slot data with the given one and returns the result. If + // the bcis of the two merged objects 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). + StackSlotAnalysisData merge(StackSlotAnalysisData other); + + public: + + // Creates a new object with an invalid bci and the given type. + StackSlotAnalysisData(BasicType type = T_CONFLICT); + + // Creates a new object with the given bci and type. + StackSlotAnalysisData(int bci, BasicType type); + + enum { + // An invalid bytecode index, as > 65535. + INVALID = 0x1FFFF + }; + + // Returns the bci. If the bci is invalid, INVALID is returned. + unsigned int get_bci(); + + // Returns true, if the bci is not invalid. + bool has_bci() { return get_bci() != INVALID; } + + // Returns the type of the slot data. + BasicType get_type(); +}; + +// A stack consisting of SimulatedOperandStackEntries. +// This represents the analysis information for the operand stack +// for a given bytecode at a given bci. +// It also holds an additional field that serves to collect +// information whether local slots were written. +class SimulatedOperandStack: CHeapObj { + + private: + + friend class ExceptionMessageBuilder; + friend class StackSlotAnalysisData; + + // The stack. + GrowableArray _stack; + + // Optimized bytecode can reuse local variable slots for several + // local variables. + // If there is no variable name information, we print 'parameter' + // if a parameter maps to a local slot. Once a local slot has been + // written, we don't know any more whether it was written as the + // corresponding parameter, or whether another local has been + // mapped to the slot. So we don't want to print 'parameter' any + // more, but 'local'. Similary for 'this'. + // Therefore, during the analysis, we mark a bit for local slots that + // get written and propagate this information. + // We only run the analysis for 64 slots. If a method has more + // parameters, we print 'local' in all cases. + uint64_t _written_local_slots; + + SimulatedOperandStack(): _written_local_slots(0) { }; + SimulatedOperandStack(const SimulatedOperandStack ©); + + // Pushes the given slot data. + void push_raw(StackSlotAnalysisData slotData); + + // Like push_raw, but if the slotData has type long or double, we push two. + void push(StackSlotAnalysisData slotData); + + // Like push(slotData), but using bci/type to create an instance of + // StackSlotAnalysisData first. + 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(SimulatedOperandStack const& other); + + public: + + // Returns the size of the stack. + int get_size() const; + + // Returns the slot data at the given index. Slot 0 is top of stack. + StackSlotAnalysisData get_slot_data(int slot); + + // Mark that local slot i was written. + void set_local_slot_written(int i); + + // Check whether local slot i was written by this or a previous bytecode. + bool local_slot_was_written(int i); +}; + +// Helper class to build internal exception messages for exceptions +// that are thrown because prerequisites to execute a bytecode +// are not met. +// E.g., if a NPE is thrown because an iload can not be executed +// by the VM because the reference to load from is null. +// +// It analyses the bytecode to assemble Java-like message text +// to give precise information where in a larger expression the +// exception occured. +// +// To assemble this message text, it is needed to know how +// operand stack slot entries were pushed on the operand stack. +// This class contains an analysis over the bytecodes to compute +// this information. The information is stored in a +// SimulatedOperandStack for each bytecode. +class ExceptionMessageBuilder : public StackObj { + + // The stacks for each bytecode. + GrowableArray* _stacks; + + // The method. + Method* _method; + + // 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; + + // 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). + static const int _max_entries = 1000000; + + static const int _max_cause_detail = 5; + + // 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, SimulatedOperandStack* stack); + + // Processes the instruction at the given bci in the method. Returns + // the size of the instruction. + int do_instruction(int bci); + + bool print_NPE_cause0(outputStream *os, int bci, int slot, int max_detail, + bool inner_expr = false, const char *prefix = NULL); + + public: + + // Creates an ExceptionMessageBuilder object and runs the analysis + // building SimulatedOperandStacks for each bytecode in 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. + ExceptionMessageBuilder(Method* method, int bci = -1); + + // Releases the resources. + ~ExceptionMessageBuilder(); + + // Returns the number of stacks (this is the size of the method). + int get_size() { return _stacks->length() - 1; } + + // 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. + int get_NPE_null_slot(int bci); + + // Prints a java-like expression for the bytecode that pushed + // the value to the given slot being live at the given bci. + // It constructs the expression by recursing backwards over the + // bytecode using the results of the analysis done in the + // constructor of ExceptionMessageBuilder. + // os: The stream to print the message to. + // bci: The index of the bytecode that caused the NPE. + // slot: The slot on the operand stack that contains null. + // The slots are numbered from TOS downwards, i.e., + // TOS has the slot number 0, that below 1 and so on. + // + // Returns false if nothing was printed, else true. + bool print_NPE_cause(outputStream *os, int bci, int slot); + + // Prints a string describing the failed action. + void print_NPE_failed_action(outputStream *os, int bci); +}; + +// Replaces the following well-known class names: +// java.lang.Object -> Object +// java.lang.String -> String +static char *trim_well_known_class_names_from_signature(char *signature) { + size_t len = strlen(signature); + size_t skip_len = strlen("java.lang."); + size_t min_pattern_len = strlen("java.lang.String"); + if (len < min_pattern_len) return signature; + + for (size_t isrc = 0, idst = 0; isrc <= len; isrc++, idst++) { + // We must be careful not to trim names like test.java.lang.String. + if ((isrc == 0 && strncmp(signature + isrc, "java.lang.Object", min_pattern_len) == 0) || + (isrc == 0 && strncmp(signature + isrc, "java.lang.String", min_pattern_len) == 0) || + (isrc > 1 && strncmp(signature + isrc-2, ", java.lang.Object", min_pattern_len+2) == 0) || + (isrc > 1 && strncmp(signature + isrc-2, ", java.lang.String", min_pattern_len+2) == 0) ) { + isrc += skip_len; + } + if (idst != isrc) { + signature[idst] = signature[isrc]; + } + } + return signature; +} + +// Replaces the following well-known class names: +// java.lang.Object -> Object +// java.lang.String -> String +static void print_klass_name(outputStream *os, Symbol *klass) { + const char *name = klass->as_klass_external_name(); + if (strcmp(name, "java.lang.Object") == 0) name = "Object"; + if (strcmp(name, "java.lang.String") == 0) name = "String"; + os->print("%s", name); +} + +// Prints the name of the method that is described at constant pool +// index cp_index in the constant pool of method 'method'. +static void print_method_name(outputStream *os, Method* method, int cp_index) { + ResourceMark rm; + ConstantPool* cp = method->constants(); + Symbol* klass = cp->klass_ref_at_noresolve(cp_index); + Symbol* name = cp->name_ref_at(cp_index); + Symbol* signature = cp->signature_ref_at(cp_index); + + print_klass_name(os, klass); + os->print(".%s(", name->as_C_string()); + stringStream sig; + signature->print_as_signature_external_parameters(&sig); + os->print("%s)", trim_well_known_class_names_from_signature(sig.as_string())); +} + +// Prints the name of the field that is described at constant pool +// index cp_index in the constant pool of method 'method'. +static void print_field_and_class(outputStream *os, Method* method, int cp_index) { + ResourceMark rm; + ConstantPool* cp = method->constants(); + Symbol* klass = cp->klass_ref_at_noresolve(cp_index); + Symbol *name = cp->name_ref_at(cp_index); + print_klass_name(os, klass); + os->print(".%s", name->as_C_string()); +} + +// Returns the name of the field that is described at constant pool +// index cp_index in the constant pool of method 'method'. +static char const* get_field_name(Method* method, int cp_index) { + Symbol* name = method->constants()->name_ref_at(cp_index); + return name->as_C_string(); +} + +static void print_local_var(outputStream *os, unsigned int bci, Method* method, int slot, bool is_parameter) { + if (method->has_localvariable_table()) { + for (int i = 0; i < method->localvariable_table_length(); i++) { + LocalVariableTableElement* elem = method->localvariable_table_start() + i; + unsigned int start = elem->start_bci; + unsigned 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(); + os->print("%s", var); + + return; + } + } + } + + // Handle at least some cases we know. + if (!method->is_static() && (slot == 0) && is_parameter) { + os->print("this"); + } else { + int curr = method->is_static() ? 0 : 1; + SignatureStream ss(method->signature()); + int param_index = 1; + 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 && is_parameter) { + os->print("", param_index); + } else { + // This is the best we can do. + os->print("", slot); + } + } +} + +StackSlotAnalysisData::StackSlotAnalysisData(BasicType type) : _bci(INVALID), _type(type) {} + +StackSlotAnalysisData::StackSlotAnalysisData(int bci, BasicType type) : _bci(bci), _type(type) { + assert(bci >= 0, "BCI must be >= 0"); + assert(bci < 65536, "BCI must be < 65536"); +} + +unsigned int StackSlotAnalysisData::get_bci() { + return _bci; +} + +BasicType StackSlotAnalysisData::get_type() { + return (BasicType)_type; +} + +StackSlotAnalysisData StackSlotAnalysisData::merge(StackSlotAnalysisData 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 StackSlotAnalysisData(get_bci(), T_OBJECT); + } else { + return StackSlotAnalysisData(T_OBJECT); + } + } else { + return StackSlotAnalysisData(T_CONFLICT); + } + } + + if (get_bci() == other.get_bci()) { + return *this; + } else { + return StackSlotAnalysisData(get_type()); + } +} + +SimulatedOperandStack::SimulatedOperandStack(const SimulatedOperandStack ©) { + for (int i = 0; i < copy.get_size(); i++) { + push_raw(copy._stack.at(i)); + } + _written_local_slots = copy._written_local_slots; +} + +void SimulatedOperandStack::push_raw(StackSlotAnalysisData slotData) { + if (slotData.get_type() == T_VOID) { + return; + } + + _stack.push(slotData); +} + +void SimulatedOperandStack::push(StackSlotAnalysisData slotData) { + if (type2size[slotData.get_type()] == 2) { + push_raw(slotData); + push_raw(slotData); + } else { + push_raw(slotData); + } +} + +void SimulatedOperandStack::push(int bci, BasicType type) { + push(StackSlotAnalysisData(bci, type)); +} + +void SimulatedOperandStack::pop(int slots) { + for (int i = 0; i < slots; ++i) { + _stack.pop(); + } + + assert(get_size() >= 0, "Popped too many slots"); +} + +void SimulatedOperandStack::merge(SimulatedOperandStack 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))); + } + _written_local_slots = _written_local_slots | other._written_local_slots; +} + +int SimulatedOperandStack::get_size() const { + return _stack.length(); +} + +StackSlotAnalysisData SimulatedOperandStack::get_slot_data(int slot) { + assert(slot >= 0, "Slot < 0"); + assert(slot < get_size(), "Slot >= size"); + + return _stack.at(get_size() - slot - 1); +} + +void SimulatedOperandStack::set_local_slot_written(int i) { + // Local slots > 63 are very unlikely. Consider these + // as written all the time. Saves space and complexity + // for dynamic data size. + if (i > 63) return; + _written_local_slots = _written_local_slots | (1 << i); +} + +bool SimulatedOperandStack::local_slot_was_written(int i) { + if (i > 63) return true; + return (_written_local_slots & (1 << i)) != 0; +} + +ExceptionMessageBuilder::ExceptionMessageBuilder(Method* method, int bci) : + _method(method), _nr_of_entries(0), + _added_one(true), _all_processed(false) { + assert(bci >= 0, "BCI too low"); + assert(bci < get_size(), "BCI too large"); + + ConstMethod* const_method = method->constMethod(); + const int len = const_method->code_size(); + + _stacks = new GrowableArray (len + 1); + + for (int i = 0; i <= len; ++i) { + _stacks->push(NULL); + } + + // Initialize stack a bci 0. + _stacks->at_put(0, new SimulatedOperandStack()); + + // 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 SimulatedOperandStack()); + _stacks->at(index)->push(index, T_OBJECT); + } + } + } + + // Do this until each bytecode has 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; + } + } + } +} + +ExceptionMessageBuilder::~ExceptionMessageBuilder() { + if (_stacks != NULL) { + for (int i = 0; i < _stacks->length(); ++i) { + delete _stacks->at(i); + } + } +} + +void ExceptionMessageBuilder::merge(int bci, SimulatedOperandStack* 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(); + } + + // Replace the stack at this bci with a copy of our new merged stack. + delete _stacks->at(bci); + _stacks->at_put(bci, new SimulatedOperandStack(*stack)); +} + +int ExceptionMessageBuilder::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; + } + + // Make a local copy of the stack for this bci to work on. + SimulatedOperandStack* stack = new SimulatedOperandStack(*_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 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: + int index; + if (is_wide) { + index = Bytes::get_Java_u2(code_base + bci + 2); + } else { + index = *(uint8_t*) (code_base + bci + 1); + } + stack->set_local_slot_written(index); + stack->pop(-Bytecodes::depth(code)); + break; + case Bytecodes::_istore_0: + case Bytecodes::_lstore_0: + case Bytecodes::_fstore_0: + case Bytecodes::_dstore_0: + case Bytecodes::_astore_0: + stack->set_local_slot_written(0); + stack->pop(-Bytecodes::depth(code)); + break; + case Bytecodes::_istore_1: + case Bytecodes::_fstore_1: + case Bytecodes::_lstore_1: + case Bytecodes::_dstore_1: + case Bytecodes::_astore_1: + stack->set_local_slot_written(1); + stack->pop(-Bytecodes::depth(code)); + break; + case Bytecodes::_istore_2: + case Bytecodes::_lstore_2: + case Bytecodes::_fstore_2: + case Bytecodes::_dstore_2: + case Bytecodes::_astore_2: + stack->set_local_slot_written(2); + stack->pop(-Bytecodes::depth(code)); + break; + case Bytecodes::_istore_3: + case Bytecodes::_lstore_3: + case Bytecodes::_fstore_3: + case Bytecodes::_dstore_3: + case Bytecodes::_astore_3: + stack->set_local_slot_written(3); + stack->pop(-Bytecodes::depth(code)); + break; + 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_slot_data(0)); + break; + + case Bytecodes::_dup_x1: { + StackSlotAnalysisData top1 = stack->get_slot_data(0); + StackSlotAnalysisData top2 = stack->get_slot_data(1); + stack->pop(2); + stack->push_raw(top1); + stack->push_raw(top2); + stack->push_raw(top1); + break; + } + + case Bytecodes::_dup_x2: { + StackSlotAnalysisData top1 = stack->get_slot_data(0); + StackSlotAnalysisData top2 = stack->get_slot_data(1); + StackSlotAnalysisData top3 = stack->get_slot_data(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_slot_data(1)); + // The former '0' entry is now at '1'. + stack->push_raw(stack->get_slot_data(1)); + break; + + case Bytecodes::_dup2_x1: { + StackSlotAnalysisData top1 = stack->get_slot_data(0); + StackSlotAnalysisData top2 = stack->get_slot_data(1); + StackSlotAnalysisData top3 = stack->get_slot_data(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: { + StackSlotAnalysisData top1 = stack->get_slot_data(0); + StackSlotAnalysisData top2 = stack->get_slot_data(1); + StackSlotAnalysisData top3 = stack->get_slot_data(2); + StackSlotAnalysisData top4 = stack->get_slot_data(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: { + StackSlotAnalysisData top1 = stack->get_slot_data(0); + StackSlotAnalysisData top2 = stack->get_slot_data(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 receiver. + 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 it 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; +} + +#define INVALID_BYTECODE_ENCOUNTERED -1 +#define NPE_EXPLICIT_CONSTRUCTED -2 +int ExceptionMessageBuilder::get_NPE_null_slot(int bci) { + // 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; // Position of argument of the bytecode. + if (code == Bytecodes::_wide) { + code = Bytecodes::java_code_at(_method, code_base + bci + 1); + pos += 1; + } + + switch (code) { + case Bytecodes::_getfield: + case Bytecodes::_arraylength: + case Bytecodes::_athrow: + case Bytecodes::_monitorenter: + case Bytecodes::_monitorexit: + return 0; + case Bytecodes::_iaload: + case Bytecodes::_faload: + case Bytecodes::_aaload: + case Bytecodes::_baload: + case Bytecodes::_caload: + case Bytecodes::_saload: + case Bytecodes::_laload: + case Bytecodes::_daload: + return 1; + case Bytecodes::_iastore: + case Bytecodes::_fastore: + case Bytecodes::_aastore: + case Bytecodes::_bastore: + case Bytecodes::_castore: + case Bytecodes::_sastore: + return 2; + case Bytecodes::_lastore: + case Bytecodes::_dastore: + return 3; + 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); + return type2size[char2type((char) signature->char_at(0))]; + } + 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); + Symbol* name = cp->symbol_at(name_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()) { + int type_index = cp->signature_ref_index_at(name_and_type_index); + Symbol* signature = cp->symbol_at(type_index); + // The 'this' parameter was null. Return the slot of it. + return ArgumentSizeComputer(signature).size(); + } else { + return NPE_EXPLICIT_CONSTRUCTED; + } + } + + default: + break; + } + + return INVALID_BYTECODE_ENCOUNTERED; +} + +bool ExceptionMessageBuilder::print_NPE_cause(outputStream* os, int bci, int slot) { + if (print_NPE_cause0(os, bci, slot, _max_cause_detail, false, " because '")) { + os->print("' is null."); + return true; + } + return false; +} + +// Recursively print what was null. +// +// Go to the bytecode that pushed slot 'slot' on the operand stack +// at bytecode 'bci'. Compute a message for that bytecode. If +// necessary (array, field), recur further. +// At most do max_detail recursions. +// Prefix is used to print a proper beginning of the whole +// sentence. +// inner_expr is used to omit some text, like 'static' in +// inner expressions like array subscripts. +// +// Returns true if something was printed. +// +bool ExceptionMessageBuilder::print_NPE_cause0(outputStream* os, int bci, int slot, + int max_detail, + bool inner_expr, const char *prefix) { + assert(bci >= 0, "BCI too low"); + assert(bci < get_size(), "BCI too large"); + + if (max_detail <= 0) { + return false; + } + + if (_stacks->at(bci) == NULL) { + return false; + } + + SimulatedOperandStack* stack = _stacks->at(bci); + assert(slot >= 0, "Slot nr. too low"); + assert(slot < stack->get_size(), "Slot nr. too large"); + + StackSlotAnalysisData slotData = stack->get_slot_data(slot); + + if (!slotData.has_bci()) { + return false; + } + + // Get the bytecode. + unsigned int source_bci = slotData.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; + } + + if (max_detail == _max_cause_detail && + prefix != NULL && + code != Bytecodes::_invokevirtual && + code != Bytecodes::_invokespecial && + code != Bytecodes::_invokestatic && + code != Bytecodes::_invokeinterface) { + os->print("%s", prefix); + } + + switch (code) { + case Bytecodes::_iload_0: + case Bytecodes::_aload_0: + print_local_var(os, source_bci, _method, 0, !stack->local_slot_was_written(0)); + return true; + + case Bytecodes::_iload_1: + case Bytecodes::_aload_1: + print_local_var(os, source_bci, _method, 1, !stack->local_slot_was_written(1)); + return true; + + case Bytecodes::_iload_2: + case Bytecodes::_aload_2: + print_local_var(os, source_bci, _method, 2, !stack->local_slot_was_written(2)); + return true; + + case Bytecodes::_iload_3: + case Bytecodes::_aload_3: + print_local_var(os, source_bci, _method, 3, !stack->local_slot_was_written(3)); + return true; + + case Bytecodes::_iload: + 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); + } + print_local_var(os, source_bci, _method, index, !stack->local_slot_was_written(index)); + return true; + } + + case Bytecodes::_aconst_null: + os->print("null"); + return true; + case Bytecodes::_iconst_m1: + os->print("-1"); + return true; + case Bytecodes::_iconst_0: + os->print("0"); + return true; + case Bytecodes::_iconst_1: + os->print("1"); + return true; + case Bytecodes::_iconst_2: + os->print("2"); + return true; + case Bytecodes::_iconst_3: + os->print("3"); + return true; + case Bytecodes::_iconst_4: + os->print("4"); + return true; + case Bytecodes::_iconst_5: + os->print("5"); + return true; + case Bytecodes::_bipush: { + jbyte con = *(jbyte*) (code_base + source_bci + 1); + os->print("%d", con); + return true; + } + case Bytecodes::_sipush: { + u2 con = Bytes::get_Java_u2(code_base + source_bci + 1); + os->print("%d", con); + return true; + } + case Bytecodes::_iaload: + case Bytecodes::_aaload: { + // Print the 'name' of the array. Go back to the bytecode that + // pushed the array reference on the operand stack. + if (!print_NPE_cause0(os, source_bci, 1, max_detail - 1, inner_expr)) { + // Returned false. Max recursion depth was reached. Print dummy. + os->print(""); + } + os->print("["); + // Print the index expression. Go back to the bytecode that + // pushed the index on the operand stack. + // inner_expr == true so we don't print unwanted strings + // as "The return value of'". And don't decrement max_detail so we always + // get a value here and only cancel out on the dereference. + if (!print_NPE_cause0(os, source_bci, 0, max_detail, true)) { + // Returned false. We don't print complex array index expressions. Print placeholder. + os->print("..."); + } + os->print("]"); + return true; + } + + case Bytecodes::_getstatic: { + int cp_index = Bytes::get_native_u2(code_base + pos) + ConstantPool::CPCACHE_INDEX_TAG; + print_field_and_class(os, _method, cp_index); + return true; + } + + case Bytecodes::_getfield: { + // Print the sender. Go back to the bytecode that + // pushed the sender on the operand stack. + if (print_NPE_cause0(os, source_bci, 0, max_detail - 1, inner_expr)) { + os->print("."); + } + int cp_index = Bytes::get_native_u2(code_base + pos) + ConstantPool::CPCACHE_INDEX_TAG; + os->print("%s", get_field_name(_method, cp_index)); + return true; + } + + 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); + if (max_detail == _max_cause_detail && !inner_expr) { + os->print(" because the return value of '"); + } + print_method_name(os, _method, cp_index); + return true; + } + + default: break; + } + return false; +} + +void ExceptionMessageBuilder::print_NPE_failed_action(outputStream *os, int bci) { + + // 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; + } + + switch (code) { + case Bytecodes::_iaload: + os->print("Cannot load from int array"); break; + case Bytecodes::_faload: + os->print("Cannot load from float array"); break; + case Bytecodes::_aaload: + os->print("Cannot load from object array"); break; + case Bytecodes::_baload: + os->print("Cannot load from byte/boolean array"); break; + case Bytecodes::_caload: + os->print("Cannot load from char array"); break; + case Bytecodes::_saload: + os->print("Cannot load from short array"); break; + case Bytecodes::_laload: + os->print("Cannot load from long array"); break; + case Bytecodes::_daload: + os->print("Cannot load from double array"); break; + + case Bytecodes::_iastore: + os->print("Cannot store to int array"); break; + case Bytecodes::_fastore: + os->print("Cannot store to float array"); break; + case Bytecodes::_aastore: + os->print("Cannot store to object array"); break; + case Bytecodes::_bastore: + os->print("Cannot store to byte/boolean array"); break; + case Bytecodes::_castore: + os->print("Cannot store to char array"); break; + case Bytecodes::_sastore: + os->print("Cannot store to short array"); break; + case Bytecodes::_lastore: + os->print("Cannot store to long array"); break; + case Bytecodes::_dastore: + os->print("Cannot store to double array"); break; + + case Bytecodes::_arraylength: + os->print("Cannot read the array length"); break; + case Bytecodes::_athrow: + os->print("Cannot throw exception"); break; + case Bytecodes::_monitorenter: + os->print("Cannot enter synchronized block"); break; + case Bytecodes::_monitorexit: + os->print("Cannot exit synchronized block"); break; + case Bytecodes::_getfield: { + 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); + os->print("Cannot read field '%s'", name->as_C_string()); + } break; + case Bytecodes::_putfield: { + int cp_index = Bytes::get_native_u2(code_base + pos) DEBUG_ONLY(+ ConstantPool::CPCACHE_INDEX_TAG); + os->print("Cannot assign field '%s'", get_field_name(_method, cp_index)); + } 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); + os->print("Cannot invoke '"); + print_method_name(os, _method, cp_index); + os->print("'"); + } break; + + default: + assert(0, "We should have checked this bytecode in get_NPE_null_slot()."); + break; + } +} + +// Main API +bool BytecodeUtils::get_NPE_message_at(outputStream* ss, Method* method, int bci) { + + NoSafepointVerifier _nsv; // Cannot use this object over a safepoint. + + // If this NPE was created via reflection, we have no real NPE. + if (method->method_holder() == + SystemDictionary::reflect_NativeConstructorAccessorImpl_klass()) { + return false; + } + + // Analyse the bytecodes. + ResourceMark rm; + ExceptionMessageBuilder emb(method, bci); + + // The slot of the operand stack that contains the null reference. + // Also checks for NPE explicitly constructed and returns NPE_EXPLICIT_CONSTRUCTED. + int slot = emb.get_NPE_null_slot(bci); + + // Build the message. + if (slot == NPE_EXPLICIT_CONSTRUCTED) { + // We don't want to print a message. + return false; + } else if (slot == INVALID_BYTECODE_ENCOUNTERED) { + // We encountered a bytecode that does not dereference a reference. + DEBUG_ONLY(ss->print("There cannot be a NullPointerException at bci %d of method %s", + bci, method->external_name())); + NOT_DEBUG(return false); + } else { + // Print string describing which action (bytecode) could not be + // performed because of the null reference. + emb.print_NPE_failed_action(ss, bci); + // Print a description of what is null. + if (!emb.print_NPE_cause(ss, bci, slot)) { + // Nothing was printed. End the sentence without the 'because' + // subordinate sentence. + ss->print("."); + } + } + return true; +} diff --git a/src/hotspot/share/interpreter/bytecodeUtils.hpp b/src/hotspot/share/interpreter/bytecodeUtils.hpp new file mode 100644 --- /dev/null +++ b/src/hotspot/share/interpreter/bytecodeUtils.hpp @@ -0,0 +1,41 @@ +/* + * 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_INTERPRETER_BYTECODEUTILS_HPP +#define SHARE_INTERPRETER_BYTECODEUTILS_HPP + +#include "memory/allocation.hpp" +#include "utilities/globalDefinitions.hpp" + +class Method; +class outputStream; + +class BytecodeUtils : public AllStatic { + public: + // NPE extended message. Return true if string is printed. + static bool get_NPE_message_at(outputStream* ss, Method* method, int bci); +}; + +#endif // SHARE_INTERPRETER_BYTECODEUTILS_HPP diff --git a/src/hotspot/share/prims/jvm.cpp b/src/hotspot/share/prims/jvm.cpp --- a/src/hotspot/share/prims/jvm.cpp +++ b/src/hotspot/share/prims/jvm.cpp @@ -38,6 +38,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" @@ -531,13 +532,37 @@ // 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)) + if (!ShowCodeDetailsInExceptionMessages) return NULL; + + oop exc = JNIHandles::resolve_non_null(throwable); + + Method* method; + int bci; + if (!java_lang_Throwable::get_top_method_and_bci(exc, &method, &bci)) { + return NULL; + } + if (method->is_native()) { + return NULL; + } + + stringStream ss; + bool ok = BytecodeUtils::get_NPE_message_at(&ss, method, bci); + if (ok) { + oop result = java_lang_String::create_oop_from_str(ss.base(), CHECK_0); + return (jstring) JNIHandles::make_local(env, result); + } else { + return NULL; + } +JVM_END // java.lang.StackTraceElement ////////////////////////////////////////////// diff --git a/src/hotspot/share/runtime/globals.hpp b/src/hotspot/share/runtime/globals.hpp --- a/src/hotspot/share/runtime/globals.hpp +++ b/src/hotspot/share/runtime/globals.hpp @@ -643,6 +643,10 @@ product(bool, OmitStackTraceInFastThrow, true, \ "Omit backtraces for some 'hot' exceptions in optimized code") \ \ + manageable(bool, ShowCodeDetailsInExceptionMessages, false, \ + "Show exception messages from RuntimeExceptions that contain " \ + "snippets of the failing code. Disable this to improve privacy.") \ + \ product(bool, PrintWarnings, true, \ "Print JVM warnings to output stream") \ \ diff --git a/src/java.base/share/classes/java/lang/NullPointerException.java b/src/java.base/share/classes/java/lang/NullPointerException.java --- a/src/java.base/share/classes/java/lang/NullPointerException.java +++ b/src/java.base/share/classes/java/lang/NullPointerException.java @@ -70,4 +70,35 @@ public NullPointerException(String s) { super(s); } + + /** + * Returns the detail message string of this throwable. + * + *

If a non-null message was supplied in a constructor it is + * returned. Otherwise, an implementation specific message or + * {@code null} is returned. + * + * @implNote + * If no explicit message was passed to the constructor, and as + * long as certain internal information is available, a verbose + * description of the null reference is returned. + * The internal information is not available in deserialized + * NullPointerExceptions. + * + * @return the detail message string, 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(); } diff --git a/src/java.base/share/native/libjava/NullPointerException.c b/src/java.base/share/native/libjava/NullPointerException.c new file mode 100644 --- /dev/null +++ b/src/java.base/share/native/libjava/NullPointerException.c @@ -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); +} diff --git a/test/hotspot/jtreg/runtime/exceptionMsgs/NullPointerException/NPEInHiddenTopFrameTest.java b/test/hotspot/jtreg/runtime/exceptionMsgs/NullPointerException/NPEInHiddenTopFrameTest.java new file mode 100644 --- /dev/null +++ b/test/hotspot/jtreg/runtime/exceptionMsgs/NullPointerException/NPEInHiddenTopFrameTest.java @@ -0,0 +1,84 @@ +/* + * 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 NullPointerException messages thrown in frames that + * are hidden in the backtrace/stackTrace. + * @bug 8218628 + * @library /test/lib + * @compile -g NPEInHiddenTopFrameTest.java + * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:-ShowHiddenFrames -XX:+ShowCodeDetailsInExceptionMessages NPEInHiddenTopFrameTest hidden + * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+ShowHiddenFrames -XX:+ShowCodeDetailsInExceptionMessages NPEInHiddenTopFrameTest visible + */ + +import jdk.test.lib.Asserts; + +public class NPEInHiddenTopFrameTest { + + @FunctionalInterface + private static interface SomeFunctionalInterface { + String someMethod(String a, String b); + } + + 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); + if (obtainedMsg == null && expectedMsg == null) return; + System.out.println("expected msg: " + expectedMsg); + Asserts.assertEquals(expectedMsg, obtainedMsg); + } + + public static void main(String[] args) throws Exception { + boolean framesAreHidden = false; + if (args.length > 0) { + String arg = args[0]; + if (arg.equals("hidden")) framesAreHidden = true; + } + + try { + final SomeFunctionalInterface concatter = String::concat; + final String nullString = null; + if (concatter != null) { + // This throws NPE from the lambda expression which is a hidden frame. + concatter.someMethod(nullString, "validString"); + } + } catch (NullPointerException e) { + checkMessage("concatter.someMethod(nullString, \"validString\");", e.getMessage(), + framesAreHidden ? + // This is the message that would be printed if the wrong method/bci are used: + // "Cannot invoke 'NPEInHiddenTopFrameTest$SomeFunctionalInterface.someMethod(String, String)'" + + // " because 'concatter' is null." + // But the NPE message generation now recognizes this situation and skips the + // message. So we expect null: + null : + // This is the correct message, but it describes code generated on-the-fly. + // You get it if you disable hiding frames (-XX:+ShowHiddenframes). + "Cannot invoke 'String.concat(String)' because '' is null." ); + e.printStackTrace(); + } + } +} diff --git a/test/hotspot/jtreg/runtime/exceptionMsgs/NullPointerException/NullPointerExceptionTest.java b/test/hotspot/jtreg/runtime/exceptionMsgs/NullPointerException/NullPointerExceptionTest.java new file mode 100644 --- /dev/null +++ b/test/hotspot/jtreg/runtime/exceptionMsgs/NullPointerException/NullPointerExceptionTest.java @@ -0,0 +1,1645 @@ +/* + * 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 + * classfiles generated with debug information. In this case the name + * of the variable containing the array is printed. + * @bug 8218628 + * @modules java.base/java.lang:open + * java.base/jdk.internal.org.objectweb.asm + * @library /test/lib + * @compile -g NullPointerExceptionTest.java + * @run main/othervm -XX:MaxJavaStackTraceDepth=1 -XX:+ShowCodeDetailsInExceptionMessages NullPointerExceptionTest hasDebugInfo + */ +/** + * @test + * @summary Test extended NullPointerException message for class + * files generated without debugging information. The message lists + * detailed information about the entity that is null. + * @bug 8218628 + * @modules java.base/java.lang:open + * java.base/jdk.internal.org.objectweb.asm + * @library /test/lib + * @compile NullPointerExceptionTest.java + * @run main/othervm -XX:MaxJavaStackTraceDepth=1 -XX:+ShowCodeDetailsInExceptionMessages NullPointerExceptionTest + */ + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.lang.invoke.MethodHandles.Lookup; +import java.util.ArrayList; + +import jdk.internal.org.objectweb.asm.ClassWriter; +import jdk.internal.org.objectweb.asm.Label; +import jdk.internal.org.objectweb.asm.MethodVisitor; +import jdk.test.lib.Asserts; + +import static java.lang.invoke.MethodHandles.lookup; +import static jdk.internal.org.objectweb.asm.Opcodes.ACC_PUBLIC; +import static jdk.internal.org.objectweb.asm.Opcodes.ACC_SUPER; +import static jdk.internal.org.objectweb.asm.Opcodes.ACONST_NULL; +import static jdk.internal.org.objectweb.asm.Opcodes.ALOAD; +import static jdk.internal.org.objectweb.asm.Opcodes.ASTORE; +import static jdk.internal.org.objectweb.asm.Opcodes.GETFIELD; +import static jdk.internal.org.objectweb.asm.Opcodes.GETSTATIC; +import static jdk.internal.org.objectweb.asm.Opcodes.ICONST_1; +import static jdk.internal.org.objectweb.asm.Opcodes.INVOKESPECIAL; +import static jdk.internal.org.objectweb.asm.Opcodes.INVOKEVIRTUAL; +import static jdk.internal.org.objectweb.asm.Opcodes.IRETURN; +import static jdk.internal.org.objectweb.asm.Opcodes.RETURN; + +/** + * 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 names = new ArrayList<>(); + ArrayList curr; + static boolean hasDebugInfo = false; + + static { + staticArray = new int[1][][][]; + staticArray[0] = new int[1][][]; + staticArray[0][0] = new int[1][]; + } + + public static void checkMessage(Throwable t, String expression, + String obtainedMsg, String expectedMsg) { + System.out.println("\nSource code:\n " + expression + "\n\nOutput:"); + t.printStackTrace(System.out); + if (obtainedMsg != expectedMsg && // E.g. both are null. + !obtainedMsg.equals(expectedMsg)) { + System.out.println("expected msg: " + expectedMsg); + Asserts.assertEquals(expectedMsg, obtainedMsg); + } + System.out.println("\n----"); + } + + public static void main(String[] args) throws Exception { + NullPointerExceptionTest t = new NullPointerExceptionTest(); + if (args.length > 0) { + hasDebugInfo = true; + } + + System.out.println("Tests for the first part of the message:"); + System.out.println("========================================\n"); + + // Test the message printed for the failed action. + t.testFailedAction(); + + System.out.println("Tests for the second part of the message:"); + System.out.println("=========================================\n"); + // Test the method printed for the null entity. + t.testNullEntity(); + + System.out.println("Further tests:"); + System.out.println("==============\n"); + + // Test if parameters are used in the code. + // This is relevant if there is no debug information. + t.testParameters(); + + // Test that no message is printed for exceptions + // allocated explicitly. + t.testCreation(); + + // Test that no message is printed for exceptions + // thrown in native methods. + t.testNative(); + + // Test that two calls to getMessage() return the same + // message. + // It is a design decision that it returns two different + // String objects. + t.testSameMessage(); + + // Test serialization. + // It is a design decision that after serialization the + // the message is lost. + t.testSerialization(); + + // Test that messages are printed for code generated + // on-the-fly. + t.testGeneratedCode(); + + // Some more interesting complex messages. + t.testComplexMessages(); + } + + // Helper method to cause test case. + private double callWithTypes(String[][] dummy1, int[][][] dummy2, float dummy3, long dummy4, short dummy5, + boolean dummy6, byte dummy7, double dummy8, char dummy9) { + return 0.0; + } + + @SuppressWarnings("null") + public void testFailedAction() { + int[] ia1 = null; + float[] fa1 = null; + Object[] oa1 = null; + boolean[] za1 = null; + byte[] ba1 = null; + char[] ca1 = null; + short[] sa1 = null; + long[] la1 = null; + double[] da1 = null; + + // iaload + try { + int val = ia1[0]; + System.out.println(val); + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "int val = ia1[0];", e.getMessage(), + "Cannot load from int array because " + + (hasDebugInfo ? "'ia1'" : "''") + " is null."); + } + // faload + try { + float val = fa1[0]; + System.out.println(val); + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "float val = fa1[0];", e.getMessage(), + "Cannot load from float array because " + + (hasDebugInfo ? "'fa1'" : "''") + " is null."); + } + // aaload + try { + Object val = oa1[0]; + System.out.println(val); + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "Object val = oa1[0];", e.getMessage(), + "Cannot load from object array because " + + (hasDebugInfo ? "'oa1'" : "''") + " is null."); + } + // baload (boolean) + try { + boolean val = za1[0]; + System.out.println(val); + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "boolean val = za1[0];", e.getMessage(), + "Cannot load from byte/boolean array because " + + (hasDebugInfo ? "'za1'" : "''") + " is null."); + } + // baload (byte) + try { + byte val = ba1[0]; + System.out.println(val); + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "byte val = ba1[0];", e.getMessage(), + "Cannot load from byte/boolean array because " + + (hasDebugInfo ? "'ba1'" : "''") + " is null."); + } + // caload + try { + char val = ca1[0]; + System.out.println(val); + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "char val = ca1[0];", e.getMessage(), + "Cannot load from char array because " + + (hasDebugInfo ? "'ca1'" : "''") + " is null."); + } + // saload + try { + short val = sa1[0]; + System.out.println(val); + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "short val = sa1[0];", e.getMessage(), + "Cannot load from short array because " + + (hasDebugInfo ? "'sa1'" : "''") + " is null."); + } + // laload + try { + long val = la1[0]; + System.out.println(val); + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "long val = la1[0];", e.getMessage(), + "Cannot load from long array because " + + (hasDebugInfo ? "'la1'" : "''") + " is null."); + } + // daload + try { + double val = da1[0]; + System.out.println(val); + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "double val = da1[0];", e.getMessage(), + "Cannot load from double array because " + + (hasDebugInfo ? "'da1'" : "''") + " is null."); + } + + // iastore + try { + ia1[0] = 0; + System.out.println(ia1[0]); + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "ia1[0] = 0;", e.getMessage(), + "Cannot store to int array because " + + (hasDebugInfo ? "'ia1'" : "''") + " is null."); + } + // fastore + try { + fa1[0] = 0.7f; + System.out.println(fa1[0]); + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "fa1[0] = 0.7f;", e.getMessage(), + "Cannot store to float array because " + + (hasDebugInfo ? "'fa1'" : "''") + " is null."); + } + // aastore + try { + oa1[0] = new Object(); + System.out.println(oa1[0]); + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "oa1[0] = new Object();", e.getMessage(), + "Cannot store to object array because " + + (hasDebugInfo ? "'oa1'" : "''") + " is null."); + } + // bastore (boolean) + try { + za1[0] = false; + System.out.println(za1[0]); + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "za1[0] = false;", e.getMessage(), + "Cannot store to byte/boolean array because " + + (hasDebugInfo ? "'za1'" : "''") + " is null."); + } + // bastore (byte) + try { + ba1[0] = 0; + System.out.println(ba1[0]); + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "ba1[0] = 0;", e.getMessage(), + "Cannot store to byte/boolean array because " + + (hasDebugInfo ? "'ba1'" : "''") + " is null."); + } + // castore + try { + ca1[0] = 0; + System.out.println(ca1[0]); + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "ca1[0] = 0;", e.getMessage(), + "Cannot store to char array because " + + (hasDebugInfo ? "'ca1'" : "''") + " is null."); + } + // sastore + try { + sa1[0] = 0; + System.out.println(sa1[0]); + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "sa1[0] = 0;", e.getMessage(), + "Cannot store to short array because " + + (hasDebugInfo ? "'sa1'" : "''") + " is null."); + } + // lastore + try { + la1[0] = 0; + System.out.println(la1[0]); + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "la1[0] = 0;", e.getMessage(), + "Cannot store to long array because " + + (hasDebugInfo ? "'la1'" : "''") + " is null."); + } + // dastore + try { + da1[0] = 0; + System.out.println(da1[0]); + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "da1[0] = 0;", e.getMessage(), + "Cannot store to double array because " + + (hasDebugInfo ? "'da1'" : "''") + " is null."); + } + + // arraylength + try { + int val = za1.length; + System.out.println(val); + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, " int val = za1.length;", e.getMessage(), + "Cannot read the array length because " + + (hasDebugInfo ? "'za1'" : "''") + " is null."); + } + // athrow + try { + RuntimeException exc = null; + throw exc; + } catch (NullPointerException e) { + checkMessage(e, "throw exc;", e.getMessage(), + "Cannot throw exception because " + + (hasDebugInfo ? "'exc'" : "''") + " is null."); + } + // monitorenter + try { + synchronized (nullInstanceField) { + // desired + } + } catch (NullPointerException e) { + checkMessage(e, "synchronized (nullInstanceField) { ... }", e.getMessage(), + "Cannot enter synchronized block because " + + "'this.nullInstanceField' is null."); + } + // monitorexit + // No test available + + // getfield + try { + Object val = nullInstanceField.nullInstanceField; + System.out.println(val); + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "Object val = nullInstanceField.nullInstanceField;", e.getMessage(), + "Cannot read field 'nullInstanceField' because " + + "'this.nullInstanceField' is null."); + } + // putfield + try { + nullInstanceField.nullInstanceField = new NullPointerExceptionTest(); + System.out.println(nullInstanceField.nullInstanceField); + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "nullInstanceField.nullInstanceField = new NullPointerExceptionTest();", e.getMessage(), + "Cannot assign field 'nullInstanceField' because " + + "'this.nullInstanceField' is null."); + } + // invoke + try { + String val = nullInstanceField.toString(); + System.out.println(val); + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "String val = nullInstanceField.toString();", e.getMessage(), + "Cannot invoke 'Object.toString()' because " + + "'this.nullInstanceField' is null."); + } + // Test parameter and return types + try { + boolean val = (nullInstanceField.callWithTypes(null, null, 0.0f, 0L, (short)0, false, (byte)0, 0.0, 'x') == 0.0); + Asserts.assertTrue(val); + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "boolean val = (nullInstanceField.callWithTypes(null, null, 0.0f, 0L, (short)0, false, (byte)0, 0.0, 'x') == 0.0);", e.getMessage(), + "Cannot invoke 'NullPointerExceptionTest.callWithTypes(String[][], int[][][], float, long, short, boolean, byte, double, char)' because " + + "'this.nullInstanceField' is null."); + } + } + + static void test_iload() { + int i0 = 0; + int i1 = 1; + int i2 = 2; + int i3 = 3; + @SuppressWarnings("unused") + int i4 = 4; + int i5 = 5; + + int[][] a = new int[6][]; + + // iload_0 + try { + a[i0][0] = 77; + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "a[i0][0] = 77;", e.getMessage(), + "Cannot store to int array because " + + (hasDebugInfo ? "'a[i0]'" : "'[]'") + " is null."); + } + // iload_1 + try { + a[i1][0] = 77; + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "a[i1][0] = 77;", e.getMessage(), + "Cannot store to int array because " + + (hasDebugInfo ? "'a[i1]'" : "'[]'") + " is null."); + } + // iload_2 + try { + a[i2][0] = 77; + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "a[i2][0] = 77;", e.getMessage(), + "Cannot store to int array because " + + (hasDebugInfo ? "'a[i2]'" : "'[]'") + " is null."); + } + // iload_3 + try { + a[i3][0] = 77; + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "a[i3][0] = 77;", e.getMessage(), + "Cannot store to int array because " + + (hasDebugInfo ? "'a[i3]'" : "'[]'") + " is null."); + } + // iload + try { + a[i5][0] = 77; + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "a[i5][0] = 77;", e.getMessage(), + "Cannot store to int array because " + + (hasDebugInfo ? "'a[i5]'" : "'[]'") + " is null."); + } + } + + // Other datatyes than int are not needed. + // If we implement l2d and similar bytecodes, we can print + // long expressions as array indexes. Then these here could + // be used. + static void test_lload() { + long long0 = 0L; // l0 looks like 10. Therefore long0. + long long1 = 1L; + long long2 = 2L; + long long3 = 3L; + @SuppressWarnings("unused") + long long4 = 4L; + long long5 = 5L; + + int[][] a = new int[6][]; + + // lload_0 + try { + a[(int)long0][0] = 77; + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "a[(int)long0][0] = 77;", e.getMessage(), + "Cannot store to int array because " + + (hasDebugInfo ? "'a[...]'" : "'[...]'") + " is null."); + } + // lload_1 + try { + a[(int)long1][0] = 77; + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "a[(int)long1][0] = 77;", e.getMessage(), + "Cannot store to int array because " + + (hasDebugInfo ? "'a[...]'" : "'[...]'") + " is null."); + } + // lload_2 + try { + a[(int)long2][0] = 77; + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "a[(int)long2][0] = 77;", e.getMessage(), + "Cannot store to int array because " + + (hasDebugInfo ? "'a[...]'" : "'[...]'") + " is null."); + } + // lload_3 + try { + a[(int)long3][0] = 77; + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "a[(int)long3][0] = 77;", e.getMessage(), + "Cannot store to int array because " + + (hasDebugInfo ? "'a[...]'" : "'[...]'") + " is null."); + } + // lload + try { + a[(int)long5][0] = 77; + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "a[(int)long5][0] = 77;", e.getMessage(), + "Cannot store to int array because " + + (hasDebugInfo ? "'a[...]'" : "'[...]'") + " is null."); + } + } + + static void test_fload() { + float f0 = 0.0f; + float f1 = 1.0f; + float f2 = 2.0f; + float f3 = 3.0f; + @SuppressWarnings("unused") + float f4 = 4.0f; + float f5 = 5.0f; + + int[][] a = new int[6][]; + + // fload_0 + try { + a[(int)f0][0] = 77; + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "a[(int)f0][0] = 77;", e.getMessage(), + "Cannot store to int array because " + + (hasDebugInfo ? "'a[...]'" : "'[...]'") + " is null."); + } + // fload_1 + try { + a[(int)f1][0] = 77; + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "a[(int)f1][0] = 77;", e.getMessage(), + "Cannot store to int array because " + + (hasDebugInfo ? "'a[...]'" : "'[...]'") + " is null."); + } + // fload_2 + try { + a[(int)f2][0] = 77; + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "a[(int)f2][0] = 77;", e.getMessage(), + "Cannot store to int array because " + + (hasDebugInfo ? "'a[...]'" : "'[...]'") + " is null."); + } + // fload_3 + try { + a[(int)f3][0] = 77; + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "a[(int)f3][0] = 77;", e.getMessage(), + "Cannot store to int array because " + + (hasDebugInfo ? "'a[...]'" : "'[...]'") + " is null."); + } + // fload + try { + a[(int)f5][0] = 77; + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "a[(int)f5][0] = 77;", e.getMessage(), + "Cannot store to int array because " + + (hasDebugInfo ? "'a[...]'" : "'[...]'") + " is null."); + } + } + + @SuppressWarnings("null") + static void test_aload() { + F f0 = null; + F f1 = null; + F f2 = null; + F f3 = null; + @SuppressWarnings("unused") + F f4 = null; + F f5 = null; + + // aload_0 + try { + f0.i = 33; + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "f0.i = 33;", e.getMessage(), + "Cannot assign field 'i' because " + + (hasDebugInfo ? "'f0'" : "''") + " is null."); + } + // aload_1 + try { + f1.i = 33; + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "f1.i = 33;", e.getMessage(), + "Cannot assign field 'i' because " + + (hasDebugInfo ? "'f1'" : "''") + " is null."); + } + // aload_2 + try { + f2.i = 33; + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "f2.i = 33;", e.getMessage(), + "Cannot assign field 'i' because " + + (hasDebugInfo ? "'f2'" : "''") + " is null."); + } + // aload_3 + try { + f3.i = 33; + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "f3.i = 33;", e.getMessage(), + "Cannot assign field 'i' because " + + (hasDebugInfo ? "'f3'" : "''") + " is null."); + } + // aload + try { + f5.i = 33; + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "f5.i = 33;", e.getMessage(), + "Cannot assign field 'i' because " + + (hasDebugInfo ? "'f5'" : "''") + " is null."); + } + } + + // Helper class for test cases. + class A { + public B to_b; + public B getB() { return to_b; } + } + + // Helper class for test cases. + class B { + public C to_c; + public B to_b; + public C getC() { return to_c; } + public B getBfromB() { return to_b; } + } + + // Helper class for test cases. + class C { + public D to_d; + public D getD() { return to_d; } + } + + // Helper class for test cases. + class D { + public int num; + public int[][] ar; + } + + + @SuppressWarnings("null") + public void testArrayChasing() { + int[][][][][][] a = null; + try { + a[0][0][0][0][0][0] = 99; + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "a[0][0][0][0][0] = 99; // a is null", e.getMessage(), + "Cannot load from object array because " + + (hasDebugInfo ? "'a'" : "''") + " is null."); + } + a = new int[1][][][][][]; + try { + a[0][0][0][0][0][0] = 99; + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "a[0][0][0][0][0] = 99; // a[0] is null", e.getMessage(), + "Cannot load from object array because " + + (hasDebugInfo ? "'a[0]'" : "'[0]'") + " is null."); + } + a[0] = new int[1][][][][]; + try { + a[0][0][0][0][0][0] = 99; + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "a[0][0][0][0][0] = 99; // a[0][0] is null", e.getMessage(), + "Cannot load from object array because " + + (hasDebugInfo ? "'a[0][0]'" : "'[0][0]'") + " is null."); + } + a[0][0] = new int[1][][][]; + try { + a[0][0][0][0][0][0] = 99; + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "a[0][0][0][0][0] = 99; // a[0][0][0] is null", e.getMessage(), + "Cannot load from object array because " + + (hasDebugInfo ? "'a[0][0][0]'" : "'[0][0][0]'") + " is null."); + } + try { + System.out.println(a[0][0][0].length); + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "a[0][0][0].length; // a[0][0][0] is null", e.getMessage(), + "Cannot read the array length because " + + (hasDebugInfo ? "'a[0][0][0]'" : "'[0][0][0]'") + " is null."); + } + a[0][0][0] = new int[1][][]; + try { + a[0][0][0][0][0][0] = 99; + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "a[0][0][0][0][0] = 99; // a[0][0][0][0] is null", e.getMessage(), + "Cannot load from object array because " + + (hasDebugInfo ? "'a[0][0][0][0]'" : "'[0][0][0][0]'") + " is null."); + } + a[0][0][0][0] = new int[1][]; + // Reaching max recursion depth. Prints . + try { + a[0][0][0][0][0][0] = 99; + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "a[0][0][0][0][0] = 99; // a[0][0][0][0][0] is null", e.getMessage(), + "Cannot store to int array because " + + "'[0][0][0][0][0]' is null."); + } + a[0][0][0][0][0] = new int[1]; + try { + a[0][0][0][0][0][0] = 99; + } catch (NullPointerException e) { + Asserts.fail(); + } + } + + @SuppressWarnings("null") + public void testPointerChasing() { + A a = null; + try { + a.to_b.to_c.to_d.num = 99; + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "a.to_b.to_c.to_d.num = 99; // a is null", e.getMessage(), + "Cannot read field 'to_b' because " + + (hasDebugInfo ? "'a'" : "''") + " is null."); + } + a = new A(); + try { + a.to_b.to_c.to_d.num = 99; + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "a.to_b.to_c.to_d.num = 99; // a.to_b is null", e.getMessage(), + "Cannot read field 'to_c' because " + + (hasDebugInfo ? "'a.to_b'" : "'.to_b'") + " is null."); + } + a.to_b = new B(); + try { + a.to_b.to_c.to_d.num = 99; + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "a.to_b.to_c.to_d.num = 99; // a.to_b.to_c is null", e.getMessage(), + "Cannot read field 'to_d' because " + + (hasDebugInfo ? "'a.to_b.to_c'" : "'.to_b.to_c'") + " is null."); + } + a.to_b.to_c = new C(); + try { + a.to_b.to_c.to_d.num = 99; + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "a.to_b.to_c.to_d.num = 99; // a.to_b.to_c.to_d is null", e.getMessage(), + "Cannot assign field 'num' because " + + (hasDebugInfo ? "'a.to_b.to_c.to_d'" : "'.to_b.to_c.to_d'") + " is null."); + } + } + + @SuppressWarnings("null") + public void testMethodChasing() { + A a = null; + try { + a.getB().getBfromB().getC().getD().num = 99; + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "a.getB().getBfromB().getC().getD().num = 99; // a is null", e.getMessage(), + "Cannot invoke 'NullPointerExceptionTest$A.getB()' because " + + (hasDebugInfo ? "'a" : "'") + "' is null."); + } + a = new A(); + try { + a.getB().getBfromB().getC().getD().num = 99; + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "a.getB().getBfromB().getC().getD().num = 99; // a.getB() is null", e.getMessage(), + "Cannot invoke 'NullPointerExceptionTest$B.getBfromB()' because " + + "the return value of 'NullPointerExceptionTest$A.getB()' is null."); + } + a.to_b = new B(); + try { + a.getB().getBfromB().getC().getD().num = 99; + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "a.getB().getBfromB().getC().getD().num = 99; // a.getB().getBfromB() is null", e.getMessage(), + "Cannot invoke 'NullPointerExceptionTest$B.getC()' because " + + "the return value of 'NullPointerExceptionTest$B.getBfromB()' is null."); + } + a.to_b.to_b = new B(); + try { + a.getB().getBfromB().getC().getD().num = 99; + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "a.getB().getBfromB().getC().getD().num = 99; // a.getB().getBfromB().getC() is null", e.getMessage(), + "Cannot invoke 'NullPointerExceptionTest$C.getD()' because " + + "the return value of 'NullPointerExceptionTest$B.getC()' is null."); + } + a.to_b.to_b.to_c = new C(); + try { + a.getB().getBfromB().getC().getD().num = 99; + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "a.getB().getBfromB().getC().getD().num = 99; // a.getB().getBfromB().getC().getD() is null", e.getMessage(), + "Cannot assign field 'num' because " + + "the return value of 'NullPointerExceptionTest$C.getD()' is null."); + } + } + + @SuppressWarnings("null") + public void testMixedChasing() { + A a = null; + try { + a.getB().getBfromB().to_c.to_d.ar[0][0] = 99; + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "a.getB().getBfromB().to_c.to_d.ar[0][0] = 99; // a is null", e.getMessage(), + "Cannot invoke 'NullPointerExceptionTest$A.getB()' because " + + (hasDebugInfo ? "'a'" : "''") + " is null."); + } + a = new A(); + try { + a.getB().getBfromB().to_c.to_d.ar[0][0] = 99; + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "a.getB().getBfromB().to_c.to_d.ar[0][0] = 99; // a.getB() is null", e.getMessage(), + "Cannot invoke 'NullPointerExceptionTest$B.getBfromB()' because " + + "the return value of 'NullPointerExceptionTest$A.getB()' is null."); + } + a.to_b = new B(); + try { + a.getB().getBfromB().to_c.to_d.ar[0][0] = 99; + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "a.getB().getBfromB().to_c.to_d.ar[0][0] = 99; // a.getB().getBfromB() is null", e.getMessage(), + "Cannot read field 'to_c' because " + + "the return value of 'NullPointerExceptionTest$B.getBfromB()' is null."); + } + 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(e, "a.getB().getBfromB().to_c.to_d.ar[0][0] = 99; // a.getB().getBfromB().to_c is null", e.getMessage(), + "Cannot read field 'to_d' because " + + "'NullPointerExceptionTest$B.getBfromB().to_c' is null."); + } + 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(e, "a.getB().getBfromB().to_c.to_d.ar[0][0] = 99; // a.getB().getBfromB().to_c.to_d is null", e.getMessage(), + "Cannot read field 'ar' because " + + "'NullPointerExceptionTest$B.getBfromB().to_c.to_d' is null."); + } + 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(e, "a.getB().getBfromB().to_c.to_d.ar[0][0] = 99; // a.getB().getBfromB().to_c.to_d.ar is null", e.getMessage(), + "Cannot load from object array because " + + "'NullPointerExceptionTest$B.getBfromB().to_c.to_d.ar' is null."); + } + try { + a.getB().getBfromB().getC().getD().ar[0][0] = 99; + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "a.getB().getBfromB().getC().getD().ar[0][0] = 99; // a.getB().getBfromB().getC().getD().ar is null", e.getMessage(), + "Cannot load from object array because " + + "'NullPointerExceptionTest$C.getD().ar' is null."); + } + 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(e, "a.getB().getBfromB().to_c.to_d.ar[0][0] = 99; // a.getB().getBfromB().to_c.to_d.ar[0] is null", e.getMessage(), + "Cannot store to int array because " + + "'NullPointerExceptionTest$B.getBfromB().to_c.to_d.ar[0]' is null."); + } + try { + a.getB().getBfromB().getC().getD().ar[0][0] = 99; + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "a.getB().getBfromB().getC().getD().ar[0][0] = 99; // a.getB().getBfromB().getC().getD().ar[0] is null", e.getMessage(), + "Cannot store to int array because " + + "'NullPointerExceptionTest$C.getD().ar[0]' is null."); + } + } + + // Helper method to cause test case. + private Object returnNull(String[][] dummy1, int[][][] dummy2, float dummy3) { + return null; + } + + // Helper method to cause test case. + private NullPointerExceptionTest returnMeAsNull(Throwable dummy1, int dummy2, char dummy3) { + return null; + } + + // Helper interface for test cases. + static interface DoubleArrayGen { + public double[] getArray(); + } + + // Helper class for test cases. + static class DoubleArrayGenImpl implements DoubleArrayGen { + @Override + public double[] getArray() { + return null; + } + } + + // Helper class for test cases. + static class NullPointerGenerator { + public static Object nullReturner(boolean dummy1) { + return null; + } + + public Object returnMyNull(double dummy1, long dummy2, short dummy3) { + return null; + } + } + + // Helper method to cause test case. + public void ImplTestLoadedFromMethod(DoubleArrayGen gen) { + try { + (gen.getArray())[0] = 1.0; + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "(gen.getArray())[0] = 1.0;", e.getMessage(), + "Cannot store to double array because " + + "the return value of 'NullPointerExceptionTest$DoubleArrayGen.getArray()' is null."); + } + } + + public void testNullEntity() { + int[][] a = new int[820][]; + + test_iload(); + test_lload(); + test_fload(); + // test_dload(); + test_aload(); + // aload_0: 'this' + try { + this.nullInstanceField.nullInstanceField = new NullPointerExceptionTest(); + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "this.nullInstanceField.nullInstanceField = new NullPointerExceptionTest();", e.getMessage(), + "Cannot assign field 'nullInstanceField' because 'this.nullInstanceField' is null."); + } + + // aconst_null + try { + throw null; + } catch (NullPointerException e) { + checkMessage(e, "throw null;", e.getMessage(), + "Cannot throw exception because 'null' is null."); + } + // iconst_0 + try { + a[0][0] = 77; + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "a[0][0] = 77;", e.getMessage(), + "Cannot store to int array because " + + (hasDebugInfo ? "'a[0]'" : "'[0]'") + " is null."); + } + // iconst_1 + try { + a[1][0] = 77; + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "a[1][0] = 77;", e.getMessage(), + "Cannot store to int array because " + + (hasDebugInfo ? "'a[1]'" : "'[1]'") + " is null."); + } + // iconst_2 + try { + a[2][0] = 77; + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "a[2][0] = 77;", e.getMessage(), + "Cannot store to int array because " + + (hasDebugInfo ? "'a[2]'" : "'[2]'") + " is null."); + } + // iconst_3 + try { + a[3][0] = 77; + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "a[3][0] = 77;", e.getMessage(), + "Cannot store to int array because " + + (hasDebugInfo ? "'a[3]'" : "'[3]'") + " is null."); + } + // iconst_4 + try { + a[4][0] = 77; + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "a[4][0] = 77;", e.getMessage(), + "Cannot store to int array because " + + (hasDebugInfo ? "'a[4]'" : "'[4]'") + " is null."); + } + // iconst_5 + try { + a[5][0] = 77; + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "a[5][0] = 77;", e.getMessage(), + "Cannot store to int array because " + + (hasDebugInfo ? "'a[5]'" : "'[5]'") + " is null."); + } + // long --> iconst + try { + a[(int)0L][0] = 77; + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "a[(int)0L][0] = 77;", e.getMessage(), + "Cannot store to int array because " + + (hasDebugInfo ? "'a[0]'" : "'[0]'") + " is null."); + } + // bipush + try { + a[139 /*0x77*/][0] = 77; + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "a[139][0] = 77;", e.getMessage(), + "Cannot store to int array because " + + (hasDebugInfo ? "'a[139]'" : "'[139]'") + " is null."); + } + // sipush + try { + a[819 /*0x333*/][0] = 77; + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "a[819][0] = 77;", e.getMessage(), + "Cannot store to int array because " + + (hasDebugInfo ? "'a[819]'" : "'[819]'") + " is null."); + } + + // aaload, with recursive descend. + testArrayChasing(); + + // getstatic + try { + boolean val = (((float[]) nullStaticField)[0] == 1.0f); + Asserts.assertTrue(val); + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "boolean val = (((float[]) nullStaticField)[0] == 1.0f);", e.getMessage(), + "Cannot load from float array because 'NullPointerExceptionTest.nullStaticField' is null."); + } + + // getfield, with recursive descend. + testPointerChasing(); + + // invokestatic + try { + char val = ((char[]) NullPointerGenerator.nullReturner(false))[0]; + System.out.println(val); + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "char val = ((char[]) NullPointerGenerator.nullReturner(false))[0];", e.getMessage(), + "Cannot load from char array because " + + "the return value of 'NullPointerExceptionTest$NullPointerGenerator.nullReturner(boolean)' is null."); + } + // invokevirtual + try { + char val = ((char[]) (new NullPointerGenerator().returnMyNull(1, 1, (short) 1)))[0]; + System.out.println(val); + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "char val = ((char[]) (new NullPointerGenerator().returnMyNull(1, 1, (short) 1)))[0];", e.getMessage(), + "Cannot load from char array because " + + "the return value of 'NullPointerExceptionTest$NullPointerGenerator.returnMyNull(double, long, short)' is null."); + } + // Call with array arguments. + try { + double val = ((double[]) returnNull(null, null, 1f))[0]; + System.out.println(val); + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "double val = ((double[]) returnNull(null, null, 1f))[0];", e.getMessage(), + "Cannot load from double array because " + + "the return value of 'NullPointerExceptionTest.returnNull(String[][], int[][][], float)' is null."); + } + // invokeinterface + ImplTestLoadedFromMethod(new DoubleArrayGenImpl()); + try { + returnMeAsNull(null, 1, 'A').dag = new DoubleArrayGenImpl(); + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "returnMeAsNull(null, 1, 'A').dag = new DoubleArrayGenImpl();", e.getMessage(), + "Cannot assign field 'dag' because " + + "the return value of 'NullPointerExceptionTest.returnMeAsNull(java.lang.Throwable, int, char)' is null."); + } + testMethodChasing(); + + // Mixed recursive descend. + testMixedChasing(); + } + + // The double placeholder takes two slots on the stack. + public void testParametersTestMethod(A a, double placeholder, B b, Integer i) throws Exception { + try { + a.to_b.to_c.to_d.num = 99; + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "a.to_b.to_c.to_d.num = 99; // to_c is null, a is a parameter.", e.getMessage(), + "Cannot read field 'to_d' because '" + + (hasDebugInfo ? "a" : "") + ".to_b.to_c' is null."); + } + + try { + b.to_c.to_d.num = 99; + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "b.to_c.to_d.num = 99; // b is null and b is a parameter.", e.getMessage(), + "Cannot read field 'to_c' because " + + // We expect number '3' for the parameter. + (hasDebugInfo ? "'b'" : "''") + " is null."); + } + + + try { + @SuppressWarnings("unused") + int my_i = i; + } catch (NullPointerException e) { + checkMessage(e, "int my_i = i; // i is a parameter of type Integer.", e.getMessage(), + "Cannot invoke 'java.lang.Integer.intValue()' because " + + (hasDebugInfo ? "'i'" : "''") + " is null."); + } + } + + + public void testParameters() throws Exception { + A a = new A(); + a.to_b = new B(); + testParametersTestMethod(a, 0.0, null, null); + } + + + public void testCreation() throws Exception { + // If allocated with new, the message should not be generated. + Asserts.assertNull(new NullPointerException().getMessage()); + String msg = new String("A pointless message."); + Asserts.assertTrue(new NullPointerException(msg).getMessage() == msg); + + // If created via reflection, the message should not be generated. + Exception ex = NullPointerException.class.getDeclaredConstructor().newInstance(); + Asserts.assertNull(ex.getMessage()); + } + + public void testNative() throws Exception { + // If NPE is thrown in a native method, the message should + // not be generated. + try { + Class.forName(null); + Asserts.fail(); + } catch (NullPointerException e) { + Asserts.assertNull(e.getMessage()); + } + + } + + // Test we get the same message calling npe.getMessage() twice. + @SuppressWarnings("null") + public void testSameMessage() throws Exception { + Object null_o = null; + String expectedMsg = + "Cannot invoke 'Object.hashCode()' because " + + (hasDebugInfo ? "'null_o" : "'") + "' is null."; + + try { + null_o.hashCode(); + Asserts.fail(); + } catch (NullPointerException e) { + String msg1 = e.getMessage(); + checkMessage(e, "null_o.hashCode()", msg1, expectedMsg); + String msg2 = e.getMessage(); + Asserts.assertTrue(msg1.equals(msg2)); + // It was decided that getMessage should generate the + // message anew on every call, so this does not hold. + //Asserts.assertTrue(msg1 == msg2); + Asserts.assertFalse(msg1 == msg2); + } + } + + @SuppressWarnings("null") + public void testSerialization() 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 { + int hc = null_o3.hashCode(); + System.out.println(hc); + Asserts.fail(); + } catch (NullPointerException npe3) { + o3 = npe3; + msg3 = npe3.getMessage(); + checkMessage(npe3, "int hc = null_o3.hashCode();", msg3, + "Cannot invoke 'Object.hashCode()' because " + + (hasDebugInfo ? "'null_o3'" : "''") + " is null."); + } + 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 or implement writeReplace(), + // thus it can not be recovered by serialization. + //Asserts.assertEquals(ex3.getMessage(), msg3); + Asserts.assertEquals(ex3.getMessage(), null); + } + + static int index17 = 17; + int getIndex17() { return 17; }; + + @SuppressWarnings({ "unused", "null" }) + public void testComplexMessages() { + try { + staticLongArray[0][0] = 2L; + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "staticLongArray[0][0] = 2L;", e.getMessage(), + "Cannot store to long array because " + + "'NullPointerExceptionTest.staticLongArray[0]' is null."); + } + + try { + NullPointerExceptionTest obj = this; + Object val = obj.dag.getArray().clone(); + Asserts.assertNull(val); + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "Object val = obj.dag.getArray().clone();", e.getMessage(), + "Cannot invoke 'NullPointerExceptionTest$DoubleArrayGen.getArray()' because " + + (hasDebugInfo ? "'obj" : "'") + ".dag' is null."); + } + try { + int indexes[] = new int[1]; + NullPointerExceptionTest[] objs = new NullPointerExceptionTest[] {this}; + Object val = objs[indexes[0]].nullInstanceField.returnNull(null, null, 1f); + Asserts.assertNull(val); + Asserts.fail(); + } catch (NullPointerException e) { + checkMessage(e, "Object val = objs[indexes[0]].nullInstanceField.returnNull(null, null, 1f);", e.getMessage(), + "Cannot invoke 'NullPointerExceptionTest.returnNull(String[][], int[][][], float)' because " + + (hasDebugInfo ? "'objs[indexes" : "'[") + "[0]].nullInstanceField' is null."); + } + + try { + int indexes[] = new int[1]; + NullPointerExceptionTest[][] objs = + new NullPointerExceptionTest[][] {new NullPointerExceptionTest[] {this}}; + synchronized (objs[indexes[0]][0].nullInstanceField) { + Asserts.fail(); + } + } catch (NullPointerException e) { + checkMessage(e, "synchronized (objs[indexes[0]][0].nullInstanceField) { ... }", e.getMessage(), + "Cannot enter synchronized block because " + + (hasDebugInfo ? "'objs[indexes" : "'[" ) + "[0]][0].nullInstanceField' is null."); + } + + try { + // If we can get the value from more than one bci, we cannot know which one + // is null. Make sure we don't print the wrong value. + String s = null; + @SuppressWarnings("unused") + byte[] val = (Math.random() < 0.5 ? s : (new String[1])[0]).getBytes(); + } catch (NullPointerException e) { + checkMessage(e, "byte[] val = (Math.random() < 0.5 ? s : (new String[1])[0]).getBytes();", e.getMessage(), + "Cannot invoke 'String.getBytes()'."); + } + + try { + // If we can get the value from more than one bci, we cannot know which one + // is null. Make sure we don't print the wrong value. Also make sure if + // we don't print the failed action we don't print a string at all. + int[][] a = new int[1][]; + int[][] b = new int[2][]; + long index = 0; + @SuppressWarnings("unused") + int val = (Math.random() < 0.5 ? a[(int)index] : b[(int)index])[13]; + } catch (NullPointerException e) { + checkMessage(e, "int val = (Math.random() < 0.5 ? a[(int)index] : b[(int)index])[13]", e.getMessage(), + "Cannot load from int array."); + } + + try { + // If we can get the value from more than one bci, we cannot know which one + // is null. Make sure we don't print the wrong value. Also make sure if + // we don't print the failed action we don't print a string at all. + int[][] a = new int[1][]; + int[][] b = new int[2][]; + long index = 0; + int val = (Math.random() < 0.5 ? a : b)[(int)index][13]; + } catch (NullPointerException e) { + checkMessage(e, "int val = (Math.random() < 0.5 ? a : b)[(int)index][13]", e.getMessage(), + "Cannot load from int array because '[...]' is null."); + } + + try { + C c1 = new C(); + C c2 = new C(); + (Math.random() < 0.5 ? c1 : c2).to_d.num = 77; + } catch (NullPointerException e) { + checkMessage(e, "(Math.random() < 0.5 ? c1 : c2).to_d.num = 77;", e.getMessage(), + "Cannot assign field 'num' because 'to_d' is null."); + } + + // Static variable as array index. + try { + staticLongArray[index17][0] = 2L; + } catch (NullPointerException e) { + checkMessage(e, "staticLongArray[index17][0] = 2L;", e.getMessage(), + "Cannot store to long array because " + + "'NullPointerExceptionTest.staticLongArray[NullPointerExceptionTest.index17]' is null."); + } + + // Method call as array index. + try { + staticLongArray[getIndex17()][0] = 2L; + } catch (NullPointerException e) { + checkMessage(e, "staticLongArray[getIndex17()][0] = 2L;", e.getMessage(), + "Cannot store to long array because " + + "'NullPointerExceptionTest.staticLongArray[NullPointerExceptionTest.getIndex17()]' is null."); + } + + // Unboxing. + Integer a = null; + try { + int b = a; + } catch (NullPointerException e) { + checkMessage(e, "Integer a = null; int b = a;", e.getMessage(), + "Cannot invoke 'java.lang.Integer.intValue()' because " + + (hasDebugInfo ? "'a'" : "''") + " is null."); + } + + // Unboxing by hand. Has the same message as above. + try { + int b = a.intValue(); + } catch (NullPointerException e) { + checkMessage(e, "Integer a = null; int b = a.intValue();", e.getMessage(), + "Cannot invoke 'java.lang.Integer.intValue()' because " + + (hasDebugInfo ? "'a'" : "''") + " is null."); + } + } + + // Generates: + // class E implements E0 { + // public int throwNPE(F f) { + // return f.i; + // } + // public void throwNPE_reuseStackSlot1(String s1) { + // System.out.println(s1.substring(1)); + // String s1_2 = null; // Reuses slot 1. + // System.out.println(s1_2.substring(1)); + // } + // public void throwNPE_reuseStackSlot4(String s1, String s2, String s3, String s4) { + // System.out.println(s4.substring(1)); + // String s4_2 = null; // Reuses slot 4. + // System.out.println(s4_2.substring(1)); + // } + // } + static byte[] generateTestClass() { + ClassWriter cw = new ClassWriter(0); + MethodVisitor mv; + + cw.visit(50, ACC_SUPER, "E", null, "java/lang/Object", new String[] { "E0" }); + + { + mv = cw.visitMethod(0, "", "()V", null, null); + mv.visitCode(); + mv.visitVarInsn(ALOAD, 0); + mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V", false); + mv.visitInsn(RETURN); + mv.visitMaxs(1, 1); + mv.visitEnd(); + } + + { + mv = cw.visitMethod(ACC_PUBLIC, "throwNPE", "(LF;)I", null, null); + mv.visitCode(); + Label label0 = new Label(); + mv.visitLabel(label0); + mv.visitLineNumber(118, label0); + mv.visitVarInsn(ALOAD, 1); + mv.visitFieldInsn(GETFIELD, "F", "i", "I"); + mv.visitInsn(IRETURN); + Label label1 = new Label(); + mv.visitLabel(label1); + mv.visitLocalVariable("this", "LE;", null, label0, label1, 0); + mv.visitLocalVariable("f", "LE;", null, label0, label1, 1); + mv.visitMaxs(1, 2); + mv.visitEnd(); + } + + { + mv = cw.visitMethod(ACC_PUBLIC, "throwNPE_reuseStackSlot1", "(Ljava/lang/String;)V", null, null); + mv.visitCode(); + Label label0 = new Label(); + mv.visitLabel(label0); + mv.visitLineNumber(7, label0); + mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); + mv.visitVarInsn(ALOAD, 1); + mv.visitInsn(ICONST_1); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "substring", "(I)Ljava/lang/String;", false); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false); + Label label1 = new Label(); + mv.visitLabel(label1); + mv.visitLineNumber(8, label1); + mv.visitInsn(ACONST_NULL); + mv.visitVarInsn(ASTORE, 1); + Label label2 = new Label(); + mv.visitLabel(label2); + mv.visitLineNumber(9, label2); + mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); + mv.visitVarInsn(ALOAD, 1); + mv.visitInsn(ICONST_1); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "substring", "(I)Ljava/lang/String;", false); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false); + Label label3 = new Label(); + mv.visitLabel(label3); + mv.visitLineNumber(10, label3); + mv.visitInsn(RETURN); + mv.visitMaxs(3, 3); + mv.visitEnd(); + } + + { + mv = cw.visitMethod(ACC_PUBLIC, "throwNPE_reuseStackSlot4", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V", null, null); + mv.visitCode(); + Label label0 = new Label(); + mv.visitLabel(label0); + mv.visitLineNumber(12, label0); + mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); + mv.visitVarInsn(ALOAD, 4); + mv.visitInsn(ICONST_1); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "substring", "(I)Ljava/lang/String;", false); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false); + Label label1 = new Label(); + mv.visitLabel(label1); + mv.visitLineNumber(13, label1); + mv.visitInsn(ACONST_NULL); + mv.visitVarInsn(ASTORE, 4); + Label label2 = new Label(); + mv.visitLabel(label2); + mv.visitLineNumber(14, label2); + mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); + mv.visitVarInsn(ALOAD, 4); + mv.visitInsn(ICONST_1); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "substring", "(I)Ljava/lang/String;", false); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false); + Label label3 = new Label(); + mv.visitLabel(label3); + mv.visitLineNumber(15, label3); + mv.visitInsn(RETURN); + mv.visitMaxs(3, 6); + mv.visitEnd(); + } + + cw.visitEnd(); + + return cw.toByteArray(); + } + + // Assign to a parameter. + // Without debug information, this will print "parameter1" if a NPE + // is raised in the first line because null was passed to the method. + // It will print "local1" if a NPE is raised in line three. + public void assign_to_parameter(String s1) { + System.out.println(s1.substring(1)); + s1 = null; + System.out.println(s1.substring(2)); + } + + // Tests that a class generated on the fly is handled properly. + public void testGeneratedCode() throws Exception { + byte[] classBytes = generateTestClass(); + Lookup lookup = lookup(); + Class clazz = lookup.defineClass(classBytes); + E0 e = (E0) clazz.getDeclaredConstructor().newInstance(); + try { + e.throwNPE(null); + } catch (NullPointerException ex) { + checkMessage(ex, "return f.i;", + ex.getMessage(), + "Cannot read field 'i' because 'f' is null."); + } + + // Optimized bytecode can reuse local variable slots for several + // local variables. + // If there is no variable name information, we print 'parameteri' + // if a parameter maps to a local slot. Once a local slot has been + // written, we don't know any more whether it was written as the + // corresponding parameter, or whether another local has been + // mapped to the slot. So we don't want to print 'parameteri' any + // more, but 'locali'. Similary for 'this'. + + // Expect message saying "parameter0". + try { + e.throwNPE_reuseStackSlot1(null); + } catch (NullPointerException ex) { + checkMessage(ex, "s1.substring(1)", + ex.getMessage(), + "Cannot invoke 'String.substring(int)' because '' is null."); + } + // Expect message saying "local0". + try { + e.throwNPE_reuseStackSlot1("aa"); + } catch (NullPointerException ex) { + checkMessage(ex, "s1_2.substring(1)", + ex.getMessage(), + "Cannot invoke 'String.substring(int)' because '' is null."); + } + // Expect message saying "parameter4". + try { + e.throwNPE_reuseStackSlot4("aa", "bb", "cc", null); + } catch (NullPointerException ex) { + checkMessage(ex, "s4.substring(1)", + ex.getMessage(), + "Cannot invoke 'String.substring(int)' because '' is null."); + } + // Expect message saying "local4". + try { + e.throwNPE_reuseStackSlot4("aa", "bb", "cc", "dd"); + } catch (NullPointerException ex) { + checkMessage(ex, "s4_2.substring(1)", + ex.getMessage(), + "Cannot invoke 'String.substring(int)' because '' is null."); + } + + // Unfortunately, with the fix for optimized code as described above + // we don't write 'parameteri' any more after the parameter variable + // has been assigned. + + if (!hasDebugInfo) { + // Expect message saying "parameter1". + try { + assign_to_parameter(null); + } catch (NullPointerException ex) { + checkMessage(ex, "s1.substring(1)", + ex.getMessage(), + "Cannot invoke 'String.substring(int)' because '' is null."); + } + // The message says "local1" although "parameter1" would be correct. + try { + assign_to_parameter("aaa"); + } catch (NullPointerException ex) { + checkMessage(ex, "s1.substring(2)", + ex.getMessage(), + "Cannot invoke 'String.substring(int)' because '' is null."); + } + } + } +} + +// Helper interface for test cases needed for generateTestClass(). +interface E0 { + public int throwNPE(F f); + public void throwNPE_reuseStackSlot1(String s1); + public void throwNPE_reuseStackSlot4(String s1, String s2, String s3, String s4); +} + +// Helper class for test cases needed for generateTestClass(). +class F { + int i; +} diff --git a/test/hotspot/jtreg/runtime/exceptionMsgs/NullPointerException/SuppressMessagesTest.java b/test/hotspot/jtreg/runtime/exceptionMsgs/NullPointerException/SuppressMessagesTest.java new file mode 100644 --- /dev/null +++ b/test/hotspot/jtreg/runtime/exceptionMsgs/NullPointerException/SuppressMessagesTest.java @@ -0,0 +1,82 @@ +/* + * 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 that the default of flag ShowCodeDetailsInExceptionMessages is 'false', + * i.e., make sure the VM does not print the message on default. + * @bug 8218628 + * @library /test/lib + * @compile -g SuppressMessagesTest.java + * @run main/othervm SuppressMessagesTest noMessage + */ +/** + * @test + * @summary Test that the messages are suppressed if flag ShowCodeDetailsInExceptionMessages is 'false'. + * @bug 8218628 + * @library /test/lib + * @compile -g SuppressMessagesTest.java + * @run main/othervm -XX:-ShowCodeDetailsInExceptionMessages SuppressMessagesTest noMessage + */ +/** + * @test + * @summary Test that the messages are printed if flag ShowCodeDetailsInExceptionMessages is 'true'. + * @bug 8218628 + * @library /test/lib + * @compile -g SuppressMessagesTest.java + * @run main/othervm -XX:+ShowCodeDetailsInExceptionMessages SuppressMessagesTest printMessage + */ + +import jdk.test.lib.Asserts; + +class A { + int aFld; +} + +// Tests that the messages are suppressed by flag ShowCodeDetailsInExceptionMessages. +public class SuppressMessagesTest { + + public static void main(String[] args) throws Exception { + A a = null; + + if (args.length != 1) { + Asserts.fail("You must specify one arg for this test."); + } + + try { + @SuppressWarnings("null") + int val = a.aFld; + System.out.println(val); + Asserts.fail(); + } catch (NullPointerException e) { + System.out.println("Stacktrace of the expected exception:"); + e.printStackTrace(System.out); + if (args[0].equals("noMessage")) { + Asserts.assertNull(e.getMessage()); + } else { + Asserts.assertEquals(e.getMessage(), "Cannot read field 'aFld' because 'a' is null."); + } + } + } +}