Skip to content

Commit e91cca1

Browse files
vepadulanopcanal
andcommitted
[core] Check for existing type before removing whitespaces
This fixes #19022 In `TClassEdit::GetNormalizedName`, the call to `gInterpreterHelper->ExistingTypeCheck` is the one where eventually any alternative names for the class name being looked for will be detected and used for replacement (in `TClassTable::Check`). The alternative names for a custom class may appear in a dictionary when the user declared their class in the `ROOT::Meta::Selection` namespace and made it derive from the `KeepFirstTemplateArguments` type trait. Previously, the call to `ExistingTypeCheck` only happened after the input demangled type name passed through the normalization logic of `TClassEdit::TSplitType`. This led to an unfortunate situation. The demangled type name resulting from a previous `TClassEdit::DemangleName` call is the name representation of the typeid of the class type for the current execution, i.e. whatever was generate by the compiler. In the case of a class with multiple template arguments, this usually leads to a representation of the form `A<B, C>`, i.e. where the two template arguments are separated by a whitespace after the comma. Crucially, this representation is also the same one used by the type system to populate the dictionary information relative to the alternative class name for classes where the user wants to strip one or more template arguments. In the example just made, the class alternative name for `A<B>` will be registered as `A<B, C>` with the whitespace in the dictionary sources. Since the call to `ExistingTypeCheck` was happening *after* the `TSplitType` normalization, the input class name was lacking the extra whitespace. Thus, any search in `TClassTable::Check` would fail because it does a hash-based lookup of the string in the map of class alternatives. Thus, this commit introduces a second call to `ExistingTypeCheck` earlier on in the function, before the removal of the whitespace. This ensures that the name lookup in the type system can work correctly. Co-authored-by: Philippe Canal <[email protected]>
1 parent b789237 commit e91cca1

File tree

6 files changed

+106
-51
lines changed

6 files changed

+106
-51
lines changed

core/clingutils/res/TClingUtils.h

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -160,25 +160,26 @@ class TNormalizedCtxt {
160160
class TClingLookupHelper : public TClassEdit::TInterpreterLookupHelper {
161161
public:
162162
typedef bool (*ExistingTypeCheck_t)(const std::string &tname, std::string &result);
163+
typedef bool (*TClassTableCheck_t)(const std::string &tname, std::string &result);
163164
typedef bool (*AutoParse_t)(const char *name);
164165

165166
private:
166167
cling::Interpreter *fInterpreter;
167168
TNormalizedCtxt *fNormalizedCtxt;
168169
ExistingTypeCheck_t fExistingTypeCheck;
170+
TClassTableCheck_t fTClassTableCheck;
169171
AutoParse_t fAutoParse;
170172
bool *fInterpreterIsShuttingDownPtr;
171173
const int *fPDebug; // debug flag, might change at runtime thus *
172174
bool WantDiags() const { return fPDebug && *fPDebug > 5; }
173175

174176
public:
175-
TClingLookupHelper(cling::Interpreter &interpreter, TNormalizedCtxt &normCtxt,
176-
ExistingTypeCheck_t existingTypeCheck,
177-
AutoParse_t autoParse,
178-
bool *shuttingDownPtr,
177+
TClingLookupHelper(cling::Interpreter &interpreter, TNormalizedCtxt &normCtxt, ExistingTypeCheck_t existingTypeCheck,
178+
TClassTableCheck_t TClassTableCheck, AutoParse_t autoParse, bool *shuttingDownPtr,
179179
const int *pgDebug = nullptr);
180180
virtual ~TClingLookupHelper() { /* we're not owner */ }
181181

182+
bool TClassTableCheck(const std::string &tname, std::string &result) override;
182183
bool ExistingTypeCheck(const std::string &tname, std::string &result) override;
183184
void GetPartiallyDesugaredName(std::string &nameLong) override;
184185
bool IsAlreadyPartiallyDesugaredName(const std::string &nondef, const std::string &nameLong) override;

core/clingutils/src/TClingUtils.cxx

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -522,17 +522,16 @@ AnnotatedRecordDecl::AnnotatedRecordDecl(long index,
522522

523523
////////////////////////////////////////////////////////////////////////////////
524524

525-
TClingLookupHelper::TClingLookupHelper(cling::Interpreter &interpreter,
526-
TNormalizedCtxt &normCtxt,
527-
ExistingTypeCheck_t existingTypeCheck,
528-
AutoParse_t autoParse,
529-
bool *shuttingDownPtr,
530-
const int* pgDebug /*= 0*/):
531-
fInterpreter(&interpreter),fNormalizedCtxt(&normCtxt),
532-
fExistingTypeCheck(existingTypeCheck),
533-
fAutoParse(autoParse),
534-
fInterpreterIsShuttingDownPtr(shuttingDownPtr),
535-
fPDebug(pgDebug)
525+
TClingLookupHelper::TClingLookupHelper(cling::Interpreter &interpreter, TNormalizedCtxt &normCtxt,
526+
ExistingTypeCheck_t existingTypeCheck, TClassTableCheck_t TClassTableCheck,
527+
AutoParse_t autoParse, bool *shuttingDownPtr, const int *pgDebug /*= 0*/)
528+
: fInterpreter(&interpreter),
529+
fNormalizedCtxt(&normCtxt),
530+
fExistingTypeCheck(existingTypeCheck),
531+
fTClassTableCheck(TClassTableCheck),
532+
fAutoParse(autoParse),
533+
fInterpreterIsShuttingDownPtr(shuttingDownPtr),
534+
fPDebug(pgDebug)
536535
{
537536
}
538537

@@ -549,6 +548,17 @@ bool TClingLookupHelper::ExistingTypeCheck(const std::string &tname,
549548
else return false;
550549
}
551550

551+
bool TClingLookupHelper::TClassTableCheck(const std::string &tname, std::string &result)
552+
{
553+
if (tname.empty())
554+
return false;
555+
556+
if (fTClassTableCheck)
557+
return fTClassTableCheck(tname, result);
558+
else
559+
return false;
560+
}
561+
552562
////////////////////////////////////////////////////////////////////////////////
553563

554564
void TClingLookupHelper::GetPartiallyDesugaredName(std::string &nameLong)

core/dictgen/src/rootcling_impl.cxx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4498,7 +4498,7 @@ int RootClingMain(int argc,
44984498

44994499
// We are now ready (enough is loaded) to init the list of opaque typedefs.
45004500
ROOT::TMetaUtils::TNormalizedCtxt normCtxt(interp.getLookupHelper());
4501-
ROOT::TMetaUtils::TClingLookupHelper helper(interp, normCtxt, nullptr, nullptr, nullptr);
4501+
ROOT::TMetaUtils::TClingLookupHelper helper(interp, normCtxt, nullptr, nullptr, nullptr, nullptr);
45024502
TClassEdit::Init(&helper);
45034503

45044504
// flags used only for the pragma parser:

core/foundation/inc/TClassEdit.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ namespace TClassEdit {
124124
TInterpreterLookupHelper() { }
125125
virtual ~TInterpreterLookupHelper();
126126

127+
virtual bool TClassTableCheck(const std::string &, std::string &) = 0;
127128
virtual bool ExistingTypeCheck(const std::string & /*tname*/,
128129
std::string & /*result*/) = 0;
129130
virtual void GetPartiallyDesugaredName(std::string & /*nameLong*/) = 0;

core/foundation/src/TClassEdit.cxx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -904,6 +904,21 @@ void TClassEdit::GetNormalizedName(std::string &norm_name, std::string_view name
904904
}
905905

906906
AtomicTypeNameHandlerRAII nameHandler(norm_name);
907+
if (gInterpreterHelper) {
908+
// Early check whether there is an existing type corresponding to `norm_name`
909+
// It is *crucial* to run this block here, before `norm_name` gets split
910+
// and reconstructed in the following lines. The reason is that we need
911+
// to make string comparisons in `ExistingTypeCheck` and they will give
912+
// different results if `norm_name` loses whitespaces. A notable example
913+
// is when looking for registered alternate names of a custom user class
914+
// present in the class dictionary.
915+
std::string typeresult;
916+
if (gInterpreterHelper->TClassTableCheck(norm_name, typeresult)) {
917+
if (!typeresult.empty()) {
918+
norm_name = typeresult;
919+
}
920+
}
921+
}
907922

908923
// Remove the std:: and default template argument and insert the Long64_t and change basic_string to string.
909924
TClassEdit::TSplitType splitname(norm_name.c_str(),(TClassEdit::EModType)(TClassEdit::kLong64 | TClassEdit::kDropStd | TClassEdit::kDropStlDefault | TClassEdit::kKeepOuterConst));

core/metacling/src/TCling.cxx

Lines changed: 63 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ clang/LLVM technology.
139139
#include <fstream>
140140
#include <sstream>
141141
#include <string>
142+
#include <string_view>
142143
#include <tuple>
143144
#include <typeinfo>
144145
#include <unordered_map>
@@ -903,6 +904,36 @@ namespace{
903904
bool fOldDiagValue;
904905
};
905906

907+
std::string StripModifiersAndArrayBrackets(const std::string &tname)
908+
{
909+
std::string_view view{tname};
910+
911+
// Remove leading "const "
912+
if (view.substr(0, 6) == "const ") {
913+
view.remove_prefix(6);
914+
}
915+
916+
// Remove trailing *, &, and array brackets
917+
while (!view.empty()) {
918+
char last = view.back();
919+
if (last == '*' || last == '&') {
920+
view.remove_suffix(1);
921+
} else if (last == ']') {
922+
// Remove until matching '['
923+
view.remove_suffix(1); // remove ']'
924+
while (!view.empty() && view.back() != '[') {
925+
view.remove_suffix(1);
926+
}
927+
if (!view.empty() && view.back() == '[') {
928+
view.remove_suffix(1); // remove '['
929+
}
930+
} else {
931+
break;
932+
}
933+
}
934+
935+
return std::string{view};
936+
}
906937
}
907938

908939
////////////////////////////////////////////////////////////////////////////////
@@ -921,26 +952,8 @@ bool TClingLookupHelper__ExistingTypeCheck(const std::string &tname,
921952
{
922953
result.clear();
923954

924-
unsigned long offset = 0;
925-
if (strncmp(tname.c_str(), "const ", 6) == 0) {
926-
offset = 6;
927-
}
928-
unsigned long end = tname.length();
929-
while( end && (tname[end-1]=='&' || tname[end-1]=='*' || tname[end-1]==']') ) {
930-
if ( tname[end-1]==']' ) {
931-
--end;
932-
while ( end && tname[end-1]!='[' ) --end;
933-
}
934-
--end;
935-
}
936-
std::string innerbuf;
937-
const char *inner;
938-
if (end != tname.length()) {
939-
innerbuf = tname.substr(offset,end-offset);
940-
inner = innerbuf.c_str();
941-
} else {
942-
inner = tname.c_str()+offset;
943-
}
955+
const auto innerBuf = StripModifiersAndArrayBrackets(tname);
956+
const auto &inner = innerBuf.c_str();
944957

945958
//if (strchr(tname.c_str(),'[')!=0) fprintf(stderr,"DEBUG: checking on %s vs %s %lu %lu\n",tname.c_str(),inner,offset,end);
946959
if (gROOT->GetListOfClasses()->FindObject(inner)
@@ -953,20 +966,22 @@ bool TClingLookupHelper__ExistingTypeCheck(const std::string &tname,
953966
TDataType *type = (TDataType *)typeTable->THashTable::FindObject( inner );
954967
if (type) {
955968
// This is a raw type and an already loaded typedef.
956-
const char *newname = type->GetFullTypeName();
957-
if (type->GetType() == kLong64_t) {
958-
newname = "Long64_t";
959-
} else if (type->GetType() == kULong64_t) {
960-
newname = "ULong64_t";
961-
}
969+
const char *newname = [type] {
970+
if (type->GetType() == kLong64_t)
971+
return "Long64_t";
972+
else if (type->GetType() == kULong64_t)
973+
return "ULong64_t";
974+
else
975+
return type->GetFullTypeName();
976+
}();
977+
962978
if (strcmp(inner,newname) == 0) {
963979
return true;
964980
}
965-
if (offset) result = "const ";
966-
result += newname;
967-
if ( end != tname.length() ) {
968-
result += tname.substr(end,tname.length()-end);
969-
}
981+
982+
// replace the core part of the original type name with newname
983+
result = tname;
984+
result.replace(result.find(innerBuf), innerBuf.length(), newname);
970985
if (result == tname) result.clear();
971986
return true;
972987
}
@@ -1010,6 +1025,20 @@ bool TClingLookupHelper__ExistingTypeCheck(const std::string &tname,
10101025
return false;
10111026
}
10121027

1028+
bool TClingLookupHelper__TClassTableCheck(const std::string &tname, std::string &result)
1029+
{
1030+
result.clear();
1031+
1032+
const auto innerBuf = StripModifiersAndArrayBrackets(tname);
1033+
const auto &inner = innerBuf.c_str();
1034+
1035+
if (gROOT->GetListOfClasses()->FindObject(inner) || TClassTable::Check(inner, result)) {
1036+
// This is a known class.
1037+
return true;
1038+
}
1039+
1040+
return false;
1041+
}
10131042
////////////////////////////////////////////////////////////////////////////////
10141043

10151044
TCling::TUniqueString::TUniqueString(Long64_t size)
@@ -1586,10 +1615,9 @@ TCling::TCling(const char *name, const char *title, const char* const argv[], vo
15861615

15871616
// We are now ready (enough is loaded) to init the list of opaque typedefs.
15881617
fNormalizedCtxt = new ROOT::TMetaUtils::TNormalizedCtxt(fInterpreter->getLookupHelper());
1589-
fLookupHelper = new ROOT::TMetaUtils::TClingLookupHelper(*fInterpreter, *fNormalizedCtxt,
1590-
TClingLookupHelper__ExistingTypeCheck,
1591-
TClingLookupHelper__AutoParse,
1592-
&fIsShuttingDown);
1618+
fLookupHelper = new ROOT::TMetaUtils::TClingLookupHelper(
1619+
*fInterpreter, *fNormalizedCtxt, TClingLookupHelper__ExistingTypeCheck, TClingLookupHelper__TClassTableCheck,
1620+
TClingLookupHelper__AutoParse, &fIsShuttingDown);
15931621
TClassEdit::Init(fLookupHelper);
15941622

15951623
// Disallow auto-parsing in rootcling

0 commit comments

Comments
 (0)