Repository: root-project/root (2026-07-20T09:12:15Z)

Issue:
[ntuple] double free when resizing small RVec
### Check duplicate issues.

- [ ] Checked for duplicates

### Description

Reading into a small RVec causes an invalid `free()` if the new length requires moving to a large RVec.

### Reproducer

Create an RVec not with RNTuple but through its standard constructor, e.g. inside a class or by explicit binding to an entry. Read more than the small vector space elements into the vector.

### ROOT version

master

### Installation method

from sources

### Operating system

n/a

### Additional context

_No response_


Discussion:
Hi @jblomer,

It appears this issue is closed, but wasn't yet added to a project. Please add upcoming versions that will include the fix, or 'not applicable' otherwise.

Sincerely,
:robot:




Patch:
diff --git a/tree/ntuple/src/RFieldSequenceContainer.cxx b/tree/ntuple/src/RFieldSequenceContainer.cxx
index 1d5a713532fe7..5cd9153897ee5 100644
--- a/tree/ntuple/src/RFieldSequenceContainer.cxx
+++ b/tree/ntuple/src/RFieldSequenceContainer.cxx
@@ -292,10 +292,7 @@ unsigned char *ROOT::RRVecField::ResizeRVec(void *rvec, std::size_t nItems, std:
       }
 
       // TODO Increment capacity by a factor rather than just enough to fit the elements.
-      if (owns) {
-         // *beginPtr points to the array of item values (allocated in an earlier call by the following malloc())
-         free(*beginPtr);
-      }
+      Internal::DestroyRVecWithChecks(itemField->GetAlignment(), beginPtr, capacityPtr);
       // We trust that malloc returns a buffer with large enough alignment.
       // This might not be the case if T in RVec<T> is over-aligned.
       *beginPtr = static_cast<unsigned char *>(malloc(nItems * itemSize));


Test patch:
diff --git a/tree/ntuple/test/rfield_vector.cxx b/tree/ntuple/test/rfield_vector.cxx
index 1056a01b7a727..af28671b25961 100644
--- a/tree/ntuple/test/rfield_vector.cxx
+++ b/tree/ntuple/test/rfield_vector.cxx
@@ -203,6 +203,39 @@ TEST(RNTuple, RVec)
    EXPECT_EQ(1.0, (*rdJetsAsStdVector)[0]);
 }
 
+TEST(RNTuple, RVecResizeSmall)
+{
+   FileRaii fileGuard("test_ntuple_rvec_small.root");
+
+   constexpr int N = 260; // more elements than fit in any small vector storage
+
+   {
+      auto model = RNTupleModel::Create();
+      auto v = model->MakeField<ROOT::RVec<int>>("v");
+      for (int i = 0; i < N; ++i) {
+         v->push_back(i);
+      }
+
+      auto writer = RNTupleWriter::Recreate(std::move(model), "ntpl", fileGuard.GetPath());
+      writer->Fill();
+   }
+
+   auto reader = RNTupleReader::Open("ntpl", fileGuard.GetPath());
+   EXPECT_EQ(1U, reader->GetNEntries());
+
+   ROOT::RVec<int> v;
+   EXPECT_TRUE(ROOT::Detail::VecOps::IsSmall(v));
+   auto e = reader->CreateEntry();
+   e->BindRawPtr("v", &v);
+
+   reader->LoadEntry(0, *e);
+   ASSERT_EQ(v.size(), N);
+   EXPECT_FALSE(ROOT::Detail::VecOps::IsSmall(v));
+   for (int i = 0; i < N; ++i) {
+      EXPECT_EQ(i, v[i]);
+   }
+}
+
 TEST(RNTuple, RVecTypeErased)
 {
    FileRaii fileGuard("test_ntuple_rvec_typeerased.root");