rc: a string made over an array's memory is a view, copied where it is kept (#481) - #488
Merged
Merged
Conversation
…s kept (#481) `<string><Opaque>Ref(buffer[i])` makes a string that points into an array's data block. Under rc a receiver retained it like any string, counting the word in front of the pointer: for `Ref(buffer[0])` that is the data block's count, so the block outlived the array, and once the array grew the string's release decremented the count of the freed old block (heap corruption) while the grown block leaked; for `Ref(buffer[8])` the "count" was elements 0-7, which the retain incremented. Now, as under own, such a string is a view under rc: it keeps no count. OwnedReturnConsumptionPass (rc only) turns each retain of a view into a copy for the receiver it was made for - a return, a store into a field, an element or a global, a push, a capture - made right there, after the bytes were written through the view (`sprintf_s(s, ...)`, then `return s`, as the default library's convertNumber does). A `let` that only ever holds a view borrows it; one assigned again starts from a copy, since the assignment releases what it held. A view still points into the array: used after the array grows or dies, it reads freed memory in every model, as a C pointer would. A view kept inside a tuple or record literal, or by a function it is passed to, is not copied. Closes #481 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
Fixes #481 with the design chosen there: under rc,
<string><Opaque>Ref(buffer[i])is a view of the array's memory, copied where it is kept.The bug
Under rc a receiver retained such a string like any other, counting the word in front of the pointer:
Ref(buffer[0]): that word is the data block's count, so the block outlived the array. Once the array grew, the string's release decremented the count of the freed old block, and the grown block leaked. On main, a function that keeps such a string in a global and then grows the array, called three times, ends in heap corruption (0xC0000374).Ref(buffer[8]): the "count" is elements 0-7. Keeping the string turnsabcdefghijklmnopintobbcdefghijklmnop.The fix (
OwnedReturnConsumptionPass, rc only)As under own, a view keeps no count. Each retain of a view becomes a copy for the receiver it was made for - a return, a store into a field, an element or a global, a push, a capture - made right there, after the bytes were written through the view.
const s = <string><Opaque>Ref(buffer[0]); sprintf_s(s, ...); return s;(the default library'sconvertNumberand date formatters) returns a copy, and the buffer is freed with the array. Aletthat only ever holds a view borrows it (no count, no copy); one that is assigned again starts from a copy, since the assignment releases what it held.gc, none and own are unchanged.
Limits, documented in the pass:
The arrays spec's rc section notes the change.
Tests
00string_view_copy.ts(compile, JIT, rc and none corpora): return through aconstand alet, a view at element 1, a view kept in a field, a global and an array, the bytes in front of a view at element 8, and a kept view whose array then grows. Main fails it under rc ("the bytes in front of a view are not a count"); it usesstrcpy, so it runs on Linux too.Gates
ctest -C Release -j 12 --timeout 300, staged default library)sprintf_s, which glibc lacks) and pass 6 / 6 after switching tostrcpyweakref_basic, gc-only by design (#420)measure.ps1, AOT)own is not touched (the step runs under rc only), so the own corpus is unaffected.
Closes #481
🤖 Generated with Claude Code