From 1878b5edc03c4bf685c7278fc4ced4704c876984 Mon Sep 17 00:00:00 2001 From: Ævar Arnfjörð Bjarmason Date: Wed, 13 Apr 2022 22:01:35 +0200 Subject: revision.[ch]: provide and start using a release_revisions() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The users of the revision.[ch] API's "struct rev_info" are a major source of memory leaks in the test suite under SANITIZE=leak, which in turn adds a lot of noise when trying to mark up tests with "TEST_PASSES_SANITIZE_LEAK=true". The users of that API are largely one-shot, e.g. "git rev-list" or "git log", or the "git checkout" and "git stash" being modified here For these callers freeing the memory is arguably a waste of time, but in many cases they've actually been trying to free the memory, and just doing that in a buggy manner. Let's provide a release_revisions() function for these users, and start migrating them over per the plan outlined in [1]. Right now this only handles the "pending" member of the struct, but more will be added in subsequent commits. Even though we only clear the "pending" member now, let's not leave a trap in code like the pre-image of index_differs_from(), where we'd start doing the wrong thing as soon as the release_revisions() learned to clear its "diffopt". I.e. we need to call release_revisions() after we've inspected any state in "struct rev_info". This leaves in place e.g. clear_pathspec(&rev.prune_data) in stash_working_tree() in builtin/stash.c, subsequent commits will teach release_revisions() to free "prune_data" and other members that in some cases are individually cleared by users of "struct rev_info" by reaching into its members. Those subsequent commits will remove the relevant calls to e.g. clear_pathspec(). We avoid amending code in index_differs_from() in diff-lib.c as well as wt_status_collect_changes_index(), has_unstaged_changes() and has_uncommitted_changes() in wt-status.c in a way that assumes that we are already clearing the "diffopt" member. That will be handled in a subsequent commit. 1. https://lore.kernel.org/git/87a6k8daeu.fsf@evledraar.gmail.com/ Signed-off-by: Ævar Arnfjörð Bjarmason Signed-off-by: Junio C Hamano --- diff-lib.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'diff-lib.c') diff --git a/diff-lib.c b/diff-lib.c index ca085a03ef..d6800274bd 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -662,7 +662,7 @@ int index_differs_from(struct repository *r, diff_flags_or(&rev.diffopt.flags, flags); rev.diffopt.ita_invisible_in_index = ita_invisible_in_index; run_diff_index(&rev, 1); - object_array_clear(&rev.pending); + release_revisions(&rev); return (rev.diffopt.flags.has_changes != 0); } -- cgit v1.3 From f0cb6b8053eb9146e9d5255b6b0473d020c6e8bd Mon Sep 17 00:00:00 2001 From: Ævar Arnfjörð Bjarmason Date: Wed, 13 Apr 2022 22:01:44 +0200 Subject: revisions API users: use release_revisions() for "prune_data" users MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use release_revisions() for users of "struct rev_list" that reach into the "struct rev_info" and clear the "prune_data" already. In a subsequent commit we'll teach release_revisions() to clear this itself, but in the meantime let's invoke release_revisions() here to clear anything else we may have missed, and for reasons of having consistent boilerplate. Signed-off-by: Ævar Arnfjörð Bjarmason Signed-off-by: Junio C Hamano --- builtin/add.c | 1 + diff-lib.c | 1 + wt-status.c | 1 + 3 files changed, 3 insertions(+) (limited to 'diff-lib.c') diff --git a/builtin/add.c b/builtin/add.c index f507d2191c..115a26ea63 100644 --- a/builtin/add.c +++ b/builtin/add.c @@ -143,6 +143,7 @@ int add_files_to_cache(const char *prefix, rev.max_count = 0; /* do not compare unmerged paths with stage #2 */ run_diff_files(&rev, DIFF_RACY_IS_MODIFIED); clear_pathspec(&rev.prune_data); + release_revisions(&rev); return !!data.add_errors; } diff --git a/diff-lib.c b/diff-lib.c index d6800274bd..0f16281253 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -642,6 +642,7 @@ int do_diff_cache(const struct object_id *tree_oid, struct diff_options *opt) if (diff_cache(&revs, tree_oid, NULL, 1)) exit(128); clear_pathspec(&revs.prune_data); + release_revisions(&revs); return 0; } diff --git a/wt-status.c b/wt-status.c index f910062137..a14fad1e03 100644 --- a/wt-status.c +++ b/wt-status.c @@ -617,6 +617,7 @@ static void wt_status_collect_changes_worktree(struct wt_status *s) copy_pathspec(&rev.prune_data, &s->pathspec); run_diff_files(&rev, 0); clear_pathspec(&rev.prune_data); + release_revisions(&rev); } static void wt_status_collect_changes_index(struct wt_status *s) -- cgit v1.3 From 689a8e80dd4f7b3a20be88146bdea833c7759603 Mon Sep 17 00:00:00 2001 From: Ævar Arnfjörð Bjarmason Date: Wed, 13 Apr 2022 22:01:50 +0200 Subject: revisions API: have release_revisions() release "prune_data" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extend the the release_revisions() function so that it frees the "prune_data" in the "struct rev_info". This means that any code that calls "release_revisions()" already can get rid of adjacent calls to clear_pathspec(). Signed-off-by: Ævar Arnfjörð Bjarmason Signed-off-by: Junio C Hamano --- add-interactive.c | 2 -- builtin/add.c | 1 - builtin/stash.c | 2 -- diff-lib.c | 1 - revision.c | 1 + wt-status.c | 2 -- 6 files changed, 1 insertion(+), 8 deletions(-) (limited to 'diff-lib.c') diff --git a/add-interactive.c b/add-interactive.c index 54cdfc8201..6047e8f648 100644 --- a/add-interactive.c +++ b/add-interactive.c @@ -568,8 +568,6 @@ static int get_modified_files(struct repository *r, run_diff_files(&rev, 0); } - if (ps) - clear_pathspec(&rev.prune_data); release_revisions(&rev); } hashmap_clear_and_free(&s.file_map, struct pathname_entry, ent); diff --git a/builtin/add.c b/builtin/add.c index 115a26ea63..fc729e14c1 100644 --- a/builtin/add.c +++ b/builtin/add.c @@ -142,7 +142,6 @@ int add_files_to_cache(const char *prefix, rev.diffopt.flags.override_submodule_config = 1; rev.max_count = 0; /* do not compare unmerged paths with stage #2 */ run_diff_files(&rev, DIFF_RACY_IS_MODIFIED); - clear_pathspec(&rev.prune_data); release_revisions(&rev); return !!data.add_errors; } diff --git a/builtin/stash.c b/builtin/stash.c index 7c9c5751f5..e6b4c12ebb 100644 --- a/builtin/stash.c +++ b/builtin/stash.c @@ -1064,7 +1064,6 @@ static int check_changes_tracked_files(const struct pathspec *ps) } done: - clear_pathspec(&rev.prune_data); release_revisions(&rev); return ret; } @@ -1276,7 +1275,6 @@ static int stash_working_tree(struct stash_info *info, const struct pathspec *ps done: discard_index(&istate); - clear_pathspec(&rev.prune_data); release_revisions(&rev); strbuf_release(&diff_output); remove_path(stash_index_path.buf); diff --git a/diff-lib.c b/diff-lib.c index 0f16281253..298265e5b5 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -641,7 +641,6 @@ int do_diff_cache(const struct object_id *tree_oid, struct diff_options *opt) if (diff_cache(&revs, tree_oid, NULL, 1)) exit(128); - clear_pathspec(&revs.prune_data); release_revisions(&revs); return 0; } diff --git a/revision.c b/revision.c index 1db58c3e4d..6f444eaaeb 100644 --- a/revision.c +++ b/revision.c @@ -2949,6 +2949,7 @@ void release_revisions(struct rev_info *revs) object_array_clear(&revs->pending); release_revisions_cmdline(&revs->cmdline); list_objects_filter_release(&revs->filter); + clear_pathspec(&revs->prune_data); release_revisions_mailmap(revs->mailmap); free_grep_patterns(&revs->grep_filter); } diff --git a/wt-status.c b/wt-status.c index a14fad1e03..61e0c1022f 100644 --- a/wt-status.c +++ b/wt-status.c @@ -616,7 +616,6 @@ static void wt_status_collect_changes_worktree(struct wt_status *s) rev.diffopt.rename_score = s->rename_score >= 0 ? s->rename_score : rev.diffopt.rename_score; copy_pathspec(&rev.prune_data, &s->pathspec); run_diff_files(&rev, 0); - clear_pathspec(&rev.prune_data); release_revisions(&rev); } @@ -664,7 +663,6 @@ static void wt_status_collect_changes_index(struct wt_status *s) copy_pathspec(&rev.prune_data, &s->pathspec); run_diff_index(&rev, 1); release_revisions(&rev); - clear_pathspec(&rev.prune_data); } static int add_file_to_list(const struct object_id *oid, -- cgit v1.3 From 54c8a7c379fc37a847b8a5ec5c419eae171322e1 Mon Sep 17 00:00:00 2001 From: Ævar Arnfjörð Bjarmason Date: Thu, 14 Apr 2022 07:56:40 +0200 Subject: revisions API: add a TODO for diff_free(&revs->diffopt) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a TODO comment indicating that we should release "diffopt" in release_revisions(). In a preceding commit we started releasing the "pruning" member of the same type, but handling "diffopt" will require us to untangle the "no_free" conditions I added in e900d494dcf (diff: add an API for deferred freeing, 2021-02-11). Let's leave a TODO comment to that effect, and so that we don't forget refactor code that was changed to use release_revisions() in earlier commits to stop using the "diffopt" member after a call to release_revisions(). This works currently, but would become a logic error as soon as we started freeing "diffopt". Doing that change now doesn't harm anything, and future-proofs us against a later change to release_revisions(). Signed-off-by: Ævar Arnfjörð Bjarmason Signed-off-by: Junio C Hamano --- diff-lib.c | 4 +++- revision.c | 1 + wt-status.c | 6 ++++-- 3 files changed, 8 insertions(+), 3 deletions(-) (limited to 'diff-lib.c') diff --git a/diff-lib.c b/diff-lib.c index 298265e5b5..7eb66a417a 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -651,6 +651,7 @@ int index_differs_from(struct repository *r, { struct rev_info rev; struct setup_revision_opt opt; + unsigned has_changes; repo_init_revisions(r, &rev, NULL); memset(&opt, 0, sizeof(opt)); @@ -662,8 +663,9 @@ int index_differs_from(struct repository *r, diff_flags_or(&rev.diffopt.flags, flags); rev.diffopt.ita_invisible_in_index = ita_invisible_in_index; run_diff_index(&rev, 1); + has_changes = rev.diffopt.flags.has_changes; release_revisions(&rev); - return (rev.diffopt.flags.has_changes != 0); + return (has_changes != 0); } static struct strbuf *idiff_prefix_cb(struct diff_options *opt, void *data) diff --git a/revision.c b/revision.c index 0107ac1077..58d6212221 100644 --- a/revision.c +++ b/revision.c @@ -2956,6 +2956,7 @@ void release_revisions(struct rev_info *revs) date_mode_release(&revs->date_mode); release_revisions_mailmap(revs->mailmap); free_grep_patterns(&revs->grep_filter); + /* TODO (need to handle "no_free"): diff_free(&revs->diffopt) */ diff_free(&revs->pruning); reflog_walk_info_release(revs->reflog_info); release_revisions_topo_walk_info(revs->topo_walk_info); diff --git a/wt-status.c b/wt-status.c index 61e0c1022f..102d904adc 100644 --- a/wt-status.c +++ b/wt-status.c @@ -2545,8 +2545,9 @@ int has_unstaged_changes(struct repository *r, int ignore_submodules) rev_info.diffopt.flags.quick = 1; diff_setup_done(&rev_info.diffopt); result = run_diff_files(&rev_info, 0); + result = diff_result_code(&rev_info.diffopt, result); release_revisions(&rev_info); - return diff_result_code(&rev_info.diffopt, result); + return result; } /** @@ -2578,8 +2579,9 @@ int has_uncommitted_changes(struct repository *r, diff_setup_done(&rev_info.diffopt); result = run_diff_index(&rev_info, 1); + result = diff_result_code(&rev_info.diffopt, result); release_revisions(&rev_info); - return diff_result_code(&rev_info.diffopt, result); + return result; } /** -- cgit v1.3