/* SPDX-License-Identifier: GPL-3.0-or-later */ #define PCRE2_CODE_UNIT_WIDTH 8 #include "pcre2_bridge.h" #include #include #include #include #define REGEX_PCRE2_COMPILE_FLAGS \ (REGEX_PCRE2_CASELESS | REGEX_PCRE2_MULTILINE | REGEX_PCRE2_DOTALL | \ REGEX_PCRE2_EXTENDED | REGEX_PCRE2_UNGREEDY | REGEX_PCRE2_UTF | \ REGEX_PCRE2_UCP | REGEX_PCRE2_DUPNAMES) #define REGEX_PCRE2_ALL_FLAGS \ (REGEX_PCRE2_COMPILE_FLAGS | REGEX_PCRE2_GLOBAL) _Static_assert(sizeof(regex_pcre2_compile_result) == 20, "Compile result is part of ABI version 3."); _Static_assert(sizeof(regex_pcre2_limits) == 20, "Limits record is part of ABI version 3."); _Static_assert(sizeof(regex_pcre2_match_record) == 16, "Match record is part of ABI version 3."); _Static_assert(sizeof(regex_pcre2_name_record) == 12, "Name record is part of ABI version 3."); _Static_assert(sizeof(regex_pcre2_run_result) == 60, "Run result is part of ABI version 3."); _Static_assert(sizeof(regex_pcre2_trace_limits) == 20, "Trace limits are part of ABI version 3."); _Static_assert(sizeof(regex_pcre2_trace_event) == 32, "Trace event is part of ABI version 3."); _Static_assert(sizeof(regex_pcre2_trace_result) == 52, "Trace result is part of ABI version 3."); typedef struct regex_pcre2_run_buffers { regex_pcre2_match_record *records; uint32_t record_capacity; regex_pcre2_name_record *name_records; uint32_t name_record_capacity; uint8_t *name_bytes; uint32_t name_bytes_capacity; uint8_t *output; uint32_t output_capacity; } regex_pcre2_run_buffers; typedef struct regex_pcre2_trace_state { regex_pcre2_trace_event *events; uint32_t event_capacity; uint8_t *mark_bytes; uint32_t mark_bytes_capacity; uint32_t maximum_events; uint32_t maximum_trace_bytes; uint32_t event_count; uint32_t total_event_count; uint32_t mark_bytes_length; uint32_t events_truncated; uint32_t marks_truncated; } regex_pcre2_trace_state; static const uint8_t empty_bytes[1] = {0}; static uint32_t to_pcre2_options(uint32_t application_flags) { uint32_t options = 0; if ((application_flags & REGEX_PCRE2_CASELESS) != 0) { options |= PCRE2_CASELESS; } if ((application_flags & REGEX_PCRE2_MULTILINE) != 0) { options |= PCRE2_MULTILINE; } if ((application_flags & REGEX_PCRE2_DOTALL) != 0) { options |= PCRE2_DOTALL; } if ((application_flags & REGEX_PCRE2_EXTENDED) != 0) { options |= PCRE2_EXTENDED; } if ((application_flags & REGEX_PCRE2_UNGREEDY) != 0) { options |= PCRE2_UNGREEDY; } if ((application_flags & REGEX_PCRE2_UTF) != 0) { options |= PCRE2_UTF; } if ((application_flags & REGEX_PCRE2_UCP) != 0) { options |= PCRE2_UCP; } if ((application_flags & REGEX_PCRE2_DUPNAMES) != 0) { options |= PCRE2_DUPNAMES; } return options; } static int32_t fail_compile(regex_pcre2_compile_result *result, int32_t status) { if (result != NULL) { memset(result, 0, sizeof(*result)); result->status = status; } return status; } static int32_t fail_run(regex_pcre2_run_result *result, int32_t status, uint32_t phase, uint32_t offset) { if (result != NULL) { memset(result, 0, sizeof(*result)); result->status = status; result->error_phase = phase; result->error_offset = offset; } return status; } static int32_t fail_trace(regex_pcre2_trace_result *result, int32_t status, uint32_t phase, uint32_t offset) { if (result != NULL) { memset(result, 0, sizeof(*result)); result->status = status; result->error_phase = phase; result->error_offset = offset; result->last_complete_event = UINT32_MAX; } return status; } static int input_pointer_valid(const uint8_t *value, uint32_t length) { return value != NULL || length == 0; } static const uint8_t *nonnull_input(const uint8_t *value) { return value == NULL ? empty_bytes : value; } static int limits_valid(const regex_pcre2_limits *limits) { return limits != NULL && limits->maximum_matches > 0 && limits->maximum_matches <= REGEX_PCRE2_MAX_MATCHES && limits->maximum_capture_rows > 0 && limits->maximum_capture_rows <= REGEX_PCRE2_MAX_CAPTURE_ROWS && limits->match_limit > 0 && limits->match_limit <= REGEX_PCRE2_MAX_MATCH_LIMIT && limits->depth_limit > 0 && limits->depth_limit <= REGEX_PCRE2_MAX_DEPTH_LIMIT && limits->heap_limit_kib > 0 && limits->heap_limit_kib <= REGEX_PCRE2_MAX_HEAP_LIMIT_KIB; } static int buffers_valid(const regex_pcre2_run_buffers *buffers, int substitution) { if (buffers == NULL || (buffers->records == NULL && buffers->record_capacity != 0) || (buffers->name_records == NULL && buffers->name_record_capacity != 0) || (buffers->name_bytes == NULL && buffers->name_bytes_capacity != 0)) { return 0; } /* * PCRE2 writes a trailing NUL even when the semantic output capacity is * zero, so the bridge contract always requires the advertised +1 byte. */ if (substitution && buffers->output == NULL) { return 0; } return 1; } static uint32_t bounded_offset(PCRE2_SIZE value) { return value > UINT32_MAX ? UINT32_MAX : (uint32_t)value; } static int32_t copy_names(const pcre2_code *code, regex_pcre2_run_buffers *buffers, regex_pcre2_run_result *result) { uint32_t entry_size = 0; PCRE2_SPTR table = NULL; uint32_t index = 0; uint32_t name_offset = 0; if (result->name_count == 0) { return 0; } if (pcre2_pattern_info(code, PCRE2_INFO_NAMEENTRYSIZE, &entry_size) != 0 || pcre2_pattern_info(code, PCRE2_INFO_NAMETABLE, &table) != 0 || entry_size < 3 || table == NULL) { return REGEX_PCRE2_ERROR_CONFIG; } for (index = 0; index < result->name_count; index += 1) { const uint8_t *entry = table + (index * entry_size); uint32_t group_number = ((uint32_t)entry[0] << 8) | (uint32_t)entry[1]; uint32_t maximum_name = entry_size - 2; uint32_t name_length = 0; while (name_length < maximum_name && entry[2 + name_length] != 0) { name_length += 1; } if (result->name_record_count >= buffers->name_record_capacity || name_length > buffers->name_bytes_capacity - name_offset) { result->names_truncated = 1; break; } buffers->name_records[result->name_record_count].group_number = group_number; buffers->name_records[result->name_record_count].name_offset = name_offset; buffers->name_records[result->name_record_count].name_length = name_length; if (name_length > 0) { memcpy(buffers->name_bytes + name_offset, entry + 2, name_length); } result->name_record_count += 1; name_offset += name_length; } result->name_bytes_length = name_offset; return 0; } static int append_utf8_prefix(regex_pcre2_run_buffers *buffers, regex_pcre2_run_result *result, const uint8_t *source, uint32_t length) { uint32_t remaining = buffers->output_capacity - result->output_length; uint32_t copied = length < remaining ? length : remaining; if (copied < length) { while (copied > 0 && (source[copied] & 0xc0u) == 0x80u) { copied -= 1; } result->output_truncated = 1; } if (copied > 0) { memcpy(buffers->output + result->output_length, source, copied); result->output_length += copied; } return copied == length; } static int32_t configure_match_context_values(pcre2_match_context *context, uint32_t match_limit, uint32_t depth_limit, uint32_t heap_limit_kib) { if (pcre2_set_match_limit(context, match_limit) != 0 || pcre2_set_depth_limit(context, depth_limit) != 0 || pcre2_set_heap_limit(context, heap_limit_kib) != 0) { return REGEX_PCRE2_ERROR_CONFIG; } return 0; } static int32_t configure_match_context(pcre2_match_context *context, const regex_pcre2_limits *limits) { return configure_match_context_values( context, limits->match_limit, limits->depth_limit, limits->heap_limit_kib); } static int trace_limits_valid(const regex_pcre2_trace_limits *limits) { return limits != NULL && limits->maximum_events > 0 && limits->maximum_events <= REGEX_PCRE2_MAX_TRACE_EVENTS && limits->maximum_trace_bytes >= sizeof(regex_pcre2_trace_event) && limits->maximum_trace_bytes <= REGEX_PCRE2_MAX_TRACE_BYTES && limits->match_limit > 0 && limits->match_limit <= REGEX_PCRE2_MAX_MATCH_LIMIT && limits->depth_limit > 0 && limits->depth_limit <= REGEX_PCRE2_MAX_DEPTH_LIMIT && limits->heap_limit_kib > 0 && limits->heap_limit_kib <= REGEX_PCRE2_MAX_HEAP_LIMIT_KIB; } static uint32_t bounded_mark_length(PCRE2_SPTR mark, uint32_t *truncated) { uint32_t length = 0; if (mark == NULL) { return 0; } while (length < REGEX_PCRE2_MAX_TRACE_MARK_BYTES && mark[length] != 0) { length += 1; } if (length == REGEX_PCRE2_MAX_TRACE_MARK_BYTES && mark[length] != 0) { *truncated = 1; while (length > 0 && (mark[length] & 0xc0u) == 0x80u) { length -= 1; } } return length; } static int trace_callout(pcre2_callout_block *block, void *data) { regex_pcre2_trace_state *state = (regex_pcre2_trace_state *)data; regex_pcre2_trace_event *event = NULL; uint32_t mark_length = 0; uint32_t mark_was_truncated = 0; uint64_t required_trace_bytes = 0; if (block == NULL || state == NULL) { return PCRE2_ERROR_CALLOUT; } if (state->total_event_count < UINT32_MAX) { state->total_event_count += 1; } mark_length = bounded_mark_length(block->mark, &mark_was_truncated); required_trace_bytes = ((uint64_t)state->event_count + 1u) * sizeof(regex_pcre2_trace_event) + state->mark_bytes_length + mark_length; if (state->event_count >= state->maximum_events || state->event_count >= state->event_capacity || required_trace_bytes > state->maximum_trace_bytes || mark_length > state->mark_bytes_capacity - state->mark_bytes_length) { state->events_truncated = 1; return PCRE2_ERROR_CALLOUT; } event = &state->events[state->event_count]; event->callout_number = block->callout_number; event->pattern_position_byte = bounded_offset(block->pattern_position); event->next_item_length_byte = bounded_offset(block->next_item_length); event->subject_position_byte = bounded_offset(block->current_position); event->capture_top = block->capture_top; event->capture_last = block->capture_last; event->mark_offset = state->mark_bytes_length; event->mark_length = mark_length; if (mark_length > 0) { memcpy(state->mark_bytes + state->mark_bytes_length, block->mark, mark_length); state->mark_bytes_length += mark_length; } state->marks_truncated |= mark_was_truncated; state->event_count += 1; return 0; } static int32_t run_bounded(const uint8_t *pattern, uint32_t pattern_length, const uint8_t *subject, uint32_t subject_length, const uint8_t *replacement, uint32_t replacement_length, uint32_t application_flags, const regex_pcre2_limits *limits, regex_pcre2_run_buffers *buffers, int substitution, regex_pcre2_run_result *result) { pcre2_code *code = NULL; pcre2_match_data *match_data = NULL; pcre2_match_context *match_context = NULL; int compile_error = 0; PCRE2_SIZE compile_offset = 0; uint32_t capture_count = 0; uint32_t name_count = 0; uint32_t options = 0; uint32_t search_offset = 0; uint32_t match_options = 0; uint32_t copied_until = 0; uint32_t capture_rows = 0; int32_t status = 0; const uint8_t *safe_pattern = nonnull_input(pattern); const uint8_t *safe_subject = nonnull_input(subject); const uint8_t *safe_replacement = nonnull_input(replacement); if (result == NULL || !input_pointer_valid(pattern, pattern_length) || !input_pointer_valid(subject, subject_length) || (substitution && !input_pointer_valid(replacement, replacement_length)) || !buffers_valid(buffers, substitution)) { return fail_run(result, REGEX_PCRE2_ERROR_INVALID_ARGUMENT, REGEX_PCRE2_PHASE_BRIDGE, 0); } memset(result, 0, sizeof(*result)); if ((application_flags & ~REGEX_PCRE2_ALL_FLAGS) != 0) { return fail_run(result, REGEX_PCRE2_ERROR_UNSUPPORTED_FLAGS, REGEX_PCRE2_PHASE_BRIDGE, 0); } if (pattern_length > REGEX_PCRE2_MAX_PATTERN_BYTES) { return fail_run(result, REGEX_PCRE2_ERROR_PATTERN_TOO_LARGE, REGEX_PCRE2_PHASE_BRIDGE, 0); } if (subject_length > REGEX_PCRE2_MAX_SUBJECT_BYTES) { return fail_run(result, REGEX_PCRE2_ERROR_SUBJECT_TOO_LARGE, REGEX_PCRE2_PHASE_BRIDGE, 0); } if (substitution && replacement_length > REGEX_PCRE2_MAX_REPLACEMENT_BYTES) { return fail_run(result, REGEX_PCRE2_ERROR_REPLACEMENT_TOO_LARGE, REGEX_PCRE2_PHASE_BRIDGE, 0); } if (!limits_valid(limits)) { return fail_run(result, REGEX_PCRE2_ERROR_LIMIT_OUT_OF_RANGE, REGEX_PCRE2_PHASE_BRIDGE, 0); } options = to_pcre2_options(application_flags); result->effective_options = options; code = pcre2_compile(safe_pattern, pattern_length, options, &compile_error, &compile_offset, NULL); if (code == NULL) { status = compile_error; result->status = status; result->error_phase = REGEX_PCRE2_PHASE_COMPILE; result->error_offset = bounded_offset(compile_offset); goto cleanup; } if (pcre2_pattern_info(code, PCRE2_INFO_CAPTURECOUNT, &capture_count) != 0 || pcre2_pattern_info(code, PCRE2_INFO_NAMECOUNT, &name_count) != 0) { status = REGEX_PCRE2_ERROR_CONFIG; result->status = status; result->error_phase = REGEX_PCRE2_PHASE_BRIDGE; goto cleanup; } result->capture_count = capture_count; result->name_count = name_count; if (capture_count > REGEX_PCRE2_MAX_CAPTURE_GROUPS) { status = REGEX_PCRE2_ERROR_CAPTURE_LIMIT; result->status = status; result->error_phase = REGEX_PCRE2_PHASE_BRIDGE; goto cleanup; } status = copy_names(code, buffers, result); if (status != 0) { result->status = status; result->error_phase = REGEX_PCRE2_PHASE_BRIDGE; goto cleanup; } match_data = pcre2_match_data_create_from_pattern(code, NULL); match_context = pcre2_match_context_create(NULL); if (match_data == NULL || match_context == NULL) { status = PCRE2_ERROR_NOMEMORY; result->status = status; result->error_phase = REGEX_PCRE2_PHASE_BRIDGE; goto cleanup; } status = configure_match_context(match_context, limits); if (status != 0) { result->status = status; result->error_phase = REGEX_PCRE2_PHASE_BRIDGE; goto cleanup; } while (search_offset <= subject_length) { int match_status = pcre2_match(code, safe_subject, subject_length, search_offset, match_options, match_data, match_context); PCRE2_SIZE *ovector = NULL; uint32_t required_records = capture_count + 1; uint32_t required_capture_rows = capture_count == 0 ? 1 : capture_count; uint32_t group = 0; uint32_t start = 0; uint32_t end = 0; if (match_status == PCRE2_ERROR_NOMATCH) { break; } if (match_status < 0) { status = match_status; result->status = status; result->error_phase = REGEX_PCRE2_PHASE_MATCH; result->error_offset = search_offset; goto cleanup; } if (match_status == 0) { status = REGEX_PCRE2_ERROR_CONFIG; result->status = status; result->error_phase = REGEX_PCRE2_PHASE_BRIDGE; goto cleanup; } ovector = pcre2_get_ovector_pointer(match_data); start = bounded_offset(ovector[0]); end = bounded_offset(ovector[1]); if (start > end || end > subject_length) { status = REGEX_PCRE2_ERROR_CONFIG; result->status = status; result->error_phase = REGEX_PCRE2_PHASE_BRIDGE; goto cleanup; } if (result->match_count >= limits->maximum_matches || capture_rows + required_capture_rows > limits->maximum_capture_rows || required_records > buffers->record_capacity - result->record_count) { result->results_truncated = 1; break; } for (group = 0; group <= capture_count; group += 1) { regex_pcre2_match_record *record = &buffers->records[result->record_count]; PCRE2_SIZE native_start = ovector[group * 2]; PCRE2_SIZE native_end = ovector[group * 2 + 1]; record->match_number = result->match_count + 1; record->group_number = group; if (native_start == PCRE2_UNSET || native_end == PCRE2_UNSET) { record->start_byte = REGEX_PCRE2_UNSET_OFFSET; record->end_byte = REGEX_PCRE2_UNSET_OFFSET; } else { record->start_byte = bounded_offset(native_start); record->end_byte = bounded_offset(native_end); } result->record_count += 1; } result->match_count += 1; capture_rows += required_capture_rows; if (substitution && result->output_truncated == 0) { PCRE2_SIZE replacement_output_length = buffers->output_capacity - result->output_length + 1; int substitute_status = 0; if (!append_utf8_prefix(buffers, result, safe_subject + copied_until, start - copied_until)) { break; } replacement_output_length = buffers->output_capacity - result->output_length + 1; substitute_status = pcre2_substitute( code, safe_subject, subject_length, search_offset, match_options | PCRE2_SUBSTITUTE_MATCHED | PCRE2_SUBSTITUTE_REPLACEMENT_ONLY | PCRE2_SUBSTITUTE_UNSET_EMPTY, match_data, match_context, safe_replacement, replacement_length, buffers->output + result->output_length, &replacement_output_length); if (substitute_status == PCRE2_ERROR_NOMEMORY) { result->output_truncated = 1; break; } if (substitute_status < 0) { status = substitute_status; result->status = status; result->error_phase = REGEX_PCRE2_PHASE_SUBSTITUTE; result->error_offset = bounded_offset(replacement_output_length); goto cleanup; } result->output_length += bounded_offset(replacement_output_length); result->substitution_count += (uint32_t)substitute_status; copied_until = end; } if ((application_flags & REGEX_PCRE2_GLOBAL) == 0) { break; } { PCRE2_SIZE next_offset = search_offset; uint32_t next_options = 0; if (!pcre2_next_match(match_data, &next_offset, &next_options)) { break; } search_offset = bounded_offset(next_offset); match_options = next_options; } } if (substitution && result->output_truncated == 0) { (void)append_utf8_prefix(buffers, result, safe_subject + copied_until, subject_length - copied_until); } cleanup: if (match_context != NULL) { pcre2_match_context_free(match_context); } if (match_data != NULL) { pcre2_match_data_free(match_data); } if (code != NULL) { pcre2_code_free(code); } return status; } uint32_t regex_pcre2_bridge_abi_version(void) { return REGEX_PCRE2_BRIDGE_ABI_VERSION; } uint32_t regex_pcre2_config_flags(void) { uint32_t unicode = 0; uint32_t jit = 0; uint32_t flags = 0; if (pcre2_config(PCRE2_CONFIG_UNICODE, &unicode) != 0 || pcre2_config(PCRE2_CONFIG_JIT, &jit) != 0) { return 0; } if (unicode != 0) { flags |= REGEX_PCRE2_CONFIG_UNICODE; } if (jit != 0) { flags |= REGEX_PCRE2_CONFIG_JIT; } return flags; } const uint8_t *regex_pcre2_version(void) { static uint8_t version[64]; if (pcre2_config(PCRE2_CONFIG_VERSION, version) <= 0) { version[0] = '\0'; } return version; } int32_t regex_pcre2_error_message(int32_t error_code, uint8_t *output, uint32_t output_capacity) { int status = 0; if (output == NULL || output_capacity == 0) { return REGEX_PCRE2_ERROR_INVALID_ARGUMENT; } status = pcre2_get_error_message(error_code, output, output_capacity); if (status < 0) { output[0] = '\0'; } return status; } int32_t regex_pcre2_compile_probe(const uint8_t *pattern, uint32_t pattern_length, uint32_t application_flags, regex_pcre2_compile_result *result) { pcre2_code *code = NULL; int error_code = 0; PCRE2_SIZE error_offset = 0; uint32_t capture_count = 0; uint32_t name_count = 0; uint32_t options = 0; if (result == NULL || !input_pointer_valid(pattern, pattern_length)) { return fail_compile(result, REGEX_PCRE2_ERROR_INVALID_ARGUMENT); } memset(result, 0, sizeof(*result)); if ((application_flags & ~REGEX_PCRE2_COMPILE_FLAGS) != 0) { return fail_compile(result, REGEX_PCRE2_ERROR_UNSUPPORTED_FLAGS); } if (pattern_length > REGEX_PCRE2_MAX_PATTERN_BYTES) { return fail_compile(result, REGEX_PCRE2_ERROR_PATTERN_TOO_LARGE); } options = to_pcre2_options(application_flags); result->effective_options = options; code = pcre2_compile(nonnull_input(pattern), pattern_length, options, &error_code, &error_offset, NULL); if (code == NULL) { result->status = error_code; result->error_offset = bounded_offset(error_offset); return error_code; } if (pcre2_pattern_info(code, PCRE2_INFO_CAPTURECOUNT, &capture_count) != 0 || pcre2_pattern_info(code, PCRE2_INFO_NAMECOUNT, &name_count) != 0) { pcre2_code_free(code); return fail_compile(result, REGEX_PCRE2_ERROR_CONFIG); } result->capture_count = capture_count; result->name_count = name_count; pcre2_code_free(code); return 0; } int32_t regex_pcre2_execute( const uint8_t *pattern, uint32_t pattern_length, const uint8_t *subject, uint32_t subject_length, uint32_t application_flags, const regex_pcre2_limits *limits, regex_pcre2_match_record *records, uint32_t record_capacity, regex_pcre2_name_record *name_records, uint32_t name_record_capacity, uint8_t *name_bytes, uint32_t name_bytes_capacity, regex_pcre2_run_result *result) { regex_pcre2_run_buffers buffers = { records, record_capacity, name_records, name_record_capacity, name_bytes, name_bytes_capacity, NULL, 0}; return run_bounded(pattern, pattern_length, subject, subject_length, NULL, 0, application_flags, limits, &buffers, 0, result); } int32_t regex_pcre2_substitute( const uint8_t *pattern, uint32_t pattern_length, const uint8_t *subject, uint32_t subject_length, const uint8_t *replacement, uint32_t replacement_length, uint32_t application_flags, const regex_pcre2_limits *limits, regex_pcre2_match_record *records, uint32_t record_capacity, regex_pcre2_name_record *name_records, uint32_t name_record_capacity, uint8_t *name_bytes, uint32_t name_bytes_capacity, uint8_t *output, uint32_t output_capacity, regex_pcre2_run_result *result) { regex_pcre2_run_buffers buffers = { records, record_capacity, name_records, name_record_capacity, name_bytes, name_bytes_capacity, output, output_capacity}; return run_bounded(pattern, pattern_length, subject, subject_length, replacement, replacement_length, application_flags, limits, &buffers, 1, result); } int32_t regex_pcre2_trace( const uint8_t *pattern, uint32_t pattern_length, const uint8_t *subject, uint32_t subject_length, uint32_t application_flags, const regex_pcre2_trace_limits *limits, regex_pcre2_trace_event *events, uint32_t event_capacity, uint8_t *mark_bytes, uint32_t mark_bytes_capacity, regex_pcre2_trace_result *result) { pcre2_code *code = NULL; pcre2_match_data *match_data = NULL; pcre2_match_context *match_context = NULL; regex_pcre2_trace_state state; int compile_error = 0; PCRE2_SIZE compile_offset = 0; uint32_t capture_count = 0; uint32_t options = 0; int32_t match_status = 0; int32_t status = 0; if (result == NULL || !input_pointer_valid(pattern, pattern_length) || !input_pointer_valid(subject, subject_length) || events == NULL || event_capacity == 0 || (mark_bytes == NULL && mark_bytes_capacity != 0)) { return fail_trace(result, REGEX_PCRE2_ERROR_INVALID_ARGUMENT, REGEX_PCRE2_PHASE_BRIDGE, 0); } memset(result, 0, sizeof(*result)); result->last_complete_event = UINT32_MAX; if ((application_flags & ~REGEX_PCRE2_ALL_FLAGS) != 0) { return fail_trace(result, REGEX_PCRE2_ERROR_UNSUPPORTED_FLAGS, REGEX_PCRE2_PHASE_BRIDGE, 0); } if (pattern_length > REGEX_PCRE2_MAX_PATTERN_BYTES) { return fail_trace(result, REGEX_PCRE2_ERROR_PATTERN_TOO_LARGE, REGEX_PCRE2_PHASE_BRIDGE, 0); } if (subject_length > REGEX_PCRE2_MAX_SUBJECT_BYTES) { return fail_trace(result, REGEX_PCRE2_ERROR_SUBJECT_TOO_LARGE, REGEX_PCRE2_PHASE_BRIDGE, 0); } if (!trace_limits_valid(limits) || event_capacity < limits->maximum_events || event_capacity > REGEX_PCRE2_MAX_TRACE_EVENTS || mark_bytes_capacity > REGEX_PCRE2_MAX_TRACE_BYTES) { return fail_trace(result, REGEX_PCRE2_ERROR_LIMIT_OUT_OF_RANGE, REGEX_PCRE2_PHASE_BRIDGE, 0); } options = to_pcre2_options(application_flags) | PCRE2_AUTO_CALLOUT; result->effective_options = options; code = pcre2_compile(nonnull_input(pattern), pattern_length, options, &compile_error, &compile_offset, NULL); if (code == NULL) { status = compile_error; result->status = status; result->error_phase = REGEX_PCRE2_PHASE_COMPILE; result->error_offset = bounded_offset(compile_offset); goto cleanup; } if (pcre2_pattern_info(code, PCRE2_INFO_CAPTURECOUNT, &capture_count) != 0) { status = REGEX_PCRE2_ERROR_CONFIG; result->status = status; result->error_phase = REGEX_PCRE2_PHASE_BRIDGE; goto cleanup; } if (capture_count > REGEX_PCRE2_MAX_CAPTURE_GROUPS) { status = REGEX_PCRE2_ERROR_CAPTURE_LIMIT; result->status = status; result->error_phase = REGEX_PCRE2_PHASE_BRIDGE; goto cleanup; } match_data = pcre2_match_data_create_from_pattern(code, NULL); match_context = pcre2_match_context_create(NULL); if (match_data == NULL || match_context == NULL) { status = PCRE2_ERROR_NOMEMORY; result->status = status; result->error_phase = REGEX_PCRE2_PHASE_BRIDGE; goto cleanup; } memset(&state, 0, sizeof(state)); state.events = events; state.event_capacity = event_capacity; state.mark_bytes = mark_bytes; state.mark_bytes_capacity = mark_bytes_capacity; state.maximum_events = limits->maximum_events; state.maximum_trace_bytes = limits->maximum_trace_bytes; status = configure_match_context_values( match_context, limits->match_limit, limits->depth_limit, limits->heap_limit_kib); if (status != 0 || pcre2_set_callout(match_context, trace_callout, &state) != 0) { status = REGEX_PCRE2_ERROR_CONFIG; result->status = status; result->error_phase = REGEX_PCRE2_PHASE_BRIDGE; goto cleanup; } /* * The global flag is intentionally application-level for normal execution. * A trace represents one exact pcre2_match() invocation. */ match_status = pcre2_match(code, nonnull_input(subject), subject_length, 0, 0, match_data, match_context); result->native_match_status = match_status; result->event_count = state.event_count; result->total_event_count = state.total_event_count; result->mark_bytes_length = state.mark_bytes_length; result->trace_bytes_length = state.event_count * (uint32_t)sizeof(regex_pcre2_trace_event) + state.mark_bytes_length; result->events_truncated = state.events_truncated; result->marks_truncated = state.marks_truncated; result->last_complete_event = state.event_count == 0 ? UINT32_MAX : state.event_count - 1; if (match_status >= 0) { result->matched = 1; } else if (match_status == PCRE2_ERROR_NOMATCH || (match_status == PCRE2_ERROR_CALLOUT && state.events_truncated != 0)) { status = 0; } else { status = match_status; result->status = status; result->error_phase = REGEX_PCRE2_PHASE_MATCH; if (state.event_count > 0) { result->error_offset = events[state.event_count - 1].subject_position_byte; } } cleanup: if (match_context != NULL) { pcre2_match_context_free(match_context); } if (match_data != NULL) { pcre2_match_data_free(match_data); } if (code != NULL) { pcre2_code_free(code); } return status; } int32_t regex_pcre2_self_test(void) { static const uint8_t pattern[] = "(?\\p{L}+)-(?\\d+)"; static const uint8_t subject[] = "x Grüße-42 y"; static const uint8_t replacement[] = "$:$"; static const uint8_t trace_pattern[] = "a+b"; static const uint8_t trace_subject[] = "aaab"; regex_pcre2_compile_result compile_result; regex_pcre2_run_result run_result; regex_pcre2_trace_result trace_result; regex_pcre2_limits limits = {10, 100, 1000000, 1000, 32768}; regex_pcre2_trace_limits trace_limits = {100, 4096, 1000000, 1000, 32768}; regex_pcre2_match_record records[3]; regex_pcre2_name_record names[2]; regex_pcre2_trace_event trace_events[100]; uint8_t name_bytes[64]; uint8_t trace_marks[64]; uint8_t output[65]; int32_t status = regex_pcre2_compile_probe( pattern, (uint32_t)(sizeof(pattern) - 1), REGEX_PCRE2_UTF | REGEX_PCRE2_UCP, &compile_result); if (status != 0 || compile_result.capture_count != 2 || compile_result.name_count != 2) { return 1; } status = regex_pcre2_substitute( pattern, (uint32_t)(sizeof(pattern) - 1), subject, (uint32_t)(sizeof(subject) - 1), replacement, (uint32_t)(sizeof(replacement) - 1), REGEX_PCRE2_UTF | REGEX_PCRE2_UCP, &limits, records, 3, names, 2, name_bytes, sizeof(name_bytes), output, sizeof(output) - 1, &run_result); if (status != 0 || run_result.match_count != 1 || run_result.record_count != 3 || run_result.substitution_count != 1 || run_result.output_truncated != 0 || run_result.output_length != sizeof("x 42:Grüße y") - 1 || memcmp(output, "x 42:Grüße y", sizeof("x 42:Grüße y") - 1) != 0) { return 2; } status = regex_pcre2_trace( trace_pattern, (uint32_t)(sizeof(trace_pattern) - 1), trace_subject, (uint32_t)(sizeof(trace_subject) - 1), REGEX_PCRE2_UTF | REGEX_PCRE2_UCP, &trace_limits, trace_events, 100, trace_marks, sizeof(trace_marks), &trace_result); if (status != 0 || trace_result.status != 0 || trace_result.native_match_status <= 0 || trace_result.matched != 1 || trace_result.event_count == 0 || trace_result.events_truncated != 0 || trace_result.last_complete_event != trace_result.event_count - 1) { return 3; } if (regex_pcre2_bridge_abi_version() != REGEX_PCRE2_BRIDGE_ABI_VERSION || regex_pcre2_config_flags() != REGEX_PCRE2_CONFIG_UNICODE) { return 4; } if (strcmp((const char *)regex_pcre2_version(), "10.47 2025-10-21") != 0) { return 5; } return 0; }