Crash when filling Tree with void*

I’m trying to save a vector of a custom class using podio. The custom class is a class with only POD types (see below). I have reduced the code to the following (main.cpp):

using namespace std;

#include "ExampleHitData.h"
#include "TTree.h"
#include "TFile.h"
#include <vector>
#include <iostream>

int main () {

  ExampleHitData h {1, 2, 3, 4, 5};
  vector<ExampleHitData> v {h};

  TFile f("test.root", "RECREATE");
  TTree tree("test", "tree");

  void* ptr = &v;
  auto vptr = (std::vector<ExampleHitData>*)ptr;
  std::cout << "Size is " << vptr->size() << std::endl;
  std::cout << (*vptr)[0].cellID << " " << (*vptr)[0].x << " " << (*vptr)[0].y << " " << (*vptr)[0].z << " " << (*vptr)[0].energy << std::endl;

  tree.Branch("events", "std::vector<ExampleHitData>", ptr);
  // tree.Branch("events", "std::vector<ExampleHitData>", vptr); // This works fine

  tree.Fill();
  tree.Write();
  
}

The issue here is that when passing the void* ptr to tree.Branch, it will crash when filling but when passing the std::vector<ExampleHitData>* it works fine and the resulting file has all the fields with the expected values. The output of the couts is:

Size is 1
1 2 3 4 5

Full stack trace:

#0  0x00007fdc8d6ea707 in wait4 () from /usr/lib/libc.so.6
#1  0x00007fdc8d6627fb in ?? () from /usr/lib/libc.so.6
#2  0x00007fdc8f2eff29 in TUnixSystem::Exec (shellcmd=<optimized out>, this=0x559b3402d090) at /tmp/jmcarcell/spack-stage/spack-stage-root-master-t3bu3cb4mqxacnsbp2lifz3xukgested/spack-src/core/unix/src/TUnixSystem.cxx:2104
#3  TUnixSystem::StackTrace (this=0x559b3402d090) at /tmp/jmcarcell/spack-stage/spack-stage-root-master-t3bu3cb4mqxacnsbp2lifz3xukgested/spack-src/core/unix/src/TUnixSystem.cxx:2395
#4  0x00007fdc8f2ef8a4 in TUnixSystem::DispatchSignals (this=0x559b3402d090, sig=kSigSegmentationViolation) at /tmp/jmcarcell/spack-stage/spack-stage-root-master-t3bu3cb4mqxacnsbp2lifz3xukgested/spack-src/core/unix/src/TUnixSystem.cxx:3615
#5  <signal handler called>
#6  0x00007fdc8ebf0eec in TStreamerInfoActions::VectorLooper::WriteBasicType<unsigned long long> (buf=..., iter=0x1, end=0x4000000000000000, loopconfig=<optimized out>, config=<optimized out>) at /tmp/jmcarcell/spack-stage/spack-stage-root-master-t3bu3cb4mqxacnsbp2lifz3xukgested/spack-src/io/io/src/TStreamerInfoActions.cxx:1815
#7  0x00007fdc8eae789c in TStreamerInfoActions::TConfiguredAction::operator() (loopconf=0x559b3535d940, end_collection=0x4000000000000000, start_collection=0x1, buffer=..., this=0x559b3535d910) at /tmp/jmcarcell/spack-stage/spack-stage-root-master-t3bu3cb4mqxacnsbp2lifz3xukgested/spack-src/io/io/inc/TStreamerInfoActions.h:131
#8  TBufferFile::ApplySequence (this=0x559b3536f830, sequence=..., start_collection=0x1, end_collection=0x4000000000000000) at /tmp/jmcarcell/spack-stage/spack-stage-root-master-t3bu3cb4mqxacnsbp2lifz3xukgested/spack-src/io/io/src/TBufferFile.cxx:3646
#9  0x00007fdc8e3182ce in TBranchElement::FillLeavesCollectionMember (this=0x559b35359cb0, b=...) at /tmp/jmcarcell/spack-stage/spack-stage-root-master-t3bu3cb4mqxacnsbp2lifz3xukgested/spack-src/tree/tree/src/TBranchElement.cxx:1553
#10 0x00007fdc8e30f2b4 in TBranch::FillImpl (this=this
entry=0x559b35359cb0, imtHelper=imtHelper
entry=0x0) at /tmp/jmcarcell/spack-stage/spack-stage-root-master-t3bu3cb4mqxacnsbp2lifz3xukgested/spack-src/tree/tree/src/TBranch.cxx:893
#11 0x00007fdc8e31b85d in TBranchElement::FillImpl (this=0x559b35359cb0, imtHelper=0x0) at /tmp/jmcarcell/spack-stage/spack-stage-root-master-t3bu3cb4mqxacnsbp2lifz3xukgested/spack-src/tree/tree/src/TBranchElement.cxx:1265
#12 0x00007fdc8e31b5a6 in TBranchElement::FillImpl (this=0x559b3534cd50, imtHelper=0x0) at /tmp/jmcarcell/spack-stage/spack-stage-root-master-t3bu3cb4mqxacnsbp2lifz3xukgested/spack-src/tree/tree/src/TBranchElement.cxx:1290
#13 0x00007fdc8e387bb7 in TTree::Fill (this=0x7fff75fdd230) at /tmp/jmcarcell/spack-stage/spack-stage-root-master-t3bu3cb4mqxacnsbp2lifz3xukgested/spack-src/tree/tree/src/TTree.cxx:4609

It fails at TStreamerInfoActions::VectorLooper::WriteBasicType<unsigned long long> because unsigned long long is the type of the first member cellID in the ExampleHitData class.

I’m not sure if it’s a bug in ROOT but if ptr is a well defined void* to a vector why shouldn’t it work when the vector pointer vptr works?

To reproduce you need ExampleHitData.h:

class ExampleHitData {
public:
  unsigned long long cellID; ///< cellID
  double x;                  ///< x-coordinate
  double y;                  ///< y-coordinate
  double z;                  ///< z-coordinate
  double energy;             ///< measured energy deposit
};

and LinkDef.h

#ifdef __CLING__
#pragma link C++ class ExampleHitData;
#pragma link C++ class std::vector<ExampleHitData>;
#endif

and then run

rootcling -f ExampleHitData.cxx ExampleHitData.h LinkDef.h
gcc -c ExampleHitData.cxx -fPIC
gcc -shared -o libExampleHitData.so ExampleHitData.o
$(root-config --cxx --cflags --glibs) -L$PWD -lExampleHitData main.cpp
./a.out

ROOT Version: 6.28.00, 6.26.00 and also master from ~1 week ago
Platform: Centos7 and Arch Linux
Compiler: gcc 11.2.0 and 12.2.1

I think @pcanal can help you.

Any ideas on what may be going wrong @pcanal ?

Try:

#ifdef __CLING__
#pragma link C++ class ExampleHitData+;
#pragma link C++ class std::vector<ExampleHitData>+;
#endif

The trailing ‘+’ requires the ‘current’ StreamerInfo based I/O (rather than the ancient streamer based version).

The void* does not provide enough information for TTree::Branch to be smart.
With:

  vector<ExampleHitData> v {h};
  vector<ExampleHitData> *vp = &v;
  void *voidptr = vp;

The Branch can distinguish between

  tree.Branch("events_from_ptr_to_ptr", "std::vector<ExampleHitData>", &vp);
  tree.Branch("events_from_ptr_value", "std::vector<ExampleHitData>", vp);
  tree.Branch("events_from_ptr_value", "std::vector<ExampleHitData>", &v);

since we can distinguish (in C++ templated code) those 3 cases.

while with:

  tree.Branch("events", "std::vector<ExampleHitData>", voidptr);

We have no type information about the address given by voidptr and thus TTree::Branch must assume (and can not check) the historical convention that the value is the address of a pointer to the object. So this will work:

  tree.Branch("events", "std::vector<ExampleHitData>", &voidptr);

Side note/reminders :), the tree will remember that address is so in this example voidptr (the variable) needs to have a lifetime greater than the lifetime of the TTree object and if not, TTree::ResetBranchAddresses (or similar at the branch level) needs to be called to make the tree forget aboud the address of the pointer variable.

1 Like

Thanks, that was the answer. It’s certainly not very intuitive that you have to pass the address of the pointer instead of the pointer itself only in the case it’s a void*. I also don’t remember finding anything about this in the documentation, but maybe I missed it.

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.