Skip to content

Regex: compile the copy for the JIT engine - #13685

Open
bryancall wants to merge 2 commits into
apache:masterfrom
bryancall:regex-compile-copy-for-jit
Open

bryancall wants to merge 2 commits into
apache:masterfrom
bryancall:regex-compile-copy-for-jit

Conversation

@bryancall

@bryancall bryancall commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

Fourth of four, split out of #13671 at review request. Independent of the other three;
it touches neither the shared contexts nor the JIT stack.

A copied Regex silently changed engine

pcre2_code_copy() duplicates a compiled pattern but not the machine code the JIT
produced for it, because that code is position dependent. The copy constructor called
nothing else, so every copied Regex matched on the interpreter: the same answers, far
slower, and under a different set of resource limits. A pattern that reports a JIT stack
limit through the original quietly matched through a copy, which is how the two can
disagree about whether a subject is acceptable at all.

plugins/experimental/maxmind_acl copies every rule.

Compile the copy for the JIT after copying it, exactly as compile() does for a new
pattern, and describe that in the header, which called it a deep copy. Nothing copies a
Regex on a request path; the cost is what a compile costs.

pcre2_code_copy() also returns null when it cannot obtain memory, and the copy
constructor passed that straight to pcre2_jit_compile(). It is checked now, and a failed
copy leaves the object empty: the state a default constructed Regex is in and the one
empty() reports, rather than a Regex holding a null pattern.

The pcre2_jit_compile() result is deliberately not checked, for the same reason
compile() does not check it. A pattern the JIT declines still matches correctly on the
interpreter, this class has no way to tell a caller which engine it got, and "no JIT" is
not a single error code across PCRE2 versions and build options. Reporting the engine
belongs to the replacement API in #13663.

Tests

Four sections asserting the property that matters: a copy answers the same as its
original on a subject sized past the JIT stack bound, through the copy constructor, copy
assignment, a copy of a copy, and on an ordinary subject that simply matches.

Built against the unfixed implementation the original returns the stack limit error and
the copy returns a match. The test needs no knowledge of whether the build has a JIT,
because without one both sides agree.

Verification

Fedora 44, gcc 16.2.1, PCRE2 10.47. [Regex] clean under AddressSanitizer with UBSan.


Siblings from the same split: #13683 (JIT stack and shared match context) and #13684 (failed recompile). The interface replacement that retires this type is #13663.

pcre2_code_copy() copies a compiled pattern but not the machine code the JIT
produced for it, because that code is position dependent. The copy constructor
called nothing else, so every copied Regex matched on the interpreter: the same
answers, far slower, and under a different set of resource limits. A pattern
that reports a JIT stack limit through the original quietly matched through a
copy, which is how the two disagree about whether a subject is acceptable at all.
plugins/experimental/maxmind_acl copies every rule.

Compile the copy for the JIT after copying it, exactly as compile() does for a
new pattern, and describe that in the header, which called it a deep copy.

pcre2_code_copy() also returns null when it cannot obtain memory, and the copy
constructor passed that straight to pcre2_jit_compile(). Check it, and leave the
object empty when the copy fails: that is the state a default constructed Regex
is in and the state empty() reports, rather than a Regex holding a null pattern.

The pcre2_jit_compile() result is deliberately not checked. A pattern the JIT
declines still matches correctly on the interpreter, this class has no way to
tell a caller which engine it got, and "no JIT" is not a single error code across
PCRE2 versions and build options. compile() has the same property. Reporting the
engine belongs to the replacement API rather than here.

The test asserts the property that matters: a copy answers the same as its
original on a subject sized past the JIT stack bound. Before this change the
original returned the stack limit error and the copy returned a match. It needs
no knowledge of whether the build has a JIT, because without one both sides
simply agree.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The changes preserve JIT behavior, handle allocation failure safely, and include regression coverage.

Pull request overview

Updates Regex deep copies to preserve PCRE2 JIT behavior and safely handle failed pattern copies.

Changes:

  • Recompiles copied patterns for JIT.
  • Leaves failed copies empty.
  • Adds coverage for copy construction, assignment, chained copies, and matching parity.
File summaries
File Description
src/tsutil/unit_tests/test_Regex.cc Tests equivalent behavior across copy paths.
src/tsutil/Regex.cc Re-JITs copied patterns and handles null copies.
include/tsutil/Regex.h Documents JIT compilation during copying.
Review details
  • Files reviewed: 3/3 changed files
  • Comments generated: 0
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@bryancall

Copy link
Copy Markdown
Contributor Author

Copilot reviewed this one as approval recommended with no findings, so there is nothing to fix here.

Noting for the record that the earlier red CI on this PR was not a build failure. Every failing check went from pending to failure in 60 to 75 seconds, which is not long enough for any ATS lane to compile, and the one check that was retried (Fedora, at 05:01Z) took 16 minutes and passed. Three PRs opened within three minutes of each other, and the jobs were aborted rather than run. Retriggering below.

@bryancall

Copy link
Copy Markdown
Contributor Author

[approve ci centos clang-analyzer debian osx rocky ubuntu autest 0 autest 1 autest 2 autest 3]

@bryancall

Copy link
Copy Markdown
Contributor Author

[approve ci autest 1 autest 2 autest 3]

@bryancall

Copy link
Copy Markdown
Contributor Author

[approve ci autest 2]

@bryancall

Copy link
Copy Markdown
Contributor Author

[approve ci autest 3]

@JosiahWI JosiahWI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the comment verbosity in the source code is a little over the top, but the change looks good overall. Please clean up the comments or give a justification for the duplicated information.

The Regex unit tests look like they need to be refactored, and most likely split into multiple files, but that can be a follow-up.

Comment thread src/tsutil/Regex.cc Outdated
@bryancall

Copy link
Copy Markdown
Contributor Author

[approve ci centos clang-analyzer debian osx rocky ubuntu autest]

Fifteen lines of comment above three lines of code, most of it repeating the
paragraph already in the test that covers this. Keep what is not evident from the
code, which is that a null copy means pcre2 ran out of memory, that the JIT compile
is what stops the copy running on a different engine than the original, and that its
result is deliberately unchecked. The rest is in the commit that made the change and
in the test.
Copilot AI review requested due to automatic review settings September 15, 2026 19:20
@bryancall

Copy link
Copy Markdown
Contributor Author

Both points addressed, and thanks for the read.

Comment verbosity. Cut in 8c76dfb, replied in the thread. Same treatment I gave the comment you flagged on #13684. You were right both times: I had put the reasoning in the source where it duplicated the test, instead of in the commit message where it belongs.

Unit test structure. Agreed, and taking it as the follow-up you suggested rather than growing this PR. test_Regex.cc is past 1,300 lines and the split here added to it. I would rather not reorganize it underneath three open PRs that all touch it; once these land I will file an issue proposing the split so the refactor is reviewable on its own.

CI was 14/14 on the previous head. The only change since is comments, and [Regex] still passes 389 assertions in 19 cases locally; CI is re-running now.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The changes are fully covered by the supplied review and tests, with no unresolved blocking issues.

Review details
  • Files reviewed: 3/3 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

@bryancall
bryancall requested a review from JosiahWI September 15, 2026 20:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Backport Marked for backport for an LTS patch release Bug Core

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants