From 42ff11a674552980e3eef988322d0e13517fe808 Mon Sep 17 00:00:00 2001 From: Giulio Eulisse <10544+ktf@users.noreply.github.com> Date: Sat, 7 Nov 2026 07:55:29 +0100 Subject: [PATCH] CCDB: read std::array objects written by libstdc++ on libc++ builds Needs review. The only member of std::array is T _M_elems[N] in libstdc++ and T __elems_[N] in libc++, so the StreamerInfo of an array written on Linux has a checksum (0x9189320d for array) that the macOS class does not have (0x919bc501). When the file is opened, TStreamerInfo::BuildCheck (ROOT 6.40.04, "perfect alias" block, since 2014) drops the on-file StreamerInfo as an alias of the in-memory one, and the read then fails: Error in : Could not find the StreamerInfo with a checksum of 0x9189320d for the class "array" in name. Error in : object of class array read too few bytes: 6 instead of 2166 leaving the array zero-filled. TRD/Calib/DCSDPsFedChamberStatus is such an object: every chamber looks off, the TRD digitizer writes 0 digits, and on macOS the full system test has no TRD and no CTF. If the in-memory StreamerInfo already exists when the file is opened, ROOT registers the on-file one but cannot map _M_elems, and reads zeros without any error. Before reading an array<...> object, DataRefUtils::registerLibstdcxxStdArray registers the on-file layout as a class version of its own, with its member renamed to the in-memory name, so ROOT finds it by checksum and reads the T[N] payload, identical in both libraries, as usual. It is called from both copies of extractFromTFile: CcdbApi (CcdbApi/BasicCCDBManager, DPL CCDB fetcher) and DataRefUtils (DPL condition inputs). Everything is inside #if defined(_LIBCPP_VERSION): libstdc++ builds (EPN, Grid, CI) compile exactly the same code as before. Verified on macOS 27.2, ROOT 6.40.04-alice: - BasicCCDBManager read of DCSDPsFedChamberStatus at 1550600800000: 540 zeros before, 521x3 + 19x0 after, the same as decoding the payload by hand; at 1720000000000: 431x3, 89x4, 2x5, 18x0. - array (TRD/Calib/DCSDPsFedCFGtag): 540 empty strings before, the real tags after. - TRD-only o2-sim-digitizer-workflow on the 10-event PbPb FST sample: 0 digits before, 126921 after. Plain-ROOT reproducer for the ROOT team: ~/src/data/ccdb-stdarray-libcxx-repro. --- CCDB/src/CcdbApi.cxx | 6 +++ .../Core/include/Framework/DataRefUtils.h | 8 ++++ Framework/Core/src/DataRefUtils.cxx | 39 +++++++++++++++++++ 3 files changed, 53 insertions(+) diff --git a/CCDB/src/CcdbApi.cxx b/CCDB/src/CcdbApi.cxx index bb9af397d527b..6ba38ea34ff50 100644 --- a/CCDB/src/CcdbApi.cxx +++ b/CCDB/src/CcdbApi.cxx @@ -51,6 +51,9 @@ #include #include #include +#if defined(_LIBCPP_VERSION) +#include "Framework/DataRefUtils.h" +#endif #include #include "rapidjson/document.h" #include "rapidjson/writer.h" @@ -986,6 +989,9 @@ void* CcdbApi::extractFromTFile(TFile& file, TClass const* cl, const char* what) if (!cl) { return nullptr; } +#if defined(_LIBCPP_VERSION) + o2::framework::DataRefUtils::registerLibstdcxxStdArray(file, cl); +#endif auto object = file.GetObjectChecked(what, cl); if (!object) { // it could be that object was stored with previous convention diff --git a/Framework/Core/include/Framework/DataRefUtils.h b/Framework/Core/include/Framework/DataRefUtils.h index 498b4eb5f7a87..fce3435f66299 100644 --- a/Framework/Core/include/Framework/DataRefUtils.h +++ b/Framework/Core/include/Framework/DataRefUtils.h @@ -31,6 +31,10 @@ namespace o2::conf { class ConfigurableParam; } +#if defined(_LIBCPP_VERSION) +class TClass; +class TFile; +#endif namespace o2::framework { @@ -185,6 +189,10 @@ struct DataRefUtils { // Decode a CCDB object using the CcdbApi. static void* decodeCCDB(DataRef const& ref, std::type_info const& info); static std::map extractCCDBHeaders(DataRef const& ref); +#if defined(_LIBCPP_VERSION) + /// Make a std::array written with libstdc++ readable from file by this libc++ build. + static void registerLibstdcxxStdArray(TFile& file, TClass const* cl); +#endif /// Return a span over the raw CCDB payload bytes, stripping the flattened HTTP /// headers footer that CCDBFetcherHelper appends. Use this when the CCDB entry is /// a binary blob rather than a ROOT-serialised object. diff --git a/Framework/Core/src/DataRefUtils.cxx b/Framework/Core/src/DataRefUtils.cxx index 894ad0b8ace63..0b5aee4287abb 100644 --- a/Framework/Core/src/DataRefUtils.cxx +++ b/Framework/Core/src/DataRefUtils.cxx @@ -20,6 +20,11 @@ #include #include #include +#if defined(_LIBCPP_VERSION) +#include +#include +#include +#endif namespace o2::framework { @@ -31,6 +36,9 @@ void* extractFromTFile(TFile& file, TClass const* cl, const char* what) if (!cl) { return nullptr; } +#if defined(_LIBCPP_VERSION) + DataRefUtils::registerLibstdcxxStdArray(file, cl); +#endif auto object = file.GetObjectChecked(what, cl); if (!object) { // it could be that object was stored with previous convention @@ -73,6 +81,37 @@ void* extractFromTFile(TFile& file, TClass const* cl, const char* what) return result; } } // namespace + +#if defined(_LIBCPP_VERSION) +// The only member of std::array is T _M_elems[N] in libstdc++, T __elems_[N] in libc++, so +// objects written on Linux carry a StreamerInfo with a checksum the libc++ class does not have. +// When opening the file, ROOT drops that StreamerInfo as an alias of the in-memory one +// (TStreamerInfo::BuildCheck), and the read fails with "Could not find the StreamerInfo with a +// checksum", leaving the array zero-filled. Register the on-file layout as a version of its own, +// under the in-memory member name, so that the payload, T[N] for both, is read as usual. +void DataRefUtils::registerLibstdcxxStdArray(TFile& file, TClass const* cl) +{ + auto* infos = std::strncmp(cl->GetName(), "array<", 6) == 0 ? file.GetStreamerInfoCache() : nullptr; + auto* onfile = infos ? dynamic_cast(infos->FindObject(cl->GetName())) : nullptr; + if (!onfile || onfile->GetNelement() != 1 || std::strcmp(onfile->GetElement(0)->GetName(), "_M_elems") != 0) { + return; + } + // Exists before BuildCheck, so that the on-file layout gets a version of its own + auto* current = static_cast(const_cast(cl)->GetStreamerInfo()); + auto* info = static_cast(cl->FindStreamerInfo(onfile->GetCheckSum())); + if (!info) { + info = static_cast(onfile->Clone()); + info->BuildCheck(&file); + } + // Also when ROOT registered it itself: it then reads nothing, without any error + if (std::strcmp(info->GetElement(0)->GetName(), current->GetElement(0)->GetName()) != 0) { + info->GetElement(0)->SetName(current->GetElement(0)->GetName()); + info->Clear("build"); + info->BuildOld(); + } +} +#endif + // Adapted from CcdbApi private method interpretAsTMemFileAndExtract // If the former is moved to public, throws on error and could be changed to // not require a mutex we could use it.