Bug Summary

File:/shared/playproj/i2c/src/dw/dw_recheck.c
Warning:line 208, column 28
Access to field 'initial_ts_nanos' results in a dereference of a null pointer (loaded from variable 'rqe')

Annotated Source Code

Press '?' to see keyboard shortcuts

clang -cc1 -cc1 -triple x86_64-pc-linux-gnu -analyze -disable-free -clear-ast-before-backend -disable-llvm-verifier -discard-value-names -main-file-name dw_recheck.c -analyzer-checker=core -analyzer-checker=apiModeling -analyzer-checker=unix -analyzer-checker=deadcode -analyzer-checker=security.insecureAPI.UncheckedReturn -analyzer-checker=security.insecureAPI.getpw -analyzer-checker=security.insecureAPI.gets -analyzer-checker=security.insecureAPI.mktemp -analyzer-checker=security.insecureAPI.mkstemp -analyzer-checker=security.insecureAPI.vfork -analyzer-checker=nullability.NullPassedToNonnull -analyzer-checker=nullability.NullReturnedFromNonnull -analyzer-output plist -w -setup-static-analyzer -mrelocation-model pic -pic-level 2 -fhalf-no-semantic-interposition -mframe-pointer=none -fmath-errno -ffp-contract=on -fno-rounding-math -mconstructor-aliases -funwind-tables=2 -target-cpu x86-64 -tune-cpu generic -debugger-tuning=gdb -fdebug-compilation-dir=/shared/playproj/i2c/src/dw -fcoverage-compilation-dir=/shared/playproj/i2c/src/dw -resource-dir /usr/lib/llvm-20/lib/clang/20 -D HAVE_CONFIG_H -I . -I ../.. -I /usr/include/libdrm -I /usr/include/glib-2.0 -I /usr/lib/x86_64-linux-gnu/glib-2.0/include -I /usr/include/sysprof-6 -I ../../src -I ../../src/public -D _GLIBCXX_ASSERTIONS -D _GNU_SOURCE -D PIC -internal-isystem /usr/lib/llvm-20/lib/clang/20/include -internal-isystem /usr/local/include -internal-isystem /usr/lib64/gcc/x86_64-linux-gnu/15/../../../../x86_64-linux-gnu/include -internal-externc-isystem /usr/include/x86_64-linux-gnu -internal-externc-isystem /include -internal-externc-isystem /usr/include -O2 -Wno-compound-token-split-by-macro -std=c11 -ferror-limit 19 -fgnuc-version=4.2.1 -fskip-odr-check-in-gmf -vectorize-loops -vectorize-slp -analyzer-output=html -faddrsig -D__GCC_HAVE_DWARF2_CFI_ASM=1 -o dw_recheck.plist -x c dw_recheck.c
1/** @file dw_recheck.c
2 *
3 * Process the queue of display references added in
4 * the main loop for which DDC communication was not
5 * immediately detected as enabled.
6 */
7
8// Copyright (C) 2025-2026 Sanford Rockowitz <rockowitz@minsoft.com>
9// SPDX-License-Identifier: GPL-2.0-or-later
10
11#include <glib-2.0/glib.h>
12
13#include "public/ddcutil_types.h"
14
15#include "util/timestamp.h"
16#include "util/traced_function_stack.h"
17
18#include "base/core.h"
19#include "base/displays.h"
20#include "base/rtti.h"
21#include "base/sleep.h"
22
23#include "ddc/ddc_displays.h"
24#include "ddc/ddc_initial_checks.h"
25
26#include "dw_common.h"
27#include "dw_dref.h"
28#include "dw_status_events.h"
29#include "dw_poll.h" // or process_event_mutex, todo: move
30
31#include "dw_recheck.h"
32
33static DDCA_Trace_Group TRACE_GROUP = DDCA_TRC_CONN;
34
35
36#ifdef UNUSED
37static int simple_ipow(int base, int exponent) {
38 assert(exponent >= 0)((exponent >= 0) ? (void) (0) : __assert_fail ("exponent >= 0"
, "dw_recheck.c", 38, __extension__ __PRETTY_FUNCTION__))
;
39 int result = 1;
40 for (int i = 0; i < exponent; i++) {
41 result = result * base;
42 }
43 return result;
44}
45#endif
46
47
48static void emit_recheck_debug_msg(
49 bool_Bool debug,
50 DDCA_Syslog_Level syslog_level,
51 const char * format, ...)
52{
53 va_list args;
54 va_start(args, format)__builtin_va_start(args, format);
55 char buffer[200];
56 vsnprintf(buffer, 200, format, args);
57 va_end(args)__builtin_va_end(args);
58
59 DBGTRC_NOPREFIX(debug, DDCA_TRC_NONE, "%s", buffer)dbgtrc( (debug) || trace_callstack_call_depth > 0 ? DDCA_TRC_ALL
: (DDCA_TRC_NONE), 0x00, __func__, 59, "dw_recheck.c", " "
"%s", buffer)
;
60 SYSLOG2(syslog_level, "%s", buffer)do { if (test_emit_syslog(syslog_level)) { int syslog_priority
= syslog_importance_from_ddcutil_syslog_level(syslog_level);
if (syslog_priority >= 0) { char * body = g_strdup_printf
("%s", buffer); syslog(syslog_priority, "[%6jd]"" %s%s", (intmax_t
) tid(), body, (tag_output) ? " (P)" : "" ); free(body); } } }
while(0)
;
61}
62
63
64typedef struct {
65 Display_Ref* dref;
66 uint64_t initial_ts_nanos;
67 int sleepctr;
68} Recheck_Queue_Entry;
69
70
71static void dw_free_recheck_queue_entry(Recheck_Queue_Entry * entry) {
72 free(entry);
73}
74
75
76GAsyncQueue * recheck_queue = NULL((void*)0);
77// GMutex * recheck_queue_mutex = NULL;
78
79
80static GAsyncQueue *
81init_recheck_queue() {
82 recheck_queue = g_async_queue_new();
83 return recheck_queue;
84}
85
86
87/** Adds a display reference to the recheck queue
88 *
89 * @param dref display reference
90 */
91void dw_put_recheck_queue(Display_Ref* dref) {
92 bool_Bool debug = false0;
93 DBGTRC_STARTING(debug, DDCA_TRC_CONN, "dref=%s", dref_reprx_t(dref))do { if (traced_function_stack_enabled) push_traced_function(
__func__); dbgtrc( (debug) || trace_callstack_call_depth >
0 || is_traced_callstack_call(__func__) ? DDCA_TRC_ALL : (DDCA_TRC_CONN
), 0x08, __func__, 93, "dw_recheck.c", "Starting ""dref=%s",
dref_reprx_t(dref)); } while(0)
;
94
95 Recheck_Queue_Entry * entry = calloc(1, sizeof(Recheck_Queue_Entry));
96 entry->dref = dref;
97 entry->initial_ts_nanos = cur_realtime_nanosec();
98 entry->sleepctr = 0;
99
100 // g_mutex_lock(recheck_queue_mutex);
101 g_async_queue_push(recheck_queue, entry);
102 // g_mutex_unlock(recheck_queue_mutex);
103
104 DBGTRC_DONE(debug, DDCA_TRC_CONN, "")do { dbgtrc( (debug) || trace_callstack_call_depth > 0 ? DDCA_TRC_ALL
: (DDCA_TRC_CONN), 0x10, __func__, 104, "dw_recheck.c", "Done "
""); if (traced_function_stack_enabled) pop_traced_function(__func__
); } while (0)
;
105}
106
107
108#ifdef NO
109typedef struct {
110 GArray * deferred_event_queue;
111 GMutex * deferred_event_queue_mutex;
112} Recheck_Displays_Data;
113#endif
114
115STATICstatic Error_Info *
116dw_recheck_dref(Display_Ref * dref) {
117 bool_Bool debug = false0;
118 DBGTRC_STARTING(debug, DDCA_TRC_NONE, "dref=%s", dref_reprx_t(dref))do { if (traced_function_stack_enabled) push_traced_function(
__func__); dbgtrc( (debug) || trace_callstack_call_depth >
0 || is_traced_callstack_call(__func__) ? DDCA_TRC_ALL : (DDCA_TRC_NONE
), 0x08, __func__, 118, "dw_recheck.c", "Starting ""dref=%s"
, dref_reprx_t(dref)); } while(0)
;
119 Error_Info * err = NULL((void*)0);
120
121 DDCA_Status ddcrc = dref_lock(dref);
122 if (ddcrc != 0) {
123 err = ERRINFO_NEW(ddcrc, "dref_lock() failed for %s", dref_reprx_t(dref))errinfo_new(ddcrc, __func__, "dref_lock() failed for %s", dref_reprx_t
(dref))
;
124 SYSLOG2(DDCA_SYSLOG_ERROR, "dref_lock() failed for %s", dref_reprx_t(dref))do { if (test_emit_syslog(DDCA_SYSLOG_ERROR)) { int syslog_priority
= syslog_importance_from_ddcutil_syslog_level(DDCA_SYSLOG_ERROR
); if (syslog_priority >= 0) { char * body = g_strdup_printf
("dref_lock() failed for %s", dref_reprx_t(dref)); syslog(syslog_priority
, "[%6jd]"" %s%s", (intmax_t) tid(), body, (tag_output) ? " (P)"
: "" ); free(body); } } } while(0)
;
125 }
126 else {
127 DBGTRC_NOPREFIX(debug, DDCA_TRC_NONE, "Obtained lock on %s:", dref_reprx_t(dref))dbgtrc( (debug) || trace_callstack_call_depth > 0 ? DDCA_TRC_ALL
: (DDCA_TRC_NONE), 0x00, __func__, 127, "dw_recheck.c", " "
"Obtained lock on %s:", dref_reprx_t(dref))
;
128 err = ddc_initial_checks_by_dref(dref, false0);
129 dref_unlock(dref);
130 DBGTRC_NOPREFIX(debug, DDCA_TRC_NONE, "Released lock on %s:", dref_reprx_t(dref))dbgtrc( (debug) || trace_callstack_call_depth > 0 ? DDCA_TRC_ALL
: (DDCA_TRC_NONE), 0x00, __func__, 130, "dw_recheck.c", " "
"Released lock on %s:", dref_reprx_t(dref))
;
131 }
132
133 DBGTRC_RET_ERRINFO(debug, DDCA_TRC_NONE, err, "")do { dbgtrc_returning_errinfo( (debug) || trace_callstack_call_depth
> 0 ? DDCA_TRC_ALL : (DDCA_TRC_NONE), 0x10, __func__, 133
, "dw_recheck.c", err, ""); if (traced_function_stack_enabled
) pop_traced_function(__func__); } while (0)
;
134 return err;
135}
136
137
138/** Function that executes in the recheck thread to check if DDC
139 * communication has become enabled for newly added display refs for which
140 * DDC communication was not initially detected as working.
141 *
142 * @param data pointer to a #Recheck_Displays_Data struct
143 *
144 * At increasing time intervals, each display ref is checked to see if DDC
145 * communication is working. If so, a #DDC_Display_Status event of type
146 * #DDCA_EVENT_DDC_ENABLED is emitted, and the display ref is removed from
147 * from the array.
148 *
149 * The time intervals are calculated as follows. The intervals are numbered
150 * from 0. The base interval is global variable #retry_thread_sleep_factor_millis.
151 * An adjustment factor is calculated as 2**i, where i is the interval number,
152 * i.e. 1, 2, 4, 8 etc.
153 *
154 * The thread terminates when either all display refs have been removed from
155 * the array because communication has succeeded, or because the maximum of
156 * sleep intervals have occurred, i.e. until a total of
157 * (1+2+4+8)*retry_thread_sleep_factor_millis milliseconds have been slept.
158 *
159 * On termination the **displays to recheck** array is freed. Note that the
160 * display references themselves are not; display references are persistent.
161 */
162gpointer dw_recheck_displays_func(gpointer data) {
163 bool_Bool debug = false0;
164 DBGTRC_STARTING(debug, TRACE_GROUP, "data=%p", data)do { if (traced_function_stack_enabled) push_traced_function(
__func__); dbgtrc( (debug) || trace_callstack_call_depth >
0 || is_traced_callstack_call(__func__) ? DDCA_TRC_ALL : (TRACE_GROUP
), 0x08, __func__, 164, "dw_recheck.c", "Starting ""data=%p"
, data); } while(0)
;
1
Assuming 'traced_function_stack_enabled' is false
2
Taking false branch
3
Assuming 'trace_callstack_call_depth' is > 0
4
Loop condition is false. Exiting loop
165 Recheck_Displays_Data* rdd = (Recheck_Displays_Data *) data;
166 init_recheck_queue();
167 // recheck_thread_active = true;
168
169 // GPtrArray * displays_to_recheck = g_ptr_array_new();
170#ifdef DEBUG
171 for (int ndx = 0; ndx < displays_to_recheck->len; ndx++) {
172 Display_Ref * dref = g_ptr_array_index(displays_to_recheck, ndx)((displays_to_recheck)->pdata)[ndx];
173 DBGMSG("dref=%s", dref_reprx_t(dref))dbgtrc(DDCA_TRC_ALL, 0x00, __func__, 173, "dw_recheck.c", "dref=%s"
, dref_reprx_t(dref))
;
174 }
175#endif
176
177 GQueue * to_check_again = g_queue_new();
178
179 int sleep_interval_millis = 200; // temp
180 int max_sleep_time_millis = 3000;
181 int pop_interval_millis = 100;
182
183 while (!terminate_watch_thread) {
5
Loop condition is true. Entering loop body
184 SLEEP_MILLIS_WITH_SYSLOG(sleep_interval_millis, "Recheck interval")do { loggable_sleep(sleep_interval_millis, SLEEP_OPT_NONE, DDCA_SYSLOG_NOTICE
, __func__, 184, "dw_recheck.c", "Recheck interval"); } while
(0)
; // move to end of loop
6
Loop condition is false. Exiting loop
185 uint64_t cur_time_nanos = cur_realtime_nanosec();
186
187 Recheck_Queue_Entry* rqe = NULL((void*)0);
7
'rqe' initialized to a null pointer value
188 while (!rqe
7.1
'rqe' is null
&& !terminate_watch_thread) {
8
Loop condition is false. Execution continues on line 202
189 while (g_queue_get_length(to_check_again) > 0) {
190 rqe = g_queue_pop_head(to_check_again);
191 g_async_queue_push_front(recheck_queue, rqe);
192 }
193 uint64_t pop_interval_micros = MILLIS2MICROS(pop_interval_millis)(pop_interval_millis*(uint64_t)1000);
194 rqe = g_async_queue_timeout_pop(recheck_queue, pop_interval_micros);
195 if (terminate_watch_thread) {
196 if (rqe) {
197 dw_free_recheck_queue_entry(rqe);
198 rqe = NULL((void*)0);
199 }
200 }
201 }
202 if (terminate_watch_thread) {
9
Assuming the condition is false
10
Taking false branch
203 DBGTRC_NOPREFIX(debug, DDCA_TRC_NONE, "terminating recheck thread execution")dbgtrc( (debug) || trace_callstack_call_depth > 0 ? DDCA_TRC_ALL
: (DDCA_TRC_NONE), 0x00, __func__, 203, "dw_recheck.c", " "
"terminating recheck thread execution")
;
204 break;
205 }
206
207 // check if max recheck time has elapsed
208 if (cur_time_nanos > rqe->initial_ts_nanos + MILLIS2NANOS(max_sleep_time_millis)(max_sleep_time_millis*(uint64_t)1000000)) {
11
Access to field 'initial_ts_nanos' results in a dereference of a null pointer (loaded from variable 'rqe')
209 emit_recheck_debug_msg(debug, DDCA_SYSLOG_NOTICE,
210 "ddc did not become enabled for %s after %d milliseconds",
211 dref_reprx_t(rqe->dref), max_sleep_time_millis);
212 dw_free_recheck_queue_entry(rqe);
213 continue;
214 }
215
216 DBGTRC_NOPREFIX(debug, TRACE_GROUP, "Locking master_dw_mutex, thread_id = %d", TID())dbgtrc( (debug) || trace_callstack_call_depth > 0 ? DDCA_TRC_ALL
: (TRACE_GROUP), 0x00, __func__, 216, "dw_recheck.c", " "
"Locking master_dw_mutex, thread_id = %d", (intmax_t) tid())
;
217 g_mutex_lock(&master_dw_mutex);
218
219 Display_Ref * dref = rqe->dref;
220 // DBGMSG(" rechecking %s", dref_repr_t(dref));
221 Error_Info * err = dw_recheck_dref(dref); // <===
222 DBGTRC_NOPREFIX(false, DDCA_TRC_NONE, "after dw_recheck_dref(), dref->flags=%s",dbgtrc( (0) || trace_callstack_call_depth > 0 ? DDCA_TRC_ALL
: (DDCA_TRC_NONE), 0x00, __func__, 223, "dw_recheck.c", " "
"after dw_recheck_dref(), dref->flags=%s", interpret_dref_flags_t
(dref->flags))
223 interpret_dref_flags_t(dref->flags))dbgtrc( (0) || trace_callstack_call_depth > 0 ? DDCA_TRC_ALL
: (DDCA_TRC_NONE), 0x00, __func__, 223, "dw_recheck.c", " "
"after dw_recheck_dref(), dref->flags=%s", interpret_dref_flags_t
(dref->flags))
;
224 // dbgrpt_display_ref(dref,false,2);
225 if (!err) {
226 emit_recheck_debug_msg(debug, DDCA_SYSLOG_NOTICE,
227 "ddc became enabled for %s after %ld milliseconds",
228 dref_reprx_t(dref), NANOS2MILLIS(cur_realtime_nanosec() - rqe->initial_ts_nanos)(((cur_realtime_nanosec() - rqe->initial_ts_nanos)+500000)
/1000000)
);
229 dref->dispno = ++dispno_max;
230
231 DBGTRC_NOPREFIX(false, DDCA_TRC_NONE, "locking process_event_mutex")dbgtrc( (0) || trace_callstack_call_depth > 0 ? DDCA_TRC_ALL
: (DDCA_TRC_NONE), 0x00, __func__, 231, "dw_recheck.c", " "
"locking process_event_mutex")
;
232 g_mutex_lock(&process_event_mutex);
233 dw_emit_or_queue_display_status_event(
234 DDCA_EVENT_DDC_ENABLED,
235 dref->drm_connector,
236 dref,
237 dref->io_path,
238 NULL((void*)0)); // deferred_event_queue);
239 g_mutex_unlock(&process_event_mutex);
240 DBGTRC_NOPREFIX(false, DDCA_TRC_NONE, "unlocked process_event_mutex")dbgtrc( (0) || trace_callstack_call_depth > 0 ? DDCA_TRC_ALL
: (DDCA_TRC_NONE), 0x00, __func__, 240, "dw_recheck.c", " "
"unlocked process_event_mutex")
;
241 dw_free_recheck_queue_entry(rqe);
242 }
243 else {
244 if (err->status_code == DDCRC_DISCONNECTED(-(3000 +29) )) {
245 emit_recheck_debug_msg(debug, DDCA_SYSLOG_NOTICE,
246 "Display %s no longer detected after %"PRIu64"l" "u"" milliseconds",
247 dref_reprx_t(dref),
248 NANOS2MILLIS(cur_time_nanos - rqe->initial_ts_nanos)(((cur_time_nanos - rqe->initial_ts_nanos)+500000)/1000000
)
);
249
250 dref->dispno = DISPNO_REMOVED-3;
251 dw_emit_or_queue_display_status_event(
252 DDCA_EVENT_DISPLAY_DISCONNECTED,
253 dref->drm_connector,
254 dref,
255 dref->io_path,
256 NULL((void*)0)); // rdd->deferred_event_queue);
257 dw_free_recheck_queue_entry(rqe);
258 }
259 else {
260 DBGTRC_NOPREFIX(debug, DDCA_TRC_NONE,dbgtrc( (debug) || trace_callstack_call_depth > 0 ? DDCA_TRC_ALL
: (DDCA_TRC_NONE), 0x00, __func__, 262, "dw_recheck.c", " "
"ddc still not enabled for %s after %d milliseconds, retrying ..."
, dref_reprx_t(rqe->dref), sleep_interval_millis)
261 "ddc still not enabled for %s after %d milliseconds, retrying ...",dbgtrc( (debug) || trace_callstack_call_depth > 0 ? DDCA_TRC_ALL
: (DDCA_TRC_NONE), 0x00, __func__, 262, "dw_recheck.c", " "
"ddc still not enabled for %s after %d milliseconds, retrying ..."
, dref_reprx_t(rqe->dref), sleep_interval_millis)
262 dref_reprx_t(rqe->dref), sleep_interval_millis)dbgtrc( (debug) || trace_callstack_call_depth > 0 ? DDCA_TRC_ALL
: (DDCA_TRC_NONE), 0x00, __func__, 262, "dw_recheck.c", " "
"ddc still not enabled for %s after %d milliseconds, retrying ..."
, dref_reprx_t(rqe->dref), sleep_interval_millis)
;
263 g_queue_push_head(to_check_again, rqe);
264 }
265 ERRINFO_FREE_WITH_REPORT(err, IS_DBGTRC(debug, DDCA_TRC_NONE) || is_report_ddc_errors_enabled() )if (err) errinfo_free_with_report(err, (( (debug) || is_tracing
((DDCA_TRC_NONE), "dw_recheck.c", __func__) ) || is_report_ddc_errors_enabled
()), __func__)
;
266 }
267
268 DBGTRC_NOPREFIX(debug, TRACE_GROUP, "Unlocking master_dw_mutex, thread_id = %d", TID())dbgtrc( (debug) || trace_callstack_call_depth > 0 ? DDCA_TRC_ALL
: (TRACE_GROUP), 0x00, __func__, 268, "dw_recheck.c", " "
"Unlocking master_dw_mutex, thread_id = %d", (intmax_t) tid()
)
;
269 g_mutex_unlock(&master_dw_mutex);
270 }
271
272 if (terminate_watch_thread) {
273 char * s = "recheck thread terminating because watch thread terminated";
274 DBGTRC_NOPREFIX(debug, DDCA_TRC_NONE, "%s", s)dbgtrc( (debug) || trace_callstack_call_depth > 0 ? DDCA_TRC_ALL
: (DDCA_TRC_NONE), 0x00, __func__, 274, "dw_recheck.c", " "
"%s", s)
;
275 SYSLOG2(DDCA_SYSLOG_NOTICE, "%s", s)do { if (test_emit_syslog(DDCA_SYSLOG_NOTICE)) { int syslog_priority
= syslog_importance_from_ddcutil_syslog_level(DDCA_SYSLOG_NOTICE
); if (syslog_priority >= 0) { char * body = g_strdup_printf
("%s", s); syslog(syslog_priority, "[%6jd]"" %s%s", (intmax_t
) tid(), body, (tag_output) ? " (P)" : "" ); free(body); } } }
while(0)
;
276
277 // free what's left on the queue
278 while (true1) {
279 Recheck_Queue_Entry * rqe = g_async_queue_timeout_pop(recheck_queue, 0);
280 if (!rqe)
281 break;
282 emit_recheck_debug_msg(debug, DDCA_SYSLOG_ERROR,
283 "Flushing request queue entry for %s ",
284 dref_reprx_t(rqe->dref));
285 dw_free_recheck_queue_entry(rqe);
286 }
287 }
288
289 g_queue_free(to_check_again);
290 dw_free_recheck_displays_data(rdd); // actually just a free()
291 DBGTRC_DONE(debug, TRACE_GROUP, "terminating recheck thread")do { dbgtrc( (debug) || trace_callstack_call_depth > 0 ? DDCA_TRC_ALL
: (TRACE_GROUP), 0x10, __func__, 291, "dw_recheck.c", "Done "
"terminating recheck thread"); if (traced_function_stack_enabled
) pop_traced_function(__func__); } while (0)
;
292 free_current_traced_function_stack();
293 // recheck_thread_active = false;
294 g_thread_exit(NULL((void*)0));
295 return NULL((void*)0); // no effect, but avoids compiler error
296}
297
298
299void init_dw_recheck() {
300 RTTI_ADD_FUNC(dw_recheck_dref)rtti_func_name_table_add(dw_recheck_dref, "dw_recheck_dref");;
301 RTTI_ADD_FUNC(dw_recheck_displays_func)rtti_func_name_table_add(dw_recheck_displays_func, "dw_recheck_displays_func"
);
;
302}