8302189: Mark assertion failures noreturn

8302799: Refactor Debugging variable usage for noreturn crash reporting

Reviewed-by: dholmes, coleenp
This commit is contained in:
Kim Barrett 2023-03-08 02:37:06 +00:00
parent 9b10c69475
commit 5fa9bd4582
8 changed files with 146 additions and 40 deletions

View File

@ -100,7 +100,7 @@ endif
DISABLED_WARNINGS_xlc := tautological-compare shift-negative-value DISABLED_WARNINGS_xlc := tautological-compare shift-negative-value
DISABLED_WARNINGS_microsoft := 4624 4244 4291 4146 4127 DISABLED_WARNINGS_microsoft := 4624 4244 4291 4146 4127 4722
################################################################################ ################################################################################
# Platform specific setup # Platform specific setup

View File

@ -42,7 +42,6 @@
#include "oops/klass.inline.hpp" #include "oops/klass.inline.hpp"
#include "prims/methodHandles.hpp" #include "prims/methodHandles.hpp"
#include "runtime/continuation.hpp" #include "runtime/continuation.hpp"
#include "runtime/flags/flagSetting.hpp"
#include "runtime/interfaceSupport.inline.hpp" #include "runtime/interfaceSupport.inline.hpp"
#include "runtime/javaThread.hpp" #include "runtime/javaThread.hpp"
#include "runtime/jniHandles.hpp" #include "runtime/jniHandles.hpp"
@ -403,7 +402,7 @@ void MacroAssembler::debug32(int rdi, int rsi, int rbp, int rsp, int rbx, int rd
void MacroAssembler::print_state32(int rdi, int rsi, int rbp, int rsp, int rbx, int rdx, int rcx, int rax, int eip) { void MacroAssembler::print_state32(int rdi, int rsi, int rbp, int rsp, int rbx, int rdx, int rcx, int rax, int eip) {
ttyLocker ttyl; ttyLocker ttyl;
FlagSetting fs(Debugging, true); DebuggingContext debugging{};
tty->print_cr("eip = 0x%08x", eip); tty->print_cr("eip = 0x%08x", eip);
#ifndef PRODUCT #ifndef PRODUCT
if ((WizardMode || Verbose) && PrintMiscellaneous) { if ((WizardMode || Verbose) && PrintMiscellaneous) {
@ -832,7 +831,7 @@ void MacroAssembler::debug64(char* msg, int64_t pc, int64_t regs[]) {
void MacroAssembler::print_state64(int64_t pc, int64_t regs[]) { void MacroAssembler::print_state64(int64_t pc, int64_t regs[]) {
ttyLocker ttyl; ttyLocker ttyl;
FlagSetting fs(Debugging, true); DebuggingContext debugging{};
tty->print_cr("rip = 0x%016lx", (intptr_t)pc); tty->print_cr("rip = 0x%016lx", (intptr_t)pc);
#ifndef PRODUCT #ifndef PRODUCT
tty->cr(); tty->cr();

View File

@ -628,7 +628,9 @@ HeapWord* ParallelScavengeHeap::block_start(const void* addr) const {
assert(young_gen()->is_in(addr), assert(young_gen()->is_in(addr),
"addr should be in allocated part of young gen"); "addr should be in allocated part of young gen");
// called from os::print_location by find or VMError // called from os::print_location by find or VMError
if (Debugging || VMError::is_error_reported()) return nullptr; if (DebuggingContext::is_enabled() || VMError::is_error_reported()) {
return nullptr;
}
Unimplemented(); Unimplemented();
} else if (old_gen()->is_in_reserved(addr)) { } else if (old_gen()->is_in_reserved(addr)) {
assert(old_gen()->is_in(addr), assert(old_gen()->is_in(addr),

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2017, 2022, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2017, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -209,7 +209,7 @@ namespace AccessInternal {
#ifdef ASSERT #ifdef ASSERT
void check_access_thread_state() { void check_access_thread_state() {
if (VMError::is_error_reported() || Debugging) { if (VMError::is_error_reported() || DebuggingContext::is_enabled()) {
return; return;
} }

View File

@ -376,6 +376,9 @@ void JavaThread::check_possible_safepoint() {
} }
void JavaThread::check_for_valid_safepoint_state() { void JavaThread::check_for_valid_safepoint_state() {
// Don't complain if running a debugging command.
if (DebuggingContext::is_enabled()) return;
// Check NoSafepointVerifier, which is implied by locks taken that can be // Check NoSafepointVerifier, which is implied by locks taken that can be
// shared with the VM thread. This makes sure that no locks with allow_vm_block // shared with the VM thread. This makes sure that no locks with allow_vm_block
// are held. // are held.

View File

@ -171,6 +171,7 @@ static int _num_mutex;
#ifdef ASSERT #ifdef ASSERT
void assert_locked_or_safepoint(const Mutex* lock) { void assert_locked_or_safepoint(const Mutex* lock) {
if (DebuggingContext::is_enabled()) return;
// check if this thread owns the lock (common case) // check if this thread owns the lock (common case)
assert(lock != nullptr, "Need non-null lock"); assert(lock != nullptr, "Need non-null lock");
if (lock->owned_by_self()) return; if (lock->owned_by_self()) return;
@ -181,6 +182,7 @@ void assert_locked_or_safepoint(const Mutex* lock) {
// a weaker assertion than the above // a weaker assertion than the above
void assert_locked_or_safepoint_weak(const Mutex* lock) { void assert_locked_or_safepoint_weak(const Mutex* lock) {
if (DebuggingContext::is_enabled()) return;
assert(lock != nullptr, "Need non-null lock"); assert(lock != nullptr, "Need non-null lock");
if (lock->is_locked()) return; if (lock->is_locked()) return;
if (SafepointSynchronize::is_at_safepoint()) return; if (SafepointSynchronize::is_at_safepoint()) return;
@ -190,6 +192,7 @@ void assert_locked_or_safepoint_weak(const Mutex* lock) {
// a stronger assertion than the above // a stronger assertion than the above
void assert_lock_strong(const Mutex* lock) { void assert_lock_strong(const Mutex* lock) {
if (DebuggingContext::is_enabled()) return;
assert(lock != nullptr, "Need non-null lock"); assert(lock != nullptr, "Need non-null lock");
if (lock->owned_by_self()) return; if (lock->owned_by_self()) return;
fatal("must own lock %s", lock->name()); fatal("must own lock %s", lock->name());

View File

@ -76,8 +76,16 @@ static intx g_asserting_thread = 0;
static void* g_assertion_context = nullptr; static void* g_assertion_context = nullptr;
#endif // CAN_SHOW_REGISTERS_ON_ASSERT #endif // CAN_SHOW_REGISTERS_ON_ASSERT
// Set to suppress secondary error reporting. int DebuggingContext::_enabled = 0; // Initially disabled.
bool Debugging = false;
DebuggingContext::DebuggingContext() {
_enabled += 1; // Increase nesting count.
}
DebuggingContext::~DebuggingContext() {
assert(is_enabled(), "Debugging nesting confusion");
_enabled -= 1; // Decrease nesting count.
}
#ifndef ASSERT #ifndef ASSERT
# ifdef _DEBUG # ifdef _DEBUG
@ -166,7 +174,6 @@ static void print_error_for_unit_test(const char* message, const char* detail_fm
void report_vm_error(const char* file, int line, const char* error_msg, const char* detail_fmt, ...) void report_vm_error(const char* file, int line, const char* error_msg, const char* detail_fmt, ...)
{ {
if (Debugging) return;
va_list detail_args; va_list detail_args;
va_start(detail_args, detail_fmt); va_start(detail_args, detail_fmt);
void* context = nullptr; void* context = nullptr;
@ -188,7 +195,6 @@ void report_vm_status_error(const char* file, int line, const char* error_msg,
} }
void report_fatal(VMErrorType error_type, const char* file, int line, const char* detail_fmt, ...) { void report_fatal(VMErrorType error_type, const char* file, int line, const char* detail_fmt, ...) {
if (Debugging) return;
va_list detail_args; va_list detail_args;
va_start(detail_args, detail_fmt); va_start(detail_args, detail_fmt);
void* context = nullptr; void* context = nullptr;
@ -208,7 +214,6 @@ void report_fatal(VMErrorType error_type, const char* file, int line, const char
void report_vm_out_of_memory(const char* file, int line, size_t size, void report_vm_out_of_memory(const char* file, int line, size_t size,
VMErrorType vm_err_type, const char* detail_fmt, ...) { VMErrorType vm_err_type, const char* detail_fmt, ...) {
if (Debugging) return;
va_list detail_args; va_list detail_args;
va_start(detail_args, detail_fmt); va_start(detail_args, detail_fmt);
@ -278,13 +283,11 @@ void report_java_out_of_memory(const char* message) {
class Command : public StackObj { class Command : public StackObj {
private: private:
ResourceMark rm; ResourceMark _rm;
bool debug_save; DebuggingContext _debugging;
public: public:
static int level; static int level;
Command(const char* str) { Command(const char* str) {
debug_save = Debugging;
Debugging = true;
if (level++ > 0) return; if (level++ > 0) return;
tty->cr(); tty->cr();
tty->print_cr("\"Executing %s\"", str); tty->print_cr("\"Executing %s\"", str);
@ -292,7 +295,6 @@ class Command : public StackObj {
~Command() { ~Command() {
tty->flush(); tty->flush();
Debugging = debug_save;
level--; level--;
} }
}; };

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 1997, 2022, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 1997, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -46,6 +46,98 @@ bool handle_assert_poison_fault(const void* ucVoid, const void* faulting_address
#define TOUCH_ASSERT_POISON #define TOUCH_ASSERT_POISON
#endif // CAN_SHOW_REGISTERS_ON_ASSERT #endif // CAN_SHOW_REGISTERS_ON_ASSERT
// The DebuggingContext class provides a mechanism for temporarily disabling
// asserts and various consistency checks. Ordinarily that would be a really
// bad idea, but it's essential for some of the debugging commands provided by
// HotSpot. (See the Command class in debug.cpp.) These commands are intended
// to be invoked from the debugger while the program is otherwise stopped.
// The commands may invoke operations while the program is in a state where
// those operations are not normally permitted, with the state checked by an
// assert. We want the debugging commands to bypass those checks.
class DebuggingContext {
static int _enabled; // Nesting counter.
public:
DebuggingContext();
~DebuggingContext();
// Asserts and other code use this to determine whether to bypass checks
// that would otherwise lead to program termination.
static bool is_enabled() { return _enabled > 0; }
};
// VMASSERT_CHECK_PASSED(P) provides the mechanism by which DebuggingContext
// disables asserts. It returns true if P is true or DebuggingContext is
// enabled. Assertion failure is reported if it returns false, terminating
// the program.
//
// The DebuggingContext check being enabled isn't placed inside the report
// function, as that would prevent the report function from being noreturn.
// The report function should be noreturn so there isn't a control path to the
// assertion's continuation that has P being false. Otherwise, the compiler
// might logically split the continuation to include that path explicitly,
// possibly leading to discovering (and warning about) invalid code. For
// example, if P := x != nullptr, and the continuation contains a dereference
// of x, the compiler might warn because there is a control path (!P -> report
// -> continuation) where that dereference is known to be invalid. (Of
// course, if execution actually took that path things would go wrong, but
// that's the risk the DebuggingContext mechanism takes.)
//
// Similarly, the check for enabled DebuggingContext shouldn't follow P.
// Having this macro expand to `P || DebuggingContext::is_enabled()` has the
// same problem of a control path through !P to the assertion's continuation.
//
// But it can't be just `DebuggingContext::is_enabled() || P` either. That
// prevents the compiler from inferring based on P that it is true in the
// continuation. But it also prevents the use of assertions in constexpr
// contexts, since that expression is not constexpr.
//
// We could accomodate constexpr usage with std::is_constant_evaluated() (from
// C++20). Unfortunately, we don't currently support C++20. However, most
// supported compilers have implemented it, and that implementation uses a
// compiler intrinsic that we can use directly without otherwise using C++20.
//
// Note that if we could use std::is_constant_evaluated() then we could just
// use this definition for DebuggingContext::is_enabled:
// static constexpr bool is_enabled() {
// return !std::is_constant_evaluated() && _enabled;
// }
// The idea being that we are definitely not executing for debugging if doing
// constant evaluation in the compiler. We don't do something like that now,
// because we need a fallback when we don't have any mechanism for detecting
// constant evaluation.
#if defined(TARGET_COMPILER_gcc) || defined(TARGET_COMPILER_xlc)
// gcc10 added both __has_builtin and __builtin_is_constant_evaluated.
// clang has had __has_builtin for a long time, so likely also in xlclang++.
// Similarly, clang has had __builtin_is_constant_evaluated for a long time.
#ifdef __has_builtin
#if __has_builtin(__builtin_is_constant_evaluated)
#define VMASSERT_CHECK_PASSED(p) \
((! __builtin_is_constant_evaluated() && DebuggingContext::is_enabled()) || (p))
#endif
#endif
#elif defined(TARGET_COMPILER_visCPP)
// std::is_constant_evaluated() and it's associated intrinsic are available in
// VS 2019 16.5. The minimum supported version of VS 2019 is already past
// that, so we can rely on the intrinsic being available.
#define VMASSERT_CHECK_PASSED(p) \
((! __builtin_is_constant_evaluated() && DebuggingContext::is_enabled()) || (p))
#endif // End dispatch on TARGET_COMPILER_xxx
// If we don't have a way to detect constant evaluation, then fall back to the
// less than ideal form of the check, and hope it works. This succeeds at
// least for gcc. The support needed to use the above definition was added in
// gcc10. The problems arising from analyzing the failed P case don't seem to
// appear until gcc12. An alternative is to not provide DebuggingContext
// support for such a configuration.
#ifndef VMASSERT_CHECK_PASSED
#define VMASSERT_CHECK_PASSED(p) ((p) || DebuggingContext::is_enabled())
#endif
// assertions // assertions
#ifndef ASSERT #ifndef ASSERT
#define vmassert(p, ...) #define vmassert(p, ...)
@ -56,10 +148,9 @@ bool handle_assert_poison_fault(const void* ucVoid, const void* faulting_address
// compiler can't handle an empty ellipsis in a macro without a warning. // compiler can't handle an empty ellipsis in a macro without a warning.
#define vmassert(p, ...) \ #define vmassert(p, ...) \
do { \ do { \
if (!(p)) { \ if (! VMASSERT_CHECK_PASSED(p)) { \
TOUCH_ASSERT_POISON; \ TOUCH_ASSERT_POISON; \
report_vm_error(__FILE__, __LINE__, "assert(" #p ") failed", __VA_ARGS__); \ report_vm_error(__FILE__, __LINE__, "assert(" #p ") failed", __VA_ARGS__); \
BREAKPOINT; \
} \ } \
} while (0) } while (0)
#endif #endif
@ -82,14 +173,13 @@ do { \
// like "Invalid argument", "out of memory" etc // like "Invalid argument", "out of memory" etc
#define vmassert_status(p, status, msg) \ #define vmassert_status(p, status, msg) \
do { \ do { \
if (!(p)) { \ if (! VMASSERT_CHECK_PASSED(p)) { \
TOUCH_ASSERT_POISON; \ TOUCH_ASSERT_POISON; \
report_vm_status_error(__FILE__, __LINE__, "assert(" #p ") failed", \ report_vm_status_error(__FILE__, __LINE__, "assert(" #p ") failed", \
status, msg); \ status, msg); \
BREAKPOINT; \
} \ } \
} while (0) } while (0)
#endif #endif // ASSERT
// For backward compatibility. // For backward compatibility.
#define assert_status(p, status, msg) vmassert_status(p, status, msg) #define assert_status(p, status, msg) vmassert_status(p, status, msg)
@ -97,12 +187,12 @@ do { \
// guarantee is like vmassert except it's always executed -- use it for // guarantee is like vmassert except it's always executed -- use it for
// cheap tests that catch errors that would otherwise be hard to find. // cheap tests that catch errors that would otherwise be hard to find.
// guarantee is also used for Verify options. // guarantee is also used for Verify options.
// guarantee is not subject to DebuggingContext bypass.
#define guarantee(p, ...) \ #define guarantee(p, ...) \
do { \ do { \
if (!(p)) { \ if (!(p)) { \
TOUCH_ASSERT_POISON; \ TOUCH_ASSERT_POISON; \
report_vm_error(__FILE__, __LINE__, "guarantee(" #p ") failed", __VA_ARGS__); \ report_vm_error(__FILE__, __LINE__, "guarantee(" #p ") failed", __VA_ARGS__); \
BREAKPOINT; \
} \ } \
} while (0) } while (0)
@ -110,35 +200,30 @@ do {
do { \ do { \
TOUCH_ASSERT_POISON; \ TOUCH_ASSERT_POISON; \
report_fatal(INTERNAL_ERROR, __FILE__, __LINE__, __VA_ARGS__); \ report_fatal(INTERNAL_ERROR, __FILE__, __LINE__, __VA_ARGS__); \
BREAKPOINT; \
} while (0) } while (0)
// out of memory // out of memory
#define vm_exit_out_of_memory(size, vm_err_type, ...) \ #define vm_exit_out_of_memory(size, vm_err_type, ...) \
do { \ do { \
report_vm_out_of_memory(__FILE__, __LINE__, size, vm_err_type, __VA_ARGS__); \ report_vm_out_of_memory(__FILE__, __LINE__, size, vm_err_type, __VA_ARGS__); \
BREAKPOINT; \
} while (0) } while (0)
#define ShouldNotCallThis() \ #define ShouldNotCallThis() \
do { \ do { \
TOUCH_ASSERT_POISON; \ TOUCH_ASSERT_POISON; \
report_should_not_call(__FILE__, __LINE__); \ report_should_not_call(__FILE__, __LINE__); \
BREAKPOINT; \
} while (0) } while (0)
#define ShouldNotReachHere() \ #define ShouldNotReachHere() \
do { \ do { \
TOUCH_ASSERT_POISON; \ TOUCH_ASSERT_POISON; \
report_should_not_reach_here(__FILE__, __LINE__); \ report_should_not_reach_here(__FILE__, __LINE__); \
BREAKPOINT; \
} while (0) } while (0)
#define Unimplemented() \ #define Unimplemented() \
do { \ do { \
TOUCH_ASSERT_POISON; \ TOUCH_ASSERT_POISON; \
report_unimplemented(__FILE__, __LINE__); \ report_unimplemented(__FILE__, __LINE__); \
BREAKPOINT; \
} while (0) } while (0)
#define Untested(msg) \ #define Untested(msg) \
@ -157,25 +242,37 @@ enum VMErrorType {
OOM_JAVA_HEAP_FATAL = 0xe0000004 OOM_JAVA_HEAP_FATAL = 0xe0000004
}; };
// Set to suppress secondary error reporting.
// Really should have a qualified name or something.
extern bool Debugging;
// error reporting helper functions // error reporting helper functions
[[noreturn]]
void report_vm_error(const char* file, int line, const char* error_msg); void report_vm_error(const char* file, int line, const char* error_msg);
[[noreturn]]
ATTRIBUTE_PRINTF(4, 5)
void report_vm_error(const char* file, int line, const char* error_msg, void report_vm_error(const char* file, int line, const char* error_msg,
const char* detail_fmt, ...) ATTRIBUTE_PRINTF(4, 5); const char* detail_fmt, ...);
[[noreturn]]
void report_vm_status_error(const char* file, int line, const char* error_msg, void report_vm_status_error(const char* file, int line, const char* error_msg,
int status, const char* detail); int status, const char* detail);
void report_fatal(VMErrorType error_type, const char* file, int line, const char* detail_fmt, ...) ATTRIBUTE_PRINTF(4, 5);
[[noreturn]]
ATTRIBUTE_PRINTF(4, 5)
void report_fatal(VMErrorType error_type, const char* file, int line, const char* detail_fmt, ...);
[[noreturn]]
ATTRIBUTE_PRINTF(5, 6)
void report_vm_out_of_memory(const char* file, int line, size_t size, VMErrorType vm_err_type, void report_vm_out_of_memory(const char* file, int line, size_t size, VMErrorType vm_err_type,
const char* detail_fmt, ...) ATTRIBUTE_PRINTF(5, 6); const char* detail_fmt, ...);
void report_should_not_call(const char* file, int line);
void report_should_not_reach_here(const char* file, int line); [[noreturn]] void report_should_not_call(const char* file, int line);
void report_unimplemented(const char* file, int line); [[noreturn]] void report_should_not_reach_here(const char* file, int line);
[[noreturn]] void report_unimplemented(const char* file, int line);
// NOT [[noreturn]]
void report_untested(const char* file, int line, const char* message); void report_untested(const char* file, int line, const char* message);
void warning(const char* format, ...) ATTRIBUTE_PRINTF(1, 2); ATTRIBUTE_PRINTF(1, 2)
void warning(const char* format, ...);
#define STATIC_ASSERT(Cond) static_assert((Cond), #Cond) #define STATIC_ASSERT(Cond) static_assert((Cond), #Cond)