diff --git a/tree/ntuple/v7/src/RFieldBase.cxx b/tree/ntuple/v7/src/RFieldBase.cxx index b0dc6865ca61a..9cf3aff708708 100644 --- a/tree/ntuple/v7/src/RFieldBase.cxx +++ b/tree/ntuple/v7/src/RFieldBase.cxx @@ -594,7 +594,10 @@ ROOT::Experimental::RFieldBase::Create(const std::string &fieldName, const std:: if (!result) { auto cl = TClass::GetClass(canonicalType.c_str()); - if (cl != nullptr) { + // NOTE: if the class is not at least "Interpreted" we don't have enough information to + // properly construct the RClassField (e.g. we are missing the list of bases), so in that + // situation we rely on field emulation instead. + if (cl != nullptr && cl->GetState() >= TClass::kInterpreted) { createContextGuard.AddClassToStack(canonicalType); if (cl->GetCollectionProxy()) { result = std::make_unique(fieldName, canonicalType); diff --git a/tree/ntuple/v7/src/RFieldMeta.cxx b/tree/ntuple/v7/src/RFieldMeta.cxx index 82241c08ba519..8026ec85c4d49 100644 --- a/tree/ntuple/v7/src/RFieldMeta.cxx +++ b/tree/ntuple/v7/src/RFieldMeta.cxx @@ -73,6 +73,12 @@ ROOT::Experimental::RClassField::RClassField(std::string_view fieldName, std::st if (fClass == nullptr) { throw RException(R__FAIL("RField: no I/O support for type " + std::string(className))); } + if (fClass->GetState() < TClass::kInterpreted) { + throw RException(R__FAIL(std::string("RField: we don't have enough information about class '") + + std::string(className) + + " to construct a RClassField based on it (maybe it's an emulated class, or it was just" + " forward declared).")); + } // Avoid accidentally supporting std types through TClass. if (fClass->Property() & kIsDefinedInStd) { throw RException(R__FAIL(std::string(className) + " is not supported")); @@ -101,7 +107,9 @@ ROOT::Experimental::RClassField::RClassField(std::string_view fieldName, std::st fTraits |= kTraitTriviallyDestructible; int i = 0; - for (auto baseClass : ROOT::Detail::TRangeStaticCast(*fClass->GetListOfBases())) { + const auto *bases = fClass->GetListOfBases(); + assert(bases); + for (auto baseClass : ROOT::Detail::TRangeStaticCast(*bases)) { if (baseClass->GetDelta() < 0) { throw RException(R__FAIL(std::string("virtual inheritance is not supported: ") + std::string(className) + " virtually inherits from " + baseClass->GetName())); diff --git a/tree/ntuple/v7/test/ntuple_emulated.cxx b/tree/ntuple/v7/test/ntuple_emulated.cxx index 69627705ba68b..0f6340e1a1726 100644 --- a/tree/ntuple/v7/test/ntuple_emulated.cxx +++ b/tree/ntuple/v7/test/ntuple_emulated.cxx @@ -88,10 +88,19 @@ struct Outer_Simple { ASSERT_EQ(inner->GetSubFields()[0]->GetFieldName(), "fInt1"); } - // Now test loading entries with a reader + // Now test loading entries with a reader. + // NOTE: using a TFile-based reader exercises the code path where the user-defined type + // gets loaded as an Emulated TClass, which we must make sure we handle properly. RNTupleDescriptor::RCreateModelOptions cmOpts; cmOpts.fEmulateUnknownTypes = true; - reader = RNTupleReader::Open(cmOpts, "ntpl", fileGuard.GetPath()); + + ROOT::TestSupport::CheckDiagsRAII diagRAII; + diagRAII.optionalDiag(kWarning, "TClass::Init", "no dictionary for class", + /*matchFullMessage=*/false); + std::unique_ptr file(TFile::Open(fileGuard.GetPath().c_str())); + std::unique_ptr ntpl(file->Get("ntpl")); + reader = RNTupleReader::Open(cmOpts, *ntpl); + reader->LoadEntry(0); }