diff mbox series

[v4,03/23] sequencer: stop leaking buf

Message ID 76585e5b1367a3adf18d761b2af9356ee64b46fd.1585962672.git.liu.denton@gmail.com (mailing list archive)
State New, archived
Headers show
Series merge: learn --autostash | expand

Commit Message

Denton Liu April 4, 2020, 1:11 a.m. UTC
In read_populate_opts(), we call read_oneliner() _after_ calling
strbuf_release(). This means that `buf` is leaked at the end of the
function.

Always clean up the strbuf by going to `done_rebase_i` whether or not
we return an error.

Signed-off-by: Denton Liu <liu.denton@gmail.com>
---
 sequencer.c | 13 +++++++++----
 1 file changed, 9 insertions(+), 4 deletions(-)

Comments

Junio C Hamano April 5, 2020, 9:33 p.m. UTC | #1
Denton Liu <liu.denton@gmail.com> writes:

> In read_populate_opts(), we call read_oneliner() _after_ calling
> strbuf_release(). This means that `buf` is leaked at the end of the
> function.

I do not think the above makes much sense.  _release() will free the
piece of memory occupied by .buf and reinitializes the strbuf, and
in doing so there is no leak.  read_oneliner() called after it
allocates and reads into there.  Freeing the resource needs to be
done after the caller is done with what read_oneliner() has read.

There is a leak, because read_oneliner() gets called and before the
code has a chance to do strbuf_reease() there is an error return.
That does not have anything to do with the call to strbuf_release()
in the middle of the function.

But that leak has nothing to do with the release called before
read_oneliner().

> Always clean up the strbuf by going to `done_rebase_i` whether or not
> we return an error.
>
> Signed-off-by: Denton Liu <liu.denton@gmail.com>
> ---
>  sequencer.c | 13 +++++++++----
>  1 file changed, 9 insertions(+), 4 deletions(-)
>
> diff --git a/sequencer.c b/sequencer.c
> index e528225e78..faab0b13e8 100644
> --- a/sequencer.c
> +++ b/sequencer.c
> @@ -2485,6 +2485,7 @@ static int read_populate_opts(struct replay_opts *opts)
>  {
>  	if (is_rebase_i(opts)) {
>  		struct strbuf buf = STRBUF_INIT;
> +		int ret = 0;
>  
>  		if (read_oneliner(&buf, rebase_path_gpg_sign_opt(), 1)) {
>  			if (!starts_with(buf.buf, "-S"))
> @@ -2525,7 +2526,7 @@ static int read_populate_opts(struct replay_opts *opts)
>  			opts->keep_redundant_commits = 1;
>  
>  		read_strategy_opts(opts, &buf);
> -		strbuf_release(&buf);
> +		strbuf_reset(&buf);

As read_oneliner() has a strbuf_reset() at the beginning *anyway*,
why not just get rid of the call to _release() here instead?  After
all, there is no _release() or _reset() before the call to read_oneliner()
in the next hunk, or two calls to read_oneliner() inside the
read_strategy_opts() function called in the above.

>  		if (read_oneliner(&opts->current_fixups,
>  				  rebase_path_current_fixups(), 1)) {
> @@ -2538,12 +2539,16 @@ static int read_populate_opts(struct replay_opts *opts)
>  		}
>  
>  		if (read_oneliner(&buf, rebase_path_squash_onto(), 0)) {
> -			if (get_oid_hex(buf.buf, &opts->squash_onto) < 0)
> -				return error(_("unusable squash-onto"));
> +			if (get_oid_hex(buf.buf, &opts->squash_onto) < 0) {
> +				ret = error(_("unusable squash-onto"));
> +				goto done_rebase_i;
> +			}
>  			opts->have_squash_onto = 1;
>  		}
>  
> -		return 0;
> +done_rebase_i:
> +		strbuf_release(&buf);
> +		return ret;

This part indeed IS the right fix to the existing leak.

>  	}
>  
>  	if (!file_exists(git_path_opts_file()))
Junio C Hamano April 5, 2020, 9:37 p.m. UTC | #2
Junio C Hamano <gitster@pobox.com> writes:

>>  		read_strategy_opts(opts, &buf);
>> -		strbuf_release(&buf);
>> +		strbuf_reset(&buf);
>
> As read_oneliner() has a strbuf_reset() at the beginning *anyway*,
> why not just get rid of the call to _release() here instead?  After
> all, there is no _release() or _reset() before the call to read_oneliner()
> in the next hunk, or two calls to read_oneliner() inside the
> read_strategy_opts() function called in the above.

Ah, I was reading the state of applying the series thru to the end.
At this point, there is no reset there, so you'd need to call reset
to ensure that the next read_oneliner() on &buf will not append, but
restart from the beginning.

>>  		if (read_oneliner(&buf, rebase_path_squash_onto(), 0)) {
>> -			if (get_oid_hex(buf.buf, &opts->squash_onto) < 0)
>> -				return error(_("unusable squash-onto"));
Denton Liu April 5, 2020, 11:42 p.m. UTC | #3
Hi Junio,

On Sun, Apr 05, 2020 at 02:33:08PM -0700, Junio C Hamano wrote:
> Denton Liu <liu.denton@gmail.com> writes:
> 
> > In read_populate_opts(), we call read_oneliner() _after_ calling
> > strbuf_release(). This means that `buf` is leaked at the end of the
> > function.
> 
> I do not think the above makes much sense.  _release() will free the
> piece of memory occupied by .buf and reinitializes the strbuf, and
> in doing so there is no leak.  read_oneliner() called after it
> allocates and reads into there.  Freeing the resource needs to be
> done after the caller is done with what read_oneliner() has read.
> 
> There is a leak, because read_oneliner() gets called and before the
> code has a chance to do strbuf_reease() there is an error return.
> That does not have anything to do with the call to strbuf_release()
> in the middle of the function.

There is also a leak in the case where we don't take the early return
since, before this patch, the read_oneliner() has no strbuf_release()
following it which means buf gets leaked.

> But that leak has nothing to do with the release called before
> read_oneliner().

What I meant to say in my paragraph is essentially, "strbuf_release() is
placed too early, there is a read_oneliner() after that nullifies the
effects of strbuf_release()". I can rewrite this to be more clear.

Thanks,

Denton
diff mbox series

Patch

diff --git a/sequencer.c b/sequencer.c
index e528225e78..faab0b13e8 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -2485,6 +2485,7 @@  static int read_populate_opts(struct replay_opts *opts)
 {
 	if (is_rebase_i(opts)) {
 		struct strbuf buf = STRBUF_INIT;
+		int ret = 0;
 
 		if (read_oneliner(&buf, rebase_path_gpg_sign_opt(), 1)) {
 			if (!starts_with(buf.buf, "-S"))
@@ -2525,7 +2526,7 @@  static int read_populate_opts(struct replay_opts *opts)
 			opts->keep_redundant_commits = 1;
 
 		read_strategy_opts(opts, &buf);
-		strbuf_release(&buf);
+		strbuf_reset(&buf);
 
 		if (read_oneliner(&opts->current_fixups,
 				  rebase_path_current_fixups(), 1)) {
@@ -2538,12 +2539,16 @@  static int read_populate_opts(struct replay_opts *opts)
 		}
 
 		if (read_oneliner(&buf, rebase_path_squash_onto(), 0)) {
-			if (get_oid_hex(buf.buf, &opts->squash_onto) < 0)
-				return error(_("unusable squash-onto"));
+			if (get_oid_hex(buf.buf, &opts->squash_onto) < 0) {
+				ret = error(_("unusable squash-onto"));
+				goto done_rebase_i;
+			}
 			opts->have_squash_onto = 1;
 		}
 
-		return 0;
+done_rebase_i:
+		strbuf_release(&buf);
+		return ret;
 	}
 
 	if (!file_exists(git_path_opts_file()))