Merge pull request #27927 from tailsu:tailsu/tiff-error-handler

tiff: use a per-TIFF error handler #27927

libtiff 4.5 introduced [per-TIFF error handlers](https://libtiff.gitlab.io/libtiff/functions/TIFFOpenOptions.html). This PR removes the global OpenCV error handlers and uses per-handle error handlers. This reduces any risks associated with modifying global state, e.g. if another library also tries to set the global error handlers and OpenCV clobbers them.

### Pull Request Readiness Checklist

See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request

- [x] I agree to contribute to the project under Apache 2 License.
- [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV
- [x] The PR is proposed to the proper branch
- [x] There is a reference to the original bug report and related work
- [x] There is accuracy test, performance test and test data in opencv_extra repository, if applicable
      Patch to opencv_extra has the same branch name.
- [x] The feature is well documented and sample code can be built with the project CMake
This commit is contained in:
Stefan Dragnev 2025-10-23 12:38:10 +02:00 committed by GitHub
parent 09c893477e
commit 12b64b0e2a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 118 additions and 14 deletions

View File

@ -52,10 +52,17 @@
#include "grfmt_tiff.hpp" #include "grfmt_tiff.hpp"
#include <limits> #include <limits>
#include <string>
#include <cstdarg>
#include "tiff.h" #include "tiff.h"
#include "tiffio.h" #include "tiffio.h"
#ifdef TIFFLIB_AT_LEAST
#if TIFFLIB_AT_LEAST(4, 5, 0)
#define OCV_HAVE_TIFF_OPEN_OPTIONS
#endif
#endif
namespace cv namespace cv
{ {
@ -78,30 +85,116 @@ static void cv_tiffCloseHandle(void* handle)
TIFFClose((TIFF*)handle); TIFFClose((TIFF*)handle);
} }
static std::string vformat(const char* fmt, va_list ap)
{
if (!fmt)
return {};
va_list ap_copy;
va_copy(ap_copy, ap);
const int len = std::vsnprintf(nullptr, 0, fmt, ap_copy);
va_end(ap_copy);
if (len < 0)
return fmt;
std::string buf(static_cast<size_t>(len) + 1, '\0');
std::vsnprintf(&buf[0], buf.size(), fmt, ap);
buf.pop_back();
return buf;
}
static std::string formatTiffMessage(const char* module, const char* fmt, va_list ap)
{
std::stringstream ss;
if (module && module[0] != '\0')
ss << module << ": ";
ss << cv::vformat(fmt, ap);
return ss.str();
}
static int TIFF_Error(TIFF *, void *, const char* module, const char* fmt, va_list ap)
{
CV_LOG_ERROR(NULL, formatTiffMessage(module, fmt, ap));
return 1;
}
static int TIFF_Warning(TIFF *, void *, const char* module, const char* fmt, va_list ap)
{
CV_LOG_WARNING(NULL, formatTiffMessage(module, fmt, ap));
return 1;
}
#ifdef OCV_HAVE_TIFF_OPEN_OPTIONS
static TIFFOpenOptions* cv_tiffCreateOptions()
{
auto opts = TIFFOpenOptionsAlloc();
TIFFOpenOptionsSetErrorHandlerExtR(opts, &TIFF_Error, nullptr);
TIFFOpenOptionsSetWarningHandlerExtR(opts, &TIFF_Warning, nullptr);
#if TIFFLIB_AT_LEAST(4, 7, 1)
TIFFOpenOptionsSetWarnAboutUnknownTags(opts, 1);
#endif
return opts;
}
#endif
static TIFF* cv_tiffOpen(const char* filename, const char* mode)
{
#ifdef OCV_HAVE_TIFF_OPEN_OPTIONS
auto opts = cv_tiffCreateOptions();
auto tiff = TIFFOpenExt(filename, mode, opts);
TIFFOpenOptionsFree(opts);
return tiff;
#else
return TIFFOpen(filename, mode);
#endif
}
static TIFF* cv_tiffClientOpen(const char* name, const char* mode, thandle_t clientdata,
TIFFReadWriteProc readproc, TIFFReadWriteProc writeproc,
TIFFSeekProc seekproc, TIFFCloseProc closeproc,
TIFFSizeProc sizeproc, TIFFMapFileProc mapproc,
TIFFUnmapFileProc unmapproc)
{
#ifdef OCV_HAVE_TIFF_OPEN_OPTIONS
auto opts = cv_tiffCreateOptions();
auto tiff = TIFFClientOpenExt(name, mode, clientdata, readproc, writeproc,
seekproc, closeproc, sizeproc, mapproc, unmapproc, opts);
TIFFOpenOptionsFree(opts);
return tiff;
#else
return TIFFClientOpen(name, mode, clientdata, readproc, writeproc,
seekproc, closeproc, sizeproc, mapproc, unmapproc);
#endif
}
#ifndef OCV_HAVE_TIFF_OPEN_OPTIONS
static void cv_tiffErrorHandler(const char* module, const char* fmt, va_list ap) static void cv_tiffErrorHandler(const char* module, const char* fmt, va_list ap)
{ {
if (cv::utils::logging::getLogLevel() < cv::utils::logging::LOG_LEVEL_DEBUG) (void) TIFF_Error(nullptr, nullptr, module, fmt, ap);
return; }
// TODO cv::vformat() with va_list parameter
fprintf(stderr, "OpenCV TIFF: "); static void cv_tiffWarningHandler(const char* module, const char* fmt, va_list ap)
if (module != NULL) {
fprintf(stderr, "%s: ", module); (void) TIFF_Warning(nullptr, nullptr, module, fmt, ap);
fprintf(stderr, "Warning, ");
vfprintf(stderr, fmt, ap);
fprintf(stderr, ".\n");
} }
static bool cv_tiffSetErrorHandler_() static bool cv_tiffSetErrorHandler_()
{ {
TIFFSetErrorHandler(cv_tiffErrorHandler); TIFFSetErrorHandler(cv_tiffErrorHandler);
TIFFSetWarningHandler(cv_tiffErrorHandler); TIFFSetWarningHandler(cv_tiffWarningHandler);
return true; return true;
} }
#endif
static bool cv_tiffSetErrorHandler() static bool cv_tiffSetErrorHandler()
{ {
#ifndef OCV_HAVE_TIFF_OPEN_OPTIONS
static bool v = cv_tiffSetErrorHandler_(); static bool v = cv_tiffSetErrorHandler_();
return v; return v;
#else
return true;
#endif
} }
static const char fmtSignTiffII[] = "II\x2a\x00"; static const char fmtSignTiffII[] = "II\x2a\x00";
@ -241,7 +334,7 @@ bool TiffDecoder::readHeader()
{ {
m_buf_pos = 0; m_buf_pos = 0;
TiffDecoderBufHelper* buf_helper = new TiffDecoderBufHelper(this->m_buf, this->m_buf_pos); TiffDecoderBufHelper* buf_helper = new TiffDecoderBufHelper(this->m_buf, this->m_buf_pos);
tif = TIFFClientOpen( "", "r", reinterpret_cast<thandle_t>(buf_helper), &TiffDecoderBufHelper::read, tif = cv_tiffClientOpen( "", "r", reinterpret_cast<thandle_t>(buf_helper), &TiffDecoderBufHelper::read,
&TiffDecoderBufHelper::write, &TiffDecoderBufHelper::seek, &TiffDecoderBufHelper::write, &TiffDecoderBufHelper::seek,
&TiffDecoderBufHelper::close, &TiffDecoderBufHelper::size, &TiffDecoderBufHelper::close, &TiffDecoderBufHelper::size,
&TiffDecoderBufHelper::map, /*unmap=*/0 ); &TiffDecoderBufHelper::map, /*unmap=*/0 );
@ -250,7 +343,7 @@ bool TiffDecoder::readHeader()
} }
else else
{ {
tif = TIFFOpen(m_filename.c_str(), "r"); tif = cv_tiffOpen(m_filename.c_str(), "r");
} }
if (tif) if (tif)
m_tif.reset(tif, cv_tiffCloseHandle); m_tif.reset(tif, cv_tiffCloseHandle);
@ -1113,7 +1206,7 @@ public:
{ {
// do NOT put "wb" as the mode, because the b means "big endian" mode, not "binary" mode. // do NOT put "wb" as the mode, because the b means "big endian" mode, not "binary" mode.
// http://www.simplesystems.org/libtiff/functions/TIFFOpen.html // http://www.simplesystems.org/libtiff/functions/TIFFOpen.html
return TIFFClientOpen( "", "w", reinterpret_cast<thandle_t>(this), &TiffEncoderBufHelper::read, return cv_tiffClientOpen( "", "w", reinterpret_cast<thandle_t>(this), &TiffEncoderBufHelper::read,
&TiffEncoderBufHelper::write, &TiffEncoderBufHelper::seek, &TiffEncoderBufHelper::write, &TiffEncoderBufHelper::seek,
&TiffEncoderBufHelper::close, &TiffEncoderBufHelper::size, &TiffEncoderBufHelper::close, &TiffEncoderBufHelper::size,
/*map=*/0, /*unmap=*/0 ); /*map=*/0, /*unmap=*/0 );
@ -1210,7 +1303,7 @@ bool TiffEncoder::writeLibTiff( const std::vector<Mat>& img_vec, const std::vect
} }
else else
{ {
tif = TIFFOpen(m_filename.c_str(), "w"); tif = cv_tiffOpen(m_filename.c_str(), "w");
} }
if (!tif) if (!tif)
{ {

View File

@ -1282,6 +1282,17 @@ TEST(Imgcodecs_Tiff, read_bigtiff_images)
} }
} }
TEST(Imgcodecs_Tiff, read_junk) {
// Test exercises the tiff error handler integration.
// Error messages can be seen with OPENCV_LOG_LEVEL=DEBUG
const char junk[] = "II\x2a\x00\x08\x00\x00\x00\x00\x00\x00\x00";
cv::Mat junkInputArray(1, sizeof(junk) - 1, CV_8UC1, (void*)junk);
cv::Mat img;
ASSERT_NO_THROW(img = cv::imdecode(junkInputArray, IMREAD_UNCHANGED));
ASSERT_TRUE(img.empty());
}
#endif #endif
}} // namespace }} // namespace