Repository navigation
[cling] Do not suppress all diagnostics during function lookup - #20946
vepadulano wants to merge 2 commits into
Conversation
This is the first part of root-project#12449 Co-authored-by: Josh Bendavid <Josh.Bendavid@cern.ch>
Use std::array to better handle the case of `sizeof(ColumnTypes)... == 0`.
Previously, errors such as this one could be seen:
```
/Users/vpadulan/Programs/rootproject/rootbuild/bendavid-template-1-relwithdebinfo/include/RooAbsDataHelper.h:146:25: error: cannot deduce actual type for variable '__range2' with type 'auto &&' from initializer list
for (auto &&val : {static_cast<double>(values)...}) {
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/Users/vpadulan/Programs/rootproject/rootbuild/bendavid-template-1-relwithdebinfo/include/ROOT/RDF/RAction.hxx:117:85: note: in instantiation of function template specialization 'RooAbsDataHelper<RooDataSet>::Exec<>' requested here
ROOT::Internal::RDF::CallGuaranteedOrder{[&](auto &&...args) { return fHelper.Exec(slot, args...); },
^
[...]
/Users/vpadulan/Programs/rootproject/rootbuild/bendavid-template-1-relwithdebinfo/include/ROOT/RDF/RVariedAction.hxx:160:24: error: no matching member function for call to 'Exec'
fHelpers[varIdx].Exec(slot, GetValueChecked<ColTypes>(slot, varIdx, ReaderIdxs, entry)...);
~~~~~~~~~~~~~~~~~^~~~
[...]
/Users/vpadulan/Programs/rootproject/rootbuild/bendavid-template-1-relwithdebinfo/include/ROOT/RDF/RInterface.hxx:3579:14: note: in instantiation of function template specialization 'ROOT::RDF::RInterfaceBase::CreateAction<ROOT::Internal::RDF::ActionTags::Book, RooDataSet, ROOT::Detail::RDF::RLoopManager, RooAbsDataHelper<RooDataSet>, 0>' requested here
return CreateAction<RDFInternal::ActionTags::Book>(/*columns=*/{}, resPtr, hPtr, fProxiedPtr, 0u);
^
```
Hinting at the fact that a `CreateAction` specialization would try to instantiate the helper to `Book` without columns.
|
This PR is currently blocked by #20952 |
Test Results 22 files 22 suites 3d 23h 39m 25s ⏱️ For more details on these failures, see this check. Results for commit 25e1e2f. |
|
Following up on the question of a C++-only test. Here's a reproducer for the class of problems from #12449 that uses only C++ through cling. The
|
| step | 6.40.04 / master | master + this PR |
|---|---|---|
GetGlobalFunctionWithPrototype("call_helper", ...) |
❌ found | ✅ not found |
GetGlobalFunctionWithPrototype("needs_big", "char") |
❌ found | ✅ not found |
GetListOfMethods() of WithDefaults |
❌ lists get<int>() |
✅ not listed |
gInterpreter->GetFunction(0, "needs_big<char>") |
❌ found | ❌ found, also in a fresh session |
TMethodCall for call_helper, executed |
❌ valid; the wrapper fails to link (operator() unresolved), the compiler error is never shown, and it returns 0 |
✅ invalid |
TMethodCall for needs_big('x'), executed |
❌ runs and returns 1 despite the failing static_assert |
❌ same after the GetFunction call; ✅ invalid in a fresh session |
afterwards, ProcessLine("call_helper(gHelper);") |
❌ no compile error, only a JIT link failure | ❌ no compile error, only a link failure |
afterwards, ProcessLine("needs_big('x');") |
❌ silent | ❌ silent |
control: the same two ProcessLines in a fresh session |
✅ both errors reported | ✅ both errors reported |
GetFunction("needs_big<char>") with gDebug = 6 (lookup with diagnostics) |
error printed, function still returned | ❌ segfault in IncrementalParser::commitTransaction |
So on master these lookups accept instantiations whose bodies are ill-formed and never show the errors. The failing function can even run, as needs_big does. The broken instantiations also persist, so later ordinary compiled code that uses them no longer reports the errors either. The fresh-session control shows those errors normally appear.
This PR fixes the first three cases, but the reproducer shows some gaps:
- Errors are missed when the function name contains template arguments, as in
GetFunction("needs_big<char>"). Parsing such a name inParseWithShortcutscallssetSuppressAllDiagnostics(diagOnOff == NoDiagnostics), and theParserStateRAIIonly restores that at the end offindFunction. So suppression is still on while the body is instantiated. clang doesn't setErrorOccurredfor suppressed diagnostics; it only increments the trap counters. SohasErrorOccurred()can't see the error. Plain names such ascall_helperskip that parsing, which is why they're caught. - When such an error is detected, failing the transaction can crash. With diagnostics on (
gDebug > 5), the same lookup is detected, the current transaction (the lookup'sPushTransactionRAIIone) is marked failed, and popping it segfaults incommitTransaction. Plain master just prints the error there. - The rollback doesn't remove the failed instantiations. Later lookups and compiled code reuse them without re-checking, as in the
ProcessLinerows above. This is probably the "imperfect transaction rollback" mentioned in [WIP] Improved error handling for template instantiation #12449. - Rolling back the whole transaction can also unload valid instantiations created as side effects, and crash later. That's cling failure when accessing
std::mapelements from Python #20952; the full explanation is in this comment.
Unlike the earlier cases, these concern C++ users of ROOT's reflection, not Python: with the cppjit-based bindings, Python no longer goes through LookupHelper at all (#24053 handles that side in CppInterOp). If this gets fixed in cling, I'd suggest following the same approach as compiler-research/CppInterOp#1179: detect errors only for the one instantiation, with a DiagnosticErrorTrap, which also counts suppressed errors. Then mark that specialization invalid and reject it, instead of checking the global error flag and failing the whole transaction.
This comment was generated by Claude Opus 5.5.
|
To state the conclusion from the previous comment explicitly: as it stands, this PR introduces crashes through its transaction rollbacks that master doesn't have.
The common cause seems to be the approach rather than one bug. The PR decides based on the global error flag and then fails the whole current transaction. But cling's unloading of template instantiations is incomplete in both directions: it removes valid declarations that other code still refers to, and it leaves failed ones behind. So I don't think the PR is safe to merge as is. Scoping detection and rejection to the single instantiation, as in compiler-research/CppInterOp#1179, avoids rolling back anything. Limits of what I tested: all builds were RelWithDebInfo without assertions; the #20952 case needs a C++17 build, since in C++20 libstdc++'s This comment was generated by Claude Opus 5.5. |
This is the first part of #12449
I'm still trying to understand how to create a test. I used the original reproducer
And ran it with
root, but I see the same errors printed both after this changes and with ROOT master