vcl/win/window/salframe.cxx | 52 +++- winaccessibility/inc/AccEventListener.hxx | 2 winaccessibility/inc/AccObject.hxx | 4 winaccessibility/inc/AccObjectWinManager.hxx | 9 winaccessibility/source/service/AccEventListener.cxx | 9 winaccessibility/source/service/AccObject.cxx | 9 winaccessibility/source/service/AccObjectWinManager.cxx | 167 +++++++++------- winaccessibility/source/service/msaaservice_impl.cxx | 2 8 files changed, 153 insertions(+), 101 deletions(-)
New commits: commit 6c6ff04a82d50691aaa5d084660b606b5c137be1 Author: Michael Stahl <[email protected]> AuthorDate: Tue Jun 13 12:30:44 2023 +0200 Commit: Michael Stahl <[email protected]> CommitDate: Wed Jun 14 10:47:18 2023 +0200 tdf#155794 vcl: handle WM_GETOBJECT without SolarMutex SalFrameWndProc() handles WM_GETOBJECT by acquiring SolarMutex and calling ImplHandleGetObject(), which again acquires the SolarMutex inside Application::SetSettings(). This was introduced with commit db214684057e3ff2fa32d57c00507309dd6c24d6 due to thread-safety crashes but it turns out that it can be problematic. When loading a document on a non-main thread, WinSalFrame::SetTitle() calls SetWindowTextW which is equivalent to SendMessage(WM_SETTEXT), while holding SolarMutex, and if the main thread doesn't finish processing it then that's a deadlock. Typically Desktop::Main() has already created the mxAccessBridge, so ImplHandleGetObject() most likely doesn't need to do it, so just skip the Settings code there in case the SolarMutex is locked by another thread. In case the SolarMutex is locked by another thread, do an unsafe read of ImplGetSVData()->mxAccessBridge - this should work until ImplSVData is deleted, by which time no Windows should exist anymore that could be receiving messages. This fixes part of the problem, winaccessibility also needs to stop using SolarMutex. Change-Id: I62b027ad06d2c3eb06a5f64b052a4acd0908f79c Reviewed-on: https://gerrit.libreoffice.org/c/core/+/152958 Tested-by: Jenkins Reviewed-by: Michael Stahl <[email protected]> (cherry picked from commit 889c12e98e04edb4bc25b86bf16b8cf1d9b68420) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/152986 diff --git a/vcl/win/window/salframe.cxx b/vcl/win/window/salframe.cxx index e0a99d8e8779..2e60f38c1f11 100644 --- a/vcl/win/window/salframe.cxx +++ b/vcl/win/window/salframe.cxx @@ -5395,30 +5395,43 @@ static void ImplHandleIMENotify( HWND hWnd, WPARAM wParam ) static bool ImplHandleGetObject(HWND hWnd, LPARAM lParam, WPARAM wParam, LRESULT & nRet) { - if (!Application::GetSettings().GetMiscSettings().GetEnableATToolSupport()) + uno::Reference<accessibility::XMSAAService> xMSAA; + if (ImplSalYieldMutexTryToAcquire()) { - // IA2 should be enabled automatically - AllSettings aSettings = Application::GetSettings(); - MiscSettings aMisc = aSettings.GetMiscSettings(); - aMisc.SetEnableATToolSupport(true); - // The above is enough, since aMisc changes the same shared ImplMiscData as used in global - // settings, so no need to call aSettings.SetMiscSettings and Application::SetSettings - if (!Application::GetSettings().GetMiscSettings().GetEnableATToolSupport()) - return false; // locked down somehow ? - } + { + // IA2 should be enabled automatically + AllSettings aSettings = Application::GetSettings(); + MiscSettings aMisc = aSettings.GetMiscSettings(); + aMisc.SetEnableATToolSupport(true); + // The above is enough, since aMisc changes the same shared ImplMiscData as used in global + // settings, so no need to call aSettings.SetMiscSettings and Application::SetSettings + + if (!Application::GetSettings().GetMiscSettings().GetEnableATToolSupport()) + return false; // locked down somehow ? + } - ImplSVData* pSVData = ImplGetSVData(); + ImplSVData* pSVData = ImplGetSVData(); - // Make sure to launch Accessibility only the following criteria are satisfied - // to avoid RFT interrupts regular accessibility processing - if ( !pSVData->mxAccessBridge.is() ) - { - if( !InitAccessBridge() ) - return false; + // Make sure to launch Accessibility only the following criteria are satisfied + // to avoid RFT interrupts regular accessibility processing + if ( !pSVData->mxAccessBridge.is() ) + { + if( !InitAccessBridge() ) + return false; + } + xMSAA.set(pSVData->mxAccessBridge, uno::UNO_QUERY); + ImplSalYieldMutexRelease(); + } + else + { // tdf#155794: access without locking: hopefully this should be fine + // as the bridge is typically inited in Desktop::Main() already and the + // WM_GETOBJECT is received only on the main thread and by the time in + // VCL shutdown when ImplSvData dies there should not be Windows any + // more that could receive messages. + xMSAA.set(ImplGetSVData()->mxAccessBridge, uno::UNO_QUERY); } - uno::Reference< accessibility::XMSAAService > xMSAA( pSVData->mxAccessBridge, uno::UNO_QUERY ); if ( xMSAA.is() ) { sal_Int32 lParam32 = static_cast<sal_Int32>(lParam); @@ -5963,12 +5976,11 @@ static LRESULT CALLBACK SalFrameWndProc( HWND hWnd, UINT nMsg, WPARAM wParam, LP break; case WM_GETOBJECT: - ImplSalYieldMutexAcquireWithWait(); + // tdf#155794: this must complete without taking SolarMutex if ( ImplHandleGetObject( hWnd, lParam, wParam, nRet ) ) { rDef = false; } - ImplSalYieldMutexRelease(); break; case WM_APPCOMMAND: commit b54b07d7282e500018226a2976104d5f5b524537 Author: Michael Stahl <[email protected]> AuthorDate: Mon Jun 12 20:03:14 2023 +0200 Commit: Michael Stahl <[email protected]> CommitDate: Wed Jun 14 10:47:04 2023 +0200 tdf#155794 winaccessibility: no SolarMutex in getAccObjectPtr() MSAAServiceImpl::getAccObjectPtr() is called when processing WM_GETOBJECT messages, and this can happen (at least when NVDA is active) during processing SendMessages. When loading a document on a non-main thread, WinSalFrame::SetTitle() calls SetWindowTextW which is equivalent to SendMessage(WM_SETTEXT), while holding SolarMutex, and if the main thread doesn't finish processing it then that's a deadlock. Introduce a new mutex in AccObjectWinManager and use it to guard the 2 members that getAccObjectPtr() reads, while keeping the rest of winaccessibility with the SolarMutex, as the UNO services may be called on any thread. This fixes part of the problem, VCL also needs to stop using SolarMutex. Change-Id: I6df5889fd76f59146b4b0b1e5f4513232f8ab867 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/152957 Tested-by: Jenkins Reviewed-by: Michael Stahl <[email protected]> (cherry picked from commit ac1099443e70eefbd8fdd7dd3705fff08a9c75b8) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/152985 diff --git a/winaccessibility/inc/AccEventListener.hxx b/winaccessibility/inc/AccEventListener.hxx index 2d2875dfb060..f134981b20a7 100644 --- a/winaccessibility/inc/AccEventListener.hxx +++ b/winaccessibility/inc/AccEventListener.hxx @@ -73,7 +73,7 @@ public: //get the accessible parent's role virtual short GetParentRole(); - void RemoveMeFromBroadcaster(); + void RemoveMeFromBroadcaster(bool isNotifyDestroy); }; /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/winaccessibility/inc/AccObject.hxx b/winaccessibility/inc/AccObject.hxx index 3e259592e5be..53b1109b0d0e 100644 --- a/winaccessibility/inc/AccObject.hxx +++ b/winaccessibility/inc/AccObject.hxx @@ -50,7 +50,7 @@ private: short m_accRole; long m_resID; HWND m_pParantID; - IMAccessible* m_pIMAcc; + IMAccessible* const m_pIMAcc; // AccObjectManager::GetTopWindowIMAccessible relies on this being const AccObject* m_pParentObj; IAccChildList m_childrenList; ::rtl::Reference<AccEventListener> m_pListener; @@ -87,7 +87,7 @@ public: void SetParentHWND(HWND hWnd);//need to set top window handle when send event to AT HWND GetParentHWND(); - void SetListener(::rtl::Reference<AccEventListener> const& pListener); + ::rtl::Reference<AccEventListener> SetListener(::rtl::Reference<AccEventListener> const& pListener); AccEventListener* getListener(); void SetParentObj(AccObject* pParentAccObj); diff --git a/winaccessibility/inc/AccObjectWinManager.hxx b/winaccessibility/inc/AccObjectWinManager.hxx index 5c162dfed1ea..86a75c80fad7 100644 --- a/winaccessibility/inc/AccObjectWinManager.hxx +++ b/winaccessibility/inc/AccObjectWinManager.hxx @@ -20,6 +20,8 @@ #pragma once #include <map> +#include <mutex> + #if !defined WIN32_LEAN_AND_MEAN # define WIN32_LEAN_AND_MEAN #endif @@ -57,6 +59,9 @@ private: typedef std::map<const HWND, css::accessibility::XAccessible* > XHWNDToDocumentHash; + // guard any access to XIdAccList and HwndXAcc + std::recursive_mutex m_Mutex; + //XAccessible to AccObject XIdToAccObjHash XIdAccList; @@ -80,11 +85,11 @@ private: long ImpleGenerateResID(); AccObject* GetAccObjByXAcc( css::accessibility::XAccessible* pXAcc); - AccObject* GetTopWindowAccObj(HWND hWnd); + IMAccessible* GetTopWindowIMAccessible(HWND hWnd); css::accessibility::XAccessible* GetAccDocByHWND(HWND hWnd); - static void DeleteAccListener( AccObject* pAccObj ); + static rtl::Reference<AccEventListener> DeleteAccListener(AccObject* pAccObj); static void InsertAccChildNode(AccObject* pCurObj,AccObject* pParentObj,HWND pWnd); static void DeleteAccChildNode(AccObject* pChild); void DeleteFromHwndXAcc(css::accessibility::XAccessible const * pXAcc ); diff --git a/winaccessibility/source/service/AccEventListener.cxx b/winaccessibility/source/service/AccEventListener.cxx index b6b4e71f3aaa..a8f8e705fd2c 100644 --- a/winaccessibility/source/service/AccEventListener.cxx +++ b/winaccessibility/source/service/AccEventListener.cxx @@ -216,7 +216,7 @@ short AccEventListener::GetParentRole() /** * remove the listener from accessible object */ -void AccEventListener::RemoveMeFromBroadcaster() +void AccEventListener::RemoveMeFromBroadcaster(bool const isNotifyDestroy) { try { @@ -237,7 +237,10 @@ void AccEventListener::RemoveMeFromBroadcaster() catch (Exception const&) { // may throw if it's already disposed - ignore that } - pAgent->NotifyDestroy(m_xAccessible.get()); + if (isNotifyDestroy) + { + pAgent->NotifyDestroy(m_xAccessible.get()); + } m_xAccessible.clear(); // release cyclic reference } catch (...) @@ -253,7 +256,7 @@ void AccEventListener::disposing(const css::lang::EventObject& /*Source*/) { SolarMutexGuard g; - RemoveMeFromBroadcaster(); + RemoveMeFromBroadcaster(true); } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/winaccessibility/source/service/AccObject.cxx b/winaccessibility/source/service/AccObject.cxx index d1e805ce2428..68dcc10105f2 100644 --- a/winaccessibility/source/service/AccObject.cxx +++ b/winaccessibility/source/service/AccObject.cxx @@ -162,7 +162,7 @@ AccObject::AccObject(XAccessible* pAcc, AccObjectManagerAgent* pAgent, AccEventListener* pListener) : m_resID (0), m_pParantID (nullptr), - m_pIMAcc (nullptr), + m_pIMAcc (UAccCOMCreateInstance()), m_pParentObj(nullptr), m_pListener (pListener), m_xAccRef( pAcc ) @@ -186,7 +186,6 @@ AccObject::AccObject(XAccessible* pAcc, AccObjectManagerAgent* pAgent, */ AccObject::~AccObject() { - m_pIMAcc = nullptr; m_xAccRef = nullptr; m_xAccActionRef = nullptr; m_xAccContextRef = nullptr; @@ -256,8 +255,6 @@ void AccObject::UpdateValidWindow() */ void AccObject::ImplInitializeCreateObj() { - m_pIMAcc = UAccCOMCreateInstance(); - assert(m_pIMAcc); } @@ -1048,9 +1045,11 @@ void AccObject::SetParentHWND(HWND hWnd) m_pParantID = hWnd; } -void AccObject::SetListener(rtl::Reference<AccEventListener> const& pListener) +rtl::Reference<AccEventListener> AccObject::SetListener(rtl::Reference<AccEventListener> const& pListener) { + rtl::Reference<AccEventListener> pRet(m_pListener); m_pListener = pListener; + return pRet; } AccEventListener* AccObject::getListener() diff --git a/winaccessibility/source/service/AccObjectWinManager.cxx b/winaccessibility/source/service/AccObjectWinManager.cxx index b0ee01525de9..c8e5c7ac936b 100644 --- a/winaccessibility/source/service/AccObjectWinManager.cxx +++ b/winaccessibility/source/service/AccObjectWinManager.cxx @@ -73,8 +73,12 @@ AccObjectWinManager::AccObjectWinManager( AccObjectManagerAgent* Agent ): */ AccObjectWinManager::~AccObjectWinManager() { - XIdAccList.clear(); - HwndXAcc.clear(); + { + std::scoped_lock l(m_Mutex); + + XIdAccList.clear(); + HwndXAcc.clear(); + } XResIdAccList.clear(); XHWNDDocList.clear(); } @@ -94,13 +98,7 @@ AccObjectWinManager::Get_ToATInterface(HWND hWnd, long lParam, WPARAM wParam) if(lParam == OBJID_CLIENT ) { - AccObject* topWindowAccObj = GetTopWindowAccObj(hWnd); - if(topWindowAccObj) - { - pRetIMAcc = topWindowAccObj->GetIMAccessible(); - if(pRetIMAcc) - pRetIMAcc->AddRef();//increase COM reference count - } + pRetIMAcc = GetTopWindowIMAccessible(hWnd); } if ( pRetIMAcc && lParam == OBJID_CLIENT ) @@ -122,6 +120,8 @@ AccObject* AccObjectWinManager::GetAccObjByXAcc( XAccessible* pXAcc) if( pXAcc == nullptr) return nullptr; + std::scoped_lock l(m_Mutex); + XIdToAccObjHash::iterator pIndTemp = XIdAccList.find( pXAcc ); if ( pIndTemp == XIdAccList.end() ) return nullptr; @@ -134,13 +134,26 @@ AccObject* AccObjectWinManager::GetAccObjByXAcc( XAccessible* pXAcc) * @param hWnd, top window handle * @return pointer to AccObject */ -AccObject* AccObjectWinManager::GetTopWindowAccObj(HWND hWnd) +IMAccessible * AccObjectWinManager::GetTopWindowIMAccessible(HWND hWnd) { + std::scoped_lock l(m_Mutex); // tdf#155794 for HwndXAcc and XIdAccList + XHWNDToXAccHash::iterator iterResult =HwndXAcc.find(hWnd); if(iterResult == HwndXAcc.end()) return nullptr; XAccessible* pXAcc = iterResult->second; - return GetAccObjByXAcc(pXAcc); + AccObject *const pAccObject(GetAccObjByXAcc(pXAcc)); + if (!pAccObject) + { + return nullptr; + } + IMAccessible *const pRet(pAccObject->GetIMAccessible()); + if (!pRet) + { + return nullptr; + } + pRet->AddRef(); + return pRet; } /** @@ -391,6 +404,8 @@ void AccObjectWinManager::DeleteAccChildNode( AccObject* pObj ) */ void AccObjectWinManager::DeleteFromHwndXAcc(XAccessible const * pXAcc ) { + std::scoped_lock l(m_Mutex); + auto iter = std::find_if(HwndXAcc.begin(), HwndXAcc.end(), [&pXAcc](XHWNDToXAccHash::value_type& rEntry) { return rEntry.second == pXAcc; }); if (iter != HwndXAcc.end()) @@ -433,35 +448,47 @@ void AccObjectWinManager::DeleteAccObj( XAccessible* pXAcc ) { if( pXAcc == nullptr ) return; - XIdToAccObjHash::iterator temp = XIdAccList.find(pXAcc); - if( temp != XIdAccList.end() ) - { - ResIdGen.SetSub( temp->second.GetResID() ); - } - else - { - return; - } - AccObject& accObj = temp->second; - DeleteAccChildNode( &accObj ); - DeleteAccListener( &accObj ); - if( accObj.GetIMAccessible() ) + rtl::Reference<AccEventListener> pListener; + { - accObj.GetIMAccessible()->Release(); + std::scoped_lock l(m_Mutex); + + XIdToAccObjHash::iterator temp = XIdAccList.find(pXAcc); + if( temp != XIdAccList.end() ) + { + ResIdGen.SetSub( temp->second.GetResID() ); + } + else + { + return; + } + + AccObject& accObj = temp->second; + DeleteAccChildNode( &accObj ); + pListener = DeleteAccListener(&accObj); + accObj.NotifyDestroy(); + if( accObj.GetIMAccessible() ) + { + accObj.GetIMAccessible()->Release(); + } + size_t i = XResIdAccList.erase(accObj.GetResID()); + assert(i != 0); + (void) i; + DeleteFromHwndXAcc(pXAcc); + if (accObj.GetRole() == AccessibleRole::DOCUMENT || + accObj.GetRole() == AccessibleRole::DOCUMENT_PRESENTATION || + accObj.GetRole() == AccessibleRole::DOCUMENT_SPREADSHEET || + accObj.GetRole() == AccessibleRole::DOCUMENT_TEXT) + { + XHWNDDocList.erase(accObj.GetParentHWND()); + } + XIdAccList.erase(pXAcc); // note: this invalidates accObj so do it last! } - size_t i = XResIdAccList.erase(accObj.GetResID()); - assert(i != 0); - (void) i; - DeleteFromHwndXAcc(pXAcc); - if (accObj.GetRole() == AccessibleRole::DOCUMENT || - accObj.GetRole() == AccessibleRole::DOCUMENT_PRESENTATION || - accObj.GetRole() == AccessibleRole::DOCUMENT_SPREADSHEET || - accObj.GetRole() == AccessibleRole::DOCUMENT_TEXT) + if (pListener) { - XHWNDDocList.erase(accObj.GetParentHWND()); + pListener->RemoveMeFromBroadcaster(false); } - XIdAccList.erase(pXAcc); // note: this invalidates accObj so do it last! } /** @@ -469,13 +496,9 @@ void AccObjectWinManager::DeleteAccObj( XAccessible* pXAcc ) * @param pAccObj Accobject pointer. * @return */ -void AccObjectWinManager::DeleteAccListener( AccObject* pAccObj ) +rtl::Reference<AccEventListener> AccObjectWinManager::DeleteAccListener( AccObject* pAccObj ) { - AccEventListener* listener = pAccObj->getListener(); - if( listener==nullptr ) - return; - listener->RemoveMeFromBroadcaster(); - pAccObj->SetListener(nullptr); + return pAccObj->SetListener(nullptr); } /** @@ -568,29 +591,6 @@ void AccObjectWinManager::InsertAccChildNode( AccObject* pCurObj, AccObject* pPa */ bool AccObjectWinManager::InsertAccObj( XAccessible* pXAcc,XAccessible* pParentXAcc,HWND pWnd ) { - XIdToAccObjHash::iterator itXacc = XIdAccList.find( pXAcc ); - if (itXacc != XIdAccList.end() ) - { - short nCurRole =GetRole(pXAcc); - if (AccessibleRole::SHAPE == nCurRole) - { - AccObject &objXacc = itXacc->second; - AccObject *pObjParent = objXacc.GetParentObj(); - if (pObjParent && - pObjParent->GetXAccessible().is() && - pObjParent->GetXAccessible().get() != pParentXAcc) - { - XIdToAccObjHash::iterator itXaccParent = XIdAccList.find( pParentXAcc ); - if(itXaccParent != XIdAccList.end()) - { - objXacc.SetParentObj(&(itXaccParent->second)); - } - } - } - return false; - } - - Reference< XAccessibleContext > pRContext; if( pXAcc == nullptr) @@ -600,6 +600,33 @@ bool AccObjectWinManager::InsertAccObj( XAccessible* pXAcc,XAccessible* pParentX if( !pRContext.is() ) return false; + { + short nCurRole = GetRole(pXAcc); + + std::scoped_lock l(m_Mutex); + + XIdToAccObjHash::iterator itXacc = XIdAccList.find( pXAcc ); + if (itXacc != XIdAccList.end() ) + { + if (AccessibleRole::SHAPE == nCurRole) + { + AccObject &objXacc = itXacc->second; + AccObject *pObjParent = objXacc.GetParentObj(); + if (pObjParent && + pObjParent->GetXAccessible().is() && + pObjParent->GetXAccessible().get() != pParentXAcc) + { + XIdToAccObjHash::iterator itXaccParent = XIdAccList.find( pParentXAcc ); + if(itXaccParent != XIdAccList.end()) + { + objXacc.SetParentObj(&(itXaccParent->second)); + } + } + } + return false; + } + } + if( pWnd == nullptr ) { if(pParentXAcc) @@ -647,9 +674,13 @@ bool AccObjectWinManager::InsertAccObj( XAccessible* pXAcc,XAccessible* pParentX else return false; - XIdAccList.emplace(pXAcc, pObj); - XIdToAccObjHash::iterator pIndTemp = XIdAccList.find( pXAcc ); - XResIdAccList.emplace(pObj.GetResID(),&(pIndTemp->second)); + { + std::scoped_lock l(m_Mutex); + + XIdAccList.emplace(pXAcc, pObj); + XIdToAccObjHash::iterator pIndTemp = XIdAccList.find( pXAcc ); + XResIdAccList.emplace(pObj.GetResID(),&(pIndTemp->second)); + } AccObject* pCurObj = GetAccObjByXAcc(pXAcc); if( pCurObj ) @@ -673,6 +704,8 @@ bool AccObjectWinManager::InsertAccObj( XAccessible* pXAcc,XAccessible* pParentX */ void AccObjectWinManager::SaveTopWindowHandle(HWND hWnd, css::accessibility::XAccessible* pXAcc) { + std::scoped_lock l(m_Mutex); + HwndXAcc.emplace(hWnd,pXAcc); } diff --git a/winaccessibility/source/service/msaaservice_impl.cxx b/winaccessibility/source/service/msaaservice_impl.cxx index ed9f88062720..dcc493718acc 100644 --- a/winaccessibility/source/service/msaaservice_impl.cxx +++ b/winaccessibility/source/service/msaaservice_impl.cxx @@ -85,7 +85,7 @@ public: sal_Int64 MSAAServiceImpl::getAccObjectPtr( sal_Int64 hWnd, sal_Int64 lParam, sal_Int64 wParam) { - SolarMutexGuard g; + // tdf#155794: this must complete without taking SolarMutex if (!m_pTopWindowListener.is()) {
