Skip to content

Commit 795dd39

Browse files
authored
refactor: Add null-handling and tests for array sanitizing (#5738)
1 parent ebc72be commit 795dd39

File tree

7 files changed

+205
-11
lines changed

7 files changed

+205
-11
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## Unreleased
44

5+
### Fixes
6+
7+
- Add null-handling for internal array sanitization (#5722)
8+
59
### Features
610

711
- Add experimental support for capturing structured logs via `SentrySDK.logger` (#5532, #5593, #5639, #5628, #5637)

Sentry.xcodeproj/project.pbxproj

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -739,8 +739,8 @@
739739
84F2A1CE2E06001300A94524 /* SentryApplaunchProfilingMalformedConfigFileTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F2A1CD2E06001300A94524 /* SentryApplaunchProfilingMalformedConfigFileTests.swift */; };
740740
84F994E62A6894B500EC0190 /* CoreData.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84F994E52A6894B500EC0190 /* CoreData.framework */; };
741741
84F994E82A6894BD00EC0190 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84F994E72A6894BD00EC0190 /* SystemConfiguration.framework */; platformFilters = (ios, maccatalyst, macos, tvos, ); };
742-
861265F92404EC1500C4AFDE /* NSArray+SentrySanitize.h in Headers */ = {isa = PBXBuildFile; fileRef = 861265F72404EC1500C4AFDE /* NSArray+SentrySanitize.h */; };
743-
861265FA2404EC1500C4AFDE /* NSArray+SentrySanitize.m in Sources */ = {isa = PBXBuildFile; fileRef = 861265F82404EC1500C4AFDE /* NSArray+SentrySanitize.m */; };
742+
861265F92404EC1500C4AFDE /* SentryArray.h in Headers */ = {isa = PBXBuildFile; fileRef = 861265F72404EC1500C4AFDE /* SentryArray.h */; };
743+
861265FA2404EC1500C4AFDE /* SentryArray.m in Sources */ = {isa = PBXBuildFile; fileRef = 861265F82404EC1500C4AFDE /* SentryArray.m */; };
744744
8E0551E026A7A63C00400526 /* TestProtocolClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E0551DF26A7A63C00400526 /* TestProtocolClient.swift */; };
745745
8E133FA225E72DEF00ABD0BF /* SentrySamplingContext.m in Sources */ = {isa = PBXBuildFile; fileRef = 8E133FA025E72DEF00ABD0BF /* SentrySamplingContext.m */; };
746746
8E133FA625E72EB400ABD0BF /* SentrySamplingContext.h in Headers */ = {isa = PBXBuildFile; fileRef = 8E133FA525E72EB400ABD0BF /* SentrySamplingContext.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -874,6 +874,7 @@
874874
D4AF00252D2E93C400F5F3D7 /* SentryNSFileManagerSwizzlingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = D4AF00242D2E93C400F5F3D7 /* SentryNSFileManagerSwizzlingTests.m */; };
875875
D4B0DC7F2DA9257A00DE61B6 /* SentryRenderVideoResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4B0DC7E2DA9257200DE61B6 /* SentryRenderVideoResult.swift */; };
876876
D4C5F59A2D4249E6002A9BF6 /* DataSentryTracingIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4C5F5992D4249E0002A9BF6 /* DataSentryTracingIntegrationTests.swift */; };
877+
D4CA34832E378C9900E92A61 /* SentryArrayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4CA34822E378C9000E92A61 /* SentryArrayTests.swift */; };
877878
D4CBA2472DE06D0200581618 /* libSentryTestUtils.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 8431F00A29B284F200D8DC56 /* libSentryTestUtils.a */; };
878879
D4CBA2532DE06D1600581618 /* TestConstantTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4CBA2512DE06D1600581618 /* TestConstantTests.swift */; };
879880
D4CD2A802DE9F91900DA9F59 /* SentryRedactRegion.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4CD2A7C2DE9F91900DA9F59 /* SentryRedactRegion.swift */; };
@@ -2048,8 +2049,8 @@
20482049
84F2A1CD2E06001300A94524 /* SentryApplaunchProfilingMalformedConfigFileTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryApplaunchProfilingMalformedConfigFileTests.swift; sourceTree = "<group>"; };
20492050
84F994E52A6894B500EC0190 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX13.3.sdk/System/Library/Frameworks/CoreData.framework; sourceTree = DEVELOPER_DIR; };
20502051
84F994E72A6894BD00EC0190 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX13.3.sdk/System/Library/Frameworks/SystemConfiguration.framework; sourceTree = DEVELOPER_DIR; };
2051-
861265F72404EC1500C4AFDE /* NSArray+SentrySanitize.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "NSArray+SentrySanitize.h"; path = "include/NSArray+SentrySanitize.h"; sourceTree = "<group>"; };
2052-
861265F82404EC1500C4AFDE /* NSArray+SentrySanitize.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSArray+SentrySanitize.m"; sourceTree = "<group>"; };
2052+
861265F72404EC1500C4AFDE /* SentryArray.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryArray.h; path = include/SentryArray.h; sourceTree = "<group>"; };
2053+
861265F82404EC1500C4AFDE /* SentryArray.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryArray.m; sourceTree = "<group>"; };
20532054
8E0551DF26A7A63C00400526 /* TestProtocolClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestProtocolClient.swift; sourceTree = "<group>"; };
20542055
8E133FA025E72DEF00ABD0BF /* SentrySamplingContext.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentrySamplingContext.m; sourceTree = "<group>"; };
20552056
8E133FA525E72EB400ABD0BF /* SentrySamplingContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SentrySamplingContext.h; path = Public/SentrySamplingContext.h; sourceTree = "<group>"; };
@@ -2196,6 +2197,7 @@
21962197
D4B0DC7E2DA9257200DE61B6 /* SentryRenderVideoResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryRenderVideoResult.swift; sourceTree = "<group>"; };
21972198
D4BCA0C22DA93C25009E49AB /* SentrySessionReplayIntegration+Test.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SentrySessionReplayIntegration+Test.h"; sourceTree = "<group>"; };
21982199
D4C5F5992D4249E0002A9BF6 /* DataSentryTracingIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataSentryTracingIntegrationTests.swift; sourceTree = "<group>"; };
2200+
D4CA34822E378C9000E92A61 /* SentryArrayTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryArrayTests.swift; sourceTree = "<group>"; };
21992201
D4CBA2432DE06D0200581618 /* SentryTestUtilsTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SentryTestUtilsTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
22002202
D4CBA2512DE06D1600581618 /* TestConstantTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestConstantTests.swift; sourceTree = "<group>"; };
22012203
D4CD2A7C2DE9F91900DA9F59 /* SentryRedactRegion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryRedactRegion.swift; sourceTree = "<group>"; };
@@ -2736,8 +2738,8 @@
27362738
63295AF41EF3C7DB002D4490 /* SentryNSDictionarySanitize.m */,
27372739
7B0DC72D288698F70039995F /* NSMutableDictionary+Sentry.h */,
27382740
7B0DC72E288698F70039995F /* NSMutableDictionary+Sentry.m */,
2739-
861265F72404EC1500C4AFDE /* NSArray+SentrySanitize.h */,
2740-
861265F82404EC1500C4AFDE /* NSArray+SentrySanitize.m */,
2741+
861265F72404EC1500C4AFDE /* SentryArray.h */,
2742+
861265F82404EC1500C4AFDE /* SentryArray.m */,
27412743
7B6438A826A70F24000D0F65 /* UIViewController+Sentry.h */,
27422744
7B6438A926A70F24000D0F65 /* UIViewController+Sentry.m */,
27432745
0A2D8D9728997887008720F6 /* NSLocale+Sentry.h */,
@@ -3433,6 +3435,7 @@
34333435
7B6438AD26A710E6000D0F65 /* Categories */ = {
34343436
isa = PBXGroup;
34353437
children = (
3438+
D4CA34822E378C9000E92A61 /* SentryArrayTests.swift */,
34363439
D41909922D48FFF6002B83D0 /* SentryNSDictionarySanitize+Tests.h */,
34373440
D41909942D490006002B83D0 /* SentryNSDictionarySanitize+Tests.m */,
34383441
D42E48582D48FC8F00D251BC /* SentryNSDictionarySanitizeTests.swift */,
@@ -5009,7 +5012,7 @@
50095012
7BBD18912449BE9000427C76 /* SentryDefaultRateLimits.h in Headers */,
50105013
7B8713AE26415ADF006D6004 /* SentryAppStartTrackingIntegration.h in Headers */,
50115014
7B7D873224864BB900D2ECFF /* SentryCrashMachineContextWrapper.h in Headers */,
5012-
861265F92404EC1500C4AFDE /* NSArray+SentrySanitize.h in Headers */,
5015+
861265F92404EC1500C4AFDE /* SentryArray.h in Headers */,
50135016
63FE712320DA4C1000CDBAE8 /* SentryCrashID.h in Headers */,
50145017
7DC27EC523997EB7006998B5 /* SentryAutoBreadcrumbTrackingIntegration.h in Headers */,
50155018
63FE707F20DA4C1000CDBAE8 /* SentryCrashVarArgs.h in Headers */,
@@ -5897,7 +5900,7 @@
58975900
8453421228BE855D00C22EEC /* SentrySampleDecision.m in Sources */,
58985901
FA8E58F12E0AD4270049F69D /* SentryDispatchQueueWrapper.swift in Sources */,
58995902
7B7D872E2486482600D2ECFF /* SentryStacktraceBuilder.m in Sources */,
5900-
861265FA2404EC1500C4AFDE /* NSArray+SentrySanitize.m in Sources */,
5903+
861265FA2404EC1500C4AFDE /* SentryArray.m in Sources */,
59015904
D802994E2BA836EF000F0081 /* SentryOnDemandReplay.swift in Sources */,
59025905
D8603DD6284F8497000E1227 /* SentryBaggage.m in Sources */,
59035906
63FE711520DA4C1000CDBAE8 /* SentryCrashJSONCodec.c in Sources */,
@@ -6236,6 +6239,7 @@
62366239
F45243882DE65968003E8F50 /* ExceptionCatcher.m in Sources */,
62376240
7B59398224AB47650003AAD2 /* SentrySessionTrackerTests.swift in Sources */,
62386241
D43647F12D5CFB71001468E0 /* SentrySpanKeyTests.swift in Sources */,
6242+
D4CA34832E378C9900E92A61 /* SentryArrayTests.swift in Sources */,
62396243
7B05A61824A4D14A00EF211D /* SentrySessionGeneratorTests.swift in Sources */,
62406244
D8CB742B294B1DD000A5F964 /* SentryUIApplicationTests.swift in Sources */,
62416245
63FE720920DA66EC00CDBAE8 /* XCTestCase+SentryCrash.m in Sources */,

Sources/Sentry/NSArray+SentrySanitize.m renamed to Sources/Sentry/SentryArray.m

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#import "NSArray+SentrySanitize.h"
1+
#import "SentryArray.h"
22
#import "SentryDateUtils.h"
33
#import "SentryNSDictionarySanitize.h"
44

@@ -13,7 +13,13 @@ + (NSArray *)sanitizeArray:(NSArray *)array;
1313
} else if ([rawValue isKindOfClass:NSNumber.class]) {
1414
[result addObject:rawValue];
1515
} else if ([rawValue isKindOfClass:NSDictionary.class]) {
16-
[result addObject:sentry_sanitize((NSDictionary *)rawValue)];
16+
NSDictionary *_Nullable sanitizedDict = sentry_sanitize((NSDictionary *)rawValue);
17+
if (sanitizedDict == nil) {
18+
// Adding `nil` to an array is not allowed in Objective-C and raises an
19+
// `NSInvalidArgumentException`.
20+
continue;
21+
}
22+
[result addObject:SENTRY_UNWRAP_NULLABLE(NSDictionary, sanitizedDict)];
1723
} else if ([rawValue isKindOfClass:NSArray.class]) {
1824
[result addObject:[SentryArray sanitizeArray:rawValue]];
1925
} else if ([rawValue isKindOfClass:NSDate.class]) {

Sources/Sentry/SentryNSDictionarySanitize.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#import "SentryNSDictionarySanitize.h"
2-
#import "NSArray+SentrySanitize.h"
2+
#import "SentryArray.h"
33
#import "SentryDateUtils.h"
44

55
NSDictionary *_Nullable sentry_sanitize(NSDictionary *_Nullable dictionary)
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
@testable import Sentry
2+
import XCTest
3+
4+
class SentryArrayTests: XCTestCase {
5+
6+
func testSanitizeArray_emptyArray_shouldReturnEmptyArray() {
7+
// Arrange
8+
let array: [String] = []
9+
10+
// Act
11+
let result = SentryArray.sanitizeArray(array)
12+
13+
// Assert
14+
XCTAssertEqual(result.count, 0)
15+
}
16+
17+
func testSanitizeArray_withStrings_shouldReturnSameStrings() throws {
18+
// Arrange
19+
let array = ["hello", "world", "test"]
20+
21+
// Act
22+
let result = SentryArray.sanitizeArray(array)
23+
24+
// Assert
25+
let stringArray = try XCTUnwrap(result as? [String])
26+
XCTAssertEqual(stringArray, ["hello", "world", "test"])
27+
}
28+
29+
func testSanitizeArray_withNumbers_shouldReturnSameNumbers() throws {
30+
// Arrange
31+
let array = [NSNumber(value: 42), NSNumber(value: 3.14), NSNumber(value: true)]
32+
33+
// Act
34+
let result = SentryArray.sanitizeArray(array)
35+
36+
// Assert
37+
XCTAssertEqual(result.count, 3)
38+
XCTAssertEqual(result[0] as? NSNumber, NSNumber(value: 42))
39+
XCTAssertEqual(result[1] as? NSNumber, NSNumber(value: 3.14))
40+
XCTAssertEqual(result[2] as? NSNumber, NSNumber(value: true))
41+
}
42+
43+
func testSanitizeArray_withValidDictionaries_shouldReturnSanitizedDictionaries() throws {
44+
// Arrange
45+
let dict1: [String: Any] = ["key1": "value1", "key2": NSNumber(value: 123)]
46+
let dict2: [String: Any] = ["key3": "value3"]
47+
let array: [Any] = [dict1, dict2]
48+
49+
// Act
50+
let result = SentryArray.sanitizeArray(array)
51+
52+
// Assert
53+
XCTAssertEqual(result.count, 2)
54+
let sanitizedDict1 = try XCTUnwrap(result[0] as? [String: Any])
55+
let sanitizedDict2 = try XCTUnwrap(result[1] as? [String: Any])
56+
XCTAssertEqual(sanitizedDict1["key1"] as? String, "value1")
57+
XCTAssertEqual(sanitizedDict1["key2"] as? NSNumber, NSNumber(value: 123))
58+
XCTAssertEqual(sanitizedDict2["key3"] as? String, "value3")
59+
}
60+
61+
func testSanitizeArray_withInvalidDictionary_shouldSkipNilResult() throws {
62+
// Arrange
63+
// Create a dictionary-like object that will cause sentry_sanitize to return nil
64+
let invalidDict = NotReallyADictionary()
65+
let validDict: [String: Any] = ["key": "value"]
66+
let array: [Any] = [invalidDict, validDict]
67+
68+
// Act
69+
let result = SentryArray.sanitizeArray(array)
70+
71+
// Assert
72+
// Should only contain the valid dictionary, invalid one should be skipped
73+
XCTAssertEqual(result.count, 1)
74+
let sanitizedDict = try XCTUnwrap(result[0] as? [String: Any])
75+
XCTAssertEqual(sanitizedDict["key"] as? String, "value")
76+
}
77+
78+
func testSanitizeArray_withNestedArrays_shouldRecursivelySanitize() throws {
79+
// Arrange
80+
let nestedArray: [Any] = ["nested1", NSNumber(value: 456)]
81+
let array: [Any] = [nestedArray, "topLevel"]
82+
83+
// Act
84+
let result = SentryArray.sanitizeArray(array)
85+
86+
// Assert
87+
XCTAssertEqual(result.count, 2)
88+
89+
let firstElement = try XCTUnwrap(result[0] as? [Any])
90+
XCTAssertEqual(firstElement[0] as? String, "nested1")
91+
XCTAssertEqual(firstElement[1] as? NSNumber, NSNumber(value: 456))
92+
93+
XCTAssertEqual(result[1] as? String, "topLevel")
94+
}
95+
96+
func testSanitizeArray_withDates_shouldConvertToISO8601String() throws {
97+
// Arrange
98+
let date1 = Date(timeIntervalSince1970: 1_640_995_200) // 2022-01-01 00:00:00 UTC
99+
let date2 = Date(timeIntervalSince1970: 0) // 1970-01-01 00:00:00 UTC
100+
let array: [Any] = [date1, date2]
101+
102+
// Act
103+
let result = SentryArray.sanitizeArray(array)
104+
105+
// Assert
106+
XCTAssertEqual(result.count, 2)
107+
// Verify the dates are converted to strings (ISO8601 format)
108+
let dateString1 = try XCTUnwrap(result[0] as? String)
109+
let dateString2 = try XCTUnwrap(result[1] as? String)
110+
XCTAssertEqual(dateString1, "2022-01-01T00:00:00.000Z")
111+
XCTAssertEqual(dateString2, "1970-01-01T00:00:00.000Z")
112+
}
113+
114+
func testSanitizeArray_withOtherObjects_shouldUseDescription() throws {
115+
// Arrange
116+
let customObject = CustomObject()
117+
let array: [Any] = [customObject]
118+
119+
// Act
120+
let result = SentryArray.sanitizeArray(array)
121+
122+
// Assert
123+
XCTAssertEqual(result.count, 1)
124+
let description = try XCTUnwrap(result[0] as? String)
125+
XCTAssertEqual(description, "CustomObject description")
126+
}
127+
128+
func testSanitizeArray_withMixedTypes_shouldHandleAllTypes() throws {
129+
// Arrange
130+
let dict: [String: Any] = ["key": "value"]
131+
let nestedArray: [Any] = ["nested"]
132+
let date = Date(timeIntervalSince1970: 1_640_995_200)
133+
let customObject = CustomObject()
134+
let array: [Any] = [
135+
"string",
136+
NSNumber(value: 42),
137+
dict,
138+
nestedArray,
139+
date,
140+
customObject
141+
]
142+
143+
// Act
144+
let result = SentryArray.sanitizeArray(array)
145+
146+
// Assert
147+
XCTAssertEqual(result.count, 6)
148+
149+
// Verify each type is handled correctly
150+
XCTAssertEqual(result[0] as? String, "string")
151+
XCTAssertEqual(result[1] as? NSNumber, NSNumber(value: 42))
152+
XCTAssertNotNil(result[2] as? [String: Any])
153+
XCTAssertNotNil(result[3] as? [Any])
154+
155+
let dateString = try XCTUnwrap(result[4] as? String)
156+
XCTAssertEqual(dateString, "2022-01-01T00:00:00.000Z")
157+
158+
let objectDescription = try XCTUnwrap(result[5] as? String)
159+
XCTAssertEqual(objectDescription, "CustomObject description")
160+
}
161+
}
162+
163+
// Helper class that inherits from NSObject but is not a real NSDictionary
164+
// This will cause sentry_sanitize to return nil when it checks isSubclassOfClass
165+
private class NotReallyADictionary: NSObject {
166+
override func isKind(of aClass: AnyClass) -> Bool {
167+
if aClass == NSDictionary.self {
168+
return true // Pretend to be a dictionary
169+
}
170+
return super.isKind(of: aClass)
171+
}
172+
}
173+
174+
// Helper class for testing description fallback
175+
private class CustomObject: NSObject {
176+
override var description: String {
177+
return "CustomObject description"
178+
}
179+
}

Tests/SentryTests/SentryTests-Bridging-Header.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
#import "SentryAppStartTrackingIntegration.h"
5656
#import "SentryAppState.h"
5757
#import "SentryAppStateManager.h"
58+
#import "SentryArray.h"
5859
#import "SentryAttachment+Private.h"
5960
#import "SentryAutoBreadcrumbTrackingIntegration+Test.h"
6061
#import "SentryAutoBreadcrumbTrackingIntegration.h"

0 commit comments

Comments
 (0)