From a15ff23a1a8d88eb8c7d3f11c3c946a9ca6258e1 Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Sat, 15 Aug 2026 01:45:26 +0200 Subject: [PATCH 1/2] [cling] Destroy the frontend on Interpreter teardown --- interpreter/cling/lib/Interpreter/CIFactory.cpp | 4 +++- .../cling/lib/Interpreter/IncrementalParser.cpp | 2 ++ interpreter/cling/lib/Interpreter/IncrementalParser.h | 4 ++++ interpreter/cling/lib/Interpreter/Interpreter.cpp | 11 +++++++---- 4 files changed, 16 insertions(+), 5 deletions(-) diff --git a/interpreter/cling/lib/Interpreter/CIFactory.cpp b/interpreter/cling/lib/Interpreter/CIFactory.cpp index 6fec8ad234e84..fd0e845a6a8ae 100644 --- a/interpreter/cling/lib/Interpreter/CIFactory.cpp +++ b/interpreter/cling/lib/Interpreter/CIFactory.cpp @@ -1622,7 +1622,9 @@ namespace { for (auto& E : moduleExtensions) FrontendOpts.ModuleFileExtensions.push_back(E); - FrontendOpts.DisableFree = true; + // The clang driver adds -disable-free to every cc1 line. + // Interpreter::ShutDown() should free the frontend. + FrontendOpts.DisableFree = false; // Set up compiler language and target if (!SetupCompiler(CI.get(), COpts, InitLang, InitTarget)) diff --git a/interpreter/cling/lib/Interpreter/IncrementalParser.cpp b/interpreter/cling/lib/Interpreter/IncrementalParser.cpp index 220f570113fa9..040c9ecdfbe36 100644 --- a/interpreter/cling/lib/Interpreter/IncrementalParser.cpp +++ b/interpreter/cling/lib/Interpreter/IncrementalParser.cpp @@ -459,6 +459,8 @@ namespace cling { return Result.getLocWithOffset(m_VirtualFileLocOffset++); } + void IncrementalParser::destroyParser() { m_Parser.reset(); } + IncrementalParser::~IncrementalParser() { Transaction* T = const_cast(getFirstTransaction()); while (T) { diff --git a/interpreter/cling/lib/Interpreter/IncrementalParser.h b/interpreter/cling/lib/Interpreter/IncrementalParser.h index ddea651239262..a7aa11b39e53f 100644 --- a/interpreter/cling/lib/Interpreter/IncrementalParser.h +++ b/interpreter/cling/lib/Interpreter/IncrementalParser.h @@ -126,6 +126,10 @@ namespace cling { bool isChildInterpreter); clang::CompilerInstance* getCI() const { return m_CI.get(); } clang::Parser* getParser() const { return m_Parser.get(); } + + ///\brief Destroy the Parser. It writes to Sema in its destructor, so + /// it must go before Interpreter::ShutDown() frees Sema. + void destroyParser(); clang::CodeGenerator* getCodeGenerator() const { return m_CodeGen; } bool hasCodeGenerator() const { return m_CodeGen; } diff --git a/interpreter/cling/lib/Interpreter/Interpreter.cpp b/interpreter/cling/lib/Interpreter/Interpreter.cpp index 2fc17556f4dc3..2db914ece69f8 100644 --- a/interpreter/cling/lib/Interpreter/Interpreter.cpp +++ b/interpreter/cling/lib/Interpreter/Interpreter.cpp @@ -600,6 +600,11 @@ namespace cling { CI->resetAndLeakASTContext(); llvm::BuryPointer(CI->takeASTConsumer().get()); } else { + // The Parser writes to Sema in its destructor, so it must go + // first; Sema, the ASTContext and the consumer are freed here, + // while the diagnostic client and the interpreter callbacks are + // still alive -- ~Sema and ~ASTContext reach both. + m_IncrParser->destroyParser(); CI->setSema(nullptr); CI->setASTContext(nullptr); CI->setASTConsumer(nullptr); @@ -625,11 +630,9 @@ namespace cling { CI->resetAndLeakPreprocessor(); CI->resetAndLeakSourceManager(); CI->resetAndLeakFileManager(); - } else { - CI->setPreprocessor(nullptr); - CI->setSourceManager(nullptr); - CI->setFileManager(nullptr); } + // ~CompilerInstance destroys the Preprocessor, SourceManager and + // FileManager in the right order. } LO.setCompilingModule(clang::LangOptions::CMK_None); From 5041177d13ddbdc254f8027927dbceb8bf5b230c Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Sat, 15 Aug 2026 01:45:27 +0200 Subject: [PATCH 2/2] [cling] Free retained JITLink allocations at interpreter teardown --- .../cling/lib/Interpreter/IncrementalJIT.cpp | 30 +++++++++++++------ 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/interpreter/cling/lib/Interpreter/IncrementalJIT.cpp b/interpreter/cling/lib/Interpreter/IncrementalJIT.cpp index d4ed021c4a099..32c2a34b2939c 100644 --- a/interpreter/cling/lib/Interpreter/IncrementalJIT.cpp +++ b/interpreter/cling/lib/Interpreter/IncrementalJIT.cpp @@ -263,7 +263,7 @@ namespace { bool needsToReserveAllocationSpace() override { return true; } }; - /// A JITLinkMemoryManager for Cling that never frees its allocations. + /// A JITLinkMemoryManager for Cling. class ClingJITLinkMemoryManager : public InProcessMemoryManager { public: using InProcessMemoryManager::InProcessMemoryManager; @@ -273,16 +273,28 @@ namespace { // Disabled until CallFunc is informed about unloading, and can // re-generate the wrapper (if the decl is still available). See // https://github.com/root-project/root/issues/10898 - - // We still have to release the allocations which resets their addresses - // to FinalizedAlloc::InvalidAddr, or the assertion in ~FinalizedAlloc - // will be unhappy... - for (auto &Alloc : Allocs) { - Alloc.release(); - } - // Pretend we successfully deallocated everything... + // + // Releasing the handles orphans each allocation's vector of JITLink + // dealloc actions. Retain them (required for CallFunc) and let the + // base class free everything when this manager is destroyed at + // interpreter teardown. + std::lock_guard G(m_RetainedMutex); + for (auto& Alloc : Allocs) + m_Retained.push_back(std::move(Alloc)); OnDeallocated(Error::success()); } + + ~ClingJITLinkMemoryManager() override { + if (!m_Retained.empty()) + InProcessMemoryManager::deallocate(std::move(m_Retained), + [](Error Err) { + consumeError(std::move(Err)); + }); + } + + private: + std::mutex m_RetainedMutex; + std::vector m_Retained; }; /// A DynamicLibrarySearchGenerator that uses ResourceTracker to remember