From 29aa52bfe6c789c2df7b9cb373a814584e79bd9d Mon Sep 17 00:00:00 2001 From: Jonas Rembser Date: Sat, 9 May 2026 15:47:23 +0200 Subject: [PATCH] [df] Throw on JIT helper compilation failure instead of crashing Check the EErrorCode returned by `ProcessLine()` and verify that each helper's `DeclId_t` is non-null, throwing `std::runtime_error` in either case so users get a proper exception instead of a hard crash. Closes #21659 --- tree/dataframe/src/RLoopManager.cxx | 25 ++++++++++++++++---- tree/dataframe/test/dataframe_vary.cxx | 32 +++++++++++++++++++++++++- 2 files changed, 52 insertions(+), 5 deletions(-) diff --git a/tree/dataframe/src/RLoopManager.cxx b/tree/dataframe/src/RLoopManager.cxx index ceb9a6a996664..4479f2a4d9590 100644 --- a/tree/dataframe/src/RLoopManager.cxx +++ b/tree/dataframe/src/RLoopManager.cxx @@ -125,7 +125,14 @@ void DeclareAndRetrieveDeferredJitCalls(const std::string &codeToDeclare) // error: 'MyHelperType' is an incomplete type // return std::make_unique(Helper_t(std::move(*h)), bl, std::move(prevNode), colRegister); // ^ - gInterpreter->ProcessLine(codeToDeclare.c_str()); + TInterpreter::EErrorCode interpErrorCode(TInterpreter::kNoError); + gInterpreter->ProcessLine(codeToDeclare.c_str(), &interpErrorCode); + if (interpErrorCode != TInterpreter::kNoError) { + throw std::runtime_error( + "\nAn error occurred during just-in-time compilation in RLoopManager::Run. The lines above might " + "indicate the cause of the error.\nAll RDF objects that have not run their event loop yet should be " + "considered in an invalid state.\n"); + } // Step 2: Retrieve the declared functions as function pointers, cache them // for later use in RunDeferredCalls @@ -139,9 +146,19 @@ void DeclareAndRetrieveDeferredJitCalls(const std::string &codeToDeclare) // fast fetch of the address via gInterpreter // (faster than gInterpreter->Evaluate(function name, ret), ret->GetAsPointer()) // Retrieve the JIT helper function we registered via RegisterJitHelperCall - auto declid = - gInterpreter->GetFunction(clinfo, ("jitNodeRegistrator_" + std::to_string(codeAndId.second)).c_str()); - assert(declid); + const std::string funcName = "jitNodeRegistrator_" + std::to_string(codeAndId.second); + auto declid = gInterpreter->GetFunction(clinfo, funcName.c_str()); + if (!declid) { + // The interpreter failed to compile the helper. Without this check + // we would later dereference a null function pointer and crash. + gInterpreter->ClassInfo_Delete(clinfo); + throw std::runtime_error( + "\nAn error occurred during just-in-time compilation in RLoopManager::Run: failed to retrieve " + "the JIT helper function '" + + funcName + + "'. The lines above might indicate the cause of the error.\nAll RDF objects that have not run " + "their event loop yet should be considered in an invalid state.\n"); + } auto minfo = gInterpreter->MethodInfo_Factory(declid); assert(gInterpreter->MethodInfo_IsValid(minfo)); auto mname = gInterpreter->MethodInfo_GetMangledName(minfo); diff --git a/tree/dataframe/test/dataframe_vary.cxx b/tree/dataframe/test/dataframe_vary.cxx index 1e8f711f8773c..4d4a990eb2ed7 100644 --- a/tree/dataframe/test/dataframe_vary.cxx +++ b/tree/dataframe/test/dataframe_vary.cxx @@ -199,6 +199,36 @@ TEST(RDFVary, RequireVariationsHaveConsistentTypeJitted) std::runtime_error); } } + +// Regression test: when a deferred JIT helper fails to compile (e.g. the +// multi-column Vary() overload was picked with a single-column expression, +// yielding an ill-formed template instantiation), RDataFrame must throw +// instead of dereferencing a null function pointer at event-loop time. +// +// Run in a forked subprocess via EXPECT_EXIT, because the failed cling +// declaration leaves the interpreter in a state from which subsequent +// JIT compilations cannot recover (it would crash later tests). +TEST(RDFVaryDeathTest, JitFailureThrowsInsteadOfCrashing) +{ + ::testing::FLAGS_gtest_death_test_style = "threadsafe"; + EXPECT_EXIT( + { + try { + auto df = ROOT::RDataFrame(10).Define("x", "1.f * rdfentry_"); + // Multi-column overload with a single-RVec expression: the + // resulting RVariation<..., IsSingleColumn=false> is ill-formed. + auto bad = df.Vary(std::vector{"x"}, "ROOT::RVecF{x - 0.5f, x + 0.5f}", + std::vector{"down", "up"}, "xVar"); + bad.Count().GetValue(); + std::exit(2); // no exception thrown: unexpected + } catch (const std::runtime_error &) { + std::exit(0); // expected path + } catch (...) { + std::exit(3); // wrong exception type + } + }, + ::testing::ExitedWithCode(0), ""); +} #endif #endif @@ -1917,4 +1947,4 @@ TEST_P(RDFVary, JittedVaryEmptyString) throw; }, std::runtime_error); -} \ No newline at end of file +}