Re: [PATCH 03/11] submodule--helper: fix "module_clone_data" memory leaks
- From
Ævar Arnfjörð Bjarmason <avarab@gmail.com>
- Date
- Jul 13, 2022, 20:30 UTC
- Message-ID
- <220713.86o7xs3g76.gmgdl@evledraar.gmail.com>
- In-Reply-To
- <kl6lilo06g82.fsf@chooglen-macbookpro.roam.corp.google.com>
On Wed, Jul 13 2022, Glen Choo wrote:
Show 66 quoted lines
> Glen Choo <chooglen@google.com> writes:
>
>> Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:
>>
>>> diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
>>> index 73717be957c..23ab9c7e349 100644
>>> --- a/builtin/submodule--helper.c
>>> +++ b/builtin/submodule--helper.c
>>> @@ -1511,6 +1511,7 @@ static int module_deinit(int argc, const char **argv, const char *prefix)
>>> struct module_clone_data {
>>> const char *prefix;
>>> const char *path;
>>> + char *path_free;
>>> const char *name;
>>> const char *url;
>>> const char *depth;
>>> @@ -1527,6 +1528,11 @@ struct module_clone_data {
>>> .single_branch = -1, \
>>> }
>>>
>>> +static void module_clone_data_release(struct module_clone_data *cd)
>>> +{
>>> + free(cd->path_free);
>>> +}
>>> +
>>> struct submodule_alternate_setup {
>>> const char *submodule_name;
>>> enum SUBMODULE_ALTERNATE_ERROR_MODE {
>>> @@ -1651,9 +1657,9 @@ static int clone_submodule(struct module_clone_data *clone_data)
>>>
>>> if (!is_absolute_path(clone_data->path)) {
>>> strbuf_addf(&sb, "%s/%s", get_git_work_tree(), clone_data->path);
>>> - clone_data->path = strbuf_detach(&sb, NULL);
>>> + clone_data->path = clone_data->path_free = strbuf_detach(&sb, NULL);
>>> } else {
>>> - clone_data->path = xstrdup(clone_data->path);
>>> + clone_data->path = clone_data->path_free = xstrdup(clone_data->path);
>>> }
>>
>> Hm, having .path_free doesn't seem necessary. If I'm reading the code
>> correctly,
>>
>> - in module_clone() we set clone_data.path from argv
>> - then we unconditionally call clone_submodule(), which does all of the
>> hard work
>> - in clone_submodule(), we always hit this conditional, which means that
>> past this point, clone_data.path is always free()-able.
>>
>> which suggests that we don't need clone_data.path_free at all. I suspect
>> that just this
>>
>> static void module_clone_data_release(struct module_clone_data *cd)
>> {
>> free(cd->path);
>> }
>>
>> is enough to plug the leak (though admittedly, I haven't properly tested
>> the leak because it's been a PITA to set up locally).
>>
>> That's a pretty hacky suggestion though, since we're still using the
>> same member to hold free()-able and non-free()-able memory....
>
> Ah, here's a wacky idea (whose feasibility I haven't thought about at
> all) that would make things a lot cleaner. If we had something like
> OPT_STRING_DUP, that xstrdup()-s the value from argv when we parse it,
> then we could drop the "const" from clone_data.path and just free() it.I suppose so, it might make some things simpler, of course at the cost of needlessly duplicating things.
But we also have various common patterns such as string_lists where some elements are dup'd, some aren't, and need to deal with that. I think just having common idioms for tracking the dupe is usually better, e.g. in the case of a string list stick the pointer to free in "util".
I think in this case the patch as-is is better than your suggestions, because it's a less fragile pattern, we explicitly mark when we dup something that's sometimes a dup, and sometimes not.
Whereas if we do it with the xstrdup() at the start it requires more moving things around, and if we have a new user who parses the same argument we'll bug out on that free().
What do you think?