feat: publish Regex Tools 0.2.0
This commit is contained in:
71
engines/pcre2/CMakeLists.txt
Normal file
71
engines/pcre2/CMakeLists.txt
Normal file
@@ -0,0 +1,71 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
cmake_minimum_required(VERSION 3.25)
|
||||
project(regex_pcre2_bridge LANGUAGES C)
|
||||
|
||||
if(NOT EMSCRIPTEN)
|
||||
message(FATAL_ERROR "The staged PCRE2 pack must be built with Emscripten.")
|
||||
endif()
|
||||
|
||||
if(NOT DEFINED PCRE2_SOURCE_DIR OR NOT IS_DIRECTORY "${PCRE2_SOURCE_DIR}")
|
||||
message(FATAL_ERROR "PCRE2_SOURCE_DIR must identify the verified PCRE2 source checkout.")
|
||||
endif()
|
||||
|
||||
if(NOT DEFINED REGEX_PCRE2_OUTPUT_DIR)
|
||||
message(FATAL_ERROR "REGEX_PCRE2_OUTPUT_DIR must identify the private staging directory.")
|
||||
endif()
|
||||
|
||||
set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
|
||||
set(BUILD_STATIC_LIBS ON CACHE BOOL "" FORCE)
|
||||
set(PCRE2_BUILD_PCRE2_8 ON CACHE BOOL "" FORCE)
|
||||
set(PCRE2_BUILD_PCRE2_16 OFF CACHE BOOL "" FORCE)
|
||||
set(PCRE2_BUILD_PCRE2_32 OFF CACHE BOOL "" FORCE)
|
||||
set(PCRE2_BUILD_PCRE2GREP OFF CACHE BOOL "" FORCE)
|
||||
set(PCRE2_BUILD_TESTS OFF CACHE BOOL "" FORCE)
|
||||
set(PCRE2_DEBUG OFF CACHE STRING "" FORCE)
|
||||
set(PCRE2_REBUILD_CHARTABLES OFF CACHE BOOL "" FORCE)
|
||||
set(PCRE2_SHOW_REPORT OFF CACHE BOOL "" FORCE)
|
||||
set(PCRE2_STATIC_PIC OFF CACHE BOOL "" FORCE)
|
||||
set(PCRE2_SUPPORT_JIT OFF CACHE BOOL "" FORCE)
|
||||
set(PCRE2_SUPPORT_UNICODE ON CACHE BOOL "" FORCE)
|
||||
|
||||
add_subdirectory("${PCRE2_SOURCE_DIR}" pcre2-source EXCLUDE_FROM_ALL)
|
||||
|
||||
add_executable(regex-pcre2-pack pcre2_bridge.c)
|
||||
target_compile_features(regex-pcre2-pack PRIVATE c_std_11)
|
||||
target_compile_definitions(regex-pcre2-pack PRIVATE PCRE2_CODE_UNIT_WIDTH=8)
|
||||
target_compile_options(
|
||||
regex-pcre2-pack
|
||||
PRIVATE
|
||||
-O3
|
||||
-fno-ident
|
||||
-fvisibility=hidden
|
||||
)
|
||||
target_link_libraries(regex-pcre2-pack PRIVATE pcre2-8-static)
|
||||
target_link_options(
|
||||
regex-pcre2-pack
|
||||
PRIVATE
|
||||
-O3
|
||||
--no-entry
|
||||
"SHELL:-s ALLOW_MEMORY_GROWTH=1"
|
||||
"SHELL:-s ASSERTIONS=0"
|
||||
"SHELL:-s ENVIRONMENT=web,worker,node"
|
||||
"SHELL:-s EXPORT_ES6=1"
|
||||
"SHELL:-s EXPORT_NAME=createRegexPcre2"
|
||||
"SHELL:-s EXPORTED_FUNCTIONS=['_regex_pcre2_bridge_abi_version','_regex_pcre2_compile_probe','_regex_pcre2_config_flags','_regex_pcre2_error_message','_regex_pcre2_execute','_regex_pcre2_self_test','_regex_pcre2_substitute','_regex_pcre2_trace','_regex_pcre2_version','_malloc','_free']"
|
||||
"SHELL:-s EXPORTED_RUNTIME_METHODS=['HEAPU8','UTF8ToString']"
|
||||
"SHELL:-s FILESYSTEM=0"
|
||||
"SHELL:-s INITIAL_MEMORY=16777216"
|
||||
"SHELL:-s MALLOC=emmalloc"
|
||||
"SHELL:-s MAXIMUM_MEMORY=268435456"
|
||||
"SHELL:-s MODULARIZE=1"
|
||||
"SHELL:-s NO_EXIT_RUNTIME=1"
|
||||
"SHELL:-s STRICT=1"
|
||||
)
|
||||
set_target_properties(
|
||||
regex-pcre2-pack
|
||||
PROPERTIES
|
||||
OUTPUT_NAME pcre2
|
||||
RUNTIME_OUTPUT_DIRECTORY "${REGEX_PCRE2_OUTPUT_DIR}"
|
||||
SUFFIX ".mjs"
|
||||
)
|
||||
27
engines/pcre2/README.md
Normal file
27
engines/pcre2/README.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# PCRE2 engine bridge
|
||||
|
||||
This directory contains the GPL-3.0-or-later application bridge and
|
||||
deterministic build description for the bundled PCRE2 flavour. It deliberately
|
||||
does not vendor the upstream PCRE2 source tree or generated binaries.
|
||||
|
||||
ABI version 3 exposes:
|
||||
|
||||
- exact engine identity and configuration;
|
||||
- compile diagnostics with UTF-8 byte offsets;
|
||||
- bounded non-overlapping matching with copied group-zero/capture records and
|
||||
copied capture-name metadata;
|
||||
- bounded native `pcre2_substitute()` semantics over the same retained match
|
||||
sequence;
|
||||
- bounded automatic-callout tracing from one native `pcre2_match()` invocation,
|
||||
with copied event and mark data and an immediate native stop at either trace
|
||||
cap;
|
||||
- match-step, depth, heap, match-count, capture-row and output limits;
|
||||
- no native pointer or compiled-code handle across a call boundary.
|
||||
|
||||
All PCRE2 allocations are released before an ABI call returns. JavaScript owns
|
||||
and frees every input/result buffer in `finally`, and the containing worker is
|
||||
terminated on timeout, crash, cancellation or supersession.
|
||||
|
||||
The explicit offline source/toolchain identity, build, verification and local
|
||||
asset-install workflow is documented in
|
||||
[`docs/ENGINE_BUILDS.md`](../../docs/ENGINE_BUILDS.md).
|
||||
910
engines/pcre2/pcre2_bridge.c
Normal file
910
engines/pcre2/pcre2_bridge.c
Normal file
@@ -0,0 +1,910 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
|
||||
#define PCRE2_CODE_UNIT_WIDTH 8
|
||||
|
||||
#include "pcre2_bridge.h"
|
||||
|
||||
#include <pcre2.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#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[] = "(?<word>\\p{L}+)-(?<number>\\d+)";
|
||||
static const uint8_t subject[] = "x Grüße-42 y";
|
||||
static const uint8_t replacement[] = "$<number>:$<word>";
|
||||
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;
|
||||
}
|
||||
236
engines/pcre2/pcre2_bridge.h
Normal file
236
engines/pcre2/pcre2_bridge.h
Normal file
@@ -0,0 +1,236 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
|
||||
#ifndef REGEX_TOOLS_PCRE2_BRIDGE_H
|
||||
#define REGEX_TOOLS_PCRE2_BRIDGE_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define REGEX_PCRE2_BRIDGE_ABI_VERSION 3u
|
||||
#define REGEX_PCRE2_MAX_PATTERN_BYTES 1048576u
|
||||
#define REGEX_PCRE2_MAX_SUBJECT_BYTES 16777216u
|
||||
#define REGEX_PCRE2_MAX_REPLACEMENT_BYTES 262144u
|
||||
#define REGEX_PCRE2_MAX_MATCHES 10000u
|
||||
#define REGEX_PCRE2_MAX_CAPTURE_ROWS 100000u
|
||||
#define REGEX_PCRE2_MAX_CAPTURE_GROUPS 1000u
|
||||
#define REGEX_PCRE2_MAX_MATCH_LIMIT 100000000u
|
||||
#define REGEX_PCRE2_MAX_DEPTH_LIMIT 100000u
|
||||
#define REGEX_PCRE2_MAX_HEAP_LIMIT_KIB 131072u
|
||||
#define REGEX_PCRE2_MAX_TRACE_EVENTS 50000u
|
||||
#define REGEX_PCRE2_MAX_TRACE_BYTES 10485760u
|
||||
#define REGEX_PCRE2_MAX_TRACE_MARK_BYTES 1024u
|
||||
#define REGEX_PCRE2_UNSET_OFFSET UINT32_MAX
|
||||
|
||||
enum regex_pcre2_flag {
|
||||
REGEX_PCRE2_CASELESS = 1u << 0,
|
||||
REGEX_PCRE2_MULTILINE = 1u << 1,
|
||||
REGEX_PCRE2_DOTALL = 1u << 2,
|
||||
REGEX_PCRE2_EXTENDED = 1u << 3,
|
||||
REGEX_PCRE2_UNGREEDY = 1u << 4,
|
||||
REGEX_PCRE2_UTF = 1u << 5,
|
||||
REGEX_PCRE2_UCP = 1u << 6,
|
||||
REGEX_PCRE2_DUPNAMES = 1u << 7,
|
||||
REGEX_PCRE2_GLOBAL = 1u << 8
|
||||
};
|
||||
|
||||
enum regex_pcre2_config_flag {
|
||||
REGEX_PCRE2_CONFIG_UNICODE = 1u << 0,
|
||||
REGEX_PCRE2_CONFIG_JIT = 1u << 1
|
||||
};
|
||||
|
||||
enum regex_pcre2_bridge_error {
|
||||
REGEX_PCRE2_ERROR_INVALID_ARGUMENT = -10001,
|
||||
REGEX_PCRE2_ERROR_UNSUPPORTED_FLAGS = -10002,
|
||||
REGEX_PCRE2_ERROR_PATTERN_TOO_LARGE = -10003,
|
||||
REGEX_PCRE2_ERROR_CONFIG = -10004,
|
||||
REGEX_PCRE2_ERROR_SUBJECT_TOO_LARGE = -10005,
|
||||
REGEX_PCRE2_ERROR_REPLACEMENT_TOO_LARGE = -10006,
|
||||
REGEX_PCRE2_ERROR_LIMIT_OUT_OF_RANGE = -10007,
|
||||
REGEX_PCRE2_ERROR_CAPTURE_LIMIT = -10008,
|
||||
REGEX_PCRE2_ERROR_OUTPUT_BUFFER = -10009
|
||||
};
|
||||
|
||||
enum regex_pcre2_error_phase {
|
||||
REGEX_PCRE2_PHASE_NONE = 0,
|
||||
REGEX_PCRE2_PHASE_BRIDGE = 1,
|
||||
REGEX_PCRE2_PHASE_COMPILE = 2,
|
||||
REGEX_PCRE2_PHASE_MATCH = 3,
|
||||
REGEX_PCRE2_PHASE_SUBSTITUTE = 4
|
||||
};
|
||||
|
||||
/*
|
||||
* This 20-byte, five-u32 record is retained for deterministic compile-only
|
||||
* build verification. Runtime callers should use the bounded APIs below.
|
||||
*/
|
||||
typedef struct regex_pcre2_compile_result {
|
||||
int32_t status;
|
||||
uint32_t error_offset;
|
||||
uint32_t capture_count;
|
||||
uint32_t name_count;
|
||||
uint32_t effective_options;
|
||||
} regex_pcre2_compile_result;
|
||||
|
||||
/* All values are required and validated against the bridge hard maxima. */
|
||||
typedef struct regex_pcre2_limits {
|
||||
uint32_t maximum_matches;
|
||||
uint32_t maximum_capture_rows;
|
||||
uint32_t match_limit;
|
||||
uint32_t depth_limit;
|
||||
uint32_t heap_limit_kib;
|
||||
} regex_pcre2_limits;
|
||||
|
||||
/*
|
||||
* One record is emitted for group zero and every capture group of each
|
||||
* retained match. Unset captures use REGEX_PCRE2_UNSET_OFFSET for both bounds.
|
||||
*/
|
||||
typedef struct regex_pcre2_match_record {
|
||||
uint32_t match_number;
|
||||
uint32_t group_number;
|
||||
uint32_t start_byte;
|
||||
uint32_t end_byte;
|
||||
} regex_pcre2_match_record;
|
||||
|
||||
/* Names are UTF-8 slices into the caller-owned name byte buffer. */
|
||||
typedef struct regex_pcre2_name_record {
|
||||
uint32_t group_number;
|
||||
uint32_t name_offset;
|
||||
uint32_t name_length;
|
||||
} regex_pcre2_name_record;
|
||||
|
||||
/*
|
||||
* Trace collection has an independent bound from match materialization. The
|
||||
* byte cap covers the fixed event records plus copied mark bytes.
|
||||
*/
|
||||
typedef struct regex_pcre2_trace_limits {
|
||||
uint32_t maximum_events;
|
||||
uint32_t maximum_trace_bytes;
|
||||
uint32_t match_limit;
|
||||
uint32_t depth_limit;
|
||||
uint32_t heap_limit_kib;
|
||||
} regex_pcre2_trace_limits;
|
||||
|
||||
/*
|
||||
* Every field except the mark slice is copied directly from a PCRE2 callout
|
||||
* block. Positions and lengths are offsets in the 8-bit UTF-8 pattern or
|
||||
* subject supplied to the engine.
|
||||
*/
|
||||
typedef struct regex_pcre2_trace_event {
|
||||
uint32_t callout_number;
|
||||
uint32_t pattern_position_byte;
|
||||
uint32_t next_item_length_byte;
|
||||
uint32_t subject_position_byte;
|
||||
uint32_t capture_top;
|
||||
uint32_t capture_last;
|
||||
uint32_t mark_offset;
|
||||
uint32_t mark_length;
|
||||
} regex_pcre2_trace_event;
|
||||
|
||||
/*
|
||||
* A zero status means that a bounded trace was returned. native_match_status
|
||||
* retains pcre2_match()'s exact result, including PCRE2_ERROR_CALLOUT when the
|
||||
* bridge deliberately stopped at a trace cap. last_complete_event is
|
||||
* UINT32_MAX when no complete event was copied.
|
||||
*/
|
||||
typedef struct regex_pcre2_trace_result {
|
||||
int32_t status;
|
||||
uint32_t error_phase;
|
||||
uint32_t error_offset;
|
||||
int32_t native_match_status;
|
||||
uint32_t effective_options;
|
||||
uint32_t event_count;
|
||||
uint32_t total_event_count;
|
||||
uint32_t mark_bytes_length;
|
||||
uint32_t trace_bytes_length;
|
||||
uint32_t events_truncated;
|
||||
uint32_t marks_truncated;
|
||||
uint32_t last_complete_event;
|
||||
uint32_t matched;
|
||||
} regex_pcre2_trace_result;
|
||||
|
||||
/*
|
||||
* This fixed 60-byte record contains no native pointers. `status` is zero on
|
||||
* success. Otherwise `error_phase` distinguishes bridge validation, compile,
|
||||
* match and replacement errors; `error_offset` is a UTF-8 byte offset when the
|
||||
* phase provides one.
|
||||
*/
|
||||
typedef struct regex_pcre2_run_result {
|
||||
int32_t status;
|
||||
uint32_t error_phase;
|
||||
uint32_t error_offset;
|
||||
uint32_t capture_count;
|
||||
uint32_t name_count;
|
||||
uint32_t match_count;
|
||||
uint32_t record_count;
|
||||
uint32_t name_record_count;
|
||||
uint32_t name_bytes_length;
|
||||
uint32_t effective_options;
|
||||
uint32_t results_truncated;
|
||||
uint32_t names_truncated;
|
||||
uint32_t output_length;
|
||||
uint32_t substitution_count;
|
||||
uint32_t output_truncated;
|
||||
} regex_pcre2_run_result;
|
||||
|
||||
uint32_t regex_pcre2_bridge_abi_version(void);
|
||||
uint32_t regex_pcre2_config_flags(void);
|
||||
const uint8_t *regex_pcre2_version(void);
|
||||
|
||||
int32_t regex_pcre2_error_message(int32_t error_code,
|
||||
uint8_t *output,
|
||||
uint32_t output_capacity);
|
||||
|
||||
int32_t regex_pcre2_compile_probe(const uint8_t *pattern,
|
||||
uint32_t pattern_length,
|
||||
uint32_t application_flags,
|
||||
regex_pcre2_compile_result *result);
|
||||
|
||||
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);
|
||||
|
||||
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,
|
||||
/* `output` must have output_capacity + 1 writable bytes for PCRE2's NUL. */
|
||||
uint8_t *output,
|
||||
uint32_t output_capacity,
|
||||
regex_pcre2_run_result *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);
|
||||
|
||||
int32_t regex_pcre2_self_test(void);
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user