Skip to content

Commit dba1012

Browse files
Merge branch 'main' into fixSwizzlingNullability
2 parents 478ffe5 + 9e4b817 commit dba1012

24 files changed

+781
-59
lines changed

.github/workflows/create-issue-for-unreferenced-pr.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,8 @@ jobs:
5555
const prNumber = pr.number;
5656
5757
// Regex for GitHub issue references (e.g., #123, fixes #456)
58-
const issueRegexGitHub = /(?:(?:close|closes|closed|fix|fixes|fixed|resolve|resolves|resolved)\s*)?#\d+/i;
58+
// https://regex101.com/r/eDiGrQ/1
59+
const issueRegexGitHub = /(?:(?:close|closes|closed|fix|fixes|fixed|resolve|resolves|resolved):?\s*)?(#\d+|https:\/\/github\.com\/getsentry\/[\w-]+\/issues\/\d+)/i;
5960
6061
// Regex for Linear issue references (e.g., ENG-123, resolves ENG-456)
6162
const issueRegexLinear = /(?:(?:close|closes|closed|fix|fixes|fixed|resolve|resolves|resolved)\s*)?[A-Z]+-\d+/i;

.github/workflows/ui-tests-critical.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,13 @@ jobs:
102102
shell: bash
103103
run: ./scripts/ci-enable-permissions.sh
104104

105+
- name: Ensure required runtime is loaded
106+
# Ideally we will not need this, but CI sometimes is failing to load some runtimes, this will ensure they are loaded
107+
timeout-minutes: 5 # 5 minutes timeout
108+
env:
109+
OS_VERSION: "18.4"
110+
run: ./scripts/ci-ensure-runtime-loaded.sh --os-version "$OS_VERSION"
111+
105112
- name: Boot simulator
106113
run: ./scripts/ci-boot-simulator.sh
107114

.github/workflows/ui-tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ jobs:
195195
ui-tests-swift6,
196196
ready-to-merge-gate,
197197
]
198-
name: UI Tests
198+
name: UI Tests - Check
199199
# This is necessary since a failed/skipped dependent job would cause this job to be skipped
200200
if: always()
201201
runs-on: ubuntu-latest

AGENTS.md

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,68 @@ This file provides comprehensive guidance for AI coding agents working with the
3737
- Fix any test or type errors until the whole suite is green.
3838
- Add or update tests for the code you change, even if nobody asked.
3939

40+
#### Testing Error Handling Paths
41+
42+
When testing error handling code paths, follow these guidelines:
43+
44+
**Testable Error Paths:**
45+
46+
Many system call errors can be reliably tested:
47+
48+
- **File operation failures**: Use invalid/non-existent paths, closed file descriptors, or permission-restricted paths
49+
- **Directory operation failures**: Use invalid directory paths
50+
- **Network operation failures**: Use invalid addresses or closed sockets
51+
52+
**Example test pattern:**
53+
54+
```objc
55+
- (void)testFunction_HandlesOperationFailure
56+
{
57+
// -- Arrange --
58+
// This test verifies that functionName handles errors correctly when operation() fails.
59+
//
60+
// The error handling code path exists in SourceFile.c and correctly handles
61+
// the error condition. The code change itself is correct and verified through code review.
62+
63+
// Setup to trigger error (e.g., invalid path, closed fd, etc.)
64+
65+
// -- Act --
66+
bool result = functionName(/* parameters that will cause error */);
67+
68+
// -- Assert --
69+
// Verify the function fails gracefully (error handling path executes)
70+
// This verifies that the error handling code path executes correctly.
71+
XCTAssertFalse(result, @"functionName should fail with error condition");
72+
}
73+
```
74+
75+
**Untestable Error Paths:**
76+
77+
Some error paths cannot be reliably tested in a test environment:
78+
79+
- **System calls with hardcoded valid parameters**: Cannot pass invalid parameters to trigger failures
80+
- **Resource exhaustion scenarios**: System limits may not be enforceable in test environments
81+
- **Function interposition limitations**: `DYLD_INTERPOSE` only works for dynamically linked symbols; statically linked system calls cannot be reliably mocked
82+
83+
**Documenting Untestable Error Paths:**
84+
85+
When an error path cannot be reliably tested:
86+
87+
1. **Remove the test** if one was attempted but couldn't be made to work
88+
2. **Add documentation** in the test file explaining:
89+
- Why there's no test for the error path
90+
- Approaches that were tried and why they failed
91+
- That the error handling code path exists and is correct (verified through code review)
92+
3. **Add a comment** in the source code at the error handling location explaining why it cannot be tested
93+
4. **Update PR description** to document untestable error paths in the "How did you test it?" section
94+
95+
**Test Comment Best Practices:**
96+
97+
- **Avoid line numbers** in test comments - they become outdated when code changes
98+
- **Reference function names and file names** instead of line numbers
99+
- **Document the error condition** being tested (e.g., "when open() fails")
100+
- **Explain verification approach** - verify that the error handling path executes correctly rather than capturing implementation details
101+
40102
### Commit Guidelines
41103

42104
- **Pre-commit Hooks**: This repository uses pre-commit hooks. If a commit fails because files were changed during the commit process (e.g., by formatting hooks), automatically retry the commit. Pre-commit hooks may modify files (like formatting), and the commit should be retried with the updated files.

Sources/Sentry/SentryAsyncSafeLog.h

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,16 @@
3030

3131
#define SENTRY_ASYNC_SAFE_LOG_C_BUFFER_SIZE 1024
3232

33+
/**
34+
* Buffer size for thread-safe strerror_r operations.
35+
*
36+
* POSIX doesn't specify a minimum buffer size for strerror_r. We use 1024 bytes to match
37+
* glibc's implementation, which uses a 1024-byte buffer for strerror() to ensure sufficient
38+
* space for error messages across all locales and systems. This provides a safe upper bound
39+
* while being reasonable for stack allocation since it's allocated per macro expansion.
40+
*/
41+
#define SENTRY_STRERROR_R_BUFFER_SIZE 1024
42+
3343
/**
3444
* In addition to writing to file, we can also write to the console. This is not safe to do from
3545
* actual async contexts, but can be helpful while running with the debugger attached in certain
@@ -43,7 +53,10 @@
4353
extern "C" {
4454
#endif
4555

56+
#include <errno.h>
4657
#include <stdbool.h>
58+
#include <stdio.h>
59+
#include <string.h>
4760

4861
static char g_logFilename[1024];
4962

@@ -148,6 +161,36 @@ int sentry_asyncLogSetFileName(const char *filename, bool overwrite);
148161
# define SENTRY_ASYNC_SAFE_LOG_TRACE(FMT, ...)
149162
#endif
150163

164+
/**
165+
* Thread-safe version of strerror using strerror_r.
166+
* On macOS/iOS, strerror_r follows XSI-compliant version which returns int.
167+
* This macro evaluates to a pointer to a buffer containing the error string.
168+
*
169+
* The buffer size is defined by SENTRY_STRERROR_R_BUFFER_SIZE (1024 bytes, matching glibc).
170+
*
171+
* The macro uses a GCC statement expression ({ ... }) which allows a block of statements
172+
* to be used as an expression. The last expression in the block (__strerror_buf;) becomes
173+
* the value of the entire expression, allowing the macro to be used directly in function
174+
* calls like: SENTRY_ASYNC_SAFE_LOG_ERROR("Error: %s", SENTRY_STRERROR_R(errno));
175+
*
176+
* IMPORTANT: Uses thread-local storage to ensure the pointer remains valid after the macro
177+
* completes while maintaining thread safety. This is necessary because the macro is used
178+
* as a function argument, and stack-allocated buffers would be deallocated before the
179+
* function (e.g., vsnprintf) reads them. Thread-local storage ensures each thread has
180+
* its own buffer, preventing race conditions.
181+
*
182+
* @param ERRNUM The error number (e.g., errno).
183+
* @return Pointer to a thread-local buffer containing the error string.
184+
*/
185+
#define SENTRY_STRERROR_R(ERRNUM) \
186+
({ \
187+
static __thread char __strerror_buf[SENTRY_STRERROR_R_BUFFER_SIZE]; \
188+
if (strerror_r((ERRNUM), __strerror_buf, sizeof(__strerror_buf)) != 0) { \
189+
snprintf(__strerror_buf, sizeof(__strerror_buf), "Unknown error %d", (ERRNUM)); \
190+
} \
191+
__strerror_buf; \
192+
})
193+
151194
/**
152195
* If @c errno is set to a non-zero value after @c statement finishes executing,
153196
* the error value is logged, and the original return value of @c statement is
@@ -160,7 +203,7 @@ int sentry_asyncLogSetFileName(const char *filename, bool overwrite);
160203
const int __log_errnum = errno; \
161204
if (__log_errnum != 0) { \
162205
SENTRY_ASYNC_SAFE_LOG_ERROR("%s failed with code: %d, description: %s", #statement, \
163-
__log_errnum, strerror(__log_errnum)); \
206+
__log_errnum, SENTRY_STRERROR_R(__log_errnum)); \
164207
} \
165208
__log_rv; \
166209
})

Sources/Sentry/SentrySessionReplaySyncC.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,8 @@ sentrySessionReplaySync_writeInfo(void)
5656
int fd = open(crashReplay.path, O_RDWR | O_CREAT | O_TRUNC, 0644);
5757

5858
if (fd < 1) {
59-
SENTRY_ASYNC_SAFE_LOG_ERROR(
60-
"Could not open replay info crash for file %s: %s", crashReplay.path, strerror(errno));
59+
SENTRY_ASYNC_SAFE_LOG_ERROR("Could not open replay info crash for file %s: %s",
60+
crashReplay.path, SENTRY_STRERROR_R(errno));
6161
return;
6262
}
6363

@@ -85,7 +85,7 @@ sentrySessionReplaySync_readInfo(SentryCrashReplay *output, const char *const pa
8585
int fd = open(path, O_RDONLY);
8686
if (fd < 0) {
8787
SENTRY_ASYNC_SAFE_LOG_ERROR(
88-
"Could not open replay info crash file %s: %s", path, strerror(errno));
88+
"Could not open replay info crash file %s: %s", path, SENTRY_STRERROR_R(errno));
8989
return false;
9090
}
9191

Sources/Sentry/SentryViewHierarchyProviderHelper.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ + (BOOL)saveViewHierarchy:(NSString *)filePath
3333
const char *path = [filePath UTF8String];
3434
int fd = open(path, O_RDWR | O_CREAT | O_TRUNC, 0644);
3535
if (fd < 0) {
36-
SENTRY_LOG_DEBUG(@"Could not open file %s for writing: %s", path, strerror(errno));
36+
SENTRY_LOG_DEBUG(@"Could not open file %s for writing: %s", path, SENTRY_STRERROR_R(errno));
3737
return NO;
3838
}
3939

Sources/Sentry/include/SentryLogC.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
#import "SentryAsyncSafeLog.h"
12
#import "SentryDefines.h"
23

34
#ifdef __cplusplus
@@ -45,7 +46,7 @@ void logFatal(const char file[], int line, NSString *format, ...);
4546
const int __log_errnum = errno; \
4647
if (__log_errnum != 0) { \
4748
SENTRY_LOG_ERROR(@"%s failed with code: %d, description: %s", #statement, \
48-
__log_errnum, strerror(__log_errnum)); \
49+
__log_errnum, SENTRY_STRERROR_R(__log_errnum)); \
4950
} \
5051
__log_rv; \
5152
})

Sources/SentryCrash/Recording/Monitors/SentryCrashMonitor_AppState.c

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,8 +270,17 @@ saveState(const char *const path)
270270
{
271271
int fd = open(path, O_RDWR | O_CREAT | O_TRUNC, 0644);
272272
if (fd < 0) {
273+
// Error handling path: Uses SENTRY_STRERROR_R(errno) for thread-safe error message
274+
// retrieval. This error path cannot be reliably tested because:
275+
// - saveState is a static function, so it cannot be called directly from tests
276+
// - While open() failures can be triggered with invalid paths or permissions, testing
277+
// this function requires calling it indirectly through app state monitoring, which
278+
// makes it difficult to control the exact error conditions
279+
// - System calls cannot be easily mocked in C without function interposition, which has
280+
// limitations for statically linked symbols
281+
// The error handling code path exists and is correct (verified through code review).
273282
SENTRY_ASYNC_SAFE_LOG_ERROR(
274-
"Could not open file %s for writing: %s", path, strerror(errno));
283+
"Could not open file %s for writing: %s", path, SENTRY_STRERROR_R(errno));
275284
return false;
276285
}
277286

Sources/SentryCrash/Recording/Monitors/SentryCrashMonitor_MachException.c

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -500,15 +500,33 @@ installExceptionHandler(void)
500500
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
501501
error = pthread_create(&g_secondaryPThread, &attr, &handleExceptions, kThreadSecondary);
502502
if (error != 0) {
503-
SENTRY_ASYNC_SAFE_LOG_ERROR("pthread_create_suspended_np: %s", strerror(error));
503+
// Error handling path: Uses SENTRY_STRERROR_R(error) for thread-safe error message
504+
// retrieval. This error path cannot be reliably tested because:
505+
// - installExceptionHandler is a static function, so it cannot be called directly from
506+
// tests
507+
// - pthread_create failures are difficult to force in a test environment (resource limits,
508+
// system constraints, etc. may not reliably trigger failures)
509+
// - System calls cannot be easily mocked in C without function interposition, which has
510+
// limitations for statically linked symbols
511+
// The error handling code path exists and is correct (verified through code review).
512+
SENTRY_ASYNC_SAFE_LOG_ERROR("pthread_create_suspended_np: %s", SENTRY_STRERROR_R(error));
504513
goto failed;
505514
}
506515
g_secondaryMachThread = pthread_mach_thread_np(g_secondaryPThread);
507516

508517
SENTRY_ASYNC_SAFE_LOG_DEBUG("Creating primary exception thread.");
509518
error = pthread_create(&g_primaryPThread, &attr, &handleExceptions, kThreadPrimary);
510519
if (error != 0) {
511-
SENTRY_ASYNC_SAFE_LOG_ERROR("pthread_create: %s", strerror(error));
520+
// Error handling path: Uses SENTRY_STRERROR_R(error) for thread-safe error message
521+
// retrieval. This error path cannot be reliably tested because:
522+
// - installExceptionHandler is a static function, so it cannot be called directly from
523+
// tests
524+
// - pthread_create failures are difficult to force in a test environment (resource limits,
525+
// system constraints, etc. may not reliably trigger failures)
526+
// - System calls cannot be easily mocked in C without function interposition, which has
527+
// limitations for statically linked symbols
528+
// The error handling code path exists and is correct (verified through code review).
529+
SENTRY_ASYNC_SAFE_LOG_ERROR("pthread_create: %s", SENTRY_STRERROR_R(error));
512530
goto failed;
513531
}
514532
pthread_attr_destroy(&attr);

0 commit comments

Comments
 (0)