Repository navigation
A String pushed into an Array a Hash hands out is shared with its iteration - #7992
Conversation
…mutations
`h.each { |k, vs| vs.each(&:strip!) }` (and each_value, `h[k].each`, a
local bound to `h[k]`) iterates an Array the Hash holds as a boxed value,
so the block parameter binds each element boxed. The pass that makes the
Strings an iterator's block mutates in place into shared handles only
looked at a local Array (typed or poly) or a literal, and passed over a
boxed receiver: the Strings stored in the Array stayed plain, each
strip!/upcase!/<< changed a copy, and the Hash printed them unchanged
with no refusal. WEBrick's parse_header strips its header values this
way, so a POST's Content-Length kept its leading space and was rejected.
A boxed receiver that is a local (a block parameter included) or a call
now demands the Strings stored into what it reads, as a poly Array does:
the walk follows the block parameter, the index read or the local back to
the Hash's stores and on into its Arrays.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ration
`header[field] << value` (or `(h[k] ||= []) << s`) stores into the Array
the Hash holds, not into the Hash. The walk that demands shared handles
for Strings mutated a container level in
(`h.each { |k, vs| vs.each(&:strip!) }`) followed only the stores into the Hash itself -- its
literal and `h[k] = []`, an empty Array -- so the pushed Strings stayed
plain and strip! changed a copy, silently. That is how WEBrick's
parse_header builds and then strips its header values, so a POST's
Content-Length kept its leading space.
A store site that reads an element out of the container (`h[k]`, `fetch`,
`h[k] ||= v`) now has the push it receives walked one level in.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configuration
📒 Files selected for processing (5)
Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 4 remain after this review. 📝 WalkthroughWalkthroughThe string-sharing analysis now considers indexed ChangesHash value mutation analysis
Priority: ➖ Normal Estimated code review effort: 3 (Moderate) | ~20 minutes Change: Bug fix Merge Risk: ⚪ Minimal · up to The changed paths preserve the string mutations exercised by the new examples. No merge-blocking issue remains after normal checks. 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
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 |
|
Found a compile-time regression in this branch on large programs (a 33k-line program goes from ~7 min to not finishing in 20 min). Working on a fix; please hold off merging until it's pushed here. 🤖 Generated with Claude Code |
|
This merged before the fix for the compile-time regression landed here; the fix is in #8010 (a 33k-line program: 391 s with it, against not finishing in 1200 s on master now). 🤖 Generated with Claude Code |
…each it strbuf_demand_param_container_stores follows a container parameter to what its callers pass, finding them by asking an_call_targets_scope of every call in the program -- for each parameter the walk reaches, from each container whose Strings it demands. On the 86k-line actionpack sample that scan (an_call_targets_scope and the receiver resolution in an_call_targets_nonunique under it) was nearly all of the first fixpoint round, which did not finish in 30 minutes before #7992 and took 19 on master. Only a call under the method's own name, under a name some class aliases a method as, or `new` for an initialize can answer yes, so those are the calls asked now, collected from the by-name call lists. And within one promote_shared_stored_strings pass the answer for a method is kept: the pass marks Strings shared and changes no call's targets, while the same methods are walked back to from many containers. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Strings in an Array held by a Hash value lost their in-place mutations (
strip!,upcase!,<<). The block changed a copy, with no refusal: silent wrong output. CRuby strips every value below; Spinel left" 1".webrick's
HTTPUtils.parse_headerbuilds headers like this, then runsheader.each { |key, values| values.each(&:strip!) }. A POST'sContent-Lengthkept its leading space and failed webrick's digits check with400 invalid content-length request header. Its continuation lines (header[field][-1] << " " << $1) were dropped too.There are two root causes, one commit each, each with its test:
promote_shared_stored_strings, element-iterator loop): stored Strings mutated by an iterator block were made shared handles only when the receiver was a local Array or a literal. Receivers likeh.each { |k, vs| vs.each(&:strip!) },each_value,h[k].eachandvs = h[k]; vs.eachwere skipped. They now demand the stored Strings, as the poly-Array path already does:strbuf_demand_container_storesfor a local, andstrbuf_container_source_walkfor a call.test/hash_value_array_element_bang.rb.strbuf_demand_container_stores_here): the demand walk followed stores into the Hash itself, but not the<<into the Array thath[k]returns. That coversheader[field] = []thenheader[field] << value, and(h[k] ||= []) << s(IndexOrWriteNodenow joinssb_store_nodes).test/hash_element_push_each_bang.rb, a webrick-styleparse_headerwith a continuation line.Both tests' expected output comes from CRuby, and both print the unstripped values on master. Of the 1041 corpus tests matching strbuf/shared/string/mutat/bang/strip/each/hash_, all pass except
array_callforms_strbuf_cycle, which passes when rerun alone (a load flake).make share-strings-testandmake reject-testpass.Not covered: store shapes this walk still doesn't follow, e.g.
h.store(k, arr).🤖 Generated with Claude Code
Summary by CodeRabbit
||=assignments, including cases involving boxed values and element iteration.