-
-
Notifications
You must be signed in to change notification settings - Fork 335
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: replace occurences of strncpy to strlcpy #4636
base: main
Are you sure you want to change the base?
Conversation
|
Performance metrics 🚀
|
Revision | Plain | With Sentry | Diff |
---|---|---|---|
dd4145f | 1233.48 ms | 1254.35 ms | 20.88 ms |
6604dbb | 1248.35 ms | 1256.14 ms | 7.79 ms |
b4f8dba | 1343.92 ms | 1362.96 ms | 19.04 ms |
a0cc9d6 | 1232.37 ms | 1249.55 ms | 17.18 ms |
3cba0e8 | 1250.86 ms | 1258.39 ms | 7.53 ms |
fa38a2b | 1217.92 ms | 1235.52 ms | 17.60 ms |
728804f | 1341.16 ms | 1371.27 ms | 30.10 ms |
bef2003 | 1248.18 ms | 1258.86 ms | 10.68 ms |
7f14650 | 1236.00 ms | 1255.66 ms | 19.66 ms |
b9b0f0a | 1251.45 ms | 1257.86 ms | 6.41 ms |
App size
Revision | Plain | With Sentry | Diff |
---|---|---|---|
dd4145f | 21.58 KiB | 540.09 KiB | 518.51 KiB |
6604dbb | 22.84 KiB | 402.56 KiB | 379.72 KiB |
b4f8dba | 21.58 KiB | 614.87 KiB | 593.29 KiB |
a0cc9d6 | 21.58 KiB | 706.46 KiB | 684.88 KiB |
3cba0e8 | 22.84 KiB | 403.19 KiB | 380.34 KiB |
fa38a2b | 21.58 KiB | 418.70 KiB | 397.12 KiB |
728804f | 22.85 KiB | 411.75 KiB | 388.91 KiB |
bef2003 | 22.85 KiB | 407.73 KiB | 384.88 KiB |
7f14650 | 22.84 KiB | 402.63 KiB | 379.78 KiB |
b9b0f0a | 20.76 KiB | 434.94 KiB | 414.18 KiB |
Previous results on branch: philprime/strncpy-replacement
Startup times
Revision | Plain | With Sentry | Diff |
---|---|---|---|
d747559 | 1237.35 ms | 1253.76 ms | 16.41 ms |
c1ccd22 | 1223.02 ms | 1244.00 ms | 20.98 ms |
30fe95a | 1230.39 ms | 1243.60 ms | 13.22 ms |
83a0223 | 1243.98 ms | 1257.29 ms | 13.31 ms |
12db161 | 1227.21 ms | 1242.79 ms | 15.58 ms |
3ec7df9 | 1225.36 ms | 1247.29 ms | 21.93 ms |
App size
Revision | Plain | With Sentry | Diff |
---|---|---|---|
d747559 | 22.31 KiB | 760.76 KiB | 738.44 KiB |
c1ccd22 | 22.31 KiB | 758.83 KiB | 736.52 KiB |
30fe95a | 22.31 KiB | 757.18 KiB | 734.87 KiB |
83a0223 | 22.31 KiB | 760.68 KiB | 738.36 KiB |
12db161 | 22.31 KiB | 757.18 KiB | 734.88 KiB |
3ec7df9 | 22.31 KiB | 756.53 KiB | 734.22 KiB |
One thing I'd recommend here is to wrap whatever function we actually want to use in a function we control, to make it easier to test and potentially change later. It could be the case that My understanding after reading the docs is that
from https://linux.die.net/man/3/strlcpy:
I guess this is what this line of code is trying to replicate: https://github.com/getsentry/sentry-cocoa/pull/4636/files#diff-de0cc4487a50688dff672b8cd13b3901fdbf09abe93afa34b54bd561d1796fe8L1260 and I also just saw we actually already had another function to do this: https://github.com/getsentry/sentry-cocoa/pull/4636/files#diff-898d2165aedeeac3e2023d294e4bffee79b96b5c04d9d9d5be7e7593d4eb9aeeL21-L27 This is why a wrapper function is my preference, we can hide all these details and keep things consistent. If we have places that move data from char arrays back to buffers, we should also have a wrapper function for that as well. |
Thanks @armcknight for reviewing the PR. As requested in the issue and mentioned by you, we need to make sure the changes do not break the SDK. That's why I went ahead and started adding additional unit tests.
I was thinking of the same, and yes we might need to extend the buffers at some places by one character, if it actually uses the full buffer length. I also found the function sentry-cocoa/Sources/SentryCrash/Reporting/Filters/Tools/SentryStringUtils.h Lines 21 to 27 in 419f1d4
TL;DR: I added todos to the description what is left to do. In total I found 7 uses of
sentry-cocoa/Sources/SentryCrash/Recording/SentryCrashReport.c Lines 1474 to 1479 in 419f1d4
sentry-cocoa/Sources/SentryCrash/Recording/SentryCrashReportFixer.c Lines 54 to 68 in 419f1d4
sentry-cocoa/Sources/SentryCrash/Recording/Tools/SentryCrashFileUtils.c Lines 155 to 167 in 419f1d4
sentry-cocoa/Sources/SentryCrash/Recording/Tools/SentryCrashJSONCodec.c Lines 1259 to 1260 in 419f1d4
|
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #4636 +/- ##
=============================================
+ Coverage 90.631% 91.286% +0.654%
=============================================
Files 621 620 -1
Lines 71153 71555 +402
Branches 25310 26028 +718
=============================================
+ Hits 64487 65320 +833
+ Misses 6574 6135 -439
- Partials 92 100 +8
... and 32 files with indirect coverage changes Continue to review full report in Codecov by Sentry.
|
- - `SentrySdkInfo.packages` should be an array (#4626) | ||
- Use the same SdkInfo for envelope header and event (#4629) | ||
|
||
### Improvements | ||
|
||
- Improve compiler error message for missing Swift declarations due to APPLICATION_EXTENSION_API_ONLY (#4603) | ||
- Mask screenshots for errors (#4623) | ||
- Slightly speed up serializing scope (#4661) | ||
|
||
### Internal | ||
|
||
- Remove loading `integrations` names from `event.extra` (#4627) | ||
- Add Hybrid SDKs API to add extra SDK packages (#4637) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I resolved this conflict incorrectly. Did we want these items from the beta release's changes repeated in the 8.43.0 RC?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are correct, there have been a couple of releases since I opened up this PR. I'll fix this when getting ready to merge.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good work to make the changes and write the tests that cover a lot of implementation code.
We use a common pattern of sut
(system under test) which you might be able to use to refactor some of the tests. I know there's a tension between DRY and self-contained test logic. Figured I'd just offer it up as food for thought.
Overall I think the changes are fine so this has my approval. I merely offer some suggestions and comments along the way.
// Also note that strlcpy() and strlcat() only operate on true ''C'' strings. | ||
// This means that for strlcpy() src must be NUL-terminated and for strlcat() | ||
// both src and dst must be NUL-terminated. | ||
// | ||
// Source: https://linux.die.net/man/3/strlcpy | ||
strncpy(context->stringBuffer, start, len); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for adding this context here.
This concerns me a little bit since context->stringBuffer
is a char *
and can point to different static char arrays of varying length. If we don't trust other areas of our code to get strncpy right, we shouldn't trust operations on a non NULL terminated char * either.
Worth noting that while this is from the original KSCrash implementation that still is like this, the JSON codec was completely rewritten in BugSnag and no longer uses a char *
buffer, it all uses a static char array of length 512. They do ultimately use memcpy
, which isn't even as safe as strncpy
because it can leave uninitialized data in the remainder of the buffer if src
contains a NULL terminator before length
😅 Also checked Crashlytics, and they use strncpy
and memcpy
.
This doesn't need to be handled here now, but we should make a mental note of this: we may want to revisit the general design of the JSON encoder for crash reports one day, depending on how much we care about memory safety.
Also I'd omit the doc portion mentioning strlcat since that's not what's being used
// Also note that strlcpy() and strlcat() only operate on true ''C'' strings. | |
// This means that for strlcpy() src must be NUL-terminated and for strlcat() | |
// both src and dst must be NUL-terminated. | |
// | |
// Source: https://linux.die.net/man/3/strlcpy | |
strncpy(context->stringBuffer, start, len); | |
// Also note that strlcpy() and strlcat() only operate on true ''C'' strings. | |
// This means that for strlcpy() src must be NUL-terminated... | |
// | |
// Source: https://linux.die.net/man/3/strlcpy | |
strncpy(context->stringBuffer, start, len); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
During testing I went ahead an replaced it with strlcpy
first, without changing any of the unit tests. It kept crashing with EXC_BAD_ACCESS
, even though strncpy
and even memcpy
did not. I also considered to replace the existing implementation with memcpy
but decided to stick with it, simply to not change a running system without exact reason.
Thanks for the cleanup of strlcat
, I left it in there so that it's actually a quote, but I also see the missing value of additional text.
std::set_terminate(&testTerminationHandler); | ||
|
||
SentryCrashMonitorAPI *api = sentrycrashcm_cppexception_getAPI(); | ||
api = sentrycrashcm_cppexception_getAPI(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This happens in all the test cases, so could go into -[setUp]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are right, for some reason I thought this had to be after std::set_terminate
which is different per test case
NSString *errorMessage = [@"" stringByPaddingToLength:(1000 + 1) | ||
withString:@"A" | ||
startingAtIndex:0]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You might set the last character to something other than A, so that we know for a fact that we're truncating from the end of the string. Super edge casey, but it occurred to me so I figured I'd share.
sentrycrashcm_setEventCallback(testHandleExceptionHandler); | ||
api = sentrycrashcm_cppexception_getAPI(); | ||
|
||
// Build a 1000 + 1 character message |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Copypasta
// Build a 1000 + 1 character message | |
// Build a 1000 character message |
|
||
- (void)testCallTerminationHandler_NotEnabled | ||
{ | ||
// -- Arrange -- |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like this pattern. Wish there was a way to somehow do it structurally with syntax vs comments (could use labels and goto
😂 💀 more realistically, C macros, but that wouldn't work in Swift). Using XCTAssert...
macros makes that part obvious I suppose. Generally, it's nice 👍🏻 I found this https://automationpanda.com/2020/07/07/arrange-act-assert-a-pattern-for-writing-good-tests/ (the author's name is Andrew Knight 🤯)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
High chance that article actually inspired me to use that pattern. Macros etc. could give this even more structure, but there are always cases where that could make it more complicated/harder to maintain.
} | ||
|
||
void | ||
testHandleExceptionHandler(struct SentryCrash_MonitorContext *context) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I prefer to not use the test-
prefix for anything other than actual test methods. Maybe mock-
?
testHandleExceptionHandler(struct SentryCrash_MonitorContext *context) | |
mockExceptionHandler(struct SentryCrash_MonitorContext *context) |
@@ -622,4 +622,149 @@ - (void)testReadLineFromFD | |||
XCTAssertTrue(bytesRead == 0, @""); | |||
} | |||
|
|||
- (void)testDeleteContentsOfPath_canNotDeletePath_shouldNotDeleteTopLevelPath |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm guessing these are in service of covering SentryCrashFileUtils.c line 164 (although not all of them do), but that's why I suggested using a wrapper function that could be tested more directly. These are integration-y and have many possible failure modes.
I don't feel strongly but it wasn't clear to me exactly why they were being added until I went digging. It's a lot more test code to achieve the same ends.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In all honesty I don't see much of a point in having a wrapper around strlcpy
as we should test it's usage, rather than it's implementation.
For this PR I tried to add test cases for all places where I changed from strncpy
to strlcpy
, therefore also sentrycrashfu_deleteContentsOfPath
defined in SentryCrashFileUtils.h
. As we want to have high test coverage anyways, I decided to add test cases for all code paths.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep totally fair 👍🏻
/** | ||
* The following unit test is used as a control unit test for too long paths. | ||
* | ||
* When the overall path length is larger than ``SentryCrashFU_MAX_PATH_LENGTH``, it the given |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
* When the overall path length is larger than ``SentryCrashFU_MAX_PATH_LENGTH``, it the given | |
* When the overall path length is larger than ``SentryCrashFU_MAX_PATH_LENGTH``, the given |
XCTAssertEqual(reportUrls.count, 1) | ||
} | ||
|
||
func testOnCrash_notCrashedDuringCrashHandling_installFilePathTooLong_shouldNotWriteToDisk() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good on you for encoding behavior in a test like this, but IMO this situation is something we should fix ASAP if it's happening in the wild. We should always expect to be able to deliver crash reports, and guard against situations that would prevent that and work around them whenever possible. At the very least a warning message should be available for end users to also potentially work around it, like if they provide a custom cache path that is too long. The only other thing I can see that would cause this is a bundle id that is too long (we should also only use the bundle id for mac apps; ios/tvos/visionos are all self-contained files trees per app)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had a quick chat with @philipphofmann yesterday, I still need to validate if a too long file path can actually be passed down from the SentrySDK options using cacheDirectoryPath
to the SentryCrashC
, or if it causes issues before that.
He also mentioned a warning should be sufficient, but at this time I already created this test case to actually test the behavior with invalid paths.
In general I still believe we should also add tests to pin down expected negative behavior (if possible), so even if an edge case can hardly ever happen, we do have documentation that it can happen. If it then turns out to be an actual issue, we can change test-case, and fix the implementation until it passes again.
var appName = "SentryCrashCTests" | ||
.cString(using: .utf8)! | ||
let installDir = URL(fileURLWithPath: NSTemporaryDirectory()) | ||
.appendingPathComponent("SentryCrashCTests-\(UUID().uuidString)") | ||
var installPath = installDir | ||
.path | ||
.cString(using: .utf8)! | ||
let expectedReportsDir = installDir | ||
.appendingPathComponent("Reports") | ||
|
||
// Smoke test the existence of the directory | ||
XCTAssertFalse(FileManager.default.fileExists( | ||
atPath: expectedReportsDir.path | ||
)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Gratuitous use of vertical space. I'd change all such code:
var appName = "SentryCrashCTests" | |
.cString(using: .utf8)! | |
let installDir = URL(fileURLWithPath: NSTemporaryDirectory()) | |
.appendingPathComponent("SentryCrashCTests-\(UUID().uuidString)") | |
var installPath = installDir | |
.path | |
.cString(using: .utf8)! | |
let expectedReportsDir = installDir | |
.appendingPathComponent("Reports") | |
// Smoke test the existence of the directory | |
XCTAssertFalse(FileManager.default.fileExists( | |
atPath: expectedReportsDir.path | |
)) | |
var appName = "SentryCrashCTests".cString(using: .utf8)! | |
let installDir = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("SentryCrashCTests-\(UUID().uuidString)") | |
var installPath = installDir.path.cString(using: .utf8)! | |
let expectedReportsDir = installDir.appendingPathComponent("Reports") | |
// Smoke test the existence of the directory | |
XCTAssertFalse(FileManager.default.fileExists(atPath: expectedReportsDir.path)) |
📜 Description
Renames all occurences of
strncpy
withstrlcpy
as explained in #2783.💡 Motivation and Context
I looked all the occurences and tried to create additional unit tests where applicable. Some of the cases using
strlcpy
have larger buffer sizes set, i.e.SentryCrashFU_MAX_PATH_LENGTH
is set to500
, but I was not able to file with a longer name because the OS throws NSPOSIXErrorDomain, code 63, indicating that the file system does not support such long files.Closes #2783
💚 How did you test it?
📝 Checklist
You have to check all boxes before merging:
sendDefaultPII
is enabled.🔮 Next steps
CPPExceptionTerminate
to test ifstrncpy_safe
andstrlcpy
do the samesentrycrashreport_writeRecrashReport
deletePathContents
for too long file paths