diff --git a/src/native/clr/host/bridge-processing.cc b/src/native/clr/host/bridge-processing.cc index b4ea077a081..7029b391c8a 100644 --- a/src/native/clr/host/bridge-processing.cc +++ b/src/native/clr/host/bridge-processing.cc @@ -174,6 +174,7 @@ bool BridgeProcessingShared::add_reference (jobject from, jobject to) noexcept env->DeleteLocalRef (java_class); env->CallVoidMethod (from, add_method_id, to); + abort_on_pending_java_exception ("A Java exception was thrown by monodroidAddReference during GC bridge processing"sv); return true; } @@ -217,6 +218,18 @@ void BridgeProcessingShared::clear_references (jobject handle) noexcept env->DeleteLocalRef (java_class); env->CallVoidMethod (handle, clear_method_id); + abort_on_pending_java_exception ("A Java exception was thrown by monodroidClearReferences during GC bridge processing"sv); +} + +void BridgeProcessingShared::abort_on_pending_java_exception (std::string_view message) noexcept +{ + if (!env->ExceptionCheck ()) [[likely]] { + return; + } + + env->ExceptionDescribe (); + env->ExceptionClear (); + Helpers::abort_application (LOG_GC, message); } void BridgeProcessingShared::take_global_ref (HandleContext &context) noexcept @@ -230,6 +243,11 @@ void BridgeProcessingShared::take_global_ref (HandleContext &context) noexcept log_weak_to_gref (weak, handle); if (handle == nullptr) { + // A null result normally means the weak reference's target was collected by the Java GC. + // However, NewGlobalRef can also fail (returning null with a pending exception) when the VM + // is out of memory or the global reference table is full. Treating that as "collected" would + // tear down a live peer, so distinguish the two and fail fast on a genuine failure. + abort_on_pending_java_exception ("Failed to promote a weak global reference to a global reference during GC bridge processing"sv); log_weak_ref_collected (weak); } @@ -249,6 +267,15 @@ void BridgeProcessingShared::take_weak_global_ref (const HandleContext &context) log_take_weak_global_ref (handle); jobject weak = env->NewWeakGlobalRef (handle); + if (weak == nullptr) [[unlikely]] { + // `handle` is a valid strong global reference, so NewWeakGlobalRef only returns null when the + // VM is out of memory. Continuing would delete the strong reference below and lose the object + // (a later NewGlobalRef of a null weak reference would look like a collected peer), so fail fast. + // The OOM failure raises a pending exception; abort unconditionally in case it somehow did not. + constexpr std::string_view failure = "Failed to create a weak global reference during GC bridge processing"sv; + abort_on_pending_java_exception (failure); + Helpers::abort_application (LOG_GC, failure); + } log_weak_gref_new (handle, weak); context.control_block->handle = weak; diff --git a/src/native/clr/include/host/bridge-processing-shared.hh b/src/native/clr/include/host/bridge-processing-shared.hh index 0e0a9a6244c..7d051dc1aa1 100644 --- a/src/native/clr/include/host/bridge-processing-shared.hh +++ b/src/native/clr/include/host/bridge-processing-shared.hh @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include @@ -51,6 +52,11 @@ private: void clear_references_if_needed (const HandleContext &context) noexcept; void clear_references (jobject handle) noexcept; + // If a Java exception is pending on `env`, describe it, clear it, and abort. Bridge + // processing has no safe way to recover from an exception thrown by a peer's reference + // callbacks, and leaving an exception pending would make subsequent JNI calls undefined. + void abort_on_pending_java_exception (std::string_view message) noexcept; + void log_missing_add_references_method (jclass java_class) noexcept; void log_missing_clear_references_method (jclass java_class) noexcept; void log_weak_to_gref (jobject weak, jobject handle) noexcept;