Why did Windows gtest catch a std::exception-derived exception
Last month, I investigated OpenJDK gtest failures on Windows. The error message was that the gtests Caught std::exception-derived exception escaping the death test statement. I tracked the commit responsible for the failures to 8343756: CAN_SHOW_REGISTERS_ON_ASSERT for Windows · openjdk/jdk@0054bbe.
gtest Death Test Structure
One of the failing tests is jdk/test/hotspot/gtest/utilities/test_vmerror.cpp.
TEST_VM_ASSERT_MSG(vmErrorTest, assert1, "assert.str == nullptr. failed: expected null") {
vmassert(str == nullptr, "expected null");
}
The TEST_VM_ASSERT_MSG macro is defined as follows
#define TEST_VM_ASSERT_MSG(category, name, msg) \
static void test_ ## category ## _ ## name ## _(); \
\
static void child_ ## category ## _ ## name ## _() { \
::testing::GTEST_FLAG(throw_on_failure) = true; \
test_ ## category ## _ ## name ## _(); \
gtest_exit_from_child_vm(0); \
} \
\
TEST(category, CONCAT(name, _vm_assert)) { \
ASSERT_EXIT(child_ ## category ## _ ## name ## _(), \
::testing::ExitedWithCode(1), \
"assert failed: " msg); \
} \
\
void test_ ## category ## _ ## name ## _()
gtest_exit_from_child_vm(0) cleanly exits the JVM after calling test_vmErrorTest_assert1(). The ASSERT_EXIT macro expects the JVM to crash. The overall design of the death tests is documented in googletest/googletest/include/gtest/gtest-death-test.h at v1.14.0 · google/googletest. The key takeaway is that a child process is started, it executes the death test, and its exit code and stderr are compared with the expected code and message (the latter via regex matching).
// Asserts that a given `statement` causes the program to exit, with an // integer exit status that satisfies `predicate`, and emitting error output // that matches `matcher`. #define ASSERT_EXIT(statement, predicate, matcher) \ GTEST_DEATH_TEST_(statement, predicate, matcher, GTEST_FATAL_FAILURE_)
I was trying to ensure my understanding of the exit code being an exact match is correct. The line EXPECT_EXIT(_exit(1), testing::ExitedWithCode(1), ""); from googletest/googletest/test/googletest-death-test-test.cc at v1.14.0 · google/googletest supports this hypothesis. The EXPECT_EXIT macro comment (below) left me wondering how ASSERT_EXIT does not continue on to successive tests. The difference between these two macros is in the final parameter, which is GTEST_NONFATAL_FAILURE_ for the EXPECT_EXIT macro.
// Like `ASSERT_EXIT`, but continues on to successive tests in the // test suite, if any: #define EXPECT_EXIT(statement, predicate, matcher) \ GTEST_DEATH_TEST_(statement, predicate, matcher, GTEST_NONFATAL_FAILURE_)
The GTEST_DEATH_TEST_ macro creates a DeathTest instance and executes the death test statement. The WindowsDeathTest::AssumeRole() method, which is key in this, is described as follows: it
creates a child process with the same executable as the current process to run the death test. The child process is given the –gtest_filter and –gtest_internal_run_death_test flags such that it knows to run the current death test only.
The GTEST_EXECUTE_DEATH_TEST_STATEMENT_ macro was the source of the error message!
#define GTEST_EXECUTE_DEATH_TEST_STATEMENT_(statement, death_test) \
try { \
GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \
} catch (const ::std::exception& gtest_exception) { \
fprintf( \
stderr, \
"\n%s: Caught std::exception-derived exception escaping the " \
"death test statement. Exception message: %s\n", \
::testing::internal::FormatFileLocation(__FILE__, __LINE__).c_str(), \
gtest_exception.what()); \
fflush(stderr); \
death_test->Abort(::testing::internal::DeathTest::TEST_THREW_EXCEPTION); \
} catch (...) { \
death_test->Abort(::testing::internal::DeathTest::TEST_THREW_EXCEPTION); \
}
Root Causing the std::exception
The question now became, why were we catching this std::exception? I asked copilot: how does a windows access violation turn into a std::exception? Part of its answer mentioned the /EHsc compiler flag so I decided to examine the flags used to compile the JVM binaries. I searched for the regex out:[^\s]+jvm.dll in the build logs and found this jvm.dll linker command. Note that 2 separate jvm.dll files get built, one for the product and another for the gtests. The /IMPLIB (Name Import Library) | Microsoft Learn flag was present, but didn’t look relevant.
I then searched for cl.exe .+jvm.lib to get the compiler command line but this gave the compiler commands for gtest-all.cc and gmock-all.cc. The -EHsc flag (/EH (Exception handling model) | Microsoft Learn) was present for these 2 files though! Next, I searched for “gtestLauncher” and found the compiler command generating gtestLauncher.obj. Notice it didn’t have -EHsc!
I also realized that I should have searched for jvm.obj! Here is the single occurence of the jvm\.obj regex. It doesn’t have -EHsc either! Hmm, strange: jdk/make/hotspot/lib/CompileGtest.gmk says gtests should have it! I then searched the make folder for the regex CFLAGS_[^\w] and the primary suspect (of the 3 results) is the SetupCompilerFlags target in jdk/make/common/native/Flags.gmk. That target was last modified in 8325877: Split up NativeCompilation.gmk · openjdk/jdk@0d51b76.
Next, I examined build\windows-x86_64-server-slowdebug\configure-support\config.log and found these lines:
OPENJDK_TARGET_OS='windows'
...
OPENJDK_TARGET_OS_TYPE='windows'
...
TOOLCHAIN_TYPE='microsoft'
I thought that this snippet from jdk/make/common/native/Flags.gmk should have picked them up!
define SetupCompilerFlags
# Pickup extra OPENJDK_TARGET_OS_TYPE, OPENJDK_TARGET_OS, TOOLCHAIN_TYPE and
# OPENJDK_TARGET_OS plus OPENJDK_TARGET_CPU pair dependent variables for CFLAGS.
$1_EXTRA_CFLAGS := $$($1_CFLAGS_$(OPENJDK_TARGET_OS_TYPE)) $$($1_CFLAGS_$(OPENJDK_TARGET_OS)) \
$$($1_CFLAGS_$(TOOLCHAIN_TYPE)) \
$$($1_CFLAGS_$(OPENJDK_TARGET_OS)_$(OPENJDK_TARGET_CPU))
What was the leading $1_ prefix though? I wasn’t sure but I tried this change next:
diff --git a/make/hotspot/lib/CompileGtest.gmk b/make/hotspot/lib/CompileGtest.gmk
index d2cdc7685c9..3908e94b624 100644
--- a/make/hotspot/lib/CompileGtest.gmk
+++ b/make/hotspot/lib/CompileGtest.gmk
@@ -63,7 +63,7 @@ $(eval $(call SetupJdkLibrary, BUILD_GTEST_LIBGTEST, \
unused-result zero-as-null-pointer-constant, \
DISABLED_WARNINGS_clang := format-nonliteral undef unused-result, \
DEFAULT_CFLAGS := false, \
- CFLAGS := $(JVM_CFLAGS) \
+ CFLAGS := $(JVM_CFLAGS) -EHsc \
-I$(GTEST_FRAMEWORK_SRC)/googletest \
-I$(GTEST_FRAMEWORK_SRC)/googletest/include \
-I$(GTEST_FRAMEWORK_SRC)/googlemock \
@@ -94,7 +94,7 @@ $(eval $(call SetupJdkLibrary, BUILD_GTEST_LIBJVM, \
EXCLUDE_PATTERNS := $(JVM_EXCLUDE_PATTERNS), \
EXTRA_OBJECT_FILES := $(BUILD_LIBJVM_ALL_OBJS), \
DEFAULT_CFLAGS := false, \
- CFLAGS := $(JVM_CFLAGS) \
+ CFLAGS := $(JVM_CFLAGS) -EHsc \
-I$(GTEST_FRAMEWORK_SRC)/googletest/include \
-I$(GTEST_FRAMEWORK_SRC)/googlemock/include \
$(addprefix -I, $(GTEST_TEST_SRC)), \
The build failed with this error:
checking for apk... [not found]
checking for pandoc... [not found]
/cygdrive/d/java/forks/openjdk/jdk/build/.configure-support/generated-configure.sh: line 64028: syntax error: unexpected end of file
configure exiting with result code 2
That file appeared to be truncated??? VSCode was doing something related to building the Java projects in the repo. It is possible that something in VSCode could have interrupted this but I just removed the build folder then reexamined the change.
I decided to find out who uses SetupCompilerFlags and found jdk/make/common/NativeCompilation.gmk. It is in turn called by SetupJdkNativeCompilation (which I had been trying to change)! The actual compilation is kicked off by jdk/make/common/NativeCompilation.gmk and done by jdk/make/common/native/CompileFile.gmk. The latter calls SetupCompileFileFlags, which is defined in jdk/make/common/native/Flags.gmk. I noticed that it includes the extra CFLAGS and CXXFLAGS in jdk/make/common/native/Flags.gmk. The most important observation though was that jdk/make/common/native/CompileFile.gmk uses the CXXFLAGS for .cpp files! I tried this change but it didn’t pass the flags to the compiler either.
diff --git a/make/hotspot/lib/CompileGtest.gmk b/make/hotspot/lib/CompileGtest.gmk
index d2cdc7685c9..8241ad04cb9 100644
--- a/make/hotspot/lib/CompileGtest.gmk
+++ b/make/hotspot/lib/CompileGtest.gmk
@@ -69,6 +69,7 @@ $(eval $(call SetupJdkLibrary, BUILD_GTEST_LIBGTEST, \
-I$(GTEST_FRAMEWORK_SRC)/googlemock \
-I$(GTEST_FRAMEWORK_SRC)/googlemock/include, \
CFLAGS_windows := -EHsc, \
+ CXXFLAGS_windows := -EHsc, \
CFLAGS_macosx := -DGTEST_OS_MAC=1, \
OPTIMIZATION := $(JVM_OPTIMIZATION), \
COPY_DEBUG_SYMBOLS := $(GTEST_COPY_DEBUG_SYMBOLS), \
@@ -99,6 +100,7 @@ $(eval $(call SetupJdkLibrary, BUILD_GTEST_LIBJVM, \
-I$(GTEST_FRAMEWORK_SRC)/googlemock/include \
$(addprefix -I, $(GTEST_TEST_SRC)), \
CFLAGS_windows := -EHsc, \
+ CXXFLAGS_windows := -EHsc, \
CFLAGS_macosx := -DGTEST_OS_MAC=1, \
DISABLED_WARNINGS_gcc := $(DISABLED_WARNINGS_gcc) \
undef stringop-overflow, \
Was the code I was changing even used? I tried this change to answer that:
diff --git a/make/hotspot/lib/CompileGtest.gmk b/make/hotspot/lib/CompileGtest.gmk
index d2cdc7685c9..4554b3c89f5 100644
--- a/make/hotspot/lib/CompileGtest.gmk
+++ b/make/hotspot/lib/CompileGtest.gmk
@@ -66,9 +66,11 @@ $(eval $(call SetupJdkLibrary, BUILD_GTEST_LIBGTEST, \
CFLAGS := $(JVM_CFLAGS) \
-I$(GTEST_FRAMEWORK_SRC)/googletest \
-I$(GTEST_FRAMEWORK_SRC)/googletest/include \
+ -I$(GTEST_FRAMEWORK_SRC)/googletest/include/saintstest \
-I$(GTEST_FRAMEWORK_SRC)/googlemock \
-I$(GTEST_FRAMEWORK_SRC)/googlemock/include, \
CFLAGS_windows := -EHsc, \
+ CXXFLAGS_windows := -EHsc, \
CFLAGS_macosx := -DGTEST_OS_MAC=1, \
OPTIMIZATION := $(JVM_OPTIMIZATION), \
COPY_DEBUG_SYMBOLS := $(GTEST_COPY_DEBUG_SYMBOLS), \
@@ -96,9 +98,11 @@ $(eval $(call SetupJdkLibrary, BUILD_GTEST_LIBJVM, \
DEFAULT_CFLAGS := false, \
CFLAGS := $(JVM_CFLAGS) \
-I$(GTEST_FRAMEWORK_SRC)/googletest/include \
+ -I$(GTEST_FRAMEWORK_SRC)/googletest/include/saintstest \
-I$(GTEST_FRAMEWORK_SRC)/googlemock/include \
$(addprefix -I, $(GTEST_TEST_SRC)), \
CFLAGS_windows := -EHsc, \
+ CXXFLAGS_windows := -EHsc, \
CFLAGS_macosx := -DGTEST_OS_MAC=1, \
DISABLED_WARNINGS_gcc := $(DISABLED_WARNINGS_gcc) \
undef stringop-overflow, \
My include path didn’t appear in the include paths for gtestLauncher.obj! I searched the repo for googlemock and the only place that path could be coming from was CompileGtest.gmk. However, I then noticed that the gtest launcher has its own configuration section. Sheesh. Here is the diff that I used to definitively see how these includes work:
diff --git a/make/hotspot/lib/CompileGtest.gmk b/make/hotspot/lib/CompileGtest.gmk
index d2cdc7685c9..72161d6d5d2 100644
--- a/make/hotspot/lib/CompileGtest.gmk
+++ b/make/hotspot/lib/CompileGtest.gmk
@@ -66,9 +66,11 @@ $(eval $(call SetupJdkLibrary, BUILD_GTEST_LIBGTEST, \
CFLAGS := $(JVM_CFLAGS) \
-I$(GTEST_FRAMEWORK_SRC)/googletest \
-I$(GTEST_FRAMEWORK_SRC)/googletest/include \
+ -I$(GTEST_FRAMEWORK_SRC)/googletest/include/saintstest1 \
-I$(GTEST_FRAMEWORK_SRC)/googlemock \
-I$(GTEST_FRAMEWORK_SRC)/googlemock/include, \
CFLAGS_windows := -EHsc, \
+ CXXFLAGS_windows := -EHsc, \
CFLAGS_macosx := -DGTEST_OS_MAC=1, \
OPTIMIZATION := $(JVM_OPTIMIZATION), \
COPY_DEBUG_SYMBOLS := $(GTEST_COPY_DEBUG_SYMBOLS), \
@@ -96,9 +98,11 @@ $(eval $(call SetupJdkLibrary, BUILD_GTEST_LIBJVM, \
DEFAULT_CFLAGS := false, \
CFLAGS := $(JVM_CFLAGS) \
-I$(GTEST_FRAMEWORK_SRC)/googletest/include \
+ -I$(GTEST_FRAMEWORK_SRC)/googletest/include/saintstest2 \
-I$(GTEST_FRAMEWORK_SRC)/googlemock/include \
$(addprefix -I, $(GTEST_TEST_SRC)), \
CFLAGS_windows := -EHsc, \
+ CXXFLAGS_windows := -EHsc, \
CFLAGS_macosx := -DGTEST_OS_MAC=1, \
DISABLED_WARNINGS_gcc := $(DISABLED_WARNINGS_gcc) \
undef stringop-overflow, \
@@ -150,8 +154,10 @@ $(eval $(call SetupJdkExecutable, BUILD_GTEST_LAUNCHER, \
CFLAGS := $(JVM_CFLAGS) \
-I$(GTEST_FRAMEWORK_SRC)/googletest \
-I$(GTEST_FRAMEWORK_SRC)/googletest/include \
+ -I$(GTEST_FRAMEWORK_SRC)/googletest/include/saintstest3 \
-I$(GTEST_FRAMEWORK_SRC)/googlemock \
-I$(GTEST_FRAMEWORK_SRC)/googlemock/include, \
+ CXXFLAGS_windows := -EHsc, \
LD_SET_ORIGIN := false, \
LDFLAGS_unix := $(call SET_SHARED_LIBRARY_ORIGIN), \
JDK_LIBS := gtest:libjvm, \
gtestLauncher.exe was now being compiled with -EHsc but the gtests still failed. Since jvm.dll is compiled without -EHsc, I added it to see if the test behavior would change. I started by searching for libjvm in the codebase. This is the additional change I made:
diff --git a/make/hotspot/lib/CompileJvm.gmk b/make/hotspot/lib/CompileJvm.gmk
index 6b5edc85b23..f7ae373ff17 100644
--- a/make/hotspot/lib/CompileJvm.gmk
+++ b/make/hotspot/lib/CompileJvm.gmk
@@ -179,6 +179,7 @@ $(eval $(call SetupJdkLibrary, BUILD_LIBJVM, \
EXCLUDE_PATTERNS := $(JVM_EXCLUDE_PATTERNS), \
DEFAULT_CFLAGS := false, \
CFLAGS := $(JVM_CFLAGS), \
+ CXXFLAGS_windows := -EHsc, \
abstract_vm_version.cpp_CXXFLAGS := $(CFLAGS_VM_VERSION), \
arguments.cpp_CXXFLAGS := $(CFLAGS_VM_VERSION), \
DISABLED_WARNINGS_gcc := $(DISABLED_WARNINGS_gcc), \
At this point, I looked at the exception handler and it looked like what was happening was that returning EXCEPTION_CONTINUE_EXECUTION let the SEH handler in the gtests continue instead of the code down the report_vm_error path! I decided to create my own handler but needed to look up the syntax. I used Structured Exception Handling (C/C++) | Microsoft Learn.
diff --git a/src/hotspot/share/utilities/debug.hpp b/src/hotspot/share/utilities/debug.hpp
index 12724153659..e40c16c1c59 100644
--- a/src/hotspot/share/utilities/debug.hpp
+++ b/src/hotspot/share/utilities/debug.hpp
@@ -39,7 +39,21 @@ class oopDesc;
#define CAN_SHOW_REGISTERS_ON_ASSERT
extern char* g_assert_poison;
extern const char* g_assert_poison_read_only;
+#if (defined(_WINDOWS))
+// We use structured exception handling when writing to the poison variable.
+// This allows us to continue execution and perform error reporting instead of
+// bailing out to other SEH handlers such as those in the googletest code.
+#include <excpt.h>
+#define TOUCH_ASSERT_POISON \
+do { \
+ __try { \
+ (*g_assert_poison) = 'X'; \
+ } __except (EXCEPTION_CONTINUE_EXECUTION) { \
+ } \
+} while (0)
+#else
#define TOUCH_ASSERT_POISON (*g_assert_poison) = 'X';
+#endif // _WINDOWS
void initialize_assert_poison();
void disarm_assert_poison();
bool handle_assert_poison_fault(const void* ucVoid);
This change failed to build with the following errors, the most notable of which is Compiler Error C2712: cannot use __try in functions that require object unwinding.
...\jdk\src\hotspot\share\utilities/growableArray.hpp(81): error C2712: Cannot use __try in functions that require object unwinding
...\jdk\src\hotspot\share\classfile/vmClassID.hpp(41): error C3615: constexpr function 'EnumeratorRangeImpl::end_value' cannot result in a constant expression
...\jdk\src\hotspot\share\utilities/enumIterator.hpp(97): note: failure was caused by a statement or an expression that is not valid in a constexpr context
...\jdk\src\hotspot\share\utilities/unsigned5.hpp(190): error C3615: constexpr function 'UNSIGNED5::max_encoded_in_length' cannot result in a constant expression
...\jdk\src\hotspot\share\utilities/unsigned5.hpp(191): note: failure was caused by a statement or an expression that is not valid in a constexpr context
...\jdk\src\hotspot\cpu\x86\register_x86.hpp(61): error C3615: constexpr function 'Register::RegisterImpl::encoding' cannot result in a constant expression
...\jdk\src\hotspot\cpu\x86\register_x86.hpp(61): note: failure was caused by a statement or an expression that is not valid in a constexpr context
...\jdk\src\hotspot\cpu\x86\register_x86.hpp(233): error C3615: constexpr function 'XMMRegister::XMMRegisterImpl::encoding' cannot result in a constant expression
...\jdk\src\hotspot\cpu\x86\register_x86.hpp(233): note: failure was caused by a statement or an expression that is not valid in a constexpr context
...\jdk\src\hotspot\share\runtime/park.hpp(131): error C2712: Cannot use __try in functions that require object unwinding
...\jdk\src\hotspot\share\runtime/mutexLocker.hpp(235): error C2712: Cannot use __try in functions that require object unwinding
...\jdk\src\hotspot\share\runtime/mutexLocker.hpp(240): error C2712: Cannot use __try in functions that require object unwinding
...\jdk\src\hotspot\share\runtime/mutexLocker.hpp(250): error C2712: Cannot use __try in functions that require object unwinding
...\jdk\src\hotspot\share\runtime/mutexLocker.hpp(255): error C2712: Cannot use __try in functions that require object unwinding
At this point, I realized that I needed to disable SEH at the gtest level. I turned off GTEST_HAS_SEH with this change and finally got the gtests to pass!
diff --git a/make/hotspot/lib/CompileGtest.gmk b/make/hotspot/lib/CompileGtest.gmk
index d2cdc7685c9..d9e73fc3847 100644
--- a/make/hotspot/lib/CompileGtest.gmk
+++ b/make/hotspot/lib/CompileGtest.gmk
@@ -68,7 +68,7 @@ $(eval $(call SetupJdkLibrary, BUILD_GTEST_LIBGTEST, \
-I$(GTEST_FRAMEWORK_SRC)/googletest/include \
-I$(GTEST_FRAMEWORK_SRC)/googlemock \
-I$(GTEST_FRAMEWORK_SRC)/googlemock/include, \
- CFLAGS_windows := -EHsc, \
+ CFLAGS_windows := -EHsc -DGTEST_HAS_SEH=0, \
CFLAGS_macosx := -DGTEST_OS_MAC=1, \
OPTIMIZATION := $(JVM_OPTIMIZATION), \
COPY_DEBUG_SYMBOLS := $(GTEST_COPY_DEBUG_SYMBOLS), \
@@ -98,7 +98,7 @@ $(eval $(call SetupJdkLibrary, BUILD_GTEST_LIBJVM, \
-I$(GTEST_FRAMEWORK_SRC)/googletest/include \
-I$(GTEST_FRAMEWORK_SRC)/googlemock/include \
$(addprefix -I, $(GTEST_TEST_SRC)), \
- CFLAGS_windows := -EHsc, \
+ CFLAGS_windows := -EHsc -DGTEST_HAS_SEH=0, \
CFLAGS_macosx := -DGTEST_OS_MAC=1, \
DISABLED_WARNINGS_gcc := $(DISABLED_WARNINGS_gcc) \
undef stringop-overflow, \
@@ -152,6 +152,7 @@ $(eval $(call SetupJdkExecutable, BUILD_GTEST_LAUNCHER, \
-I$(GTEST_FRAMEWORK_SRC)/googletest/include \
-I$(GTEST_FRAMEWORK_SRC)/googlemock \
-I$(GTEST_FRAMEWORK_SRC)/googlemock/include, \
+ CFLAGS_windows := -EHsc -DGTEST_HAS_SEH=0, \
LD_SET_ORIGIN := false, \
LDFLAGS_unix := $(call SET_SHARED_LIBRARY_ORIGIN), \
JDK_LIBS := gtest:libjvm, \
diff --git a/make/hotspot/lib/CompileJvm.gmk b/make/hotspot/lib/CompileJvm.gmk
index 6b5edc85b23..42f3969c775 100644
--- a/make/hotspot/lib/CompileJvm.gmk
+++ b/make/hotspot/lib/CompileJvm.gmk
@@ -179,6 +179,7 @@ $(eval $(call SetupJdkLibrary, BUILD_LIBJVM, \
EXCLUDE_PATTERNS := $(JVM_EXCLUDE_PATTERNS), \
DEFAULT_CFLAGS := false, \
CFLAGS := $(JVM_CFLAGS), \
+ CFLAGS_windows := -DGTEST_HAS_SEH=0, \
abstract_vm_version.cpp_CXXFLAGS := $(CFLAGS_VM_VERSION), \
arguments.cpp_CXXFLAGS := $(CFLAGS_VM_VERSION), \
DISABLED_WARNINGS_gcc := $(DISABLED_WARNINGS_gcc), \
What was not sure of was whether the JVM reporting code was running (vs the JVM just exiting) and whether there was a narrower way to pass the GTEST_HAS_SEH define – I noticed it in thousands of lines in the compilation log, which might also explain why I was getting error C2712: Cannot use __try in functions that require object unwinding in many more places than I expected when I added the -EHsc flag when compiling jvm.obj. Therefore, it was logical to try to find the minimal diff that would fix the gtests. Here’s one I tried:
diff --git a/make/hotspot/lib/CompileGtest.gmk b/make/hotspot/lib/CompileGtest.gmk
index d2cdc7685c9..95794ff0bbe 100644
--- a/make/hotspot/lib/CompileGtest.gmk
+++ b/make/hotspot/lib/CompileGtest.gmk
@@ -152,6 +152,7 @@ $(eval $(call SetupJdkExecutable, BUILD_GTEST_LAUNCHER, \
-I$(GTEST_FRAMEWORK_SRC)/googletest/include \
-I$(GTEST_FRAMEWORK_SRC)/googlemock \
-I$(GTEST_FRAMEWORK_SRC)/googlemock/include, \
+ CFLAGS_windows := -DGTEST_HAS_SEH=0, \
LD_SET_ORIGIN := false, \
LDFLAGS_unix := $(call SET_SHARED_LIBRARY_ORIGIN), \
JDK_LIBS := gtest:libjvm, \
diff --git a/make/hotspot/lib/CompileJvm.gmk b/make/hotspot/lib/CompileJvm.gmk
index 6b5edc85b23..42f3969c775 100644
--- a/make/hotspot/lib/CompileJvm.gmk
+++ b/make/hotspot/lib/CompileJvm.gmk
@@ -179,6 +179,7 @@ $(eval $(call SetupJdkLibrary, BUILD_LIBJVM, \
EXCLUDE_PATTERNS := $(JVM_EXCLUDE_PATTERNS), \
DEFAULT_CFLAGS := false, \
CFLAGS := $(JVM_CFLAGS), \
+ CFLAGS_windows := -DGTEST_HAS_SEH=0, \
abstract_vm_version.cpp_CXXFLAGS := $(CFLAGS_VM_VERSION), \
arguments.cpp_CXXFLAGS := $(CFLAGS_VM_VERSION), \
DISABLED_WARNINGS_gcc := $(DISABLED_WARNINGS_gcc), \
The gtests built from the diff below still failed:
diff --git a/make/hotspot/lib/CompileGtest.gmk b/make/hotspot/lib/CompileGtest.gmk
index d2cdc7685c9..403613c406c 100644
--- a/make/hotspot/lib/CompileGtest.gmk
+++ b/make/hotspot/lib/CompileGtest.gmk
@@ -98,7 +98,7 @@ $(eval $(call SetupJdkLibrary, BUILD_GTEST_LIBJVM, \
-I$(GTEST_FRAMEWORK_SRC)/googletest/include \
-I$(GTEST_FRAMEWORK_SRC)/googlemock/include \
$(addprefix -I, $(GTEST_TEST_SRC)), \
- CFLAGS_windows := -EHsc, \
+ CFLAGS_windows := -EHsc -DGTEST_HAS_SEH=0, \
CFLAGS_macosx := -DGTEST_OS_MAC=1, \
DISABLED_WARNINGS_gcc := $(DISABLED_WARNINGS_gcc) \
undef stringop-overflow, \
diff --git a/make/hotspot/lib/CompileJvm.gmk b/make/hotspot/lib/CompileJvm.gmk
index 6b5edc85b23..42f3969c775 100644
--- a/make/hotspot/lib/CompileJvm.gmk
+++ b/make/hotspot/lib/CompileJvm.gmk
@@ -179,6 +179,7 @@ $(eval $(call SetupJdkLibrary, BUILD_LIBJVM, \
EXCLUDE_PATTERNS := $(JVM_EXCLUDE_PATTERNS), \
DEFAULT_CFLAGS := false, \
CFLAGS := $(JVM_CFLAGS), \
+ CFLAGS_windows := -DGTEST_HAS_SEH=0, \
abstract_vm_version.cpp_CXXFLAGS := $(CFLAGS_VM_VERSION), \
arguments.cpp_CXXFLAGS := $(CFLAGS_VM_VERSION), \
DISABLED_WARNINGS_gcc := $(DISABLED_WARNINGS_gcc), \
This manual approach of finding the minimal change needed was tedious so I decided to add my own defines to see which portions of the gmk files are used and for which compile/link commands:
diff --git a/make/hotspot/lib/CompileGtest.gmk b/make/hotspot/lib/CompileGtest.gmk
index d2cdc7685c9..acf0eae159a 100644
--- a/make/hotspot/lib/CompileGtest.gmk
+++ b/make/hotspot/lib/CompileGtest.gmk
@@ -68,7 +68,7 @@ $(eval $(call SetupJdkLibrary, BUILD_GTEST_LIBGTEST, \
-I$(GTEST_FRAMEWORK_SRC)/googletest/include \
-I$(GTEST_FRAMEWORK_SRC)/googlemock \
-I$(GTEST_FRAMEWORK_SRC)/googlemock/include, \
- CFLAGS_windows := -EHsc, \
+ CFLAGS_windows := -EHsc -DGTEST_HAS_SEH=0 -DMYTEST_LOCATION1, \
CFLAGS_macosx := -DGTEST_OS_MAC=1, \
OPTIMIZATION := $(JVM_OPTIMIZATION), \
COPY_DEBUG_SYMBOLS := $(GTEST_COPY_DEBUG_SYMBOLS), \
@@ -98,7 +98,7 @@ $(eval $(call SetupJdkLibrary, BUILD_GTEST_LIBJVM, \
-I$(GTEST_FRAMEWORK_SRC)/googletest/include \
-I$(GTEST_FRAMEWORK_SRC)/googlemock/include \
$(addprefix -I, $(GTEST_TEST_SRC)), \
- CFLAGS_windows := -EHsc, \
+ CFLAGS_windows := -EHsc -DGTEST_HAS_SEH=0 -DMYTEST_LOCATION2, \
CFLAGS_macosx := -DGTEST_OS_MAC=1, \
DISABLED_WARNINGS_gcc := $(DISABLED_WARNINGS_gcc) \
undef stringop-overflow, \
@@ -152,6 +152,7 @@ $(eval $(call SetupJdkExecutable, BUILD_GTEST_LAUNCHER, \
-I$(GTEST_FRAMEWORK_SRC)/googletest/include \
-I$(GTEST_FRAMEWORK_SRC)/googlemock \
-I$(GTEST_FRAMEWORK_SRC)/googlemock/include, \
+ CFLAGS_windows := -EHsc -DGTEST_HAS_SEH=0 -DMYTEST_LOCATION3, \
LD_SET_ORIGIN := false, \
LDFLAGS_unix := $(call SET_SHARED_LIBRARY_ORIGIN), \
JDK_LIBS := gtest:libjvm, \
diff --git a/make/hotspot/lib/CompileJvm.gmk b/make/hotspot/lib/CompileJvm.gmk
index 6b5edc85b23..5994ffc6be1 100644
--- a/make/hotspot/lib/CompileJvm.gmk
+++ b/make/hotspot/lib/CompileJvm.gmk
@@ -179,6 +179,7 @@ $(eval $(call SetupJdkLibrary, BUILD_LIBJVM, \
EXCLUDE_PATTERNS := $(JVM_EXCLUDE_PATTERNS), \
DEFAULT_CFLAGS := false, \
CFLAGS := $(JVM_CFLAGS), \
+ CFLAGS_windows := -DGTEST_HAS_SEH=0 -DMYTEST_LOCATION0, \
abstract_vm_version.cpp_CXXFLAGS := $(CFLAGS_VM_VERSION), \
arguments.cpp_CXXFLAGS := $(CFLAGS_VM_VERSION), \
DISABLED_WARNINGS_gcc := $(DISABLED_WARNINGS_gcc), \
Location 1 only showed up for the 2 files below (matching the INCLUDE_FILES in make/hotspot/lib/CompileGtest.gmk), which made it clear that -DGTEST_HAS_SEH=0 was needed in this section.
- /cygdrive/c/repos/googletest/googlemock/src/gmock-all.cc
- /cygdrive/c/repos/googletest/googletest/src/gtest-all.cc
For location 2, there were 214 lines matching the regex DMYTEST_LOCATION2.+.cpp and 213 lines matching the regex DMYTEST_LOCATION2.+/test/hotspot/gtest/.+.cpp. The location 2 define was therefore correctly scoped to the gtests only. These 213 lines compiled files like test_blocktree.cpp and test_vmerror.cpp. The line that was different between the 2 regexes was the one compiling build/windows-x86_64-server-slowdebug/hotspot/variant-server/libjvm/gtest/objs/BUILD_GTEST_LIBJVM_pch.cpp. Location 3 was only used for compiling test/hotspot/gtest/gtestLauncher.cpp. The challenging case was location 0, which seemed to appear for way more files than it should. Was it really necessary? No it wasn’t! That made life much easier for me.
Inspecting gTest Code Coverage
In the course of this investigation, I considered using time travel debugging to see which code paths were executed. An alternative was to see whether the exception filter code was covered at the end of the gtest execution! The path to the code coverage tool is C:\Program Files\Microsoft Visual Studio\2022\Enterprise\Common7\IDE\Extensions\Microsoft\CodeCoverage.Console\Microsoft.CodeCoverage.Console.exe – it should be in the path by default in the Developer Command Prompt. I kicked it off with these commands:
cd build/windows-x86_64-server-slowdebug/images/test/hotspot/gtest/server
mkdir ../server-orig
cp * ../server-orig
Microsoft.CodeCoverage.Console.exe instrument gtestLauncher.exe
Microsoft.CodeCoverage.Console.exe instrument jvm.dll
Microsoft.CodeCoverage.Console.exe collect "gtestLauncher.exe -jdk:D:\java\forks\openjdk\jdk\build\windows-x86_64-server-slowdebug\images\jdk"
start output.coverage
Search for “exceptionfilter” in the Code Coverage Results pane to view the code coverage for the exception filter.

Verifying Execution Path
The first time I paused execution of the gtests in the debugger, stopped in jdk/src/hotspot/share/utilities/bitMap.cpp. I set a breakpoint there. I liked this location because I could move execution into the failure path (in the assembly view). This was how I saw the gtest structured exception handler kicking in without the JVM’s failure reporting code executing. With the tests now passing, I found the write to the poison location just going through without interruption. Did this mean the test was broken? Or did it mean that the exception filter ran and successfully said to continue execution? I think it has to be the latter but I’ll need time travel debugging to verify this. In the meantime, I sought to at least ensure there were multiple test processes involved.
Verifying Multiple Processes are Started
I started Process Monitor and added a filter for path containing “slowdebug”. Notice tons of PIDs for gtestlauncher.exe in the process monitor screenshot below (as expected).

I could successfully execute the error handling path by manually moving the program counter (RIP) after skipping into the failure path of BitMap::verify_range. Why didn’t the PID change in the debugger? Oh wait, was I was still stepping thereby causing recursion? This callstack did not support that hypothesis. Looks like it was just error reporting continuing to execute.
> jvm.dll!BitMap::verify_range(unsigned __int64 beg, unsigned __int64 end) Line 212 C++ jvm.dll!BitMap::clear_range(unsigned __int64 beg, unsigned __int64 end) Line 280 C++ jvm.dll!JVMFlag::printFlags(outputStream * out, bool withComments, bool printRanges, bool skipDefaults) Line 706 C++ jvm.dll!VMError::report(outputStream * st, bool _verbose) Line 1260 C++ jvm.dll!VMError::report_and_die(int id, const char * message, const char * detail_fmt, char * detail_args, Thread * thread, unsigned char * pc, const void * siginfo, const void * context, const char * filename, int lineno, unsigned __int64 size) Line 1847 C++ jvm.dll!report_vm_error(const char * file, int line, const char * error_msg, const char * detail_fmt, ...) Line 195 C++ jvm.dll!CompressedKlassPointers::check_init<int>(int var) Line 154 C++ jvm.dll!CompressedKlassPointers::shift() Line 218 C++ jvm.dll!CompressedKlassPointers::print_mode(outputStream * st) Line 301 C++ jvm.dll!VMError::report(outputStream * st, bool _verbose) Line 1196 C++ jvm.dll!VMError::report_and_die(int id, const char * message, const char * detail_fmt, char * detail_args, Thread * thread, unsigned char * pc, const void * siginfo, const void * context, const char * filename, int lineno, unsigned __int64 size) Line 1847 C++ jvm.dll!report_vm_error(const char * file, int line, const char * error_msg, const char * detail_fmt, ...) Line 195 C++ jvm.dll!BitMap::verify_limit(unsigned __int64 bit) Line 206 C++ jvm.dll!BitMap::to_words_align_down(unsigned __int64 bit) Line 94 C++ jvm.dll!BitMap::word_addr(unsigned __int64 bit) Line 144 C++ jvm.dll!BitMap::set_bit(unsigned __int64 bit) Line 37 C++ jvm.dll!JfrEventVerifier::set_field_bit(unsigned __int64 field_idx) Line 41 C++ jvm.dll!JfrEvent<EventTenuringDistribution>::set_field_bit(unsigned __int64 field_idx) Line 267 C++ jvm.dll!EventObjectAllocationOutsideTLAB::set_objectClass(const Klass * new_value) Line 7304 C++ jvm.dll!trace_flag_changed<bool,EventBooleanFlagChanged>(JVMFlag * flag, const bool old_value, const bool new_value, const JVMFlagOrigin origin) Line 39 C++ jvm.dll!TypedFlagAccessImpl<bool,EventBooleanFlagChanged>::check_constraint_and_set(JVMFlag * flag, void * value_addr, JVMFlagOrigin origin, bool verbose) Line 78 C++ jvm.dll!FlagAccessImpl_bool::set_impl(JVMFlag * flag, void * value_addr, JVMFlagOrigin origin) Line 98 C++ jvm.dll!FlagAccessImpl::set(JVMFlag * flag, void * value, JVMFlagOrigin origin) Line 49 C++ jvm.dll!JVMFlagAccess::set_impl(JVMFlag * flag, void * value, JVMFlagOrigin origin) Line 307 C++ jvm.dll!JVMFlagAccess::set_or_assert(JVMFlagsEnum flag_enum, int type_enum, void * value, JVMFlagOrigin origin) Line 353 C++ jvm.dll!JVMFlagAccess::set<bool,0>(JVMFlagsEnum flag_enum, bool value, JVMFlagOrigin origin) Line 101 C++ jvm.dll!Flag_UseLargePagesIndividualAllocation_set(bool value, JVMFlagOrigin origin) Line 69 C++ jvm.dll!os::init() Line 4436 C++ jvm.dll!Threads::create_vm(JavaVMInitArgs * args, bool * canTryAgain) Line 463 C++ jvm.dll!JNI_CreateJavaVM_inner(JavaVM_ * * vm, void * * penv, void * args) Line 3589 C++ jvm.dll!JNI_CreateJavaVM(JavaVM_ * * vm, void * * penv, void * args) Line 3680 C++ jvm.dll!init_jvm(int argc, char * * argv, bool disable_error_handling, JavaVM_ * * jvm_ptr) Line 94 C++ jvm.dll!JVMInitializerListener::OnTestStart(const testing::TestInfo & test_info) Line 124 C++ jvm.dll!testing::internal::TestEventRepeater::OnTestStart(const testing::TestInfo & parameter) Line 3858 C++ jvm.dll!testing::TestInfo::Run() Line 2821 C++ jvm.dll!testing::TestSuite::Run() Line 3015 C++ jvm.dll!testing::internal::UnitTestImpl::RunAllTests() Line 5920 C++ jvm.dll!testing::internal::HandleSehExceptionsInMethodIfSupported<testing::internal::UnitTestImpl,bool>(testing::internal::UnitTestImpl * object, bool(testing::internal::UnitTestImpl::*)() method, const char * location) Line 2614 C++ jvm.dll!testing::internal::HandleExceptionsInMethodIfSupported<testing::internal::UnitTestImpl,bool>(testing::internal::UnitTestImpl * object, bool(testing::internal::UnitTestImpl::*)() method, const char * location) Line 2648 C++ jvm.dll!testing::UnitTest::Run() Line 5484 C++ jvm.dll!RUN_ALL_TESTS() Line 2317 C++ jvm.dll!runUnitTestsInner(int argc, char * * argv) Line 290 C++ jvm.dll!runUnitTests(int argc, char * * argv) Line 371 C++ gtestLauncher.exe!main(int argc, char * * argv) Line 40 C++ [Inline Frame] gtestLauncher.exe!invoke_main() Line 78 C++ gtestLauncher.exe!__scrt_common_main_seh() Line 288 C++ kernel32.dll!00007ffdcbdce8d7() Unknown ntdll.dll!00007ffdcc97c34c() Unknown
One advantage of the stack above is that it showed how the os::init code gets executed (which I was curious about when wondering whether the exception filter was being set up). Disabling the breakpoint just before skipping into the failure path and resuming execution now led to the JVM dying:
[==========] Running 1197 tests from 205 test suites.
[----------] Global test environment set-up.
[----------] 3 tests from AltHashingTest
[ RUN ] AltHashingTest.halfsiphash_test_ByteArray
[ OK ] AltHashingTest.halfsiphash_test_ByteArray (0 ms)
[ RUN ] AltHashingTest.halfsiphash_test_CharArray
[ OK ] AltHashingTest.halfsiphash_test_CharArray (0 ms)
[ RUN ] AltHashingTest.halfsiphash_test_FromReference
[ OK ] AltHashingTest.halfsiphash_test_FromReference (0 ms)
[----------] 3 tests from AltHashingTest (2 ms total)
[----------] 1 test from ThreadsListHandle
[ RUN ] ThreadsListHandle.sanity_vm
#
# A fatal error has been detected by the Java Runtime Environment:
#
# Internal Error (d:\java\forks\openjdk\jdk\src\hotspot\share\utilities\bitMap.cpp:208), pid=39132, tid=109872
# assert(bit <= _size) failed: BitMap limit out of bounds: 0 > 64
#
# JRE version: ((uninitialized)) (slowdebug build )
# Java VM: OpenJDK 64-Bit Server VM (slowdebug 26-internal-adhoc.saint.jdk, mixed mode, sharing, tiered, compressed class ptrs, unknown gc, windows-amd64)
# Core dump will be written. Default location: D:\java\forks\openjdk\jdk\build\windows-x86_64-server-slowdebug\images\test\hotspot\gtest\server\hs_err_pid39132.mdmp
#
# An error report file with more information is saved as:
# D:\java\forks\openjdk\jdk\build\windows-x86_64-server-slowdebug\images\test\hotspot\gtest\server\hs_err_pid39132.log
#
#
D:\java\forks\openjdk\jdk\build\windows-x86_64-server-slowdebug\images\test\hotspot\gtest\server\gtestLauncher.exe (process 39132) exited with code 1 (0x1).
To automatically close the console when debugging stops, enable Tools->Options->Debugging->Automatically close the console when debugging stops.
Press any key to close this window . . .
Here’s the stack for when the breakpoint is hit:
jvm.dll!BitMap::verify_limit(unsigned __int64 bit) Line 206
jvm.dll!BitMap::to_words_align_down(unsigned __int64 bit) Line 94
jvm.dll!BitMap::word_addr(unsigned __int64 bit) Line 144
jvm.dll!BitMap::set_bit(unsigned __int64 bit) Line 37
jvm.dll!JfrEventVerifier::set_field_bit(unsigned __int64 field_idx) Line 41
jvm.dll!JfrEvent<EventTenuringDistribution>::set_field_bit(unsigned __int64 field_idx) Line 267
jvm.dll!EventObjectAllocationOutsideTLAB::set_objectClass(const Klass * new_value) Line 7304
jvm.dll!trace_flag_changed<bool,EventBooleanFlagChanged>(JVMFlag * flag, const bool old_value, const bool new_value, const JVMFlagOrigin origin) Line 39
jvm.dll!TypedFlagAccessImpl<bool,EventBooleanFlagChanged>::check_constraint_and_set(JVMFlag * flag, void * value_addr, JVMFlagOrigin origin, bool verbose) Line 78
jvm.dll!FlagAccessImpl_bool::set_impl(JVMFlag * flag, void * value_addr, JVMFlagOrigin origin) Line 98
jvm.dll!FlagAccessImpl::set(JVMFlag * flag, void * value, JVMFlagOrigin origin) Line 49
jvm.dll!JVMFlagAccess::set_impl(JVMFlag * flag, void * value, JVMFlagOrigin origin) Line 307
jvm.dll!JVMFlagAccess::set_or_assert(JVMFlagsEnum flag_enum, int type_enum, void * value, JVMFlagOrigin origin) Line 353
jvm.dll!JVMFlagAccess::set<bool,0>(JVMFlagsEnum flag_enum, bool value, JVMFlagOrigin origin) Line 101
jvm.dll!Flag_UseLargePagesIndividualAllocation_set(bool value, JVMFlagOrigin origin) Line 69
jvm.dll!os::init() Line 4436
jvm.dll!Threads::create_vm(JavaVMInitArgs * args, bool * canTryAgain) Line 463
jvm.dll!JNI_CreateJavaVM_inner(JavaVM_ * * vm, void * * penv, void * args) Line 3589
jvm.dll!JNI_CreateJavaVM(JavaVM_ * * vm, void * * penv, void * args) Line 3680
jvm.dll!init_jvm(int argc, char * * argv, bool disable_error_handling, JavaVM_ * * jvm_ptr) Line 94
jvm.dll!JVMInitializerListener::OnTestStart(const testing::TestInfo & test_info) Line 124
jvm.dll!testing::internal::TestEventRepeater::OnTestStart(const testing::TestInfo & parameter) Line 3858
jvm.dll!testing::TestInfo::Run() Line 2821
jvm.dll!testing::TestSuite::Run() Line 3015
jvm.dll!testing::internal::UnitTestImpl::RunAllTests() Line 5920
jvm.dll!testing::internal::HandleSehExceptionsInMethodIfSupported<testing::internal::UnitTestImpl,bool>(testing::internal::UnitTestImpl * object, bool(testing::internal::UnitTestImpl::*)() method, const char * location) Line 2614
jvm.dll!testing::internal::HandleExceptionsInMethodIfSupported<testing::internal::UnitTestImpl,bool>(testing::internal::UnitTestImpl * object, bool(testing::internal::UnitTestImpl::*)() method, const char * location) Line 2648
jvm.dll!testing::UnitTest::Run() Line 5484
jvm.dll!RUN_ALL_TESTS() Line 2317
jvm.dll!runUnitTestsInner(int argc, char * * argv) Line 290
jvm.dll!runUnitTests(int argc, char * * argv) Line 371
gtestLauncher.exe!main(int argc, char * * argv) Line 40
[Inline Frame] gtestLauncher.exe!invoke_main() Line 78
Pull Request Feedback
With all this information at hand, I opened 8364664: gtest death tests failing on Windows by swesonga · Pull Request #26661 · openjdk/jdk. One reviewer asked about removing the -EHsc flag from the gtests altogether. I tried it with the change below:
diff --git a/make/hotspot/lib/CompileGtest.gmk b/make/hotspot/lib/CompileGtest.gmk
index d2cdc7685c9..2c6b5f23516 100644
--- a/make/hotspot/lib/CompileGtest.gmk
+++ b/make/hotspot/lib/CompileGtest.gmk
@@ -68,7 +68,6 @@ $(eval $(call SetupJdkLibrary, BUILD_GTEST_LIBGTEST, \
-I$(GTEST_FRAMEWORK_SRC)/googletest/include \
-I$(GTEST_FRAMEWORK_SRC)/googlemock \
-I$(GTEST_FRAMEWORK_SRC)/googlemock/include, \
- CFLAGS_windows := -EHsc, \
CFLAGS_macosx := -DGTEST_OS_MAC=1, \
OPTIMIZATION := $(JVM_OPTIMIZATION), \
COPY_DEBUG_SYMBOLS := $(GTEST_COPY_DEBUG_SYMBOLS), \
@@ -98,7 +97,6 @@ $(eval $(call SetupJdkLibrary, BUILD_GTEST_LIBJVM, \
-I$(GTEST_FRAMEWORK_SRC)/googletest/include \
-I$(GTEST_FRAMEWORK_SRC)/googlemock/include \
$(addprefix -I, $(GTEST_TEST_SRC)), \
- CFLAGS_windows := -EHsc, \
CFLAGS_macosx := -DGTEST_OS_MAC=1, \
DISABLED_WARNINGS_gcc := $(DISABLED_WARNINGS_gcc) \
undef stringop-overflow, \
The build failed with this error:
c:\progra~1\mib055~1\2022\enterprise\vc\tools\msvc\14.44.35207\include\__msvc_ostream.hpp(781): error C2220: the following warning is treated as an error
c:\progra~1\mib055~1\2022\enterprise\vc\tools\msvc\14.44.35207\include\__msvc_ostream.hpp(781): warning C4530: C++ exception handler used, but unwind semantics are not enabled. Specify /EHsc
c:\progra~1\mib055~1\2022\enterprise\vc\tools\msvc\14.44.35207\include\__msvc_ostream.hpp(781): note: the template instantiation context (the oldest one first) is
c:\repos\googletest\googletest\include\gtest/gtest-message.h(118): note: see reference to function template instantiation 'std::basic_ostream<char,std::char_traits<char>> &std::operator <<<std::char_traits<char>>(std::basic_ostream<char,std::char_traits<char>> &,const char *)' being compiled
This is expected since the googletest code is using C++ exception handling. The more significant revelation for me is that other groups are running gtests on Windows with --gtest_catch_exceptions=0 which disables the inbuilt exception handler. This is done using the GTestWrapper. This comment is helpful because it has links to the earlier issues around this space and explicitly clarifies that C++ exceptions are not used in libjvm code. While this PR did not result in a patch, it was an educational investigation for me!
Leave a Reply