Fix: CFAttributedString multi-run attribute storage - #151
Open
DTW-Thalion wants to merge 1 commit into
Open
Conversation
This was referenced Jul 22, 2026
Setting an attribute across a string that holds two or more runs crashed. GSBSearch set its upper bound to mid - 1, skipping index 0 and returning -1 for any key below the first entry, and the callers indexed the run array with that value. Restore the usual binary search bound. The mutable and immutable instance sizes subtracted the struct size from CFRuntimeClass instead of subtracting CFRuntimeBase from the struct. Instances were allocated too small and _isEditing held garbage. Correct both size macros and initialise _isEditing. The attribute cache is a bag keyed on the set pointer. Reusing a cached set left its count unchanged, so releasing one run freed a set that another run still referenced. Count cached references through a new GSHashTableAddValueCounted, and give CFBag the same counting so a repeated value raises its multiplicity. Growing and shrinking the run array used the wrong element count and left the capacity stale, and coalescing adjacent equal runs read past the start of the array. Correct the reallocation sizes and the coalesce bounds. Implement CFAttributedStringGetAttributeAndLongestEffectiveRange and CFAttributedStringGetAttributesAndLongestEffectiveRange, which returned NULL.
DTW-Thalion
force-pushed
the
fix-cfattributedstring-mutable
branch
from
July 24, 2026 15:22
c4201ef to
4c2a639
Compare
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.
Setting an attribute across a mutable attributed string that holds two
or more runs crashed before this change. The root cause was an
off-by-one in GSBSearch, which set its upper bound to
mid - 1and soreturned -1 for any key below the first entry; the CFAttributedString
run lookups then indexed the run array with that -1.
Several further defects in the mutable core were exposed once the
lookup no longer crashed:
The mutable and immutable instance-size macros subtracted the struct
size from
CFRuntimeClassrather than subtractingCFRuntimeBasefromthe struct, so instances were allocated smaller than the struct and
_isEditingwas never a defined value.The attribute cache is a bag keyed on the attribute-set pointer.
Reusing an already cached set did not raise its reference count, so
releasing one run freed a set that another run still pointed at. This
adds
GSHashTableAddValueCountedfor the counted insert and routesCFBagAddValuethrough it as well, so a bag now records multiplicity.The run array grew and shrank with the wrong element count and left the
stored capacity stale, and the coalescing pass read one slot before the
start of the array.
CFAttributedStringGetAttributeAndLongestEffectiveRangeandCFAttributedStringGetAttributesAndLongestEffectiveRangereturned NULLand are now implemented.
Tests/CFAttributedString/multirun.mandlongest_range.mcover themulti-run accessors and the longest-effective-range walk. Both abort on
the current master and pass with this change. The full suite shows no
new failures against master.
Related
Part of the CFAttributedString series. #152 (substring, attribute removal and replacement) is stacked on this branch. Merge order for #152: #150 and #151 first, then #152.