From b126049c21c0f1d7c1ca2eaad5c371c4f5ba91cd Mon Sep 17 00:00:00 2001 From: Demetrios Agourakis Date: Fri, 16 Oct 2026 00:57:21 +0300 Subject: [PATCH 1/2] [rootcling] Classify LinkDefs by basename in callbacks Directory names containing LinkDef must not turn ordinary included headers into selection files. Otherwise the callback can collect an internal relative include or lose a required public include. Use basenames only for classification and keep emitted paths intact. Cover both directory cases and the existing nested clear/lock behavior. Related to #23640. The reporter's actual prefix remains unknown. Assisted-by: codex:GPT 6.1 Sol --- core/dictgen/src/rootcling_impl.cxx | 7 ++++--- roottest/root/meta/rootcling/CMakeLists.txt | 12 ++++++++++++ .../root/meta/rootcling/LinkDefDirectory/Wrapper.h | 6 ++++++ .../rootcling/LinkDefDirectory/detail/Payload.h | 13 +++++++++++++ .../root/meta/rootcling/NestedSelection/After.h | 3 +++ .../root/meta/rootcling/NestedSelection/Before.h | 3 +++ .../meta/rootcling/NestedSelection/InnerLinkDef.h | 6 ++++++ .../meta/rootcling/NestedSelection/OuterLinkDef.h | 3 +++ .../root/meta/rootcling/NestedSelection/Payload.h | 10 ++++++++++ roottest/root/meta/rootcling/includeDirectory.h | 1 + .../includeDirectoryFromSelectionLinkDef.h | 8 ++++++++ .../root/meta/rootcling/includeDirectoryLinkDef.h | 6 ++++++ roottest/root/meta/rootcling/includeNested.h | 1 + 13 files changed, 76 insertions(+), 3 deletions(-) create mode 100644 roottest/root/meta/rootcling/LinkDefDirectory/Wrapper.h create mode 100644 roottest/root/meta/rootcling/LinkDefDirectory/detail/Payload.h create mode 100644 roottest/root/meta/rootcling/NestedSelection/After.h create mode 100644 roottest/root/meta/rootcling/NestedSelection/Before.h create mode 100644 roottest/root/meta/rootcling/NestedSelection/InnerLinkDef.h create mode 100644 roottest/root/meta/rootcling/NestedSelection/OuterLinkDef.h create mode 100644 roottest/root/meta/rootcling/NestedSelection/Payload.h create mode 100644 roottest/root/meta/rootcling/includeDirectory.h create mode 100644 roottest/root/meta/rootcling/includeDirectoryFromSelectionLinkDef.h create mode 100644 roottest/root/meta/rootcling/includeDirectoryLinkDef.h create mode 100644 roottest/root/meta/rootcling/includeNested.h diff --git a/core/dictgen/src/rootcling_impl.cxx b/core/dictgen/src/rootcling_impl.cxx index 2df77178d3093..57a229446dd7c 100644 --- a/core/dictgen/src/rootcling_impl.cxx +++ b/core/dictgen/src/rootcling_impl.cxx @@ -3345,11 +3345,12 @@ class TRootClingCallbacks : public cling::InterpreterCallbacks { if (!curLexer) return; auto fileEntry = curLexer->getFileEntry(); if (!fileEntry) return; - auto thisFileName = fileEntry->getName(); + auto thisFileName = llvm::sys::path::filename(fileEntry->getName()).str(); auto fileNameAsString = FileName.str(); - auto isThisLinkdef = ROOT::TMetaUtils::IsLinkdefFile(thisFileName.data()); + auto isThisLinkdef = ROOT::TMetaUtils::IsLinkdefFile(thisFileName.c_str()); if (isThisLinkdef) { - auto isTheIncludedLinkdef = ROOT::TMetaUtils::IsLinkdefFile(fileNameAsString.c_str()); + auto includedFileName = llvm::sys::path::filename(FileName).str(); + auto isTheIncludedLinkdef = ROOT::TMetaUtils::IsLinkdefFile(includedFileName.c_str()); if (isTheIncludedLinkdef) { fFilesIncludedByLinkdef.clear(); isLocked = true; diff --git a/roottest/root/meta/rootcling/CMakeLists.txt b/roottest/root/meta/rootcling/CMakeLists.txt index a915789400f99..cef9fc4a93ea5 100644 --- a/roottest/root/meta/rootcling/CMakeLists.txt +++ b/roottest/root/meta/rootcling/CMakeLists.txt @@ -30,6 +30,18 @@ ROOTTEST_ADD_TEST(includeInLinkdef COPY_TO_BUILDDIR headerForLdef.h FIXTURES_REQUIRED root-meta-roocling-includeInLinkdefDict-fixture) +# Directory names must not make ordinary headers look like LinkDef files. +ROOTTEST_GENERATE_DICTIONARY(includeDirectoryDict + includeDirectory.h + LINKDEF includeDirectoryLinkDef.h) + +ROOTTEST_GENERATE_DICTIONARY(includeDirectoryFromSelectionDict + LINKDEF includeDirectoryFromSelectionLinkDef.h) + +ROOTTEST_GENERATE_DICTIONARY(includeNestedDict + includeNested.h + LINKDEF NestedSelection/OuterLinkDef.h) + # ROOT-8492 ROOTTEST_ADD_TEST(selectUnion COMMAND ${ROOT_rootcling_CMD} -f NestedClassOff.Dict.cc ${CMAKE_CURRENT_SOURCE_DIR}/NestedClassOff.h ${CMAKE_CURRENT_SOURCE_DIR}/NestedClassOff.LinkDef.h diff --git a/roottest/root/meta/rootcling/LinkDefDirectory/Wrapper.h b/roottest/root/meta/rootcling/LinkDefDirectory/Wrapper.h new file mode 100644 index 0000000000000..a58cb8c81ebb8 --- /dev/null +++ b/roottest/root/meta/rootcling/LinkDefDirectory/Wrapper.h @@ -0,0 +1,6 @@ +#ifndef ROOTTEST_ROOT_META_ROOTCLING_DIRECTORY_WRAPPER_H +#define ROOTTEST_ROOT_META_ROOTCLING_DIRECTORY_WRAPPER_H + +#include "detail/Payload.h" + +#endif diff --git a/roottest/root/meta/rootcling/LinkDefDirectory/detail/Payload.h b/roottest/root/meta/rootcling/LinkDefDirectory/detail/Payload.h new file mode 100644 index 0000000000000..81d338110f4de --- /dev/null +++ b/roottest/root/meta/rootcling/LinkDefDirectory/detail/Payload.h @@ -0,0 +1,13 @@ +#ifndef ROOTTEST_ROOT_META_ROOTCLING_DIRECTORY_PAYLOAD_H +#define ROOTTEST_ROOT_META_ROOTCLING_DIRECTORY_PAYLOAD_H + +namespace RootclingDirectory { +struct Payload { + int value = 0; +}; +struct DirectPayload { + int value = 0; +}; +} // namespace RootclingDirectory + +#endif diff --git a/roottest/root/meta/rootcling/NestedSelection/After.h b/roottest/root/meta/rootcling/NestedSelection/After.h new file mode 100644 index 0000000000000..9a217e3db23a0 --- /dev/null +++ b/roottest/root/meta/rootcling/NestedSelection/After.h @@ -0,0 +1,3 @@ +#ifndef __ROOTCLING__ +#error "Headers collected after a nested LinkDef must not be emitted directly." +#endif diff --git a/roottest/root/meta/rootcling/NestedSelection/Before.h b/roottest/root/meta/rootcling/NestedSelection/Before.h new file mode 100644 index 0000000000000..9758ead0b4d19 --- /dev/null +++ b/roottest/root/meta/rootcling/NestedSelection/Before.h @@ -0,0 +1,3 @@ +#ifndef __ROOTCLING__ +#error "Headers collected before a nested LinkDef must not be emitted directly." +#endif diff --git a/roottest/root/meta/rootcling/NestedSelection/InnerLinkDef.h b/roottest/root/meta/rootcling/NestedSelection/InnerLinkDef.h new file mode 100644 index 0000000000000..2062fc78f3591 --- /dev/null +++ b/roottest/root/meta/rootcling/NestedSelection/InnerLinkDef.h @@ -0,0 +1,6 @@ +#ifdef __ROOTCLING__ +#pragma link off all globals; +#pragma link off all classes; +#pragma link off all functions; +#pragma link C++ class RootclingNested::Payload + ; +#endif diff --git a/roottest/root/meta/rootcling/NestedSelection/OuterLinkDef.h b/roottest/root/meta/rootcling/NestedSelection/OuterLinkDef.h new file mode 100644 index 0000000000000..5890bda2539c8 --- /dev/null +++ b/roottest/root/meta/rootcling/NestedSelection/OuterLinkDef.h @@ -0,0 +1,3 @@ +#include "Before.h" +#include "InnerLinkDef.h" +#include "After.h" diff --git a/roottest/root/meta/rootcling/NestedSelection/Payload.h b/roottest/root/meta/rootcling/NestedSelection/Payload.h new file mode 100644 index 0000000000000..ccf141a6a6213 --- /dev/null +++ b/roottest/root/meta/rootcling/NestedSelection/Payload.h @@ -0,0 +1,10 @@ +#ifndef ROOTTEST_ROOT_META_ROOTCLING_NESTED_PAYLOAD_H +#define ROOTTEST_ROOT_META_ROOTCLING_NESTED_PAYLOAD_H + +namespace RootclingNested { +struct Payload { + int value = 0; +}; +} // namespace RootclingNested + +#endif diff --git a/roottest/root/meta/rootcling/includeDirectory.h b/roottest/root/meta/rootcling/includeDirectory.h new file mode 100644 index 0000000000000..75d646b744524 --- /dev/null +++ b/roottest/root/meta/rootcling/includeDirectory.h @@ -0,0 +1 @@ +#include "LinkDefDirectory/Wrapper.h" diff --git a/roottest/root/meta/rootcling/includeDirectoryFromSelectionLinkDef.h b/roottest/root/meta/rootcling/includeDirectoryFromSelectionLinkDef.h new file mode 100644 index 0000000000000..8beee46f9e18d --- /dev/null +++ b/roottest/root/meta/rootcling/includeDirectoryFromSelectionLinkDef.h @@ -0,0 +1,8 @@ +#include "LinkDefDirectory/Wrapper.h" + +#ifdef __ROOTCLING__ +#pragma link off all globals; +#pragma link off all classes; +#pragma link off all functions; +#pragma link C++ class RootclingDirectory::DirectPayload + ; +#endif diff --git a/roottest/root/meta/rootcling/includeDirectoryLinkDef.h b/roottest/root/meta/rootcling/includeDirectoryLinkDef.h new file mode 100644 index 0000000000000..ca6a7f8a5f8f2 --- /dev/null +++ b/roottest/root/meta/rootcling/includeDirectoryLinkDef.h @@ -0,0 +1,6 @@ +#ifdef __ROOTCLING__ +#pragma link off all globals; +#pragma link off all classes; +#pragma link off all functions; +#pragma link C++ class RootclingDirectory::Payload + ; +#endif diff --git a/roottest/root/meta/rootcling/includeNested.h b/roottest/root/meta/rootcling/includeNested.h new file mode 100644 index 0000000000000..531916d84b50b --- /dev/null +++ b/roottest/root/meta/rootcling/includeNested.h @@ -0,0 +1 @@ +#include "NestedSelection/Payload.h" From 9694854edbac3947e54ab38f1eaaa5070e1ec46c Mon Sep 17 00:00:00 2001 From: Demetrios Agourakis Date: Tue, 27 Oct 2026 19:35:42 -0300 Subject: [PATCH 2/2] [roottest] Verify intended rootcling dictionary registrations Load each generated library in a fresh ROOT process and require the expected compiled dictionary registration. Connect runtime checks to their dictionary generation fixtures and clarify the selection-header guards. Assisted-by: Codex:GPT-6.1-Sol --- roottest/root/meta/rootcling/CMakeLists.txt | 24 +++++++++++-- .../meta/rootcling/NestedSelection/After.h | 2 ++ .../meta/rootcling/NestedSelection/Before.h | 2 ++ .../meta/rootcling/assertIncludeDictionary.C | 34 +++++++++++++++++++ 4 files changed, 59 insertions(+), 3 deletions(-) create mode 100644 roottest/root/meta/rootcling/assertIncludeDictionary.C diff --git a/roottest/root/meta/rootcling/CMakeLists.txt b/roottest/root/meta/rootcling/CMakeLists.txt index cef9fc4a93ea5..9aacc7abef034 100644 --- a/roottest/root/meta/rootcling/CMakeLists.txt +++ b/roottest/root/meta/rootcling/CMakeLists.txt @@ -33,14 +33,32 @@ ROOTTEST_ADD_TEST(includeInLinkdef # Directory names must not make ordinary headers look like LinkDef files. ROOTTEST_GENERATE_DICTIONARY(includeDirectoryDict includeDirectory.h - LINKDEF includeDirectoryLinkDef.h) + LINKDEF includeDirectoryLinkDef.h + FIXTURES_SETUP root-meta-rootcling-includeDirectoryDict-fixture) + +ROOTTEST_ADD_TEST(includeDirectory + MACRO assertIncludeDictionary.C + MACROARG "\"includeDirectoryDict\",\"RootclingDirectory::Payload\"" + FIXTURES_REQUIRED root-meta-rootcling-includeDirectoryDict-fixture) ROOTTEST_GENERATE_DICTIONARY(includeDirectoryFromSelectionDict - LINKDEF includeDirectoryFromSelectionLinkDef.h) + LINKDEF includeDirectoryFromSelectionLinkDef.h + FIXTURES_SETUP root-meta-rootcling-includeDirectoryFromSelectionDict-fixture) + +ROOTTEST_ADD_TEST(includeDirectoryFromSelection + MACRO assertIncludeDictionary.C + MACROARG "\"includeDirectoryFromSelectionDict\",\"RootclingDirectory::DirectPayload\"" + FIXTURES_REQUIRED root-meta-rootcling-includeDirectoryFromSelectionDict-fixture) ROOTTEST_GENERATE_DICTIONARY(includeNestedDict includeNested.h - LINKDEF NestedSelection/OuterLinkDef.h) + LINKDEF NestedSelection/OuterLinkDef.h + FIXTURES_SETUP root-meta-rootcling-includeNestedDict-fixture) + +ROOTTEST_ADD_TEST(includeNested + MACRO assertIncludeDictionary.C + MACROARG "\"includeNestedDict\",\"RootclingNested::Payload\"" + FIXTURES_REQUIRED root-meta-rootcling-includeNestedDict-fixture) # ROOT-8492 ROOTTEST_ADD_TEST(selectUnion diff --git a/roottest/root/meta/rootcling/NestedSelection/After.h b/roottest/root/meta/rootcling/NestedSelection/After.h index 9a217e3db23a0..4cfb545bfd9a7 100644 --- a/roottest/root/meta/rootcling/NestedSelection/After.h +++ b/roottest/root/meta/rootcling/NestedSelection/After.h @@ -1,3 +1,5 @@ +// Parsing this selection-only header in rootcling is intentional. The guard +// detects an unwanted direct include during later dictionary C++ compilation. #ifndef __ROOTCLING__ #error "Headers collected after a nested LinkDef must not be emitted directly." #endif diff --git a/roottest/root/meta/rootcling/NestedSelection/Before.h b/roottest/root/meta/rootcling/NestedSelection/Before.h index 9758ead0b4d19..bf86672086878 100644 --- a/roottest/root/meta/rootcling/NestedSelection/Before.h +++ b/roottest/root/meta/rootcling/NestedSelection/Before.h @@ -1,3 +1,5 @@ +// Parsing this selection-only header in rootcling is intentional. The guard +// detects an unwanted direct include during later dictionary C++ compilation. #ifndef __ROOTCLING__ #error "Headers collected before a nested LinkDef must not be emitted directly." #endif diff --git a/roottest/root/meta/rootcling/assertIncludeDictionary.C b/roottest/root/meta/rootcling/assertIncludeDictionary.C new file mode 100644 index 0000000000000..20e52ad19ff53 --- /dev/null +++ b/roottest/root/meta/rootcling/assertIncludeDictionary.C @@ -0,0 +1,34 @@ +#include "TClass.h" +#include "TClassTable.h" +#include "TError.h" +#include "TSystem.h" + +#include + +int assertIncludeDictionary(const char *library, const char *className) +{ + // Each CTest invocation starts a fresh ROOT process. Do not let a dictionary + // loaded by another case hide a missing selection in the requested library. + if (TClassTable::GetDictNorm(className)) { + Error("assertIncludeDictionary", "Dictionary for %s was already registered", className); + return 1; + } + if (gSystem->Load(library) < 0) { + Error("assertIncludeDictionary", "Could not load %s", library); + return 2; + } + + // Look up the compiled registration directly, without interpreter fallback, + // header autoparsing or autoloading a different dictionary library. + auto dictionary = TClassTable::GetDictNorm(className); + if (!dictionary) { + Error("assertIncludeDictionary", "%s did not register a dictionary for %s", library, className); + return 3; + } + auto cl = dictionary(); + if (!cl || !cl->HasDictionary() || std::strcmp(cl->GetName(), className) != 0) { + Error("assertIncludeDictionary", "Invalid compiled dictionary for %s from %s", className, library); + return 4; + } + return 0; +}