Skip to content

Commit 4f5b489

Browse files
fix: Make scope.span writable again (#6740)
The Unreal SDK has some use cases for setting the span on the scope, so it's also likely that some users rely on this. This PR also fixes a force case that can lead to a crash if the set span isn't of the internal type SentrySpan. Fixes GH-6739
1 parent a4bd249 commit 4f5b489

File tree

7 files changed

+166
-5
lines changed

7 files changed

+166
-5
lines changed

CHANGELOG.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
- Removes `integrations` property from `SentryOptions` (#5749)
1818
- Removes `defaultIntegrations` function from `SentryOptions` (#6664)
1919
- Makes `SentryEventDecodable` internal (#5808)
20-
- The `span` property on `SentryScope` is now readonly (#5866)
2120
- Removes `SentryIntegrationProtocol` from the API. This is not used after the removal of the `integrations` property (#6660)
2221
- Removes deprecated SentryDebugImageProvider class (#5598)
2322
- Properties on SentryOptions that had no effect on the WithoutUIKit variant are now removed from the API (#6644)

Sources/Sentry/Public/SentryScope.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ NS_SWIFT_NAME(Scope)
2828
* Returns current Span or Transaction.
2929
* @return current Span or Transaction or null if transaction has not been set.
3030
*/
31-
@property (nullable, nonatomic, readonly, strong) id<SentrySpan> span;
31+
@property (nullable, nonatomic, strong) id<SentrySpan> span;
3232

3333
/**
3434
* The id of current session replay.

Sources/Sentry/SentryCrashIntegration.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
void
3636
sentry_finishAndSaveTransaction(void)
3737
{
38-
SentrySpan *span = (SentrySpan *)SentrySDKInternal.currentHub.scope.span;
38+
SentrySpan *span = [SentrySDKInternal.currentHub.scope getCastedInternalSpan];
3939

4040
if (span != nil) {
4141
SentryTracer *tracer = [span tracer];

Sources/Sentry/SentryScope.m

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,23 @@ - (void)setPropagationContext:(SentryPropagationContext *)propagationContext
150150
}
151151
}
152152

153+
- (nullable SentrySpan *)getCastedInternalSpan
154+
{
155+
id<SentrySpan> span = self.span;
156+
157+
if (span == nil) {
158+
return nil;
159+
}
160+
161+
if (span && [span isKindOfClass:[SentrySpan class]]) {
162+
return (SentrySpan *)span;
163+
}
164+
165+
SENTRY_LOG_DEBUG(@"The span on the scope is not of type SentrySpan, returning nil.");
166+
167+
return nil;
168+
}
169+
153170
- (void)clear
154171
{
155172
// As we need to synchronize the accesses of the arrays and dictionaries and we use the

Sources/Sentry/include/SentryScope+Private.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
@class SentryAttachment;
66
@class SentryPropagationContext;
77
@class SentrySession;
8+
@class SentrySpan;
89

910
@protocol SentryScopeObserver;
1011

@@ -13,7 +14,6 @@ NS_ASSUME_NONNULL_BEGIN
1314
@interface SentryScope ()
1415

1516
@property (atomic, copy, nullable) NSString *environmentString;
16-
@property (nullable, nonatomic, strong) id<SentrySpan> span;
1717

1818
@property (atomic, strong, readonly) NSArray<SentryAttachment *> *attachments;
1919

@@ -46,7 +46,7 @@ NS_ASSUME_NONNULL_BEGIN
4646

4747
- (NSArray<SentryBreadcrumb *> *)breadcrumbs;
4848

49-
- (void)setSpan:(nullable id<SentrySpan>)span;
49+
- (nullable SentrySpan *)getCastedInternalSpan;
5050

5151
/**
5252
* used to add values in event context.

Tests/SentryTests/SentryScopeSwiftTests.swift

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -798,6 +798,65 @@ class SentryScopeSwiftTests: XCTestCase {
798798
XCTAssertEqual(spanId.sentrySpanIdString, traceContext["span_id"] as? String)
799799
}
800800

801+
func testGetCasedInternalSpan_SpanIsNil() {
802+
// -- Arrange --
803+
let scope = Scope()
804+
805+
// -- Act --
806+
let span = scope.getCastedInternalSpan()
807+
808+
// -- Assert --
809+
XCTAssertNil(span)
810+
}
811+
812+
#if os(macOS)
813+
// We test this only on macOS because the SentrySpan init methods require a frames tracker.
814+
// As we're testing simple logic here, we can skip the other platforms.
815+
func testGetCasedInternalSpan_SpanIsOfInternalTypeSpan() throws {
816+
// -- Arrange --
817+
let scope = Scope()
818+
let span = SentrySpan(context: SpanContext(operation: "TEST"))
819+
820+
scope.span = span
821+
822+
// -- Act --
823+
let actualSpan = try XCTUnwrap(scope.getCastedInternalSpan())
824+
825+
// -- Assert --
826+
XCTAssertEqual(actualSpan, span)
827+
XCTAssertEqual(actualSpan.spanId, span.spanId)
828+
}
829+
830+
func testGetCasedInternalSpan_SpanIsSubClassOfInternalTypeSpan() throws {
831+
// -- Arrange --
832+
let scope = Scope()
833+
let span = SubClassOfSentrySpan(context: SpanContext(operation: "TEST"))
834+
835+
scope.span = span
836+
837+
// -- Act --
838+
let actualSpan = try XCTUnwrap(scope.getCastedInternalSpan())
839+
840+
// -- Assert --
841+
XCTAssertEqual(actualSpan, span)
842+
XCTAssertEqual(actualSpan.spanId, span.spanId)
843+
}
844+
#endif // os(macOS)
845+
846+
func testGetCasedInternalSpan_SpanIsOfDifferentType() {
847+
// -- Arrange --
848+
let scope = Scope()
849+
let span = NotOfTypeSpan()
850+
851+
scope.span = span
852+
853+
// -- Act --
854+
let actualSpan = scope.getCastedInternalSpan()
855+
856+
// -- Assert --
857+
XCTAssertNil(actualSpan)
858+
}
859+
801860
private class TestScopeObserver: NSObject, SentryScopeObserver {
802861
var tags: [String: String]?
803862
func setTags(_ tags: [String: String]?) {
@@ -863,3 +922,46 @@ class SentryScopeSwiftTests: XCTestCase {
863922
}
864923
}
865924
}
925+
926+
// A minimal dummy Span implementation that is not SentrySpan.
927+
private final class NotOfTypeSpan: NSObject, Span {
928+
929+
init(traceId: SentryId = SentryId()) {
930+
self.traceId = traceId
931+
}
932+
933+
// MARK: - Properties required by Span (set to neutral values)
934+
var traceId: SentryId = SentryId()
935+
var spanId: SpanId = SpanId()
936+
var parentSpanId: SpanId?
937+
var sampled: SentrySampleDecision = .undecided
938+
var operation: String = ""
939+
var origin: String = ""
940+
var spanDescription: String?
941+
var status: SentrySpanStatus = .undefined
942+
var timestamp: Date?
943+
var startTimestamp: Date?
944+
var data: [String: Any] { [:] }
945+
var tags: [String: String] { [:] }
946+
var isFinished: Bool { false }
947+
var traceContext: TraceContext? { nil }
948+
949+
// MARK: - Methods required by Span (no-ops)
950+
func startChild(operation: String) -> Span { return self }
951+
func startChild(operation: String, description: String?) -> Span { return self }
952+
func setData(value: Any?, key: String) {}
953+
func removeData(key: String) {}
954+
func setTag(value: String, key: String) {}
955+
func removeTag(key: String) {}
956+
func setMeasurement(name: String, value: NSNumber) {}
957+
func setMeasurement(name: String, value: NSNumber, unit: MeasurementUnit) {}
958+
func finish() {}
959+
func finish(status: SentrySpanStatus) {}
960+
func toTraceHeader() -> TraceHeader { return TraceHeader(trace: traceId, spanId: spanId, sampled: sampled) }
961+
func baggageHttpHeader() -> String? { return nil }
962+
963+
// MARK: - SentrySerializable (no-op payload)
964+
func serialize() -> [String: Any] { return [:] }
965+
}
966+
967+
private final class SubClassOfSentrySpan: SentrySpan {}

sdk_api.json

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14931,6 +14931,49 @@
1493114931
"Dynamic"
1493214932
],
1493314933
"accessorKind": "get"
14934+
},
14935+
{
14936+
"kind": "Accessor",
14937+
"name": "Set",
14938+
"printedName": "Set()",
14939+
"children": [
14940+
{
14941+
"kind": "TypeNameAlias",
14942+
"name": "Void",
14943+
"printedName": "Swift.Void",
14944+
"children": [
14945+
{
14946+
"kind": "TypeNominal",
14947+
"name": "Void",
14948+
"printedName": "()"
14949+
}
14950+
]
14951+
},
14952+
{
14953+
"kind": "TypeNominal",
14954+
"name": "Optional",
14955+
"printedName": "(any Sentry.Span)?",
14956+
"children": [
14957+
{
14958+
"kind": "TypeNominal",
14959+
"name": "Span",
14960+
"printedName": "any Sentry.Span",
14961+
"usr": "c:objc(pl)SentrySpan"
14962+
}
14963+
],
14964+
"usr": "s:Sq"
14965+
}
14966+
],
14967+
"declKind": "Accessor",
14968+
"usr": "c:objc(cs)SentryScope(im)setSpan:",
14969+
"moduleName": "Sentry",
14970+
"isOpen": true,
14971+
"objc_name": "setSpan:",
14972+
"declAttributes": [
14973+
"ObjC",
14974+
"Dynamic"
14975+
],
14976+
"accessorKind": "set"
1493414977
}
1493514978
]
1493614979
},

0 commit comments

Comments
 (0)