My application reads an object contained in a branch of a tree in a Root file and writes the same object in a tree inside an output file. I am seeing a crash, which I think I have tracked down to a change in the readout buffer of a field of my object.
To be more precise, the object has two TClonesArray* fields, call them A and B. The objects read from the input file have both A and B pointers non-null, but B has 0 elements (the file has been filled with objects made like this, I have no control over it). I read the objects as usual, i.e. I set the branch address for readout and thus A and B are set to point to some memory areas where the data read from file is put. I use the same buffer as the buffer for output.
When I read the first entry, A and B are set to a certain value, I can smoothly save the object on the output file and then go on with the second entry. Here I notice that while A stays the same (i.e. it points to the same memory area) the value of B changes: I interpret this as Root allocating a new memory area for the readout buffer and updating the related pointer:
Read entry 0: B = 0x1026db0, A = 0xf1c5e0
Read entry 1: B = 0xf4d3b0, A = 0xf1c5e0
The event is read witout problems, but now trying to save the object in the output file result in a crash. Using a debugger I have been able to see that the crash happens when the branch corresponding to B is written. the crash happens in TBranchElement.cxx at line 1604:
char **arr = (char **)clones->GetObjectRef(0);
where clones still is 0x1026db0 albeit the buffer has been moved from 0x1026db0 to 0xf4d3b0 (if I understand correctly).
So I think that my crash is due to this relocation of the input buffer. I investigated the Root code and I think I found the reason of this relocation. In TStreamerInfoReadBuffer.cxx at line 1074 I read:
switch (kase) {
case TStreamerInfo::kAnyp: // Class* not derived from TObject with comment field //->
case TStreamerInfo::kAnyp+TStreamerInfo::kOffsetL:
case TStreamerInfo::kObjectp: // Class* derived from TObject with comment field //->
case TStreamerInfo::kObjectp+TStreamerInfo::kOffsetL:
isPreAlloc = 1;
// Intentional fallthrough now that isPreAlloc is set.
case TStreamerInfo::kObjectP: // Class* derived from TObject with no comment field NOTE: Re-added by Phil
case TStreamerInfo::kObjectP+TStreamerInfo::kOffsetL:
case TStreamerInfo::kAnyP: // Class* not derived from TObject with no comment field NOTE:: Re-added by Phil
case TStreamerInfo::kAnyP+TStreamerInfo::kOffsetL: {
DOLOOP {
b.ReadFastArray((void**)(arr[k]+ioffset),cle,compinfo[i]->fLength,isPreAlloc,pstreamer);
}
}
When reading B, kase has value 64, i.e. TStreamerInfo::kObjectP. According to the comment, this corresponds to a class without a comment field, and this is how B is actually looking in the input file:
…
Br 25 :B : TClonesArray *
*Entries : 50 : Total Size= 4452 bytes File Size = 197 *
*Baskets : 1 : Basket Size= 32000 bytes Compression= 20.15 *
…
I’m not sure about this, but I think that this could be because B has always 0 entries. Anyway, inside the case the variable isPreAlloc is not set to 1 which results in a reallocation of the readout buffer inside TBufferFile::ReadFastArray.
Does this make sense? I am using Root 5.34.20. Thanks