Skip to content

Write MRB_STR_BINARY through accessors, as the other flags are - #7150

Merged
matz merged 1 commit into
mruby:masterfrom
takumin:binary-flag-through-accessors
Aug 14, 2026
Merged

Write MRB_STR_BINARY through accessors, as the other flags are#7150
matz merged 1 commit into
mruby:masterfrom
takumin:binary-flag-through-accessors

Conversation

@takumin

@takumin takumin commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

include/mruby/string.h gives each string flag a named way in and out. Three of the four are complete, and one is not:

flag predicate set unset copy
MRB_STR_SINGLE_BYTE RSTR_SINGLE_BYTE_P RSTR_SET_SINGLE_BYTE_FLAG RSTR_UNSET_SINGLE_BYTE_FLAG RSTR_COPY_SINGLE_BYTE_FLAG
MRB_STR_VALID_ENC RSTR_VALID_ENC_P RSTR_SET_VALID_ENC_FLAG RSTR_UNSET_VALID_ENC_FLAG RSTR_COPY_VALID_ENC_FLAG
MRB_STR_BROKEN_ENC RSTR_BROKEN_ENC_P RSTR_SET_BROKEN_ENC_FLAG RSTR_UNSET_BROKEN_ENC_FLAG RSTR_COPY_BROKEN_ENC_FLAG
MRB_STR_BINARY RSTR_BINARY_P none none RSTR_COPY_BINARY_FLAG

Because the two are missing, every place that turns the binary flag on or off reaches past the header and names the bit itself. Before this change:

$ grep -rn "flags |= MRB_STR_BINARY\|flags &= ~MRB_STR_BINARY" . --include='*.c'
mrbgems/mruby-encoding/src/encoding.c:72:    s->flags |= MRB_STR_BINARY;
mrbgems/mruby-encoding/src/encoding.c:75:    s->flags &= ~MRB_STR_BINARY;
mrbgems/mruby-regexp/src/regexp.c:1166:  mrb_str_ptr(result)->flags |= MRB_STR_BINARY;
mrbgems/mruby-sprintf/src/sprintf.c:362:  if (p < e) r->flags |= MRB_STR_BINARY;
mrbgems/mruby-string-ext/src/string.c:32:    mrb_str_ptr(str)->flags |= MRB_STR_BINARY;
mrbgems/mruby-string-ext/src/string.c:128:  if (p < e) r->flags |= MRB_STR_BINARY;
mrbgems/mruby-string-ext/src/string.c:1525:  mrb_str_ptr(str)->flags |= MRB_STR_BINARY;
mrbgems/mruby-string-ext/src/string.c:1917:    mrb_str_ptr(result)->flags |= MRB_STR_BINARY;
mrbgems/mruby-string-ext/src/string.c:1991:    mrb_str_ptr(result)->flags |= MRB_STR_BINARY;
mrbgems/mruby-string-bitops/src/string_bitops.c:430:  mrb_str_ptr(result)->flags |= MRB_STR_BINARY;
src/string.c:1422:    t->flags |= MRB_STR_BINARY;
src/string.c:1854:      str->flags |= MRB_STR_BINARY;
src/string.c:3641:    mrb_str_ptr(ret)->flags |= MRB_STR_BINARY;
src/string.c:3882:    mrb_str_ptr(result)->flags |= MRB_STR_BINARY;

Fourteen sites, one src file and five gems. After it, the same grep finds the two macro bodies in the header and nothing else.

What this adds

#define RSTR_SET_BINARY_FLAG(s) ((s)->flags |= MRB_STR_BINARY)
#define RSTR_UNSET_BINARY_FLAG(s) ((s)->flags &= ~MRB_STR_BINARY)

They sit next to RSTR_BINARY_P() rather than inside the #ifdef MRB_UTF8_STRING block the other three flags' accessors are in. A build without MRB_UTF8_STRING still tells a byte read string from the rest, and String#b and String#force_encoding("BINARY") still write the flag there, so the accessors have to be real in that build, exactly as RSTR_BINARY_P() already is.

RSTR_COPY_BINARY_FLAG() keeps the body it has. Expressing it through the new unset would change what it compiles to and buy nothing: this change adds the two ways in that were missing, it does not rebuild the ones already there.

What this changes

  • Generated code: nothing. Each macro expands to the expression its call sites spelled out, so only parentheses move. gcc -S -O3 with the full-core defines over the six sources that change gives assembly identical to the assembly before this change, byte for byte.
  • Public header: two lines. lib/mruby/amalgam.rb inlines include/mruby/string.h whole, so the amalgamated header gains them too. Nothing is removed or renamed, so code outside this repository is unaffected.
  • Ruby level behavior: none. No new test comes with this change, because there is no new answer to pin.

Testing

  • full-core gembox, MRB_UTF8_STRING on: 2286 tests, 2276 OK, 10 skip, 0 KO, 0 crash, no new warnings.
  • default gembox, where mruby-encoding is absent and strings index by byte: 2070 tests, 2042 OK, 28 skip, 0 KO, 0 crash.

Still left out

Two writes to other string flags still name the bit directly. Neither is a binary flag write, and each wants its own reasoning:

  • mrbgems/mruby-string-ext/src/string.c, in str_ascii_only_p(): flags |= MRB_STR_SINGLE_BYTE, where RSTR_SET_SINGLE_BYTE_FLAG() exists but compiles to nothing without MRB_UTF8_STRING, so swapping it in is a behavior question, not a spelling one.
  • mrbgems/mruby-sprintf/src/sprintf.c, around line 624: RSTR_SET_EMBED_LEN() written out by hand, next to a branch that sets the heap length to a different value. Folding the pair into RSTR_SET_LEN() is not a rename.

Summary by CodeRabbit

  • Bug Fixes
    • Improved consistency when handling binary strings across string, encoding, regular expression, formatting, and bit-operation features.
    • Preserved binary-string behavior during concatenation, replacement, appending, substitution, conversion, and byte manipulation.
  • Refactor
    • Standardized internal handling of binary-string status for more reliable behavior across supported operations.

`include/mruby/string.h` names a way in and out of each string flag it
defines. `MRB_STR_SINGLE_BYTE`, `MRB_STR_VALID_ENC` and
`MRB_STR_BROKEN_ENC` have four apiece: a predicate, a set, an unset and
a copy. Nothing outside the header names those bits.

`MRB_STR_BINARY` has the predicate and the copy and neither of the other
two, so the places that turn it on and off spell the bit out:

    mrb_str_ptr(result)->flags |= MRB_STR_BINARY;
    s->flags &= ~MRB_STR_BINARY;

There are fourteen of them, spread over `src/string.c` and five gems.

Add `RSTR_SET_BINARY_FLAG()` and `RSTR_UNSET_BINARY_FLAG()` in the shape
the other three flags use, and write all fourteen through them. Both go
next to `RSTR_BINARY_P()`, outside the `MRB_UTF8_STRING` guard the other
three sit behind, because a build without it still tells a byte read
string from the rest: `String#b` and `String#force_encoding` write the
flag there too.

`RSTR_COPY_BINARY_FLAG()` keeps the body it has. Spelling it through the
new unset would change what it compiles to and buy nothing; this adds
the two ways in that were missing, it does not rebuild the ones already
there.

Each new macro expands to the expression its call sites spelled out, so
the generated code is unchanged. `gcc -S -O3` over the six sources that
change gives assembly identical to the assembly before it.
@coderabbitai

coderabbitai Bot commented Aug 14, 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: e220269a-30e1-44ba-a62e-6835ce594df8

📥 Commits

Reviewing files that changed from the base of the PR and between 0ebee4a and e441b2f.

📒 Files selected for processing (7)
  • include/mruby/string.h
  • mrbgems/mruby-encoding/src/encoding.c
  • mrbgems/mruby-regexp/src/regexp.c
  • mrbgems/mruby-sprintf/src/sprintf.c
  • mrbgems/mruby-string-bitops/src/string_bitops.c
  • mrbgems/mruby-string-ext/src/string.c
  • src/string.c

📝 Walkthrough

Walkthrough

This change adds public macros for setting and clearing MRB_STR_BINARY and replaces direct flag mutations across core string operations and related mrbgems. String behavior remains unchanged.

Changes

Binary String Flag API Migration

Layer / File(s) Summary
Define binary flag macros
include/mruby/string.h, mrbgems/mruby-encoding/src/encoding.c
Adds RSTR_SET_BINARY_FLAG and RSTR_UNSET_BINARY_FLAG. str_force_encoding uses these macros.
Migrate core string operations
src/string.c
String concatenation, partial replacement, append, and substitution paths use RSTR_SET_BINARY_FLAG.
Migrate gem string operations
mrbgems/mruby-regexp/src/regexp.c, mrbgems/mruby-sprintf/src/sprintf.c, mrbgems/mruby-string-bitops/src/string_bitops.c, mrbgems/mruby-string-ext/src/string.c
Related gem paths use RSTR_SET_BINARY_FLAG for binary result strings and conversions.

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

Merge Risk: ⚪ Minimal · up to e441b

This localized refactor routes binary string-flag writes through named accessors without changing generated code or Ruby-level behavior. The reported test suites pass with no failures or crashes, so no actionable merge-blocking risk remains beyond normal checks and review.

Possibly related PRs

Suggested reviewers: matz

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes replacing direct MRB_STR_BINARY writes with accessors, which is the main change in the pull request.
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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants