Skip to content

Commit ebc72be

Browse files
authored
refactor: Add full nullability handling to SentryFileManager (#5737)
1 parent b41e6a7 commit ebc72be

File tree

7 files changed

+176
-90
lines changed

7 files changed

+176
-90
lines changed

SentryTestUtils/SentryFileManager+Test.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ NSString *_Nullable sentryGetScopedCachesDirectory(NSString *cachesDirectory);
88
NSString *_Nullable sentryBuildScopedCachesDirectoryPath(NSString *cachesDirectory,
99
BOOL isSandboxed, NSString *_Nullable bundleIdentifier, NSString *_Nullable lastPathComponent);
1010

11-
SENTRY_EXTERN NSURL *launchProfileConfigFileURL(void);
11+
SENTRY_EXTERN NSURL *_Nullable launchProfileConfigFileURL(void);
1212
SENTRY_EXTERN NSURL *_Nullable sentryLaunchConfigFileURL;
1313

1414
@interface SentryFileManager ()

Sources/Sentry/Public/SentryDefines.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,3 +203,20 @@ typedef void (^SentryUserFeedbackConfigurationBlock)(
203203
SentryUserFeedbackConfiguration *_Nonnull configuration);
204204

205205
#endif // TARGET_OS_IOS && SENTRY_HAS_UIKIT
206+
207+
/**
208+
* `SENTRY_UNWRAP_NULLABLE` is used to unwrap a nullable pointer type to a non-nullable pointer type
209+
* It should be used after the pointer has been checked for nullability.
210+
*
211+
* For example:
212+
* ```objc
213+
* id _Nullable nullablePointer = ...;
214+
* if (nullablePointer != nil) {
215+
* MyClass *_Nonnull nonNullPointer = SENTRY_UNWRAP_NULLABLE(MyClass, nullablePointer);
216+
* }
217+
* ```
218+
*
219+
* We use this macro instead of directly casting to be able to find all usages of this
220+
* pattern in the codebase.
221+
*/
222+
#define SENTRY_UNWRAP_NULLABLE(type, nullable_var) (type *_Nonnull)(nullable_var)

Sources/Sentry/SentryFileManager.m

Lines changed: 94 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -74,13 +74,13 @@
7474
void
7575
_non_thread_safe_removeFileAtPath(NSString *path)
7676
{
77-
NSError *error = nil;
77+
NSError *_Nullable error = nil;
7878
NSFileManager *fileManager = [NSFileManager defaultManager];
7979
if ([fileManager removeItemAtPath:path error:&error]) {
8080
SENTRY_LOG_DEBUG(@"Successfully deleted file at %@", path);
8181
} else if (error.code == NSFileNoSuchFileError) {
8282
SENTRY_LOG_DEBUG(@"No file to delete at %@", path);
83-
} else if (isErrorPathTooLong(error)) {
83+
} else if (error != NULL && isErrorPathTooLong(SENTRY_UNWRAP_NULLABLE(NSError, error))) {
8484
SENTRY_LOG_FATAL(@"Failed to remove file, path is too long: %@", path);
8585
} else {
8686
SENTRY_LOG_ERROR(@"Error occurred while deleting file at %@ because of %@", path, error);
@@ -158,7 +158,22 @@ - (void)createPathsWithOptions:(SentryOptions *)options
158158
SENTRY_LOG_DEBUG(@"SentryFileManager.cachePath: %@", cachePath);
159159

160160
self.basePath = [cachePath stringByAppendingPathComponent:@"io.sentry"];
161-
self.sentryPath = [self.basePath stringByAppendingPathComponent:[options.parsedDsn getHash]];
161+
162+
NSString *_Nullable nullableDsnHash = [options.parsedDsn getHash];
163+
if (nullableDsnHash == nil) {
164+
SENTRY_LOG_FATAL(@"No DSN provided, using base path for envelopes: %@", self.basePath);
165+
}
166+
// We decided against changing the `sentryPath` and use a null fallback instead, because this
167+
// has been broken for a long time and the impact of changing the base path can result in
168+
// critical issues.
169+
//
170+
// Instead we silence the nullability warning and let `stringByAppendingPathComponent` handle
171+
// the null case.
172+
//
173+
// Full discussion in https://github.com/getsentry/sentry-cocoa/pull/5737
174+
self.sentryPath = [self.basePath
175+
stringByAppendingPathComponent:SENTRY_UNWRAP_NULLABLE(NSString, nullableDsnHash)];
176+
162177
self.currentSessionFilePath =
163178
[self.sentryPath stringByAppendingPathComponent:@"session.current"];
164179
self.crashedSessionFilePath =
@@ -233,7 +248,7 @@ - (nullable NSString *)getEnvelopesPath:(NSString *)filePath
233248
return nil;
234249
}
235250

236-
NSError *error = nil;
251+
NSError *_Nullable error = nil;
237252
NSDictionary *dict = [[NSFileManager defaultManager] attributesOfItemAtPath:fullPath
238253
error:&error];
239254
if (error != nil) {
@@ -295,7 +310,7 @@ - (void)deleteOldEnvelopeItems
295310
- (void)deleteAllEnvelopes
296311
{
297312
[self removeFileAtPath:self.envelopesPath];
298-
NSError *error;
313+
NSError *_Nullable error;
299314
if (!createDirectoryIfNotExists(self.envelopesPath, &error)) {
300315
SENTRY_LOG_ERROR(@"Couldn't create envelopes path.");
301316
}
@@ -355,7 +370,12 @@ - (void)storeTimestampLastInForeground:(NSDate *)timestamp
355370
NSString *timestampString = sentry_toIso8601String(timestamp);
356371
SENTRY_LOG_DEBUG(@"Persisting lastInForeground: %@", timestampString);
357372
@synchronized(self.lastInForegroundFilePath) {
358-
if (![self writeData:[timestampString dataUsingEncoding:NSUTF8StringEncoding]
373+
NSData *_Nullable nullableData = [timestampString dataUsingEncoding:NSUTF8StringEncoding];
374+
if (nullableData == nil) {
375+
SENTRY_LOG_ERROR(@"Failed to convert lastInForeground timestamp to data.");
376+
return;
377+
}
378+
if (![self writeData:SENTRY_UNWRAP_NULLABLE(NSData, nullableData)
359379
toPath:self.lastInForegroundFilePath]) {
360380
SENTRY_LOG_WARN(@"Failed to store timestamp of last foreground event.");
361381
}
@@ -491,7 +511,7 @@ - (NSArray *)readPreviousBreadcrumbs
491511
continue;
492512
}
493513

494-
NSError *error;
514+
NSError *_Nullable error;
495515
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data
496516
options:0
497517
error:&error];
@@ -535,7 +555,14 @@ - (void)storeTimezoneOffset:(NSInteger)offset
535555
NSString *timezoneOffsetString = [NSString stringWithFormat:@"%ld", (long)offset];
536556
SENTRY_LOG_DEBUG(@"Persisting timezone offset: %@", timezoneOffsetString);
537557
@synchronized(self.timezoneOffsetFilePath) {
538-
if (![self writeData:[timezoneOffsetString dataUsingEncoding:NSUTF8StringEncoding]
558+
NSData *_Nullable nullableData =
559+
[timezoneOffsetString dataUsingEncoding:NSUTF8StringEncoding];
560+
if (nullableData == nil) {
561+
SENTRY_LOG_ERROR(@"Failed to convert timezone offset to data.");
562+
return;
563+
}
564+
565+
if (![self writeData:SENTRY_UNWRAP_NULLABLE(NSData, nullableData)
539566
toPath:self.timezoneOffsetFilePath]) {
540567
SENTRY_LOG_WARN(@"Failed to store timezone offset.");
541568
}
@@ -613,7 +640,7 @@ - (nullable NSData *)readDataFromPath:(NSString *)path
613640

614641
- (BOOL)writeData:(NSData *)data toPath:(NSString *)path
615642
{
616-
NSError *error;
643+
NSError *_Nullable error;
617644
if (!createDirectoryIfNotExists(self.sentryPath, &error)) {
618645
SENTRY_LOG_ERROR(@"File I/O not available at path %@: %@", path, error);
619646
return NO;
@@ -645,7 +672,7 @@ - (void)removeFileAtPath:(NSString *)path
645672
return @[];
646673
}
647674

648-
NSError *error = nil;
675+
NSError *_Nullable error = nil;
649676
NSArray<NSString *> *storedFiles = [fileManager contentsOfDirectoryAtPath:path error:&error];
650677
if (error != nil) {
651678
SENTRY_LOG_ERROR(@"Couldn't load files in folder %@: %@", path, error);
@@ -674,13 +701,14 @@ - (BOOL)isDirectory:(NSString *)path
674701
// For iOS apps and macOS apps with sandboxing, this path will be scoped for the current
675702
// app. For macOS apps without sandboxing, this path is not scoped and will be shared
676703
// between all apps.
677-
NSString *_Nullable cachesDirectory
704+
NSString *_Nullable nullableCachesDirectory
678705
= NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)
679706
.firstObject;
680-
if (cachesDirectory == nil) {
707+
if (nullableCachesDirectory == nil) {
681708
SENTRY_LOG_WARN(@"No caches directory location reported.");
682709
return;
683710
}
711+
NSString *_Nonnull cachesDirectory = (NSString *_Nonnull)nullableCachesDirectory;
684712

685713
// We need to ensure our own scoped directory so that this path is not shared between other
686714
// apps on the same system.
@@ -767,7 +795,7 @@ - (BOOL)isDirectory:(NSString *)path
767795
return nil;
768796
}
769797

770-
return [cachesDirectory stringByAppendingPathComponent:identifier];
798+
return [cachesDirectory stringByAppendingPathComponent:(NSString *_Nonnull)identifier];
771799
}
772800

773801
NSString *_Nullable sentryStaticBasePath(void)
@@ -789,7 +817,12 @@ - (BOOL)isDirectory:(NSString *)path
789817
void
790818
removeSentryStaticBasePath(void)
791819
{
792-
_non_thread_safe_removeFileAtPath(sentryStaticBasePath());
820+
NSString *_Nullable basePath = sentryStaticBasePath();
821+
if (basePath == nil) {
822+
SENTRY_LOG_DEBUG(@"No base path available to remove.");
823+
return;
824+
}
825+
_non_thread_safe_removeFileAtPath((NSString *_Nonnull)basePath);
793826
}
794827
#endif // defined(SENTRY_TEST) || defined(SENTRY_TEST_CI) || defined(DEBUG)
795828

@@ -808,7 +841,7 @@ - (BOOL)isDirectory:(NSString *)path
808841
SENTRY_LOG_WARN(@"No location available to write a launch profiling config.");
809842
return;
810843
}
811-
NSError *error;
844+
NSError *_Nullable error;
812845
if (!createDirectoryIfNotExists(basePath, &error)) {
813846
SENTRY_LOG_ERROR(
814847
@"Can't create base path to store launch profile config file: %@", error);
@@ -824,14 +857,25 @@ - (BOOL)isDirectory:(NSString *)path
824857
NSDictionary<NSString *, NSNumber *> *_Nullable sentry_persistedLaunchProfileConfigurationOptions(
825858
void)
826859
{
827-
NSURL *url = launchProfileConfigFileURL();
828-
if (![[NSFileManager defaultManager] fileExistsAtPath:url.path]) {
860+
NSURL *_Nullable url = launchProfileConfigFileURL();
861+
if (url == nil) {
862+
SENTRY_LOG_ERROR(@"Failed to construct the URL to retrieve launch profile configs.")
863+
return nil;
864+
}
865+
NSString *_Nullable nullablePath = url.path;
866+
if (nullablePath == nil) {
867+
SENTRY_LOG_ERROR(@"Failed to construct the path to retrieve launch profile configs.")
868+
return nil;
869+
}
870+
if (![[NSFileManager defaultManager]
871+
fileExistsAtPath:SENTRY_UNWRAP_NULLABLE(NSString, nullablePath)]) {
829872
return nil;
830873
}
831874

832-
NSError *error;
833-
NSDictionary<NSString *, NSNumber *> *config =
834-
[NSDictionary<NSString *, NSNumber *> dictionaryWithContentsOfURL:url error:&error];
875+
NSError *_Nullable error;
876+
NSDictionary<NSString *, NSNumber *> *config = [NSDictionary<NSString *, NSNumber *>
877+
dictionaryWithContentsOfURL:SENTRY_UNWRAP_NULLABLE(NSURL, url)
878+
error:&error];
835879

836880
if (error != nil) {
837881
SENTRY_LOG_ERROR(
@@ -845,9 +889,14 @@ - (BOOL)isDirectory:(NSString *)path
845889
BOOL
846890
appLaunchProfileConfigFileExists(void)
847891
{
848-
NSString *path = launchProfileConfigFileURL().path;
892+
NSURL *_Nullable url = launchProfileConfigFileURL();
893+
if (url == nil) {
894+
SENTRY_LOG_ERROR(@"Failed to construct the URL to check for launch profile configs.")
895+
return NO;
896+
}
897+
NSString *_Nullable path = url.path;
849898
if (path == nil) {
850-
SENTRY_LOG_DEBUG(@"Failed to construct the path to check for launch profile configs.")
899+
SENTRY_LOG_ERROR(@"Failed to construct the path to check for launch profile configs.")
851900
return NO;
852901
}
853902

@@ -857,16 +906,34 @@ - (BOOL)isDirectory:(NSString *)path
857906
void
858907
writeAppLaunchProfilingConfigFile(NSMutableDictionary<NSString *, NSNumber *> *config)
859908
{
860-
NSError *error;
861-
SENTRY_LOG_DEBUG(@"Writing launch profiling config file.");
862-
SENTRY_CASSERT([config writeToURL:launchProfileConfigFileURL() error:&error],
909+
NSURL *_Nullable url = launchProfileConfigFileURL();
910+
if (url == nil) {
911+
SENTRY_LOG_ERROR(@"Failed to construct the URL to write launch profile configs.");
912+
return;
913+
}
914+
SENTRY_LOG_DEBUG(@"Writing launch profiling config file at url %@", url);
915+
916+
NSError *_Nullable error;
917+
SENTRY_CASSERT([config writeToURL:SENTRY_UNWRAP_NULLABLE(NSURL, url) error:&error],
863918
@"Failed to write launch profile config file: %@.", error);
864919
}
865920

866921
void
867922
removeAppLaunchProfilingConfigFile(void)
868923
{
869-
_non_thread_safe_removeFileAtPath(launchProfileConfigFileURL().path);
924+
NSURL *_Nullable url = launchProfileConfigFileURL();
925+
if (url == nil) {
926+
SENTRY_LOG_ERROR(@"Failed to construct the URL to remove launch profile configs.");
927+
return;
928+
}
929+
NSString *_Nullable path = url.path;
930+
if (path == nil) {
931+
SENTRY_LOG_ERROR(@"Failed to construct the path to remove launch profile configs.");
932+
return;
933+
}
934+
935+
SENTRY_LOG_DEBUG(@"Removing launch profiling config file at path: %@", path);
936+
_non_thread_safe_removeFileAtPath(SENTRY_UNWRAP_NULLABLE(NSString, path));
870937
}
871938
#endif // SENTRY_TARGET_PROFILING_SUPPORTED
872939

@@ -940,7 +1007,7 @@ - (void)moveState:(NSString *)stateFilePath toPreviousState:(NSString *)previous
9401007
// We first need to remove the old previous state file,
9411008
// or we can't move the current state file to it.
9421009
[self removeFileAtPath:previousStateFilePath];
943-
NSError *error = nil;
1010+
NSError *_Nullable error = nil;
9441011
if (![fileManager moveItemAtPath:stateFilePath toPath:previousStateFilePath error:&error]) {
9451012
// We don't want to log an error if the file doesn't exist.
9461013
if (nil != error && error.code != NSFileNoSuchFileError) {

Sources/Sentry/SentryNSDataUtils.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ + (NSData *_Nullable)sentry_gzippedWithData:(NSData *)data
6363
if (data == nil) {
6464
return nil;
6565
}
66-
NSMutableData *mutable = [NSMutableData dataWithData:data];
66+
NSMutableData *mutable = [NSMutableData dataWithData:SENTRY_UNWRAP_NULLABLE(NSData, data)];
6767
[mutable appendBytes:"\0" length:1];
6868
return mutable;
6969
}

0 commit comments

Comments
 (0)