object-name: free leaking object contexts

While it is documented in `struct object_context::path` that this variable needs to be released by the caller, this fact is rather easy to miss given that we do not ever provide a function to release the object context. And of course, while some callers dutifully release the path, many others don't. Introduce a new `object_context_release()` function that releases the path. Convert callsites that used to free the path to use that new function and add missing calls to callsites that were leaking memory. Refactor those callsites as required to have a single return path, only. Signed-off-by: Patrick Steinhardt <ps@pks.im> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Patrick Steinhardt committed Jun 11, 2024 at 11:19 UTC f87c55c2647cf3aa0e6b5e45738facb6b62fe37c
11 files changed +97 -47
builtin/cat-file.c
+11 -6
@@ -102,7 +102,7 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name,
102 enum object_type type;
103 char *buf;
104 unsigned long size;
105 - struct object_context obj_context;
105 + struct object_context obj_context = {0};
106 struct object_info oi = OBJECT_INFO_INIT;
107 struct strbuf sb = STRBUF_INIT;
108 unsigned flags = OBJECT_INFO_LOOKUP_REPLACE;
@@ -163,7 +163,8 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name,
163 goto cleanup;
164
165 case 'e':
166 - return !repo_has_object_file(the_repository, &oid);
166 + ret = !repo_has_object_file(the_repository, &oid);
167 + goto cleanup;
168
169 case 'w':
170
@@ -268,7 +269,7 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name,
269 ret = 0;
270 cleanup:
271 free(buf);
271 - free(obj_context.path);
272 + object_context_release(&obj_context);
273 return ret;
274 }
275
@@ -520,7 +521,7 @@ static void batch_one_object(const char *obj_name,
521 struct batch_options *opt,
522 struct expand_data *data)
523 {
523 - struct object_context ctx;
524 + struct object_context ctx = {0};
525 int flags =
526 GET_OID_HASH_ANY |
527 (opt->follow_symlinks ? GET_OID_FOLLOW_SYMLINKS : 0);
@@ -557,7 +558,8 @@ static void batch_one_object(const char *obj_name,
558 break;
559 }
560 fflush(stdout);
560 - return;
561 +
562 + goto out;
563 }
564
565 if (ctx.mode == 0) {
@@ -565,10 +567,13 @@ static void batch_one_object(const char *obj_name,
567 (uintmax_t)ctx.symlink_path.len,
568 opt->output_delim, ctx.symlink_path.buf, opt->output_delim);
569 fflush(stdout);
568 - return;
570 + goto out;
571 }
572
573 batch_object_write(obj_name, scratch, opt, data, NULL, 0);
574 +
575 +out:
576 + object_context_release(&ctx);
577 }
578
579 struct object_cb_data {
builtin/grep.c
+2 -2
@@ -1114,7 +1114,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
1114 for (i = 0; i < argc; i++) {
1115 const char *arg = argv[i];
1116 struct object_id oid;
1117 - struct object_context oc;
1117 + struct object_context oc = {0};
1118 struct object *object;
1119
1120 if (!strcmp(arg, "--")) {
@@ -1140,7 +1140,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
1140 if (!seen_dashdash)
1141 verify_non_filename(prefix, arg);
1142 add_object_array_with_path(object, arg, &list, oc.mode, oc.path);
1143 - free(oc.path);
1143 + object_context_release(&oc);
1144 }
1145
1146 /*
builtin/log.c
+3 -3
@@ -682,7 +682,7 @@ static void show_tagger(const char *buf, struct rev_info *rev)
682 static int show_blob_object(const struct object_id *oid, struct rev_info *rev, const char *obj_name)
683 {
684 struct object_id oidc;
685 - struct object_context obj_context;
685 + struct object_context obj_context = {0};
686 char *buf;
687 unsigned long size;
688
@@ -698,7 +698,7 @@ static int show_blob_object(const struct object_id *oid, struct rev_info *rev, c
698 if (!obj_context.path ||
699 !textconv_object(the_repository, obj_context.path,
700 obj_context.mode, &oidc, 1, &buf, &size)) {
701 - free(obj_context.path);
701 + object_context_release(&obj_context);
702 return stream_blob_to_fd(1, oid, NULL, 0);
703 }
704
@@ -706,7 +706,7 @@ static int show_blob_object(const struct object_id *oid, struct rev_info *rev, c
706 die(_("git show %s: bad file"), obj_name);
707
708 write_or_die(1, buf, size);
709 - free(obj_context.path);
709 + object_context_release(&obj_context);
710 return 0;
711 }
712
builtin/ls-tree.c
+2 -1
@@ -367,7 +367,7 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
367 OPT_END()
368 };
369 struct ls_tree_cmdmode_to_fmt *m2f = ls_tree_cmdmode_format;
370 - struct object_context obj_context;
370 + struct object_context obj_context = {0};
371 int ret;
372
373 git_config(git_default_config, NULL);
@@ -441,5 +441,6 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
441
442 ret = !!read_tree(the_repository, tree, &options.pathspec, fn, &options);
443 clear_pathspec(&options.pathspec);
444 + object_context_release(&obj_context);
445 return ret;
446 }
builtin/rev-parse.c
+2
@@ -1128,6 +1128,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
1128 }
1129 if (!get_oid_with_context(the_repository, name,
1130 flags, &oid, &unused)) {
1131 + object_context_release(&unused);
1132 if (output_algo)
1133 repo_oid_to_algop(the_repository, &oid,
1134 output_algo, &oid);
@@ -1137,6 +1138,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
1138 show_rev(type, &oid, name);
1139 continue;
1140 }
1141 + object_context_release(&unused);
1142 if (verify)
1143 die_no_single_rev(quiet);
1144 if (has_dashdash)
builtin/stash.c
+9 -3
@@ -1018,13 +1018,14 @@ static int store_stash(int argc, const char **argv, const char *prefix)
1018 int quiet = 0;
1019 const char *stash_msg = NULL;
1020 struct object_id obj;
1021 - struct object_context dummy;
1021 + struct object_context dummy = {0};
1022 struct option options[] = {
1023 OPT__QUIET(&quiet, N_("be quiet")),
1024 OPT_STRING('m', "message", &stash_msg, "message",
1025 N_("stash message")),
1026 OPT_END()
1027 };
1028 + int ret;
1029
1030 argc = parse_options(argc, argv, prefix, options,
1031 git_stash_store_usage,
@@ -1043,10 +1044,15 @@ static int store_stash(int argc, const char **argv, const char *prefix)
1044 if (!quiet)
1045 fprintf_ln(stderr, _("Cannot update %s with %s"),
1046 ref_stash, argv[0]);
1046 - return -1;
1047 + ret = -1;
1048 + goto out;
1049 }
1050
1049 - return do_store_stash(&obj, stash_msg, quiet);
1051 + ret = do_store_stash(&obj, stash_msg, quiet);
1052 +
1053 +out:
1054 + object_context_release(&dummy);
1055 + return ret;
1056 }
1057
1058 static void add_pathspecs(struct strvec *args,
list-objects-filter.c
+2
@@ -542,6 +542,8 @@ static void filter_sparse_oid__init(
542 filter->filter_data = d;
543 filter->filter_object_fn = filter_sparse;
544 filter->free_fn = filter_sparse_free;
545 +
546 + object_context_release(&oc);
547 }
548
549 /*
object-name.c
+29 -11
@@ -1757,6 +1757,11 @@ int strbuf_check_branch_ref(struct strbuf *sb, const char *name)
1757 return check_refname_format(sb->buf, 0);
1758 }
1759
1760 +void object_context_release(struct object_context *ctx)
1761 +{
1762 + free(ctx->path);
1763 +}
1764 +
1765 /*
1766 * This is like "get_oid_basic()", except it allows "object ID expressions",
1767 * notably "xyz^" for "parent of xyz"
@@ -1764,7 +1769,9 @@ int strbuf_check_branch_ref(struct strbuf *sb, const char *name)
1769 int repo_get_oid(struct repository *r, const char *name, struct object_id *oid)
1770 {
1771 struct object_context unused;
1767 - return get_oid_with_context(r, name, 0, oid, &unused);
1772 + int ret = get_oid_with_context(r, name, 0, oid, &unused);
1773 + object_context_release(&unused);
1774 + return ret;
1775 }
1776
1777 /*
@@ -1802,8 +1809,10 @@ int repo_get_oid_committish(struct repository *r,
1809 struct object_id *oid)
1810 {
1811 struct object_context unused;
1805 - return get_oid_with_context(r, name, GET_OID_COMMITTISH,
1806 - oid, &unused);
1812 + int ret = get_oid_with_context(r, name, GET_OID_COMMITTISH,
1813 + oid, &unused);
1814 + object_context_release(&unused);
1815 + return ret;
1816 }
1817
1818 int repo_get_oid_treeish(struct repository *r,
@@ -1811,8 +1820,10 @@ int repo_get_oid_treeish(struct repository *r,
1820 struct object_id *oid)
1821 {
1822 struct object_context unused;
1814 - return get_oid_with_context(r, name, GET_OID_TREEISH,
1815 - oid, &unused);
1823 + int ret = get_oid_with_context(r, name, GET_OID_TREEISH,
1824 + oid, &unused);
1825 + object_context_release(&unused);
1826 + return ret;
1827 }
1828
1829 int repo_get_oid_commit(struct repository *r,
@@ -1820,8 +1831,10 @@ int repo_get_oid_commit(struct repository *r,
1831 struct object_id *oid)
1832 {
1833 struct object_context unused;
1823 - return get_oid_with_context(r, name, GET_OID_COMMIT,
1824 - oid, &unused);
1834 + int ret = get_oid_with_context(r, name, GET_OID_COMMIT,
1835 + oid, &unused);
1836 + object_context_release(&unused);
1837 + return ret;
1838 }
1839
1840 int repo_get_oid_tree(struct repository *r,
@@ -1829,8 +1842,10 @@ int repo_get_oid_tree(struct repository *r,
1842 struct object_id *oid)
1843 {
1844 struct object_context unused;
1832 - return get_oid_with_context(r, name, GET_OID_TREE,
1833 - oid, &unused);
1845 + int ret = get_oid_with_context(r, name, GET_OID_TREE,
1846 + oid, &unused);
1847 + object_context_release(&unused);
1848 + return ret;
1849 }
1850
1851 int repo_get_oid_blob(struct repository *r,
@@ -1838,8 +1853,10 @@ int repo_get_oid_blob(struct repository *r,
1853 struct object_id *oid)
1854 {
1855 struct object_context unused;
1841 - return get_oid_with_context(r, name, GET_OID_BLOB,
1842 - oid, &unused);
1856 + int ret = get_oid_with_context(r, name, GET_OID_BLOB,
1857 + oid, &unused);
1858 + object_context_release(&unused);
1859 + return ret;
1860 }
1861
1862 /* Must be called only when object_name:filename doesn't exist. */
@@ -2117,6 +2134,7 @@ void maybe_die_on_misspelt_object_name(struct repository *r,
2134 struct object_id oid;
2135 get_oid_with_context_1(r, name, GET_OID_ONLY_TO_DIE | GET_OID_QUIETLY,
2136 prefix, &oid, &oc);
2137 + object_context_release(&oc);
2138 }
2139
2140 enum get_oid_result get_oid_with_context(struct repository *repo,
object-name.h
+2
@@ -22,6 +22,8 @@ struct object_context {
22 char *path;
23 };
24
25 +void object_context_release(struct object_context *ctx);
26 +
27 /*
28 * Return an abbreviated sha1 unique within this repository's object database.
29 * The result will be at least `len` characters long, and will be NUL
revision.c
+34 -21
@@ -2130,30 +2130,26 @@ static int handle_dotdot(const char *arg,
2130 struct rev_info *revs, int flags,
2131 int cant_be_filename)
2132 {
2133 - struct object_context a_oc, b_oc;
2133 + struct object_context a_oc = {0}, b_oc = {0};
2134 char *dotdot = strstr(arg, "..");
2135 int ret;
2136
2137 if (!dotdot)
2138 return -1;
2139
2140 - memset(&a_oc, 0, sizeof(a_oc));
2141 - memset(&b_oc, 0, sizeof(b_oc));
2142 -
2140 *dotdot = '\0';
2141 ret = handle_dotdot_1(arg, dotdot, revs, flags, cant_be_filename,
2142 &a_oc, &b_oc);
2143 *dotdot = '.';
2144
2148 - free(a_oc.path);
2149 - free(b_oc.path);
2150 -
2145 + object_context_release(&a_oc);
2146 + object_context_release(&b_oc);
2147 return ret;
2148 }
2149
2150 static int handle_revision_arg_1(const char *arg_, struct rev_info *revs, int flags, unsigned revarg_opt)
2151 {
2156 - struct object_context oc;
2152 + struct object_context oc = {0};
2153 char *mark;
2154 struct object *object;
2155 struct object_id oid;
@@ -2161,6 +2157,7 @@ static int handle_revision_arg_1(const char *arg_, struct rev_info *revs, int fl
2157 const char *arg = arg_;
2158 int cant_be_filename = revarg_opt & REVARG_CANNOT_BE_FILENAME;
2159 unsigned get_sha1_flags = GET_OID_RECORD_PATH;
2160 + int ret;
2161
2162 flags = flags & UNINTERESTING ? flags | BOTTOM : flags & ~BOTTOM;
2163
@@ -2169,17 +2166,22 @@ static int handle_revision_arg_1(const char *arg_, struct rev_info *revs, int fl
2166 * Just ".."? That is not a range but the
2167 * pathspec for the parent directory.
2168 */
2172 - return -1;
2169 + ret = -1;
2170 + goto out;
2171 }
2172
2175 - if (!handle_dotdot(arg, revs, flags, revarg_opt))
2176 - return 0;
2173 + if (!handle_dotdot(arg, revs, flags, revarg_opt)) {
2174 + ret = 0;
2175 + goto out;
2176 + }
2177
2178 mark = strstr(arg, "^@");
2179 if (mark && !mark[2]) {
2180 *mark = 0;
2181 - if (add_parents_only(revs, arg, flags, 0))
2182 - return 0;
2181 + if (add_parents_only(revs, arg, flags, 0)) {
2182 + ret = 0;
2183 + goto out;
2184 + }
2185 *mark = '^';
2186 }
2187 mark = strstr(arg, "^!");
@@ -2194,8 +2196,10 @@ static int handle_revision_arg_1(const char *arg_, struct rev_info *revs, int fl
2196
2197 if (mark[2]) {
2198 if (strtol_i(mark + 2, 10, &exclude_parent) ||
2197 - exclude_parent < 1)
2198 - return -1;
2199 + exclude_parent < 1) {
2200 + ret = -1;
2201 + goto out;
2202 + }
2203 }
2204
2205 *mark = 0;
@@ -2217,17 +2221,25 @@ static int handle_revision_arg_1(const char *arg_, struct rev_info *revs, int fl
2221 * should error out if we can't even get an oid, as
2222 * `--missing=print` should be able to report missing oids.
2223 */
2220 - if (get_oid_with_context(revs->repo, arg, get_sha1_flags, &oid, &oc))
2221 - return revs->ignore_missing ? 0 : -1;
2224 + if (get_oid_with_context(revs->repo, arg, get_sha1_flags, &oid, &oc)) {
2225 + ret = revs->ignore_missing ? 0 : -1;
2226 + goto out;
2227 + }
2228 if (!cant_be_filename)
2229 verify_non_filename(revs->prefix, arg);
2230 object = get_reference(revs, arg, &oid, flags ^ local_flags);
2225 - if (!object)
2226 - return (revs->ignore_missing || revs->do_not_die_on_missing_objects) ? 0 : -1;
2231 + if (!object) {
2232 + ret = (revs->ignore_missing || revs->do_not_die_on_missing_objects) ? 0 : -1;
2233 + goto out;
2234 + }
2235 add_rev_cmdline(revs, object, arg_, REV_CMD_REV, flags ^ local_flags);
2236 add_pending_object_with_path(revs, object, arg, oc.mode, oc.path);
2229 - free(oc.path);
2230 - return 0;
2237 +
2238 + ret = 0;
2239 +
2240 +out:
2241 + object_context_release(&oc);
2242 + return ret;
2243 }
2244
2245 int handle_revision_arg(const char *arg, struct rev_info *revs, int flags, unsigned revarg_opt)
@@ -3062,6 +3074,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
3074 diagnose_missing_default(revs->def);
3075 object = get_reference(revs, revs->def, &oid, 0);
3076 add_pending_object_with_mode(revs, object, revs->def, oc.mode);
3077 + object_context_release(&oc);
3078 }
3079
3080 /* Did the user ask for any diff output? Run the diff! */
t/t7012-skip-worktree-writing.sh
+1
@@ -5,6 +5,7 @@
5
6 test_description='test worktree writing operations when skip-worktree is used'
7
8 +TEST_PASSES_SANITIZE_LEAK=true
9 . ./test-lib.sh
10
11 test_expect_success 'setup' '