(via claude)
Crash Report
GitUp crashes with NSInvalidArgumentException: capacity (18446744073709551615) is ridiculous in -[GCDiff _cacheDeltasIfNeeded] when another process holds the git index lock.
Exception
Exception Type: EXC_CRASH (SIGABRT)
Exception Reason: *** -[__NSPlaceholderArray initWithCapacity:]: capacity (18446744073709551615) is ridiculous
Crash Backtrace
0 CoreFoundation -[__NSPlaceholderArray initWithCapacity:] + 376
1 GitUpKit -[GCDiff _cacheDeltasIfNeeded] + 92
2 GitUpKit -[GCDiff deltas] + 20
3 GitUpKit -[GIAdvancedCommitViewController _reloadContents] + 132
4 GitUpKit -[GIAdvancedCommitViewController repositoryStatusDidUpdate] + 68
5 ...
6 GitUpKit -[GCLiveRepository _updateStatus:] + 1068
7 GitUpKit -[GCLiveRepository _notifyWorkingDirectoryChanged:gitDirectoryChanged:] + 344
Root Cause Analysis
The crash is caused by a chain of events when the git index file is locked by another process:
git_diff_index_to_workdir() is called with the GIT_DIFF_UPDATE_INDEX flag (in GCDiff.m, both diffWorkingDirectoryWithIndex: and diffWorkingDirectoryWithCommit:usingIndex:)
- The diff computation succeeds, but
git_index_write() fails with GIT_ELOCKED because another process holds the lock
- In the bundled libgit2 (
diff_generate.c:1513-1518), on GIT_ELOCKED after diff computation, the function jumps to its cleanup label which frees the computed diff and leaves *out = NULL
- GitUp's code overrides
GIT_ELOCKED → GIT_OK, assuming the diff pointer is still valid — but it is now NULL
- A
GCDiff object is created wrapping a NULL git_diff* pointer
- Later,
git_diff_num_deltas(NULL) triggers GIT_ASSERT_ARG(diff) in assert_safe.h. In release builds, this macro returns -1 rather than crashing
- Since
git_diff_num_deltas returns size_t (unsigned), -1 becomes 18446744073709551615 (0xFFFFFFFFFFFFFFFF)
[[NSMutableArray alloc] initWithCapacity:0xFFFFFFFFFFFFFFFF] throws the "capacity is ridiculous" exception
Affected Code
GCDiff.m — diffWorkingDirectoryWithIndex: (line ~600):
int status = git_diff_index_to_workdir(outDiff, self.private, index.private, diffOptions);
if (status == GIT_ELOCKED) {
status = GIT_OK; // BUG: *outDiff is NULL here, diff was freed by libgit2
}
GCDiff.m — diffWorkingDirectoryWithCommit:usingIndex: (line ~562):
Same pattern — also has a secondary bug where git_diff_merge(*outDiff, diff2) is called with a NULL diff2, then *outDiff is freed but the pointer is not NULLed out, causing a double-free in _diffWithType:'s cleanup.
Proposed Fix
Primary fix: When git_diff_index_to_workdir returns GIT_ELOCKED, retry without the GIT_DIFF_UPDATE_INDEX flag to obtain a valid diff:
int status = git_diff_index_to_workdir(outDiff, self.private, index.private, diffOptions);
if (status == GIT_ELOCKED) {
// On GIT_ELOCKED, libgit2 frees the diff and sets *outDiff to NULL.
// Retry without the flag to get a valid diff.
diffOptions->flags &= ~GIT_DIFF_UPDATE_INDEX;
status = git_diff_index_to_workdir(outDiff, self.private, index.private, diffOptions);
}
Secondary fix (defense-in-depth): Add a NULL check in _cacheDeltasIfNeeded:
- (void)_cacheDeltasIfNeeded {
if (_deltas == nil) {
if (_private == NULL) {
XLOG_DEBUG_UNREACHABLE();
_deltas = [[NSMutableArray alloc] init];
return;
}
size_t count = git_diff_num_deltas(_private);
// ...
Tertiary fix: In the diffWorkingDirectoryWithCommit: block, NULL out *outDiff after freeing it to prevent double-free:
if (status != GIT_OK) {
git_diff_free(*outDiff);
*outDiff = NULL; // Prevent double-free in _diffWithType: cleanup
}
Environment
- GitUp 1.4.3 (build 1052)
- macOS 26.4.1 (25E253), ARM64
(via claude)
Crash Report
GitUp crashes with
NSInvalidArgumentException: capacity (18446744073709551615) is ridiculousin-[GCDiff _cacheDeltasIfNeeded]when another process holds the git index lock.Exception
Crash Backtrace
Root Cause Analysis
The crash is caused by a chain of events when the git index file is locked by another process:
git_diff_index_to_workdir()is called with theGIT_DIFF_UPDATE_INDEXflag (inGCDiff.m, bothdiffWorkingDirectoryWithIndex:anddiffWorkingDirectoryWithCommit:usingIndex:)git_index_write()fails withGIT_ELOCKEDbecause another process holds the lockdiff_generate.c:1513-1518), onGIT_ELOCKEDafter diff computation, the function jumps to its cleanup label which frees the computed diff and leaves*out = NULLGIT_ELOCKED→GIT_OK, assuming the diff pointer is still valid — but it is nowNULLGCDiffobject is created wrapping a NULLgit_diff*pointergit_diff_num_deltas(NULL)triggersGIT_ASSERT_ARG(diff)inassert_safe.h. In release builds, this macro returns -1 rather than crashinggit_diff_num_deltasreturnssize_t(unsigned),-1becomes18446744073709551615(0xFFFFFFFFFFFFFFFF)[[NSMutableArray alloc] initWithCapacity:0xFFFFFFFFFFFFFFFF]throws the "capacity is ridiculous" exceptionAffected Code
GCDiff.m—diffWorkingDirectoryWithIndex:(line ~600):GCDiff.m—diffWorkingDirectoryWithCommit:usingIndex:(line ~562):Same pattern — also has a secondary bug where
git_diff_merge(*outDiff, diff2)is called with a NULLdiff2, then*outDiffis freed but the pointer is not NULLed out, causing a double-free in_diffWithType:'s cleanup.Proposed Fix
Primary fix: When
git_diff_index_to_workdirreturnsGIT_ELOCKED, retry without theGIT_DIFF_UPDATE_INDEXflag to obtain a valid diff:Secondary fix (defense-in-depth): Add a NULL check in
_cacheDeltasIfNeeded:Tertiary fix: In the
diffWorkingDirectoryWithCommit:block, NULL out*outDiffafter freeing it to prevent double-free:Environment