This is an archive of the discontinued LLVM Phabricator instance.

[lldb][TypeSystem] ForEach: Don't hold the TypeSystemMap lock across callback
ClosedPublic

Authored by Michael137 on May 5 2023, 4:24 AM.

Details

Summary

The TypeSystemMap::m_mutex guards against concurrent modifications
of members of TypeSystemMap. In particular, m_map.

TypeSystemMap::ForEach iterates through the entire m_map calling
a user-specified callback for each entry. This is all done while
m_mutex is locked. However, there's nothing that guarantees that
the callback itself won't call back into TypeSystemMap APIs on the
same thread. This lead to double-locking m_mutex, which is undefined
behaviour. We've seen this cause a deadlock in the swift plugin with
following backtrace:

int main() {
    std::unique_ptr<int> up = std::make_unique<int>(5);

    volatile int val = *up;
    return val;
}

clang++ -std=c++2a -g -O1 main.cpp

./bin/lldb -o “br se -p return” -o run -o “v *up” -o “expr *up” -b
frame #4: std::lock_guard<std::mutex>::lock_guard
frame #5: lldb_private::TypeSystemMap::GetTypeSystemForLanguage <<<< Lock #2
frame #6: lldb_private::TypeSystemMap::GetTypeSystemForLanguage
frame #7: lldb_private::Target::GetScratchTypeSystemForLanguage
...
frame #26: lldb_private::SwiftASTContext::LoadLibraryUsingPaths
frame #27: lldb_private::SwiftASTContext::LoadModule
frame #30: swift::ModuleDecl::collectLinkLibraries
frame #31: lldb_private::SwiftASTContext::LoadModule
frame #34: lldb_private::SwiftASTContext::GetCompileUnitImportsImpl
frame #35: lldb_private::SwiftASTContext::PerformCompileUnitImports
frame #36: lldb_private::TypeSystemSwiftTypeRefForExpressions::GetSwiftASTContext
frame #37: lldb_private::TypeSystemSwiftTypeRefForExpressions::GetPersistentExpressionState
frame #38: lldb_private::Target::GetPersistentSymbol
frame #41: lldb_private::TypeSystemMap::ForEach                 <<<< Lock #1
frame #42: lldb_private::Target::GetPersistentSymbol
frame #43: lldb_private::IRExecutionUnit::FindInUserDefinedSymbols
frame #44: lldb_private::IRExecutionUnit::FindSymbol
frame #45: lldb_private::IRExecutionUnit::MemoryManager::GetSymbolAddressAndPresence
frame #46: lldb_private::IRExecutionUnit::MemoryManager::findSymbol
frame #47: non-virtual thunk to lldb_private::IRExecutionUnit::MemoryManager::findSymbol
frame #48: llvm::LinkingSymbolResolver::findSymbol
frame #49: llvm::LegacyJITSymbolResolver::lookup
frame #50: llvm::RuntimeDyldImpl::resolveExternalSymbols
frame #51: llvm::RuntimeDyldImpl::resolveRelocations
frame #52: llvm::MCJIT::finalizeLoadedModules
frame #53: llvm::MCJIT::finalizeObject
frame #54: lldb_private::IRExecutionUnit::ReportAllocations
frame #55: lldb_private::IRExecutionUnit::GetRunnableInfo
frame #56: lldb_private::ClangExpressionParser::PrepareForExecution
frame #57: lldb_private::ClangUserExpression::TryParse
frame #58: lldb_private::ClangUserExpression::Parse

Our solution is to simply iterate over a local copy of m_map.

Testing

  • Confirmed on manual reproducer (would reproduce 100% of the time before the patch)

Diff Detail

Event Timeline

Michael137 created this revision.May 5 2023, 4:24 AM
Herald added a project: Restricted Project. · View Herald TranscriptMay 5 2023, 4:24 AM
Michael137 requested review of this revision.May 5 2023, 4:24 AM
Herald added a project: Restricted Project. · View Herald TranscriptMay 5 2023, 4:24 AM
Michael137 updated this revision to Diff 519803.May 5 2023, 4:26 AM
  • Add back change dropped in cherry-pick
aprantl accepted this revision.May 5 2023, 8:14 AM
This revision is now accepted and ready to land.May 5 2023, 8:14 AM

Do we have other ForEach methods that contain a similar footgun?

Do we have other ForEach methods that contain a similar footgun?

Not that I can tell

labath added a subscriber: labath.May 15 2023, 3:16 AM

Just as a drive-by, since we're constructing a copy anyway it would be possible to merge this construction with the "uniqueing" behavior of the visited set. I.e., instead of making a literal copy of the map, just construct a to_be_visited set of /unique/ objects, and then iterate over that.

Just as a drive-by, since we're constructing a copy anyway it would be possible to merge this construction with the "uniqueing" behavior of the visited set. I.e., instead of making a literal copy of the map, just construct a to_be_visited set of /unique/ objects, and then iterate over that.

Agreed that would be a nice cleanup. I considered it initially but thought I'd split it out into a separate patch (but never did)