Survive forbidden objects during the frame scan - #53
Open
dfrisone wants to merge 1 commit into
Open
Conversation
IsForbidden() is no longer sufficient on its own. Objects exist that answer false and then raise "Attempt to access forbidden object from code tainted by an AddOn" on the very next call, GetObjectType(). ScanFrames hits one, the error is swallowed by coroutine.resume returning false rather than propagating, the walk ends mid-tree, and WriteCache commits the truncated set. Every later rescan dies at the same object, so nothing recovers it and GetUnitFrame returns nil for every unit. Measured in a raid on 12.1: replaying the same walk with the same gates, stopping at the first raising node reaches 0 unit frames, while continuing past them reaches 10, with 2990 raising objects in a single pass. They are spread across the UI rather than owned by one addon, roughly 2010 directly under UIParent and the rest under unit frames and raid frame buttons. GetObjectType and the button inspection are wrapped so one bad node is skipped instead of ending the scan. Neither wrapper contains the recursion, since ScanFrames yields and yielding across a pcall boundary is an error in 5.1; that is why the button body moved into its own function. coroutine.resume's result is now checked and reported once per scan through geterrorhandler. With the wrappers above this should be rare, but a silent truncation is indistinguishable from a UI that genuinely has no unit frames, which is what made this hard to find. GetChildren is deliberately left unwrapped: reaching it means GetObjectType already succeeded on that object, and wrapping it would need a table per node across the whole tree. If it ever does raise, the resume check above now reports it instead of hiding it.
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.
The problem
IsForbidden()is no longer sufficient on its own. On 12.1 there are objects that answer false and then raise on the very next call:ScanFrameshits one at line 402, and becausecoroutine.resumereports errors by returningfalse, errrather than propagating, the error is discarded at line 442. The walk ends mid-tree, the loop exits oncoroutine.status(co) == "dead", andWriteCachecommits whatever partial set was reached. Every later rescan dies at the same object, soGetUnitFramereturns nil for every unit from then on and nothing recovers it.For users this presents as a raid-frame glow silently doing nothing, with no error, in some content while working in others.
Evidence
Replaying the same walk over
UIParentwith the same gates in the same order, differing only in whether it stops at the first raising node. Measured in a raid on 12.1:2990 raising objects in a single pass. They are spread across the UI rather than owned by any one addon: roughly 2010 directly under
UIParent, the rest under unit frames and raid frame buttons. The frames the caller wants are perfectly reachable; the scan just never arrives.Things I ruled out before landing on this, in case they save you time: a stale cache (calling
lib.ScanForUnitFrames()and re-checking resolved nothing), refused unit comparison inGetUnitFrames(C_Secrets.CanCompareUnitTokensreturns true andUnitIsUnitis not secret in the failing content), and missing name patterns (the frames were already indefaultFramePriorities).The change
GetObjectTypeand the button inspection are wrapped, so one bad node is skipped rather than ending the scan. Neither wrapper contains the recursion --ScanFramesyields, and yielding across a pcall boundary is an error in 5.1 -- which is why the button body moved into its ownInspectButtonfunction rather than being wrapped in place.coroutine.resume's result is now checked and surfaced once per scan throughgeterrorhandler. With the wrappers above that should be rare, but a silent truncation is indistinguishable from a UI that genuinely has no unit frames, which is most of why this took a while to find.GetChildrenis deliberately left unwrapped: reaching it meansGetObjectTypealready succeeded on that object, and wrapping it would mean a table allocation per node across the whole tree. If it ever does raise, the resume check now reports it rather than hiding it.MINOR bumped to 78 so a fixed copy wins the LibStub race against embedded 77s.
Happy to adjust any of this -- in particular if you would rather the resume error be silent, or want
GetChildrenwrapped too despite the allocation.