Skip to content

string.c: answer an ASCII-case build from the header's fallback - #7222

Merged
matz merged 1 commit into
mruby:masterfrom
takumin:ascii-case-unicode-stub
Aug 17, 2026
Merged

string.c: answer an ASCII-case build from the header's fallback#7222
matz merged 1 commit into
mruby:masterfrom
takumin:ascii-case-unicode-stub

Conversation

@takumin

@takumin takumin commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

include/mruby/internal.h already answers -1 for a build that compiles no
case tables:

/* include/mruby/internal.h */
#ifdef MRB_UTF8_STRING
int mrb_str_case_convert_unicode(mrb_state *mrb, mrb_value str, enum mrb_case_mode mode);
#else
#define mrb_str_case_convert_unicode(mrb, str, mode) (-1)
#endif

The guard asks only about MRB_UTF8_STRING, but that is not the condition the
tables are compiled under. MRB_USE_ASCII_CASE keeps MRB_UTF8_STRING and
drops the tables, so such a build takes the declared side of the guard, and
src/string.c has to supply a body for the promise:

/* src/string.c */
#elif defined(MRB_UTF8_STRING)
int
mrb_str_case_convert_unicode(mrb_state *mrb, mrb_value str, enum mrb_case_mode mode)
{
  (void)mrb;
  (void)str;
  (void)mode;
  return -1;
}
#endif  /* MRB_UTF8_STRING */

Two spellings of one answer, and the one costing a call and a return is the one
that build takes.

The change

Widen the guard to the condition the tables are actually compiled under, and
the #elif arm goes away whole:

#if defined(MRB_UTF8_STRING) && !defined(MRB_USE_ASCII_CASE)
int mrb_str_case_convert_unicode(mrb_state *mrb, mrb_value str, enum mrb_case_mode mode);
#else
#define mrb_str_case_convert_unicode(mrb, str, mode) (-1)
#endif

That condition is the one src/string.c opens the walk with and the one
include/mruby/internal.h already uses a few lines below for the codepoint
accessors, so the header now names the tables the same way in both places.

All seven call sites pass plain locals, so a macro evaluating none of its
arguments answers each of them as before:

file caller
src/string.c mrb_str_capitalize_bang(), mrb_str_downcase_bang(), mrb_str_upcase_bang()
mrbgems/mruby-string-ext/src/string.c String#swapcase!, and both operands of String#casecmp?

Each already reads the result as int uc = ...; if (uc >= 0) or as ... < 0,
which (-1) satisfies. A build without MRB_UTF8_STRING has taken the macro
for as long as it has existed, so this is the path those five methods are
already compiled from where the tables are absent; what changes is which builds
count as absent.

The stale #endif /* MRB_UTF8_STRING */ comment, which closed a chain whose
first arm is MRB_UTF8_STRING && !MRB_USE_ASCII_CASE, is carried off by the
same change.

Size

.text summed over every .o, each side built from an empty build directory:

build master this PR delta
full-debug (-O0) 2,842,151 2,842,151 0
bintest 1,829,550 1,829,550 0
cxx_abi 1,839,166 1,839,166 0
byte-string 1,778,920 1,778,920 0
ascii-case 1,800,224 1,799,984 -240
asan (-O0) 12,375,935 12,375,935 0

ascii-case is the only build whose guard moves, and every other one is
unchanged to the byte, which is what a change that only redraws a #if should
show.

Testing

build_config/ci/gcc-clang.rb and build_config/asan.rb, run per build so the
counts are attributable:

build tests OK KO skip
full-debug 2336 2333 0 3
bintest 2336 2325 0 11
cxx_abi 2336 2325 0 11
byte-string 2266 2217 0 49
ascii-case 2332 2319 0 13
asan 2336 2333 0 3

Identical to master, build for build. bintest (the binary tests) passes 117
of 117 under ci/gcc-clang and 79 of 79 under asan.

No new tests come with this. ascii-case already asks all five methods for the
-1 answer: mrbgems/mruby-string-ext/test/string.rb skips only the Unicode
assertions of String#swapcase and String#casecmp? there and keeps the ASCII
ones, and test/t/string.rb asserts downcase, upcase and capitalize on
strings the walk would have refused either way.

Environment

Details
OS Ubuntu 24.04.4 LTS
Kernel Linux 7.0.0-28-generic x86_64
CPU AMD Ryzen 9 5950X (16 cores, 32 threads)
C compiler gcc 13.3.0 (Ubuntu 13.3.0-6ubuntu2~24.04.1)
binutils GNU ld 2.47.20260726
CRuby (build host) ruby 4.0.6 (2026-07-14 revision 03b6d3f889) +PRISM

The optimization level is not the same in every build, so these are the lines
that actually compiled src/string.c, with -MMD -c, -I and -o dropped.
cxx_abi compiles with gcc -x c++, not with g++; g++ only links.

# 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 src/string.c

# 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 src/string.c

# 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 src/string.c

# 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 src/string.c

# 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 src/string.c

# build_config/asan.rb
gcc -std=gnu99 -g -O3 -Wall -Wundef -Werror-implicit-function-declaration -Wwrite-strings -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 src/string.c

Summary by CodeRabbit

  • Bug Fixes
    • Corrected Unicode string case conversion behavior for configurations using ASCII case conversion.
    • Prevented unavailable Unicode case-conversion functionality from being exposed in those configurations.

`include/mruby/internal.h` defines `mrb_str_case_convert_unicode()` as
`(-1)` for a build that compiles no case tables, but its guard asked only
about `MRB_UTF8_STRING`. A build defining `MRB_USE_ASCII_CASE` keeps
`MRB_UTF8_STRING` and drops the tables, so it took the declared side of
that guard and `src/string.c` had to supply a body for it, which did
nothing but return the same `-1` the header already had. Two spellings of
one answer, and the one costing a call and return per `downcase`,
`upcase`, `capitalize`, `swapcase` and `casecmp?` was the one such a build
took.

Widen the guard to the condition the tables are compiled under, and the
out-of-line stub goes away whole. All seven call sites pass plain locals
and read the result as `int uc = ...; if (uc >= 0)` or `... < 0`, so a
macro evaluating none of its arguments answers each of them as before.

Built full-core with `MRB_USE_ASCII_CASE` (gcc 15.2.0, x86_64):

|                | assertions | .text     |
|----------------|------------|-----------|
| before         | 2319, KO 0 | 1,266,102 |
| after          | 2319, KO 0 | 1,265,862 |

full-core with `MRB_UTF8_STRING` and full-core reading bytes are both
unchanged: 2325 and 2217 assertions, KO 0.
@takumin
takumin requested a review from matz as a code owner August 17, 2026 03:36
@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: 3a430ca3-079d-4667-a2c8-919d012bacf1

📥 Commits

Reviewing files that changed from the base of the PR and between 0ea59d9 and 27870c9.

📒 Files selected for processing (2)
  • include/mruby/internal.h
  • src/string.c

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


📝 Walkthrough

Walkthrough

The build guards for mrb_str_case_convert_unicode now require UTF-8 strings with ASCII case conversion disabled. The unused fallback stub for ASCII case-conversion builds was removed.

Changes

Unicode case conversion guards

Layer / File(s) Summary
Align declaration and implementation guards
include/mruby/internal.h, src/string.c
The declaration and implementation now require MRB_UTF8_STRING && !MRB_USE_ASCII_CASE. The ASCII-case-conversion fallback stub was removed.

Estimated code review effort: 1 (Trivial) | ~5 minutes

Merge Risk: ⚪ Minimal · up to 27870

This localized build-configuration change aligns the fallback behavior with the configurations that omit Unicode case tables, with no actionable merge-blocking risk remaining after normal checks.

Possibly related PRs

  • mruby/mruby#7182: Introduced Unicode case conversion and mrb_str_case_convert_unicode.
  • mruby/mruby#7183: Added the shared Unicode case-conversion implementation in the same areas.
  • mruby/mruby#7190: Adjusted related UTF-8 and ASCII case-conversion guards.

Suggested labels: core

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 describes the ASCII-case fallback alignment between string.c and the header, which is the main change.
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.

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