Skip to content

Commit eceba53

Browse files
newrengitster
authored andcommitted
dir: fix problematic API to avoid memory leaks
The dir structure seemed to have a number of leaks and problems around it. First I noticed that parent_hashmap and recursive_hashmap were being leaked (though Peff noticed and submitted fixes before me). Then I noticed in the previous commit that clear_directory() was only taking responsibility for a subset of fields within dir_struct, despite the fact that entries[] and ignored[] we allocated internally to dir.c. That, of course, resulted in many callers either leaking or haphazardly trying to free these arrays and their contents. Digging further, I found that despite the pretty clear documentation near the top of dir.h that folks were supposed to call clear_directory() when the user no longer needed the dir_struct, there were four callers that didn't bother doing that at all. However, two of them clearly thought about leaks since they had an UNLEAK(dir) directive, which to me suggests that the method to free the data was too unclear. I suspect the non-obviousness of the API and its holes led folks to avoid it, which then snowballed into further problems with the entries[], ignored[], parent_hashmap, and recursive_hashmap problems. Rename clear_directory() to dir_clear() to be more in line with other data structures in git, and introduce a dir_init() to handle the suggested memsetting of dir_struct to all zeroes. I hope that a name like "dir_clear()" is more clear, and that the presence of dir_init() will provide a hint to those looking at the code that they need to look for either a dir_clear() or a dir_free() and lead them to find dir_clear(). Signed-off-by: Elijah Newren <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent dad4f23 commit eceba53

File tree

10 files changed

+36
-28
lines changed

10 files changed

+36
-28
lines changed

builtin/add.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -534,11 +534,11 @@ int cmd_add(int argc, const char **argv, const char *prefix)
534534
die_in_unpopulated_submodule(&the_index, prefix);
535535
die_path_inside_submodule(&the_index, &pathspec);
536536

537+
dir_init(&dir);
537538
if (add_new_files) {
538539
int baselen;
539540

540541
/* Set up the default git porcelain excludes */
541-
memset(&dir, 0, sizeof(dir));
542542
if (!ignored_too) {
543543
dir.flags |= DIR_COLLECT_IGNORED;
544544
setup_standard_excludes(&dir);
@@ -611,7 +611,7 @@ int cmd_add(int argc, const char **argv, const char *prefix)
611611
COMMIT_LOCK | SKIP_IF_UNCHANGED))
612612
die(_("Unable to write new index file"));
613613

614+
dir_clear(&dir);
614615
UNLEAK(pathspec);
615-
UNLEAK(dir);
616616
return exit_status;
617617
}

builtin/check-ignore.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ int cmd_check_ignore(int argc, const char **argv, const char *prefix)
180180
if (!no_index && read_cache() < 0)
181181
die(_("index file corrupt"));
182182

183-
memset(&dir, 0, sizeof(dir));
183+
dir_init(&dir);
184184
setup_standard_excludes(&dir);
185185

186186
if (stdin_paths) {
@@ -190,7 +190,7 @@ int cmd_check_ignore(int argc, const char **argv, const char *prefix)
190190
maybe_flush_or_die(stdout, "ignore to stdout");
191191
}
192192

193-
clear_directory(&dir);
193+
dir_clear(&dir);
194194

195195
return !num_ignored;
196196
}

builtin/clean.c

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -667,7 +667,7 @@ static int filter_by_patterns_cmd(void)
667667
if (!confirm.len)
668668
break;
669669

670-
memset(&dir, 0, sizeof(dir));
670+
dir_init(&dir);
671671
pl = add_pattern_list(&dir, EXC_CMDL, "manual exclude");
672672
ignore_list = strbuf_split_max(&confirm, ' ', 0);
673673

@@ -698,7 +698,7 @@ static int filter_by_patterns_cmd(void)
698698
}
699699

700700
strbuf_list_free(ignore_list);
701-
clear_directory(&dir);
701+
dir_clear(&dir);
702702
}
703703

704704
strbuf_release(&confirm);
@@ -923,7 +923,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
923923
argc = parse_options(argc, argv, prefix, options, builtin_clean_usage,
924924
0);
925925

926-
memset(&dir, 0, sizeof(dir));
926+
dir_init(&dir);
927927
if (!interactive && !dry_run && !force) {
928928
if (config_set)
929929
die(_("clean.requireForce set to true and neither -i, -n, nor -f given; "
@@ -1021,7 +1021,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
10211021
string_list_append(&del_list, rel);
10221022
}
10231023

1024-
clear_directory(&dir);
1024+
dir_clear(&dir);
10251025

10261026
if (interactive && del_list.nr > 0)
10271027
interactive_main_loop();

builtin/grep.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -693,7 +693,7 @@ static int grep_directory(struct grep_opt *opt, const struct pathspec *pathspec,
693693
struct dir_struct dir;
694694
int i, hit = 0;
695695

696-
memset(&dir, 0, sizeof(dir));
696+
dir_init(&dir);
697697
if (!use_index)
698698
dir.flags |= DIR_NO_GITLINKS;
699699
if (exc_std)
@@ -705,6 +705,7 @@ static int grep_directory(struct grep_opt *opt, const struct pathspec *pathspec,
705705
if (hit && opt->status_only)
706706
break;
707707
}
708+
dir_clear(&dir);
708709
return hit;
709710
}
710711

builtin/ls-files.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -584,7 +584,7 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
584584
if (argc == 2 && !strcmp(argv[1], "-h"))
585585
usage_with_options(ls_files_usage, builtin_ls_files_options);
586586

587-
memset(&dir, 0, sizeof(dir));
587+
dir_init(&dir);
588588
prefix = cmd_prefix;
589589
if (prefix)
590590
prefix_len = strlen(prefix);
@@ -688,6 +688,6 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
688688
return bad ? 1 : 0;
689689
}
690690

691-
UNLEAK(dir);
691+
dir_clear(&dir);
692692
return 0;
693693
}

builtin/stash.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -864,7 +864,7 @@ static int get_untracked_files(const struct pathspec *ps, int include_untracked,
864864
int found = 0;
865865
struct dir_struct dir;
866866

867-
memset(&dir, 0, sizeof(dir));
867+
dir_init(&dir);
868868
if (include_untracked != INCLUDE_ALL_FILES)
869869
setup_standard_excludes(&dir);
870870

@@ -877,7 +877,7 @@ static int get_untracked_files(const struct pathspec *ps, int include_untracked,
877877
strbuf_addch(untracked_files, '\0');
878878
}
879879

880-
clear_directory(&dir);
880+
dir_clear(&dir);
881881
return found;
882882
}
883883

dir.c

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,11 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir,
5454
static int resolve_dtype(int dtype, struct index_state *istate,
5555
const char *path, int len);
5656

57+
void dir_init(struct dir_struct *dir)
58+
{
59+
memset(dir, 0, sizeof(*dir));
60+
}
61+
5762
int count_slashes(const char *s)
5863
{
5964
int cnt = 0;
@@ -3012,7 +3017,7 @@ int remove_path(const char *name)
30123017
* Frees memory within dir which was allocated, and resets fields for further
30133018
* use. Does not free dir itself.
30143019
*/
3015-
void clear_directory(struct dir_struct *dir)
3020+
void dir_clear(struct dir_struct *dir)
30163021
{
30173022
int i, j;
30183023
struct exclude_list_group *group;
@@ -3045,7 +3050,7 @@ void clear_directory(struct dir_struct *dir)
30453050
}
30463051
strbuf_release(&dir->basebuf);
30473052

3048-
memset(&dir, 0, sizeof(*dir));
3053+
dir_init(dir);
30493054
}
30503055

30513056
struct ondisk_untracked_cache {

dir.h

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,24 +19,23 @@
1919
* CE_SKIP_WORKTREE marked. If you want to exclude files, make sure you have
2020
* loaded the index first.
2121
*
22-
* - Prepare `struct dir_struct dir` and clear it with `memset(&dir, 0,
23-
* sizeof(dir))`.
22+
* - Prepare `struct dir_struct dir` using `dir_init()` function.
2423
*
2524
* - To add single exclude pattern, call `add_pattern_list()` and then
2625
* `add_pattern()`.
2726
*
2827
* - To add patterns from a file (e.g. `.git/info/exclude`), call
29-
* `add_patterns_from_file()` , and/or set `dir.exclude_per_dir`. A
30-
* short-hand function `setup_standard_excludes()` can be used to set
31-
* up the standard set of exclude settings.
28+
* `add_patterns_from_file()` , and/or set `dir.exclude_per_dir`.
3229
*
33-
* - Set options described in the Data Structure section above.
30+
* - A short-hand function `setup_standard_excludes()` can be used to set
31+
* up the standard set of exclude settings, instead of manually calling
32+
* the add_pattern*() family of functions.
3433
*
35-
* - Call `read_directory()`.
34+
* - Call `fill_directory()`.
3635
*
37-
* - Use `dir.entries[]`.
36+
* - Use `dir.entries[]` and `dir.ignored[]`.
3837
*
39-
* - Call `clear_directory()` when the contained elements are no longer in use.
38+
* - Call `dir_clear()` when the contained elements are no longer in use.
4039
*
4140
*/
4241

@@ -362,6 +361,8 @@ int match_pathspec(const struct index_state *istate,
362361
int report_path_error(const char *ps_matched, const struct pathspec *pathspec);
363362
int within_depth(const char *name, int namelen, int depth, int max_depth);
364363

364+
void dir_init(struct dir_struct *dir);
365+
365366
int fill_directory(struct dir_struct *dir,
366367
struct index_state *istate,
367368
const struct pathspec *pathspec);
@@ -428,7 +429,7 @@ void parse_path_pattern(const char **string, int *patternlen, unsigned *flags, i
428429
void add_pattern(const char *string, const char *base,
429430
int baselen, struct pattern_list *pl, int srcpos);
430431
void clear_pattern_list(struct pattern_list *pl);
431-
void clear_directory(struct dir_struct *dir);
432+
void dir_clear(struct dir_struct *dir);
432433

433434
int repo_file_exists(struct repository *repo, const char *path);
434435
int file_exists(const char *);

merge.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,8 @@ int checkout_fast_forward(struct repository *r,
8080
}
8181

8282
memset(&opts, 0, sizeof(opts));
83+
dir_init(&dir);
8384
if (overwrite_ignore) {
84-
memset(&dir, 0, sizeof(dir));
8585
dir.flags |= DIR_SHOW_IGNORED;
8686
setup_standard_excludes(&dir);
8787
opts.dir = &dir;
@@ -102,6 +102,7 @@ int checkout_fast_forward(struct repository *r,
102102
clear_unpack_trees_porcelain(&opts);
103103
return -1;
104104
}
105+
dir_clear(&dir);
105106
clear_unpack_trees_porcelain(&opts);
106107

107108
if (write_locked_index(r->index, &lock_file, COMMIT_LOCK))

wt-status.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -703,7 +703,7 @@ static void wt_status_collect_untracked(struct wt_status *s)
703703
if (!s->show_untracked_files)
704704
return;
705705

706-
memset(&dir, 0, sizeof(dir));
706+
dir_init(&dir);
707707
if (s->show_untracked_files != SHOW_ALL_UNTRACKED_FILES)
708708
dir.flags |=
709709
DIR_SHOW_OTHER_DIRECTORIES | DIR_HIDE_EMPTY_DIRECTORIES;
@@ -732,7 +732,7 @@ static void wt_status_collect_untracked(struct wt_status *s)
732732
string_list_insert(&s->ignored, ent->name);
733733
}
734734

735-
clear_directory(&dir);
735+
dir_clear(&dir);
736736

737737
if (advice_status_u_option)
738738
s->untracked_in_ms = (getnanotime() - t_begin) / 1000000;

0 commit comments

Comments
 (0)