[BUG] `git_xdiff` ignores `xdl_diff` errors and returns an empty diff · Issue #7420 · libgit2/libgit2 · GitHub
Skip to content

[BUG] git_xdiff ignores xdl_diff errors and returns an empty diff #7420

Open

Description

@bunch-suffice-ivy

libgit2 commit 07b89d1 src/libgit2/diff_xdiff.c:226.

Bug

git_xdiff discards the return value of xdl_diff and returns only the error recorded by the hunk/line callbacks:

xdl_diff(&info.xd_old_data, &info.xd_new_data,
    &xo->params, &xo->config, &xo->callback);        /* 226: result ignored */

git_diff_find_context_clear(&findctxt);

return xo->output.error;                            /* only set by callbacks */

When xdl_diff fails (for example out of memory while preparing the diff environment or building the edit script),
it returns -1 before any callback runs.
xo->output.error stays 0, so git_patch_from_buffers and every other text diff consumer (patch, diff, blame, merge) report success with no hunks.
There is no crash.

Reproduce

05.c installs a counting allocator through GIT_OPT_SET_ALLOCATOR that returns NULL for the Nth allocation, which looks exactly like a real OOM to libgit2 (xdl_malloc is git__malloc). It diffs two small buffers with git_patch_from_buffers, once normally and then with the Nth allocation failing, for N = 1..300, and prints every run that returns 0 with a different hunk count.

gcc -g -O0 05.c -I"$LIBGIT2_INCLUDE" "$LIBGIT2_LIBDIR/libgit2.a" -lz -lpthread -ldl -o 05_c
./05_c
reference: git_patch_from_buffers -> 0, hunks = 1
alloc #4: git_patch_from_buffers -> 0, hunks = 0 (expected 1)
alloc #5: git_patch_from_buffers -> 0, hunks = 0 (expected 1)
...
alloc #26: git_patch_from_buffers -> 0, hunks = 0 (expected 1)
5 clean errors, 23 wrong results reported as success

All 23 wrong results fail inside xdl_diff (xdl_prepare_env, xdl_do_diff, xdl_build_script). The 5 clean errors come from allocations before xdl_diff and from the hunk/line callbacks, whose errors git_xdiff does return.

05.c
#include <git2.h>
#include <git2/sys/alloc.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static long fail_at, count; /* fail the fail_at-th allocation; 0 = never */

static int should_fail(void) { return fail_at && ++count == fail_at; }

static void *x_malloc(size_t n, const char *file, int line)
{
	(void)file; (void)line;
	return should_fail() ? NULL : malloc(n ? n : 1);
}

static void *x_realloc(void *p, size_t n, const char *file, int line)
{
	(void)file; (void)line;
	return should_fail() ? NULL : realloc(p, n ? n : 1);
}

/* Diff OLD against NEW with the nth allocation failing; returns rc, sets *hunks. */
static int diff(long n, size_t *hunks)
{
	static const char OLD[] = "alpha\nbeta\ngamma\ndelta\n";
	static const char NEW[] = "alpha\nBETA\ngamma\nDELTA\nepsilon\n";
	git_patch *patch;
	int rc;

	fail_at = n, count = 0;
	rc = git_patch_from_buffers(&patch, OLD, strlen(OLD), NULL, NEW, strlen(NEW), NULL, NULL);
	fail_at = 0;
	if (rc == 0) {
		*hunks = git_patch_num_hunks(patch);
		git_patch_free(patch);
	}
	return rc;
}

int main(void)
{
	git_allocator alloc = { x_malloc, x_realloc, free };
	size_t good, hunks;
	int wrong = 0, clean = 0;
	long n;

	setvbuf(stdout, NULL, _IONBF, 0);
	if (git_libgit2_init() < 0 || git_libgit2_opts(GIT_OPT_SET_ALLOCATOR, &alloc) ||
	    diff(0, &good) || good == 0)
		return fprintf(stderr, "05_c: setup failed\n"), 1;
	printf("reference: git_patch_from_buffers -> 0, hunks = %zu\n", good);

	for (n = 1; n <= 300; n++) {
		if (diff(n, &hunks))
			clean++;
		else if (hunks != good)
			wrong++, printf("alloc #%ld: git_patch_from_buffers -> 0, hunks = %zu (expected %zu)\n",
			                n, hunks, good);
	}
	printf("%d clean errors, %d wrong results reported as success\n", clean, wrong);
	return wrong ? 0 : 1;
}

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions