fix: add bounds checking to RewindOCRService.extractTextWithBounds#5895
fix: add bounds checking to RewindOCRService.extractTextWithBounds#5895
Conversation
Greptile SummaryThis PR adds two defensive guards to
Confidence Score: 3/5
Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[extractTextWithBounds called] --> B{results castable to\nVNRecognizedTextObservation array?}
B -- No --> C[Return empty OCRResult]
B -- Yes --> D{observations.isEmpty?\nNEW GUARD}
D -- Yes --> C
D -- No --> E[Loop over observations]
E --> F{topCandidates 0 .isEmpty?\nNEW PROBE}
F -- Empty → skip --> E
F -- Non-empty --> G[topCandidates 1]
G --> H{candidates.first\nexists?}
H -- No → skip --> E
H -- Yes --> I[Build OCRTextBlock]
I --> E
E -- done --> J[Return OCRResult with blocks]
Last reviewed commit: "fix: add bounds chec..." |
| guard !observation.topCandidates(0).isEmpty else { continue } | ||
| let candidates = observation.topCandidates(1) | ||
| guard let candidate = candidates.first else { continue } |
There was a problem hiding this comment.
topCandidates(0) uses undocumented parameter value
The Apple Vision documentation specifies that the maxCandidateCount parameter for topCandidates(_:) must be in the range [1, 10]. Passing 0 is technically outside the documented contract.
There are two possible runtime behaviours:
- The framework clamps
0to1, makingtopCandidates(0)equivalent totopCandidates(1)— which would still crash on invalid observations, offering no protection. - The framework returns an empty array for count
0without accessing any internal elements — which would always be empty, so the guard would skip every observation and silently break OCR for all valid frames.
If the fix works as described (crash gone, OCR still produces results), it implies a third behaviour — that passing 0 avoids the internal bounds check that fires for 1. That is plausible but depends on undocumented implementation details that could change across macOS versions.
A safer alternative is to check observation.confidence (a property that is always accessible regardless of candidate count) as the validity probe, and only call topCandidates(1) when confidence is meaningful:
// Confidence is always accessible; skip observations with no usable data
guard observation.confidence > 0 else { continue }
let candidates = observation.topCandidates(1)
guard let candidate = candidates.first else { continue }Or, if the intent is truly to guard against completely empty candidate lists, wrapping topCandidates(1) directly is equivalent to the current two-call approach without the undocumented-value risk:
let candidates = observation.topCandidates(1)
guard let candidate = candidates.first else { continue }(The original code before this PR was already doing this — the crash must therefore originate somewhere other than an empty candidates array, e.g. an assertion inside Vision itself before the array is constructed. The topCandidates(0) probe would only help if Vision specifically skips that assertion for a count of 0.)
| guard !observations.isEmpty else { | ||
| continuation.resume(returning: OCRResult(fullText: "", blocks: [], processedAt: Date())) | ||
| return | ||
| } |
There was a problem hiding this comment.
The guard added on lines 177-180 is a no-op given the control flow that already exists: if observations is an empty array, the for observation in observations loop body never executes, and execution falls through to build an OCRResult with empty blocks and fullTextLines — the same result produced by the early return here.
The guard is harmless and adds some readability, but it is worth noting it does not change the observable behaviour.
| guard !observations.isEmpty else { | |
| continuation.resume(returning: OCRResult(fullText: "", blocks: [], processedAt: Date())) | |
| return | |
| } | |
| // If observations is empty the loop produces the same empty result; | |
| // the guard is here for clarity only. | |
| guard !observations.isEmpty else { | |
| continuation.resume(returning: OCRResult(fullText: "", blocks: [], processedAt: Date())) | |
| return | |
| } |
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Summary
Fixes #5891 and #5151 — macOS Screen Capture crashes on enable due to VNRecognizeTextRequest array bounds failure.
Root Cause
extractTextWithBoundsinRewindOCRService.swiftcrashes withEXC_BREAKPOINT (SIGTRAP)when accessingobservation.topCandidates(1)on empty/invalid VNRecognizedTextObservation internal arrays.Fix
topCandidates(0)before callingtopCandidates(1)to prevent internal array crashTesting