Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
imageio_magick_abort_guard.h
Go to the documentation of this file.
1/*
2 This file is part of Ansel,
3 Copyright (C) 2026 Aurélien PIERRE.
4
5 Ansel is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 Ansel is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with Ansel. If not, see <http://www.gnu.org/licenses/>.
17*/
18#pragma once
19
20/*
21 * GraphicsMagick/ImageMagick call assert() on malformed input, and the
22 * distro-packaged builds we link against do not define NDEBUG, so assert()
23 * calls abort() and takes the whole process down with it (Sentry issues
24 * 130678348 and 129978857 - the same failure mode hits both the full-image
25 * GM/IM loaders in imageio_gm.c/imageio_im.c and the embedded-thumbnail
26 * decoder inlined in dt_imageio_large_thumbnail()).
27 *
28 * This guard turns that abort() into a recoverable error per call site:
29 *
30 * - it uses plain ISO C signal()/setjmp(), not the POSIX sigaction()/
31 * sigsetjmp() variants, so it actually compiles on Windows/MinGW;
32 * - the jump buffer is thread-local. Thumbnail/preview generation runs
33 * GM/IM concurrently on worker threads (dt_control_work, mipmap cache),
34 * and a single process-wide buffer would let one thread's recovery
35 * longjmp into a different thread's stack;
36 * - on recovery, callers must NOT call back into the library on the object
37 * that was being built (DestroyImage, DestroyMagickWand, etc.). abort()
38 * means the library hit its own internal assertion, so that object's
39 * state is unknown - touching it again, even to free it, can re-enter
40 * the same broken code path or corrupt memory. Leaking that one object
41 * is the safe trade-off for "this single file is malformed". Buffers
42 * Ansel itself allocated (mipmap/pixelpipe buffers) are unaffected by
43 * this and should still be freed normally by the recovery statement;
44 * - it restores Ansel's own signal handlers afterward
45 * (common/system_signal_handling.c) since GraphicsMagick is known to
46 * silently steal them as a side effect of its calls (see the
47 * InitializeMagick() callers in common/darktable.c).
48 *
49 * Usage, mirroring the existing goto-based error handling in these files:
50 *
51 * DT_MAGICK_ABORT_GUARD("GraphicsMagick_open", filename, goto error);
52 * image = ReadImage(image_info, &exception);
53 * ...
54 * DT_MAGICK_ABORT_GUARD_DISARM();
55 * return DT_IMAGEIO_OK;
56 *
57 * `recovery` runs with the guard already disarmed and the signal handler
58 * already restored; it must be a single statement (typically a `goto` or a
59 * `return`) and must not declare variables (it sits inside an `if` body
60 * opened by the macro).
61 */
62
63// This header is included unconditionally by imageio.c (which decides
64// between GM/IM/neither per-build), so self-guard: on a "nofeatures" build
65// with neither library enabled, nothing below must be emitted, or the
66// unused static handler/variables fail a -Werror=unused-function build.
67#if defined(HAVE_GRAPHICSMAGICK) || defined(HAVE_IMAGEMAGICK)
68
70
71#include <setjmp.h>
72#include <signal.h>
73#include <stdio.h>
74
75static __thread jmp_buf _dt_magick_abort_jmp;
76static __thread int _dt_magick_abort_armed = 0;
77static __thread void (*_dt_magick_abort_prev_handler)(int) = NULL;
78
79static void _dt_magick_abort_handler(int sig)
80{
81 (void)sig;
82 if(_dt_magick_abort_armed)
83 {
84 _dt_magick_abort_armed = 0;
85 longjmp(_dt_magick_abort_jmp, 1);
86 }
87 // abort() on this thread but outside a guarded call: don't swallow it,
88 // let the normal crash-reporting path (Sentry / gdb backtrace) handle it.
89 signal(SIGABRT, SIG_DFL);
90 raise(SIGABRT);
91}
92
93#define DT_MAGICK_ABORT_GUARD(label, fname, recovery) \
94 _dt_magick_abort_prev_handler = signal(SIGABRT, _dt_magick_abort_handler); \
95 if(setjmp(_dt_magick_abort_jmp)) \
96 { \
97 fprintf(stderr, "[%s] caught an internal abort() raised by the image library while loading `%s' - " \
98 "treating it as corrupted\n", (label), (fname)); \
99 signal(SIGABRT, _dt_magick_abort_prev_handler); \
100 dt_set_signal_handlers(); \
101 recovery; \
102 } \
103 _dt_magick_abort_armed = 1;
104
105#define DT_MAGICK_ABORT_GUARD_DISARM() \
106 do \
107 { \
108 _dt_magick_abort_armed = 0; \
109 signal(SIGABRT, _dt_magick_abort_prev_handler); \
110 } while(0)
111
112#endif // defined(HAVE_GRAPHICSMAGICK) || defined(HAVE_IMAGEMAGICK)
113
114// clang-format off
115// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
116// vim: shiftwidth=2 expandtab tabstop=2 cindent
117// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
118// clang-format on
typedef void((*dt_cache_allocate_t)(void *userdata, dt_cache_entry_t *entry))