git.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH] reset slab_alloc and state fields in clear_alloc_state()
@ 2025-08-26 19:57 ノウラ | Flare via GitGitGadget
  2025-08-27  2:07 ` Junio C Hamano
  2025-08-27 23:28 ` [PATCH v2] alloc: fix dangling pointer in alloc_state cleanup ノウラ | Flare via GitGitGadget
  0 siblings, 2 replies; 28+ messages in thread
From: ノウラ | Flare via GitGitGadget @ 2025-08-26 19:57 UTC (permalink / raw)
  To: git; +Cc: ノウラ | Flare, ノウラ

From: =?UTF-8?q?=E3=83=8E=E3=82=A6=E3=83=A9?= <nea@odoo.com>

clear_alloc_state() freed all slabs and nulled the slabs pointer but
left slab_alloc, nr, and p unchanged.  If the alloc_state is reused,
ALLOC_GROW() can wrongly assume that the slab array is already
allocated because slab_alloc still holds a stale nonzero capacity.
In that case s->slabs remains NULL and the next dereference writes
through a NULL pointer, causing undefined behavior.

To fix this, we reset slab_alloc, nr, and p to zero/NULL after
freeing the slabs.  This leaves alloc_state in a consistent empty
state for reuse and avoids dangling pointers.

Signed-off-by: Noura EL ALLAM <nouraellm@gmail.com>
---
    Reset slab_alloc and state fields in clear_alloc_state()

Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2040%2Fnouraellm%2Ffix-dangling-pointer-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2040/nouraellm/fix-dangling-pointer-v1
Pull-Request: https://github.com/git/git/pull/2040

 alloc.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/alloc.c b/alloc.c
index 377e80f5dda..6bf9421c123 100644
--- a/alloc.c
+++ b/alloc.c
@@ -49,6 +49,9 @@ void clear_alloc_state(struct alloc_state *s)
 	}
 
 	FREE_AND_NULL(s->slabs);
+	s->slab_alloc = 0;
+	s->nr = 0;
+	s->p = NULL;
 }
 
 static inline void *alloc_node(struct alloc_state *s, size_t node_size)

base-commit: f814da676ae46aac5be0a98b99373a76dee6cedb
-- 
gitgitgadget

^ permalink raw reply related	[flat|nested] 28+ messages in thread

* Re: [PATCH] reset slab_alloc and state fields in clear_alloc_state()
  2025-08-26 19:57 [PATCH] reset slab_alloc and state fields in clear_alloc_state() ノウラ | Flare via GitGitGadget
@ 2025-08-27  2:07 ` Junio C Hamano
  2025-08-27 23:28 ` [PATCH v2] alloc: fix dangling pointer in alloc_state cleanup ノウラ | Flare via GitGitGadget
  1 sibling, 0 replies; 28+ messages in thread
From: Junio C Hamano @ 2025-08-27  2:07 UTC (permalink / raw)
  To: ノウラ | Flare via GitGitGadget
  Cc: git, ノウラ | Flare, ノウラ

"ノウラ | Flare via GitGitGadget" <gitgitgadget@gmail.com> writes:

> From: =?UTF-8?q?=E3=83=8E=E3=82=A6=E3=83=A9?= <nea@odoo.com>

This name used on the in-body From: line must match who signs off
the patch below on Signed-off-by: line.

> clear_alloc_state() freed all slabs and nulled the slabs pointer but
> left slab_alloc, nr, and p unchanged.  If the alloc_state is reused,
> ALLOC_GROW() can wrongly assume that the slab array is already
> allocated because slab_alloc still holds a stale nonzero capacity.
> In that case s->slabs remains NULL and the next dereference writes
> through a NULL pointer, causing undefined behavior.

Let's not give such a misuse-prone API to consumers, then.  How
about doing something like this  instead?

As Documentation/CodingGuidelines says the API functions for
subsystem S should in general be called S_<something>, let's rename
allocate_alloc_state() to alloc_state_alloc(), get rid of the "just
clear" function and make it alloc_state_free_and_null() to do just
that.

 alloc.c  |  7 +++++--
 alloc.h  |  4 ++--
 object.c | 26 +++++++++++---------------
 3 files changed, 18 insertions(+), 19 deletions(-)

diff --git c/alloc.c w/alloc.c
index 377e80f5dd..3a5d0b2bd8 100644
--- c/alloc.c
+++ w/alloc.c
@@ -36,19 +36,22 @@ struct alloc_state {
 	int slab_nr, slab_alloc;
 };
 
-struct alloc_state *allocate_alloc_state(void)
+struct alloc_state *alloc_state_alloc(void)
 {
 	return xcalloc(1, sizeof(struct alloc_state));
 }
 
-void clear_alloc_state(struct alloc_state *s)
+void alloc_state_free_and_null(struct alloc_state **s_)
 {
+	struct alloc_state *s = *s_;
+
 	while (s->slab_nr > 0) {
 		s->slab_nr--;
 		free(s->slabs[s->slab_nr]);
 	}
 
 	FREE_AND_NULL(s->slabs);
+	FREE_AND_NULL(*s_);
 }
 
 static inline void *alloc_node(struct alloc_state *s, size_t node_size)
diff --git c/alloc.h w/alloc.h
index 3f4a0ad310..cd6ed16ffb 100644
--- c/alloc.h
+++ w/alloc.h
@@ -14,7 +14,7 @@ void *alloc_commit_node(struct repository *r);
 void *alloc_tag_node(struct repository *r);
 void *alloc_object_node(struct repository *r);
 
-struct alloc_state *allocate_alloc_state(void);
-void clear_alloc_state(struct alloc_state *s);
+struct alloc_state *alloc_state_alloc(void);
+void alloc_state_free_and_null(struct alloc_state **s);
 
 #endif
diff --git c/object.c w/object.c
index c1553ee433..4469755ea6 100644
--- c/object.c
+++ w/object.c
@@ -517,11 +517,11 @@ struct parsed_object_pool *parsed_object_pool_new(struct repository *repo)
 	memset(o, 0, sizeof(*o));
 
 	o->repo = repo;
-	o->blob_state = allocate_alloc_state();
-	o->tree_state = allocate_alloc_state();
-	o->commit_state = allocate_alloc_state();
-	o->tag_state = allocate_alloc_state();
-	o->object_state = allocate_alloc_state();
+	o->blob_state = alloc_state_alloc();
+	o->tree_state = alloc_state_alloc();
+	o->commit_state = alloc_state_alloc();
+	o->tag_state = alloc_state_alloc();
+	o->object_state = alloc_state_alloc();
 
 	o->is_shallow = -1;
 	CALLOC_ARRAY(o->shallow_stat, 1);
@@ -573,16 +573,12 @@ void parsed_object_pool_clear(struct parsed_object_pool *o)
 	o->buffer_slab = NULL;
 
 	parsed_object_pool_reset_commit_grafts(o);
-	clear_alloc_state(o->blob_state);
-	clear_alloc_state(o->tree_state);
-	clear_alloc_state(o->commit_state);
-	clear_alloc_state(o->tag_state);
-	clear_alloc_state(o->object_state);
+	alloc_state_free_and_null(&o->blob_state);
+	alloc_state_free_and_null(&o->tree_state);
+	alloc_state_free_and_null(&o->commit_state);
+	alloc_state_free_and_null(&o->tag_state);
+	alloc_state_free_and_null(&o->object_state);
+
 	stat_validity_clear(o->shallow_stat);
-	FREE_AND_NULL(o->blob_state);
-	FREE_AND_NULL(o->tree_state);
-	FREE_AND_NULL(o->commit_state);
-	FREE_AND_NULL(o->tag_state);
-	FREE_AND_NULL(o->object_state);
 	FREE_AND_NULL(o->shallow_stat);
 }

^ permalink raw reply related	[flat|nested] 28+ messages in thread

* [PATCH v2] alloc: fix dangling pointer in alloc_state cleanup
  2025-08-26 19:57 [PATCH] reset slab_alloc and state fields in clear_alloc_state() ノウラ | Flare via GitGitGadget
  2025-08-27  2:07 ` Junio C Hamano
@ 2025-08-27 23:28 ` ノウラ | Flare via GitGitGadget
  2025-08-28 19:29   ` Torsten Bögershausen
  2025-08-29 13:00   ` [PATCH v3] " ノウラ | Flare via GitGitGadget
  1 sibling, 2 replies; 28+ messages in thread
From: ノウラ | Flare via GitGitGadget @ 2025-08-27 23:28 UTC (permalink / raw)
  To: git; +Cc: ノウラ | Flare,
	ノウラ | Flare

From: =?UTF-8?q?=E3=83=8E=E3=82=A6=E3=83=A9=20=7C=20Flare?=
 <nouraellm@gmail.com>

clear_alloc_state() freed all slabs and nulled the slabs pointer but
left slab_alloc, nr, and p unchanged. If the alloc_state is reused,
ALLOC_GROW() can wrongly assume that the slab array is already
allocated because slab_alloc still holds a stale nonzero capacity.
In that case s->slabs remains NULL and the next dereference writes
through a NULL pointer, causing undefined behavior.

To fix this, this patch:

- Renames allocate_alloc_state() → alloc_state_alloc().
- Replaces the “just clear” API with
  alloc_state_free_and_null(struct alloc_state **s_),
  which frees all slabs and the alloc_state itself,
  and then nulls the caller’s pointer.
- Updates call sites to use the new helpers and drops
  redundant FREE_AND_NULL() calls.

This makes the alloc_state lifecycle API harder to misuse,
eliminates stale-capacity state,
and aligns naming with project conventions.

Signed-off-by: ノウラ | Flare <nouraellm@gmail.com>
---
    alloc: fix dangling pointer in alloc_state cleanup

Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2040%2Fnouraellm%2Ffix-dangling-pointer-v2
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2040/nouraellm/fix-dangling-pointer-v2
Pull-Request: https://github.com/git/git/pull/2040

Range-diff vs v1:

 1:  643f14e6225 < -:  ----------- reset slab_alloc and state fields in clear_alloc_state()
 -:  ----------- > 1:  0b980850bdf alloc: fix dangling pointer in alloc_state cleanup


 alloc.c  |  9 +++++++--
 alloc.h  |  4 ++--
 object.c | 26 ++++++++++----------------
 3 files changed, 19 insertions(+), 20 deletions(-)

diff --git a/alloc.c b/alloc.c
index 377e80f5dda..c1065551c07 100644
--- a/alloc.c
+++ b/alloc.c
@@ -36,19 +36,24 @@ struct alloc_state {
 	int slab_nr, slab_alloc;
 };
 
-struct alloc_state *allocate_alloc_state(void)
+struct alloc_state *alloc_state_alloc(void)
 {
 	return xcalloc(1, sizeof(struct alloc_state));
 }
 
-void clear_alloc_state(struct alloc_state *s)
+void alloc_state_free_and_null(struct alloc_state **s_)
 {
+	struct alloc_state *s = *s_;
+
+	if (!s_ || !*s_) return;
+
 	while (s->slab_nr > 0) {
 		s->slab_nr--;
 		free(s->slabs[s->slab_nr]);
 	}
 
 	FREE_AND_NULL(s->slabs);
+	FREE_AND_NULL(*s_);
 }
 
 static inline void *alloc_node(struct alloc_state *s, size_t node_size)
diff --git a/alloc.h b/alloc.h
index 3f4a0ad310a..87a47a97095 100644
--- a/alloc.h
+++ b/alloc.h
@@ -14,7 +14,7 @@ void *alloc_commit_node(struct repository *r);
 void *alloc_tag_node(struct repository *r);
 void *alloc_object_node(struct repository *r);
 
-struct alloc_state *allocate_alloc_state(void);
-void clear_alloc_state(struct alloc_state *s);
+struct alloc_state *alloc_state_alloc(void);
+void alloc_state_free_and_null(struct alloc_state **s_);
 
 #endif
diff --git a/object.c b/object.c
index c1553ee4330..986114a6dba 100644
--- a/object.c
+++ b/object.c
@@ -517,12 +517,11 @@ struct parsed_object_pool *parsed_object_pool_new(struct repository *repo)
 	memset(o, 0, sizeof(*o));
 
 	o->repo = repo;
-	o->blob_state = allocate_alloc_state();
-	o->tree_state = allocate_alloc_state();
-	o->commit_state = allocate_alloc_state();
-	o->tag_state = allocate_alloc_state();
-	o->object_state = allocate_alloc_state();
-
+	o->blob_state = alloc_state_alloc();
+	o->tree_state = alloc_state_alloc();
+	o->commit_state = alloc_state_alloc();
+	o->tag_state = alloc_state_alloc();
+	o->object_state = alloc_state_alloc();
 	o->is_shallow = -1;
 	CALLOC_ARRAY(o->shallow_stat, 1);
 
@@ -573,16 +572,11 @@ void parsed_object_pool_clear(struct parsed_object_pool *o)
 	o->buffer_slab = NULL;
 
 	parsed_object_pool_reset_commit_grafts(o);
-	clear_alloc_state(o->blob_state);
-	clear_alloc_state(o->tree_state);
-	clear_alloc_state(o->commit_state);
-	clear_alloc_state(o->tag_state);
-	clear_alloc_state(o->object_state);
+	alloc_state_free_and_null(&o->blob_state);
+	alloc_state_free_and_null(&o->tree_state);
+	alloc_state_free_and_null(&o->commit_state);
+	alloc_state_free_and_null(&o->tag_state);
+	alloc_state_free_and_null(&o->object_state);
 	stat_validity_clear(o->shallow_stat);
-	FREE_AND_NULL(o->blob_state);
-	FREE_AND_NULL(o->tree_state);
-	FREE_AND_NULL(o->commit_state);
-	FREE_AND_NULL(o->tag_state);
-	FREE_AND_NULL(o->object_state);
 	FREE_AND_NULL(o->shallow_stat);
 }

base-commit: f814da676ae46aac5be0a98b99373a76dee6cedb
-- 
gitgitgadget

^ permalink raw reply related	[flat|nested] 28+ messages in thread

* Re: [PATCH v2] alloc: fix dangling pointer in alloc_state cleanup
  2025-08-27 23:28 ` [PATCH v2] alloc: fix dangling pointer in alloc_state cleanup ノウラ | Flare via GitGitGadget
@ 2025-08-28 19:29   ` Torsten Bögershausen
  2025-08-28 19:47     ` Junio C Hamano
  2025-08-29 13:00   ` [PATCH v3] " ノウラ | Flare via GitGitGadget
  1 sibling, 1 reply; 28+ messages in thread
From: Torsten Bögershausen @ 2025-08-28 19:29 UTC (permalink / raw)
  To: ノウラ | Flare via GitGitGadget
  Cc: git, ノウラ | Flare

On Wed, Aug 27, 2025 at 11:28:32PM +0000, ノウラ | Flare via GitGitGadget wrote:
> From: =?UTF-8?q?=E3=83=8E=E3=82=A6=E3=83=A9=20=7C=20Flare?=
>  <nouraellm@gmail.com>
> 
> clear_alloc_state() freed all slabs and nulled the slabs pointer but
> left slab_alloc, nr, and p unchanged. If the alloc_state is reused,
> ALLOC_GROW() can wrongly assume that the slab array is already
> allocated because slab_alloc still holds a stale nonzero capacity.
> In that case s->slabs remains NULL and the next dereference writes
> through a NULL pointer, causing undefined behavior.
This is good.

> 
> To fix this, this patch:
Style nit, we tend to use the "imperative form" here in Git,
like this:

- Rename allocate_alloc_state() → alloc_state_alloc().
- Replace ...
- Update ...
> 
> - Renames allocate_alloc_state() → alloc_state_alloc().
> - Replaces the “just clear” API with
>   alloc_state_free_and_null(struct alloc_state **s_),
>   which frees all slabs and the alloc_state itself,
>   and then nulls the caller’s pointer.
> - Updates call sites to use the new helpers and drops
>   redundant FREE_AND_NULL() calls.
> 
> This makes the alloc_state lifecycle API harder to misuse,
> eliminates stale-capacity state,
> and aligns naming with project conventions.

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH v2] alloc: fix dangling pointer in alloc_state cleanup
  2025-08-28 19:29   ` Torsten Bögershausen
@ 2025-08-28 19:47     ` Junio C Hamano
  2025-08-28 20:01       ` Junio C Hamano
  0 siblings, 1 reply; 28+ messages in thread
From: Junio C Hamano @ 2025-08-28 19:47 UTC (permalink / raw)
  To: Torsten Bögershausen
  Cc: ノウラ | Flare via GitGitGadget, git,
	ノウラ | Flare

Torsten Bögershausen <tboegi@web.de> writes:

> On Wed, Aug 27, 2025 at 11:28:32PM +0000, ノウラ | Flare via GitGitGadget wrote:
>> From: =?UTF-8?q?=E3=83=8E=E3=82=A6=E3=83=A9=20=7C=20Flare?=
>>  <nouraellm@gmail.com>
>> 
>> clear_alloc_state() freed all slabs and nulled the slabs pointer but
>> left slab_alloc, nr, and p unchanged. If the alloc_state is reused,
>> ALLOC_GROW() can wrongly assume that the slab array is already
>> allocated because slab_alloc still holds a stale nonzero capacity.
>> In that case s->slabs remains NULL and the next dereference writes
>> through a NULL pointer, causing undefined behavior.
> This is good.
>
>> 
>> To fix this, this patch:
> Style nit, we tend to use the "imperative form" here in Git,
> like this:
>
> - Rename allocate_alloc_state() → alloc_state_alloc().
> - Replace ...
> - Update ...

Thanks.  We also tend to avoid bulleted list.

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH v2] alloc: fix dangling pointer in alloc_state cleanup
  2025-08-28 19:47     ` Junio C Hamano
@ 2025-08-28 20:01       ` Junio C Hamano
  0 siblings, 0 replies; 28+ messages in thread
From: Junio C Hamano @ 2025-08-28 20:01 UTC (permalink / raw)
  To: Torsten Bögershausen
  Cc: ノウラ | Flare via GitGitGadget, git,
	ノウラ | Flare

Junio C Hamano <gitster@pobox.com> writes:

>> Style nit, we tend to use the "imperative form" here in Git,
>> like this:
>>
>> - Rename allocate_alloc_state() → alloc_state_alloc().
>> - Replace ...
>> - Update ...
>
> Thanks.  We also tend to avoid bulleted list.

Perhaps it needs a bit of clarification.  

If you truly require a bulleted list to enumerate what you did in
the patch, then you may be doing too many things in a single patch.

But in this case, the changes do not even have to be broken down
into bullets.  If you change a called function, it goes without
saying that you have to adjust existing callers.  Perhaps something
like

    All callers of clear_alloc_state() immediately free what they
    cleared, so currently it does not hurt anybody that the
    alloc_state is left in an unreusable state, but it is an
    error-prone API.  Replace it with a new function that clears but
    in addition frees the structure, as well as NULLing the pointer
    that points at it and adjust existing callers.

    While at it, rename allocate_alloc_state() and name the new
    function alloc_state_free_and_null(), to follow more closely the
    function naming convention specified in the CodingGuidelines
    (namely, functions about S are named with S_ prefix and then
    verb).

should be more in line with what we usually do.

Thanks.

^ permalink raw reply	[flat|nested] 28+ messages in thread

* [PATCH v3] alloc: fix dangling pointer in alloc_state cleanup
  2025-08-27 23:28 ` [PATCH v2] alloc: fix dangling pointer in alloc_state cleanup ノウラ | Flare via GitGitGadget
  2025-08-28 19:29   ` Torsten Bögershausen
@ 2025-08-29 13:00   ` ノウラ | Flare via GitGitGadget
  2025-09-03 11:18     ` Jeff King
  2025-09-03 23:17     ` [PATCH v4] " ノウラ | Flare via GitGitGadget
  1 sibling, 2 replies; 28+ messages in thread
From: ノウラ | Flare via GitGitGadget @ 2025-08-29 13:00 UTC (permalink / raw)
  To: git; +Cc: ノウラ | Flare,
	ノウラ | Flare

From: =?UTF-8?q?=E3=83=8E=E3=82=A6=E3=83=A9=20=7C=20Flare?=
 <nouraellm@gmail.com>

All callers of clear_alloc_state() immediately free what they
cleared, so currently it does not hurt anybody that the
alloc_state is left in an unreusable state, but it is an
error-prone API. Replace it with a new function that clears but
in addition frees the structure, as well as NULLing the pointer
that points at it and adjust existing callers.

While at it, rename allocate_alloc_state() and name the new
function alloc_state_free_and_null(), to follow more closely the
function naming convention specified in the CodingGuidelines
(namely, functions about S are named with S_ prefix and then
verb).

Signed-off-by: ノウラ | Flare <nouraellm@gmail.com>
---
    alloc: fix dangling pointer in alloc_state cleanup
    
    cc: Torsten Bögershausen tboegi@web.de

Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2040%2Fnouraellm%2Ffix-dangling-pointer-v3
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2040/nouraellm/fix-dangling-pointer-v3
Pull-Request: https://github.com/git/git/pull/2040

Range-diff vs v2:

 1:  0b980850bd ! 1:  c521b44adb alloc: fix dangling pointer in alloc_state cleanup
     @@ Metadata
       ## Commit message ##
          alloc: fix dangling pointer in alloc_state cleanup
      
     -    clear_alloc_state() freed all slabs and nulled the slabs pointer but
     -    left slab_alloc, nr, and p unchanged. If the alloc_state is reused,
     -    ALLOC_GROW() can wrongly assume that the slab array is already
     -    allocated because slab_alloc still holds a stale nonzero capacity.
     -    In that case s->slabs remains NULL and the next dereference writes
     -    through a NULL pointer, causing undefined behavior.
     +    All callers of clear_alloc_state() immediately free what they
     +    cleared, so currently it does not hurt anybody that the
     +    alloc_state is left in an unreusable state, but it is an
     +    error-prone API. Replace it with a new function that clears but
     +    in addition frees the structure, as well as NULLing the pointer
     +    that points at it and adjust existing callers.
      
     -    To fix this, this patch:
     -
     -    - Renames allocate_alloc_state() → alloc_state_alloc().
     -    - Replaces the “just clear” API with
     -      alloc_state_free_and_null(struct alloc_state **s_),
     -      which frees all slabs and the alloc_state itself,
     -      and then nulls the caller’s pointer.
     -    - Updates call sites to use the new helpers and drops
     -      redundant FREE_AND_NULL() calls.
     -
     -    This makes the alloc_state lifecycle API harder to misuse,
     -    eliminates stale-capacity state,
     -    and aligns naming with project conventions.
     +    While at it, rename allocate_alloc_state() and name the new
     +    function alloc_state_free_and_null(), to follow more closely the
     +    function naming convention specified in the CodingGuidelines
     +    (namely, functions about S are named with S_ prefix and then
     +    verb).
      
          Signed-off-by: ノウラ | Flare <nouraellm@gmail.com>
      


 alloc.c  |  9 +++++++--
 alloc.h  |  4 ++--
 object.c | 26 ++++++++++----------------
 3 files changed, 19 insertions(+), 20 deletions(-)

diff --git a/alloc.c b/alloc.c
index 377e80f5dd..c1065551c0 100644
--- a/alloc.c
+++ b/alloc.c
@@ -36,19 +36,24 @@ struct alloc_state {
 	int slab_nr, slab_alloc;
 };
 
-struct alloc_state *allocate_alloc_state(void)
+struct alloc_state *alloc_state_alloc(void)
 {
 	return xcalloc(1, sizeof(struct alloc_state));
 }
 
-void clear_alloc_state(struct alloc_state *s)
+void alloc_state_free_and_null(struct alloc_state **s_)
 {
+	struct alloc_state *s = *s_;
+
+	if (!s_ || !*s_) return;
+
 	while (s->slab_nr > 0) {
 		s->slab_nr--;
 		free(s->slabs[s->slab_nr]);
 	}
 
 	FREE_AND_NULL(s->slabs);
+	FREE_AND_NULL(*s_);
 }
 
 static inline void *alloc_node(struct alloc_state *s, size_t node_size)
diff --git a/alloc.h b/alloc.h
index 3f4a0ad310..87a47a9709 100644
--- a/alloc.h
+++ b/alloc.h
@@ -14,7 +14,7 @@ void *alloc_commit_node(struct repository *r);
 void *alloc_tag_node(struct repository *r);
 void *alloc_object_node(struct repository *r);
 
-struct alloc_state *allocate_alloc_state(void);
-void clear_alloc_state(struct alloc_state *s);
+struct alloc_state *alloc_state_alloc(void);
+void alloc_state_free_and_null(struct alloc_state **s_);
 
 #endif
diff --git a/object.c b/object.c
index c1553ee433..986114a6db 100644
--- a/object.c
+++ b/object.c
@@ -517,12 +517,11 @@ struct parsed_object_pool *parsed_object_pool_new(struct repository *repo)
 	memset(o, 0, sizeof(*o));
 
 	o->repo = repo;
-	o->blob_state = allocate_alloc_state();
-	o->tree_state = allocate_alloc_state();
-	o->commit_state = allocate_alloc_state();
-	o->tag_state = allocate_alloc_state();
-	o->object_state = allocate_alloc_state();
-
+	o->blob_state = alloc_state_alloc();
+	o->tree_state = alloc_state_alloc();
+	o->commit_state = alloc_state_alloc();
+	o->tag_state = alloc_state_alloc();
+	o->object_state = alloc_state_alloc();
 	o->is_shallow = -1;
 	CALLOC_ARRAY(o->shallow_stat, 1);
 
@@ -573,16 +572,11 @@ void parsed_object_pool_clear(struct parsed_object_pool *o)
 	o->buffer_slab = NULL;
 
 	parsed_object_pool_reset_commit_grafts(o);
-	clear_alloc_state(o->blob_state);
-	clear_alloc_state(o->tree_state);
-	clear_alloc_state(o->commit_state);
-	clear_alloc_state(o->tag_state);
-	clear_alloc_state(o->object_state);
+	alloc_state_free_and_null(&o->blob_state);
+	alloc_state_free_and_null(&o->tree_state);
+	alloc_state_free_and_null(&o->commit_state);
+	alloc_state_free_and_null(&o->tag_state);
+	alloc_state_free_and_null(&o->object_state);
 	stat_validity_clear(o->shallow_stat);
-	FREE_AND_NULL(o->blob_state);
-	FREE_AND_NULL(o->tree_state);
-	FREE_AND_NULL(o->commit_state);
-	FREE_AND_NULL(o->tag_state);
-	FREE_AND_NULL(o->object_state);
 	FREE_AND_NULL(o->shallow_stat);
 }

base-commit: f814da676ae46aac5be0a98b99373a76dee6cedb
-- 
gitgitgadget

^ permalink raw reply related	[flat|nested] 28+ messages in thread

* Re: [PATCH v3] alloc: fix dangling pointer in alloc_state cleanup
  2025-08-29 13:00   ` [PATCH v3] " ノウラ | Flare via GitGitGadget
@ 2025-09-03 11:18     ` Jeff King
  2025-09-03 21:59       ` Junio C Hamano
  2025-09-03 23:17     ` [PATCH v4] " ノウラ | Flare via GitGitGadget
  1 sibling, 1 reply; 28+ messages in thread
From: Jeff King @ 2025-09-03 11:18 UTC (permalink / raw)
  To: ノウラ | Flare via GitGitGadget
  Cc: git, ノウラ | Flare

On Fri, Aug 29, 2025 at 01:00:06PM +0000, ノウラ | Flare via GitGitGadget wrote:

> +void alloc_state_free_and_null(struct alloc_state **s_)
>  {
> +	struct alloc_state *s = *s_;
> +
> +	if (!s_ || !*s_) return;
> +

Coverity complains that there's a NULL check here for "s_", but we'll
have already dereferenced it in the initializer for "s".

I don't think any caller passes NULL, so you can't trigger a segfault in
practice. But the code is kind of misleading. Should it just be:

  if (!*s_)
	return;

? Or even just "if (!s)".

-Peff

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH v3] alloc: fix dangling pointer in alloc_state cleanup
  2025-09-03 11:18     ` Jeff King
@ 2025-09-03 21:59       ` Junio C Hamano
  0 siblings, 0 replies; 28+ messages in thread
From: Junio C Hamano @ 2025-09-03 21:59 UTC (permalink / raw)
  To: Jeff King
  Cc: ノウラ | Flare via GitGitGadget, git,
	ノウラ | Flare

Jeff King <peff@peff.net> writes:

> On Fri, Aug 29, 2025 at 01:00:06PM +0000, ノウラ | Flare via GitGitGadget wrote:
>
>> +void alloc_state_free_and_null(struct alloc_state **s_)
>>  {
>> +	struct alloc_state *s = *s_;
>> +
>> +	if (!s_ || !*s_) return;
>> +
>
> Coverity complains that there's a NULL check here for "s_", but we'll
> have already dereferenced it in the initializer for "s".
>
> I don't think any caller passes NULL, so you can't trigger a segfault in
> practice. But the code is kind of misleading. Should it just be:
>
>   if (!*s_)
> 	return;
>
> ? Or even just "if (!s)".

Yup, I like that.

The primary point of s_ (parameter with a trailing underscore) is
that we would want to use it as-is as little as possible.  When we
talk about the pointer to alloc_state in this function (not the
location such a pointer is stored at), we should use "s" (not
"*s_").

Thanks for sanity checking.  This may have been my breakage.



^ permalink raw reply	[flat|nested] 28+ messages in thread

* [PATCH v4] alloc: fix dangling pointer in alloc_state cleanup
  2025-08-29 13:00   ` [PATCH v3] " ノウラ | Flare via GitGitGadget
  2025-09-03 11:18     ` Jeff King
@ 2025-09-03 23:17     ` ノウラ | Flare via GitGitGadget
  2025-09-04  7:47       ` Junio C Hamano
  2025-09-04 17:44       ` [PATCH v5] " ノウラ | Flare via GitGitGadget
  1 sibling, 2 replies; 28+ messages in thread
From: ノウラ | Flare via GitGitGadget @ 2025-09-03 23:17 UTC (permalink / raw)
  To: git; +Cc: ノウラ | Flare,
	ノウラ | Flare

From: =?UTF-8?q?=E3=83=8E=E3=82=A6=E3=83=A9=20=7C=20Flare?=
 <nouraellm@gmail.com>

All callers of clear_alloc_state() immediately free what they
cleared, so currently it does not hurt anybody that the
alloc_state is left in an unreusable state, but it is an
error-prone API. Replace it with a new function that clears but
in addition frees the structure, as well as NULLing the pointer
that points at it and adjust existing callers.

While at it, rename allocate_alloc_state() and name the new
function alloc_state_free_and_null(), to follow more closely the
function naming convention specified in the CodingGuidelines
(namely, functions about S are named with S_ prefix and then
verb).

Signed-off-by: ノウラ | Flare <nouraellm@gmail.com>
---
    alloc: fix dangling pointer in alloc_state cleanup
    
    cc: Torsten Bögershausen tboegi@web.de cc: Jeff King peff@peff.net

Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2040%2Fnouraellm%2Ffix-dangling-pointer-v4
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2040/nouraellm/fix-dangling-pointer-v4
Pull-Request: https://github.com/git/git/pull/2040

Range-diff vs v3:

 1:  c521b44adb ! 1:  82d228661d alloc: fix dangling pointer in alloc_state cleanup
     @@ alloc.c: struct alloc_state {
      -void clear_alloc_state(struct alloc_state *s)
      +void alloc_state_free_and_null(struct alloc_state **s_)
       {
     -+	struct alloc_state *s = *s_;
     ++	struct alloc_state *s;
      +
      +	if (!s_ || !*s_) return;
     ++
     ++	s = *s_;
      +
       	while (s->slab_nr > 0) {
       		s->slab_nr--;


 alloc.c  | 11 +++++++++--
 alloc.h  |  4 ++--
 object.c | 26 ++++++++++----------------
 3 files changed, 21 insertions(+), 20 deletions(-)

diff --git a/alloc.c b/alloc.c
index 377e80f5dd..1483697ca8 100644
--- a/alloc.c
+++ b/alloc.c
@@ -36,19 +36,26 @@ struct alloc_state {
 	int slab_nr, slab_alloc;
 };
 
-struct alloc_state *allocate_alloc_state(void)
+struct alloc_state *alloc_state_alloc(void)
 {
 	return xcalloc(1, sizeof(struct alloc_state));
 }
 
-void clear_alloc_state(struct alloc_state *s)
+void alloc_state_free_and_null(struct alloc_state **s_)
 {
+	struct alloc_state *s;
+
+	if (!s_ || !*s_) return;
+
+	s = *s_;
+
 	while (s->slab_nr > 0) {
 		s->slab_nr--;
 		free(s->slabs[s->slab_nr]);
 	}
 
 	FREE_AND_NULL(s->slabs);
+	FREE_AND_NULL(*s_);
 }
 
 static inline void *alloc_node(struct alloc_state *s, size_t node_size)
diff --git a/alloc.h b/alloc.h
index 3f4a0ad310..87a47a9709 100644
--- a/alloc.h
+++ b/alloc.h
@@ -14,7 +14,7 @@ void *alloc_commit_node(struct repository *r);
 void *alloc_tag_node(struct repository *r);
 void *alloc_object_node(struct repository *r);
 
-struct alloc_state *allocate_alloc_state(void);
-void clear_alloc_state(struct alloc_state *s);
+struct alloc_state *alloc_state_alloc(void);
+void alloc_state_free_and_null(struct alloc_state **s_);
 
 #endif
diff --git a/object.c b/object.c
index c1553ee433..986114a6db 100644
--- a/object.c
+++ b/object.c
@@ -517,12 +517,11 @@ struct parsed_object_pool *parsed_object_pool_new(struct repository *repo)
 	memset(o, 0, sizeof(*o));
 
 	o->repo = repo;
-	o->blob_state = allocate_alloc_state();
-	o->tree_state = allocate_alloc_state();
-	o->commit_state = allocate_alloc_state();
-	o->tag_state = allocate_alloc_state();
-	o->object_state = allocate_alloc_state();
-
+	o->blob_state = alloc_state_alloc();
+	o->tree_state = alloc_state_alloc();
+	o->commit_state = alloc_state_alloc();
+	o->tag_state = alloc_state_alloc();
+	o->object_state = alloc_state_alloc();
 	o->is_shallow = -1;
 	CALLOC_ARRAY(o->shallow_stat, 1);
 
@@ -573,16 +572,11 @@ void parsed_object_pool_clear(struct parsed_object_pool *o)
 	o->buffer_slab = NULL;
 
 	parsed_object_pool_reset_commit_grafts(o);
-	clear_alloc_state(o->blob_state);
-	clear_alloc_state(o->tree_state);
-	clear_alloc_state(o->commit_state);
-	clear_alloc_state(o->tag_state);
-	clear_alloc_state(o->object_state);
+	alloc_state_free_and_null(&o->blob_state);
+	alloc_state_free_and_null(&o->tree_state);
+	alloc_state_free_and_null(&o->commit_state);
+	alloc_state_free_and_null(&o->tag_state);
+	alloc_state_free_and_null(&o->object_state);
 	stat_validity_clear(o->shallow_stat);
-	FREE_AND_NULL(o->blob_state);
-	FREE_AND_NULL(o->tree_state);
-	FREE_AND_NULL(o->commit_state);
-	FREE_AND_NULL(o->tag_state);
-	FREE_AND_NULL(o->object_state);
 	FREE_AND_NULL(o->shallow_stat);
 }

base-commit: f814da676ae46aac5be0a98b99373a76dee6cedb
-- 
gitgitgadget

^ permalink raw reply related	[flat|nested] 28+ messages in thread

* Re: [PATCH v4] alloc: fix dangling pointer in alloc_state cleanup
  2025-09-03 23:17     ` [PATCH v4] " ノウラ | Flare via GitGitGadget
@ 2025-09-04  7:47       ` Junio C Hamano
  2025-09-04 13:25         ` ノウラ | Flare
  2025-09-04 17:44       ` [PATCH v5] " ノウラ | Flare via GitGitGadget
  1 sibling, 1 reply; 28+ messages in thread
From: Junio C Hamano @ 2025-09-04  7:47 UTC (permalink / raw)
  To: ノウラ | Flare via GitGitGadget
  Cc: git, ノウラ | Flare

"ノウラ | Flare via GitGitGadget" <gitgitgadget@gmail.com> writes:

> +void alloc_state_free_and_null(struct alloc_state **s_)
>  {
> +	struct alloc_state *s;
> +
> +	if (!s_ || !*s_) return;

I still do not see the point of this check.  If the caller passes a
NULL pointer, when they are expected to pass the address of a
pointer variable so that the struct the pointer points at is cleared
and freed, and the pointer variable is NULLed, it is called a
programmer error and they deserve a segfault.  Why would it be
better to sweep such an error under the rug by returning without
anything?  It would delay discovery of such a bug, but for what
gain?


^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH v4] alloc: fix dangling pointer in alloc_state cleanup
  2025-09-04  7:47       ` Junio C Hamano
@ 2025-09-04 13:25         ` ノウラ | Flare
  2025-09-04 16:43           ` Junio C Hamano
  0 siblings, 1 reply; 28+ messages in thread
From: ノウラ | Flare @ 2025-09-04 13:25 UTC (permalink / raw)
  To: Junio C Hamano, ノウラ | Flare via GitGitGadget; +Cc: git

The point of the check was to avoid subtle crashes
aligning with defensive programming requirements.

If you lean more towards strict contract enforcement
Just say the word.

On 04/09/2025 09:47, Junio C Hamano wrote:
> "ノウラ | Flare via GitGitGadget" <gitgitgadget@gmail.com> writes:
>
>> +void alloc_state_free_and_null(struct alloc_state **s_)
>>   {
>> +	struct alloc_state *s;
>> +
>> +	if (!s_ || !*s_) return;
> I still do not see the point of this check.  If the caller passes a
> NULL pointer, when they are expected to pass the address of a
> pointer variable so that the struct the pointer points at is cleared
> and freed, and the pointer variable is NULLed, it is called a
> programmer error and they deserve a segfault.  Why would it be
> better to sweep such an error under the rug by returning without
> anything?  It would delay discovery of such a bug, but for what
> gain?
>

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH v4] alloc: fix dangling pointer in alloc_state cleanup
  2025-09-04 13:25         ` ノウラ | Flare
@ 2025-09-04 16:43           ` Junio C Hamano
  0 siblings, 0 replies; 28+ messages in thread
From: Junio C Hamano @ 2025-09-04 16:43 UTC (permalink / raw)
  To: ノウラ | Flare
  Cc: ノウラ | Flare via GitGitGadget, git

ノウラ | Flare <nouraellm@gmail.com> writes:

> The point of the check was to avoid subtle crashes
> aligning with defensive programming requirements.

It is a programming error, period.  Do not silently return.  That's
not being defensive.  That is sweeping a problem under the rug.

We can always do assert() or if (condition) BUG("...") if we are
unsure about the ability of our developers to grok subtle
pre-conditions to call into certain code paths, and I would call
that defensive.

But I do not see a need for _this_ particular call.  Our developers
should be able to see what they are supposed to call it with just
fine.

Thanks.

^ permalink raw reply	[flat|nested] 28+ messages in thread

* [PATCH v5] alloc: fix dangling pointer in alloc_state cleanup
  2025-09-03 23:17     ` [PATCH v4] " ノウラ | Flare via GitGitGadget
  2025-09-04  7:47       ` Junio C Hamano
@ 2025-09-04 17:44       ` ノウラ | Flare via GitGitGadget
  2025-09-04 20:25         ` Junio C Hamano
                           ` (2 more replies)
  1 sibling, 3 replies; 28+ messages in thread
From: ノウラ | Flare via GitGitGadget @ 2025-09-04 17:44 UTC (permalink / raw)
  To: git; +Cc: ノウラ | Flare,
	ノウラ | Flare

From: =?UTF-8?q?=E3=83=8E=E3=82=A6=E3=83=A9=20=7C=20Flare?=
 <nouraellm@gmail.com>

All callers of clear_alloc_state() immediately free what they
cleared, so currently it does not hurt anybody that the
alloc_state is left in an unreusable state, but it is an
error-prone API. Replace it with a new function that clears but
in addition frees the structure, as well as NULLing the pointer
that points at it and adjust existing callers.

While at it, rename allocate_alloc_state() and name the new
function alloc_state_free_and_null(), to follow more closely the
function naming convention specified in the CodingGuidelines
(namely, functions about S are named with S_ prefix and then
verb).

Signed-off-by: ノウラ | Flare <nouraellm@gmail.com>
---
    alloc: fix dangling pointer in alloc_state cleanup
    
    cc: Torsten Bögershausen tboegi@web.de cc: Jeff King peff@peff.net

Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2040%2Fnouraellm%2Ffix-dangling-pointer-v5
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2040/nouraellm/fix-dangling-pointer-v5
Pull-Request: https://github.com/git/git/pull/2040

Range-diff vs v4:

 1:  82d228661d ! 1:  d04b0258f5 alloc: fix dangling pointer in alloc_state cleanup
     @@ alloc.c: struct alloc_state {
      -void clear_alloc_state(struct alloc_state *s)
      +void alloc_state_free_and_null(struct alloc_state **s_)
       {
     -+	struct alloc_state *s;
     -+
     -+	if (!s_ || !*s_) return;
     -+
     -+	s = *s_;
     ++	struct alloc_state *s = *s_;
      +
       	while (s->slab_nr > 0) {
       		s->slab_nr--;


 alloc.c  |  7 +++++--
 alloc.h  |  4 ++--
 object.c | 26 ++++++++++----------------
 3 files changed, 17 insertions(+), 20 deletions(-)

diff --git a/alloc.c b/alloc.c
index 377e80f5dd..3a5d0b2bd8 100644
--- a/alloc.c
+++ b/alloc.c
@@ -36,19 +36,22 @@ struct alloc_state {
 	int slab_nr, slab_alloc;
 };
 
-struct alloc_state *allocate_alloc_state(void)
+struct alloc_state *alloc_state_alloc(void)
 {
 	return xcalloc(1, sizeof(struct alloc_state));
 }
 
-void clear_alloc_state(struct alloc_state *s)
+void alloc_state_free_and_null(struct alloc_state **s_)
 {
+	struct alloc_state *s = *s_;
+
 	while (s->slab_nr > 0) {
 		s->slab_nr--;
 		free(s->slabs[s->slab_nr]);
 	}
 
 	FREE_AND_NULL(s->slabs);
+	FREE_AND_NULL(*s_);
 }
 
 static inline void *alloc_node(struct alloc_state *s, size_t node_size)
diff --git a/alloc.h b/alloc.h
index 3f4a0ad310..87a47a9709 100644
--- a/alloc.h
+++ b/alloc.h
@@ -14,7 +14,7 @@ void *alloc_commit_node(struct repository *r);
 void *alloc_tag_node(struct repository *r);
 void *alloc_object_node(struct repository *r);
 
-struct alloc_state *allocate_alloc_state(void);
-void clear_alloc_state(struct alloc_state *s);
+struct alloc_state *alloc_state_alloc(void);
+void alloc_state_free_and_null(struct alloc_state **s_);
 
 #endif
diff --git a/object.c b/object.c
index c1553ee433..986114a6db 100644
--- a/object.c
+++ b/object.c
@@ -517,12 +517,11 @@ struct parsed_object_pool *parsed_object_pool_new(struct repository *repo)
 	memset(o, 0, sizeof(*o));
 
 	o->repo = repo;
-	o->blob_state = allocate_alloc_state();
-	o->tree_state = allocate_alloc_state();
-	o->commit_state = allocate_alloc_state();
-	o->tag_state = allocate_alloc_state();
-	o->object_state = allocate_alloc_state();
-
+	o->blob_state = alloc_state_alloc();
+	o->tree_state = alloc_state_alloc();
+	o->commit_state = alloc_state_alloc();
+	o->tag_state = alloc_state_alloc();
+	o->object_state = alloc_state_alloc();
 	o->is_shallow = -1;
 	CALLOC_ARRAY(o->shallow_stat, 1);
 
@@ -573,16 +572,11 @@ void parsed_object_pool_clear(struct parsed_object_pool *o)
 	o->buffer_slab = NULL;
 
 	parsed_object_pool_reset_commit_grafts(o);
-	clear_alloc_state(o->blob_state);
-	clear_alloc_state(o->tree_state);
-	clear_alloc_state(o->commit_state);
-	clear_alloc_state(o->tag_state);
-	clear_alloc_state(o->object_state);
+	alloc_state_free_and_null(&o->blob_state);
+	alloc_state_free_and_null(&o->tree_state);
+	alloc_state_free_and_null(&o->commit_state);
+	alloc_state_free_and_null(&o->tag_state);
+	alloc_state_free_and_null(&o->object_state);
 	stat_validity_clear(o->shallow_stat);
-	FREE_AND_NULL(o->blob_state);
-	FREE_AND_NULL(o->tree_state);
-	FREE_AND_NULL(o->commit_state);
-	FREE_AND_NULL(o->tag_state);
-	FREE_AND_NULL(o->object_state);
 	FREE_AND_NULL(o->shallow_stat);
 }

base-commit: f814da676ae46aac5be0a98b99373a76dee6cedb
-- 
gitgitgadget

^ permalink raw reply related	[flat|nested] 28+ messages in thread

* Re: [PATCH v5] alloc: fix dangling pointer in alloc_state cleanup
  2025-09-04 17:44       ` [PATCH v5] " ノウラ | Flare via GitGitGadget
@ 2025-09-04 20:25         ` Junio C Hamano
  2025-09-04 20:49         ` Jeff King
  2025-09-05 18:51         ` [PATCH v6] " ノウラ | Flare via GitGitGadget
  2 siblings, 0 replies; 28+ messages in thread
From: Junio C Hamano @ 2025-09-04 20:25 UTC (permalink / raw)
  To: ノウラ | Flare via GitGitGadget
  Cc: git, ノウラ | Flare

"ノウラ | Flare via GitGitGadget" <gitgitgadget@gmail.com> writes:

> index 377e80f5dd..3a5d0b2bd8 100644
> --- a/alloc.c
> +++ b/alloc.c
> @@ -36,19 +36,22 @@ struct alloc_state {
>  	int slab_nr, slab_alloc;
>  };
>  
> -struct alloc_state *allocate_alloc_state(void)
> +struct alloc_state *alloc_state_alloc(void)
>  {
>  	return xcalloc(1, sizeof(struct alloc_state));
>  }
>  
> -void clear_alloc_state(struct alloc_state *s)
> +void alloc_state_free_and_null(struct alloc_state **s_)
>  {
> +	struct alloc_state *s = *s_;
> +
>  	while (s->slab_nr > 0) {
>  		s->slab_nr--;
>  		free(s->slabs[s->slab_nr]);
>  	}
>  
>  	FREE_AND_NULL(s->slabs);
> +	FREE_AND_NULL(*s_);
>  }
>  
>  static inline void *alloc_node(struct alloc_state *s, size_t node_size)

Looking good.

> diff --git a/alloc.h b/alloc.h
> index 3f4a0ad310..87a47a9709 100644
> --- a/alloc.h
> +++ b/alloc.h
> @@ -14,7 +14,7 @@ void *alloc_commit_node(struct repository *r);
>  void *alloc_tag_node(struct repository *r);
>  void *alloc_object_node(struct repository *r);
>  
> -struct alloc_state *allocate_alloc_state(void);
> -void clear_alloc_state(struct alloc_state *s);
> +struct alloc_state *alloc_state_alloc(void);
> +void alloc_state_free_and_null(struct alloc_state **s_);
>  
>  #endif
> diff --git a/object.c b/object.c
> index c1553ee433..986114a6db 100644
> --- a/object.c
> +++ b/object.c
> @@ -573,16 +572,11 @@ void parsed_object_pool_clear(struct parsed_object_pool *o)
>  	o->buffer_slab = NULL;
>  
>  	parsed_object_pool_reset_commit_grafts(o);
> -	clear_alloc_state(o->blob_state);
> -	clear_alloc_state(o->tree_state);
> -	clear_alloc_state(o->commit_state);
> -	clear_alloc_state(o->tag_state);
> -	clear_alloc_state(o->object_state);
> +	alloc_state_free_and_null(&o->blob_state);
> +	alloc_state_free_and_null(&o->tree_state);
> +	alloc_state_free_and_null(&o->commit_state);
> +	alloc_state_free_and_null(&o->tag_state);
> +	alloc_state_free_and_null(&o->object_state);
>  	stat_validity_clear(o->shallow_stat);
> -	FREE_AND_NULL(o->blob_state);
> -	FREE_AND_NULL(o->tree_state);
> -	FREE_AND_NULL(o->commit_state);
> -	FREE_AND_NULL(o->tag_state);
> -	FREE_AND_NULL(o->object_state);
>  	FREE_AND_NULL(o->shallow_stat);
>  }

Very nice.

Thanks.  Will queue.


^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH v5] alloc: fix dangling pointer in alloc_state cleanup
  2025-09-04 17:44       ` [PATCH v5] " ノウラ | Flare via GitGitGadget
  2025-09-04 20:25         ` Junio C Hamano
@ 2025-09-04 20:49         ` Jeff King
  2025-09-04 22:26           ` Junio C Hamano
  2025-09-05 18:51         ` [PATCH v6] " ノウラ | Flare via GitGitGadget
  2 siblings, 1 reply; 28+ messages in thread
From: Jeff King @ 2025-09-04 20:49 UTC (permalink / raw)
  To: ノウラ | Flare via GitGitGadget
  Cc: git, ノウラ | Flare

On Thu, Sep 04, 2025 at 05:44:16PM +0000, ノウラ | Flare via GitGitGadget wrote:

> -void clear_alloc_state(struct alloc_state *s)
> +void alloc_state_free_and_null(struct alloc_state **s_)
>  {
> +	struct alloc_state *s = *s_;
> +
>  	while (s->slab_nr > 0) {
>  		s->slab_nr--;
>  		free(s->slabs[s->slab_nr]);
>  	}
>  
>  	FREE_AND_NULL(s->slabs);
> +	FREE_AND_NULL(*s_);
>  }

It's probably not worth going back and forth on this too much, but I
thought the happy medium was:

  if (!s)
	return;

That is, it is perfectly reasonable and friendly for it to be a noop to
free-and-null a NULL value (either never initialized, or already freed).
The overkill was worrying about whether somebody passed in a NULL
double-pointer. I.e., doing:

  alloc_state_free_and_null(&foo);

is reasonable and should be idempotent but:

  alloc_state_free_and_null(NULL);

is a silly programming error that we do not need to protect against.

-Peff

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH v5] alloc: fix dangling pointer in alloc_state cleanup
  2025-09-04 20:49         ` Jeff King
@ 2025-09-04 22:26           ` Junio C Hamano
  2025-09-05  0:02             ` ノウラ | Flare
                               ` (2 more replies)
  0 siblings, 3 replies; 28+ messages in thread
From: Junio C Hamano @ 2025-09-04 22:26 UTC (permalink / raw)
  To: Jeff King
  Cc: ノウラ | Flare via GitGitGadget, git,
	ノウラ | Flare

Jeff King <peff@peff.net> writes:

> It's probably not worth going back and forth on this too much, but I
> thought the happy medium was:
>
>   if (!s)
> 	return;
>
> That is, it is perfectly reasonable and friendly for it to be a noop to
> free-and-null a NULL value (either never initialized, or already freed).
> The overkill was worrying about whether somebody passed in a NULL
> double-pointer. I.e., doing:
>
>   alloc_state_free_and_null(&foo);
>
> is reasonable and should be idempotent

... when foo == NULL, e.g., after alloc_state_free_and_null(&foo)
has just successfully returned?

I can by that argument with the reasoning in the updated log message
below.  Does it good to everybody?

Thanks.

--- >8 ---
From: ノウラ | Flare <nouraellm@gmail.com>
Subject: [PATCH] alloc: fix dangling pointer in alloc_state cleanup

All callers of clear_alloc_state() immediately free what they
cleared, so currently it does not hurt anybody that the
alloc_state is left in an unreusable state, but it is an
error-prone API. Replace it with a new function that clears but
in addition frees the structure, as well as NULLing the pointer
that points at it and adjust existing callers.

As it is a moral equivalent of FREE_AND_NULL(), except that what it
frees has internal structure that needs to be cleaned, allow the
helper to be called twice in a row, by making a call with a pointer
to a pointer variable that already is NULLed.

While at it, rename allocate_alloc_state() and name the new
function alloc_state_free_and_null(), to follow more closely the
function naming convention specified in the CodingGuidelines
(namely, functions about S are named with S_ prefix and then
verb).

Signed-off-by: ノウラ | Flare <nouraellm@gmail.com>
Helped-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 alloc.c  | 10 ++++++++--
 alloc.h  |  4 ++--
 object.c | 26 ++++++++++----------------
 3 files changed, 20 insertions(+), 20 deletions(-)

diff --git a/alloc.c b/alloc.c
index 377e80f5dd..533a045c2a 100644
--- a/alloc.c
+++ b/alloc.c
@@ -36,19 +36,25 @@ struct alloc_state {
 	int slab_nr, slab_alloc;
 };
 
-struct alloc_state *allocate_alloc_state(void)
+struct alloc_state *alloc_state_alloc(void)
 {
 	return xcalloc(1, sizeof(struct alloc_state));
 }
 
-void clear_alloc_state(struct alloc_state *s)
+void alloc_state_free_and_null(struct alloc_state **s_)
 {
+	struct alloc_state *s = *s_;
+
+	if (!s)
+		return;
+
 	while (s->slab_nr > 0) {
 		s->slab_nr--;
 		free(s->slabs[s->slab_nr]);
 	}
 
 	FREE_AND_NULL(s->slabs);
+	FREE_AND_NULL(*s_);
 }
 
 static inline void *alloc_node(struct alloc_state *s, size_t node_size)
diff --git a/alloc.h b/alloc.h
index 3f4a0ad310..87a47a9709 100644
--- a/alloc.h
+++ b/alloc.h
@@ -14,7 +14,7 @@ void *alloc_commit_node(struct repository *r);
 void *alloc_tag_node(struct repository *r);
 void *alloc_object_node(struct repository *r);
 
-struct alloc_state *allocate_alloc_state(void);
-void clear_alloc_state(struct alloc_state *s);
+struct alloc_state *alloc_state_alloc(void);
+void alloc_state_free_and_null(struct alloc_state **s_);
 
 #endif
diff --git a/object.c b/object.c
index c1553ee433..986114a6db 100644
--- a/object.c
+++ b/object.c
@@ -517,12 +517,11 @@ struct parsed_object_pool *parsed_object_pool_new(struct repository *repo)
 	memset(o, 0, sizeof(*o));
 
 	o->repo = repo;
-	o->blob_state = allocate_alloc_state();
-	o->tree_state = allocate_alloc_state();
-	o->commit_state = allocate_alloc_state();
-	o->tag_state = allocate_alloc_state();
-	o->object_state = allocate_alloc_state();
-
+	o->blob_state = alloc_state_alloc();
+	o->tree_state = alloc_state_alloc();
+	o->commit_state = alloc_state_alloc();
+	o->tag_state = alloc_state_alloc();
+	o->object_state = alloc_state_alloc();
 	o->is_shallow = -1;
 	CALLOC_ARRAY(o->shallow_stat, 1);
 
@@ -573,16 +572,11 @@ void parsed_object_pool_clear(struct parsed_object_pool *o)
 	o->buffer_slab = NULL;
 
 	parsed_object_pool_reset_commit_grafts(o);
-	clear_alloc_state(o->blob_state);
-	clear_alloc_state(o->tree_state);
-	clear_alloc_state(o->commit_state);
-	clear_alloc_state(o->tag_state);
-	clear_alloc_state(o->object_state);
+	alloc_state_free_and_null(&o->blob_state);
+	alloc_state_free_and_null(&o->tree_state);
+	alloc_state_free_and_null(&o->commit_state);
+	alloc_state_free_and_null(&o->tag_state);
+	alloc_state_free_and_null(&o->object_state);
 	stat_validity_clear(o->shallow_stat);
-	FREE_AND_NULL(o->blob_state);
-	FREE_AND_NULL(o->tree_state);
-	FREE_AND_NULL(o->commit_state);
-	FREE_AND_NULL(o->tag_state);
-	FREE_AND_NULL(o->object_state);
 	FREE_AND_NULL(o->shallow_stat);
 }
-- 
2.51.0-314-g9a15cfd6dc



^ permalink raw reply related	[flat|nested] 28+ messages in thread

* Re: [PATCH v5] alloc: fix dangling pointer in alloc_state cleanup
  2025-09-04 22:26           ` Junio C Hamano
@ 2025-09-05  0:02             ` ノウラ | Flare
  2025-09-05 13:23               ` Jeff King
  2025-09-05  0:07             ` ノウラ | Flare
  2025-09-05 13:15             ` Jeff King
  2 siblings, 1 reply; 28+ messages in thread
From: ノウラ | Flare @ 2025-09-05  0:02 UTC (permalink / raw)
  To: Junio C Hamano, Jeff King
  Cc: ノウラ | Flare via GitGitGadget, git

No. I am confused here.

 > It is a programming error, period.  Do not silently return. That's
 > not being defensive.  That is sweeping a problem under the rug.

Yet

 > +    if (!s) return;

However, I agree with Peff. After calling alloc_state_free_and_null(&foo)
Having foo == NULL is an expected behavior, especially since the function
Is designed to free the memory and null out the caller’s pointer using
A double pointer ensuring the helper is idempotent

So, calling it again on the same pointer is safe because it simply
no-ops if the memory is already freed

Regarding the sanity check, it should be:

+ if (!*s) return;

The reasoning is the following:

=> s is the double pointer (the address of the caller’s pointer)
=> *s is the actual pointer to the memory we want to free

Thus, we check !*s to allow the function to safely handle already-NULL 
pointers
But if we instead checked !s then we would be testing whether the caller 
passed
A NULL double pointer which is a programmer error
So silently returning on !s would hide a bug

Finally, I'd appreciate more explicit instructions.

On 05/09/2025 00:26, Junio C Hamano wrote:
> Jeff King <peff@peff.net> writes:
>
>> It's probably not worth going back and forth on this too much, but I
>> thought the happy medium was:
>>
>>    if (!s)
>> 	return;
>>
>> That is, it is perfectly reasonable and friendly for it to be a noop to
>> free-and-null a NULL value (either never initialized, or already freed).
>> The overkill was worrying about whether somebody passed in a NULL
>> double-pointer. I.e., doing:
>>
>>    alloc_state_free_and_null(&foo);
>>
>> is reasonable and should be idempotent
> ... when foo == NULL, e.g., after alloc_state_free_and_null(&foo)
> has just successfully returned?
>
> I can by that argument with the reasoning in the updated log message
> below.  Does it good to everybody?
>
> Thanks.
>
> --- >8 ---
> From: ノウラ | Flare <nouraellm@gmail.com>
> Subject: [PATCH] alloc: fix dangling pointer in alloc_state cleanup
>
> All callers of clear_alloc_state() immediately free what they
> cleared, so currently it does not hurt anybody that the
> alloc_state is left in an unreusable state, but it is an
> error-prone API. Replace it with a new function that clears but
> in addition frees the structure, as well as NULLing the pointer
> that points at it and adjust existing callers.
>
> As it is a moral equivalent of FREE_AND_NULL(), except that what it
> frees has internal structure that needs to be cleaned, allow the
> helper to be called twice in a row, by making a call with a pointer
> to a pointer variable that already is NULLed.
>
> While at it, rename allocate_alloc_state() and name the new
> function alloc_state_free_and_null(), to follow more closely the
> function naming convention specified in the CodingGuidelines
> (namely, functions about S are named with S_ prefix and then
> verb).
>
> Signed-off-by: ノウラ | Flare <nouraellm@gmail.com>
> Helped-by: Jeff King <peff@peff.net>
> Signed-off-by: Junio C Hamano <gitster@pobox.com>
> ---
>   alloc.c  | 10 ++++++++--
>   alloc.h  |  4 ++--
>   object.c | 26 ++++++++++----------------
>   3 files changed, 20 insertions(+), 20 deletions(-)
>
> diff --git a/alloc.c b/alloc.c
> index 377e80f5dd..533a045c2a 100644
> --- a/alloc.c
> +++ b/alloc.c
> @@ -36,19 +36,25 @@ struct alloc_state {
>   	int slab_nr, slab_alloc;
>   };
>   
> -struct alloc_state *allocate_alloc_state(void)
> +struct alloc_state *alloc_state_alloc(void)
>   {
>   	return xcalloc(1, sizeof(struct alloc_state));
>   }
>   
> -void clear_alloc_state(struct alloc_state *s)
> +void alloc_state_free_and_null(struct alloc_state **s_)
>   {
> +	struct alloc_state *s = *s_;
> +
> +	if (!s)
> +		return;
> +
>   	while (s->slab_nr > 0) {
>   		s->slab_nr--;
>   		free(s->slabs[s->slab_nr]);
>   	}
>   
>   	FREE_AND_NULL(s->slabs);
> +	FREE_AND_NULL(*s_);
>   }
>   
>   static inline void *alloc_node(struct alloc_state *s, size_t node_size)
> diff --git a/alloc.h b/alloc.h
> index 3f4a0ad310..87a47a9709 100644
> --- a/alloc.h
> +++ b/alloc.h
> @@ -14,7 +14,7 @@ void *alloc_commit_node(struct repository *r);
>   void *alloc_tag_node(struct repository *r);
>   void *alloc_object_node(struct repository *r);
>   
> -struct alloc_state *allocate_alloc_state(void);
> -void clear_alloc_state(struct alloc_state *s);
> +struct alloc_state *alloc_state_alloc(void);
> +void alloc_state_free_and_null(struct alloc_state **s_);
>   
>   #endif
> diff --git a/object.c b/object.c
> index c1553ee433..986114a6db 100644
> --- a/object.c
> +++ b/object.c
> @@ -517,12 +517,11 @@ struct parsed_object_pool *parsed_object_pool_new(struct repository *repo)
>   	memset(o, 0, sizeof(*o));
>   
>   	o->repo = repo;
> -	o->blob_state = allocate_alloc_state();
> -	o->tree_state = allocate_alloc_state();
> -	o->commit_state = allocate_alloc_state();
> -	o->tag_state = allocate_alloc_state();
> -	o->object_state = allocate_alloc_state();
> -
> +	o->blob_state = alloc_state_alloc();
> +	o->tree_state = alloc_state_alloc();
> +	o->commit_state = alloc_state_alloc();
> +	o->tag_state = alloc_state_alloc();
> +	o->object_state = alloc_state_alloc();
>   	o->is_shallow = -1;
>   	CALLOC_ARRAY(o->shallow_stat, 1);
>   
> @@ -573,16 +572,11 @@ void parsed_object_pool_clear(struct parsed_object_pool *o)
>   	o->buffer_slab = NULL;
>   
>   	parsed_object_pool_reset_commit_grafts(o);
> -	clear_alloc_state(o->blob_state);
> -	clear_alloc_state(o->tree_state);
> -	clear_alloc_state(o->commit_state);
> -	clear_alloc_state(o->tag_state);
> -	clear_alloc_state(o->object_state);
> +	alloc_state_free_and_null(&o->blob_state);
> +	alloc_state_free_and_null(&o->tree_state);
> +	alloc_state_free_and_null(&o->commit_state);
> +	alloc_state_free_and_null(&o->tag_state);
> +	alloc_state_free_and_null(&o->object_state);
>   	stat_validity_clear(o->shallow_stat);
> -	FREE_AND_NULL(o->blob_state);
> -	FREE_AND_NULL(o->tree_state);
> -	FREE_AND_NULL(o->commit_state);
> -	FREE_AND_NULL(o->tag_state);
> -	FREE_AND_NULL(o->object_state);
>   	FREE_AND_NULL(o->shallow_stat);
>   }

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH v5] alloc: fix dangling pointer in alloc_state cleanup
  2025-09-04 22:26           ` Junio C Hamano
  2025-09-05  0:02             ` ノウラ | Flare
@ 2025-09-05  0:07             ` ノウラ | Flare
  2025-09-05  0:25               ` ノウラ | Flare
  2025-09-05 13:15             ` Jeff King
  2 siblings, 1 reply; 28+ messages in thread
From: ノウラ | Flare @ 2025-09-05  0:07 UTC (permalink / raw)
  To: Junio C Hamano, Jeff King
  Cc: ノウラ | Flare via GitGitGadget, git

No. I am confused here.

 > It is a programming error, period.  Do not silently return. That's
 > not being defensive.  That is sweeping a problem under the rug.

Yet

 > +    if (!s) return;

However, I agree with Peff. After calling alloc_state_free_and_null(&foo)
Having foo == NULL is an expected behavior, especially since the function
Is designed to free the memory and null out the caller’s pointer using
A double pointer ensuring the helper is idempotent

So, calling it again on the same pointer is safe because it simply
no-ops if the memory is already freed

Regarding the sanity check, it should be:

+ if (!*s) return;

The reasoning is the following:

=> s is the double pointer (the address of the caller’s pointer)
=> *s is the actual pointer to the memory we want to free

Thus, we check !*s to allow the function to safely handle already-NULL 
pointers
But if we instead checked !s then we would be testing whether the caller 
passed
A NULL double pointer which is a programmer error
So silently returning on !s would hide a bug

Finally, I'd appreciate more explicit instructions.

On 05/09/2025 00:26, Junio C Hamano wrote:
> Jeff King <peff@peff.net> writes:
>
>> It's probably not worth going back and forth on this too much, but I
>> thought the happy medium was:
>>
>>    if (!s)
>> 	return;
>>
>> That is, it is perfectly reasonable and friendly for it to be a noop to
>> free-and-null a NULL value (either never initialized, or already freed).
>> The overkill was worrying about whether somebody passed in a NULL
>> double-pointer. I.e., doing:
>>
>>    alloc_state_free_and_null(&foo);
>>
>> is reasonable and should be idempotent
> ... when foo == NULL, e.g., after alloc_state_free_and_null(&foo)
> has just successfully returned?
>
> I can by that argument with the reasoning in the updated log message
> below.  Does it good to everybody?
>
> Thanks.
>
> --- >8 ---
> From: ノウラ | Flare <nouraellm@gmail.com>
> Subject: [PATCH] alloc: fix dangling pointer in alloc_state cleanup
>
> All callers of clear_alloc_state() immediately free what they
> cleared, so currently it does not hurt anybody that the
> alloc_state is left in an unreusable state, but it is an
> error-prone API. Replace it with a new function that clears but
> in addition frees the structure, as well as NULLing the pointer
> that points at it and adjust existing callers.
>
> As it is a moral equivalent of FREE_AND_NULL(), except that what it
> frees has internal structure that needs to be cleaned, allow the
> helper to be called twice in a row, by making a call with a pointer
> to a pointer variable that already is NULLed.
>
> While at it, rename allocate_alloc_state() and name the new
> function alloc_state_free_and_null(), to follow more closely the
> function naming convention specified in the CodingGuidelines
> (namely, functions about S are named with S_ prefix and then
> verb).
>
> Signed-off-by: ノウラ | Flare <nouraellm@gmail.com>
> Helped-by: Jeff King <peff@peff.net>
> Signed-off-by: Junio C Hamano <gitster@pobox.com>
> ---
>   alloc.c  | 10 ++++++++--
>   alloc.h  |  4 ++--
>   object.c | 26 ++++++++++----------------
>   3 files changed, 20 insertions(+), 20 deletions(-)
>
> diff --git a/alloc.c b/alloc.c
> index 377e80f5dd..533a045c2a 100644
> --- a/alloc.c
> +++ b/alloc.c
> @@ -36,19 +36,25 @@ struct alloc_state {
>   	int slab_nr, slab_alloc;
>   };
>   
> -struct alloc_state *allocate_alloc_state(void)
> +struct alloc_state *alloc_state_alloc(void)
>   {
>   	return xcalloc(1, sizeof(struct alloc_state));
>   }
>   
> -void clear_alloc_state(struct alloc_state *s)
> +void alloc_state_free_and_null(struct alloc_state **s_)
>   {
> +	struct alloc_state *s = *s_;
> +
> +	if (!s)
> +		return;
> +
>   	while (s->slab_nr > 0) {
>   		s->slab_nr--;
>   		free(s->slabs[s->slab_nr]);
>   	}
>   
>   	FREE_AND_NULL(s->slabs);
> +	FREE_AND_NULL(*s_);
>   }
>   
>   static inline void *alloc_node(struct alloc_state *s, size_t node_size)
> diff --git a/alloc.h b/alloc.h
> index 3f4a0ad310..87a47a9709 100644
> --- a/alloc.h
> +++ b/alloc.h
> @@ -14,7 +14,7 @@ void *alloc_commit_node(struct repository *r);
>   void *alloc_tag_node(struct repository *r);
>   void *alloc_object_node(struct repository *r);
>   
> -struct alloc_state *allocate_alloc_state(void);
> -void clear_alloc_state(struct alloc_state *s);
> +struct alloc_state *alloc_state_alloc(void);
> +void alloc_state_free_and_null(struct alloc_state **s_);
>   
>   #endif
> diff --git a/object.c b/object.c
> index c1553ee433..986114a6db 100644
> --- a/object.c
> +++ b/object.c
> @@ -517,12 +517,11 @@ struct parsed_object_pool *parsed_object_pool_new(struct repository *repo)
>   	memset(o, 0, sizeof(*o));
>   
>   	o->repo = repo;
> -	o->blob_state = allocate_alloc_state();
> -	o->tree_state = allocate_alloc_state();
> -	o->commit_state = allocate_alloc_state();
> -	o->tag_state = allocate_alloc_state();
> -	o->object_state = allocate_alloc_state();
> -
> +	o->blob_state = alloc_state_alloc();
> +	o->tree_state = alloc_state_alloc();
> +	o->commit_state = alloc_state_alloc();
> +	o->tag_state = alloc_state_alloc();
> +	o->object_state = alloc_state_alloc();
>   	o->is_shallow = -1;
>   	CALLOC_ARRAY(o->shallow_stat, 1);
>   
> @@ -573,16 +572,11 @@ void parsed_object_pool_clear(struct parsed_object_pool *o)
>   	o->buffer_slab = NULL;
>   
>   	parsed_object_pool_reset_commit_grafts(o);
> -	clear_alloc_state(o->blob_state);
> -	clear_alloc_state(o->tree_state);
> -	clear_alloc_state(o->commit_state);
> -	clear_alloc_state(o->tag_state);
> -	clear_alloc_state(o->object_state);
> +	alloc_state_free_and_null(&o->blob_state);
> +	alloc_state_free_and_null(&o->tree_state);
> +	alloc_state_free_and_null(&o->commit_state);
> +	alloc_state_free_and_null(&o->tag_state);
> +	alloc_state_free_and_null(&o->object_state);
>   	stat_validity_clear(o->shallow_stat);
> -	FREE_AND_NULL(o->blob_state);
> -	FREE_AND_NULL(o->tree_state);
> -	FREE_AND_NULL(o->commit_state);
> -	FREE_AND_NULL(o->tag_state);
> -	FREE_AND_NULL(o->object_state);
>   	FREE_AND_NULL(o->shallow_stat);
>   }

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH v5] alloc: fix dangling pointer in alloc_state cleanup
  2025-09-05  0:07             ` ノウラ | Flare
@ 2025-09-05  0:25               ` ノウラ | Flare
  2025-09-05  1:03                 ` ノウラ | Flare
  2025-09-05 14:39                 ` Junio C Hamano
  0 siblings, 2 replies; 28+ messages in thread
From: ノウラ | Flare @ 2025-09-05  0:25 UTC (permalink / raw)
  To: Junio C Hamano, Jeff King
  Cc: ノウラ | Flare via GitGitGadget, git

By *s I am referring to *s_ so a sanity check with: if (!*s_) return;

On 05/09/2025 02:07, ノウラ | Flare wrote:
> No. I am confused here.
>
> > It is a programming error, period.  Do not silently return. That's
> > not being defensive.  That is sweeping a problem under the rug.
>
> Yet
>
> > +    if (!s) return;
>
> However, I agree with Peff. After calling alloc_state_free_and_null(&foo)
> Having foo == NULL is an expected behavior, especially since the function
> Is designed to free the memory and null out the caller’s pointer using
> A double pointer ensuring the helper is idempotent
>
> So, calling it again on the same pointer is safe because it simply
> no-ops if the memory is already freed
>
> Regarding the sanity check, it should be:
>
> + if (!*s) return;
>
> The reasoning is the following:
>
> => s is the double pointer (the address of the caller’s pointer)
> => *s is the actual pointer to the memory we want to free
>
> Thus, we check !*s to allow the function to safely handle already-NULL 
> pointers
> But if we instead checked !s then we would be testing whether the 
> caller passed
> A NULL double pointer which is a programmer error
> So silently returning on !s would hide a bug
>
> Finally, I'd appreciate more explicit instructions.
>
> On 05/09/2025 00:26, Junio C Hamano wrote:
>> Jeff King <peff@peff.net> writes:
>>
>>> It's probably not worth going back and forth on this too much, but I
>>> thought the happy medium was:
>>>
>>>    if (!s)
>>>     return;
>>>
>>> That is, it is perfectly reasonable and friendly for it to be a noop to
>>> free-and-null a NULL value (either never initialized, or already 
>>> freed).
>>> The overkill was worrying about whether somebody passed in a NULL
>>> double-pointer. I.e., doing:
>>>
>>>    alloc_state_free_and_null(&foo);
>>>
>>> is reasonable and should be idempotent
>> ... when foo == NULL, e.g., after alloc_state_free_and_null(&foo)
>> has just successfully returned?
>>
>> I can by that argument with the reasoning in the updated log message
>> below.  Does it good to everybody?
>>
>> Thanks.
>>
>> --- >8 ---
>> From: ノウラ | Flare <nouraellm@gmail.com>
>> Subject: [PATCH] alloc: fix dangling pointer in alloc_state cleanup
>>
>> All callers of clear_alloc_state() immediately free what they
>> cleared, so currently it does not hurt anybody that the
>> alloc_state is left in an unreusable state, but it is an
>> error-prone API. Replace it with a new function that clears but
>> in addition frees the structure, as well as NULLing the pointer
>> that points at it and adjust existing callers.
>>
>> As it is a moral equivalent of FREE_AND_NULL(), except that what it
>> frees has internal structure that needs to be cleaned, allow the
>> helper to be called twice in a row, by making a call with a pointer
>> to a pointer variable that already is NULLed.
>>
>> While at it, rename allocate_alloc_state() and name the new
>> function alloc_state_free_and_null(), to follow more closely the
>> function naming convention specified in the CodingGuidelines
>> (namely, functions about S are named with S_ prefix and then
>> verb).
>>
>> Signed-off-by: ノウラ | Flare <nouraellm@gmail.com>
>> Helped-by: Jeff King <peff@peff.net>
>> Signed-off-by: Junio C Hamano <gitster@pobox.com>
>> ---
>>   alloc.c  | 10 ++++++++--
>>   alloc.h  |  4 ++--
>>   object.c | 26 ++++++++++----------------
>>   3 files changed, 20 insertions(+), 20 deletions(-)
>>
>> diff --git a/alloc.c b/alloc.c
>> index 377e80f5dd..533a045c2a 100644
>> --- a/alloc.c
>> +++ b/alloc.c
>> @@ -36,19 +36,25 @@ struct alloc_state {
>>       int slab_nr, slab_alloc;
>>   };
>>   -struct alloc_state *allocate_alloc_state(void)
>> +struct alloc_state *alloc_state_alloc(void)
>>   {
>>       return xcalloc(1, sizeof(struct alloc_state));
>>   }
>>   -void clear_alloc_state(struct alloc_state *s)
>> +void alloc_state_free_and_null(struct alloc_state **s_)
>>   {
>> +    struct alloc_state *s = *s_;
>> +
>> +    if (!s)
>> +        return;
>> +
>>       while (s->slab_nr > 0) {
>>           s->slab_nr--;
>>           free(s->slabs[s->slab_nr]);
>>       }
>>         FREE_AND_NULL(s->slabs);
>> +    FREE_AND_NULL(*s_);
>>   }
>>     static inline void *alloc_node(struct alloc_state *s, size_t 
>> node_size)
>> diff --git a/alloc.h b/alloc.h
>> index 3f4a0ad310..87a47a9709 100644
>> --- a/alloc.h
>> +++ b/alloc.h
>> @@ -14,7 +14,7 @@ void *alloc_commit_node(struct repository *r);
>>   void *alloc_tag_node(struct repository *r);
>>   void *alloc_object_node(struct repository *r);
>>   -struct alloc_state *allocate_alloc_state(void);
>> -void clear_alloc_state(struct alloc_state *s);
>> +struct alloc_state *alloc_state_alloc(void);
>> +void alloc_state_free_and_null(struct alloc_state **s_);
>>     #endif
>> diff --git a/object.c b/object.c
>> index c1553ee433..986114a6db 100644
>> --- a/object.c
>> +++ b/object.c
>> @@ -517,12 +517,11 @@ struct parsed_object_pool 
>> *parsed_object_pool_new(struct repository *repo)
>>       memset(o, 0, sizeof(*o));
>>         o->repo = repo;
>> -    o->blob_state = allocate_alloc_state();
>> -    o->tree_state = allocate_alloc_state();
>> -    o->commit_state = allocate_alloc_state();
>> -    o->tag_state = allocate_alloc_state();
>> -    o->object_state = allocate_alloc_state();
>> -
>> +    o->blob_state = alloc_state_alloc();
>> +    o->tree_state = alloc_state_alloc();
>> +    o->commit_state = alloc_state_alloc();
>> +    o->tag_state = alloc_state_alloc();
>> +    o->object_state = alloc_state_alloc();
>>       o->is_shallow = -1;
>>       CALLOC_ARRAY(o->shallow_stat, 1);
>>   @@ -573,16 +572,11 @@ void parsed_object_pool_clear(struct 
>> parsed_object_pool *o)
>>       o->buffer_slab = NULL;
>>         parsed_object_pool_reset_commit_grafts(o);
>> -    clear_alloc_state(o->blob_state);
>> -    clear_alloc_state(o->tree_state);
>> -    clear_alloc_state(o->commit_state);
>> -    clear_alloc_state(o->tag_state);
>> -    clear_alloc_state(o->object_state);
>> +    alloc_state_free_and_null(&o->blob_state);
>> +    alloc_state_free_and_null(&o->tree_state);
>> +    alloc_state_free_and_null(&o->commit_state);
>> +    alloc_state_free_and_null(&o->tag_state);
>> +    alloc_state_free_and_null(&o->object_state);
>>       stat_validity_clear(o->shallow_stat);
>> -    FREE_AND_NULL(o->blob_state);
>> -    FREE_AND_NULL(o->tree_state);
>> -    FREE_AND_NULL(o->commit_state);
>> -    FREE_AND_NULL(o->tag_state);
>> -    FREE_AND_NULL(o->object_state);
>>       FREE_AND_NULL(o->shallow_stat);
>>   }

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH v5] alloc: fix dangling pointer in alloc_state cleanup
  2025-09-05  0:25               ` ノウラ | Flare
@ 2025-09-05  1:03                 ` ノウラ | Flare
  2025-09-05 14:39                 ` Junio C Hamano
  1 sibling, 0 replies; 28+ messages in thread
From: ノウラ | Flare @ 2025-09-05  1:03 UTC (permalink / raw)
  To: Junio C Hamano, Jeff King
  Cc: ノウラ | Flare via GitGitGadget, git

 > => s is the double pointer (the address of the caller’s pointer)

For this part as well, it's s_ not s




^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH v5] alloc: fix dangling pointer in alloc_state cleanup
  2025-09-04 22:26           ` Junio C Hamano
  2025-09-05  0:02             ` ノウラ | Flare
  2025-09-05  0:07             ` ノウラ | Flare
@ 2025-09-05 13:15             ` Jeff King
  2 siblings, 0 replies; 28+ messages in thread
From: Jeff King @ 2025-09-05 13:15 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: ノウラ | Flare via GitGitGadget, git,
	ノウラ | Flare

On Thu, Sep 04, 2025 at 03:26:21PM -0700, Junio C Hamano wrote:

> Jeff King <peff@peff.net> writes:
> 
> > It's probably not worth going back and forth on this too much, but I
> > thought the happy medium was:
> >
> >   if (!s)
> > 	return;
> >
> > That is, it is perfectly reasonable and friendly for it to be a noop to
> > free-and-null a NULL value (either never initialized, or already freed).
> > The overkill was worrying about whether somebody passed in a NULL
> > double-pointer. I.e., doing:
> >
> >   alloc_state_free_and_null(&foo);
> >
> > is reasonable and should be idempotent
> 
> ... when foo == NULL, e.g., after alloc_state_free_and_null(&foo)
> has just successfully returned?

Exactly.

> I can by that argument with the reasoning in the updated log message
> below.  Does it good to everybody?

Yep, it looks good to me.

-Peff

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH v5] alloc: fix dangling pointer in alloc_state cleanup
  2025-09-05  0:02             ` ノウラ | Flare
@ 2025-09-05 13:23               ` Jeff King
  2025-09-05 17:27                 ` ノウラ | Flare
  0 siblings, 1 reply; 28+ messages in thread
From: Jeff King @ 2025-09-05 13:23 UTC (permalink / raw)
  To: ノウラ | Flare
  Cc: Junio C Hamano, ノウラ | Flare via GitGitGadget,
	git

On Fri, Sep 05, 2025 at 02:02:39AM +0200, ノウラ | Flare wrote:

> > +    if (!s) return;
> 
> However, I agree with Peff. After calling alloc_state_free_and_null(&foo)
> Having foo == NULL is an expected behavior, especially since the function
> Is designed to free the memory and null out the caller’s pointer using
> A double pointer ensuring the helper is idempotent
> 
> So, calling it again on the same pointer is safe because it simply
> no-ops if the memory is already freed
> 
> Regarding the sanity check, it should be:
> 
> + if (!*s) return;

With the correction in your follow-up that this should be:

  if (!*s_) return;

I agree that is the right thing. But it is equivalent to:

  if (!s) return;

since we'll have just assigned "s". Which one to choose is purely a
matter of style. Using "*s_" perhaps makes it more clear that we are
sanity-checking the input (and could happen even before we assign "s").
Using "s" is consistent with the rest of the function in working with
the more direct pointer value. I am happy with either.

> The reasoning is the following:
> 
> => s is the double pointer (the address of the caller’s pointer)
> => *s is the actual pointer to the memory we want to free
> 
> Thus, we check !*s to allow the function to safely handle already-NULL
> pointers
> But if we instead checked !s then we would be testing whether the caller
> passed
> A NULL double pointer which is a programmer error
> So silently returning on !s would hide a bug

I think this is wrong. "s" is the value we would have allocated from
alloc_state_new(), and what we are actually interested in freeing. We
only see the double-pointer "s_" because we want to modify the caller's
containing pointer.

The FREE_AND_NULL() macro does not itself have this same confusion
because it _is_ a macro. So it is expanded in the caller's context and
does not need the extra layer of indirection.

I do not think this is a good idea, because it obscures things further
for people who are accustomed to C idioms, but one could imagine a more
general macro like:

  #define FREE_AND_NULL_WITH(p, fn) do { fn(p); (p) = NULL; } while (0)
  #define FREE_AND_NULL(p) FREE_AND_NULL_WITH(p, free)

and then you could do:

  FREE_AND_NULL_WITH(o->blob_state, alloc_state_free);

where alloc_state_free() would only receive the single pointer "s", and
would free it but not assign NULL.

-Peff

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH v5] alloc: fix dangling pointer in alloc_state cleanup
  2025-09-05  0:25               ` ノウラ | Flare
  2025-09-05  1:03                 ` ノウラ | Flare
@ 2025-09-05 14:39                 ` Junio C Hamano
  2025-09-05 17:47                   ` ノウラ | Flare
  1 sibling, 1 reply; 28+ messages in thread
From: Junio C Hamano @ 2025-09-05 14:39 UTC (permalink / raw)
  To: ノウラ | Flare
  Cc: Jeff King, ノウラ | Flare via GitGitGadget, git

ノウラ | Flare <nouraellm@gmail.com> writes:

> By *s I am referring to *s_ so a sanity check with: if (!*s_) return;

Because we

	s = *s_;

upfront, exactly because we do want the code to segfault if the
caller passes NULL to the function (so s_ that is NULL will cause a
NULL dereference right there), after that happens checking the NULL
ness of s and *s_ is equivalent.

And the whole point of doing "s = *s_" upfront is because readers
can easily get confused when they have to deal with double pointers.
The only reason why we pass the address of the pointer variable is
so that we can assign NULL to it at the very end, and before we can
do so, we want to be able inspect the innards of alloc_state object.
By dereferencing s_ early into s, the code can work with the object
itself without having to worry about following double pointer, so
even though if (!*s_) and if (!s) may be equivalent, writing the
latter is more in line with the whole reason why we have a variable
's' that is separate from 's_'.

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH v5] alloc: fix dangling pointer in alloc_state cleanup
  2025-09-05 13:23               ` Jeff King
@ 2025-09-05 17:27                 ` ノウラ | Flare
  0 siblings, 0 replies; 28+ messages in thread
From: ノウラ | Flare @ 2025-09-05 17:27 UTC (permalink / raw)
  To: Jeff King
  Cc: Junio C Hamano, ノウラ | Flare via GitGitGadget,
	git

>>> > I agree that is the right thing. But it is equivalent to:
>>>
>>> > if (!s) return;
>>>
>>> > since we'll have just assigned "s". Which one to choose is purely a
>>> > matter of style. Using "*s_" perhaps makes it more clear that we are
>>> > sanity-checking the input (and could happen even before we assign "s").
>>> > Using "s" is consistent with the rest of the function in working with
>>> > the more direct pointer value. I am happy with either.

I know, I used the original one (**s_) for the explanation. No the check cannot
happen before we assign (that's what I did initially) because tests will
fail given C90 requires declarations to be at the top of the block.


^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH v5] alloc: fix dangling pointer in alloc_state cleanup
  2025-09-05 14:39                 ` Junio C Hamano
@ 2025-09-05 17:47                   ` ノウラ | Flare
  0 siblings, 0 replies; 28+ messages in thread
From: ノウラ | Flare @ 2025-09-05 17:47 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Jeff King, ノウラ | Flare via GitGitGadget, git


On 05/09/2025 16:39, Junio C Hamano wrote:
> ノウラ | Flare <nouraellm@gmail.com> writes:
>
>> By *s I am referring to *s_ so a sanity check with: if (!*s_) return;
> Because we
>
> 	s = *s_;
>
> upfront, exactly because we do want the code to segfault if the
> caller passes NULL to the function (so s_ that is NULL will cause a
> NULL dereference right there), after that happens checking the NULL
> ness of s and *s_ is equivalent.
>
> And the whole point of doing "s = *s_" upfront is because readers
> can easily get confused when they have to deal with double pointers.
> The only reason why we pass the address of the pointer variable is
> so that we can assign NULL to it at the very end, and before we can
> do so, we want to be able inspect the innards of alloc_state object.
> By dereferencing s_ early into s, the code can work with the object
> itself without having to worry about following double pointer, so
> even though if (!*s_) and if (!s) may be equivalent, writing the
> latter is more in line with the whole reason why we have a variable
> 's' that is separate from 's_'.

I see your point, but I previously wrote we could lean more towards

Strict contract enforcement instead of defensive programming

Which would keep if (!s) return; instead of if (!s_ || !*s_) return;

In any case, I believe we all now agree on if (!s) return;

Pushing it right now.

Happy w-e y'all!


^ permalink raw reply	[flat|nested] 28+ messages in thread

* [PATCH v6] alloc: fix dangling pointer in alloc_state cleanup
  2025-09-04 17:44       ` [PATCH v5] " ノウラ | Flare via GitGitGadget
  2025-09-04 20:25         ` Junio C Hamano
  2025-09-04 20:49         ` Jeff King
@ 2025-09-05 18:51         ` ノウラ | Flare via GitGitGadget
  2025-09-05 19:37           ` Junio C Hamano
  2 siblings, 1 reply; 28+ messages in thread
From: ノウラ | Flare via GitGitGadget @ 2025-09-05 18:51 UTC (permalink / raw)
  To: git; +Cc: ノウラ | Flare,
	ノウラ | Flare

From: =?UTF-8?q?=E3=83=8E=E3=82=A6=E3=83=A9=20=7C=20Flare?=
 <nouraellm@gmail.com>

All callers of clear_alloc_state() immediately free what they
cleared, so currently it does not hurt anybody that the
alloc_state is left in an unreusable state, but it is an
error-prone API. Replace it with a new function that clears but
in addition frees the structure, as well as NULLing the pointer
that points at it and adjust existing callers.

As it is a moral equivalent of FREE_AND_NULL(), except that what it
frees has internal structure that needs to be cleaned, allow the
helper to be called twice in a row, by making a call with a pointer
to a pointer variable that already is NULLed.

While at it, rename allocate_alloc_state() and name the new
function alloc_state_free_and_null(), to follow more closely the
function naming convention specified in the CodingGuidelines
(namely, functions about S are named with S_ prefix and then
verb).

Signed-off-by: ノウラ | Flare <nouraellm@gmail.com>
Helped-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
    alloc: fix dangling pointer in alloc_state cleanup
    
    cc: Torsten Bögershausen tboegi@web.de cc: Jeff King peff@peff.net

Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2040%2Fnouraellm%2Ffix-dangling-pointer-v6
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2040/nouraellm/fix-dangling-pointer-v6
Pull-Request: https://github.com/git/git/pull/2040

Range-diff vs v5:

 1:  d04b0258f5 ! 1:  5afd5b6a26 alloc: fix dangling pointer in alloc_state cleanup
     @@ Commit message
          in addition frees the structure, as well as NULLing the pointer
          that points at it and adjust existing callers.
      
     +    As it is a moral equivalent of FREE_AND_NULL(), except that what it
     +    frees has internal structure that needs to be cleaned, allow the
     +    helper to be called twice in a row, by making a call with a pointer
     +    to a pointer variable that already is NULLed.
     +
          While at it, rename allocate_alloc_state() and name the new
          function alloc_state_free_and_null(), to follow more closely the
          function naming convention specified in the CodingGuidelines
     @@ Commit message
          verb).
      
          Signed-off-by: ノウラ | Flare <nouraellm@gmail.com>
     +    Helped-by: Jeff King <peff@peff.net>
     +    Signed-off-by: Junio C Hamano <gitster@pobox.com>
      
       ## alloc.c ##
      @@ alloc.c: struct alloc_state {
     @@ alloc.c: struct alloc_state {
      +void alloc_state_free_and_null(struct alloc_state **s_)
       {
      +	struct alloc_state *s = *s_;
     ++
     ++	if (!s)
     ++	    return;
      +
       	while (s->slab_nr > 0) {
       		s->slab_nr--;


 alloc.c  | 10 ++++++++--
 alloc.h  |  4 ++--
 object.c | 26 ++++++++++----------------
 3 files changed, 20 insertions(+), 20 deletions(-)

diff --git a/alloc.c b/alloc.c
index 377e80f5dd..7a0af6b4bb 100644
--- a/alloc.c
+++ b/alloc.c
@@ -36,19 +36,25 @@ struct alloc_state {
 	int slab_nr, slab_alloc;
 };
 
-struct alloc_state *allocate_alloc_state(void)
+struct alloc_state *alloc_state_alloc(void)
 {
 	return xcalloc(1, sizeof(struct alloc_state));
 }
 
-void clear_alloc_state(struct alloc_state *s)
+void alloc_state_free_and_null(struct alloc_state **s_)
 {
+	struct alloc_state *s = *s_;
+
+	if (!s)
+	    return;
+
 	while (s->slab_nr > 0) {
 		s->slab_nr--;
 		free(s->slabs[s->slab_nr]);
 	}
 
 	FREE_AND_NULL(s->slabs);
+	FREE_AND_NULL(*s_);
 }
 
 static inline void *alloc_node(struct alloc_state *s, size_t node_size)
diff --git a/alloc.h b/alloc.h
index 3f4a0ad310..87a47a9709 100644
--- a/alloc.h
+++ b/alloc.h
@@ -14,7 +14,7 @@ void *alloc_commit_node(struct repository *r);
 void *alloc_tag_node(struct repository *r);
 void *alloc_object_node(struct repository *r);
 
-struct alloc_state *allocate_alloc_state(void);
-void clear_alloc_state(struct alloc_state *s);
+struct alloc_state *alloc_state_alloc(void);
+void alloc_state_free_and_null(struct alloc_state **s_);
 
 #endif
diff --git a/object.c b/object.c
index c1553ee433..986114a6db 100644
--- a/object.c
+++ b/object.c
@@ -517,12 +517,11 @@ struct parsed_object_pool *parsed_object_pool_new(struct repository *repo)
 	memset(o, 0, sizeof(*o));
 
 	o->repo = repo;
-	o->blob_state = allocate_alloc_state();
-	o->tree_state = allocate_alloc_state();
-	o->commit_state = allocate_alloc_state();
-	o->tag_state = allocate_alloc_state();
-	o->object_state = allocate_alloc_state();
-
+	o->blob_state = alloc_state_alloc();
+	o->tree_state = alloc_state_alloc();
+	o->commit_state = alloc_state_alloc();
+	o->tag_state = alloc_state_alloc();
+	o->object_state = alloc_state_alloc();
 	o->is_shallow = -1;
 	CALLOC_ARRAY(o->shallow_stat, 1);
 
@@ -573,16 +572,11 @@ void parsed_object_pool_clear(struct parsed_object_pool *o)
 	o->buffer_slab = NULL;
 
 	parsed_object_pool_reset_commit_grafts(o);
-	clear_alloc_state(o->blob_state);
-	clear_alloc_state(o->tree_state);
-	clear_alloc_state(o->commit_state);
-	clear_alloc_state(o->tag_state);
-	clear_alloc_state(o->object_state);
+	alloc_state_free_and_null(&o->blob_state);
+	alloc_state_free_and_null(&o->tree_state);
+	alloc_state_free_and_null(&o->commit_state);
+	alloc_state_free_and_null(&o->tag_state);
+	alloc_state_free_and_null(&o->object_state);
 	stat_validity_clear(o->shallow_stat);
-	FREE_AND_NULL(o->blob_state);
-	FREE_AND_NULL(o->tree_state);
-	FREE_AND_NULL(o->commit_state);
-	FREE_AND_NULL(o->tag_state);
-	FREE_AND_NULL(o->object_state);
 	FREE_AND_NULL(o->shallow_stat);
 }

base-commit: f814da676ae46aac5be0a98b99373a76dee6cedb
-- 
gitgitgadget

^ permalink raw reply related	[flat|nested] 28+ messages in thread

* Re: [PATCH v6] alloc: fix dangling pointer in alloc_state cleanup
  2025-09-05 18:51         ` [PATCH v6] " ノウラ | Flare via GitGitGadget
@ 2025-09-05 19:37           ` Junio C Hamano
  0 siblings, 0 replies; 28+ messages in thread
From: Junio C Hamano @ 2025-09-05 19:37 UTC (permalink / raw)
  To: ノウラ | Flare via GitGitGadget
  Cc: git, ノウラ | Flare

"ノウラ | Flare via GitGitGadget" <gitgitgadget@gmail.com> writes:

> From: =?UTF-8?q?=E3=83=8E=E3=82=A6=E3=83=A9=20=7C=20Flare?=
>  <nouraellm@gmail.com>
>
> All callers of clear_alloc_state() immediately free what they
> cleared, so currently it does not hurt anybody that the
> alloc_state is left in an unreusable state, but it is an
> error-prone API. Replace it with a new function that clears but
> in addition frees the structure, as well as NULLing the pointer
> that points at it and adjust existing callers.
>
> As it is a moral equivalent of FREE_AND_NULL(), except that what it
> frees has internal structure that needs to be cleaned, allow the
> helper to be called twice in a row, by making a call with a pointer
> to a pointer variable that already is NULLed.
>
> While at it, rename allocate_alloc_state() and name the new
> function alloc_state_free_and_null(), to follow more closely the
> function naming convention specified in the CodingGuidelines
> (namely, functions about S are named with S_ prefix and then
> verb).
>
> Signed-off-by: ノウラ | Flare <nouraellm@gmail.com>
> Helped-by: Jeff King <peff@peff.net>
> Signed-off-by: Junio C Hamano <gitster@pobox.com>
> ---
>     alloc: fix dangling pointer in alloc_state cleanup
>     
>     cc: Torsten Bögershausen tboegi@web.de cc: Jeff King peff@peff.net
>
> Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2040%2Fnouraellm%2Ffix-dangling-pointer-v6
> Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2040/nouraellm/fix-dangling-pointer-v6
> Pull-Request: https://github.com/git/git/pull/2040

Nicely done.
I see no more need for further changes.

> +	if (!s)
> +	    return;

This must be indented with two tabs, i.e.

	if (!s)
		return;

but the copy I have since yesterday already is formatted that way,
so no need to resend this patch.

Thanks.  Let's mark the topic for 'next'.

^ permalink raw reply	[flat|nested] 28+ messages in thread

end of thread, other threads:[~2025-09-05 19:37 UTC | newest]

Thread overview: 28+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-08-26 19:57 [PATCH] reset slab_alloc and state fields in clear_alloc_state() ノウラ | Flare via GitGitGadget
2025-08-27  2:07 ` Junio C Hamano
2025-08-27 23:28 ` [PATCH v2] alloc: fix dangling pointer in alloc_state cleanup ノウラ | Flare via GitGitGadget
2025-08-28 19:29   ` Torsten Bögershausen
2025-08-28 19:47     ` Junio C Hamano
2025-08-28 20:01       ` Junio C Hamano
2025-08-29 13:00   ` [PATCH v3] " ノウラ | Flare via GitGitGadget
2025-09-03 11:18     ` Jeff King
2025-09-03 21:59       ` Junio C Hamano
2025-09-03 23:17     ` [PATCH v4] " ノウラ | Flare via GitGitGadget
2025-09-04  7:47       ` Junio C Hamano
2025-09-04 13:25         ` ノウラ | Flare
2025-09-04 16:43           ` Junio C Hamano
2025-09-04 17:44       ` [PATCH v5] " ノウラ | Flare via GitGitGadget
2025-09-04 20:25         ` Junio C Hamano
2025-09-04 20:49         ` Jeff King
2025-09-04 22:26           ` Junio C Hamano
2025-09-05  0:02             ` ノウラ | Flare
2025-09-05 13:23               ` Jeff King
2025-09-05 17:27                 ` ノウラ | Flare
2025-09-05  0:07             ` ノウラ | Flare
2025-09-05  0:25               ` ノウラ | Flare
2025-09-05  1:03                 ` ノウラ | Flare
2025-09-05 14:39                 ` Junio C Hamano
2025-09-05 17:47                   ` ノウラ | Flare
2025-09-05 13:15             ` Jeff King
2025-09-05 18:51         ` [PATCH v6] " ノウラ | Flare via GitGitGadget
2025-09-05 19:37           ` Junio C Hamano

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).