Fix usage of 'volatile' in src/Fl_JPEG_Image.cxx (#1207)
Build and Test / build-linux (push) Has been cancelled
Build and Test / build-wayland (push) Has been cancelled
Build and Test / build-macos (push) Has been cancelled
Build and Test / build-windows (push) Has been cancelled

- allocate a new struct 'load_stat' on the heap
- use struct load_stat to open image file (fp) and for error counters

The previous fix of #1207 unfortunately decremented volatile variables
which caused (plausible) compiler warnings by clang.
This commit is contained in:
Albrecht Schlosser
2025-06-06 16:10:35 +02:00
parent 5293beea6f
commit 340caa2dc3
+35 -40
View File
@@ -206,11 +206,33 @@ static void jpeg_unprotected_mem_src(j_decompress_ptr cinfo, const unsigned char
}
#endif // HAVE_LIBJPEG
// The following struct is used to control reading an image file and some
// error conditions. It *must* be allocated on the heap (not as a local/stack
// variable) because otherwise the macOS/clang optimizer would optimize it
// away for some unknown reason. See GitHub Issue #1207.
// This struct also includes `FILE *fp` which had been allocated on the heap
// previously for a similar reason (gcc: [-Wclobbered]).
struct load_stat {
FILE *fp; // file pointer if reading from disk
int max_finish_decompress_err; // count errors and give up after a while
int max_destroy_decompress_err; // ... to avoid recursion and deadlock
load_stat() { // c'tor
fp = nullptr;
max_finish_decompress_err = 5;
max_destroy_decompress_err = 5;
}
~load_stat() { // d'tor
if (fp)
fclose(fp);
}
};
/*
This method reads JPEG image data and creates an RGB or grayscale image.
To avoid code duplication, we set filename if we want to read form a file or
data to read from memory instead. Sharename can be set if the image is
To avoid code duplication, we set filename if we want to read from a file
or data to read from memory instead. Sharename can be set if the image is
supposed to be added to the Fl_Shared_Image list.
*/
void Fl_JPEG_Image::load_jpg_(const char *filename, const char *sharename, const unsigned char *data, int data_length)
@@ -220,18 +242,7 @@ void Fl_JPEG_Image::load_jpg_(const char *filename, const char *sharename, const
fl_jpeg_error_mgr jerr; // Error handler info
JSAMPROW row; // Sample row pointer
// The following variables are pointers allocating some private space that
// is not reset by 'setjmp()'. At least under macOS, it's necessay to make
// these variables volatile to avoid errors occurring when compiled with -O1 (issue #1207).
volatile char* max_finish_decompress_err; // count errors and give up after a while
volatile char* max_destroy_decompress_err; // to avoid recursion and deadlock
// Note: The file pointer fp must not be an automatic (stack) variable
// to avoid potential clobbering by setjmp/longjmp (gcc: [-Wclobbered]).
// Hence the actual 'fp' is allocated with operator new.
FILE** fp = new FILE*; // always allocate file pointer
*fp = NULL;
struct load_stat *lstat = new load_stat();
// Clear data...
alloc_array = 0;
@@ -239,15 +250,15 @@ void Fl_JPEG_Image::load_jpg_(const char *filename, const char *sharename, const
// Open the image file if we read from the file system
if (filename) {
if ((*fp = fl_fopen(filename, "rb")) == NULL) {
if ((lstat->fp = fl_fopen(filename, "rb")) == NULL) {
ld(ERR_FILE_ACCESS);
delete fp;
delete lstat;
return;
}
} else {
if (data==0L) {
ld(ERR_FILE_ACCESS);
delete fp;
delete lstat;
return;
}
}
@@ -257,12 +268,6 @@ void Fl_JPEG_Image::load_jpg_(const char *filename, const char *sharename, const
jerr.pub_.error_exit = fl_jpeg_error_handler;
jerr.pub_.output_message = fl_jpeg_output_handler;
// Setup error loop variables
max_finish_decompress_err = (char*)malloc(1); // allocate space on the frame for error counters
max_destroy_decompress_err = (char*)malloc(1); // otherwise, the variables are reset on the longjmp
*max_finish_decompress_err=10;
*max_destroy_decompress_err=10;
if (setjmp(jerr.errhand_))
{
// JPEG error handling...
@@ -270,16 +275,14 @@ void Fl_JPEG_Image::load_jpg_(const char *filename, const char *sharename, const
if (filename) name = filename;
else if (sharename) name = sharename;
Fl::warning("JPEG file \"%s\" is too large or contains errors!\n", name);
// if any of the cleanup routines hits another error, we would end up
// in a loop. So instead, we decrement max_err for some upper cleanup limit.
if ( ((*max_finish_decompress_err)-- > 0) && array)
if ( ((lstat->max_finish_decompress_err)-- > 0) && array)
jpeg_finish_decompress(&dinfo);
if ( (*max_destroy_decompress_err)-- > 0)
if ( (lstat->max_destroy_decompress_err)-- > 0)
jpeg_destroy_decompress(&dinfo);
if (*fp)
fclose(*fp);
w(0);
h(0);
d(0);
@@ -290,17 +293,14 @@ void Fl_JPEG_Image::load_jpg_(const char *filename, const char *sharename, const
alloc_array = 0;
}
free((void*)max_destroy_decompress_err);
free((void*)max_finish_decompress_err);
ld(ERR_FORMAT);
delete fp;
delete lstat;
return;
}
jpeg_create_decompress(&dinfo);
if (*fp) {
jpeg_stdio_src(&dinfo, *fp);
if (lstat->fp) {
jpeg_stdio_src(&dinfo, lstat->fp);
} else {
if (data_length==-1)
jpeg_unprotected_mem_src(&dinfo, data);
@@ -336,16 +336,11 @@ void Fl_JPEG_Image::load_jpg_(const char *filename, const char *sharename, const
jpeg_finish_decompress(&dinfo);
jpeg_destroy_decompress(&dinfo);
free((void*)max_destroy_decompress_err);
free((void*)max_finish_decompress_err);
if (*fp)
fclose(*fp);
delete lstat;
if (sharename && w() && h()) {
Fl_Shared_Image *si = new Fl_Shared_Image(sharename, this);
si->add();
}
delete fp;
#endif // HAVE_LIBJPEG
}