Skip to content

Commit

Permalink
Capture objc exceptions on task delegate (#203)
Browse files Browse the repository at this point in the history
  • Loading branch information
Reflejo authored Jan 29, 2025
1 parent d72c528 commit 7cf018a
Show file tree
Hide file tree
Showing 10 changed files with 104 additions and 32 deletions.
2 changes: 1 addition & 1 deletion .bazelrc
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,6 @@ build:ios-tsan --features=tsan

build:asan --config=fake-nightly
build:asan --features=address
build:tsan --@rules_rust//rust/settings:extra_rustc_flag=-Zsanitizer=address
build:asan --@rules_rust//rust/settings:extra_rustc_flag=-Zsanitizer=address

try-import %workspace%/tmp/ci-bazelrc
16 changes: 1 addition & 15 deletions .github/workflows/ios.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -44,19 +44,6 @@ jobs:
# TODO(snowp): Add some kind of assertion that the app does that it's supposed to
- run: ./bazelw run --config ci //examples/swift/hello_world:ios_app &> /tmp/envoy.log &
name: 'Run app'
macos_asan:
runs-on: macos-14
needs: "pre_check"
if: needs.pre_check.outputs.should_run == 'true'
steps:
# Checkout repo to Github Actions runner
- name: Checkout
uses: actions/checkout@v4

- name: 'Install dependencies'
run: ./ci/mac_ci_setup.sh
- name: Run tests (asan)
run: env -u ANDROID_NDK_HOME ./bazelw test //core/... //platform/... //test/platform/swift/unit_integration/... --test_tag_filters=macos_only --test_output=errors --build_tests_only --config ci --config asan
macos_tsan:
runs-on: macos-14
needs: "pre_check"
Expand All @@ -72,7 +59,7 @@ jobs:
run: env -u ANDROID_NDK_HOME ./bazelw test $(./bazelw query 'kind(ios_unit_test, //test/platform/swift/unit_integration/core/...)') --test_tag_filters=macos_only --test_output=errors --config ci --config ios-tsan
verify_ios:
runs-on: ubuntu-latest
needs: ["macos_tsan", "macos_asan", "swift_hello_world"]
needs: ["macos_tsan", "swift_hello_world"]
if: always()
steps:
# Checkout repo to Github Actions runner
Expand All @@ -82,5 +69,4 @@ jobs:
fetch-depth: 1
- run: |
./ci/check_result.sh ${{ needs.macos_tsan.result }} \
&& ./ci/check_result.sh ${{ needs.macos_asan.result }} \
&& ./ci/check_result.sh ${{ needs.swift_hello_world.result }}
6 changes: 4 additions & 2 deletions platform/swift/source/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ swift_library(
module_name = "Capture",
private_deps = [
":CapturePassable",
":rust_bridge",
":objc_bridge",
],
tags = [
"macos_only",
Expand Down Expand Up @@ -71,9 +71,11 @@ bitdrift_rust_library(
)

objc_library(
name = "rust_bridge",
name = "objc_bridge",
srcs = ["ObjCWrapper.mm"],
hdrs = [
"CaptureRustBridge.h",
"ObjCWrapper.h",
],
module_name = "CaptureLoggerBridge",
tags = ["macos_only"],
Expand Down
17 changes: 17 additions & 0 deletions platform/swift/source/ObjCWrapper.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// capture-sdk - bitdrift's client SDK
// Copyright Bitdrift, Inc. All rights reserved.
//
// Use of this source code is governed by a source available license that can be found in the
// LICENSE file or at:
// https://polyformproject.org/wp-content/uploads/2020/06/PolyForm-Shield-1.0.0.txt

#import <UIKit/UIKit.h>

@interface ObjCWrapper: NSObject

/**
* Try to execute the block and catch any exceptions.
*/
+ (BOOL)doTry:(void(^)())block error:(NSError **)err;

@end
26 changes: 26 additions & 0 deletions platform/swift/source/ObjCWrapper.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// capture-sdk - bitdrift's client SDK
// Copyright Bitdrift, Inc. All rights reserved.
//
// Use of this source code is governed by a source available license that can be found in the
// LICENSE file or at:
// https://polyformproject.org/wp-content/uploads/2020/06/PolyForm-Shield-1.0.0.txt

#import <Foundation/Foundation.h>
#import "ObjCWrapper.h"

@implementation ObjCWrapper

+ (BOOL)doTry:(void(^)())block error:(NSError **)err {
@try {
block();
return YES;
}
@catch (NSException *exception) {
*err = [NSError errorWithDomain:@"io.bitdrift"
code:-1
userInfo:@{@"exception": exception}];
return NO;
}
}

@end
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,10 @@ final class URLSessionIntegration {
let session = URLSession(configuration: .ephemeral)
defer { session.invalidateAndCancel() }

return type(of: session.dataTask(with: request))
let task = session.dataTask(with: request)
defer { task.cancel() }

return type(of: task)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,22 @@
// LICENSE file or at:
// https://polyformproject.org/wp-content/uploads/2020/06/PolyForm-Shield-1.0.0.txt

@_implementationOnly import CaptureLoggerBridge
import Foundation
import ObjectiveC

extension URLSessionTask {
@available(iOS 15.0, *)
@objc
func cap_resume() {
defer { self.cap_resume() }
if self.state == .completed || self.state == .canceling {
return
}

URLSessionTaskTracker.shared.taskWillStart(self)
self.delegate = ProxyURLSessionTaskDelegate(target: self.delegate)
self.cap_resume()
try? ObjCWrapper.doTry {
self.delegate = ProxyURLSessionTaskDelegate(target: self.delegate)
}
}
}
2 changes: 1 addition & 1 deletion test/platform/swift/unit_integration/core/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ bitdrift_mobile_swift_test(
visibility = ["//visibility:public"],
deps = [
"//platform/swift/source:ios_lib",
"//platform/swift/source:rust_bridge",
"//platform/swift/source:objc_bridge",
"//test/platform/swift/benchmark:benchmarks",
"//test/platform/swift/bridging:rust_bridge",
"//test/platform/swift/unit_integration/mocks",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,37 @@ final class URLSessionIntegrationTests: XCTestCase {
}
}

func testBackgroundSessionTasks() throws {
self.customSetUp(swizzle: true)

let session = URLSession(configuration: .background(withIdentifier: "w00t"))
let task = session.dataTask(with: self.makeURL())

let logRequestExpectation = self.expectation(description: "request logged")
self.logger.logRequestExpectation = logRequestExpectation
task.resume()

XCTAssertEqual(.completed, XCTWaiter().wait(for: [logRequestExpectation], timeout: 3, enforceOrder: false))
XCTAssertEqual(1, self.logger.logs.count)

let requestInfo = try XCTUnwrap(self.logger.logs[0].request())
var requestInfoFields = try XCTUnwrap(requestInfo.toFields() as? [String: String])
requestInfoFields["_span_id"] = nil

XCTAssertEqual(
[
"_host": "api-fe.bitdrift.io",
"_method": "GET",
"_path": "/fe/ping",
"_query": "q=test",
],
requestInfoFields
)

self.customTearDown()
session.finishTasksAndInvalidate()
}

func testCustomCaptureSessionTasks() throws {
for taskTestCase in self.makeTaskWithoutCompletionClosureTestCases() {
self.customSetUp(swizzle: false)
Expand Down Expand Up @@ -543,9 +574,9 @@ final class URLSessionIntegrationTests: XCTestCase {

XCTAssertEqual(
[
"_host": "www.google.com",
"_host": "api-fe.bitdrift.io",
"_method": "GET",
"_path": "/search",
"_path": "/fe/ping",
"_query": "q=test",
],
requestInfoFields
Expand All @@ -564,9 +595,9 @@ final class URLSessionIntegrationTests: XCTestCase {

XCTAssertEqual(
[
"_host": "www.google.com",
"_host": "api-fe.bitdrift.io",
"_method": "GET",
"_path": "/search",
"_path": "/fe/ping",
"_query": "q=test",
"_result": "success",
"_status_code": "200",
Expand Down Expand Up @@ -610,9 +641,9 @@ final class URLSessionIntegrationTests: XCTestCase {

XCTAssertEqual(
[
"_host": "www.google.com",
"_host": "api-fe.bitdrift.io",
"_method": "GET",
"_path": "/search",
"_path": "/fe/ping",
"_query": "q=test",
],
requestInfoFields
Expand All @@ -631,9 +662,9 @@ final class URLSessionIntegrationTests: XCTestCase {

XCTAssertEqual(
[
"_host": "www.google.com",
"_host": "api-fe.bitdrift.io",
"_method": "GET",
"_path": "/search",
"_path": "/fe/ping",
"_query": "q=test",
"_result": "canceled",
],
Expand Down Expand Up @@ -778,7 +809,7 @@ final class URLSessionIntegrationTests: XCTestCase {
// significantly more time when ran on the CI.
// TODO(Augustyniak): Move to using bitdrift ping.
// swiftlint:disable:next force_unwrapping
return URL(string: "http://www.google.com/search?q=test")!
return URL(string: "https://api-fe.bitdrift.io/fe/ping?q=test")!
}

private func makeTempFileURL(name: String) throws -> URL {
Expand Down
2 changes: 1 addition & 1 deletion test/platform/swift/unit_integration/mocks/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@ swift_library(
visibility = ["//visibility:public"],
deps = [
"//platform/swift/source:ios_lib",
"//platform/swift/source:rust_bridge",
"//platform/swift/source:objc_bridge",
],
)

0 comments on commit 7cf018a

Please sign in to comment.