Skip to content

Commit fd4714b

Browse files
Merge pull request #2754 from DataDog/hotfix/3.8.1
Merge `hotfix/3.8.1` to develop
2 parents 1f54654 + 93df533 commit fd4714b

17 files changed

Lines changed: 146 additions & 18 deletions

.gitlab-ci.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,8 @@ Smoke Tests (iOS):
234234
rules:
235235
- !reference [.test-pipeline-job, rules]
236236
- !reference [.release-pipeline-job, rules]
237+
id_tokens:
238+
<<: *dd-octo-sts-id-token
237239
variables:
238240
PLATFORM: "iOS Simulator"
239241
DEVICE: "iPhone 16 Pro"
@@ -247,6 +249,8 @@ Smoke Tests (tvOS):
247249
rules:
248250
- !reference [.test-pipeline-job, rules]
249251
- !reference [.release-pipeline-job, rules]
252+
id_tokens:
253+
<<: *dd-octo-sts-id-token
250254
variables:
251255
PLATFORM: "tvOS Simulator"
252256
DEVICE: "Apple TV"
@@ -359,6 +363,8 @@ Build Artifacts:
359363
stage: release-build
360364
rules:
361365
- !reference [.release-pipeline-job, rules]
366+
id_tokens:
367+
<<: *dd-octo-sts-id-token
362368
artifacts:
363369
paths:
364370
- artifacts

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Unreleased
22

3+
# 3.8.1 / 13-03-2026
4+
5+
- [FIX] Fix crash when network instrumentation intercepts `AVAssetDownloadTask` or `AVAggregateAssetDownloadTask`. See [#2750][]
6+
37
# 3.8.0 / 12-03-2026
48

59
- [FEATURE] Add OOB scroll and swipe action tracking. See [#2717][]
@@ -1075,6 +1079,7 @@ Release `2.0` introduces breaking changes. Follow the [Migration Guide](MIGRATIO
10751079
[#2721]: https://github.com/DataDog/dd-sdk-ios/pull/2721
10761080
[#2726]: https://github.com/DataDog/dd-sdk-ios/pull/2726
10771081
[#2740]: https://github.com/DataDog/dd-sdk-ios/pull/2740
1082+
[#2750]: https://github.com/DataDog/dd-sdk-ios/pull/2750
10781083

10791084
[@00fa9a]: https://github.com/00FA9A
10801085
[@britton-earnin]: https://github.com/Britton-Earnin

DatadogCore.podspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Pod::Spec.new do |s|
22
s.name = "DatadogCore"
3-
s.version = "3.8.0"
3+
s.version = "3.8.1"
44
s.summary = "Official Datadog Swift SDK for iOS."
55

66
s.homepage = "https://www.datadoghq.com"
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
// GENERATED FILE: Do not edit directly
22

3-
internal let __sdkVersion = "3.8.0"
3+
internal let __sdkVersion = "3.8.1"

DatadogCrashReporting.podspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Pod::Spec.new do |s|
22
s.name = "DatadogCrashReporting"
3-
s.version = "3.8.0"
3+
s.version = "3.8.1"
44
s.summary = "Official Datadog Crash Reporting SDK for iOS."
55

66
s.homepage = "https://www.datadoghq.com"

DatadogFlags.podspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Pod::Spec.new do |s|
22
s.name = "DatadogFlags"
3-
s.version = "3.8.0"
3+
s.version = "3.8.1"
44
s.summary = "Official Datadog Feature Flags module of the Swift SDK."
55

66
s.homepage = "https://www.datadoghq.com"

DatadogInternal.podspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Pod::Spec.new do |s|
22
s.name = "DatadogInternal"
3-
s.version = "3.8.0"
3+
s.version = "3.8.1"
44
s.summary = "Datadog Internal Package. This module is not for public use."
55

66
s.homepage = "https://www.datadoghq.com"

DatadogInternal/Sources/NetworkInstrumentation/NetworkInstrumentationFeature.swift

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,19 @@ internal final class NetworkInstrumentationFeature: DatadogFeature {
9191
return
9292
}
9393

94+
// Skip task types that declare standard URLSessionTask properties as
95+
// NS_UNAVAILABLE and throw NSGenericException at runtime when accessed
96+
// (e.g. AVAssetDownloadTask, AVAggregateAssetDownloadTask).
97+
guard task.isSupportedForInstrumentation else {
98+
return
99+
}
100+
101+
guard let currentRequest = task.currentRequest else {
102+
return
103+
}
104+
94105
// Skip Datadog's own intake requests to prevent infinite recursion
95-
if self.isDatadogIntakeRequest(task.currentRequest) {
106+
if self.isDatadogIntakeRequest(currentRequest) {
96107
return
97108
}
98109

@@ -106,11 +117,9 @@ internal final class NetworkInstrumentationFeature: DatadogFeature {
106117
var injectedTraceContexts = [RequestInstrumentationContext]()
107118

108119
let configuredFirstPartyHosts = FirstPartyHosts(firstPartyHosts: configuration?.firstPartyHostsTracing) ?? .init()
109-
if let currentRequest = task.currentRequest {
110-
let (request, traceContexts) = self.intercept(request: currentRequest, additionalFirstPartyHosts: configuredFirstPartyHosts)
111-
task.dd.override(currentRequest: request)
112-
injectedTraceContexts = traceContexts
113-
}
120+
let (request, traceContexts) = self.intercept(request: currentRequest, additionalFirstPartyHosts: configuredFirstPartyHosts)
121+
task.dd.override(currentRequest: request)
122+
injectedTraceContexts = traceContexts
114123

115124
self.intercept(task: task, with: injectedTraceContexts, additionalFirstPartyHosts: configuredFirstPartyHosts, trackingMode: trackingMode)
116125
}

DatadogInternal/Sources/NetworkInstrumentation/URLSession/URLSessionTask+Tracking.swift

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,3 +50,25 @@ extension DatadogExtension where ExtendedType: URLSessionTask {
5050
}
5151

5252
private var hasCompletionKey: Void?
53+
54+
extension URLSessionTask {
55+
/// `URLSessionTask` subclasses that declare most of their inherited properties as `NS_UNAVAILABLE`
56+
/// and throw `NSGenericException` at runtime when those properties are accessed.
57+
/// Resolved once using `NSClassFromString` to avoid importing AVFoundation.
58+
private static let unsupportedTaskClasses: [AnyClass] = {
59+
[
60+
"AVAssetDownloadTask",
61+
"NSURLSessionAVAssetDownloadTask",
62+
"AVAggregateAssetDownloadTask",
63+
"NSURLSessionAVAggregateAssetDownloadTask"
64+
]
65+
.compactMap { NSClassFromString($0) }
66+
}()
67+
68+
/// Returns `true` if the task supports standard `URLSessionTask` property access and
69+
/// can be instrumented. Some subclasses declare properties like `currentRequest` and
70+
/// `response` as `NS_UNAVAILABLE` and throw `NSGenericException` at runtime when accessed.
71+
var isSupportedForInstrumentation: Bool {
72+
!Self.unsupportedTaskClasses.contains { self.isKind(of: $0) }
73+
}
74+
}

DatadogInternal/Tests/NetworkInstrumentation/NetworkInstrumentationFeatureTests.swift

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1887,6 +1887,92 @@ class NetworkInstrumentationFeatureTests: XCTestCase {
18871887
XCTAssertEqual(interception.trace, traceContexts.first, "It should register first injected Trace Context")
18881888
}
18891889

1890+
// MARK: - isSupportedForInstrumentation
1891+
1892+
func testIsSupportedForInstrumentation_returnsTrueForDataTask() {
1893+
let session = URLSession(configuration: .ephemeral)
1894+
let task = session.dataTask(with: URL.mockAny())
1895+
defer { task.cancel() }
1896+
XCTAssertTrue(task.isSupportedForInstrumentation)
1897+
}
1898+
1899+
func testIsSupportedForInstrumentation_returnsTrueForUploadTask() {
1900+
let session = URLSession(configuration: .ephemeral)
1901+
let task = session.uploadTask(with: URLRequest(url: URL.mockAny()), from: Data())
1902+
defer { task.cancel() }
1903+
XCTAssertTrue(task.isSupportedForInstrumentation)
1904+
}
1905+
1906+
func testIsSupportedForInstrumentation_returnsTrueForDownloadTask() {
1907+
let session = URLSession(configuration: .ephemeral)
1908+
let task = session.downloadTask(with: URL.mockAny())
1909+
defer { task.cancel() }
1910+
XCTAssertTrue(task.isSupportedForInstrumentation)
1911+
}
1912+
1913+
@available(iOS 13.0, tvOS 13.0, *)
1914+
func testIsSupportedForInstrumentation_returnsTrueForWebSocketTask() {
1915+
let session = URLSession(configuration: .ephemeral)
1916+
let task = session.webSocketTask(with: URL(string: "wss://example.com")!)
1917+
defer { task.cancel() }
1918+
XCTAssertTrue(task.isSupportedForInstrumentation)
1919+
}
1920+
1921+
func testIsSupportedForInstrumentation_returnsTrueForStreamTask() {
1922+
let session = URLSession(configuration: .ephemeral)
1923+
let task = session.streamTask(withHostName: "example.com", port: 80)
1924+
defer { task.cancel() }
1925+
XCTAssertTrue(task.isSupportedForInstrumentation)
1926+
}
1927+
1928+
func testIsSupportedForInstrumentation_returnsFalseForUnsupportedAVTaskTypes() {
1929+
let unsupportedClassNames = [
1930+
"AVAssetDownloadTask",
1931+
"NSURLSessionAVAssetDownloadTask",
1932+
"AVAggregateAssetDownloadTask",
1933+
"NSURLSessionAVAggregateAssetDownloadTask",
1934+
"__NSCFBackgroundAVAssetDownloadTask"
1935+
]
1936+
for className in unsupportedClassNames {
1937+
guard let task = NSClassFromString(className)?.alloc() as? URLSessionTask else {
1938+
continue // class unavailable on this platform/OS version
1939+
}
1940+
XCTAssertFalse(task.isSupportedForInstrumentation, "\(className) should not be instrumented")
1941+
}
1942+
}
1943+
1944+
// MARK: - Crash regression: resume() on various task types
1945+
1946+
@available(iOS 13.0, tvOS 13.0, *)
1947+
func testWebSocketTask_resumeDoesNotCrash() throws {
1948+
// Regression: verify that resuming a WebSocketTask with the swizzle installed doesn't crash.
1949+
// The crash in interceptResume is synchronous, so no real connection is needed — we cancel immediately.
1950+
try URLSessionInstrumentation.enableOrThrow(with: nil, in: core)
1951+
let session = URLSession(configuration: .ephemeral)
1952+
let task = session.webSocketTask(with: URL(string: "wss://example.com")!)
1953+
task.resume()
1954+
task.cancel()
1955+
1956+
let feature = try XCTUnwrap(core.get(feature: NetworkInstrumentationFeature.self))
1957+
feature.flush()
1958+
// No crash = pass. WebSocketTask is a supported type and should be tracked.
1959+
XCTAssertEqual(handler.interceptions.count, 1)
1960+
}
1961+
1962+
func testStreamTask_resumeDoesNotCrash() throws {
1963+
// Regression: verify that resuming a StreamTask with the swizzle installed doesn't crash.
1964+
try URLSessionInstrumentation.enableOrThrow(with: nil, in: core)
1965+
let session = URLSession(configuration: .ephemeral)
1966+
let task = session.streamTask(withHostName: "example.com", port: 80)
1967+
task.resume()
1968+
task.cancel()
1969+
1970+
let feature = try XCTUnwrap(core.get(feature: NetworkInstrumentationFeature.self))
1971+
feature.flush()
1972+
// No crash = pass. StreamTask is a supported type and should be tracked.
1973+
XCTAssertEqual(handler.interceptions.count, 1)
1974+
}
1975+
18901976
// MARK: - First Party Hosts
18911977

18921978
func testAutomaticMode_detectsFirstPartyHosts() throws {

0 commit comments

Comments
 (0)