Commit 812aaf36 for tesseract
commit 812aaf36ca7b948cf670e68e42e782a124aaa715
Author: Stefan Weil <sw@weilnetz.de>
Date: Fri Nov 13 17:30:49 2026 +0100
matrix: keep the padding when a GENERIC_2D_ARRAY is copied
NetworkIO allocates its int8_t array with padding, because the SIMD
code for the matrix multiplication reads whole registers beyond the
last timestep. The copy assignment of GENERIC_2D_ARRAY resized the
destination without padding, so a copy (like *output = input in
Input::Forward()) could be read out of bounds by the following layer.
Remember the padding which was given to ResizeNoInit() and use it for
copies and moves.
Add a unit test.
Fix proposed by the Anthropic OSS Scanner (finding ANT-2026-MZ7IPJ7U).
Assisted-by: OpenCode / Claude Fable 5.2 (Anthropic)
Signed-off-by: Stefan Weil <sw@weilnetz.de>
diff --git a/src/ccstruct/matrix.h b/src/ccstruct/matrix.h
index 147634db..773027f7 100644
--- a/src/ccstruct/matrix.h
+++ b/src/ccstruct/matrix.h
@@ -77,19 +77,22 @@ public:
}
GENERIC_2D_ARRAY(GENERIC_2D_ARRAY<T> &&src) noexcept
: array_(src.array_), empty_(src.empty_), dim1_(src.dim1_), dim2_(src.dim2_),
- size_allocated_(src.size_allocated_) {
+ size_allocated_(src.size_allocated_), pad_(src.pad_) {
src.array_ = nullptr;
src.empty_ = static_cast<T>(0);
src.dim1_ = 0;
src.dim2_ = 0;
src.size_allocated_ = 0;
+ src.pad_ = 0;
}
virtual ~GENERIC_2D_ARRAY() {
delete[] array_;
}
void operator=(const GENERIC_2D_ARRAY<T> &src) {
- ResizeNoInit(src.dim1(), src.dim2());
+ // Keep the padding of src, as users of the copy may rely on it in the
+ // same way as users of src (eg the SIMD code reading an int8_t array).
+ ResizeNoInit(src.dim1(), src.dim2(), src.pad_);
int size = num_elements();
if (size > 0) {
memcpy(array_, src.array_, size * sizeof(array_[0]));
@@ -103,11 +106,13 @@ public:
dim1_ = src.dim1_;
dim2_ = src.dim2_;
size_allocated_ = src.size_allocated_;
+ pad_ = src.pad_;
src.array_ = nullptr;
src.empty_ = static_cast<T>(0);
src.dim1_ = 0;
src.dim2_ = 0;
src.size_allocated_ = 0;
+ src.pad_ = 0;
}
}
@@ -144,6 +149,7 @@ public:
}
dim1_ = size1;
dim2_ = size2;
+ pad_ = pad;
// Fill the padding data so it isn't uninitialized.
for (int i = new_size - pad; i < new_size; ++i) {
array_[i] = empty_;
@@ -178,6 +184,7 @@ public:
dim1_ = size1;
dim2_ = size2;
size_allocated_ = new_size;
+ pad_ = 0;
}
}
@@ -661,6 +668,9 @@ protected:
// needed. If Resize is used, memory is retained so it can be re-expanded
// without a further alloc, and this stores the allocated size.
int size_allocated_;
+ // The number of extra elements that ResizeNoInit provided beyond the bounds
+ // of the array (dim1_ * dim2_). A copy of the array gets the same padding.
+ int pad_ = 0;
};
// A generic class to store a banded triangular matrix with entries of type T.
diff --git a/unittest/matrix_test.cc b/unittest/matrix_test.cc
index d37bfab7..293ed0a7 100644
--- a/unittest/matrix_test.cc
+++ b/unittest/matrix_test.cc
@@ -136,4 +136,31 @@ TEST_F(MatrixTest, RotatingTranspose_0_2) {
EXPECT_EQ(6, m(15, 0));
}
+// Tests that a copy of an array keeps the padding requested by ResizeNoInit,
+// as NetworkIO relies on it for the SIMD code reading an int8_t array.
+TEST_F(MatrixTest, CopyKeepsPadding) {
+ class PaddedArray : public GENERIC_2D_ARRAY<int8_t> {
+ public:
+ int size_allocated() const {
+ return size_allocated_;
+ }
+ };
+ const int kDim1 = 5, kDim2 = 3, kPad = 31;
+ PaddedArray src;
+ src.ResizeNoInit(kDim1, kDim2, kPad);
+ src.Clear();
+ PaddedArray assigned;
+ assigned = src;
+ EXPECT_EQ(kDim1, assigned.dim1());
+ EXPECT_EQ(kDim2, assigned.dim2());
+ EXPECT_EQ(kDim1 * kDim2 + kPad, assigned.size_allocated());
+ PaddedArray constructed(src);
+ EXPECT_EQ(kDim1 * kDim2 + kPad, constructed.size_allocated());
+ // The padding also survives a move followed by a copy.
+ PaddedArray moved(std::move(src));
+ assigned = PaddedArray();
+ assigned = moved;
+ EXPECT_EQ(kDim1 * kDim2 + kPad, assigned.size_allocated());
+}
+
} // namespace tesseract