Skip to content

mruby-regexp: test a lookbehind over a class that holds a stray byte - #7219

Merged
matz merged 1 commit into
mruby:masterfrom
takumin:lookbehind-class-stray-byte-tests
Aug 17, 2026
Merged

mruby-regexp: test a lookbehind over a class that holds a stray byte#7219
matz merged 1 commit into
mruby:masterfrom
takumin:lookbehind-class-stray-byte-tests

Conversation

@takumin

@takumin takumin commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

A character class may hold a byte that starts no character, and a lookbehind rewinds over characters rather than bytes. The two agree on the unit already: such a byte is a character of its own, which is the step the forward match takes for it, so a class holding one is one character wide like any other class. Nothing pinned that.

The class rows nearby all hold characters, and the stray-byte rows all write the byte into the pattern literally rather than into a class. The one existing crossing is a binary subject, which rewinds by bytes; the character reading of the same question has no row.

Three readings, because the answer depends on what reads the subject:

  • A binary subject rewinds by bytes in either build, and it is where a stray byte can be put to a subject at all: a subject whose bytes spell no character is refused wherever an encoding reads them.
  • Reading by character, the class is asked about a character even when it holds a byte. The rewind steps back over the whole of Ā, so the class is handed U+0100 and neither C4 nor 80 matches it.
  • Reading by byte, there is one character per byte, so the same class does see the continuation byte, and only that one.

Tests only. The block carries no skip, so it runs everywhere rather than skipping silently on the builds that read by byte: every build below reports one test more than master with its skip count unchanged, KO 0.

build master this PR skip
build_config/default.rb 2110 2111 49
ci/gcc-clang, full-debug 2334 2335 3
ci/gcc-clang, bintest 2334 2335 11
ci/gcc-clang, cxx_abi 2334 2335 11
ci/gcc-clang, byte-string 2264 2265 49
ci/gcc-clang, ascii-case 2330 2331 13
build_config/asan.rb 2334 2335 3

Environment

Details
OS Ubuntu 24.04.4 LTS
Kernel Linux 7.0.0-28-generic x86_64
CPU AMD Ryzen 9 5950X
C compiler gcc (Ubuntu 13.3.0-6ubuntu2~24.04.1) 13.3.0
C compiler (asan) Homebrew clang 22.1.8
binutils GNU ld (GNU Binutils) 2.47.20260726
CRuby ruby 4.0.6 (2026-07-14 revision 03b6d3f889) +PRISM [x86_64-linux]

The line each build actually compiles src/string.c with, taken from rake --verbose with -MMD -c, -I and -o dropped:

# build_config/default.rb
gcc -std=gnu99 -g -O3 -Wall -Wundef -Werror-implicit-function-declaration -Wwrite-strings -DHAVE_MRUBY_REGEXP_GEM -DMRB_USE_SET -DHAVE_MRUBY_IO_GEM -DMRB_USE_RATIONAL -DMRB_USE_COMPLEX -DMRB_USE_BIGINT -DMRB_USE_DEBUG_HOOK

# ci/gcc-clang, full-debug
gcc -std=gnu99 -g -O3 -Wall -Wundef -Werror-implicit-function-declaration -Wwrite-strings -g3 -O0 -DMRB_GC_STRESS -DMRB_USE_DEBUG_HOOK -DMRB_DEBUG -DMRB_USE_BIGINT -DMRB_USE_COMPLEX -DHAVE_MRUBY_ENCODING_GEM -DMRB_UTF8_STRING -DHAVE_MRUBY_IO_GEM -DMRB_USE_RATIONAL -DHAVE_MRUBY_REGEXP_GEM -DMRB_USE_SET -DMRB_USE_TASK_SCHEDULER

# ci/gcc-clang, bintest
gcc -std=gnu99 -g -O3 -Wall -Wundef -Werror-implicit-function-declaration -Wwrite-strings -DMRB_GC_FIXED_ARENA -DMRB_USE_BIGINT -DMRB_USE_COMPLEX -DHAVE_MRUBY_ENCODING_GEM -DMRB_UTF8_STRING -DHAVE_MRUBY_IO_GEM -DMRB_USE_RATIONAL -DHAVE_MRUBY_REGEXP_GEM -DMRB_USE_SET -DMRB_USE_TASK_SCHEDULER -DMRB_USE_DEBUG_HOOK

# ci/gcc-clang, cxx_abi
gcc -g -O3 -Wall -Wundef -Wwrite-strings -x c++ -std=gnu++03 -DMRB_GC_FIXED_ARENA -DMRB_USE_CXX_EXCEPTION -DMRB_USE_CXX_ABI -DMRB_USE_BIGINT -DMRB_USE_COMPLEX -DHAVE_MRUBY_ENCODING_GEM -DMRB_UTF8_STRING -DHAVE_MRUBY_IO_GEM -DMRB_USE_RATIONAL -DHAVE_MRUBY_REGEXP_GEM -DMRB_USE_SET -DMRB_USE_TASK_SCHEDULER

# ci/gcc-clang, byte-string
gcc -std=gnu99 -g -O3 -Wall -Wundef -Werror-implicit-function-declaration -Wwrite-strings -DMRB_USE_BIGINT -DMRB_USE_COMPLEX -DHAVE_MRUBY_IO_GEM -DMRB_USE_RATIONAL -DHAVE_MRUBY_REGEXP_GEM -DMRB_USE_SET -DMRB_USE_TASK_SCHEDULER

# ci/gcc-clang, ascii-case
gcc -std=gnu99 -g -O3 -Wall -Wundef -Werror-implicit-function-declaration -Wwrite-strings -DMRB_USE_ASCII_CASE -DMRB_USE_BIGINT -DMRB_USE_COMPLEX -DHAVE_MRUBY_ENCODING_GEM -DMRB_UTF8_STRING -DHAVE_MRUBY_IO_GEM -DMRB_USE_RATIONAL -DHAVE_MRUBY_REGEXP_GEM -DMRB_USE_SET -DMRB_USE_TASK_SCHEDULER

# build_config/asan.rb
clang -std=gnu99 -g -O3 -Wall -Wundef -Werror-implicit-function-declaration -Wwrite-strings -Wzero-length-array -fsanitize=address,undefined -g3 -O0 -DMRB_DEBUG -DMRB_USE_BIGINT -DMRB_USE_COMPLEX -DHAVE_MRUBY_ENCODING_GEM -DMRB_UTF8_STRING -DHAVE_MRUBY_IO_GEM -DMRB_USE_RATIONAL -DHAVE_MRUBY_REGEXP_GEM -DMRB_USE_SET -DMRB_USE_TASK_SCHEDULER

-g -O3 is what the gcc toolchain sets. full-debug and the sanitizer build then append -g3 -O0 through enable_debug(), so those two are -O0, not -O3. cxx_abi is the C compiler driven as C++ with -x c++ -std=gnu++03, and g++ links it.

Summary by CodeRabbit

  • Tests
    • Added regression coverage for regular-expression lookbehind with character classes containing invalid byte sequences.
    • Added validation for binary subjects and UTF-8 inputs with invalid encoding.
    • Expanded coverage to distinguish matching individual continuation or leading bytes from matching complete multibyte characters.

Two changes compose here and the composition has no test.  A character
class may hold a byte that starts no character, and a lookbehind rewinds
over characters rather than bytes.  They already agree on the unit: such a
byte is a character of its own, which is the step the forward match takes
for it, so a class holding one is one character wide like any other class.

Nothing pinned that.  The class rows nearby all hold characters, and the
stray-byte rows all write the byte into the pattern literally rather than
into a class.

Three readings, because the answer depends on what reads the subject:

  - A binary subject rewinds by bytes in either build, which is where the
    stray byte can be put to a subject at all: a subject whose bytes spell
    no character is refused wherever an encoding reads them.
  - Reading by character, the class is asked about a character even when it
    holds a byte: the rewind steps back over the whole of A-macron, so the
    class is handed U+0100 and neither C4 nor 80 matches it.
  - Reading by byte, there is one character per byte, so the same class does
    see the continuation byte, and only that one.
@takumin
takumin requested a review from matz as a code owner August 17, 2026 02:42
@coderabbitai

coderabbitai Bot commented Aug 17, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d79fa8e9-8597-4fd4-baa7-e9e7fc7e65e0

📥 Commits

Reviewing files that changed from the base of the PR and between 1bc2d8f and 0e0bd0e.

📒 Files selected for processing (1)
  • mrbgems/mruby-regexp/test/regexp_syntax.rb

Included review availability: Your plan includes up to 8 reviews per rolling hour; 4 remain after this review.


📝 Walkthrough

Walkthrough

The change adds regression tests for regular-expression lookbehind over character classes containing invalid bytes. The tests cover binary subjects, UTF-8 errors, multibyte rewinding, and non-UTF-8 byte behavior.

Changes

Regexp lookbehind regression coverage

Layer / File(s) Summary
Invalid-byte lookbehind coverage
mrbgems/mruby-regexp/test/regexp_syntax.rb
Adds tests for byte-oriented subjects, invalid UTF-8 handling, multibyte-character rewind behavior, and matching continuation or lead bytes separately from complete multibyte characters.

Estimated code review effort: 2 (Simple) | ~10 minutes

Merge Risk: ⚪ Minimal · up to 0e0bd

This change adds regression coverage for regexp lookbehind behavior without changing production behavior or runtime configuration; no actionable merge-blocking risk remains beyond normal checks and review.

Possibly related PRs

  • mruby/mruby#7059: Adds related regression tests for lookbehind and invalid UTF-8 bytes.
  • mruby/mruby#7069: Covers invalid-byte character classes and UTF-8 lookbehind behavior.
  • mruby/mruby#7070: Adds lookbehind tests for invalid UTF-8 bytes.

Suggested reviewers: matz

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the added mruby-regexp tests for lookbehind over a character class containing a stray byte.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@matz
matz merged commit 989c2c6 into mruby:master Aug 17, 2026
20 of 21 checks passed
@takumin
takumin deleted the lookbehind-class-stray-byte-tests branch August 17, 2026 02:58
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.

2 participants