Fix intermittent tvOS crash enumerating Obj-C classes during URLSession instrumentation#281
Merged
Merged
Conversation
…ation injectInNSURLClasses() enumerated every registered Obj-C class via objc_getClassList() and called class_copyMethodList on each. That list can include classes whose superclass chain references an unrealized stub class (weak-linked classes from unloaded frameworks, or relative-method-list stub classes). class_copyMethodList realizes the class, realization walks into the stub superclass, and the runtime aborts with "Attempt to use unknown class". Newer runtimes (Xcode 26.4 / tvOS) ship more of these stubs, which is why it surfaced now. Replace the unused, prefix-based objc_getSafeClassList with a real safety filter that keeps only classes whose entire superclass chain is itself in the class list, and use it from injectInNSURLClasses. Reading class_getSuperclass does not realize the class, so the filter never touches an unknown class. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ound Root cause: objc_getClassList(buf, n) writes at most n entries but returns the total number of registered classes. During app/test launch other threads load frameworks and register classes concurrently, so the second call can return a count larger than the buffer we sized from the first call. Iterating to that larger count read uninitialized memory and produced garbage "class" pointers, which crashed intermittently as "Attempt to use unknown class" / bad access the moment they were dereferenced. Clamp the loop to the allocated capacity. This was the actual bug, so the earlier superclass-chain safe-list workaround is no longer needed and is removed (it also made things worse by dereferencing the garbage pointers extra times). Keep the classConforms optimization, which skips class_copyMethodList for the thousands of classes that don't adopt URLSessionDelegate, and drop the unused objc_getSafeClassList helper and unused imports. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…opped Instead of clamping to the initial buffer capacity (which would silently drop classes registered mid-scan, possibly missing real URLSession delegates), keep re-querying and growing the buffer until objc_getClassList's returned total fits what we allocated. This captures every class while still never reading past the buffer. Small headroom lets it settle in a single retry under typical concurrent framework loading. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
gnufede
approved these changes
Jun 15, 2026
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.
Summary
Fixes an intermittent crash (
objc[…]: Attempt to use unknown class 0x…/Bad pointer dereference at 0xffffffffffffffff) duringURLSessionInstrumentationinit on tvOS, surfaced on Xcode 26.x.Root cause
InstrumentationUtils.objc_getClassList()used the classic two-call pattern but iterated to the count returned by the second call.objc_getClassList(buf, n)fills at mostnslots but returns the total number of registered classes. During app/test launch other threads load frameworks and register classes concurrently, so that total could exceed the buffer sized from the first call. The loop then read uninitialized memory past the buffer, producing garbage "class" pointers that crashed the moment they were dereferenced (in the scan, or in the superclass walk). The race is why it failed only intermittently and didn't reproduce on every toolchain/simulator.Fix
objc_getClassList()now grows and retries: re-query and enlarge the buffer until the returned total fits what we allocated. This captures every class, never reads past the buffer, and settles in a single retry under typical concurrent loading (small headroom).classConformsoptimization, which skips the expensiveclass_copyMethodListfor the thousands of classes that don't adoptURLSessionDelegate.objc_getSafeClassListhelper and unused imports; the delegate scan is otherwise back to its original, proven shape.Testing
DDNetworkInstrumentationTestspasses on the tvOS 26.5 simulator.🤖 Generated with Claude Code