ascii-chat 0.11.33
Video chat in your terminal
Loading...
Searching...
No Matches
pcre2.c
Go to the documentation of this file.
1
9#include <ascii-chat/common.h>
10#include <ascii-chat/log/log.h>
11#include <string.h>
12#include <ascii-chat/atomic.h>
13#include <errno.h>
14
30
31/* Global registry of all PCRE2 singletons for automatic cleanup */
32static pcre2_singleton_t *g_singleton_registry = NULL;
33static lifecycle_t g_registry_lc = LIFECYCLE_INIT;
34
48 if (!pattern) {
49 log_error("PCRE2 singleton: pattern is NULL");
50 return NULL;
51 }
52
53 /* Allocate singleton structure */
55 if (!singleton) {
56 log_error("PCRE2 singleton: failed to allocate singleton structure");
57 return NULL;
58 }
59
60 /* Copy pattern string to avoid stack-use-after-return bugs */
61 size_t pattern_len = strlen(pattern);
62 singleton->pattern = SAFE_MALLOC(pattern_len + 1, char *);
63 if (!singleton->pattern) {
64 log_error("PCRE2 singleton: failed to allocate pattern buffer");
65 SAFE_FREE(singleton);
66 return NULL;
67 }
68 memcpy(singleton->pattern, pattern, pattern_len + 1);
69
70 /* Initialize fields */
71 singleton->lc = (lifecycle_t)LIFECYCLE_INIT;
72 atomic_store_u64(&singleton->code, 0);
73 singleton->jit_stack = NULL;
74 singleton->flags = flags;
75
76 /* Register singleton in global list for automatic cleanup */
77 singleton->next = g_singleton_registry;
78 g_singleton_registry = singleton;
79
80 /* Mark registry as initialized (one-time per process) */
81 lifecycle_init(&g_registry_lc, "pcre2_registry");
82
83 return singleton;
84}
85
99 if (!singleton) {
100 return NULL;
101 }
102
103 /* Fast path: check if already compiled (lock-free atomic load, no mutex) */
104 pcre2_code *code = (pcre2_code *)(uintptr_t)atomic_load_u64(&singleton->code);
105 if (code != NULL) {
106 return code;
107 }
108
109 /* Check if compilation already attempted via lifecycle state */
110 if (lifecycle_is_initialized(&singleton->lc)) {
111 /* Already initialized but code is NULL - compilation failed previously */
112 return NULL;
113 }
114
115 /* Try to win the compilation race using lifecycle_t */
116 if (!lifecycle_init(&singleton->lc, "pcre2_pattern")) {
117 /* Lost race - someone else is compiling or already compiled. Spin for their result. */
118 while ((pcre2_code *)(uintptr_t)atomic_load_u64(&singleton->code) == NULL &&
119 lifecycle_is_initialized(&singleton->lc)) {
120 /* Spin-wait for compiler to finish */
121 }
122 return (pcre2_code *)(uintptr_t)atomic_load_u64(&singleton->code);
123 }
124
125 /* Slow path: we won the race, need to compile. */
126 int errornumber;
127 PCRE2_SIZE erroroffset;
128
129 code = pcre2_compile((PCRE2_SPTR8)singleton->pattern, PCRE2_ZERO_TERMINATED, singleton->flags, &errornumber,
130 &erroroffset, NULL);
131
132 if (!code) {
133 PCRE2_UCHAR error_buf[256];
134 pcre2_get_error_message(errornumber, error_buf, sizeof(error_buf));
135 log_warn("Failed to compile PCRE2 regex at offset %zu: %s", erroroffset, (const char *)error_buf);
136 /* Mark compilation as attempted (failed) by moving to INITIALIZED state */
137 lifecycle_init_commit(&singleton->lc);
138 return NULL;
139 }
140
141 /* Attempt JIT compilation (non-fatal if fails) */
142 int jit_rc = PCRE2_ERROR_JIT_BADOPTION;
143 if (!SAFE_GETENV("ASCII_CHAT_PCRE2_DISABLE_JIT")) {
144 jit_rc = pcre2_jit_compile(code, PCRE2_JIT_COMPLETE);
145 }
146 if (jit_rc < 0) {
147 log_debug("PCRE2 JIT compilation not available (code %d), using interpreted mode", jit_rc);
148 } else {
149 /* Create JIT stack for pattern execution */
150 singleton->jit_stack = pcre2_jit_stack_create(32 * 1024, 512 * 1024, NULL);
151 if (!singleton->jit_stack) {
152 log_warn("Failed to create JIT stack for PCRE2 regex");
153 }
154 }
155
156 /* Store compiled code (atomic, so subsequent calls see it immediately) */
157 atomic_store_u64(&singleton->code, (uint64_t)(uintptr_t)code);
158 /* Mark compilation as completed by moving to INITIALIZED state */
159 lifecycle_init_commit(&singleton->lc);
160
161 return code;
162}
163
171 if (!singleton) {
172 return false;
173 }
174 return (pcre2_code *)(uintptr_t)atomic_load_u64(&singleton->code) != NULL;
175}
176
186 if (!singleton) {
187 return;
188 }
189
190 /* Free compiled code if it exists */
191 pcre2_code *code = (pcre2_code *)(uintptr_t)atomic_load_u64(&singleton->code);
192 if (code) {
193 pcre2_code_free(code);
194 }
195
196 /* Free JIT stack if it exists */
197 if (singleton->jit_stack) {
198 pcre2_jit_stack_free(singleton->jit_stack);
199 }
200
201 /* Free pattern string */
202 SAFE_FREE(singleton->pattern);
203
204 /* Free the singleton structure itself */
205 SAFE_FREE(singleton);
206}
207
215 if (!lifecycle_shutdown(&g_registry_lc)) {
216 return; /* No singletons were ever created or already cleaned up */
217 }
218
219 /* Check if already cleaned up */
220 if (g_singleton_registry == NULL) {
221 return;
222 }
223
224 pcre2_singleton_t *current = g_singleton_registry;
225
226 /* Clear registry first to prevent re-entry */
227 g_singleton_registry = NULL;
228
229 /* Now free all singletons */
230 while (current) {
231 pcre2_singleton_t *next = current->next;
232
233 /* Free compiled code */
234 pcre2_code *code = (pcre2_code *)(uintptr_t)atomic_load_u64(&current->code);
235 if (code) {
236 pcre2_code_free(code);
237 atomic_store_u64(&current->code, 0);
238 }
239
240 /* Free JIT stack */
241 if (current->jit_stack) {
242 pcre2_jit_stack_free(current->jit_stack);
243 current->jit_stack = NULL;
244 }
245
246 /* Free pattern string */
247 SAFE_FREE(current->pattern);
248
249 /* Do not free the singleton struct itself - static pointers in other
250 * modules (path.c, url.c, etc.) may still reference it during late
251 * shutdown logging. The struct is tiny and harmless to leak at exit. */
252
253 current = next;
254 }
255}
256
269char *asciichat_pcre2_extract_named_group(pcre2_code *regex, pcre2_match_data *match_data, const char *group_name,
270 const char *subject) {
271 if (!regex || !match_data || !group_name || !subject) {
272 log_error("pcre2_extract_named_group: invalid parameters");
273 return NULL;
274 }
275
276 /* Get group number from name */
277 int group_number = pcre2_substring_number_from_name(regex, (PCRE2_SPTR)group_name);
278 if (group_number < 0) {
279 return NULL; /* Group doesn't exist */
280 }
281
282 /* Get ovector (output vector with match offsets) */
283 PCRE2_SIZE *ovector = pcre2_get_ovector_pointer(match_data);
284 PCRE2_SIZE start = ovector[2 * group_number];
285 PCRE2_SIZE end = ovector[2 * group_number + 1];
286
287 if (start == PCRE2_UNSET || end == PCRE2_UNSET) {
288 return NULL; /* Group not matched */
289 }
290
291 /* Allocate and copy substring */
292 size_t len = end - start;
293 char *result = SAFE_MALLOC(len + 1, char *);
294 if (!result) {
295 log_error("pcre2_extract_named_group: failed to allocate %zu bytes", len + 1);
296 return NULL;
297 }
298
299 memcpy(result, subject + start, len);
300 result[len] = '\0';
301
302 return result;
303}
304
308char *asciichat_pcre2_extract_group(pcre2_match_data *match_data, int group_num, const char *subject) {
309 if (!match_data || !subject || group_num < 0) {
310 return NULL;
311 }
312
313 /* Get ovector (output vector with match offsets) */
314 PCRE2_SIZE *ovector = pcre2_get_ovector_pointer(match_data);
315 PCRE2_SIZE start = ovector[2 * group_num];
316 PCRE2_SIZE end = ovector[2 * group_num + 1];
317
318 if (start == PCRE2_UNSET || end == PCRE2_UNSET) {
319 return NULL; /* Group not matched */
320 }
321
322 /* Allocate and copy substring */
323 size_t len = end - start;
324 char *result = SAFE_MALLOC(len + 1, char *);
325 if (!result) {
326 log_error("asciichat_pcre2_extract_group: failed to allocate %zu bytes", len + 1);
327 return NULL;
328 }
329
330 memcpy(result, subject + start, len);
331 result[len] = '\0';
332
333 return result;
334}
335
339const char *asciichat_pcre2_extract_group_ptr(pcre2_match_data *match_data, int group_num, const char *subject,
340 size_t *out_len) {
341 if (!match_data || !subject || !out_len || group_num < 0) {
342 return NULL;
343 }
344
345 /* Get ovector (output vector with match offsets) */
346 PCRE2_SIZE *ovector = pcre2_get_ovector_pointer(match_data);
347 PCRE2_SIZE start = ovector[2 * group_num];
348 PCRE2_SIZE end = ovector[2 * group_num + 1];
349
350 if (start == PCRE2_UNSET || end == PCRE2_UNSET) {
351 return NULL; /* Group not matched */
352 }
353
354 *out_len = end - start;
355 return subject + start;
356}
357
361bool asciichat_pcre2_extract_group_ulong(pcre2_match_data *match_data, int group_num, const char *subject,
362 unsigned long *out_value) {
363 if (!match_data || !subject || !out_value || group_num < 0) {
364 return false;
365 }
366
367 /* Get ovector (output vector with match offsets) */
368 PCRE2_SIZE *ovector = pcre2_get_ovector_pointer(match_data);
369 PCRE2_SIZE start = ovector[2 * group_num];
370 PCRE2_SIZE end = ovector[2 * group_num + 1];
371
372 if (start == PCRE2_UNSET || end == PCRE2_UNSET) {
373 return false; /* Group not matched */
374 }
375
376 /* Extract substring into temporary buffer */
377 size_t len = end - start;
378 if (len == 0 || len > 63) { /* Sanity check for reasonable number length */
379 return false;
380 }
381
382 char temp[64];
383 memcpy(temp, subject + start, len);
384 temp[len] = '\0';
385
386 /* Parse as unsigned long */
387 char *endptr;
388 errno = 0;
389 unsigned long value = strtoul(temp, &endptr, 10);
390
391 if (errno != 0 || endptr == temp || *endptr != '\0') {
392 return false; /* Parse failed */
393 }
394
395 *out_value = value;
396 return true;
397}
398
399int asciichat_pcre2_match(const pcre2_code *code, PCRE2_SPTR subject, PCRE2_SIZE length, PCRE2_SIZE startoffset,
400 uint32_t options, pcre2_match_data *match_data, pcre2_match_context *context) {
401 int result = pcre2_jit_match(code, subject, length, startoffset, options, match_data, context);
402 if (result == PCRE2_ERROR_JIT_BADOPTION || result == PCRE2_ERROR_JIT_STACKLIMIT)
403 return pcre2_match(code, subject, length, startoffset, options | PCRE2_NO_JIT, match_data, context);
404 return result;
405}
void atomic_store_u64(atomic_t *a, uint64_t value)
Atomically store a uint64_t value.
Definition atomic.c:241
uint64_t atomic_load_u64(atomic_t *a)
Atomically load a uint64_t value.
Definition atomic.c:233
⚛️ Atomic operations abstraction layer with debug tracking
⚙️ Common definitions, error codes, macros, and types shared throughout the application
Cross-platform error handling utilities.
unsigned int uint32_t
Definition common.h:58
#define SAFE_FREE(ptr)
Definition common.h:376
#define SAFE_MALLOC(size, cast)
Definition common.h:264
#define SAFE_GETENV(name)
Definition common.h:434
unsigned long long uint64_t
Definition common.h:59
#define log_warn(...)
Log a WARN message.
Definition log/log.h:574
#define log_error(...)
Log an ERROR message.
Definition log/log.h:587
#define log_debug(...)
Log a DEBUG message.
Definition log/log.h:548
int errno
bool lifecycle_is_initialized(const lifecycle_t *lc)
Definition lifecycle.c:155
void lifecycle_init_commit(lifecycle_t *lc)
Definition lifecycle.c:90
bool lifecycle_init(lifecycle_t *lc, const char *name)
Definition lifecycle.c:26
bool lifecycle_shutdown(lifecycle_t *lc)
Definition lifecycle.c:108
Lock-free module lifecycle state machine using pure stdatomic.
#define LIFECYCLE_INIT
Static initializer for module-global lifecycle variables (no sync primitive)
Definition lifecycle.h:68
📝 Logging API with multiple log levels and terminal output control
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:98
bool asciichat_pcre2_singleton_is_initialized(pcre2_singleton_t *singleton)
Check if a singleton was successfully initialized.
Definition pcre2.c:170
int asciichat_pcre2_match(const pcre2_code *code, PCRE2_SPTR subject, PCRE2_SIZE length, PCRE2_SIZE startoffset, uint32_t options, pcre2_match_data *match_data, pcre2_match_context *context)
Definition pcre2.c:399
void asciichat_pcre2_cleanup_all(void)
Free all PCRE2 singletons in the global registry.
Definition pcre2.c:214
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:308
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:269
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:339
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:361
pcre2_singleton_t * asciichat_pcre2_singleton_compile(const char *pattern, uint32_t flags)
Compile and cache a PCRE2 regex pattern with thread-safe singleton semantics.
Definition pcre2.c:47
void asciichat_pcre2_singleton_free(pcre2_singleton_t *singleton)
Free a PCRE2 singleton and its resources.
Definition pcre2.c:185
🔍 Centralized PCRE2 singleton pattern for efficient regex compilation
Atomic value wrapper for integral/boolean types.
Definition atomic.h:76
Represents a thread-safe compiled PCRE2 regex singleton.
Definition pcre2.c:22
char * pattern
Pattern string (owned by singleton, dynamically allocated)
Definition pcre2.c:26
uint32_t flags
PCRE2 compile flags.
Definition pcre2.c:27
struct pcre2_singleton * next
Next singleton in global registry.
Definition pcre2.c:28
pcre2_jit_stack * jit_stack
JIT stack for performance.
Definition pcre2.c:25
atomic_t code
Compiled regex (lazy init, atomic pointer as uint64_t)
Definition pcre2.c:24
lifecycle_t lc
Per-object compilation lifecycle (UNINITIALIZED → INITIALIZED)
Definition pcre2.c:23