Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Deserializing SentryFrame #4739

Merged
merged 13 commits into from
Jan 23, 2025
12 changes: 12 additions & 0 deletions Sentry.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@
622C08DB29E554B9002571D4 /* SentrySpanContext+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 622C08D929E554B9002571D4 /* SentrySpanContext+Private.h */; };
62375FB92B47F9F000CC55F1 /* SentryDependencyContainerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62375FB82B47F9F000CC55F1 /* SentryDependencyContainerTests.swift */; };
623C45B02A651D8200D9E88B /* SentryCoreDataTracker+Test.m in Sources */ = {isa = PBXBuildFile; fileRef = 623C45AF2A651D8200D9E88B /* SentryCoreDataTracker+Test.m */; };
623FD9022D3FA5E000803EDA /* SentryFrameCodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 623FD9012D3FA5DA00803EDA /* SentryFrameCodable.swift */; };
623FD9042D3FA92700803EDA /* NSNumberDecodableWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 623FD9032D3FA90900803EDA /* NSNumberDecodableWrapper.swift */; };
623FD9062D3FA9C800803EDA /* NSNumberDecodableWrapperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 623FD9052D3FA9BA00803EDA /* NSNumberDecodableWrapperTests.swift */; };
624688192C048EF10006179C /* SentryBaggageSerialization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 624688182C048EF10006179C /* SentryBaggageSerialization.swift */; };
626E2D4C2BEA0C37005596FE /* SentryEnabledFeaturesBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 626E2D4B2BEA0C37005596FE /* SentryEnabledFeaturesBuilderTests.swift */; };
6271ADF32BA06D9B0098D2E9 /* SentryInternalSerializable.h in Headers */ = {isa = PBXBuildFile; fileRef = 6271ADF22BA06D9B0098D2E9 /* SentryInternalSerializable.h */; settings = {ATTRIBUTES = (Private, ); }; };
Expand Down Expand Up @@ -1130,6 +1133,9 @@
62375FB82B47F9F000CC55F1 /* SentryDependencyContainerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryDependencyContainerTests.swift; sourceTree = "<group>"; };
623C45AE2A651C4500D9E88B /* SentryCoreDataTracker+Test.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SentryCoreDataTracker+Test.h"; sourceTree = "<group>"; };
623C45AF2A651D8200D9E88B /* SentryCoreDataTracker+Test.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "SentryCoreDataTracker+Test.m"; sourceTree = "<group>"; };
623FD9012D3FA5DA00803EDA /* SentryFrameCodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryFrameCodable.swift; sourceTree = "<group>"; };
623FD9032D3FA90900803EDA /* NSNumberDecodableWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSNumberDecodableWrapper.swift; sourceTree = "<group>"; };
623FD9052D3FA9BA00803EDA /* NSNumberDecodableWrapperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSNumberDecodableWrapperTests.swift; sourceTree = "<group>"; };
624688182C048EF10006179C /* SentryBaggageSerialization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryBaggageSerialization.swift; sourceTree = "<group>"; };
626E2D4B2BEA0C37005596FE /* SentryEnabledFeaturesBuilderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryEnabledFeaturesBuilderTests.swift; sourceTree = "<group>"; };
6271ADF22BA06D9B0098D2E9 /* SentryInternalSerializable.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryInternalSerializable.h; path = include/SentryInternalSerializable.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2177,7 +2183,9 @@
620078752D38F1110022CB67 /* Codable */ = {
isa = PBXGroup;
children = (
623FD9032D3FA90900803EDA /* NSNumberDecodableWrapper.swift */,
6281C5712D3E4F06009D0978 /* DecodeArbitraryData.swift */,
623FD9012D3FA5DA00803EDA /* SentryFrameCodable.swift */,
620078712D38F00D0022CB67 /* SentryGeoCodable.swift */,
628094732D39584700B3F18B /* SentryUserCodable.swift */,
620078732D38F0DF0022CB67 /* SentryCodable.swift */,
Expand All @@ -2188,6 +2196,7 @@
620078762D3906AD0022CB67 /* Codable */ = {
isa = PBXGroup;
children = (
623FD9052D3FA9BA00803EDA /* NSNumberDecodableWrapperTests.swift */,
6281C5732D3E50D8009D0978 /* ArbitraryDataTests.swift */,
620078772D3906BF0022CB67 /* SentryCodableTests.swift */,
);
Expand Down Expand Up @@ -4752,6 +4761,7 @@
03F84D3727DD4191008FE43F /* SentrySamplingProfiler.cpp in Sources */,
8453421628BE8A9500C22EEC /* SentrySpanStatus.m in Sources */,
7B08A3472924CF9C0059603A /* SentryMetricKitIntegration.m in Sources */,
623FD9022D3FA5E000803EDA /* SentryFrameCodable.swift in Sources */,
7B63459B280EB9E200CFA05A /* SentryUIEventTrackingIntegration.m in Sources */,
D8AE48AE2C577EAB0092A2A6 /* SentryLog.swift in Sources */,
15E0A8ED240F2CB000F044E3 /* SentrySerialization.m in Sources */,
Expand All @@ -4764,6 +4774,7 @@
63FE716920DA4C1100CDBAE8 /* SentryCrashStackCursor.c in Sources */,
6221BBCA2CAA932100C627CA /* SentryANRType.swift in Sources */,
7BA61CCA247D128B00C130A8 /* SentryThreadInspector.m in Sources */,
623FD9042D3FA92700803EDA /* NSNumberDecodableWrapper.swift in Sources */,
D8CA12952C203E71005894F4 /* SentrySessionListener.swift in Sources */,
63FE718D20DA4C1100CDBAE8 /* SentryCrashReportStore.c in Sources */,
7BA0C0482805600A003E0326 /* SentryTransportAdapter.m in Sources */,
Expand Down Expand Up @@ -5157,6 +5168,7 @@
D8CCFC632A1520C900DE232E /* SentryBinaryImageCacheTests.m in Sources */,
A811D867248E2770008A41EA /* SentrySystemEventBreadcrumbsTest.swift in Sources */,
7B82D54924E2A2D400EE670F /* SentryIdTests.swift in Sources */,
623FD9062D3FA9C800803EDA /* NSNumberDecodableWrapperTests.swift in Sources */,
7B87C916295ECFD700510C52 /* SentryMetricKitEventTests.swift in Sources */,
7B6D98ED24C703F8005502FA /* Async.swift in Sources */,
7BA0C04C28056556003E0326 /* SentryTransportAdapterTests.swift in Sources */,
Expand Down
22 changes: 22 additions & 0 deletions Sources/Swift/Protocol/Codable/NSNumberDecodableWrapper.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
struct NSNumberDecodableWrapper: Decodable {
let value: NSNumber?

init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let intValue = try? container.decode(Int.self) {
value = NSNumber(value: intValue)
}
// On 32-bit platforms UInt is UInt32, so we use UInt64 to cover all platforms.
// We don't need UInt128 because NSNumber doesn't support it.
else if let uint64Value = try? container.decode(UInt64.self) {
value = NSNumber(value: uint64Value)
} else if let doubleValue = try? container.decode(Double.self) {
value = NSNumber(value: doubleValue)
} else if let boolValue = try? container.decode(Bool.self) {
value = NSNumber(value: boolValue)
} else {
SentryLog.warning("Failed to decode NSNumber from container for key: \(container.codingPath.last?.stringValue ?? "unknown")")
value = nil
}
}
}
42 changes: 42 additions & 0 deletions Sources/Swift/Protocol/Codable/SentryFrameCodable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
@_implementationOnly import _SentryPrivate
import Foundation

extension Frame: Decodable {

enum CodingKeys: String, CodingKey {
case symbolAddress = "symbol_addr"
case fileName = "filename"
case function
case module
case package
case imageAddress = "image_addr"
case platform
case instructionAddress = "instruction_addr"
// Leaving out instruction on purpose. The event payload does not contain this field
// and SentryFrame.serialize doesn't add it to the serialized dict.
// We will remove the property in the next major see:
// https://github.com/getsentry/sentry-cocoa/issues/4738
case lineNumber = "lineno"
case columnNumber = "colno"
case inApp = "in_app"
case stackStart = "stack_start"
}

required convenience public init(from decoder: any Decoder) throws {
self.init()

let container = try decoder.container(keyedBy: CodingKeys.self)
self.symbolAddress = try container.decodeIfPresent(String.self, forKey: .symbolAddress)
self.fileName = try container.decodeIfPresent(String.self, forKey: .fileName)
self.function = try container.decodeIfPresent(String.self, forKey: .function)
self.module = try container.decodeIfPresent(String.self, forKey: .module)
self.package = try container.decodeIfPresent(String.self, forKey: .package)
self.imageAddress = try container.decodeIfPresent(String.self, forKey: .imageAddress)
self.platform = try container.decodeIfPresent(String.self, forKey: .platform)
self.instructionAddress = try container.decodeIfPresent(String.self, forKey: .instructionAddress)
self.lineNumber = (try container.decodeIfPresent(NSNumberDecodableWrapper.self, forKey: .lineNumber))?.value
self.columnNumber = (try container.decodeIfPresent(NSNumberDecodableWrapper.self, forKey: .columnNumber))?.value
self.inApp = (try container.decodeIfPresent(NSNumberDecodableWrapper.self, forKey: .inApp))?.value
self.stackStart = (try container.decodeIfPresent(NSNumberDecodableWrapper.self, forKey: .stackStart))?.value
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
@testable import Sentry
import SentryTestUtils
import XCTest

class NSNumberDecodableWrapperTests: XCTestCase {

func testDecode_BoolTrue() throws {
// Arrange
let jsonData = #"""
{
"number": true
}
"""#.data(using: .utf8)!

// Act
let actual = try XCTUnwrap(decodeFromJSONData(jsonData: jsonData) as ClassWithNSNumber?)

// Assert
let number = try XCTUnwrap(actual.number)
XCTAssertTrue(number.boolValue)
}

func testDecode_BoolFalse() throws {
// Arrange
let jsonData = #"""
{
"number": false
}
"""#.data(using: .utf8)!

// Act
let actual = try XCTUnwrap(decodeFromJSONData(jsonData: jsonData) as ClassWithNSNumber?)

// Assert
let number = try XCTUnwrap(actual.number)
XCTAssertFalse(number.boolValue)
}

func testDecode_PositiveInt() throws {
// Arrange
let jsonData = #"""
{
"number": 1
}
"""#.data(using: .utf8)!

// Act
let actual = try XCTUnwrap(decodeFromJSONData(jsonData: jsonData) as ClassWithNSNumber?)

// Assert
let number = try XCTUnwrap(actual.number)
XCTAssertEqual(number.intValue, 1)
}

func testDecode_IntMax() throws {
// Arrange
let jsonData = """
{
"number": \(Int.max)
}
""".data(using: .utf8)!

// Act
let actual = try XCTUnwrap(decodeFromJSONData(jsonData: jsonData) as ClassWithNSNumber?)

// Assert
let number = try XCTUnwrap(actual.number)
XCTAssertEqual(number.intValue, Int.max)
}

func testDecode_IntMin() throws {
// Arrange
let jsonData = """
{
"number": \(Int.min)
}
""".data(using: .utf8)!

// Act
let actual = try XCTUnwrap(decodeFromJSONData(jsonData: jsonData) as ClassWithNSNumber?)

// Assert
let number = try XCTUnwrap(actual.number)
XCTAssertEqual(number.intValue, Int.min)
}

func testDecode_UInt32Max() throws {
// Arrange
let jsonData = """
{
"number": \(UInt32.max)
}
""".data(using: .utf8)!

// Act
let actual = try XCTUnwrap(decodeFromJSONData(jsonData: jsonData) as ClassWithNSNumber?)

// Assert
let number = try XCTUnwrap(actual.number)
XCTAssertEqual(number.uint32Value, UInt32.max)
}

func testDecode_UInt64Max() throws {
// Arrange
let jsonData = """
{
"number": \(UInt64.max)
}
""".data(using: .utf8)!

// Act
let actual = try XCTUnwrap(decodeFromJSONData(jsonData: jsonData) as ClassWithNSNumber?)

// Assert
let number = try XCTUnwrap(actual.number)
XCTAssertEqual(number.uint64Value, UInt64.max)
}

func testDecode_UInt128Max_UsesDouble() throws {
if #available(iOS 18.0, macOS 15.0, tvOS 18.0, visionOS 2, *) {

Check failure on line 120 in Tests/SentryTests/Protocol/Codable/NSNumberDecodableWrapperTests.swift

View workflow job for this annotation

GitHub Actions / Unit iOS - Thread Sanitizer

unrecognized platform name 'visionOS'
// Arrange
let jsonData = """
{
"number": \(UInt128.max)

Check failure on line 124 in Tests/SentryTests/Protocol/Codable/NSNumberDecodableWrapperTests.swift

View workflow job for this annotation

GitHub Actions / Unit iOS - Xcode 15.4 - OS 17.2 SentrySwiftUI

cannot find 'UInt128' in scope

Check failure on line 124 in Tests/SentryTests/Protocol/Codable/NSNumberDecodableWrapperTests.swift

View workflow job for this annotation

GitHub Actions / Unit iOS - Xcode 14.3 - OS 16.4 Sentry

cannot find 'UInt128' in scope

Check failure on line 124 in Tests/SentryTests/Protocol/Codable/NSNumberDecodableWrapperTests.swift

View workflow job for this annotation

GitHub Actions / Unit iOS - Xcode 15.4 - OS 17.2 Sentry

cannot find 'UInt128' in scope

Check failure on line 124 in Tests/SentryTests/Protocol/Codable/NSNumberDecodableWrapperTests.swift

View workflow job for this annotation

GitHub Actions / Unit iOS - Thread Sanitizer

cannot find 'UInt128' in scope

Check failure on line 124 in Tests/SentryTests/Protocol/Codable/NSNumberDecodableWrapperTests.swift

View workflow job for this annotation

GitHub Actions / Unit Catalyst - Xcode 15.4 - OS latest Sentry

cannot find 'UInt128' in scope

Check failure on line 124 in Tests/SentryTests/Protocol/Codable/NSNumberDecodableWrapperTests.swift

View workflow job for this annotation

GitHub Actions / Unit tvOS - Xcode 15.4 - OS 17.5 Sentry

cannot find 'UInt128' in scope
}
""".data(using: .utf8)!

// Act
let actual = try XCTUnwrap(decodeFromJSONData(jsonData: jsonData) as ClassWithNSNumber?)

// Assert
let number = try XCTUnwrap(actual.number)

Check failure on line 132 in Tests/SentryTests/Protocol/Codable/NSNumberDecodableWrapperTests.swift

View workflow job for this annotation

GitHub Actions / Unit iOS - Xcode 15.4 - OS 17.2 SentrySwiftUI

generic parameter 'T' could not be inferred

Check failure on line 132 in Tests/SentryTests/Protocol/Codable/NSNumberDecodableWrapperTests.swift

View workflow job for this annotation

GitHub Actions / Unit iOS - Xcode 15.4 - OS 17.2 Sentry

generic parameter 'T' could not be inferred

Check failure on line 132 in Tests/SentryTests/Protocol/Codable/NSNumberDecodableWrapperTests.swift

View workflow job for this annotation

GitHub Actions / Unit Catalyst - Xcode 15.4 - OS latest Sentry

generic parameter 'T' could not be inferred

Check failure on line 132 in Tests/SentryTests/Protocol/Codable/NSNumberDecodableWrapperTests.swift

View workflow job for this annotation

GitHub Actions / Unit tvOS - Xcode 15.4 - OS 17.5 Sentry

generic parameter 'T' could not be inferred

Check failure on line 132 in Tests/SentryTests/Protocol/Codable/NSNumberDecodableWrapperTests.swift

View workflow job for this annotation

GitHub Actions / Unit macOS - Xcode 15.4 - OS latest Sentry

generic parameter 'T' could not be inferred
XCTAssertEqual(number.doubleValue, Double(UInt128.max))

Check failure on line 133 in Tests/SentryTests/Protocol/Codable/NSNumberDecodableWrapperTests.swift

View workflow job for this annotation

GitHub Actions / Unit iOS - Xcode 15.4 - OS 17.2 SentrySwiftUI

cannot find 'UInt128' in scope

Check failure on line 133 in Tests/SentryTests/Protocol/Codable/NSNumberDecodableWrapperTests.swift

View workflow job for this annotation

GitHub Actions / Unit iOS - Xcode 14.3 - OS 16.4 Sentry

cannot find 'UInt128' in scope

Check failure on line 133 in Tests/SentryTests/Protocol/Codable/NSNumberDecodableWrapperTests.swift

View workflow job for this annotation

GitHub Actions / Unit iOS - Xcode 15.4 - OS 17.2 Sentry

cannot find 'UInt128' in scope

Check failure on line 133 in Tests/SentryTests/Protocol/Codable/NSNumberDecodableWrapperTests.swift

View workflow job for this annotation

GitHub Actions / Unit iOS - Thread Sanitizer

cannot find 'UInt128' in scope

Check failure on line 133 in Tests/SentryTests/Protocol/Codable/NSNumberDecodableWrapperTests.swift

View workflow job for this annotation

GitHub Actions / Unit Catalyst - Xcode 15.4 - OS latest Sentry

cannot find 'UInt128' in scope

Check failure on line 133 in Tests/SentryTests/Protocol/Codable/NSNumberDecodableWrapperTests.swift

View workflow job for this annotation

GitHub Actions / Unit tvOS - Xcode 15.4 - OS 17.5 Sentry

cannot find 'UInt128' in scope

Check failure on line 133 in Tests/SentryTests/Protocol/Codable/NSNumberDecodableWrapperTests.swift

View workflow job for this annotation

GitHub Actions / Unit macOS - Xcode 15.4 - OS latest Sentry

cannot find 'UInt128' in scope
}
}

func testDecode_Zero() throws {
// Arrange
let jsonData = """
{
"number": 0.0
}
""".data(using: .utf8)!

// Act
let actual = try XCTUnwrap(decodeFromJSONData(jsonData: jsonData) as ClassWithNSNumber?)

// Assert
let number = try XCTUnwrap(actual.number)
XCTAssertEqual(number.intValue, 0)
}

func testDecode_Double() throws {
// Arrange
let jsonData = """
{
"number": 0.1
}
""".data(using: .utf8)!

// Act
let actual = try XCTUnwrap(decodeFromJSONData(jsonData: jsonData) as ClassWithNSNumber?)

// Assert
let number = try XCTUnwrap(actual.number)
XCTAssertEqual(number.doubleValue, 0.1)
}

func testDecode_DoubleMax() throws {
// Arrange
let jsonData = """
{
"number": \(Double.greatestFiniteMagnitude)
}
""".data(using: .utf8)!

// Act
let actual = try XCTUnwrap(decodeFromJSONData(jsonData: jsonData) as ClassWithNSNumber?)

// Assert
let number = try XCTUnwrap(actual.number)
XCTAssertEqual(number.doubleValue, Double.greatestFiniteMagnitude)
}

func testDecode_DoubleMin() throws {
// Arrange
let jsonData = """
{
"number": \(Double.leastNormalMagnitude)
}
""".data(using: .utf8)!

// Act
let actual = try XCTUnwrap(decodeFromJSONData(jsonData: jsonData) as ClassWithNSNumber?)

// Assert
let number = try XCTUnwrap(actual.number)
XCTAssertEqual(number.doubleValue, Double.leastNormalMagnitude)
}

func testDecode_Nil() throws {
// Arrange
let jsonData = """
{
"number": null
}
""".data(using: .utf8)!

// Act
let actual = try XCTUnwrap(decodeFromJSONData(jsonData: jsonData) as ClassWithNSNumber?)
XCTAssertNil(actual.number)
}

func testDecode_String() throws {
// Arrange
let jsonData = """
{
"number": "hello"
}
""".data(using: .utf8)!

// Act
let actual = try XCTUnwrap(decodeFromJSONData(jsonData: jsonData) as ClassWithNSNumber?)
XCTAssertNil(actual.number)
}
}

private class ClassWithNSNumber: Decodable {

var number: NSNumber?

enum CodingKeys: String, CodingKey {
case number
}

required convenience public init(from decoder: any Decoder) throws {
self.init()

let container = try decoder.container(keyedBy: CodingKeys.self)
self.number = (try container.decodeIfPresent(NSNumberDecodableWrapper.self, forKey: .number))?.value
}
}
Loading
Loading