From e3d18689d59f0d7f5cbfd75daeeb123008742011 Mon Sep 17 00:00:00 2001 From: Zoltan Herczeg Date: Thu, 22 Oct 2026 10:58:06 +0000 Subject: [PATCH] Reject single character optimizations which affected by control verbs --- src/pcre2_jit_compile.c | 113 ++++++++++++++++++++++++---------------- testdata/testinput2 | 24 ++++++++- testdata/testoutput2 | 34 +++++++++++- 3 files changed, 124 insertions(+), 47 deletions(-) diff --git a/src/pcre2_jit_compile.c b/src/pcre2_jit_compile.c index 250b10e17..08284185a 100644 --- a/src/pcre2_jit_compile.c +++ b/src/pcre2_jit_compile.c @@ -219,6 +219,8 @@ typedef struct stub_list { enum frame_types { no_frame = -1, no_stack = -2 }; +enum verb_offset_types { verb_offset_then = 0x1, verb_offset_any = 0x2 }; + enum control_types { type_mark = 0, type_then_trap = 1 }; enum early_fail_types { type_skip = 0, type_fail = 1, type_fail_range = 2 }; @@ -403,7 +405,7 @@ typedef struct compiler_common { /* Bitset for tracking capture bracket status. */ sljit_u8 *cbracket_bitset; /* Tells whether the starting offset is a target of then. */ - sljit_u8 *then_offsets; + sljit_u8 *verb_offsets; /* Current position where a THEN must jump. */ then_trap_backtrack *then_trap; /* Starting offset of private data for capturing brackets. */ @@ -1302,31 +1304,40 @@ check_opcode_types(compiler_common *common, PCRE2_SPTR cc, PCRE2_SPTR ccend) cc += 1 + LINK_SIZE; break; + case OP_THEN: + common->has_then = TRUE; + common->control_head_ptr = 1; + PCRE2_FALLTHROUGH /* Fall through */ + + case OP_PRUNE: + common->verb_offsets = (sljit_u8*)1; + cc += 1; + break; + case OP_THEN_ARG: common->has_then = TRUE; common->control_head_ptr = 1; PCRE2_FALLTHROUGH /* Fall through */ - case OP_COMMIT_ARG: case OP_PRUNE_ARG: + common->verb_offsets = (sljit_u8*)1; + PCRE2_FALLTHROUGH /* Fall through */ + + case OP_COMMIT_ARG: case OP_MARK: set_mark = TRUE; cc += 1 + 2 + cc[1]; break; - case OP_THEN: - common->has_then = TRUE; - common->control_head_ptr = 1; - cc += 1; - break; - case OP_SKIP: + common->verb_offsets = (sljit_u8*)1; if (cc < assert_back_end) common->has_skip_in_assert_back = TRUE; cc += 1; break; case OP_SKIP_ARG: + common->verb_offsets = (sljit_u8*)1; common->control_head_ptr = 1; common->has_skip_arg = TRUE; if (cc < assert_back_end) @@ -1434,7 +1445,7 @@ detect_early_fail(compiler_common *common, PCRE2_SPTR cc, int *private_data_star count = start; cc += 1 + LINK_SIZE + ((*cc == OP_CBRA) ? IMM2_SIZE : 0); - if (!common->has_then || common->then_offsets[cc - common->start] == 0) + if (common->verb_offsets == NULL || common->verb_offsets[cc - common->start] == 0) { while (TRUE) { @@ -3492,31 +3503,25 @@ copy_recurse_data(compiler_common *common, PCRE2_SPTR cc, PCRE2_SPTR ccend, int delayed_mem_copy_finish(&status); } -static SLJIT_INLINE void set_then_offsets(compiler_common *common) +static SLJIT_INLINE void set_verb_offsets(compiler_common *common) { PCRE2_SPTR cc = common->start; - PCRE2_SPTR end; + PCRE2_SPTR next_top_alternative = cc + GET(cc, 1); BOOL has_alternatives = TRUE; - sljit_s32 start = 1 + LINK_SIZE; - sljit_s32 last_alternative, end_offset; + sljit_s32 start = 1 + LINK_SIZE, top_start = 1 + LINK_SIZE; + sljit_s32 end_offset; SLJIT_ASSERT(*cc == OP_BRA); - end = cc; - do - { - last_alternative = (sljit_s32)((end - cc) + 1 + LINK_SIZE); - end += GET(end, 1); - } - while (*end == OP_ALT); + SLJIT_ASSERT(*next_top_alternative == OP_ALT || *next_top_alternative == OP_KET); cc += 1 + LINK_SIZE; - while (cc < end) + while (*cc != OP_END) { if (*cc >= OP_ASSERT && *cc <= OP_SCOND) { end_offset = (sljit_s32)((bracketend(cc) - common->start) - (1 + LINK_SIZE)); - common->then_offsets[end_offset] = (sljit_u8)has_alternatives; - SLJIT_ASSERT(common->private_data_ptrs[end - common->start] == 0); + common->verb_offsets[end_offset] = (sljit_u8)has_alternatives; + SLJIT_ASSERT(common->private_data_ptrs[end_offset] == 0); common->private_data_ptrs[end_offset] = start; has_alternatives = cc[GET(cc, 1)] == OP_ALT; @@ -3554,9 +3559,9 @@ static SLJIT_INLINE void set_then_offsets(compiler_common *common) if (*cc >= OP_KET && *cc <= OP_KETRPOS) { - has_alternatives = (BOOL)common->then_offsets[cc - common->start]; + has_alternatives = (BOOL)common->verb_offsets[cc - common->start]; start = common->private_data_ptrs[cc - common->start]; - common->then_offsets[cc - common->start] = 0; + common->verb_offsets[cc - common->start] = 0; common->private_data_ptrs[cc - common->start] = 0; cc += 1 + LINK_SIZE; continue; @@ -3564,6 +3569,12 @@ static SLJIT_INLINE void set_then_offsets(compiler_common *common) if (*cc == OP_ALT) { + if (cc == next_top_alternative) { + next_top_alternative += GET(next_top_alternative, 1); + SLJIT_ASSERT(*next_top_alternative == OP_ALT || *next_top_alternative == OP_KET); + top_start = (sljit_s32)(cc + 1 + LINK_SIZE - common->start); + } + cc += 1 + LINK_SIZE; if (has_alternatives) @@ -3578,9 +3589,21 @@ static SLJIT_INLINE void set_then_offsets(compiler_common *common) continue; } - if (*cc >= OP_THEN && *cc <= OP_THEN_ARG && start != 0) - common->then_offsets[start] = (start == last_alternative) + 1; + switch (*cc) + { + case OP_THEN: + case OP_THEN_ARG: + if (start != 0) + common->verb_offsets[start] |= (start == top_start && *next_top_alternative == OP_KET) ? verb_offset_any : verb_offset_then; + break; + case OP_PRUNE: + case OP_PRUNE_ARG: + case OP_SKIP: + case OP_SKIP_ARG: + common->verb_offsets[top_start] |= verb_offset_any; + break; + } cc = next_opcode(common, cc); } } @@ -12479,7 +12502,7 @@ compile_matchingpath(compiler_common *common, PCRE2_SPTR cc, PCRE2_SPTR ccend, SLJIT_ASSERT(*ccend == OP_END || (*ccend >= OP_ALT && *ccend <= OP_KETRPOS)); - if (common->has_then && common->then_offsets[cc - common->start] == 1) + if (common->has_then && (common->verb_offsets[cc - common->start] & verb_offset_then) != 0) { SLJIT_ASSERT(*ccend != OP_END && common->control_head_ptr != 0); has_then_trap = TRUE; @@ -14671,18 +14694,18 @@ jit_compile(pcre2_code *code, sljit_u32 mode) common->cbra_ptr = OVECTOR_START + (re->top_bracket + 1) * 2 * sizeof(sljit_sw); private_data_size = common->cbra_ptr + (re->top_bracket + 1) * sizeof(sljit_sw); - if (common->has_then) + if (common->verb_offsets != NULL) { total_length = ccend - common->start; - common->then_offsets = (sljit_u8 *)SLJIT_MALLOC(total_length, allocator_data); - if (!common->then_offsets) + common->verb_offsets = (sljit_u8 *)SLJIT_MALLOC(total_length, allocator_data); + if (!common->verb_offsets) { SLJIT_FREE(common->private_data_ptrs, allocator_data); return PCRE2_ERROR_NOMEMORY; } - memset(common->then_offsets, 0, total_length); - set_then_offsets(common); + memset(common->verb_offsets, 0, total_length); + set_verb_offsets(common); } if ((re->overall_options & PCRE2_ANCHORED) == 0 && @@ -14697,8 +14720,8 @@ jit_compile(pcre2_code *code, sljit_u32 mode) if (private_data_size > 65536) { SLJIT_FREE(common->private_data_ptrs, allocator_data); - if (common->has_then) - SLJIT_FREE(common->then_offsets, allocator_data); + if (common->verb_offsets != NULL) + SLJIT_FREE(common->verb_offsets, allocator_data); return PCRE2_ERROR_JIT_UNSUPPORTED; } @@ -14706,8 +14729,8 @@ jit_compile(pcre2_code *code, sljit_u32 mode) if (!compiler) { SLJIT_FREE(common->private_data_ptrs, allocator_data); - if (common->has_then) - SLJIT_FREE(common->then_offsets, allocator_data); + if (common->verb_offsets != NULL) + SLJIT_FREE(common->verb_offsets, allocator_data); return PCRE2_ERROR_NOMEMORY; } @@ -14809,8 +14832,8 @@ jit_compile(pcre2_code *code, sljit_u32 mode) { sljit_free_compiler(compiler); SLJIT_FREE(common->private_data_ptrs, allocator_data); - if (common->has_then) - SLJIT_FREE(common->then_offsets, allocator_data); + if (common->verb_offsets != NULL) + SLJIT_FREE(common->verb_offsets, allocator_data); PRIV(jit_free_rodata)(common->read_only_data_head, allocator_data); return PCRE2_ERROR_NOMEMORY; } @@ -14894,8 +14917,8 @@ jit_compile(pcre2_code *code, sljit_u32 mode) { sljit_free_compiler(compiler); SLJIT_FREE(common->private_data_ptrs, allocator_data); - if (common->has_then) - SLJIT_FREE(common->then_offsets, allocator_data); + if (common->verb_offsets != NULL) + SLJIT_FREE(common->verb_offsets, allocator_data); PRIV(jit_free_rodata)(common->read_only_data_head, allocator_data); return PCRE2_ERROR_NOMEMORY; } @@ -15013,8 +15036,8 @@ jit_compile(pcre2_code *code, sljit_u32 mode) sljit_free_compiler(compiler); SLJIT_FREE(common->private_data_ptrs, allocator_data); - if (common->has_then) - SLJIT_FREE(common->then_offsets, allocator_data); + if (common->verb_offsets != NULL) + SLJIT_FREE(common->verb_offsets, allocator_data); PRIV(jit_free_rodata)(common->read_only_data_head, allocator_data); return PCRE2_ERROR_NOMEMORY; } @@ -15182,8 +15205,8 @@ jit_compile(pcre2_code *code, sljit_u32 mode) #endif /* SUPPORT_UNICODE */ SLJIT_FREE(common->private_data_ptrs, allocator_data); - if (common->has_then) - SLJIT_FREE(common->then_offsets, allocator_data); + if (common->verb_offsets != NULL) + SLJIT_FREE(common->verb_offsets, allocator_data); executable_func = sljit_generate_code(compiler, 0, NULL); executable_size = sljit_get_generated_code_size(compiler); diff --git a/testdata/testinput2 b/testdata/testinput2 index ab50b797e..a0d521738 100644 --- a/testdata/testinput2 +++ b/testdata/testinput2 @@ -5527,13 +5527,35 @@ a)"xI aa aaaa -/a+?b|a+?(*THEN)/endanchored +/a+?b|a+?(*THEN:arg)/endanchored aaxaa aaxaab /a+?(?:b(*THEN)c|c(*THEN)b)/endanchored aaabcaaacbaaabc +/a+?(*SKIP)/endanchored + aa + aaaa + +/a+?b|a+?(*SKIP:arg)/endanchored + aaxaa + aaxaab + +/a+?(?:b(*SKIP)c|c(*SKIP)b)/endanchored + aaabcaaacbaaabc + +/a+?(*PRUNE)/endanchored + aa + aaaa + +/a+?b|a+?(*PRUNE:arg)/endanchored + aaxaa + aaxaab + +/a+?(?:b(*PRUNE)c|c(*PRUNE)b)/endanchored + aaabcaaacbaaabc + # Check auto-anchoring when there is a group that is never obeyed at # the start of a branch. diff --git a/testdata/testoutput2 b/testdata/testoutput2 index 9fb4658c0..c9f1e2afe 100644 --- a/testdata/testoutput2 +++ b/testdata/testoutput2 @@ -17168,7 +17168,7 @@ No match aaaa 0: a -/a+?b|a+?(*THEN)/endanchored +/a+?b|a+?(*THEN:arg)/endanchored aaxaa 0: a aaxaab @@ -17178,6 +17178,38 @@ No match aaabcaaacbaaabc 0: aaabc +/a+?(*SKIP)/endanchored + aa + 0: a + aaaa + 0: a + +/a+?b|a+?(*SKIP:arg)/endanchored + aaxaa + 0: aa + aaxaab + 0: aab + +/a+?(?:b(*SKIP)c|c(*SKIP)b)/endanchored + aaabcaaacbaaabc + 0: aaabc + +/a+?(*PRUNE)/endanchored + aa + 0: a + aaaa + 0: a + +/a+?b|a+?(*PRUNE:arg)/endanchored + aaxaa + 0: a + aaxaab + 0: aab + +/a+?(?:b(*PRUNE)c|c(*PRUNE)b)/endanchored + aaabcaaacbaaabc + 0: aaabc + # Check auto-anchoring when there is a group that is never obeyed at # the start of a branch.