From 051a447668cf5c15ef159c644b7f931da072f1e6 Mon Sep 17 00:00:00 2001 From: Gary Tokman Date: Fri, 9 Oct 2026 14:48:30 -0400 Subject: [PATCH] Avoid isolated destructor crash when releasing highlight regions --- .../Highlight/LTXHighlightRegion.swift | 4 +++ .../HighlightRegionLifetimeTests.swift | 30 +++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 Tests/MarkdownViewTests/HighlightRegionLifetimeTests.swift diff --git a/Sources/Litext/LTXLabel/Highlight/LTXHighlightRegion.swift b/Sources/Litext/LTXLabel/Highlight/LTXHighlightRegion.swift index 7d55c16..451496a 100644 --- a/Sources/Litext/LTXLabel/Highlight/LTXHighlightRegion.swift +++ b/Sources/Litext/LTXLabel/Highlight/LTXHighlightRegion.swift @@ -18,6 +18,10 @@ public class LTXHighlightRegion { self.stringRange = stringRange } + // Avoid the isolated-deinit runtime crash when synchronous layout releases a region. + // https://github.com/swiftlang/swift/issues/85663 + nonisolated deinit {} + func addRect(_ rect: CGRect) { #if canImport(UIKit) rects.append(NSValue(cgRect: rect)) diff --git a/Tests/MarkdownViewTests/HighlightRegionLifetimeTests.swift b/Tests/MarkdownViewTests/HighlightRegionLifetimeTests.swift new file mode 100644 index 0000000..5050f32 --- /dev/null +++ b/Tests/MarkdownViewTests/HighlightRegionLifetimeTests.swift @@ -0,0 +1,30 @@ +import Foundation +import XCTest +@testable import Litext + +@MainActor +final class HighlightRegionLifetimeTests: XCTestCase { + private enum LayoutContext { + @TaskLocal static var value = "outside" + } + + // Keep this synchronous: UIKit layout and observation callbacks need not run in a Swift task. + func testSynchronousReleaseInsideTaskLocalScope() { + for _ in 0 ..< 100 { + LayoutContext.$value.withValue("layout") { + autoreleasepool { + var regions = [0: LTXHighlightRegion( + attributes: [NSAttributedString.Key("link"): "https://example.com"], + stringRange: NSRange(location: 0, length: 4) + )] + weak var region: LTXHighlightRegion? + region = regions[0] + regions.removeAll() + XCTAssertNil(region) + } + XCTAssertEqual(LayoutContext.value, "layout") + } + XCTAssertEqual(LayoutContext.value, "outside") + } + } +}