Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 20 additions & 8 deletions src/hotspot/share/classfile/javaClasses.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3056,21 +3056,33 @@ Handle java_lang_Throwable::create_initialization_error(JavaThread* current, Han
return init_error;
}

bool java_lang_Throwable::get_top_method_and_bci(oop throwable, Method** method, int* bci) {
bool java_lang_Throwable::get_method_and_bci(oop throwable, Method** method, int* bci, int depth, bool allow_hidden) {
JavaThread* current = JavaThread::current();
objArrayHandle result(current, objArrayOop(backtrace(throwable)));
BacktraceIterator iter(result, current);
// 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 != nullptr) {
return false;
// Restriction only exists for 1 depth; Objects.requireNonNull uses a hidden frame
if (!allow_hidden) {
oop hidden = ((objArrayOop)backtrace(throwable))->obj_at(trace_hidden_offset);
if (hidden != nullptr) {
return false;
}
}

// Get first backtrace element.
BacktraceIterator iter(result, current);
// Get the backtrace element.
do {
// No backtrace available.
if (!iter.repeat()) {
return false;
}


if (--depth > 0) {
iter.next(current);
}
} while (depth > 0);
BacktraceElement bte = iter.next(current);

InstanceKlass* holder = InstanceKlass::cast(java_lang_Class::as_Klass(bte._mirror()));
Expand Down
5 changes: 3 additions & 2 deletions src/hotspot/share/classfile/javaClasses.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -694,8 +694,9 @@ class java_lang_Throwable: AllStatic {
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);
// Gets the method and bci of a particular frame (TOS). Returns false if this failed.
// allow_hidden allows the caller of the NPE constructor to be a hidden frame.
static bool get_method_and_bci(oop throwable, Method** method, int* bci, int depth, bool allow_hidden);
};


Expand Down
2 changes: 1 addition & 1 deletion src/hotspot/share/include/jvm.h
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ JVM_InitStackTraceElement(JNIEnv* env, jobject element, jobject stackFrameInfo);
*/

JNIEXPORT jstring JNICALL
JVM_GetExtendedNPEMessage(JNIEnv *env, jthrowable throwable);
JVM_GetExtendedNPEMessage(JNIEnv *env, jthrowable throwable, jint stack_offset, jint search_slot);

/*
* java.lang.StackWalker
Expand Down
92 changes: 64 additions & 28 deletions src/hotspot/share/interpreter/bytecodeUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -192,9 +192,17 @@ class ExceptionMessageBuilder : public StackObj {
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 = nullptr);
bool inner_expr, bool because_clause = false);

public:
enum class BacktrackNullSlot : int {
// This NullPointerException is explicitly constructed
NPE_EXPLICIT_CONSTRUCTED = -2,
// There cannot be a NullPointerException at the given BCI
INVALID_BYTECODE_ENCOUNTERED = -1,
// A slot is found
FOUND
};
Comment on lines +198 to +205
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So you define an ENUM to capture the negative cases but then you can use any int >= 0 and pretend it is a member of the enum. ?? Is that typical C++ enum usage?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was from @jdksjolen's suggestion. I emulated how valhalla's LayoutKind is declared.


// Creates an ExceptionMessageBuilder object and runs the analysis
// building SimulatedOperandStacks for each bytecode in the given
Expand All @@ -214,7 +222,7 @@ class ExceptionMessageBuilder : public StackObj {
// 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);
BacktrackNullSlot 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.
Expand All @@ -226,9 +234,10 @@ class ExceptionMessageBuilder : public StackObj {
// 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.
// because_clause: Whether to prefix with " because ..."
//
// Returns false if nothing was printed, else true.
bool print_NPE_cause(outputStream *os, int bci, int slot);
bool print_NPE_cause(outputStream *os, int bci, int slot, bool because_clause);

// Prints a string describing the failed action.
void print_NPE_failed_action(outputStream *os, int bci);
Expand Down Expand Up @@ -311,7 +320,7 @@ static void print_local_var(outputStream *os, unsigned int bci, Method* method,

if ((bci >= start) && (bci < end) && (elem->slot == slot)) {
ConstantPool* cp = method->constants();
char *var = cp->symbol_at(elem->name_cp_index)->as_C_string();
char *var = cp->symbol_at(elem->name_cp_index)->as_C_string();
os->print("%s", var);

return;
Expand Down Expand Up @@ -342,6 +351,19 @@ static void print_local_var(outputStream *os, unsigned int bci, Method* method,
}

if (found && is_parameter) {
// check MethodParameters for a name, if it carries a name
int actual_param_index = param_index - 1; // 0 based
if (method->has_method_parameters() && actual_param_index < method->method_parameters_length()) {
MethodParametersElement elem = method->method_parameters_start()[actual_param_index];
if (elem.name_cp_index != 0) {
ConstantPool* cp = method->constants();
char *var = cp->symbol_at(elem.name_cp_index)->as_C_string();
os->print("%s", var);
return;
}
}

// TODO we should use arg%d forms, 0-based, like core reflection
os->print("<parameter%d>", param_index);
} else {
// This is the best we can do.
Expand Down Expand Up @@ -1092,9 +1114,7 @@ int ExceptionMessageBuilder::do_instruction(int bci) {
return len;
}

#define INVALID_BYTECODE_ENCOUNTERED -1
#define NPE_EXPLICIT_CONSTRUCTED -2
int ExceptionMessageBuilder::get_NPE_null_slot(int bci) {
ExceptionMessageBuilder::BacktrackNullSlot 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);
Expand All @@ -1110,7 +1130,7 @@ int ExceptionMessageBuilder::get_NPE_null_slot(int bci) {
case Bytecodes::_athrow:
case Bytecodes::_monitorenter:
case Bytecodes::_monitorexit:
return 0;
return static_cast<BacktrackNullSlot>(0);
case Bytecodes::_iaload:
case Bytecodes::_faload:
case Bytecodes::_aaload:
Expand All @@ -1119,25 +1139,25 @@ int ExceptionMessageBuilder::get_NPE_null_slot(int bci) {
case Bytecodes::_saload:
case Bytecodes::_laload:
case Bytecodes::_daload:
return 1;
return static_cast<BacktrackNullSlot>(1);
case Bytecodes::_iastore:
case Bytecodes::_fastore:
case Bytecodes::_aastore:
case Bytecodes::_bastore:
case Bytecodes::_castore:
case Bytecodes::_sastore:
return 2;
return static_cast<BacktrackNullSlot>(2);
case Bytecodes::_lastore:
case Bytecodes::_dastore:
return 3;
return static_cast<BacktrackNullSlot>(3);
case Bytecodes::_putfield: {
int cp_index = Bytes::get_native_u2(code_base + pos);
ConstantPool* cp = _method->constants();
int name_and_type_index = cp->name_and_type_ref_index_at(cp_index, code);
int type_index = cp->signature_ref_index_at(name_and_type_index);
Symbol* signature = cp->symbol_at(type_index);
BasicType bt = Signature::basic_type(signature);
return type2size[bt];
return static_cast<BacktrackNullSlot>(type2size[bt]);
}
case Bytecodes::_invokevirtual:
case Bytecodes::_invokespecial:
Expand All @@ -1155,21 +1175,21 @@ int ExceptionMessageBuilder::get_NPE_null_slot(int bci) {
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();
return static_cast<BacktrackNullSlot>(ArgumentSizeComputer(signature).size());
} else {
return NPE_EXPLICIT_CONSTRUCTED;
return BacktrackNullSlot::NPE_EXPLICIT_CONSTRUCTED;
}
}

default:
break;
}

return INVALID_BYTECODE_ENCOUNTERED;
return BacktrackNullSlot::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 \"")) {
bool ExceptionMessageBuilder::print_NPE_cause(outputStream* os, int bci, int slot, bool because_clause) {
if (print_NPE_cause0(os, bci, slot, _max_cause_detail, false, because_clause)) {
os->print("\" is null");
return true;
}
Expand All @@ -1182,16 +1202,16 @@ bool ExceptionMessageBuilder::print_NPE_cause(outputStream* os, int bci, int slo
// 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.
// because_clause is used to print a "because" prefix when this is
// not inner_expr ()
// 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) {
bool inner_expr, bool because_clause) {
assert(bci >= 0, "BCI too low");
assert(bci < get_size(), "BCI too large");

Expand Down Expand Up @@ -1227,12 +1247,16 @@ bool ExceptionMessageBuilder::print_NPE_cause0(outputStream* os, int bci, int sl
}

if (max_detail == _max_cause_detail &&
prefix != nullptr &&
!inner_expr &&
code != Bytecodes::_invokevirtual &&
code != Bytecodes::_invokespecial &&
code != Bytecodes::_invokestatic &&
code != Bytecodes::_invokeinterface) {
os->print("%s", prefix);
if (because_clause) {
os->print(" because \"");
} else {
os->print("\"");
}
}

switch (code) {
Expand Down Expand Up @@ -1347,7 +1371,12 @@ bool ExceptionMessageBuilder::print_NPE_cause0(outputStream* os, int bci, int sl
case Bytecodes::_invokeinterface: {
int cp_index = Bytes::get_native_u2(code_base + pos);
if (max_detail == _max_cause_detail && !inner_expr) {
os->print(" because the return value of \"");
if (because_clause) {
os->print(" because t");
} else {
os->print("T");
}
os->print("he return value of \"");
}
print_method_name(os, _method, cp_index, code);
return true;
Expand Down Expand Up @@ -1440,7 +1469,7 @@ void ExceptionMessageBuilder::print_NPE_failed_action(outputStream *os, int bci)
}

// Main API
bool BytecodeUtils::get_NPE_message_at(outputStream* ss, Method* method, int bci) {
bool BytecodeUtils::get_NPE_message_at(outputStream* ss, Method* method, int bci, NullSlot slot) {

NoSafepointVerifier _nsv; // Cannot use this object over a safepoint.

Expand All @@ -1454,15 +1483,22 @@ bool BytecodeUtils::get_NPE_message_at(outputStream* ss, Method* method, int bci
ResourceMark rm;
ExceptionMessageBuilder emb(method, bci);

// Is an explicit slot given?
if (slot != NullSlot::SEARCH) {
// Search from the given slot in bci in Method.
// Omit the failed action.
return emb.print_NPE_cause(ss, bci, static_cast<int>(slot), false);
}

// 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);
ExceptionMessageBuilder::BacktrackNullSlot backtrackSlot = emb.get_NPE_null_slot(bci);

// Build the message.
if (slot == NPE_EXPLICIT_CONSTRUCTED) {
if (backtrackSlot == ExceptionMessageBuilder::BacktrackNullSlot::NPE_EXPLICIT_CONSTRUCTED) {
// We don't want to print a message.
return false;
} else if (slot == INVALID_BYTECODE_ENCOUNTERED) {
} else if (backtrackSlot == ExceptionMessageBuilder::BacktrackNullSlot::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()));
Expand All @@ -1472,7 +1508,7 @@ bool BytecodeUtils::get_NPE_message_at(outputStream* ss, Method* method, int bci
// 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)) {
if (!emb.print_NPE_cause(ss, bci, static_cast<int>(backtrackSlot), true)) {
// Nothing was printed. End the sentence without the 'because'
// subordinate sentence.
}
Expand Down
14 changes: 12 additions & 2 deletions src/hotspot/share/interpreter/bytecodeUtils.hpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 2022 SAP SE. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
Expand Down Expand Up @@ -34,8 +34,18 @@ class outputStream;

class BytecodeUtils : public AllStatic {
public:
// The slot where the null value comes from.
enum class NullSlot : int {
// Unknown; perform a search at the given bci to check instead.
SEARCH = -1,
// A given slot value to search from.
EXPLICIT
};
// NPE extended message. Return true if string is printed.
static bool get_NPE_message_at(outputStream* ss, Method* method, int bci);
// Slot can be nonnegative to indicate an explicit search for the source of null.
// If slot is SEARCH (default), also search for the action that caused the NPE before
// deriving the actual slot and source of null by code parsing.
static bool get_NPE_message_at(outputStream* ss, Method* method, int bci, NullSlot slot = NullSlot::SEARCH);
};

#endif // SHARE_INTERPRETER_BYTECODEUTILS_HPP
11 changes: 8 additions & 3 deletions src/hotspot/share/prims/jvm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -503,22 +503,27 @@ JVM_END

// java.lang.NullPointerException ///////////////////////////////////////////

JVM_ENTRY(jstring, JVM_GetExtendedNPEMessage(JNIEnv *env, jthrowable throwable))
JVM_ENTRY(jstring, JVM_GetExtendedNPEMessage(JNIEnv *env, jthrowable throwable, jint stack_offset, jint search_slot))
if (!ShowCodeDetailsInExceptionMessages) return nullptr;

oop exc = JNIHandles::resolve_non_null(throwable);
// If we are performing an explicit search instructed by alternative internal NPE constructor
BytecodeUtils::NullSlot slot = search_slot >= 0 ? static_cast<BytecodeUtils::NullSlot>(search_slot)
: BytecodeUtils::NullSlot::SEARCH;

Method* method;
int bci;
if (!java_lang_Throwable::get_top_method_and_bci(exc, &method, &bci)) {
int depth = slot != BytecodeUtils::NullSlot::SEARCH ? stack_offset : 0; // 1-based depth
// The explicit search alternative internal NPE constructor is called from a @Hidden method, allow them
if (!java_lang_Throwable::get_method_and_bci(exc, &method, &bci, depth + 1, slot != BytecodeUtils::NullSlot::SEARCH)) {
return nullptr;
}
if (method->is_native()) {
return nullptr;
}

stringStream ss;
bool ok = BytecodeUtils::get_NPE_message_at(&ss, method, bci);
bool ok = BytecodeUtils::get_NPE_message_at(&ss, method, bci, slot);
if (ok) {
oop result = java_lang_String::create_oop_from_str(ss.base(), CHECK_NULL);
return (jstring) JNIHandles::make_local(THREAD, result);
Expand Down
Loading