From 6250e4c681f76f72c3f45fd3c06ec3747e10ac1d Mon Sep 17 00:00:00 2001 From: Philippe Canal Date: Wed, 9 Apr 2025 17:25:37 -0500 Subject: [PATCH 01/10] meta: Allow global disabling of AutoParsing during TClass::GetClass If TClass.cxx is build with the cpp macro: ROOT_DISABLE_TCLASS_GET_CLASS_AUTOPARSING defined, it will no longer do any auto-parsing during the execution of `TClass::GetClass`. This will result in not being able to find TClass-es when the name requires not-already loaded interpreted information (eg. a typedef to be resolved). Comments include additional possible interfaces to turn on this feature. --- core/meta/src/TClass.cxx | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/core/meta/src/TClass.cxx b/core/meta/src/TClass.cxx index b8969d77dc2d7..aeb4cf9c6cd93 100644 --- a/core/meta/src/TClass.cxx +++ b/core/meta/src/TClass.cxx @@ -3053,6 +3053,21 @@ TClass *TClass::GetClass(const char *name, Bool_t load, Bool_t silent, size_t hi // continue as before ... } + bool disableAutoParsing = gInterpreter->IsAutoParsingSuspended(); + // FIXME: We need to decided on the interface to disable auto-parsing only during TClass::GetClass. +#ifdef ROOT_DISABLE_TCLASS_GET_CLASS_AUTOPARSING + constexpr bool requestDisableAutoParsing = true; +#else + // We could get the user choice from: + // - environment variable ROOT_DISABLE_TCLASS_GET_CLASS_AUTOPARSING + // - rootrc key Root.TClass.GetClass.AutoParsing + // - TClass::SetGetClassAutoParsing + constexpr bool requestDisableAutoParsing = false; +#endif + if (requestDisableAutoParsing) + disableAutoParsing = true; + TInterpreter::SuspendAutoParsing autoparseFence(gInterpreter, disableAutoParsing); + // Note: this variable does not always holds the fully normalized name // as there is information from a not yet loaded library or from header // not yet parsed that may be needed to fully normalize the name. From de4d2b30fcfce84d29e494979e1b918886abeec4 Mon Sep 17 00:00:00 2001 From: Philippe Canal Date: Wed, 9 Apr 2025 17:26:39 -0500 Subject: [PATCH 02/10] TCling: Register, give access to and print list of autoparsed class. Use `gInterpreter->Print("autoparsed");` to print a list of the class names that directly lead to auto-parsing. Use `gCling->GetAutoParseClasses()` to programatically get a set of the class names that directly lead to auto-parsing. --- core/metacling/src/TCling.cxx | 29 +++++++++++++++++++ core/metacling/src/TCling.h | 3 ++ .../headerParsingOnDemand.ref | 1 + 3 files changed, 33 insertions(+) diff --git a/core/metacling/src/TCling.cxx b/core/metacling/src/TCling.cxx index 79b6f4ab2e4dd..6b60427fb2b23 100644 --- a/core/metacling/src/TCling.cxx +++ b/core/metacling/src/TCling.cxx @@ -2706,6 +2706,29 @@ void TCling::PrintIntro() { } +//////////////////////////////////////////////////////////////////////////////// +/// Print information about the interpreter. +///\param[in] option Selects the type of information to print. +/// +/// List of currently support options: +/// - autoparsed: Print the list of classes that triggered autoparsing. +void TCling::Print(Option_t *option) const +{ + if (option && *option) { + if (!strcmp(option, "autoparsed")) { + std::cout << "Auto parsed classes:" << std::endl; + for (auto & cls : fAutoParseClasses) { + std::cout << " " << cls << std::endl; + } + } else { + ::Error("TCling::Print", "Unknown option '%s'", option); + } + } else { + ::Info("TCling::Print", "No options specified"); + } +} + + //////////////////////////////////////////////////////////////////////////////// /// \brief Add a directory to the list of directories in which the /// interpreter looks for include files. @@ -6593,6 +6616,12 @@ UInt_t TCling::AutoParseImplRecurse(const char *cls, bool topLevel) } } + if (nHheadersParsed) { + // Register that we did autoparsing for this class. + fAutoParseClasses.insert(cls); + if (gDebug) + Info("AutoParse", "Parsed %d headers for %s", nHheadersParsed, cls); + } return nHheadersParsed; } diff --git a/core/metacling/src/TCling.h b/core/metacling/src/TCling.h index 1f0aa189fd89c..24ba476e0da2a 100644 --- a/core/metacling/src/TCling.h +++ b/core/metacling/src/TCling.h @@ -121,6 +121,7 @@ class TCling final : public TInterpreter { std::set fLookedUpClasses; // Set of classes for which headers were looked up already std::set fPayloads; // Set of payloads std::set fParsedPayloadsAddresses; // Set of payloads which were parsed + std::set fAutoParseClasses; // Set of classes for which we autoparsed a header std::hash fStringHashFunction; // A simple hashing function std::unordered_set fNSFromRootmaps; // Collection of namespaces fwd declared in the rootmaps TObjArray* fRootmapFiles; // Loaded rootmap files. @@ -200,6 +201,7 @@ class TCling final : public TInterpreter { Int_t AutoLoad(const char *classname, Bool_t knowDictNotLoaded = kFALSE) final; Int_t AutoLoad(const std::type_info& typeinfo, Bool_t knowDictNotLoaded = kFALSE) final; Int_t AutoParse(const char* cls) final; + const std::set& GetAutoParseClasses() const { return fAutoParseClasses; } void* LazyFunctionCreatorAutoload(const std::string& mangled_name); bool LibraryLoadingFailed(const std::string&, const std::string&, bool, bool); Bool_t IsAutoLoadNamespaceCandidate(const clang::NamespaceDecl* nsDecl); @@ -240,6 +242,7 @@ class TCling final : public TInterpreter { Longptr_t ProcessLineAsynch(const char* line, EErrorCode* error = nullptr); Longptr_t ProcessLineSynch(const char* line, EErrorCode* error = nullptr) final; void PrintIntro() final; + void Print(Option_t *option="") const final; bool RegisterPrebuiltModulePath(const std::string& FullPath, const std::string& ModuleMapName = "module.modulemap") const final; void RegisterModule(const char* modulename, diff --git a/roottest/root/meta/autoloading/headerParsingOnDemand/headerParsingOnDemand.ref b/roottest/root/meta/autoloading/headerParsingOnDemand/headerParsingOnDemand.ref index 36943e5f2cca4..684b009dc6337 100644 --- a/roottest/root/meta/autoloading/headerParsingOnDemand/headerParsingOnDemand.ref +++ b/roottest/root/meta/autoloading/headerParsingOnDemand/headerParsingOnDemand.ref @@ -2,6 +2,7 @@ Processing runFullheaderParsingOnDemand.C... Class name myClass0 Info in : loaded library libFullheaderParsingOnDemand_dictrflx.so for myClass0 Info in : Parsing full payload for myClass0 +Info in : Parsed 1 headers for myClass0 Class name myClass1 Class name myClass1 Class name myClass2 From 55b0bc022a963be0e6da8708812ac74441b1a516 Mon Sep 17 00:00:00 2001 From: Philippe Canal Date: Fri, 11 Apr 2025 12:19:23 -0500 Subject: [PATCH 03/10] Remove use of obsolete gccxmlpath --- cmake/modules/RootMacros.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/modules/RootMacros.cmake b/cmake/modules/RootMacros.cmake index c969b5cf62ca8..6b0a0702a6826 100644 --- a/cmake/modules/RootMacros.cmake +++ b/cmake/modules/RootMacros.cmake @@ -194,7 +194,7 @@ function(REFLEX_GENERATE_DICTIONARY dictionary) OUTPUT ${gensrcdict} ${rootmapname} COMMAND ${ROOT_genreflex_CMD} ARGS ${headerfiles} -o ${gensrcdict} ${rootmapopts} --select=${selectionfile} - --gccxmlpath=${GCCXML_home}/bin ${ARG_OPTIONS} + ${ARG_OPTIONS} "-I$>,;-I>" "$<$>:-D$>" DEPENDS ${headerfiles} ${selectionfile} ${ARG_DEPENDS} From 18a4f5cccfb12d8deae54015a5ee49285906ce4f Mon Sep 17 00:00:00 2001 From: Philippe Canal Date: Mon, 14 Apr 2025 15:55:05 -0500 Subject: [PATCH 04/10] meta: Add `Root.TClass.GetClass.AutoParsing` rootrc key. This allows to disable auto-parsing during `TClass::GetClass` for debugging purposes. --- config/rootrc.in | 8 ++++++++ core/meta/src/TClass.cxx | 7 ++----- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/config/rootrc.in b/config/rootrc.in index 06fd91e69d6f6..265d80e5d4026 100644 --- a/config/rootrc.in +++ b/config/rootrc.in @@ -618,3 +618,11 @@ Rint.Canvas.HighLightColor: 5 # 1 All Branches (default) # Can be overridden by the environment variable ROOT_TTREECACHE_PREFILL # TTreeCache.Prefill: 1 + +# Advanced Debug Settings +# Setting Root.TClass.GetClass.AutoParsing to false +# will disable any auto-parsing execution of `TClass::GetClass`. This will +# result in not being able to find TClass-es when the name requires not-already +# loaded interpreted information (eg. a typedef to be resolved). +# +# Root.TClass.GetClass.AutoParsing: true diff --git a/core/meta/src/TClass.cxx b/core/meta/src/TClass.cxx index aeb4cf9c6cd93..dbebde5d1f377 100644 --- a/core/meta/src/TClass.cxx +++ b/core/meta/src/TClass.cxx @@ -55,6 +55,7 @@ In order to access the name of a class within the ROOT type system, the method T #include "TDataType.h" #include "TDatime.h" #include "TEnum.h" +#include "TEnv.h" #include "TError.h" #include "TExMap.h" #include "TFunctionTemplate.h" @@ -3058,11 +3059,7 @@ TClass *TClass::GetClass(const char *name, Bool_t load, Bool_t silent, size_t hi #ifdef ROOT_DISABLE_TCLASS_GET_CLASS_AUTOPARSING constexpr bool requestDisableAutoParsing = true; #else - // We could get the user choice from: - // - environment variable ROOT_DISABLE_TCLASS_GET_CLASS_AUTOPARSING - // - rootrc key Root.TClass.GetClass.AutoParsing - // - TClass::SetGetClassAutoParsing - constexpr bool requestDisableAutoParsing = false; + static const bool requestDisableAutoParsing = !gEnv->GetValue("Root.TClass.GetClass.AutoParsing", true); #endif if (requestDisableAutoParsing) disableAutoParsing = true; From 99c94ea45addd11d4a814874bc03f45ff5f42c0c Mon Sep 17 00:00:00 2001 From: Philippe Canal Date: Mon, 14 Apr 2025 17:19:07 -0500 Subject: [PATCH 05/10] TCling: Register and print list of autoloaded libraries. Use `gInterpreter->Print(autoloaded);` to print a list of the libraries that have been automaticaly loaded during TClass::GetClass and due to a symbol requested during code interpretation. --- core/base/src/TROOT.cxx | 3 +++ core/meta/inc/TInterpreter.h | 1 + core/metacling/src/TCling.cxx | 14 ++++++++++++++ core/metacling/src/TCling.h | 3 +++ 4 files changed, 21 insertions(+) diff --git a/core/base/src/TROOT.cxx b/core/base/src/TROOT.cxx index 57f56133d00cb..700e01f15ce09 100644 --- a/core/base/src/TROOT.cxx +++ b/core/base/src/TROOT.cxx @@ -2204,6 +2204,9 @@ Int_t TROOT::LoadClass(const char * /*classname*/, const char *libname, // TSystem::Load returns 1 when the library was already loaded, return success in this case. if (err == 1) err = 0; + if (err == 0) + // Register the Autoloading of the library + gCling->RegisterAutoLoadedLibrary(libname); return err; } } else { diff --git a/core/meta/inc/TInterpreter.h b/core/meta/inc/TInterpreter.h index 59f2a09b91039..94980fdb0a7fc 100644 --- a/core/meta/inc/TInterpreter.h +++ b/core/meta/inc/TInterpreter.h @@ -194,6 +194,7 @@ class TInterpreter : public TNamed { virtual void AddAvailableIndentifiers(TSeqCollection&) = 0; virtual void RegisterTClassUpdate(TClass *oldcl,DictFuncPtr_t dict) = 0; virtual void UnRegisterTClassUpdate(const TClass *oldcl) = 0; + virtual void RegisterAutoLoadedLibrary(const char *libname) = 0; virtual Int_t SetClassSharedLibs(const char *cls, const char *libs) = 0; virtual void SetGetline(const char*(*getlineFunc)(const char* prompt), void (*histaddFunc)(const char* line)) = 0; diff --git a/core/metacling/src/TCling.cxx b/core/metacling/src/TCling.cxx index 6b60427fb2b23..d238db6c5a91d 100644 --- a/core/metacling/src/TCling.cxx +++ b/core/metacling/src/TCling.cxx @@ -2720,6 +2720,11 @@ void TCling::Print(Option_t *option) const for (auto & cls : fAutoParseClasses) { std::cout << " " << cls << std::endl; } + } else if (!strcmp(option, "autoloaded")) { + std::cout << "Auto loaded libraries:" << std::endl; + for (auto & lib : fAutoLoadedLibraries) { + std::cout << " " << lib << std::endl; + } } else { ::Error("TCling::Print", "Unknown option '%s'", option); } @@ -3490,6 +3495,14 @@ static bool StartsWithStrLit(const char *haystack, const char (&needle)[N]) { } } +//////////////////////////////////////////////////////////////////////////////// +/// Register that a library was autoloaded either to provide a 'missing' symbol +/// or to provide a class (see TClass::GetClass and TROOT::LoadClass). +void TCling::RegisterAutoLoadedLibrary(const char *libname) +{ + fAutoLoadedLibraries.insert(libname); +} + //////////////////////////////////////////////////////////////////////////////// /// Register a new shared library name with the interpreter; add it to /// fSharedLibs. @@ -6728,6 +6741,7 @@ void* TCling::LazyFunctionCreatorAutoload(const std::string& mangled_name) { if (!LibLoader(libName)) return nullptr; + fAutoLoadedLibraries.insert(libName); return llvm::sys::DynamicLibrary::SearchForAddressOfSymbol(dlsym_mangled_name); } diff --git a/core/metacling/src/TCling.h b/core/metacling/src/TCling.h index 24ba476e0da2a..36b1bce8812e1 100644 --- a/core/metacling/src/TCling.h +++ b/core/metacling/src/TCling.h @@ -122,6 +122,7 @@ class TCling final : public TInterpreter { std::set fPayloads; // Set of payloads std::set fParsedPayloadsAddresses; // Set of payloads which were parsed std::set fAutoParseClasses; // Set of classes for which we autoparsed a header + std::set fAutoLoadedLibraries; // Set of libraries that were autoloaded std::hash fStringHashFunction; // A simple hashing function std::unordered_set fNSFromRootmaps; // Collection of namespaces fwd declared in the rootmaps TObjArray* fRootmapFiles; // Loaded rootmap files. @@ -259,6 +260,8 @@ class TCling final : public TInterpreter { void RegisterTClassUpdate(TClass *oldcl,DictFuncPtr_t dict) final; void UnRegisterTClassUpdate(const TClass *oldcl) final; + void RegisterAutoLoadedLibrary(const char *libname) final; + Int_t SetClassSharedLibs(const char *cls, const char *libs) final; void SetGetline(const char * (*getlineFunc)(const char* prompt), void (*histaddFunc)(const char* line)) final; From 96261155aa07d5b1243d5754edc8d1f58f0133ee Mon Sep 17 00:00:00 2001 From: Philippe Canal Date: Wed, 23 Apr 2025 15:47:31 -0500 Subject: [PATCH 06/10] TCling: Also register via TCling__LoadLibrary --- core/metacling/src/TCling.cxx | 1 + 1 file changed, 1 insertion(+) diff --git a/core/metacling/src/TCling.cxx b/core/metacling/src/TCling.cxx index d238db6c5a91d..2184303c5fe51 100644 --- a/core/metacling/src/TCling.cxx +++ b/core/metacling/src/TCling.cxx @@ -348,6 +348,7 @@ void TCling__PrintStackTrace() { extern "C" int TCling__LoadLibrary(const char *library) { + gCling->RegisterAutoLoadedLibrary(library); return gSystem->Load(library, "", false); } From 53a878457366aeaeda5211e1cb087976585cb479 Mon Sep 17 00:00:00 2001 From: Philippe Canal Date: Wed, 23 Apr 2025 15:48:25 -0500 Subject: [PATCH 07/10] io: Make TActionSequence::fLoopConfig a unique_ptr --- io/io/inc/TStreamerInfoActions.h | 11 ++++---- io/io/src/TBufferFile.cxx | 2 +- io/io/src/TBufferText.cxx | 2 +- io/io/src/TStreamerInfoActions.cxx | 42 ++++++++++++++++-------------- 4 files changed, 29 insertions(+), 28 deletions(-) diff --git a/io/io/inc/TStreamerInfoActions.h b/io/io/inc/TStreamerInfoActions.h index 40f2ac9cf8419..f09054389579a 100644 --- a/io/io/inc/TStreamerInfoActions.h +++ b/io/io/inc/TStreamerInfoActions.h @@ -191,9 +191,8 @@ namespace TStreamerInfoActions { SetBit((UInt_t)EStatusBits::kVectorPtrLooper); fActions.reserve(maxdata); }; - ~TActionSequence() override { - delete fLoopConfig; - } + // We have no owner pointer, so the default is perfect. + ~TActionSequence() override = default; template void AddAction( action_t action, TConfiguration *conf ) { @@ -208,7 +207,7 @@ namespace TStreamerInfoActions { } TVirtualStreamerInfo *fStreamerInfo; ///< StreamerInfo used to derive these actions. - TLoopConfiguration *fLoopConfig; ///< If this is a bundle of memberwise streaming action, this configures the looping + std::unique_ptr fLoopConfig; ///< If this is a bundle of memberwise streaming action, this configures the looping ActionContainer_t fActions; void AddToOffset(Int_t delta); @@ -216,9 +215,9 @@ namespace TStreamerInfoActions { TActionSequence *CreateCopy(); static TActionSequence *CreateReadMemberWiseActions(TVirtualStreamerInfo *info, TVirtualCollectionProxy &proxy); - static TActionSequence *CreateReadMemberWiseActions(TVirtualStreamerInfo &info, TLoopConfiguration *loopConfig); // 2nd arg should be unique_ptr + static TActionSequence *CreateReadMemberWiseActions(TVirtualStreamerInfo &info, std::unique_ptr loopConfig); static TActionSequence *CreateWriteMemberWiseActions(TVirtualStreamerInfo *info, TVirtualCollectionProxy &proxy); - static TActionSequence *CreateWriteMemberWiseActions(TVirtualStreamerInfo &info, TLoopConfiguration *loopConfig); // 2nd arg should be unique_ptr + static TActionSequence *CreateWriteMemberWiseActions(TVirtualStreamerInfo &info, std::unique_ptr loopConfig); TActionSequence *CreateSubSequence(const std::vector &element_ids, size_t offset); TActionSequence *CreateSubSequence(const TIDs &element_ids, size_t offset, SequenceGetter_t create); diff --git a/io/io/src/TBufferFile.cxx b/io/io/src/TBufferFile.cxx index e6ac648781ec0..30f00b2f10a6f 100644 --- a/io/io/src/TBufferFile.cxx +++ b/io/io/src/TBufferFile.cxx @@ -3788,7 +3788,7 @@ Int_t TBufferFile::ApplySequenceVecPtr(const TStreamerInfoActions::TActionSequen Int_t TBufferFile::ApplySequence(const TStreamerInfoActions::TActionSequence &sequence, void *start_collection, void *end_collection) { - TStreamerInfoActions::TLoopConfiguration *loopconfig = sequence.fLoopConfig; + TStreamerInfoActions::TLoopConfiguration *loopconfig = sequence.fLoopConfig.get(); if (gDebug) { // Get the address of the first item for the PrintDebug. diff --git a/io/io/src/TBufferText.cxx b/io/io/src/TBufferText.cxx index dcbe184f2fd77..76e625f8ead24 100644 --- a/io/io/src/TBufferText.cxx +++ b/io/io/src/TBufferText.cxx @@ -140,7 +140,7 @@ Int_t TBufferText::ApplySequence(const TStreamerInfoActions::TActionSequence &se TVirtualStreamerInfo *info = sequence.fStreamerInfo; IncrementLevel(info); - TStreamerInfoActions::TLoopConfiguration *loopconfig = sequence.fLoopConfig; + TStreamerInfoActions::TLoopConfiguration *loopconfig = sequence.fLoopConfig.get(); if (gDebug) { // Get the address of the first item for the PrintDebug. diff --git a/io/io/src/TStreamerInfoActions.cxx b/io/io/src/TStreamerInfoActions.cxx index 61a046c898297..35f315029fce5 100644 --- a/io/io/src/TStreamerInfoActions.cxx +++ b/io/io/src/TStreamerInfoActions.cxx @@ -1520,9 +1520,9 @@ namespace TStreamerInfoActions static std::unique_ptr CreateReadActionSequence(TStreamerInfo &info, TLoopConfiguration *loopConfig) { - TLoopConfiguration *localLoopConfig = loopConfig ? loopConfig->Copy() : nullptr; + std::unique_ptr localLoopConfig{ loopConfig ? loopConfig->Copy() : nullptr }; std::unique_ptr actions( - TActionSequence::CreateReadMemberWiseActions(info, localLoopConfig)); + TActionSequence::CreateReadMemberWiseActions(info, std::move(localLoopConfig))); return actions; } @@ -1532,9 +1532,9 @@ namespace TStreamerInfoActions static std::unique_ptr CreateWriteActionSequence(TStreamerInfo &info, TLoopConfiguration *loopConfig) { - TLoopConfiguration *localLoopConfig = loopConfig ? loopConfig->Copy() : nullptr; + std::unique_ptr localLoopConfig{ loopConfig ? loopConfig->Copy() : nullptr }; std::unique_ptr actions( - TActionSequence::CreateWriteMemberWiseActions(info, localLoopConfig)); + TActionSequence::CreateWriteMemberWiseActions(info, std::move(localLoopConfig))); return actions; } @@ -5171,7 +5171,7 @@ TStreamerInfoActions::TActionSequence *TStreamerInfoActions::TActionSequence::Cr return new TStreamerInfoActions::TActionSequence(0,0); } - TLoopConfiguration *loopConfig = nullptr; + std::unique_ptr loopConfig; if (IsDefaultVector(proxy)) { if (proxy.HasPointers()) { @@ -5182,30 +5182,31 @@ TStreamerInfoActions::TActionSequence *TStreamerInfoActions::TActionSequence::Cr // We can speed up the iteration in case of vector. We also know that all emulated collection are stored internally as a vector. Long_t increment = proxy.GetIncrement(); - loopConfig = new TVectorLoopConfig(&proxy, increment, /* read */ kTRUE); + loopConfig = std::make_unique(&proxy, increment, /* read */ kTRUE); } else if (proxy.GetCollectionType() == ROOT::kSTLset || proxy.GetCollectionType() == ROOT::kSTLunorderedset || proxy.GetCollectionType() == ROOT::kSTLmultiset || proxy.GetCollectionType() == ROOT::kSTLunorderedmultiset || proxy.GetCollectionType() == ROOT::kSTLmap || proxy.GetCollectionType() == ROOT::kSTLmultimap || proxy.GetCollectionType() == ROOT::kSTLunorderedmap || proxy.GetCollectionType() == ROOT::kSTLunorderedmultimap) { Long_t increment = proxy.GetIncrement(); - loopConfig = new TVectorLoopConfig(&proxy, increment, /* read */ kTRUE); + loopConfig = std::make_unique(&proxy, increment, /* read */ kTRUE); // sequence->fLoopConfig = new TAssocLoopConfig(proxy); } else { - loopConfig = new TGenericLoopConfig(&proxy, /* read */ kTRUE); + loopConfig = std::make_unique(&proxy, /* read */ kTRUE); } - return CreateReadMemberWiseActions(*info, loopConfig); + return CreateReadMemberWiseActions(*info, std::move(loopConfig)); } //////////////////////////////////////////////////////////////////////////////// /// Create the bundle of the actions necessary for the streaming memberwise of the content described by 'info' into the collection described by 'proxy' -TStreamerInfoActions::TActionSequence *TStreamerInfoActions::TActionSequence::CreateReadMemberWiseActions(TVirtualStreamerInfo &info, TLoopConfiguration *loopConfig) +TStreamerInfoActions::TActionSequence *TStreamerInfoActions::TActionSequence::CreateReadMemberWiseActions(TVirtualStreamerInfo &info, std::unique_ptr inLoopConfig) { UInt_t ndata = info.GetElements()->GetEntriesFast(); TStreamerInfoActions::TActionSequence *sequence = new TStreamerInfoActions::TActionSequence(&info, ndata); - sequence->fLoopConfig = loopConfig; + sequence->fLoopConfig = std::move(inLoopConfig); + TLoopConfiguration *loopConfig = sequence->fLoopConfig.get(); for (UInt_t i = 0; i < ndata; ++i) { TStreamerElement *element = (TStreamerElement*) info.GetElements()->At(i); @@ -5294,7 +5295,7 @@ TStreamerInfoActions::TActionSequence *TStreamerInfoActions::TActionSequence::Cr return new TStreamerInfoActions::TActionSequence(0,0); } - TLoopConfiguration *loopConfig = nullptr; + std::unique_ptr loopConfig; if (IsDefaultVector(proxy)) { if (proxy.HasPointers()) { @@ -5305,11 +5306,11 @@ TStreamerInfoActions::TActionSequence *TStreamerInfoActions::TActionSequence::Cr // We can speed up the iteration in case of vector. We also know that all emulated collection are stored internally as a vector. Long_t increment = proxy.GetIncrement(); - loopConfig = new TVectorLoopConfig(&proxy, increment, /* read */ kFALSE); + loopConfig = std::make_unique(&proxy, increment, /* read */ kFALSE); } else { - loopConfig = new TGenericLoopConfig(&proxy, /* read */ kFALSE); + loopConfig = std::make_unique(&proxy, /* read */ kFALSE); } - return CreateWriteMemberWiseActions(*info, loopConfig); + return CreateWriteMemberWiseActions(*info, std::move(loopConfig)); } //////////////////////////////////////////////////////////////////////////////// @@ -5318,11 +5319,12 @@ TStreamerInfoActions::TActionSequence *TStreamerInfoActions::TActionSequence::Cr /// TActionSequence class (stored as public fLoopConfig internally, will be deleted in destructor) \return new /// `sequence` pointer of type TActionSequence, the memory ownership is transferred to caller, must delete it later -TStreamerInfoActions::TActionSequence *TStreamerInfoActions::TActionSequence::CreateWriteMemberWiseActions(TVirtualStreamerInfo &info, TLoopConfiguration *loopConfig) +TStreamerInfoActions::TActionSequence *TStreamerInfoActions::TActionSequence::CreateWriteMemberWiseActions(TVirtualStreamerInfo &info, std::unique_ptr inLoopConfig) { UInt_t ndata = info.GetElements()->GetEntriesFast(); TStreamerInfoActions::TActionSequence *sequence = new TStreamerInfoActions::TActionSequence(&info, ndata); - sequence->fLoopConfig = loopConfig; + sequence->fLoopConfig = std::move(inLoopConfig); + TLoopConfiguration *loopConfig = sequence->fLoopConfig.get(); for (UInt_t i = 0; i < ndata; ++i) { TStreamerElement *element = (TStreamerElement*) info.GetElements()->At(i); @@ -5434,7 +5436,7 @@ TStreamerInfoActions::TActionSequence *TStreamerInfoActions::TActionSequence::Cr TStreamerInfoActions::TActionSequence *sequence = new TStreamerInfoActions::TActionSequence(fStreamerInfo, fActions.size(), IsForVectorPtrLooper()); - sequence->fLoopConfig = fLoopConfig ? fLoopConfig->Copy() : 0; + sequence->fLoopConfig.reset(fLoopConfig ? fLoopConfig->Copy() : nullptr); TStreamerInfoActions::ActionContainer_t::iterator end = fActions.end(); for(TStreamerInfoActions::ActionContainer_t::iterator iter = fActions.begin(); @@ -5516,7 +5518,7 @@ TStreamerInfoActions::TActionSequence *TStreamerInfoActions::TActionSequence::Cr TStreamerInfoActions::TActionSequence *sequence = new TStreamerInfoActions::TActionSequence(fStreamerInfo, element_ids.size(), IsForVectorPtrLooper()); - sequence->fLoopConfig = fLoopConfig ? fLoopConfig->Copy() : 0; + sequence->fLoopConfig = std::unique_ptr(fLoopConfig ? fLoopConfig->Copy() : 0); AddToSubSequence(sequence, element_ids, offset, create); @@ -5530,7 +5532,7 @@ TStreamerInfoActions::TActionSequence *TStreamerInfoActions::TActionSequence::Cr TStreamerInfoActions::TActionSequence *sequence = new TStreamerInfoActions::TActionSequence(fStreamerInfo, element_ids.size(), IsForVectorPtrLooper()); - sequence->fLoopConfig = fLoopConfig ? fLoopConfig->Copy() : 0; + sequence->fLoopConfig.reset(fLoopConfig ? fLoopConfig->Copy() : 0); for(UInt_t id = 0; id < element_ids.size(); ++id) { if ( element_ids[id] < 0 ) { From 767b9a82a4797f1309e420f1f25375f5c5b6f79f Mon Sep 17 00:00:00 2001 From: Philippe Canal Date: Thu, 19 Mar 2026 15:03:32 -0500 Subject: [PATCH 08/10] core: Always compile with GetClass auto-parsing disability --- core/meta/src/TClass.cxx | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/core/meta/src/TClass.cxx b/core/meta/src/TClass.cxx index dbebde5d1f377..d086bb54d2fb3 100644 --- a/core/meta/src/TClass.cxx +++ b/core/meta/src/TClass.cxx @@ -3055,12 +3055,10 @@ TClass *TClass::GetClass(const char *name, Bool_t load, Bool_t silent, size_t hi } bool disableAutoParsing = gInterpreter->IsAutoParsingSuspended(); - // FIXME: We need to decided on the interface to disable auto-parsing only during TClass::GetClass. -#ifdef ROOT_DISABLE_TCLASS_GET_CLASS_AUTOPARSING - constexpr bool requestDisableAutoParsing = true; -#else + // We could get the user choice from: + // - environment variable ROOT_DISABLE_TCLASS_GET_CLASS_AUTOPARSING + // - TClass::SetGetClassAutoParsing static const bool requestDisableAutoParsing = !gEnv->GetValue("Root.TClass.GetClass.AutoParsing", true); -#endif if (requestDisableAutoParsing) disableAutoParsing = true; TInterpreter::SuspendAutoParsing autoparseFence(gInterpreter, disableAutoParsing); From 5dfd2cde5331a42f78f7a023933ec6d0d18c1582 Mon Sep 17 00:00:00 2001 From: Philippe Canal Date: Thu, 19 Mar 2026 15:06:09 -0500 Subject: [PATCH 09/10] core: Control GetClass auto-parsing disable by env variable. Add support for disabling the auto-parsing during TClass::GetClass by setting the environment variable: ROOT_DISABLE_TCLASS_GET_CLASS_AUTOPARSING to any value. --- core/meta/src/TClass.cxx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/core/meta/src/TClass.cxx b/core/meta/src/TClass.cxx index d086bb54d2fb3..5bf56eb6cae10 100644 --- a/core/meta/src/TClass.cxx +++ b/core/meta/src/TClass.cxx @@ -3056,9 +3056,10 @@ TClass *TClass::GetClass(const char *name, Bool_t load, Bool_t silent, size_t hi bool disableAutoParsing = gInterpreter->IsAutoParsingSuspended(); // We could get the user choice from: - // - environment variable ROOT_DISABLE_TCLASS_GET_CLASS_AUTOPARSING // - TClass::SetGetClassAutoParsing - static const bool requestDisableAutoParsing = !gEnv->GetValue("Root.TClass.GetClass.AutoParsing", true); + static const bool requestDisableAutoParsing = + !gEnv->GetValue("Root.TClass.GetClass.AutoParsing", true) || + gSystem->Getenv("ROOT_DISABLE_TCLASS_GET_CLASS_AUTOPARSING") != nullptr; if (requestDisableAutoParsing) disableAutoParsing = true; TInterpreter::SuspendAutoParsing autoparseFence(gInterpreter, disableAutoParsing); From 6c7eb7d8b113501d8f72c93104056966a6bdb2de Mon Sep 17 00:00:00 2001 From: Philippe Canal Date: Fri, 20 Mar 2026 12:23:13 -0500 Subject: [PATCH 10/10] meta: Document disabling of auto-parsing during GetClass --- README/ReleaseNotes/v640/index.md | 16 ++++++++++++++-- core/meta/src/TClass.cxx | 5 +++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/README/ReleaseNotes/v640/index.md b/README/ReleaseNotes/v640/index.md index 088e26e083750..bf2ae6264b139 100644 --- a/README/ReleaseNotes/v640/index.md +++ b/README/ReleaseNotes/v640/index.md @@ -71,10 +71,22 @@ The following people have contributed to this new version: ## Core Libraries -- ROOT now adds a RUNPATH to compiled macros. This ensures that when compiled macros are loaded, they load the libraries that belong to the ROOT installation +* ROOT now adds a RUNPATH to compiled macros. This ensures that when compiled macros are loaded, they load the libraries that belong to the ROOT installation that compiled the macro. See [TSystem::SetMakeSharedLib()](https://root.cern.ch/doc/master/classTSystem.html#a80cd12e064e2285b35e9f39b5111d20e) for customising or disabling the RUNPATH. -- `rootcling` fails if no selection rule is specified and if the creation of a C++ module is not requested. +* `rootcling` fails if no selection rule is specified and if the creation of a C++ module is not requested. +* To ease debugging of unwanted auto-parsing triggered by TClass::GetClass, two new features are introduced: +* * Give access to the list of classes that triggered auto-parsing: +``` +// Print the list +gInterpreter->Print("autoparsed"); +// Get the list/set: +((TCling*)gInterpreter)->GetAutoParseClasses(); +``` +* * Auto-parsing of header files can now be explicitly disabled during the execution of TClass::GetClass; +for example, this can be used to enforce that no header is loaded for I/O operations. To disable the +auto-parsing during `TClass::GetClass`, you can either set the shell environment variable +`ROOT_DISABLE_TCLASS_GET_CLASS_AUTOPARSING` (to anything) or set the `rootrc` key `Root.TClass.GetClass.AutoParsing` to `false`. ## Geometry diff --git a/core/meta/src/TClass.cxx b/core/meta/src/TClass.cxx index 5bf56eb6cae10..137f09b0bcd8c 100644 --- a/core/meta/src/TClass.cxx +++ b/core/meta/src/TClass.cxx @@ -2970,6 +2970,11 @@ TVirtualIsAProxy* TClass::GetIsAProxy() const /// If silent is 'true', do not warn about missing dictionary for the class. /// (typically used for classes that are used only for transient members) /// Returns `nullptr` in case class is not found. +/// +/// To completely disallow auto-parsing during TClass::GetClass, you can either +/// set the shell environment variable `ROOT_DISABLE_TCLASS_GET_CLASS_AUTOPARSING` +/// (to anything) or set the `rootrc` key `Root.TClass.GetClass.AutoParsing` to +/// `false`. TClass *TClass::GetClass(const char *name, Bool_t load, Bool_t silent) {