Skip to content

string.c: compare the append source as an address, not as a pointer - #7051

Merged
matz merged 1 commit into
mruby:masterfrom
takumin:string-cat-overlap-uintptr
Aug 9, 2026
Merged

string.c: compare the append source as an address, not as a pointer#7051
matz merged 1 commit into
mruby:masterfrom
takumin:string-cat-overlap-uintptr

Conversation

@takumin

@takumin takumin commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Summary

mrb_str_cat() decides whether ptr points into the string it is appending to, so that
the source can be followed if the buffer moves under it:

  if (ptr >= RSTR_PTR(s) && ptr <= RSTR_PTR(s) + (size_t)RSTR_LEN(s)) {
      off = ptr - RSTR_PTR(s);
  }

ptr is a parameter of a public API and may point into any object. When it points into
something other than s, which is the ordinary case for a plain append, the two pointers
are not into the same object, and C leaves the relational comparison undefined (C11
6.5.8p5). The subtraction on the line below is undefined on the same grounds (6.5.6p9),
though it only runs once the comparison has said the two are related.

That is the shape every pointer containment test has. There is no way to ask the question
in pointers alone: the operators that would ask it are defined only for operands already
known to be related.

The change

Convert both operands to uintptr_t once and compare the addresses. Integers are ordered
across the whole range, so the test means what it is written to mean.

Nothing changes at runtime. Wherever the address space is flat, and that is everywhere
mruby builds, the addresses order the same way the pointers did, so the same appends are
recognized as overlapping and the same offset comes out.

uintptr_t is no new requirement either: mrb_ptr_to_str() in this file already uses it,
as does is_pool_memory() in mrbgems/mruby-bigint/core/bigint.c, which writes its own
containment test this way.

Scope

The comparison has stood since 54132e436 (2014). No sanitizer flags it and no compiler I
am aware of exploits it, so this is a conformance change rather than a bug fix, which is
why it comes on its own rather than folded into anything.

Relation to #7043

#7043 moves these same lines above str_modify_cat() without changing their form. The two
do not overlap in intent, only in position. Whichever lands first, I will rebase the other
onto it.

Testing

rake test with build_config/default.rb, MRUBY_CONFIG=asan rake test
(address,undefined), and MRUBY_CONFIG=ci/gcc-clang rake test, which covers
MRB_GC_STRESS with MRB_USE_DEBUG_HOOK, MRB_GC_FIXED_ARENA, and the C++ ABI build.
KO: 0 and Crash: 0 on all of them.

No test comes with this. Undefined behavior that every current toolchain compiles into the
intended code is not something the suite can distinguish.

Summary by CodeRabbit

  • Bug Fixes
    • Improved string handling when source and destination memory regions overlap.
    • Prevented potential undefined behavior during string concatenation while preserving existing results.

@takumin
takumin requested a review from matz as a code owner August 9, 2026 14:25
@coderabbitai

coderabbitai Bot commented Aug 9, 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: 91247661-3411-4e1b-a585-993ca0721d6d

📥 Commits

Reviewing files that changed from the base of the PR and between 9233195 and bd55e53.

📒 Files selected for processing (1)
  • src/string.c

📝 Walkthrough

Walkthrough

mrb_str_cat now detects source-buffer overlap with uintptr_t address comparisons. It preserves the source offset for copying after possible buffer reallocation or detachment.

Changes

String concatenation overlap handling

Layer / File(s) Summary
Pointer-safe overlap detection
src/string.c
mrb_str_cat records overlap using integer-converted pointer addresses before modification. It retains the source offset for copying after buffer changes.

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

Possibly related PRs

  • mruby/mruby#7039: Both changes modify mrb_str_cat shared-buffer append handling.
  • mruby/mruby#7043: Both changes modify source-overlap detection in mrb_str_cat.

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 and concisely describes the main change to compare the append source as an address instead of a pointer.
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.

@github-actions github-actions Bot added the core label Aug 9, 2026
@matz

matz commented Aug 9, 2026

Copy link
Copy Markdown
Member

This needs a rebase: #7043 landed as 0aeae4b and moved the containment test above str_modify_cat(), so it now sits three lines from where this branch expects it.

The change itself I want, and the reasoning is right: >=, <= and the subtraction are all defined only for pointers already known to be into the same object, which is exactly what the test is trying to establish. uintptr_t is the way to ask it, and the answer is unchanged on every target mruby builds for.

After the rebase both changes apply to the same four lines and compose without interacting, since one moves the test and the other changes what it compares:

  /* comment from #7043 */
  if ((uintptr_t)ptr >= (uintptr_t)RSTR_PTR(s) &&
      (uintptr_t)ptr <= (uintptr_t)RSTR_PTR(s) + (size_t)RSTR_LEN(s)) {
      off = ptr - RSTR_PTR(s);
  }
  mrb_int capa = str_modify_cat(mrb, s, (mrb_int)len);

One thing to decide while you are in there, rather than a request: the off = ptr - RSTR_PTR(s) on the line below is the subtraction you cite under 6.5.6p9. It only runs once the comparison has said the two are related, so on a flat address space it is as safe as the comparison was, but if the point is to stop writing pointer arithmetic whose definedness depends on a fact C cannot see, that line is the other half of it. Doing it as (uintptr_t)ptr - (uintptr_t)RSTR_PTR(s) would finish the job. I have no strong view; either is an improvement on what is there.

`mrb_str_cat()` finds out whether `ptr` points into `s` by comparing it
against `RSTR_PTR(s)`, and subtracts the two where it does. `ptr` is a
parameter of a public API and may come from any object, so the two need
not point into the same one, and C leaves both the relational comparison
(C11 6.5.8p5) and the pointer subtraction (6.5.6p9) undefined when they
do not. A containment test has no way of saying what it means in
pointers alone; `uintptr_t` is where it can be said, since the integers
are ordered across the whole range.

Convert both operands and test the addresses. The answer is the same
wherever the address space is flat, which is everywhere mruby builds, so
nothing changes at runtime. `uintptr_t` is no new requirement either:
`mrb_ptr_to_str()` in this file already uses it, as does
`is_pool_memory()` in `mrbgems/mruby-bigint/core/bigint.c`, which writes
its own containment test the same way.
@takumin
takumin force-pushed the string-cat-overlap-uintptr branch from cd8c891 to bd55e53 Compare August 9, 2026 14:54
@takumin

takumin commented Aug 9, 2026

Copy link
Copy Markdown
Contributor Author

Rebased onto 9233195. The conflict was the four lines you named: #7043 moved the test above str_modify_cat() and gave it a comment, this branch changed what it compares. Both are in now, with the uintptr_t reasoning following the paragraph #7043 added rather than starting a second comment above the same test.

On the subtraction: it was already uintptr_t here. The branch has read

      off = (ptrdiff_t)(ptr_addr - str_addr);

since the first push, so 6.5.6p9 is covered along with 6.5.8p5, and the sketch in your comment is the one place the old form still appears.

  /* ... comment from #7043 ...

     `ptr` is allowed to come from anywhere, so it and `RSTR_PTR(s)` need not
     point into the same object, and relational comparison and subtraction
     between pointers that do not is undefined. Going through `uintptr_t`
     leaves both on integers, where the whole range is ordered. */
  uintptr_t ptr_addr = (uintptr_t)ptr;
  uintptr_t str_addr = (uintptr_t)RSTR_PTR(s);
  if (ptr_addr >= str_addr && ptr_addr <= str_addr + (uintptr_t)RSTR_LEN(s)) {
      off = (ptrdiff_t)(ptr_addr - str_addr);
  }
  mrb_int capa = str_modify_cat(mrb, s, (mrb_int)len);

Retested after the rebase: rake test with build_config/default.rb and MRUBY_CONFIG=asan rake test (address,undefined), KO: 0 and Crash: 0 on both.

@matz
matz merged commit d64680b into mruby:master Aug 9, 2026
21 checks passed
@takumin
takumin deleted the string-cat-overlap-uintptr branch August 9, 2026 21:48
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