From 3c41f61ad3053185ab149143986f069af0000332 Mon Sep 17 00:00:00 2001 From: Serhii Snitsaruk Date: Thu, 7 Aug 2025 11:39:23 +0200 Subject: [PATCH 01/10] feat: Add source code context fields to SentryFrame --- Sources/Sentry/Public/SentryFrame.h | 18 ++++++++++++++++++ Sources/Sentry/SentryFrame.m | 3 +++ 2 files changed, 21 insertions(+) diff --git a/Sources/Sentry/Public/SentryFrame.h b/Sources/Sentry/Public/SentryFrame.h index 64f8ee0d2b5..f88db191a05 100644 --- a/Sources/Sentry/Public/SentryFrame.h +++ b/Sources/Sentry/Public/SentryFrame.h @@ -74,6 +74,24 @@ NS_SWIFT_NAME(Frame) */ @property (nonatomic, copy) NSNumber *_Nullable columnNumber; +/** + * Source code line at the error location. + * Mostly used for Godot errors. + */ +@property (nonatomic, copy) NSString *_Nullable contextLine; + +/** + * Source code lines before the error location (up to 5 lines). + * Mostly used for Godot errors. + */ +@property (nonatomic, copy) NSArray *_Nullable preContext; + +/** + * Source code lines after the error location (up to 5 lines). + * Mostly used for Godot errors. + */ +@property (nonatomic, copy) NSArray *_Nullable postContext; + /** * Determines if the Frame is inApp or not */ diff --git a/Sources/Sentry/SentryFrame.m b/Sources/Sentry/SentryFrame.m index c66ca71dcf0..1cdc2230e5b 100644 --- a/Sources/Sentry/SentryFrame.m +++ b/Sources/Sentry/SentryFrame.m @@ -27,6 +27,9 @@ - (instancetype)init [serializedData setValue:self.imageAddress forKey:@"image_addr"]; [serializedData setValue:self.instructionAddress forKey:@"instruction_addr"]; [serializedData setValue:self.platform forKey:@"platform"]; + [serializedData setValue:self.contextLine forKey:@"context_line"]; + [serializedData setValue:self.preContext forKey:@"pre_context"]; + [serializedData setValue:self.postContext forKey:@"post_context"]; [SentryDictionary setBoolValue:self.inApp forKey:@"in_app" intoDictionary:serializedData]; [SentryDictionary setBoolValue:self.stackStart forKey:@"stack_start" From b8916f323162d3b2d3a67a14d766a0ad33cc6aa4 Mon Sep 17 00:00:00 2001 From: Serhii Snitsaruk Date: Thu, 7 Aug 2025 16:48:09 +0200 Subject: [PATCH 02/10] Add `vars` field to SentryFrame --- Sources/Sentry/Public/SentryFrame.h | 6 ++++++ Sources/Sentry/SentryFrame.m | 2 ++ 2 files changed, 8 insertions(+) diff --git a/Sources/Sentry/Public/SentryFrame.h b/Sources/Sentry/Public/SentryFrame.h index f88db191a05..c459239bbd8 100644 --- a/Sources/Sentry/Public/SentryFrame.h +++ b/Sources/Sentry/Public/SentryFrame.h @@ -102,6 +102,12 @@ NS_SWIFT_NAME(Frame) */ @property (nonatomic, copy) NSNumber *_Nullable stackStart; +/** + * A mapping of variables which were available within this frame. + * Mostly used for Godot errors. + */ +@property (nonatomic, copy) NSDictionary *_Nullable vars; + - (instancetype)init; + (instancetype)new NS_UNAVAILABLE; diff --git a/Sources/Sentry/SentryFrame.m b/Sources/Sentry/SentryFrame.m index 1cdc2230e5b..499a9746050 100644 --- a/Sources/Sentry/SentryFrame.m +++ b/Sources/Sentry/SentryFrame.m @@ -1,5 +1,6 @@ #import "SentryFrame.h" #import "NSMutableDictionary+Sentry.h" +#import "SentryNSDictionarySanitize.h" NS_ASSUME_NONNULL_BEGIN @@ -30,6 +31,7 @@ - (instancetype)init [serializedData setValue:self.contextLine forKey:@"context_line"]; [serializedData setValue:self.preContext forKey:@"pre_context"]; [serializedData setValue:self.postContext forKey:@"post_context"]; + [serializedData setValue:sentry_sanitize(self.vars) forKey:@"vars"]; [SentryDictionary setBoolValue:self.inApp forKey:@"in_app" intoDictionary:serializedData]; [SentryDictionary setBoolValue:self.stackStart forKey:@"stack_start" From aa8ea246397847d4306cb17cf8392771f5b20c8b Mon Sep 17 00:00:00 2001 From: Serhii Snitsaruk Date: Fri, 8 Aug 2025 14:05:26 +0200 Subject: [PATCH 03/10] Add decoding --- .../Swift/Protocol/Codable/SentryFrameCodable.swift | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Sources/Swift/Protocol/Codable/SentryFrameCodable.swift b/Sources/Swift/Protocol/Codable/SentryFrameCodable.swift index fecd572532d..77f5bbdd69b 100644 --- a/Sources/Swift/Protocol/Codable/SentryFrameCodable.swift +++ b/Sources/Swift/Protocol/Codable/SentryFrameCodable.swift @@ -27,6 +27,10 @@ extension FrameDecodable: Decodable { // https://github.com/getsentry/sentry-cocoa/issues/4738 case lineNumber = "lineno" case columnNumber = "colno" + case contextLine = "context_line" + case preContext = "pre_context" + case postContext = "post_context" + case vars case inApp = "in_app" case stackStart = "stack_start" } @@ -51,6 +55,12 @@ extension FrameDecodable: Decodable { 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.contextLine = try container.decodeIfPresent(String.self, forKey: .contextLine) + self.preContext = try container.decodeIfPresent([String].self, forKey: .preContext) + self.postContext = try container.decodeIfPresent([String].self, forKey: .postContext) + self.vars = decodeArbitraryData { + try container.decodeIfPresent([String: ArbitraryData].self, forKey: .vars) + } self.inApp = (try container.decodeIfPresent(NSNumberDecodableWrapper.self, forKey: .inApp))?.value self.stackStart = (try container.decodeIfPresent(NSNumberDecodableWrapper.self, forKey: .stackStart))?.value } From a7a45b28277a3230ad1ece1f97c93499f434412c Mon Sep 17 00:00:00 2001 From: Serhii Snitsaruk Date: Fri, 8 Aug 2025 14:06:08 +0200 Subject: [PATCH 04/10] Add tests for Godot frame and new Frame fields --- .../Protocol/SentryFrameTests.swift | 93 ++++++++++++++----- Tests/SentryTests/Protocol/TestData.swift | 20 ++++ 2 files changed, 88 insertions(+), 25 deletions(-) diff --git a/Tests/SentryTests/Protocol/SentryFrameTests.swift b/Tests/SentryTests/Protocol/SentryFrameTests.swift index 0aaf6c20f46..45b5d112dde 100644 --- a/Tests/SentryTests/Protocol/SentryFrameTests.swift +++ b/Tests/SentryTests/Protocol/SentryFrameTests.swift @@ -11,20 +11,20 @@ class SentryFrameTests: XCTestCase { let actual = frame.serialize() // Assert - XCTAssertEqual(frame.symbolAddress, actual["symbol_addr"] as? String) - XCTAssertEqual(frame.fileName, actual["filename"] as? String) - XCTAssertEqual(frame.function, actual["function"] as? String) - XCTAssertEqual(frame.module, actual["module"] as? String) - XCTAssertEqual(frame.lineNumber, actual["lineno"] as? NSNumber) - XCTAssertEqual(frame.columnNumber, actual["colno"] as? NSNumber) - XCTAssertEqual(frame.package, actual["package"] as? String) - XCTAssertEqual(frame.imageAddress, actual["image_addr"] as? String) - XCTAssertEqual(frame.instructionAddress, actual["instruction_addr"] as? String) - XCTAssertEqual(frame.platform, actual["platform"] as? String) - XCTAssertEqual(frame.inApp, actual["in_app"] as? NSNumber) - XCTAssertEqual(frame.stackStart, actual["stack_start"] as? NSNumber) + assertFrameSerializationMatches(frame: frame, serialized: actual) } - + + func testSerialize_WithGodotFrame() { + // Arrange + let frame = TestData.godotFrame + + // Act + let serialized = frame.serialize() + + // Assert + assertFrameSerializationMatches(frame: frame, serialized: serialized) + } + func testDecode_WithAllProperties() throws { // Arrange let frame = TestData.mainFrame @@ -34,19 +34,21 @@ class SentryFrameTests: XCTestCase { let decoded = try XCTUnwrap(decodeFromJSONData(jsonData: data) as Frame?) // Assert - XCTAssertEqual(frame.symbolAddress, decoded.symbolAddress) - XCTAssertEqual(frame.fileName, decoded.fileName) - XCTAssertEqual(frame.function, decoded.function) - XCTAssertEqual(frame.module, decoded.module) - XCTAssertEqual(frame.lineNumber, decoded.lineNumber) - XCTAssertEqual(frame.columnNumber, decoded.columnNumber) - XCTAssertEqual(frame.package, decoded.package) - XCTAssertEqual(frame.imageAddress, decoded.imageAddress) - XCTAssertEqual(frame.platform, decoded.platform) - XCTAssertEqual(frame.inApp, decoded.inApp) - XCTAssertEqual(frame.stackStart, decoded.stackStart) + assertFrameDecodingMatches(original: frame, decoded: decoded) } - + + func testDecode_WithGodotFrame() throws { + // Arrange + let frame = TestData.godotFrame + let data = try XCTUnwrap(SentrySerialization.data(withJSONObject: frame.serialize())) + + // Act + let decoded = try XCTUnwrap(decodeFromJSONData(jsonData: data) as Frame?) + + // Assert + assertFrameDecodingMatches(original: frame, decoded: decoded) + } + func testDecode_WithAllPropertiesNil() throws { // Arrange let frame = Frame() @@ -66,6 +68,10 @@ class SentryFrameTests: XCTestCase { XCTAssertNil(decoded.imageAddress) XCTAssertNil(decoded.instructionAddress) XCTAssertNil(decoded.platform) + XCTAssertNil(decoded.contextLine) + XCTAssertNil(decoded.preContext) + XCTAssertNil(decoded.postContext) + XCTAssertNil(decoded.vars) XCTAssertNil(decoded.inApp) XCTAssertNil(decoded.stackStart) } @@ -74,4 +80,41 @@ class SentryFrameTests: XCTestCase { SentryBooleanSerialization.test(Frame(), property: "inApp", serializedProperty: "in_app") SentryBooleanSerialization.test(Frame(), property: "stackStart", serializedProperty: "stack_start") } + + private func assertFrameSerializationMatches(frame: Frame, serialized: [String: Any]) { + XCTAssertEqual(frame.symbolAddress, serialized["symbol_addr"] as? String) + XCTAssertEqual(frame.fileName, serialized["filename"] as? String) + XCTAssertEqual(frame.function, serialized["function"] as? String) + XCTAssertEqual(frame.module, serialized["module"] as? String) + XCTAssertEqual(frame.lineNumber, serialized["lineno"] as? NSNumber) + XCTAssertEqual(frame.columnNumber, serialized["colno"] as? NSNumber) + XCTAssertEqual(frame.package, serialized["package"] as? String) + XCTAssertEqual(frame.imageAddress, serialized["image_addr"] as? String) + XCTAssertEqual(frame.instructionAddress, serialized["instruction_addr"] as? String) + XCTAssertEqual(frame.platform, serialized["platform"] as? String) + XCTAssertEqual(frame.contextLine, serialized["context_line"] as? String) + XCTAssertEqual(frame.preContext, serialized["pre_context"] as? [String]) + XCTAssertEqual(frame.postContext, serialized["post_context"] as? [String]) + XCTAssertEqual(frame.vars as? [String: AnyHashable], serialized["vars"] as? [String: AnyHashable]) + XCTAssertEqual(frame.inApp, serialized["in_app"] as? NSNumber) + XCTAssertEqual(frame.stackStart, serialized["stack_start"] as? NSNumber) + } + + private func assertFrameDecodingMatches(original: Frame, decoded: Frame) { + XCTAssertEqual(original.symbolAddress, decoded.symbolAddress) + XCTAssertEqual(original.fileName, decoded.fileName) + XCTAssertEqual(original.function, decoded.function) + XCTAssertEqual(original.module, decoded.module) + XCTAssertEqual(original.lineNumber, decoded.lineNumber) + XCTAssertEqual(original.columnNumber, decoded.columnNumber) + XCTAssertEqual(original.package, decoded.package) + XCTAssertEqual(original.imageAddress, decoded.imageAddress) + XCTAssertEqual(original.platform, decoded.platform) + XCTAssertEqual(original.contextLine, decoded.contextLine) + XCTAssertEqual(original.preContext, decoded.preContext) + XCTAssertEqual(original.postContext, decoded.postContext) + XCTAssertEqual(original.vars as? [String: AnyHashable], decoded.vars as? [String: AnyHashable]) + XCTAssertEqual(original.inApp, decoded.inApp) + XCTAssertEqual(original.stackStart, decoded.stackStart) + } } diff --git a/Tests/SentryTests/Protocol/TestData.swift b/Tests/SentryTests/Protocol/TestData.swift index cabf07b41ea..44f4670a633 100644 --- a/Tests/SentryTests/Protocol/TestData.swift +++ b/Tests/SentryTests/Protocol/TestData.swift @@ -204,6 +204,26 @@ class TestData { return frame } + static var godotFrame: Frame { + let frame = Frame() + frame.fileName = "player/player.gd" + frame.function = "take_damage" + frame.lineNumber = 42 + frame.columnNumber = 15 + frame.platform = "gdscript" + frame.inApp = true + frame.contextLine = " health -= damage" + frame.preContext = [ + "func take_damage(damage):", + " if damage <= 0:", + " return"] + frame.postContext = [ + " if health <= 0:", + " die()"] + frame.vars = ["damage": 25, "health": 75, "player_name": "Hero"] + return frame + } + static var outsideFrame: Frame { let frame = Frame() frame.columnNumber = 1 From 1d32c2a03d952c07f229303fdfc2c84410bc421b Mon Sep 17 00:00:00 2001 From: Serhii Snitsaruk Date: Fri, 8 Aug 2025 14:39:29 +0200 Subject: [PATCH 05/10] Update CHANGELOG.md --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 14a76723d3f..757791a7e86 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Features + +- Add source context and vars fields to SentryFrame ([#5853](https://github.com/getsentry/sentry-cocoa/pull/5853)) + ### Fixes - Add support for PDFKit views in session replay (#5750) From 14a952466d5aaabe92248887b8486dff49b49f9b Mon Sep 17 00:00:00 2001 From: Serhii Snitsaruk Date: Fri, 8 Aug 2025 14:39:49 +0200 Subject: [PATCH 06/10] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 757791a7e86..cafcbb18af2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ ### Features -- Add source context and vars fields to SentryFrame ([#5853](https://github.com/getsentry/sentry-cocoa/pull/5853)) +- Add source context and vars fields to SentryFrame (#5853) ### Fixes From f2879876e620b084e762784c63791044fc0f0b29 Mon Sep 17 00:00:00 2001 From: Serhii Snitsaruk Date: Fri, 8 Aug 2025 15:03:09 +0200 Subject: [PATCH 07/10] Update API json --- sdk_api.json | 515 ++++++++++++++++++++++++++++++++++++++++++++++++ sdk_api_V9.json | 515 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1030 insertions(+) diff --git a/sdk_api.json b/sdk_api.json index 8e2d713c41d..7c984ac549c 100644 --- a/sdk_api.json +++ b/sdk_api.json @@ -9439,6 +9439,375 @@ } ] }, + { + "kind": "Var", + "name": "contextLine", + "printedName": "contextLine", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.String?", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Var", + "usr": "c:objc(cs)SentryFrame(py)contextLine", + "moduleName": "Sentry", + "isOpen": true, + "objc_name": "contextLine", + "declAttributes": [ + "ObjC", + "Dynamic" + ], + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.String?", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "c:objc(cs)SentryFrame(im)contextLine", + "moduleName": "Sentry", + "isOpen": true, + "objc_name": "contextLine", + "declAttributes": [ + "DiscardableResult", + "ObjC", + "Dynamic" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNameAlias", + "name": "Void", + "printedName": "Swift.Void", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ] + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.String?", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "c:objc(cs)SentryFrame(im)setContextLine:", + "moduleName": "Sentry", + "isOpen": true, + "objc_name": "setContextLine:", + "declAttributes": [ + "ObjC", + "Dynamic" + ], + "accessorKind": "set" + } + ] + }, + { + "kind": "Var", + "name": "preContext", + "printedName": "preContext", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "[Swift.String]?", + "children": [ + { + "kind": "TypeNominal", + "name": "Array", + "printedName": "[Swift.String]", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:Sa" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Var", + "usr": "c:objc(cs)SentryFrame(py)preContext", + "moduleName": "Sentry", + "isOpen": true, + "objc_name": "preContext", + "declAttributes": [ + "ObjC", + "Dynamic" + ], + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "[Swift.String]?", + "children": [ + { + "kind": "TypeNominal", + "name": "Array", + "printedName": "[Swift.String]", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:Sa" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "c:objc(cs)SentryFrame(im)preContext", + "moduleName": "Sentry", + "isOpen": true, + "objc_name": "preContext", + "declAttributes": [ + "DiscardableResult", + "ObjC", + "Dynamic" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNameAlias", + "name": "Void", + "printedName": "Swift.Void", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ] + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "[Swift.String]?", + "children": [ + { + "kind": "TypeNominal", + "name": "Array", + "printedName": "[Swift.String]", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:Sa" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "c:objc(cs)SentryFrame(im)setPreContext:", + "moduleName": "Sentry", + "isOpen": true, + "objc_name": "setPreContext:", + "declAttributes": [ + "ObjC", + "Dynamic" + ], + "accessorKind": "set" + } + ] + }, + { + "kind": "Var", + "name": "postContext", + "printedName": "postContext", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "[Swift.String]?", + "children": [ + { + "kind": "TypeNominal", + "name": "Array", + "printedName": "[Swift.String]", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:Sa" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Var", + "usr": "c:objc(cs)SentryFrame(py)postContext", + "moduleName": "Sentry", + "isOpen": true, + "objc_name": "postContext", + "declAttributes": [ + "ObjC", + "Dynamic" + ], + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "[Swift.String]?", + "children": [ + { + "kind": "TypeNominal", + "name": "Array", + "printedName": "[Swift.String]", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:Sa" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "c:objc(cs)SentryFrame(im)postContext", + "moduleName": "Sentry", + "isOpen": true, + "objc_name": "postContext", + "declAttributes": [ + "DiscardableResult", + "ObjC", + "Dynamic" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNameAlias", + "name": "Void", + "printedName": "Swift.Void", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ] + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "[Swift.String]?", + "children": [ + { + "kind": "TypeNominal", + "name": "Array", + "printedName": "[Swift.String]", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:Sa" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "c:objc(cs)SentryFrame(im)setPostContext:", + "moduleName": "Sentry", + "isOpen": true, + "objc_name": "setPostContext:", + "declAttributes": [ + "ObjC", + "Dynamic" + ], + "accessorKind": "set" + } + ] + }, { "kind": "Var", "name": "inApp", @@ -9655,6 +10024,152 @@ } ] }, + { + "kind": "Var", + "name": "vars", + "printedName": "vars", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "[Swift.String : Any]?", + "children": [ + { + "kind": "TypeNominal", + "name": "Dictionary", + "printedName": "[Swift.String : Any]", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + }, + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "Any" + } + ], + "usr": "s:SD" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Var", + "usr": "c:objc(cs)SentryFrame(py)vars", + "moduleName": "Sentry", + "isOpen": true, + "objc_name": "vars", + "declAttributes": [ + "ObjC", + "Dynamic" + ], + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "[Swift.String : Any]?", + "children": [ + { + "kind": "TypeNominal", + "name": "Dictionary", + "printedName": "[Swift.String : Any]", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + }, + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "Any" + } + ], + "usr": "s:SD" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "c:objc(cs)SentryFrame(im)vars", + "moduleName": "Sentry", + "isOpen": true, + "objc_name": "vars", + "declAttributes": [ + "DiscardableResult", + "ObjC", + "Dynamic" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNameAlias", + "name": "Void", + "printedName": "Swift.Void", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ] + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "[Swift.String : Any]?", + "children": [ + { + "kind": "TypeNominal", + "name": "Dictionary", + "printedName": "[Swift.String : Any]", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + }, + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "Any" + } + ], + "usr": "s:SD" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "c:objc(cs)SentryFrame(im)setVars:", + "moduleName": "Sentry", + "isOpen": true, + "objc_name": "setVars:", + "declAttributes": [ + "ObjC", + "Dynamic" + ], + "accessorKind": "set" + } + ] + }, { "kind": "Constructor", "name": "init", diff --git a/sdk_api_V9.json b/sdk_api_V9.json index 9278c46aead..97ce0fc4056 100644 --- a/sdk_api_V9.json +++ b/sdk_api_V9.json @@ -9182,6 +9182,375 @@ } ] }, + { + "kind": "Var", + "name": "contextLine", + "printedName": "contextLine", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.String?", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Var", + "usr": "c:objc(cs)SentryFrame(py)contextLine", + "moduleName": "Sentry", + "isOpen": true, + "objc_name": "contextLine", + "declAttributes": [ + "ObjC", + "Dynamic" + ], + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.String?", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "c:objc(cs)SentryFrame(im)contextLine", + "moduleName": "Sentry", + "isOpen": true, + "objc_name": "contextLine", + "declAttributes": [ + "DiscardableResult", + "ObjC", + "Dynamic" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNameAlias", + "name": "Void", + "printedName": "Swift.Void", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ] + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.String?", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "c:objc(cs)SentryFrame(im)setContextLine:", + "moduleName": "Sentry", + "isOpen": true, + "objc_name": "setContextLine:", + "declAttributes": [ + "ObjC", + "Dynamic" + ], + "accessorKind": "set" + } + ] + }, + { + "kind": "Var", + "name": "preContext", + "printedName": "preContext", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "[Swift.String]?", + "children": [ + { + "kind": "TypeNominal", + "name": "Array", + "printedName": "[Swift.String]", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:Sa" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Var", + "usr": "c:objc(cs)SentryFrame(py)preContext", + "moduleName": "Sentry", + "isOpen": true, + "objc_name": "preContext", + "declAttributes": [ + "ObjC", + "Dynamic" + ], + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "[Swift.String]?", + "children": [ + { + "kind": "TypeNominal", + "name": "Array", + "printedName": "[Swift.String]", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:Sa" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "c:objc(cs)SentryFrame(im)preContext", + "moduleName": "Sentry", + "isOpen": true, + "objc_name": "preContext", + "declAttributes": [ + "DiscardableResult", + "ObjC", + "Dynamic" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNameAlias", + "name": "Void", + "printedName": "Swift.Void", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ] + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "[Swift.String]?", + "children": [ + { + "kind": "TypeNominal", + "name": "Array", + "printedName": "[Swift.String]", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:Sa" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "c:objc(cs)SentryFrame(im)setPreContext:", + "moduleName": "Sentry", + "isOpen": true, + "objc_name": "setPreContext:", + "declAttributes": [ + "ObjC", + "Dynamic" + ], + "accessorKind": "set" + } + ] + }, + { + "kind": "Var", + "name": "postContext", + "printedName": "postContext", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "[Swift.String]?", + "children": [ + { + "kind": "TypeNominal", + "name": "Array", + "printedName": "[Swift.String]", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:Sa" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Var", + "usr": "c:objc(cs)SentryFrame(py)postContext", + "moduleName": "Sentry", + "isOpen": true, + "objc_name": "postContext", + "declAttributes": [ + "ObjC", + "Dynamic" + ], + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "[Swift.String]?", + "children": [ + { + "kind": "TypeNominal", + "name": "Array", + "printedName": "[Swift.String]", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:Sa" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "c:objc(cs)SentryFrame(im)postContext", + "moduleName": "Sentry", + "isOpen": true, + "objc_name": "postContext", + "declAttributes": [ + "DiscardableResult", + "ObjC", + "Dynamic" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNameAlias", + "name": "Void", + "printedName": "Swift.Void", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ] + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "[Swift.String]?", + "children": [ + { + "kind": "TypeNominal", + "name": "Array", + "printedName": "[Swift.String]", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:Sa" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "c:objc(cs)SentryFrame(im)setPostContext:", + "moduleName": "Sentry", + "isOpen": true, + "objc_name": "setPostContext:", + "declAttributes": [ + "ObjC", + "Dynamic" + ], + "accessorKind": "set" + } + ] + }, { "kind": "Var", "name": "inApp", @@ -9398,6 +9767,152 @@ } ] }, + { + "kind": "Var", + "name": "vars", + "printedName": "vars", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "[Swift.String : Any]?", + "children": [ + { + "kind": "TypeNominal", + "name": "Dictionary", + "printedName": "[Swift.String : Any]", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + }, + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "Any" + } + ], + "usr": "s:SD" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Var", + "usr": "c:objc(cs)SentryFrame(py)vars", + "moduleName": "Sentry", + "isOpen": true, + "objc_name": "vars", + "declAttributes": [ + "ObjC", + "Dynamic" + ], + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "[Swift.String : Any]?", + "children": [ + { + "kind": "TypeNominal", + "name": "Dictionary", + "printedName": "[Swift.String : Any]", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + }, + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "Any" + } + ], + "usr": "s:SD" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "c:objc(cs)SentryFrame(im)vars", + "moduleName": "Sentry", + "isOpen": true, + "objc_name": "vars", + "declAttributes": [ + "DiscardableResult", + "ObjC", + "Dynamic" + ], + "accessorKind": "get" + }, + { + "kind": "Accessor", + "name": "Set", + "printedName": "Set()", + "children": [ + { + "kind": "TypeNameAlias", + "name": "Void", + "printedName": "Swift.Void", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ] + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "[Swift.String : Any]?", + "children": [ + { + "kind": "TypeNominal", + "name": "Dictionary", + "printedName": "[Swift.String : Any]", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + }, + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "Any" + } + ], + "usr": "s:SD" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "c:objc(cs)SentryFrame(im)setVars:", + "moduleName": "Sentry", + "isOpen": true, + "objc_name": "setVars:", + "declAttributes": [ + "ObjC", + "Dynamic" + ], + "accessorKind": "set" + } + ] + }, { "kind": "Constructor", "name": "init", From 158d1e07c093b33ddff715ab6e7feb3a9e4a5adb Mon Sep 17 00:00:00 2001 From: Serhii Snitsaruk Date: Tue, 12 Aug 2025 12:19:48 +0200 Subject: [PATCH 08/10] Add instructionAddress to frame decoding test --- Tests/SentryTests/Protocol/SentryFrameTests.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Tests/SentryTests/Protocol/SentryFrameTests.swift b/Tests/SentryTests/Protocol/SentryFrameTests.swift index 45b5d112dde..560ddac981c 100644 --- a/Tests/SentryTests/Protocol/SentryFrameTests.swift +++ b/Tests/SentryTests/Protocol/SentryFrameTests.swift @@ -109,6 +109,7 @@ class SentryFrameTests: XCTestCase { XCTAssertEqual(original.columnNumber, decoded.columnNumber) XCTAssertEqual(original.package, decoded.package) XCTAssertEqual(original.imageAddress, decoded.imageAddress) + XCTAssertEqual(original.instructionAddress, decoded.instructionAddress) XCTAssertEqual(original.platform, decoded.platform) XCTAssertEqual(original.contextLine, decoded.contextLine) XCTAssertEqual(original.preContext, decoded.preContext) From a33afc3a3d95b7487ed179f9f1577655eac2337e Mon Sep 17 00:00:00 2001 From: Serhii Snitsaruk Date: Tue, 12 Aug 2025 15:20:22 +0200 Subject: [PATCH 09/10] Reverse DRY in tests --- .../Protocol/SentryFrameTests.swift | 110 +++++++++++------- 1 file changed, 66 insertions(+), 44 deletions(-) diff --git a/Tests/SentryTests/Protocol/SentryFrameTests.swift b/Tests/SentryTests/Protocol/SentryFrameTests.swift index 560ddac981c..97ae035ee40 100644 --- a/Tests/SentryTests/Protocol/SentryFrameTests.swift +++ b/Tests/SentryTests/Protocol/SentryFrameTests.swift @@ -11,7 +11,22 @@ class SentryFrameTests: XCTestCase { let actual = frame.serialize() // Assert - assertFrameSerializationMatches(frame: frame, serialized: actual) + XCTAssertEqual(frame.symbolAddress, actual["symbol_addr"] as? String) + XCTAssertEqual(frame.fileName, actual["filename"] as? String) + XCTAssertEqual(frame.function, actual["function"] as? String) + XCTAssertEqual(frame.module, actual["module"] as? String) + XCTAssertEqual(frame.lineNumber, actual["lineno"] as? NSNumber) + XCTAssertEqual(frame.columnNumber, actual["colno"] as? NSNumber) + XCTAssertEqual(frame.package, actual["package"] as? String) + XCTAssertEqual(frame.imageAddress, actual["image_addr"] as? String) + XCTAssertEqual(frame.instructionAddress, actual["instruction_addr"] as? String) + XCTAssertEqual(frame.platform, actual["platform"] as? String) + XCTAssertEqual(frame.contextLine, actual["context_line"] as? String) + XCTAssertEqual(frame.preContext, actual["pre_context"] as? [String]) + XCTAssertEqual(frame.postContext, actual["post_context"] as? [String]) + XCTAssertEqual(frame.vars as? [String: AnyHashable], actual["vars"] as? [String: AnyHashable]) + XCTAssertEqual(frame.inApp, actual["in_app"] as? NSNumber) + XCTAssertEqual(frame.stackStart, actual["stack_start"] as? NSNumber) } func testSerialize_WithGodotFrame() { @@ -19,10 +34,25 @@ class SentryFrameTests: XCTestCase { let frame = TestData.godotFrame // Act - let serialized = frame.serialize() + let actual = frame.serialize() // Assert - assertFrameSerializationMatches(frame: frame, serialized: serialized) + XCTAssertEqual(frame.symbolAddress, actual["symbol_addr"] as? String) + XCTAssertEqual(frame.fileName, actual["filename"] as? String) + XCTAssertEqual(frame.function, actual["function"] as? String) + XCTAssertEqual(frame.module, actual["module"] as? String) + XCTAssertEqual(frame.lineNumber, actual["lineno"] as? NSNumber) + XCTAssertEqual(frame.columnNumber, actual["colno"] as? NSNumber) + XCTAssertEqual(frame.package, actual["package"] as? String) + XCTAssertEqual(frame.imageAddress, actual["image_addr"] as? String) + XCTAssertEqual(frame.instructionAddress, actual["instruction_addr"] as? String) + XCTAssertEqual(frame.platform, actual["platform"] as? String) + XCTAssertEqual(frame.contextLine, actual["context_line"] as? String) + XCTAssertEqual(frame.preContext, actual["pre_context"] as? [String]) + XCTAssertEqual(frame.postContext, actual["post_context"] as? [String]) + XCTAssertEqual(frame.vars as? [String: AnyHashable], actual["vars"] as? [String: AnyHashable]) + XCTAssertEqual(frame.inApp, actual["in_app"] as? NSNumber) + XCTAssertEqual(frame.stackStart, actual["stack_start"] as? NSNumber) } func testDecode_WithAllProperties() throws { @@ -34,7 +64,22 @@ class SentryFrameTests: XCTestCase { let decoded = try XCTUnwrap(decodeFromJSONData(jsonData: data) as Frame?) // Assert - assertFrameDecodingMatches(original: frame, decoded: decoded) + XCTAssertEqual(frame.symbolAddress, decoded.symbolAddress) + XCTAssertEqual(frame.fileName, decoded.fileName) + XCTAssertEqual(frame.function, decoded.function) + XCTAssertEqual(frame.module, decoded.module) + XCTAssertEqual(frame.lineNumber, decoded.lineNumber) + XCTAssertEqual(frame.columnNumber, decoded.columnNumber) + XCTAssertEqual(frame.package, decoded.package) + XCTAssertEqual(frame.imageAddress, decoded.imageAddress) + XCTAssertEqual(frame.instructionAddress, decoded.instructionAddress) + XCTAssertEqual(frame.platform, decoded.platform) + XCTAssertEqual(frame.contextLine, decoded.contextLine) + XCTAssertEqual(frame.preContext, decoded.preContext) + XCTAssertEqual(frame.postContext, decoded.postContext) + XCTAssertEqual(frame.vars as? [String: AnyHashable], decoded.vars as? [String: AnyHashable]) + XCTAssertEqual(frame.inApp, decoded.inApp) + XCTAssertEqual(frame.stackStart, decoded.stackStart) } func testDecode_WithGodotFrame() throws { @@ -46,7 +91,22 @@ class SentryFrameTests: XCTestCase { let decoded = try XCTUnwrap(decodeFromJSONData(jsonData: data) as Frame?) // Assert - assertFrameDecodingMatches(original: frame, decoded: decoded) + XCTAssertEqual(frame.symbolAddress, decoded.symbolAddress) + XCTAssertEqual(frame.fileName, decoded.fileName) + XCTAssertEqual(frame.function, decoded.function) + XCTAssertEqual(frame.module, decoded.module) + XCTAssertEqual(frame.lineNumber, decoded.lineNumber) + XCTAssertEqual(frame.columnNumber, decoded.columnNumber) + XCTAssertEqual(frame.package, decoded.package) + XCTAssertEqual(frame.imageAddress, decoded.imageAddress) + XCTAssertEqual(frame.instructionAddress, decoded.instructionAddress) + XCTAssertEqual(frame.platform, decoded.platform) + XCTAssertEqual(frame.contextLine, decoded.contextLine) + XCTAssertEqual(frame.preContext, decoded.preContext) + XCTAssertEqual(frame.postContext, decoded.postContext) + XCTAssertEqual(frame.vars as? [String: AnyHashable], decoded.vars as? [String: AnyHashable]) + XCTAssertEqual(frame.inApp, decoded.inApp) + XCTAssertEqual(frame.stackStart, decoded.stackStart) } func testDecode_WithAllPropertiesNil() throws { @@ -79,43 +139,5 @@ class SentryFrameTests: XCTestCase { func testSerialize_Bools() { SentryBooleanSerialization.test(Frame(), property: "inApp", serializedProperty: "in_app") SentryBooleanSerialization.test(Frame(), property: "stackStart", serializedProperty: "stack_start") - } - - private func assertFrameSerializationMatches(frame: Frame, serialized: [String: Any]) { - XCTAssertEqual(frame.symbolAddress, serialized["symbol_addr"] as? String) - XCTAssertEqual(frame.fileName, serialized["filename"] as? String) - XCTAssertEqual(frame.function, serialized["function"] as? String) - XCTAssertEqual(frame.module, serialized["module"] as? String) - XCTAssertEqual(frame.lineNumber, serialized["lineno"] as? NSNumber) - XCTAssertEqual(frame.columnNumber, serialized["colno"] as? NSNumber) - XCTAssertEqual(frame.package, serialized["package"] as? String) - XCTAssertEqual(frame.imageAddress, serialized["image_addr"] as? String) - XCTAssertEqual(frame.instructionAddress, serialized["instruction_addr"] as? String) - XCTAssertEqual(frame.platform, serialized["platform"] as? String) - XCTAssertEqual(frame.contextLine, serialized["context_line"] as? String) - XCTAssertEqual(frame.preContext, serialized["pre_context"] as? [String]) - XCTAssertEqual(frame.postContext, serialized["post_context"] as? [String]) - XCTAssertEqual(frame.vars as? [String: AnyHashable], serialized["vars"] as? [String: AnyHashable]) - XCTAssertEqual(frame.inApp, serialized["in_app"] as? NSNumber) - XCTAssertEqual(frame.stackStart, serialized["stack_start"] as? NSNumber) - } - - private func assertFrameDecodingMatches(original: Frame, decoded: Frame) { - XCTAssertEqual(original.symbolAddress, decoded.symbolAddress) - XCTAssertEqual(original.fileName, decoded.fileName) - XCTAssertEqual(original.function, decoded.function) - XCTAssertEqual(original.module, decoded.module) - XCTAssertEqual(original.lineNumber, decoded.lineNumber) - XCTAssertEqual(original.columnNumber, decoded.columnNumber) - XCTAssertEqual(original.package, decoded.package) - XCTAssertEqual(original.imageAddress, decoded.imageAddress) - XCTAssertEqual(original.instructionAddress, decoded.instructionAddress) - XCTAssertEqual(original.platform, decoded.platform) - XCTAssertEqual(original.contextLine, decoded.contextLine) - XCTAssertEqual(original.preContext, decoded.preContext) - XCTAssertEqual(original.postContext, decoded.postContext) - XCTAssertEqual(original.vars as? [String: AnyHashable], decoded.vars as? [String: AnyHashable]) - XCTAssertEqual(original.inApp, decoded.inApp) - XCTAssertEqual(original.stackStart, decoded.stackStart) - } + } } From 39664d5edeff5c7e80636622938940e1f9095bf4 Mon Sep 17 00:00:00 2001 From: Serhii Snitsaruk Date: Wed, 13 Aug 2025 08:52:28 +0200 Subject: [PATCH 10/10] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 497892440fe..3908195fc8e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,8 +4,8 @@ ### Features -- Add source context and vars fields to SentryFrame (#5853) - Structured Logs: Flush logs on SDK flush/close (#5834) +- Add source context and vars fields to SentryFrame (#5853) ## 8.54.1-alpha.1