-
Notifications
You must be signed in to change notification settings - Fork 1.3k
[meta] Fix the crash in tutorial-analysis-dataframe-df014_CSVDataSource-py #18625
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
Another observation:
It looks like this PR is also speeding up the lookup of |
ab7332c
to
01d120c
Compare
@@ -3181,6 +3181,14 @@ TClass *TClass::GetClass(const char *name, Bool_t load, Bool_t silent, size_t hi | |||
if (normalizedName != name) { | |||
cl = (TClass*)gROOT->GetListOfClasses()->FindObject(normalizedName.c_str()); | |||
|
|||
if (!cl) { | |||
// The normalized name above might still contain the default template arguments |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That part is odd. Do we know why TClassEdit::GetNormalizedName
does not strip the default parameter in this case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Side note, default-Parameter-related issues:
#7470
#15996
https://its.cern.ch/jira/browse/ROOT-5862
And maybe other loosely related:
#18401
#8470
https://its.cern.ch/jira/browse/ROOT-10311
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#7470 Likely different as it is about the forward declaration written in the rootmap file
#15996 Is about the customization of which default argument to drop from the normalization name
#8470 Should not play a role here (it is about handling of unknown types)
its.cern.ch/jira/browse/ROOT-10311 is unrelated (it is about look for an non-existing type) and may have already been solved
its.cern.ch/jira/browse/ROOT-5862
#18401
might or might not be related.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the insight!
Side note:
ROOT-10311 is still an issue in master.
TTree::TTree constructor function gets auto-stripped into TTree (it thinks first one might be a namespace) and then GetClass("TTree") returns a valid pointer when it should not
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
puzzling.
root [0] string s="unordered_map<string,char>"
(std::string &) "unordered_map<string,char>"
root [1] string n
(std::string &) ""
root [2] TClassEdit::GetNormalizedName(n, s);
root [3] n
(std::string &) "unordered_map<string,char,hash<string>,equal_to<string>,allocator<pair<const string,char> > >"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Digging deeper. There is a difference in interpreted and compiled code. This snippet:
std::string s = "unordered_map<string,char>";
std::string n;
TClassEdit::GetNormalizedName(n, s);
std::cout << s << " --> " << n << std::endl;
if compiled in an executable prints unordered_map<string,char> --> unordered_map<string,char>
.
If interpreted, e.g. at the prompt, prints unordered_map<string,char> --> unordered_map<string,char,hash<string>,equal_to<string>,allocator<pair<const string,char> > >
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Digging deeper. There is a difference in interpreted and compiled code. This snippet:
std::string s = "unordered_map<string,char>"; std::string n; TClassEdit::GetNormalizedName(n, s); std::cout << s << " --> " << n << std::endl;
if compiled in an executable prints
unordered_map<string,char> --> unordered_map<string,char>
. If interpreted, e.g. at the prompt, printsunordered_map<string,char> --> unordered_map<string,char,hash<string>,equal_to<string>,allocator<pair<const string,char> > >
I see this, too, in a test, but the difference here is whether the test runs in isolation
./testClassEdit --gtest_filter=*TestName*
or whether it runs after the other tests:
./testClassEdit
I have a fix for when it runs in isolation, but I'm digging now for the case where other queries to the type system have happened before.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That part is odd. Do we know why
TClassEdit::GetNormalizedName
does not strip the default parameter in this case.
@pcanal Now we know, indeed:
- The trailing
const
in the allocator template was throwing off the type normalisation. - And the
__cxx11::basic_string
was preventing the normalisation tostring
.
Those two are solved now, and I get a consistent TClass now in this test (part of the PR now):
auto first = TClass::GetClass("unordered_map<string,char>", true, true);
std::unordered_map<std::string, char> map;
auto second = first->GetActualClass(&map);
EXPECT_EQ(first, second);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now there seems to be only one remaining issue, and I'm trying to find out if we can live with it. It's about the stripping (or not) of the default template arguments:
Digging deeper. There is a difference in interpreted and compiled code. This snippet:
std::string s = "unordered_map<string,char>"; std::string n; TClassEdit::GetNormalizedName(n, s); std::cout << s << " --> " << n << std::endl;
if compiled in an executable prints
unordered_map<string,char> --> unordered_map<string,char>
. If interpreted, e.g. at the prompt, printsunordered_map<string,char> --> unordered_map<string,char,hash<string>,equal_to<string>,allocator<pair<const string,char> > >
The code responsible is this:
root/core/foundation/src/TClassEdit.cxx
Lines 903 to 911 in b30a5f2
if (gInterpreterHelper) { | |
// See if the expanded name itself is a typedef. | |
std::string typeresult; | |
if (gInterpreterHelper->ExistingTypeCheck(norm_name, typeresult) | |
|| gInterpreterHelper->GetPartiallyDesugaredNameWithScopeHandling(norm_name, typeresult)) { | |
if (!typeresult.empty()) norm_name = typeresult; | |
} | |
} |
Before these lines, and with the changes of the PR applied, the unordered_map<very long list>
collapses to unordered_map<string,char>
, as the documentation of the functions claims:
root/core/foundation/src/TClassEdit.cxx
Lines 853 to 861 in b30a5f2
//////////////////////////////////////////////////////////////////////////////// | |
/// Return the normalized name. See TMetaUtils::GetNormalizedName. | |
/// | |
/// Return the type name normalized for ROOT, | |
/// keeping only the ROOT opaque typedef (Double32_t, etc.) and | |
/// removing the STL collections default parameter if any. | |
/// | |
/// Compare to TMetaUtils::GetNormalizedName, this routines does not | |
/// and can not add default template parameters. |
However, when gInterpreterHelper != nullptr
, the norm_name
runs through TCling, and comes back as unordered_map<string,char,hash<string>,equal_to<string>,allocator<pair<const string,char> > >
. This contradicts the docstring, but it explains the difference between interpreted and compiled code that Danilo observed.
This also explains why my test ./testClassEdit --gtest_filter=*TestName*
yields the short name, and ./testClassEdit
yields the long name with default arguments. Interestingly, the TClass lookup seems to work now in both cases, so the origin of the df104_CSVDataSource.py
crash seems to be fixed nevertheless.
That being said, I would like to ask @pcanal and @dpiparo to weigh in what the normalised name should be (see next conversation).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
std::string s = "unordered_map<string,char>";
std::string n;
TClassEdit::GetNormalizedName(n, s);
std::cout << s << " --> " << n << std::endl;
if compiled in an executable prints 1unordered_map<string,char> --> unordered_map<string,char>`.
If interpreted, e.g. at the prompt, prints 1unordered_map<string,char> --> unordered_map<string,char,hash,equal_to,allocator<pair<const string,char> > >1
Since this is GetNormalizedName
I would expect that the default template parameter to be dropped for unordered_map
.
Test Results 18 files 18 suites 3d 8h 44m 6s ⏱️ For more details on these failures, see this check. Results for commit fbecfcc. ♻️ This comment has been updated with latest results. |
01d120c
to
a7e167a
Compare
When looking up a TClass via name vs via typeid, the lookup proceeded without default template arguments in the former, and with default arguments in the latter case. This lead to a situation where tclass->GetActualClass(std::map<x,y>*) could delete itself while performing the lookup, since a new instance was created that replaced the existing instance. Change in TClassEdit: - Improve the handling of a trailing const in the allocator definition. - Make default allocator check more robust w.r.t. spaces in type definition. - Improve detection of std::basic_string, std::__cxx11::basic_string and similar types with/without const and std::
c56e27c
to
ef8617b
Compare
const auto target = "unordered_map<string,char>"; | ||
|
||
std::string out; | ||
TClassEdit::GetNormalizedName(out, in); | ||
EXPECT_STREQ(target, out.c_str()); | ||
|
||
TClassEdit::GetNormalizedName(out, in_cxx11); | ||
EXPECT_STREQ(target, out.c_str()); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@pcanal @dpiparo here is the test for the normalised name, which passes when the interpreterHelper isn't instantiated yet, but fails when it is.
In the former case, we get
unordered_map<string,char>
and in the latter:
unordered_map<string,char,hash<string>,equal_to<string>,allocator<pair<const string,char> > >
What should be the correct result?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unordered_map<string,char>
is the normalized name.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, in this case, after calling the interpreter, we just might have to fixup what it returns, because the type normalisation in TClingUtils explicitly specifies that it doesn't remove the default template parameters:
root/core/clingutils/src/TClingUtils.cxx
Lines 4130 to 4137 in b30a5f2
//////////////////////////////////////////////////////////////////////////////// | |
/// Return the type normalized for ROOT, | |
/// keeping only the ROOT opaque typedef (Double32_t, etc.) and | |
/// adding default template argument for all types except those explicitly | |
/// requested to be drop by the user. | |
/// Default template for STL collections are not yet removed by this routine. | |
clang::QualType ROOT::TMetaUtils::GetNormalizedType(const clang::QualType &type, const cling::Interpreter &interpreter, const TNormalizedCtxt &normCtxt) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right. This does not return the normalized name but the type which indeed might/should still have the default template arguments. If I remember correctly the are meant to be remove lexicographicaly in a higher level routine.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I.e GetNormalizedType
can/should return with the default parameter
but in the end GetNormalizedName
needs to drop them (for STL collections) both compiled and interpreted code.
This code should be unnecessary once name normalisation is fixed.
- Test that two TClass queries for unordered_map yield the same instance. - Add string type normalisation test both for __cxx11::basic_string and std::basic_string as suggested by Danilo.
ef8617b
to
fbecfcc
Compare
|
||
void RemoveSpace(std::string_view & s) | ||
{ | ||
while (!s.empty() && s[0] == ' ') s.remove_prefix(1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential optimization:
while (!s.empty() && s[0] == ' ') s.remove_prefix(1); | |
s.remove_prefix(std::min(s.find_first_not_of(" "), s.length())); |
@@ -48,6 +48,11 @@ namespace { | |||
gInterpreterHelper->ShuttingDownSignal(); | |||
} | |||
}; | |||
|
|||
void RemoveSpace(std::string_view & s) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
void RemoveSpace(std::string_view & s) | |
void RemoveLeadingSpaces(std::string_view & s) |
Also: could this function be maybe moved to class RStringUtils?
It could have a second argument delims = " " that is a space by default.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some minor optimization suggestions
@@ -641,9 +646,11 @@ bool TClassEdit::IsDefAlloc(const char *allocname, const char *classname) | |||
} | |||
a.remove_prefix(alloclen); | |||
|
|||
RemoveSpace(a); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
RemoveSpace(a); | |
RemoveLeadingSpaces(a); |
@@ -664,7 +671,9 @@ bool TClassEdit::IsDefAlloc(const char *allocname, const char *classname) | |||
a.remove_prefix(k.length()); | |||
} | |||
|
|||
if (a.compare(0,1,">")!=0 && a.compare(0,2," >")!=0) { | |||
RemoveSpace(a); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
RemoveSpace(a); | |
RemoveLeadingSpaces(a); |
@@ -682,6 +691,7 @@ bool TClassEdit::IsDefAlloc(const char *allocname, | |||
if (IsDefAlloc(allocname,keyclassname)) return true; | |||
|
|||
string_view a( allocname ); | |||
RemoveSpace(a); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
RemoveSpace(a); | |
RemoveLeadingSpaces(a); |
@@ -691,6 +701,7 @@ bool TClassEdit::IsDefAlloc(const char *allocname, | |||
} | |||
a.remove_prefix(alloclen); | |||
|
|||
RemoveSpace(a); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
RemoveSpace(a); | |
RemoveLeadingSpaces(a); |
@@ -704,6 +715,7 @@ bool TClassEdit::IsDefAlloc(const char *allocname, | |||
a.remove_prefix(constlen+1); | |||
} | |||
|
|||
RemoveSpace(a); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
RemoveSpace(a); | |
RemoveLeadingSpaces(a); |
} | ||
// Deal with a trailing const of the allocated type | ||
a.remove_prefix(k.length()); | ||
RemoveSpace(a); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
RemoveSpace(a); | |
RemoveLeadingSpaces(a); |
@@ -772,7 +782,9 @@ bool TClassEdit::IsDefAlloc(const char *allocname, | |||
a.remove_prefix(v.length()); | |||
} | |||
|
|||
if (a.compare(0,1,">")!=0 && a.compare(0,2," >")!=0) { | |||
RemoveSpace(a); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
RemoveSpace(a); | |
RemoveLeadingSpaces(a); |
if (a.compare(end+skipSpace,constlen,"const") == 0) { | ||
end += constlen+skipSpace; | ||
} | ||
// Deal with a trailing const of the allocated type |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If it's a "trailing" const, shouldn't it be remove_suffix rather than remove_prefix in the line below? (And then removeTrailingSpaces in the second-line below)
When looking up a TClass via name vs via typeid, the lookup proceeded
without default template arguments in the former, and with default
arguments in the latter case.
This lead to a situation where
tclass->GetActualClass(std::map<x,y>*)
could delete itself while performing the lookup, since a new instance
was created that replaced the existing instance.
In the df014 tutorial, cppyy was essentially calling
Before this PR,
first != second
, andfirst
was being deleted whilefirst->GetActualClass
was still running.Now, the class lookup in such a case is retried without the default STL arguments, which should lead to
first == second
in the above example. A test for this behaviour was added.Thanks to @pcanal for advice.