ascii-chat 0.8.38
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
pcre2.c
Go to the documentation of this file.
1
7#include <ascii-chat/util/pcre2.h>
8#include <ascii-chat/common.h>
9#include <ascii-chat/log/logging.h>
10#include <string.h>
11#include <stdatomic.h>
12#include <errno.h>
13
21typedef struct pcre2_singleton {
22 _Atomic(pcre2_code *) code;
23 pcre2_jit_stack *jit_stack;
24 _Atomic(bool) compiled;
25 char *pattern;
26 uint32_t flags;
27 struct pcre2_singleton *next;
29
30/* Global registry of all PCRE2 singletons for automatic cleanup */
31static pcre2_singleton_t *g_singleton_registry = NULL;
32static _Atomic(bool) g_registry_initialized = false;
33
46pcre2_singleton_t *asciichat_pcre2_singleton_compile(const char *pattern, uint32_t flags) {
47 if (!pattern) {
48 log_error("PCRE2 singleton: pattern is NULL");
49 return NULL;
50 }
51
52 /* Allocate singleton structure */
53 pcre2_singleton_t *singleton = SAFE_MALLOC(sizeof(pcre2_singleton_t), pcre2_singleton_t *);
54 if (!singleton) {
55 log_error("PCRE2 singleton: failed to allocate singleton structure");
56 return NULL;
57 }
58
59 /* Copy pattern string to avoid stack-use-after-return bugs */
60 size_t pattern_len = strlen(pattern);
61 singleton->pattern = SAFE_MALLOC(pattern_len + 1, char *);
62 if (!singleton->pattern) {
63 log_error("PCRE2 singleton: failed to allocate pattern buffer");
64 SAFE_FREE(singleton);
65 return NULL;
66 }
67 memcpy(singleton->pattern, pattern, pattern_len + 1);
68
69 /* Initialize fields */
70 atomic_store(&singleton->code, NULL);
71 singleton->jit_stack = NULL;
72 atomic_store(&singleton->compiled, false);
73 singleton->flags = flags;
74
75 /* Register singleton in global list for automatic cleanup */
76 singleton->next = g_singleton_registry;
77 g_singleton_registry = singleton;
78 atomic_store(&g_registry_initialized, true);
79
80 return singleton;
81}
82
96 if (!singleton) {
97 return NULL;
98 }
99
100 /* Fast path: check if already compiled (lock-free atomic load, no mutex) */
101 pcre2_code *code = atomic_load(&singleton->code);
102 if (code != NULL) {
103 return code;
104 }
105
106 /* If compilation was already attempted (code freed during cleanup, or compilation
107 * failed), don't recompile. Prevents re-entrant allocation during shutdown. */
108 if (atomic_load(&singleton->compiled)) {
109 return NULL;
110 }
111
112 /* Slow path: first call, need to compile. */
113 int errornumber;
114 PCRE2_SIZE erroroffset;
115
116 code = pcre2_compile((PCRE2_SPTR8)singleton->pattern, PCRE2_ZERO_TERMINATED, singleton->flags, &errornumber,
117 &erroroffset, NULL);
118
119 if (!code) {
120 PCRE2_UCHAR error_buf[256];
121 pcre2_get_error_message(errornumber, error_buf, sizeof(error_buf));
122 log_warn("Failed to compile PCRE2 regex at offset %zu: %s", erroroffset, (const char *)error_buf);
123 /* Mark as "attempted" by setting a sentinel value (store NULL explicitly) */
124 atomic_store(&singleton->compiled, true);
125 return NULL;
126 }
127
128 /* Attempt JIT compilation (non-fatal if fails) */
129 int jit_rc = pcre2_jit_compile(code, PCRE2_JIT_COMPLETE);
130 if (jit_rc < 0) {
131 log_debug("PCRE2 JIT compilation not available (code %d), using interpreted mode", jit_rc);
132 } else {
133 /* Create JIT stack for pattern execution */
134 singleton->jit_stack = pcre2_jit_stack_create(32 * 1024, 512 * 1024, NULL);
135 if (!singleton->jit_stack) {
136 log_warn("Failed to create JIT stack for PCRE2 regex");
137 }
138 }
139
140 /* Store compiled code (atomic, so subsequent calls see it immediately) */
141 atomic_store(&singleton->code, code);
142 atomic_store(&singleton->compiled, true);
143
144 return code;
145}
146
154 if (!singleton) {
155 return false;
156 }
157 return atomic_load(&singleton->code) != NULL;
158}
159
169 if (!singleton) {
170 return;
171 }
172
173 /* Free compiled code if it exists */
174 pcre2_code *code = atomic_load(&singleton->code);
175 if (code) {
176 pcre2_code_free(code);
177 }
178
179 /* Free JIT stack if it exists */
180 if (singleton->jit_stack) {
181 pcre2_jit_stack_free(singleton->jit_stack);
182 }
183
184 /* Free pattern string */
185 SAFE_FREE(singleton->pattern);
186
187 /* Free the singleton structure itself */
188 SAFE_FREE(singleton);
189}
190
198 if (!atomic_load(&g_registry_initialized)) {
199 return; /* No singletons were ever created or already cleaned up */
200 }
201
202 /* Check if already cleaned up */
203 if (g_singleton_registry == NULL) {
204 return;
205 }
206
207 pcre2_singleton_t *current = g_singleton_registry;
208
209 /* Clear registry first to prevent re-entry */
210 g_singleton_registry = NULL;
211 atomic_store(&g_registry_initialized, false);
212
213 /* Now free all singletons */
214 while (current) {
215 pcre2_singleton_t *next = current->next;
216
217 /* Free compiled code */
218 pcre2_code *code = atomic_load(&current->code);
219 if (code) {
220 pcre2_code_free(code);
221 atomic_store(&current->code, NULL);
222 }
223
224 /* Free JIT stack */
225 if (current->jit_stack) {
226 pcre2_jit_stack_free(current->jit_stack);
227 current->jit_stack = NULL;
228 }
229
230 /* Free pattern string */
231 SAFE_FREE(current->pattern);
232
233 /* Free singleton structure */
234 SAFE_FREE(current);
235
236 current = next;
237 }
238}
239
252char *asciichat_pcre2_extract_named_group(pcre2_code *regex, pcre2_match_data *match_data, const char *group_name,
253 const char *subject) {
254 if (!regex || !match_data || !group_name || !subject) {
255 log_error("pcre2_extract_named_group: invalid parameters");
256 return NULL;
257 }
258
259 /* Get group number from name */
260 int group_number = pcre2_substring_number_from_name(regex, (PCRE2_SPTR)group_name);
261 if (group_number < 0) {
262 return NULL; /* Group doesn't exist */
263 }
264
265 /* Get ovector (output vector with match offsets) */
266 PCRE2_SIZE *ovector = pcre2_get_ovector_pointer(match_data);
267 PCRE2_SIZE start = ovector[2 * group_number];
268 PCRE2_SIZE end = ovector[2 * group_number + 1];
269
270 if (start == PCRE2_UNSET || end == PCRE2_UNSET) {
271 return NULL; /* Group not matched */
272 }
273
274 /* Allocate and copy substring */
275 size_t len = end - start;
276 char *result = SAFE_MALLOC(len + 1, char *);
277 if (!result) {
278 log_error("pcre2_extract_named_group: failed to allocate %zu bytes", len + 1);
279 return NULL;
280 }
281
282 memcpy(result, subject + start, len);
283 result[len] = '\0';
284
285 return result;
286}
287
291char *asciichat_pcre2_extract_group(pcre2_match_data *match_data, int group_num, const char *subject) {
292 if (!match_data || !subject || group_num < 0) {
293 return NULL;
294 }
295
296 /* Get ovector (output vector with match offsets) */
297 PCRE2_SIZE *ovector = pcre2_get_ovector_pointer(match_data);
298 PCRE2_SIZE start = ovector[2 * group_num];
299 PCRE2_SIZE end = ovector[2 * group_num + 1];
300
301 if (start == PCRE2_UNSET || end == PCRE2_UNSET) {
302 return NULL; /* Group not matched */
303 }
304
305 /* Allocate and copy substring */
306 size_t len = end - start;
307 char *result = SAFE_MALLOC(len + 1, char *);
308 if (!result) {
309 log_error("asciichat_pcre2_extract_group: failed to allocate %zu bytes", len + 1);
310 return NULL;
311 }
312
313 memcpy(result, subject + start, len);
314 result[len] = '\0';
315
316 return result;
317}
318
322const char *asciichat_pcre2_extract_group_ptr(pcre2_match_data *match_data, int group_num, const char *subject,
323 size_t *out_len) {
324 if (!match_data || !subject || !out_len || group_num < 0) {
325 return NULL;
326 }
327
328 /* Get ovector (output vector with match offsets) */
329 PCRE2_SIZE *ovector = pcre2_get_ovector_pointer(match_data);
330 PCRE2_SIZE start = ovector[2 * group_num];
331 PCRE2_SIZE end = ovector[2 * group_num + 1];
332
333 if (start == PCRE2_UNSET || end == PCRE2_UNSET) {
334 return NULL; /* Group not matched */
335 }
336
337 *out_len = end - start;
338 return subject + start;
339}
340
344bool asciichat_pcre2_extract_group_ulong(pcre2_match_data *match_data, int group_num, const char *subject,
345 unsigned long *out_value) {
346 if (!match_data || !subject || !out_value || group_num < 0) {
347 return false;
348 }
349
350 /* Get ovector (output vector with match offsets) */
351 PCRE2_SIZE *ovector = pcre2_get_ovector_pointer(match_data);
352 PCRE2_SIZE start = ovector[2 * group_num];
353 PCRE2_SIZE end = ovector[2 * group_num + 1];
354
355 if (start == PCRE2_UNSET || end == PCRE2_UNSET) {
356 return false; /* Group not matched */
357 }
358
359 /* Extract substring into temporary buffer */
360 size_t len = end - start;
361 if (len == 0 || len > 63) { /* Sanity check for reasonable number length */
362 return false;
363 }
364
365 char temp[64];
366 memcpy(temp, subject + start, len);
367 temp[len] = '\0';
368
369 /* Parse as unsigned long */
370 char *endptr;
371 errno = 0;
372 unsigned long value = strtoul(temp, &endptr, 10);
373
374 if (errno != 0 || endptr == temp || *endptr != '\0') {
375 return false; /* Parse failed */
376 }
377
378 *out_value = value;
379 return true;
380}
struct pcre2_singleton pcre2_singleton_t
Represents a thread-safe compiled PCRE2 regex singleton.
pcre2_code * asciichat_pcre2_singleton_get_code(pcre2_singleton_t *singleton)
Get the compiled pcre2_code from a singleton handle.
Definition pcre2.c:95
bool asciichat_pcre2_singleton_is_initialized(pcre2_singleton_t *singleton)
Check if a singleton was successfully initialized.
Definition pcre2.c:153
void asciichat_pcre2_cleanup_all(void)
Free all PCRE2 singletons in the global registry.
Definition pcre2.c:197
char * asciichat_pcre2_extract_group(pcre2_match_data *match_data, int group_num, const char *subject)
Extract numbered capture group as allocated string.
Definition pcre2.c:291
char * asciichat_pcre2_extract_named_group(pcre2_code *regex, pcre2_match_data *match_data, const char *group_name, const char *subject)
Extract named substring from PCRE2 match data.
Definition pcre2.c:252
const char * asciichat_pcre2_extract_group_ptr(pcre2_match_data *match_data, int group_num, const char *subject, size_t *out_len)
Extract numbered capture group as pointer into subject (non-allocating)
Definition pcre2.c:322
bool asciichat_pcre2_extract_group_ulong(pcre2_match_data *match_data, int group_num, const char *subject, unsigned long *out_value)
Extract numbered capture group and convert to unsigned long.
Definition pcre2.c:344
void asciichat_pcre2_singleton_free(pcre2_singleton_t *singleton)
Free a PCRE2 singleton and its resources.
Definition pcre2.c:168
Represents a thread-safe compiled PCRE2 regex singleton.
Definition pcre2.c:21