Commit b31e8585 for tesseract
commit b31e8585de29a66e0806faae48befee283e97748
Author: Stefan Weil <sw@weilnetz.de>
Date: Fri Nov 13 17:30:59 2026 +0100
classify: avoid a float overflow in ReadParamDesc
Range and MidRange were calculated in float from the limits Min and
Max of a normproto file. For limits like -3e38 and 3e38 the difference
overflows, and the tesseract executable traps FE_OVERFLOW when it was
built with g++ (SIGFPE in Init()).
Calculate in double and clip the range to the range of float.
Add a unit test. ReadParamDesc() gets TESS_API for it, as the test
must also link with a shared library which hides its symbols.
Based on the fix proposed by the Anthropic OSS Scanner
(finding ANT-2026-0INCQPMJ).
Assisted-by: OpenCode / Claude Fable 5.2 (Anthropic)
Signed-off-by: Stefan Weil <sw@weilnetz.de>
diff --git a/src/classify/clusttool.cpp b/src/classify/clusttool.cpp
index e11c9374..d14d9971 100644
--- a/src/classify/clusttool.cpp
+++ b/src/classify/clusttool.cpp
@@ -19,6 +19,8 @@
#include "clusttool.h"
+#include <algorithm> // for std::clamp
+#include <cfloat> // for FLT_MAX
#include <cmath> // for M_PI, std::isnan
#include <locale> // for std::locale::classic
#include <sstream> // for std::stringstream
@@ -149,9 +151,14 @@ PARAM_DESC *ReadParamDesc(TFile *fp, uint16_t N) {
ASSERT_HOST(!stream.fail());
ParamDesc[i].Circular = (linear_token[0] == 'c');
ParamDesc[i].NonEssential = (essential_token[0] != 'e');
- ParamDesc[i].Range = ParamDesc[i].Max - ParamDesc[i].Min;
+ // Min and Max come from the file and can be any float value, so compute
+ // in double and clip the range to avoid a floating point overflow
+ // (which the tesseract program traps with SIGFPE).
+ const double min = ParamDesc[i].Min;
+ const double max = ParamDesc[i].Max;
+ ParamDesc[i].Range = std::clamp<double>(max - min, -FLT_MAX, FLT_MAX);
ParamDesc[i].HalfRange = ParamDesc[i].Range / 2;
- ParamDesc[i].MidRange = (ParamDesc[i].Max + ParamDesc[i].Min) / 2;
+ ParamDesc[i].MidRange = (max + min) / 2;
}
return (ParamDesc);
}
diff --git a/src/classify/clusttool.h b/src/classify/clusttool.h
index 6cca04af..c6b5edcc 100644
--- a/src/classify/clusttool.h
+++ b/src/classify/clusttool.h
@@ -28,6 +28,7 @@ namespace tesseract {
uint16_t ReadSampleSize(tesseract::TFile *fp);
+TESS_API
PARAM_DESC *ReadParamDesc(tesseract::TFile *fp, uint16_t N);
PROTOTYPE *ReadPrototype(tesseract::TFile *fp, uint16_t N);
diff --git a/unittest/normproto_test.cc b/unittest/normproto_test.cc
index b0f1b5c4..be94325f 100644
--- a/unittest/normproto_test.cc
+++ b/unittest/normproto_test.cc
@@ -18,9 +18,12 @@
#include "include_gunit.h"
#include "classify.h"
+#include "clusttool.h" // for ReadParamDesc
#include "serialis.h" // for TFile
#include "util/tempdir.h" // for CreateTempDir, RemoveTempDir
+#include <cfenv> // for std::feclearexcept, std::fetestexcept
+#include <cmath> // for std::isfinite
#include <cstdio>
#include <string>
#include <vector>
@@ -103,5 +106,22 @@ TEST_F(NormprotoTest, ReadsValidNormprotos) {
EXPECT_EQ(classifier_.NormProtos, nullptr);
}
+// Parameter limits whose difference or sum does not fit in a float must not
+// raise a floating point overflow (the tesseract program traps it).
+TEST(ParamDescTest, ToleratesHugeLimits) {
+ for (const char *limits : {"-3e38 3e38", "3e38 3e38", "3e38 -3e38", "-3e38 -3e38"}) {
+ std::string line = std::string("linear essential ") + limits + "\n";
+ TFile fp;
+ ASSERT_TRUE(fp.Open(line.data(), line.size()));
+ std::feclearexcept(FE_OVERFLOW);
+ PARAM_DESC *desc = ReadParamDesc(&fp, 1);
+ EXPECT_FALSE(std::fetestexcept(FE_OVERFLOW)) << limits;
+ EXPECT_TRUE(std::isfinite(desc->Range)) << limits;
+ EXPECT_TRUE(std::isfinite(desc->HalfRange)) << limits;
+ EXPECT_TRUE(std::isfinite(desc->MidRange)) << limits;
+ delete[] desc;
+ }
+}
+
} // namespace
} // namespace tesseract