Skip to content

[ntuple] Bad state after attempting to make field for virtual type #13910

Open

Description

@nsmith-

Check duplicate issues.

  • Checked for duplicates

Description

If a code attempts to create a field in an RNTuple for a virtual object type, it rightfully fails, but not with a very clear warning as to why (as compared to constructing the object on the interpreter). However, after the failure, types that should be OK to make fields from now fail. So I guess some internal data structure gets corrupted.

Reproducer

import ROOT

ROOT.gInterpreter.Declare("""\
class Abstract {
  public:
    Abstract() : data1_(3) {};
    virtual ~Abstract() {};

    virtual int method() = 0;

  private:
    int data1_;
};

class Derived : public Abstract {
  public:
    Derived() : Abstract() {};

    int method() override { return data2_; };

  private:
    int data2_;
};
""")

model = ROOT.Experimental.RNTupleModel.Create()

# Derived can be constructed without complaint
# obj1 = model.MakeField["Derived"]("test1")

# If we attempt (foolishly) to make Abstract, and
# catch the exception, then later attempts to make Derived now fail
# with puzzling JIT errors
try:
    obj1 = model.MakeField["Abstract"]("test1")
except ValueError as ex:
    print(ex)
obj2 = model.MakeField["Derived"]("test2")

ROOT version

master (6.31.01+)

Installation method

CMSSW IB (/cvmfs/cms-ib.cern.ch/sw/x86_64/nweek-02807/el8_amd64_gcc12/cms/cmssw/CMSSW_13_3_ROOT6_X_2023-10-18-2300)

Operating system

EL8

Additional context

No response

Activity

  1. nsmith- commented on Oct 20, 2023

    @nsmith-
    Author

    The same symptom also appears if I try to make a field for a class with no default constructor:

    import ROOT
    
    ROOT.gInterpreter.Declare("""\
    class NoDefaultCtor {
      public:
        NoDefaultCtor(int x) : data1_(x) {};
    
      private:
        int data1_;
    };
    """)
    
    model = ROOT.Experimental.RNTupleModel.Create()
    
    try:
        model.MakeField["NoDefaultCtor"]("test")
    except ValueError as ex:
        print(ex)
    
    # fails
    model.MakeField["std::vector<float>"]("test2")

    There, I even get

    cling JIT session error: Failed to materialize symbols: { (main, { _ZSt7forwardIRPN4ROOT12Experimental6Detail10RFieldBaseEEOT_RNSt16remove_referenceIS6_E4typeE }) }
    

    which is very weird, why would we be making the symbol

    ROOT::Experimental::Detail::RFieldBase*& std::forward<ROOT::Experimental::Detail::RFieldBase*&>(std::remove_reference<ROOT::Experimental::Detail::RFieldBase*&>::type&)
    
  2. changed the title [-][rntuple] Bad state after attempting to make field for virtual type[/-] [+][ntuple] Bad state after attempting to make field for virtual type[/+] on Oct 20, 2023
  3. moved this to Backlog in Fixathonon Feb 6, 2024
  4. hahnjo commented on Feb 14, 2024

    @hahnjo
    Member

    After a quick look, this appears to a be a DeclUnloader problem because of the failed transaction. On the C++ prompt, both cases (abstract class and class without default constructor) work, only some warnings (again related to DeclUnloader)...

  5. moved this from Backlog to In progress in Fixathonon Feb 14, 2024
  6. moved this from In progress to Backlog in Fixathonon Feb 14, 2024
  7. ferdymercury commented on Aug 6, 2025

    @ferdymercury
    Collaborator

    I tried both code snippets in ROOT master (debug build) and I do not see any crash, but there is no warning issued either.

  8. SamdaniSayam commented on Oct 23, 2026

    @SamdaniSayam

    I reproduced this on 6.40.04 and traced the remaining failure to RNTuple.

    • On 6.40.04 the later cascading JIT failure no longer reproduces, and the original Python repro no longer triggers because PyROOT creates bare models: MakeField does not construct a value. ROOT.Experimental.RNTupleModel is now ROOT.RNTupleModel.
    • In C++ with a default entry (RNTupleModel::Create()), MakeField<Abstract>() (or a class without a default constructor) makes TClass::New() JIT an invalid constructor wrapper. RClassField::ConstructValue() ignored the null return, so MakeField returned a non-null shared_ptr to unconstructed memory; destroying it segfaults in RClassDeleter.
    • The failed TClass::New compilation is the Cling transaction trigger from this issue.

    I opened #23806 with a fix: pre-check kIsAbstract/HasDefaultConstructor(kTRUE) before TClass::New, throw RException, free the buffer on failure, and make REntry::AddValue exception-safe. It includes a roottest regression. I validated it against 6.40.04 via a preload shim + ACLiC (pre-patch: bad pointer/crash; post-patch: clean exception and model still usable); no full rebuild here, so CI will be the first full-build run.

    For PyROOT users on current releases, a practical guard before MakeField is:

    cls = ROOT.TClass.GetClass("MyClass")
    if cls is None or (cls.Property() & 0x40) or not cls.HasDefaultConstructor(True):
        ...

    (0x40 is kIsAbstract, not exposed in Python.) For closed polymorphism, std::variant<ConcreteA, ConcreteB> is the supported RNTuple representation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions