diff --git a/tree/ntuple/v7/src/RFieldBase.cxx b/tree/ntuple/v7/src/RFieldBase.cxx index 58fe4c4e39312..4dd2a2fc2ec40 100644 --- a/tree/ntuple/v7/src/RFieldBase.cxx +++ b/tree/ntuple/v7/src/RFieldBase.cxx @@ -594,7 +594,11 @@ 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 currently don't try to construct + // the RClassField, as in that case we'd need to fetch the information from the StreamerInfo + // rather than from TClass. This might be desirable in the future, but for now in this + // 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 092342e50399d..fc8305365e306 100644 --- a/tree/ntuple/v7/src/RFieldMeta.cxx +++ b/tree/ntuple/v7/src/RFieldMeta.cxx @@ -74,6 +74,10 @@ 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: RClassField \"") + std::string(className) + + " cannot be constructed from a class that's not at least Interpreted")); + } // Avoid accidentally supporting std types through TClass. if (fClass->Property() & kIsDefinedInStd) { throw RException(R__FAIL(std::string(className) + " is not supported")); @@ -102,7 +106,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 a91373ca07b4c..932f8855f20f9 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); }