From 86afdafe4485bf2140b1f4c1011c49a43981cbf1 Mon Sep 17 00:00:00 2001 From: Jonas Rembser Date: Tue, 7 Jul 2026 09:06:07 +0200 Subject: [PATCH] [core] Handle non-space whitespace in TClassEdit::CleanType MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A class name in a selection XML can span multiple lines. `CleanType()` only treated `' '` as a blank, so a newline was kept in the cleaned name and, when the template arguments were later re-processed individually, an argument starting with a newline was truncated to an empty string. This corrupted the rootmap entries and the alternate name registrations (e.g. `"Foo<,vector,>"`), leading to `"Second registration of ..."` errors from `TClassTable::AddAlternate` at library load time. Treat all whitespace like a space: drop it where a space would be dropped, and emit a single plain space where it separates two identifiers. Closes #22359. 🤖 Done with the help of AI. --- core/foundation/src/TClassEdit.cxx | 9 ++++++++- core/foundation/test/testClassEdit.cxx | 17 +++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/core/foundation/src/TClassEdit.cxx b/core/foundation/src/TClassEdit.cxx index 280317fbef5cb..9c2ccf48c6969 100644 --- a/core/foundation/src/TClassEdit.cxx +++ b/core/foundation/src/TClassEdit.cxx @@ -1303,9 +1303,16 @@ string TClassEdit::CleanType(const char *typeDesc, int mode, const char **tail) const char* c; for(c=typeDesc;*c;c++) { - if (c[0]==' ') { + if (std::isspace(c[0])) { if (kbl) continue; if (!isalnum(c[ 1]) && c[ 1] !='_') continue; + if (c[0] != ' ') { + // Significant whitespace other than ' ' (e.g. \n in a multi-line + // class name from a selection XML) is replaced by a plain space. + result += ' '; + kbl = 1; + continue; + } } if (kbl && (mode>=2 || parensStack.empty())) { //remove "const' etc... int done = 0; diff --git a/core/foundation/test/testClassEdit.cxx b/core/foundation/test/testClassEdit.cxx index a9215bbbc8f15..f10ef09cc7745 100644 --- a/core/foundation/test/testClassEdit.cxx +++ b/core/foundation/test/testClassEdit.cxx @@ -375,3 +375,20 @@ TEST(TClassEdit, SplitType) auto si = (TStreamerInfo*) c->GetStreamerInfo(); si->ls("noaddr"); } + +TEST(TClassEdit, MultiLineName) +{ + // A class name in a selection XML can span multiple lines. Whitespace + // other than ' ' used to empty out the template arguments it preceded, + // e.g. "Foo<,vector,>" (https://github.com/root-project/root/issues/22359). + const char *name = "Foo<\n std::vector, std::vector,\n\tunsigned int,\n Bar ::Ref>>"; + TClassEdit::TSplitType split(name, (TClassEdit::EModType)(TClassEdit::kLong64 | TClassEdit::kDropStd)); + std::string out; + split.ShortType(out, TClassEdit::kLong64 | TClassEdit::kDropStd); + EXPECT_STREQ("Foo,vector,unsigned int,Bar::Ref > >", out.c_str()); + + // Whitespace that separates two identifiers is significant and must be + // kept as a single plain space. + EXPECT_STREQ("unsigned int", TClassEdit::CleanType("unsigned\nint").c_str()); + EXPECT_STREQ("Foo", TClassEdit::CleanType("\n Foo<\n int\n>").c_str()); +}