From 12b64b0e2abd91f235cf3cc49df0f0f985aa0fee Mon Sep 17 00:00:00 2001 From: Stefan Dragnev Date: Thu, 23 Oct 2025 12:38:10 +0200 Subject: [PATCH] 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 --- modules/imgcodecs/src/grfmt_tiff.cpp | 121 +++++++++++++++++++++++---- modules/imgcodecs/test/test_tiff.cpp | 11 +++ 2 files changed, 118 insertions(+), 14 deletions(-) diff --git a/modules/imgcodecs/src/grfmt_tiff.cpp b/modules/imgcodecs/src/grfmt_tiff.cpp index 142ad559a0..2061f14900 100644 --- a/modules/imgcodecs/src/grfmt_tiff.cpp +++ b/modules/imgcodecs/src/grfmt_tiff.cpp @@ -52,10 +52,17 @@ #include "grfmt_tiff.hpp" #include +#include +#include #include "tiff.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 { @@ -78,30 +85,116 @@ static void cv_tiffCloseHandle(void* 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(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) { - if (cv::utils::logging::getLogLevel() < cv::utils::logging::LOG_LEVEL_DEBUG) - return; - // TODO cv::vformat() with va_list parameter - fprintf(stderr, "OpenCV TIFF: "); - if (module != NULL) - fprintf(stderr, "%s: ", module); - fprintf(stderr, "Warning, "); - vfprintf(stderr, fmt, ap); - fprintf(stderr, ".\n"); + (void) TIFF_Error(nullptr, nullptr, module, fmt, ap); +} + +static void cv_tiffWarningHandler(const char* module, const char* fmt, va_list ap) +{ + (void) TIFF_Warning(nullptr, nullptr, module, fmt, ap); } static bool cv_tiffSetErrorHandler_() { TIFFSetErrorHandler(cv_tiffErrorHandler); - TIFFSetWarningHandler(cv_tiffErrorHandler); + TIFFSetWarningHandler(cv_tiffWarningHandler); return true; } +#endif + static bool cv_tiffSetErrorHandler() { +#ifndef OCV_HAVE_TIFF_OPEN_OPTIONS static bool v = cv_tiffSetErrorHandler_(); return v; +#else + return true; +#endif } static const char fmtSignTiffII[] = "II\x2a\x00"; @@ -241,7 +334,7 @@ bool TiffDecoder::readHeader() { m_buf_pos = 0; TiffDecoderBufHelper* buf_helper = new TiffDecoderBufHelper(this->m_buf, this->m_buf_pos); - tif = TIFFClientOpen( "", "r", reinterpret_cast(buf_helper), &TiffDecoderBufHelper::read, + tif = cv_tiffClientOpen( "", "r", reinterpret_cast(buf_helper), &TiffDecoderBufHelper::read, &TiffDecoderBufHelper::write, &TiffDecoderBufHelper::seek, &TiffDecoderBufHelper::close, &TiffDecoderBufHelper::size, &TiffDecoderBufHelper::map, /*unmap=*/0 ); @@ -250,7 +343,7 @@ bool TiffDecoder::readHeader() } else { - tif = TIFFOpen(m_filename.c_str(), "r"); + tif = cv_tiffOpen(m_filename.c_str(), "r"); } if (tif) 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. // http://www.simplesystems.org/libtiff/functions/TIFFOpen.html - return TIFFClientOpen( "", "w", reinterpret_cast(this), &TiffEncoderBufHelper::read, + return cv_tiffClientOpen( "", "w", reinterpret_cast(this), &TiffEncoderBufHelper::read, &TiffEncoderBufHelper::write, &TiffEncoderBufHelper::seek, &TiffEncoderBufHelper::close, &TiffEncoderBufHelper::size, /*map=*/0, /*unmap=*/0 ); @@ -1210,7 +1303,7 @@ bool TiffEncoder::writeLibTiff( const std::vector& img_vec, const std::vect } else { - tif = TIFFOpen(m_filename.c_str(), "w"); + tif = cv_tiffOpen(m_filename.c_str(), "w"); } if (!tif) { diff --git a/modules/imgcodecs/test/test_tiff.cpp b/modules/imgcodecs/test/test_tiff.cpp index 8137d0aa2b..af995158b4 100644 --- a/modules/imgcodecs/test/test_tiff.cpp +++ b/modules/imgcodecs/test/test_tiff.cpp @@ -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 }} // namespace