fix(watcher): never hand qsort a NULL array in the SVN observation core

A status document with no content candidates (only clean entries,
externals or property changes) left the candidate array NULL, and
fingerprint_candidates sorted it anyway. glibc declares qsort's base
nonnull even for a zero count, so the trapping UBSan leg aborts there;
macOS and recovering UBSan only print or ignore it. Sort only when there
are at least two candidates.

parse_clean_props_and_externals_are_not_local_changes reaches this path:
RED on the Linux arm64 GCC leg built with -fno-sanitize-recover=undefined
before the guard, GREEN after.

Refs #1127

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
This commit is contained in:
Martin Vogel
2026-10-21 00:34:35 +02:00
parent 0a5b30befa
commit 668dc5c889
2 changed files with 10 additions and 2 deletions
+6 -2
View File
@@ -1173,8 +1173,12 @@ static int walk_candidates(svn_walk_t *walk, candidate_list_t *candidates) {
}
static int fingerprint_candidates(parser_t *parser, cbm_svn_observation_t *observation) {
qsort(parser->candidates.paths, parser->candidates.count, sizeof(*parser->candidates.paths),
candidate_compare);
/* With no candidates the array is NULL, and glibc declares qsort's base
* nonnull even for a zero count. */
if (parser->candidates.count > 1) {
qsort(parser->candidates.paths, parser->candidates.count, sizeof(*parser->candidates.paths),
candidate_compare);
}
svn_walk_t walk = {0};
walk.root = parser->root;
int result = 0;
+4
View File
@@ -198,6 +198,10 @@ TEST(parse_rejects_declarations_and_truncated_documents) {
PASS();
}
/* No entry here needs its content read, so the candidate list stays empty:
* this also guards against sorting a NULL array. That guard binds only on the
* trapping UBSan leg (-fno-sanitize-recover=undefined); macOS and recovering
* UBSan pass without it. */
TEST(parse_clean_props_and_externals_are_not_local_changes) {
wc_fixture_t fixture;
ASSERT_TRUE(wc_fixture_init(&fixture, "cbm_svn_clean"));