From: Steinar H. Gunderson Date: Sat, 8 Dec 2018 21:10:16 +0000 (+0100) Subject: Don't die on libjpeg errors. X-Git-Tag: 1.8.0~43 X-Git-Url: https://git.sesse.net/?p=nageru;a=commitdiff_plain;h=996c5ad2c506048694b32988f7e376a97924c3e9 Don't die on libjpeg errors. Makes everything significantly more robust to broken JPEGs. --- diff --git a/futatabi/jpeg_frame_view.cpp b/futatabi/jpeg_frame_view.cpp index 9dc2ec2..a9cf11c 100644 --- a/futatabi/jpeg_frame_view.cpp +++ b/futatabi/jpeg_frame_view.cpp @@ -2,6 +2,7 @@ #include "defs.h" #include "jpeg_destroyer.h" +#include "jpeglib_error_wrapper.h" #include "shared/post_to_main_thread.h" #include "video_stream.h" #include "ycbcr_converter.h" @@ -96,13 +97,18 @@ shared_ptr decode_jpeg(const string &jpeg) frame.reset(new Frame); jpeg_decompress_struct dinfo; - jpeg_error_mgr jerr; - dinfo.err = jpeg_std_error(&jerr); - jpeg_create_decompress(&dinfo); + JPEGWrapErrorManager error_mgr(&dinfo); + if (!error_mgr.run([&dinfo]{ jpeg_create_decompress(&dinfo); })) { + return get_black_frame(); + } JPEGDestroyer destroy_dinfo(&dinfo); - jpeg_mem_src(&dinfo, reinterpret_cast(jpeg.data()), jpeg.size()); - jpeg_read_header(&dinfo, true); + if (!error_mgr.run([&dinfo, &jpeg]{ + jpeg_mem_src(&dinfo, reinterpret_cast(jpeg.data()), jpeg.size()); + jpeg_read_header(&dinfo, true); + })) { + return get_black_frame(); + } if (dinfo.num_components != 3) { fprintf(stderr, "Not a color JPEG. (%d components, Y=%dx%d, Cb=%dx%d, Cr=%dx%d)\n", @@ -110,7 +116,7 @@ shared_ptr decode_jpeg(const string &jpeg) dinfo.comp_info[0].h_samp_factor, dinfo.comp_info[0].v_samp_factor, dinfo.comp_info[1].h_samp_factor, dinfo.comp_info[1].v_samp_factor, dinfo.comp_info[2].h_samp_factor, dinfo.comp_info[2].v_samp_factor); - exit(1); + return get_black_frame(); } if (dinfo.comp_info[0].h_samp_factor != dinfo.max_h_samp_factor || dinfo.comp_info[0].v_samp_factor != dinfo.max_v_samp_factor || // Y' must not be subsampled. @@ -126,7 +132,11 @@ shared_ptr decode_jpeg(const string &jpeg) } dinfo.raw_data_out = true; - jpeg_start_decompress(&dinfo); + if (!error_mgr.run([&dinfo]{ + jpeg_start_decompress(&dinfo); + })) { + return get_black_frame(); + } frame->width = dinfo.output_width; frame->height = dinfo.output_height; @@ -150,21 +160,25 @@ shared_ptr decode_jpeg(const string &jpeg) frame->pitch_y = luma_width_blocks * DCTSIZE; frame->pitch_chroma = chroma_width_blocks * DCTSIZE; - JSAMPROW yptr[v_mcu_size], cbptr[v_mcu_size], crptr[v_mcu_size]; - JSAMPARRAY data[3] = { yptr, cbptr, crptr }; - for (unsigned y = 0; y < mcu_height_blocks; ++y) { - // NOTE: The last elements of cbptr/crptr will be unused for vertically subsampled chroma. - for (unsigned yy = 0; yy < v_mcu_size; ++yy) { - yptr[yy] = frame->y.get() + (y * DCTSIZE * dinfo.max_v_samp_factor + yy) * frame->pitch_y; - cbptr[yy] = frame->cb.get() + (y * DCTSIZE * dinfo.comp_info[1].v_samp_factor + yy) * frame->pitch_chroma; - crptr[yy] = frame->cr.get() + (y * DCTSIZE * dinfo.comp_info[1].v_samp_factor + yy) * frame->pitch_chroma; + if (!error_mgr.run([&dinfo, &frame, v_mcu_size, mcu_height_blocks] { + JSAMPROW yptr[v_mcu_size], cbptr[v_mcu_size], crptr[v_mcu_size]; + JSAMPARRAY data[3] = { yptr, cbptr, crptr }; + for (unsigned y = 0; y < mcu_height_blocks; ++y) { + // NOTE: The last elements of cbptr/crptr will be unused for vertically subsampled chroma. + for (unsigned yy = 0; yy < v_mcu_size; ++yy) { + yptr[yy] = frame->y.get() + (y * DCTSIZE * dinfo.max_v_samp_factor + yy) * frame->pitch_y; + cbptr[yy] = frame->cb.get() + (y * DCTSIZE * dinfo.comp_info[1].v_samp_factor + yy) * frame->pitch_chroma; + crptr[yy] = frame->cr.get() + (y * DCTSIZE * dinfo.comp_info[1].v_samp_factor + yy) * frame->pitch_chroma; + } + + jpeg_read_raw_data(&dinfo, data, v_mcu_size); } - jpeg_read_raw_data(&dinfo, data, v_mcu_size); + (void)jpeg_finish_decompress(&dinfo); + })) { + return get_black_frame(); } - (void)jpeg_finish_decompress(&dinfo); - return frame; } @@ -453,3 +467,22 @@ void JPEGFrameView::set_overlay(const string &text) // Don't refresh immediately; we might not have an OpenGL context here. overlay_input_needs_refresh = true; } + +shared_ptr get_black_frame() +{ + static shared_ptr black_frame; + static once_flag flag; + call_once(flag, [] { + black_frame.reset(new Frame); + black_frame->y.reset(new uint8_t[1280 * 720]); + black_frame->cb.reset(new uint8_t[(1280 / 2) * (720 / 2)]); + black_frame->cr.reset(new uint8_t[(1280 / 2) * (720 / 2)]); + black_frame->width = 1280; + black_frame->height = 720; + black_frame->chroma_subsampling_x = 2; + black_frame->chroma_subsampling_y = 2; + black_frame->pitch_y = 1280; + black_frame->pitch_chroma = 1280 / 2; + }); + return black_frame; +} diff --git a/futatabi/jpeg_frame_view.h b/futatabi/jpeg_frame_view.h index 3ecfa0d..9af7d2c 100644 --- a/futatabi/jpeg_frame_view.h +++ b/futatabi/jpeg_frame_view.h @@ -22,6 +22,7 @@ enum CacheMissBehavior { std::shared_ptr decode_jpeg(const std::string &jpeg); std::shared_ptr decode_jpeg_with_cache(FrameOnDisk id, CacheMissBehavior cache_miss_behavior, FrameReader *frame_reader, bool *did_decode); +std::shared_ptr get_black_frame(); class JPEGFrameView : public QGLWidget { Q_OBJECT diff --git a/futatabi/jpeglib_error_wrapper.h b/futatabi/jpeglib_error_wrapper.h new file mode 100644 index 0000000..3818f53 --- /dev/null +++ b/futatabi/jpeglib_error_wrapper.h @@ -0,0 +1,76 @@ +#ifndef _JPEGLIB_ERROR_WRAPPER +#define _JPEGLIB_ERROR_WRAPPER 1 + +/* + A wrapper class for libjpeg's very cumbersome error handling. + By default, any error will simply exit(); you can set your own + error handler, but it can't return. You can't throw exceptions + through C code legally, so the only real choice is setjmp/longjmp, + which is also what libjpeg recommends. However, longjmp has + undefined behavior if a similar try/catch pair would invoke + running any nontrivial destructors, so it's better to wrap it + into a common class where we know for sure there are no such + destructors; we choose to simply convert it into a normal + true/false idiom for success/failure. + + Use as: + + JPEGWrapErrorManager error_mgr(&cinfo); + if (!error_mgr.run([&cinfo]{ jpeg_read_header(&cinfo, true); })) { + // Something went wrong. + return nullptr; + } + if (!error_mgr.run([&cinfo]{ jpeg_start_decompress(&cinfo); })) { + // Something went wrong. + return nullptr; + } + // etc. + + If you call libjpeg calls outside of run() and they fail, or if + you declare objects with nontrivial destructors in your lambda + (including in the capture), you end up in undefined behavior. + */ + +#include +#include + +struct JPEGWrapErrorManager { + struct jpeg_error_mgr pub; + jmp_buf setjmp_buffer; + + explicit JPEGWrapErrorManager(jpeg_compress_struct *cinfo) // Does not take ownership. + { + cinfo->err = jpeg_std_error(&pub); + pub.error_exit = error_exit_thunk; + } + + explicit JPEGWrapErrorManager(jpeg_decompress_struct *dinfo) // Does not take ownership. + { + dinfo->err = jpeg_std_error(&pub); + pub.error_exit = error_exit_thunk; + } + + static void error_exit_thunk(jpeg_common_struct *cinfo) + { + ((JPEGWrapErrorManager *)cinfo->err)->error_exit(cinfo); + } + + void error_exit(jpeg_common_struct *cinfo) + { + (pub.output_message)(cinfo); + longjmp(setjmp_buffer, 1); + } + + // Returns false if and only if the call failed. + template + inline bool run(T &&func) + { + if (setjmp(setjmp_buffer)) { + return false; + } + func(); + return true; + } +}; + +#endif diff --git a/futatabi/vaapi_jpeg_decoder.cpp b/futatabi/vaapi_jpeg_decoder.cpp index 2cd2f00..cec70da 100644 --- a/futatabi/vaapi_jpeg_decoder.cpp +++ b/futatabi/vaapi_jpeg_decoder.cpp @@ -2,6 +2,7 @@ #include "jpeg_destroyer.h" #include "jpeg_frame.h" +#include "jpeglib_error_wrapper.h" #include "shared/memcpy_interleaved.h" #include @@ -329,13 +330,16 @@ private: shared_ptr decode_jpeg_vaapi(const string &jpeg) { jpeg_decompress_struct dinfo; - jpeg_error_mgr jerr; - dinfo.err = jpeg_std_error(&jerr); - jpeg_create_decompress(&dinfo); + JPEGWrapErrorManager error_mgr(&dinfo); + if (!error_mgr.run([&dinfo] { jpeg_create_decompress(&dinfo); })) { + return nullptr; + } JPEGDestroyer destroy_dinfo(&dinfo); jpeg_mem_src(&dinfo, reinterpret_cast(jpeg.data()), jpeg.size()); - jpeg_read_header(&dinfo, true); + if (!error_mgr.run([&dinfo] { jpeg_read_header(&dinfo, true); })) { + return nullptr; + } if (dinfo.num_components != 3) { fprintf(stderr, "Not a color JPEG. (%d components, Y=%dx%d, Cb=%dx%d, Cr=%dx%d)\n",