Move the String#valid_encoding? body into core as mrb_str_valid_encoding_p - #7102
Merged
Conversation
`String#valid_encoding?` in mruby-encoding walks the string itself and sets `MRB_STR_SINGLE_BYTE` directly. `mrb_utf8_strlen` walks a string the same way one function away, and `utf8_strlen` sets the same flag under the same condition. Of the two walks, only the gem's walk tests each character for a byte that stands for no character, and only core's skips a run of ASCII a word at a time. Rename the body of `mrb_utf8_strlen` to a static `utf8_strlen_check` that takes an optional `mrb_bool *validp`. When it is given, the walk stops at the first non-ASCII byte that `mrb_utf8len` measures as one byte, which is a byte standing for no character, and reports it. `mrb_utf8_strlen` passes `NULL` and is unchanged for its callers, in what it answers and in what it costs. `mrb_str_valid_encoding_p` builds on that worker and keeps what the gem does: a binary string is valid without a walk, a string carrying a byte that stands for no character is not, and a string that walks to as many characters as it has bytes gets the single byte flag on the way out. The flag is not read on the way in, for the reason 470396b gives: it says one byte per character, which a string of stray bytes satisfies too. Declare it outside the `MRB_UTF8_STRING` guard in internal.h, as `mrb_str_char_to_byte` and `mrb_str_byte_to_char` are. A build without UTF-8 support reads a string as bytes with no encoding to disagree with, so it answers TRUE there.
`str_valid_enc_p` is now a wrapper over `mrb_str_valid_encoding_p`, and the gem no longer reaches into `RSTR_LEN`, `RSTR_PTR` and `RSTR_SET_SINGLE_BYTE_FLAG` to answer the question itself. The answer and the single byte flag it leaves behind are unchanged. The walk is now the one core runs for `mrb_utf8_strlen`, which skips a run of ASCII a word at a time rather than calling `mrb_utf8len` on every byte; a string of 10000 ASCII bytes is answered about 80 times faster, and one that is all multi-byte measures the same as before. Add tests for two shapes the existing cases do not reach. Those cases are short enough to be decoded from the first byte without crossing a run of ASCII, and none of them measures a substring that shares its parent's buffer, where the walk has to stop at a length of its own rather than at the parent's bytes.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (4)
📝 WalkthroughWalkthroughThe PR adds ChangesString encoding validation
Estimated code review effort: 3 (Moderate) | ~20 minutes Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
This was referenced Aug 12, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
String#valid_encoding?in mruby-encoding walks the string withmrb_utf8lenand counts the characters.mrb_utf8_strlen()instring.c,reached through
utf8_strlen(), walks a string for the same purpose. Thetwo loops make the same decisions about how a UTF-8 string is read:
mrb_utf8lenwhatever bytes follow it
marked
MRB_STR_SINGLE_BYTEon the way outOnly two steps are not shared: the gem tests each character for a byte that
stands for no character, and core skips runs of ASCII a word at a time.
This moves the body into
string.casmrb_str_valid_encoding_pandleaves the gem method as a wrapper. The one walk that remains does both:
it skips the ASCII a word at a time, and it reports the first byte that
stands for no character. Neither step can hide the other, since a skipped
byte is ASCII and therefore valid, and a continuation byte is never ASCII.
Why this belongs in core
The method stays in the gem.
String#valid_encoding?is defined bymruby-encoding exactly as before, and a build without the gem gains no
method. What moves is the walk that core already performs for
String#sizeand character indexing, and the single byte flag that
utf8_strlen()setswhile doing it.
string.c
The body of
mrb_utf8_strlenbecomes a staticutf8_strlen_check(str, byte_len, validp). Withvalidpgiven, the walk stops at the firstnon-ASCII byte that
mrb_utf8lenmeasures as one byte and sets*validpto FALSE; the returned count is a character count only while
*validpstays TRUE.
mrb_utf8_strlencalls it withNULL, so nothing changes forits existing callers.
mrb_str_valid_encoding_p(mrb, str)sits on top of that worker with therules the gem has: a binary string is valid without a walk, and a string
whose character count equals its byte count gets the single byte flag. It
does not read that flag on the way in, keeping what 470396b established:
the flag says one byte per character, which a string of stray bytes
satisfies as well as an ASCII one.
internal.h
The declaration sits with
mrb_str_char_to_byteandmrb_str_byte_to_char,outside the
MRB_UTF8_STRINGguard, so a caller does not need to guard thecall. Without UTF-8 support a string is a sequence of bytes with no encoding
to disagree with, and the function answers TRUE.
mruby-encoding
str_valid_enc_pis now one line, and the gem no longer reaches intoRSTR_LEN,RSTR_PTRandRSTR_SET_SINGLE_BYTE_FLAGto answer thisquestion itself.
Behavior
No change to what
String#valid_encoding?answers, and no change to whenit sets the single byte flag. What changes is the cost of the walk, since
core skips runs of ASCII a word at a time through
search_nonascii()instead of calling
mrb_utf8lenon every byte. Measured with the defaultgembox plus mruby-encoding, gcc -O3, 200 calls per case, best of five, both
binaries built with the same config on the same machine and run one after
the other:
A string that is all multi-byte gains nothing, having no run of ASCII to
skip, and nothing measured got slower.
String#sizereaches the same walknow, through
utf8_strlen_check(str, byte_len, NULL), and measures the sameas before.
Two tests are added for shapes the existing cases do not reach: a broken
byte found after a run of ASCII, and a substring long enough to share its
parent's buffer, whose walk has to stop at its own length.
Testing
The gem is in no gembox, so a default
rake testbuilds neither the gemnor
MRB_UTF8_STRING. Both sides were run from a cleanbuild/:MRUBY_CONFIG=host-debug rake test(full-core, so the gem is in andMRB_UTF8_STRINGis on): 2237 assertions, 0 KO, 0 crash, plus 116bintest.
rake testwith the default config: 2053 assertions, 0 KO, 0 crash, plus105 bintest. This is the build where
mrb_str_valid_encoding_panswersTRUE without a walk.
MRUBY_CONFIG=minimal rake, to confirm the function compiles where UTF-8support is absent.
prek run --all-filespasses, except thatmarkdownlintcould not installlocally; no Markdown is touched here.
Summary by CodeRabbit
New Features
mrb_str_valid_encoding_p.String#valid_encoding?now reliably handles UTF-8, binary strings, invalid trailing bytes, truncated sequences, and shared substrings.Bug Fixes