diff --git a/src/coreclr/vm/eedbginterfaceimpl.cpp b/src/coreclr/vm/eedbginterfaceimpl.cpp index 2c33b9c94a7fc7..d3a2b06c1cdd5d 100644 --- a/src/coreclr/vm/eedbginterfaceimpl.cpp +++ b/src/coreclr/vm/eedbginterfaceimpl.cpp @@ -247,7 +247,6 @@ OBJECTHANDLE EEDbgInterfaceImpl::GetThreadException(Thread *pThread) } // Return the last thrown object if there's no current throwable. - // This logic is similar to UpdateCurrentThrowable(). return pThread->m_LastThrownObjectHandle; } diff --git a/src/coreclr/vm/eepolicy.cpp b/src/coreclr/vm/eepolicy.cpp index 6fcbaec5aaf61a..036c984a2ed7af 100644 --- a/src/coreclr/vm/eepolicy.cpp +++ b/src/coreclr/vm/eepolicy.cpp @@ -789,8 +789,7 @@ void DECLSPEC_NORETURN EEPolicy::HandleFatalStackOverflow(EXCEPTION_POINTERS *pE OBJECTHANDLE ohSO = CLRException::GetPreallocatedStackOverflowExceptionHandle(); if (ohSO != NULL) { - pThread->SafeSetThrowables(ObjectFromHandle(ohSO), - TRUE); + pThread->SetLastThrownObject(ObjectFromHandle(ohSO), TRUE); } else { diff --git a/src/coreclr/vm/excep.cpp b/src/coreclr/vm/excep.cpp index a841feafc3aca4..4a280202c56850 100644 --- a/src/coreclr/vm/excep.cpp +++ b/src/coreclr/vm/excep.cpp @@ -116,10 +116,6 @@ BOOL ShouldOurUEFDisplayUI(PEXCEPTION_POINTERS pExceptionInfo) void NotifyAppDomainsOfUnhandledException( PEXCEPTION_POINTERS pExceptionPointers, - OBJECTREF *pThrowableIn, - BOOL useLastThrownObject); - -VOID SetManagedUnhandledExceptionBit( BOOL useLastThrownObject); //------------------------------------------------------------------------------- @@ -2021,10 +2017,10 @@ VOID DECLSPEC_NORETURN RaiseTheExceptionInternalOnly(OBJECTREF throwable) // Always save the current object in the handle so on rethrow we can reuse it. This is important as it // contains stack trace info. // - // Note: we use SafeSetLastThrownObject, which will try to set the throwable and if there are any problems, + // Note: we use SetLastThrownObject, which will try to set the throwable and if there are any problems, // it will set the throwable to something appropriate (like OOM exception) and return the new // exception. Thus, the user's exception object can be replaced here. - throwable = pThread->SafeSetLastThrownObject(throwable); + throwable = pThread->SetLastThrownObject(throwable); ULONG_PTR hr = GetHRFromThrowable(throwable); @@ -3750,36 +3746,6 @@ BOOL InstallUnhandledExceptionFilter() { return TRUE; } -// -// Update the current throwable on the thread if necessary. If we're looking at one of our exceptions, and if the -// current throwable on the thread is NULL, then we'll set it to something more useful based on the -// LastThrownObject. -// -BOOL UpdateCurrentThrowable(PEXCEPTION_RECORD pExceptionRecord) -{ - STATIC_CONTRACT_THROWS; - STATIC_CONTRACT_MODE_ANY; - STATIC_CONTRACT_GC_TRIGGERS; - - BOOL useLastThrownObject = FALSE; - - Thread* pThread = GetThread(); - - // GetThrowable needs cooperative. - GCX_COOP(); - - if ((pThread->GetThrowable() == NULL) && (pThread->LastThrownObject() != NULL)) - { - // If GetThrowable is NULL and LastThrownObject is not, use lastThrownObject. - // In current (June 05) implementation, this is only used to pass to - // NotifyAppDomainsOfUnhandledException, which needs to get a throwable - // from somewhere, with which to notify the AppDomains. - useLastThrownObject = TRUE; - } - - return useLastThrownObject; -} - // // COMUnhandledExceptionFilter is used to catch all unhandled exceptions. // The debugger will either handle the exception, attach a debugger, or @@ -3953,7 +3919,7 @@ LONG InternalUnhandledExceptionFilter_Worker( BOOL useLastThrownObject = FALSE; if (!pParam->fIgnore && (pParam->pThread != NULL)) { - useLastThrownObject = UpdateCurrentThrowable(pParam->pExceptionInfo->ExceptionRecord); + useLastThrownObject = pParam->pThread->IsThrowableNull() && !pParam->pThread->IsLastThrownObjectNull(); } #ifdef DEBUGGING_SUPPORTED @@ -3988,29 +3954,13 @@ LONG InternalUnhandledExceptionFilter_Worker( #endif // !TARGET_UNIX // Send notifications to the AppDomains. - NotifyAppDomainsOfUnhandledException(pParam->pExceptionInfo, NULL, useLastThrownObject); + NotifyAppDomainsOfUnhandledException(pParam->pExceptionInfo, useLastThrownObject); } else { LOG((LF_EH, LL_INFO100, "InternalUnhandledExceptionFilter_Worker: Not collecting bucket information as thread object does not exist\n")); } - // AppDomain.UnhandledException event could have thrown an exception that would have gone unhandled in managed code. - // The runtime swallows all such exceptions. Hence, if we are not using LastThrownObject and the current LastThrownObject - // is not the same as the one in active exception tracker (if available), then update the last thrown object. - if ((pParam->pThread != NULL) && (!useLastThrownObject)) - { - GCX_COOP_NO_DTOR(); - - OBJECTREF oThrowable = pParam->pThread->GetThrowable(); - if ((oThrowable != NULL) && (pParam->pThread->LastThrownObject() != oThrowable)) - { - pParam->pThread->SafeSetLastThrownObject(oThrowable); - LOG((LF_EH, LL_INFO100, "InternalUnhandledExceptionFilter_Worker: Resetting the LastThrownObject as it appears to have changed.\n")); - } - - GCX_COOP_NO_DTOR_END(); - } // Launch Watson and see if we want to debug the process // @@ -4546,7 +4496,6 @@ DefaultCatchHandler(PEXCEPTION_POINTERS pExceptionPointers, //****************************************************************************** void NotifyAppDomainsOfUnhandledException( PEXCEPTION_POINTERS pExceptionPointers, - OBJECTREF *pThrowableIn, BOOL useLastThrownObject) { CONTRACTL @@ -4580,11 +4529,7 @@ void NotifyAppDomainsOfUnhandledException( OBJECTREF throwable; - if (pThrowableIn != NULL) - { - throwable = *pThrowableIn; - } - else if (useLastThrownObject) + if (useLastThrownObject) { throwable = pThread->LastThrownObject(); } @@ -8194,7 +8139,7 @@ void SetupInitialThrowBucketDetails(UINT_PTR adjustedIp) #ifdef _DEBUG // Under OOM scenarios, its possible that when we are raising a threadabort, // the throwable may get converted to preallocated OOM object when RaiseTheExceptionInternalOnly - // invokes Thread::SafeSetLastThrownObject. We check if this is the current case and use it in + // invokes Thread::SetLastThrownObject. We check if this is the current case and use it in // our validation below. BOOL fIsPreallocatedOOMExceptionForTA = FALSE; if ((!fIsThreadAbortException) && pUEWatsonBucketTracker->CapturedForThreadAbort()) diff --git a/src/coreclr/vm/excep.h b/src/coreclr/vm/excep.h index 33f7444ae220a7..f355c8fd1b18d4 100644 --- a/src/coreclr/vm/excep.h +++ b/src/coreclr/vm/excep.h @@ -113,7 +113,6 @@ BOOL IsExceptionOfType(RuntimeExceptionKind reKind, OBJECTREF *pThrowable); BOOL IsExceptionOfType(RuntimeExceptionKind reKind, Exception *pException); BOOL IsUncatchable(OBJECTREF *pThrowable); VOID FixupOnRethrow(Thread *pCurThread, EXCEPTION_POINTERS *pExceptionPointers); -BOOL UpdateCurrentThrowable(PEXCEPTION_RECORD pExceptionRecord); BOOL IsStackOverflowException(Thread* pThread, EXCEPTION_RECORD* pExceptionRecord); void WrapNonCompliantException(OBJECTREF *ppThrowable); OBJECTREF PossiblyUnwrapThrowable(OBJECTREF throwable, Assembly *pAssembly); diff --git a/src/coreclr/vm/exceptionhandling.cpp b/src/coreclr/vm/exceptionhandling.cpp index 6473f4d069958c..0f0f8b46f2c6a3 100644 --- a/src/coreclr/vm/exceptionhandling.cpp +++ b/src/coreclr/vm/exceptionhandling.cpp @@ -3186,12 +3186,9 @@ void CallCatchFunclet(OBJECTREF throwable, BYTE* pHandlerIP, REGDISPLAY* pvRegDi if (!pThread->GetExceptionState()->IsExceptionInProgress()) { - pThread->SafeSetLastThrownObject(NULL); + pThread->SetLastThrownObject(NULL); } - // Sync managed exception state, for the managed thread, based upon any active exception tracker - pThread->SyncManagedExceptionState(false); - ExInfo::UpdateNonvolatileRegisters(pvRegDisplay->pCurrentContext, pvRegDisplay, FALSE); if (pHandlerIP != NULL) { @@ -3596,7 +3593,6 @@ static void NotifyExceptionPassStarted(StackFrameIterator *pThis, Thread *pThrea if (pExInfo->m_passNumber == 1) { GCX_COOP(); - pThread->SafeSetThrowables(pExInfo->m_exception); FirstChanceExceptionNotification(); EEToProfilerExceptionInterfaceWrapper::ExceptionThrown(pThread); } diff --git a/src/coreclr/vm/exinfo.cpp b/src/coreclr/vm/exinfo.cpp index 76d02424215de6..7c9596a6ea5874 100644 --- a/src/coreclr/vm/exinfo.cpp +++ b/src/coreclr/vm/exinfo.cpp @@ -98,13 +98,21 @@ void ExInfo::ReleaseResources() // static void ExInfo::PopExInfos(Thread *pThread, void *targetSp) { + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_COOPERATIVE; + } + CONTRACTL_END; + STRESS_LOG1(LF_EH, LL_INFO100, "Popping ExInfos below SP=%p\n", targetSp); ExInfo *pExInfo = (PTR_ExInfo)pThread->GetExceptionState()->GetCurrentExceptionTracker(); #if defined(DEBUGGING_SUPPORTED) DWORD_PTR dwInterceptStackFrame = 0; - // This method may be called on an unmanaged thread, in which case no interception can be done. + // If there is no current ExInfo, there is nothing to inspect for interception. if (pExInfo) { ThreadExceptionState* pExState = pThread->GetExceptionState(); @@ -131,6 +139,14 @@ void ExInfo::PopExInfos(Thread *pThread, void *targetSp) } #endif // DEBUGGING_SUPPORTED + // Set LTO from the exception being destroyed so that post-ExInfo consumers + // (EX_CATCH via CLRLastThrownObjectException, ProcessCLRException bridging) + // can find the exception object after the ExInfo is gone. + if (pExInfo->m_exception != NULL) + { + pThread->SetLastThrownObject(pExInfo->m_exception); + } + pExInfo->ReleaseResources(); pExInfo = (PTR_ExInfo)pExInfo->m_pPrevNestedInfo; } diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index ebf0533da70904..9a04ef77307db1 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -10479,83 +10479,6 @@ int32_t * CEEInfo::getAddrOfCaptureThreadGlobal(void **ppIndirection) return result; } -// This code is called if FilterException chose to handle the exception. -void CEEInfo::HandleException(struct _EXCEPTION_POINTERS *pExceptionPointers) -{ - CONTRACTL { - NOTHROW; - GC_NOTRIGGER; - } CONTRACTL_END; - - JIT_TO_EE_TRANSITION_LEAF(); - - if (IsComPlusException(pExceptionPointers->ExceptionRecord)) - { - GCX_COOP(); - - // This is actually the LastThrown exception object. - OBJECTREF throwable = CLRException::GetThrowableFromExceptionRecord(pExceptionPointers->ExceptionRecord); - - if (throwable != NULL) - { - struct - { - OBJECTREF oLastThrownObject; - OBJECTREF oCurrentThrowable; - } _gc; - - ZeroMemory(&_gc, sizeof(_gc)); - - PTR_Thread pCurThread = GetThread(); - - // Setup the throwables - _gc.oLastThrownObject = throwable; - - // This will be NULL if no managed exception is active. Otherwise, - // it will reference the active throwable. - _gc.oCurrentThrowable = pCurThread->GetThrowable(); - - GCPROTECT_BEGIN(_gc); - - // JIT does not use or reference managed exceptions at all and simply swallows them, - // or lets them fly through so that they will either get caught in managed code, the VM - // or will go unhandled. - // - // Blind swallowing of managed exceptions can break the semantic of "which exception handler" - // gets to process the managed exception first. The expected handler is managed code exception - // handler (e.g. COMPlusFrameHandler on x86 and ProcessCLRException on 64bit) which will setup - // the exception tracker for the exception that will enable the expected sync between the - // LastThrownObject (LTO), setup in RaiseTheExceptionInternalOnly, and the exception tracker. - // - // However, JIT can break this by swallowing the managed exception before managed code exception - // handler gets a chance to setup an exception tracker for it. Since there is no cleanup - // done for the swallowed exception as part of the unwind (because no exception tracker may have been setup), - // we need to reset the LTO, if it is out of sync from the active throwable. - // - // Hence, check if the LastThrownObject and active-exception throwable are in sync or not. - // If not, bring them in sync. - // - // Example - // ------- - // It is possible that an exception was already in progress and while processing it (e.g. - // invoking finally block), we invoked JIT that had another managed exception @ JIT-EE transition boundary - // that is swallowed by the JIT before managed code exception handler sees it. This breaks the sync between - // LTO and the active exception in the exception tracker. - if (_gc.oCurrentThrowable != _gc.oLastThrownObject) - { - // Update the LTO. - // - // Note: Incase of OOM, this will get set to OOM instance. - pCurThread->SafeSetLastThrownObject(_gc.oCurrentThrowable); - } - - GCPROTECT_END(); - } - } - - EE_TO_JIT_TRANSITION_LEAF(); -} - CORINFO_MODULE_HANDLE CEEInfo::embedModuleHandle(CORINFO_MODULE_HANDLE handle, void **ppIndirection) { diff --git a/src/coreclr/vm/jitinterface.h b/src/coreclr/vm/jitinterface.h index 955923c747fe4b..c761d6e18514a5 100644 --- a/src/coreclr/vm/jitinterface.h +++ b/src/coreclr/vm/jitinterface.h @@ -303,7 +303,6 @@ class CEEInfo : public ICorJitInfo TypeHandle GetTypeFromContext(CORINFO_CONTEXT_HANDLE context); void GetTypeContext(CORINFO_CONTEXT_HANDLE context, SigTypeContext* pTypeContext); - void HandleException(struct _EXCEPTION_POINTERS* pExceptionPointers); public: #include "icorjitinfoimpl_generated.h" uint32_t getClassAttribsInternal (CORINFO_CLASS_HANDLE cls); diff --git a/src/coreclr/vm/threads.cpp b/src/coreclr/vm/threads.cpp index 5e1f114f56d5a4..9d9cf0ff092889 100644 --- a/src/coreclr/vm/threads.cpp +++ b/src/coreclr/vm/threads.cpp @@ -2271,7 +2271,7 @@ Thread::~Thread() if (!IsAtProcessExit()) { // Destroy any handles that we're using to hold onto exception objects - SafeSetThrowables(NULL); + SetLastThrownObject(NULL); DestroyShortWeakHandle(m_ExposedObject); DestroyStrongHandle(m_StrongHndToExposedObject); @@ -2635,7 +2635,7 @@ void Thread::OnThreadTerminate(BOOL holdingLock) GCX_COOP(); // Destroy the LastThrown handle (and anything that violates the above assert). - SafeSetThrowables(NULL); + SetLastThrownObject(NULL); // Free loader allocator structures related to this thread FreeLoaderAllocatorHandlesForTLSData(this); @@ -3107,11 +3107,11 @@ void Thread::SetExposedObject(OBJECTREF exposed) // IncExternalCount(); } -void Thread::SetLastThrownObject(OBJECTREF throwable, BOOL isUnhandled) +OBJECTREF Thread::SetLastThrownObject(OBJECTREF throwable, BOOL isUnhandled) { CONTRACTL { - if ((throwable == NULL) || CLRException::IsPreallocatedExceptionObject(throwable)) NOTHROW; else THROWS; // From CreateHandle + NOTHROW; GC_NOTRIGGER; if (throwable == NULL) MODE_ANY; else MODE_COOPERATIVE; } @@ -3138,203 +3138,62 @@ void Thread::SetLastThrownObject(OBJECTREF throwable, BOOL isUnhandled) // a new handle below. } - if (throwable != NULL) - { - _ASSERTE(this == GetThread()); - - // Non-compliant exceptions are always wrapped. - // The use of the ExceptionNative:: helper here (rather than the global ::IsException helper) - // is hokey, but we need a GC_NOTRIGGER version and it's only for an ASSERT. - _ASSERTE(IsException(throwable->GetMethodTable())); - - // If we're tracking one of the preallocated exception objects, then just use the global handle that - // matches it rather than creating a new one. - if (CLRException::IsPreallocatedExceptionObject(throwable)) - { - m_LastThrownObjectHandle = CLRException::GetPreallocatedHandleForObject(throwable); - } - else - { - m_LastThrownObjectHandle = AppDomain::GetCurrentDomain()->CreateHandle(throwable); - } - - _ASSERTE(m_LastThrownObjectHandle != NULL); - m_ltoIsUnhandled = isUnhandled; - } - else + if (throwable == NULL) { m_ltoIsUnhandled = FALSE; + return NULL; } -} - -void Thread::SetSOForLastThrownObject() -{ - CONTRACTL - { - NOTHROW; - GC_NOTRIGGER; - MODE_COOPERATIVE; - CANNOT_TAKE_LOCK; - } - CONTRACTL_END; - - - // If we are saving stack overflow exception, we can just null out the current handle. - // The current domain is going to be unloaded or the process is going to be killed, so - // we will not leak a handle. - m_LastThrownObjectHandle = CLRException::GetPreallocatedStackOverflowExceptionHandle(); -} - -// -// This is a nice wrapper for SetLastThrownObject which catches any exceptions caused by not being able to create -// the handle for the throwable, and setting the last thrown object to the preallocated out of memory exception -// instead. -// -OBJECTREF Thread::SafeSetLastThrownObject(OBJECTREF throwable) -{ - CONTRACTL - { - NOTHROW; - GC_NOTRIGGER; - if (throwable == NULL) MODE_ANY; else MODE_COOPERATIVE; - } - CONTRACTL_END; - - // We return the original throwable if nothing goes wrong. - OBJECTREF ret = throwable; - EX_TRY - { - // Try to set the throwable. - SetLastThrownObject(throwable); - } - EX_CATCH - { - // If it didn't work, then set the last thrown object to the preallocated OOM exception, and return that - // object instead of the original throwable. - ret = CLRException::GetPreallocatedOutOfMemoryException(); - SetLastThrownObject(ret); - } - EX_END_CATCH + _ASSERTE(this == GetThread()); - return ret; -} + // Non-compliant exceptions are always wrapped. + // The use of the ExceptionNative:: helper here (rather than the global ::IsException helper) + // is hokey, but we need a GC_NOTRIGGER version and it's only for an ASSERT. + _ASSERTE(IsException(throwable->GetMethodTable())); -// -// This is a nice wrapper for updating the last thrown object handle, which catches any exceptions caused by not -// being able to create the handle for the throwable, and falls back to the preallocated out of memory exception -// for the last thrown object instead. The throwable itself is stored directly in ExInfo::m_exception by managed -// EH code, so this helper only updates the last thrown object state. -// -OBJECTREF Thread::SafeSetThrowables(OBJECTREF throwable, - BOOL isUnhandled) -{ - CONTRACTL + // If we're tracking one of the preallocated exception objects, then just use the global handle that + // matches it rather than creating a new one. + if (CLRException::IsPreallocatedExceptionObject(throwable)) { - NOTHROW; - GC_NOTRIGGER; - if (throwable == NULL) MODE_ANY; else MODE_COOPERATIVE; + m_LastThrownObjectHandle = CLRException::GetPreallocatedHandleForObject(throwable); } - CONTRACTL_END; - - // We return the original throwable if nothing goes wrong. - OBJECTREF ret = throwable; - - EX_TRY + else { - // The exception object is stored directly in ExInfo::m_exception by managed EH code, - // so we only need to update the last thrown object handle here. - if (LastThrownObject() != throwable) + EX_TRY { - SetLastThrownObject(throwable); + m_LastThrownObjectHandle = AppDomain::GetCurrentDomain()->CreateHandle(throwable); } - - if (isUnhandled) + EX_CATCH { - MarkLastThrownObjectUnhandled(); + // If we can't allocate a handle for the throwable, fall back to the preallocated OOM exception + // and return it so the caller can use it in place of the original throwable. + throwable = CLRException::GetPreallocatedOutOfMemoryException(); + m_LastThrownObjectHandle = CLRException::GetPreallocatedHandleForObject(throwable); } - } - EX_CATCH - { - // If we can't create a handle, set the last thrown object to the preallocated OOM exception. - ret = CLRException::GetPreallocatedOutOfMemoryException(); - - SetLastThrownObject(ret, isUnhandled); - } - EX_END_CATCH - - - return ret; -} - -// This method will sync the managed exception state to be in sync with the topmost active exception -// for a given thread -void Thread::SyncManagedExceptionState(bool fIsDebuggerThread) -{ - CONTRACTL - { - NOTHROW; - GC_NOTRIGGER; - MODE_ANY; - } - CONTRACTL_END; - - { - GCX_COOP(); - - // Syncup the LastThrownObject on the managed thread - SafeUpdateLastThrownObject(); - } -} - -void Thread::SetLastThrownObjectHandle(OBJECTHANDLE h) -{ - CONTRACTL - { - NOTHROW; - GC_NOTRIGGER; - MODE_COOPERATIVE; - } - CONTRACTL_END; - - if (m_LastThrownObjectHandle != NULL && - !CLRException::IsPreallocatedExceptionHandle(m_LastThrownObjectHandle)) - { - DestroyHandle(m_LastThrownObjectHandle); + EX_END_CATCH } - m_LastThrownObjectHandle = h; + _ASSERTE(m_LastThrownObjectHandle != NULL); + m_ltoIsUnhandled = isUnhandled; + return throwable; } -// -// Create a duplicate handle of the current throwable and set the last thrown object to that. This ensures that the -// last thrown object and the current throwable have handles that are in the same app domain. -// -void Thread::SafeUpdateLastThrownObject(void) +void Thread::SetSOForLastThrownObject() { CONTRACTL { NOTHROW; GC_NOTRIGGER; MODE_COOPERATIVE; + CANNOT_TAKE_LOCK; } CONTRACTL_END; - OBJECTREF throwable = GetExceptionState()->GetThrowable(); - if (throwable != NULL) - { - EX_TRY - { - SetLastThrownObject(throwable); - } - EX_CATCH - { - // If we can't create a handle, set the last thrown object to the preallocated OOM exception. - SafeSetThrowables(CLRException::GetPreallocatedOutOfMemoryException()); - } - EX_END_CATCH - } + // If we are saving stack overflow exception, we can just null out the current handle. + // The current domain is going to be unloaded or the process is going to be killed, so + // we will not leak a handle. + m_LastThrownObjectHandle = CLRException::GetPreallocatedStackOverflowExceptionHandle(); } // Background threads must be counted, because the EE should shut down when the diff --git a/src/coreclr/vm/threads.h b/src/coreclr/vm/threads.h index 195a01434d8ed3..c98b1404058c2d 100644 --- a/src/coreclr/vm/threads.h +++ b/src/coreclr/vm/threads.h @@ -1470,8 +1470,6 @@ class Thread } - void SyncManagedExceptionState(bool fIsDebuggerThread); - //--------------------------------------------------------------- // Per-thread information used by handler //--------------------------------------------------------------- @@ -2613,10 +2611,26 @@ class Thread friend class EEDbgInterfaceImpl; private: - // Stores the most recently thrown exception. We need to have a handle in case a GC occurs before - // we catch so we don't lose the object. Having a static allows others to catch outside of CLR w/o leaking - // a handler and allows rethrow outside of CLR too. - // Differs from m_pThrowable in that it doesn't stack on nested exceptions. + // m_LastThrownObjectHandle (LTO) holds the most recently thrown exception as + // an OBJECTHANDLE. It serves two purposes: + // + // 1. Bridging: When a managed exception propagates through native frames via + // SEH (RaiseTheExceptionInternalOnly -> ProcessCLRException), LTO carries + // the exception object across the gap where no ExInfo exists yet. + // + // 2. Post-ExInfo access: After an ExInfo is popped (e.g. in EX_CATCH blocks), + // LTO is the only way to retrieve the exception object. This is used by + // CLRLastThrownObjectException::CreateThrowable and other EX_CATCH consumers. + // + // LTO is NOT kept in sync with ExInfo::m_exception during active exception + // dispatch. While an ExInfo is alive, m_exception is the source of truth - + // use GetThrowable() or GetThrowableAsPseudoHandle() to read it. LTO is set + // lazily by PopExInfos just before each ExInfo is destroyed, and by + // RaiseTheExceptionInternalOnly before the ExInfo is created. + // + // LTO may be stale during active dispatch. Readers that need the current + // exception should call GetThrowable() first and fall back to LastThrownObject() + // only when GetThrowable() returns NULL. OBJECTHANDLE m_LastThrownObjectHandle; // Unsafe to use directly. Use accessors instead. // Indicates that the throwable in m_lastThrownObjectHandle should be treated as @@ -2653,9 +2667,12 @@ class Thread return m_LastThrownObjectHandle; } - void SetLastThrownObject(OBJECTREF throwable, BOOL isUnhandled = FALSE); + // Sets the last thrown object. If the throwable cannot be tracked due to OOM, sets the + // last thrown object to the preallocated OOM exception and returns it instead of the + // original throwable. + OBJECTREF SetLastThrownObject(OBJECTREF throwable, BOOL isUnhandled = FALSE); + void SetSOForLastThrownObject(); - OBJECTREF SafeSetLastThrownObject(OBJECTREF throwable); // Inidcates that the last thrown object is now treated as unhandled void MarkLastThrownObjectUnhandled() @@ -2671,10 +2688,6 @@ class Thread return m_ltoIsUnhandled; } - void SafeUpdateLastThrownObject(void); - OBJECTREF SafeSetThrowables(OBJECTREF pThrowable, - BOOL isUnhandled = FALSE); - bool IsLastThrownObjectStackOverflowException() { LIMITED_METHOD_CONTRACT; @@ -2693,8 +2706,6 @@ class Thread void ClearThreadCurrNotification(); private: - void SetLastThrownObjectHandle(OBJECTHANDLE h); - ThreadExceptionState m_ExceptionState; private: