Skip to content

Fix the JIT prefix scan of negated classes at 16 and 32 bits - #980

Merged
NWilson merged 1 commit into
PCRE2Project:mainfrom
mattst88:jit-nclass-wide
Sep 12, 2026
Merged

NWilson merged 1 commit into
PCRE2Project:mainfrom
mattst88:jit-nclass-wide

Conversation

@mattst88

@mattst88 mattst88 commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

In the 16-bit and 32-bit libraries a negated class accepts every code unit above 255, whatever its bitmap says about 255 itself. scan_prefix() only took the class to accept those code units when bit 255 was set, so a negated class which leaves out \xff, such as [^\x00-\x40\x42-\xff], was taken to accept A alone. The JIT then fast-forwarded to the next A and missed a \x{100} before it, which the interpreter matches:

$ pcre2test -16 -jit
/[^\x00-\x40\x42-\xff]/
\x{100}
No match

This treats a negated class as accepting everything above the bitmap at those widths, as pcre2_study() already does when it builds the start bitmap. 10.47 is affected as well, so it may be worth a backport.

I found this while extending #941 to 16 and 32 bits, and the next revision of that PR builds on this change. The new tests in testinput11 fail under the JIT without the fix. RunTest and pcre2_jit_test pass at all three widths.

Comment thread src/pcre2_jit_compile.c Outdated
bytes = (sljit_u8*) (cc + 1);
/* Code units above the bitmap are accepted when its highest bit is set.
A negated class accepts all of them regardless, since the bitmap only
covers the first 256. */

@zherczeg zherczeg Sep 11, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Isn't the type (class/nclass) represents the acceptance of chars > 255? If class can accept characters > 255 then nclass is meaningless.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes, you're right. The opcode alone decides it, as it does in the
interpreter, where a code unit above 255 fails an OP_CLASS and passes an
OP_NCLASS without the bitmap being looked at. Bit 255 was never the right
test: the fix now sets accept_above from the opcode at 16 and 32 bits, and
to FALSE at 8 bits, where there is nothing above the bitmap.

That also covers the other direction. A positive class which includes \xff,
such as [A\xff], used to make its position count as any character, which was
only a missed optimization, and it now fast-forwards to its two characters.
I added it to testinput11 next to the negated case, with \x{1ff} in the
subject to check that only the code unit 0xff itself is taken.

In the 16-bit and 32-bit libraries a negated class accepts every code unit
above 255 and a positive class none of them, whatever the bitmap says about
255 itself. scan_prefix() instead took a class to accept those code units
when bit 255 of its bitmap was set, so a negated class which leaves out
\xff, such as [^\x00-\x40\x42-\xff], was taken to accept 'A' alone. The
JIT then fast-forwarded to the next 'A' and missed a \x{100} before it,
which the interpreter matches.

Decide on the opcode alone, as the interpreter does. The 8-bit library has
no code units above 255, so there the bitmap always describes the class on
its own, and a class which includes \xff no longer makes the scan give up
on its position. 10.47 is affected as well.
@NWilson

NWilson commented Sep 11, 2026

Copy link
Copy Markdown
Member

I thought that bit 255 has no special meaning in a class. It does in some of the 'study' code. But OP_CLASS and OP_NCLASS should be treating all the bits in the bitmap exactly the same way. Only class/nclass distinction should matter.

I may be misremembering... I haven't verified that.

@zherczeg zherczeg left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM

@zherczeg

Copy link
Copy Markdown
Collaborator

Yes, we are talking about the same thing.

@NWilson

NWilson commented Sep 11, 2026

Copy link
Copy Markdown
Member

This is a fairly serious match correctness bug - we'll need to see if it's a regression, and how far back it goes in history.

Anything that could case a 'reasonable' regex (like Matt's example) to return incorrect matches is bad enough that I'll triage it for backporting to all the old releases.

@NWilson

NWilson commented Sep 11, 2026

Copy link
Copy Markdown
Member

@mattst88 Just to let you know - after I have merged all open PRs, I plan to re-indent the whole codebase!

Any branches you have will need to be reformatted and de-conflicted.

Would it be possible for you to pause opening PRs? (Well, you can open them, but any created after today will be held until I'm done.)

I really apologise for this disruption.

https://groups.google.com/g/pcre2-dev/c/Znj5dAtwIAE

@mattst88

Copy link
Copy Markdown
Contributor Author

No problem -- sounds good to me. I don't have anything else in the queue at the moment.

@NWilson

NWilson commented Sep 12, 2026

Copy link
Copy Markdown
Member

I confirm affected versions:

10.48
10.47
10.46
10.45
10.44
10.43
10.42
10.41
10.40
10.39
... and likely earlier as well.

I will fix in 10.49-DEV, and produce a patch for backport.

EDIT: Matt has fixed in 10.49-DEV. Many thanks!

@NWilson
NWilson merged commit c20eb5f into PCRE2Project:main Sep 12, 2026
40 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants