reset slab_alloc and state fields in clear_alloc_state()
PATCH 1/1 reset slab_alloc and state fields in clear_alloc_state()
From: =?UTF-8?q?=E3=83=8E=E3=82=A6=E3=83=A9?= <[email protected]>
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.
---
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(+)
alloc.c +3 -0
@@ -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
Signature · 1 line
gitgitgadget
Quoted 1 line · ノウラ | Flare via GitGitGadget
From: =?UTF-8?q?=E3=83=8E=E3=82=A6=E3=83=A9?= <[email protected]>
This name used on the in-body From: line must match who signs off the patch below on Signed-off-by: line.
Quoted 6 lines
clear_alloc_state()freed all slabs and nulled the slabs pointer but leftslab_alloc, nr, and p unchanged. If thealloc_stateis reused,ALLOC_GROW()can wrongly assume that the slab array is already allocated becauseslab_allocstill holds a stale nonzero capacity. In that cases->slabsremains 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(-)
alloc.c +5 -2
@@ -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)
alloc.h +2 -2
@@ -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
object.c +11 -15
@@ -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); }
PATCH v2 1/1 alloc: fix dangling pointer in alloc_state cleanup
In reply to ノウラ | Flare via GitGitGadgetFrom: =?UTF-8?q?=E3=83=8E=E3=82=A6=E3=83=A9=20=7C=20Flare?=
<[email protected]>
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.
---
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(-)
alloc.c +7 -2
@@ -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)
alloc.h +2 -2
@@ -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
object.c +10 -16
@@ -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
Signature · 1 line
gitgitgadget
Re: [PATCH v2] alloc: fix dangling pointer in alloc_state cleanup
Quoted 9 lines · ノウラ | Flare via GitGitGadget
From: =?UTF-8?q?=E3=83=8E=E3=82=A6=E3=83=A9=20=7C=20Flare?=
<[email protected]>
clear_alloc_state()freed all slabs and nulled the slabs pointer but leftslab_alloc, nr, and p unchanged. If thealloc_stateis reused,ALLOC_GROW()can wrongly assume that the slab array is already allocated becauseslab_allocstill holds a stale nonzero capacity. In that cases->slabsremains NULL and the next dereference writes through a NULL pointer, causing undefined behavior.
This is good.
Quoted 2 lines
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 ...
Quoted 12 lines
- Renames
allocate_alloc_state()→alloc_state_alloc().
- Replaces the “just clear” API withalloc_state_free_and_null(struct alloc_state **s_), which frees all slabs and thealloc_stateitself, and then nulls the caller’s pointer.
- Updates call sites to use the new helpers and drops redundantFREE_AND_NULL()calls.This makes the
alloc_statelifecycle API harder to misuse, eliminates stale-capacity state,
and aligns naming with project conventions.
Re: [PATCH v2] alloc: fix dangling pointer in alloc_state cleanup
Quoted 13 lines · Torsten Bögershausen
Quoted 9 lines · ノウラ | Flare via GitGitGadget
From: =?UTF-8?q?=E3=83=8E=E3=82=A6=E3=83=A9=20=7C=20Flare?=
<[email protected]>
clear_alloc_state()freed all slabs and nulled the slabs pointer but leftslab_alloc, nr, and p unchanged. If thealloc_stateis reused,ALLOC_GROW()can wrongly assume that the slab array is already allocated becauseslab_allocstill holds a stale nonzero capacity. In that cases->slabsremains NULL and the next dereference writes through a NULL pointer, causing undefined behavior.This is good.
Quoted 1 line
Quoted 7 lines
Quoted 1 line
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.
Re: [PATCH v2] alloc: fix dangling pointer in alloc_state cleanup
Quoted 8 lines · Junio C Hamano
Quoted 6 lines
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.
PATCH v3 1/1 alloc: fix dangling pointer in alloc_state cleanup
In reply to ノウラ | Flare via GitGitGadgetFrom: =?UTF-8?q?=E3=83=8E=E3=82=A6=E3=83=A9=20=7C=20Flare?=
<[email protected]>
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).
---
alloc: fix dangling pointer in alloc_state cleanup
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).
alloc.c | 9 +++++++-- alloc.h | 4 ++-- object.c | 26 ++++++++++---------------- 3 files changed, 19 insertions(+), 20 deletions(-)
alloc.c +7 -2
@@ -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)
alloc.h +2 -2
@@ -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
object.c +10 -16
@@ -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
Signature · 1 line
gitgitgadget
Re: [PATCH v3] alloc: fix dangling pointer in alloc_state cleanup
alloc.c struct alloc_state { · line 49
Quoted 6 lines · ノウラ | Flare via GitGitGadget
alloc.c +5
-0+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
Re: [PATCH v3] alloc: fix dangling pointer in alloc_state cleanup
Quoted 12 lines · Jeff King
alloc.c struct alloc_state { · line 49
Quoted 6 lines · ノウラ | Flare via GitGitGadget
alloc.c +5
-0+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".
Quoted 7 lines
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.
PATCH v4 1/1 alloc: fix dangling pointer in alloc_state cleanup
In reply to ノウラ | Flare via GitGitGadgetFrom: =?UTF-8?q?=E3=83=8E=E3=82=A6=E3=83=A9=20=7C=20Flare?=
<[email protected]>
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).
---
alloc: fix dangling pointer in alloc_state cleanup
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(-)
alloc.c +9 -2
@@ -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)
alloc.h +2 -2
@@ -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
object.c +10 -16
@@ -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
Signature · 1 line
gitgitgadget
Re: [PATCH v4] alloc: fix dangling pointer in alloc_state cleanup
alloc.c struct alloc_state { · line 48
Quoted 5 lines · ノウラ | Flare via GitGitGadget
alloc.c +4
-0+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?
Re: [PATCH v4] alloc: fix dangling pointer in alloc_state cleanup
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.
Quoted 16 lines · Junio C Hamano
alloc.c struct alloc_state { · line 48
Quoted 5 lines · ノウラ | Flare via GitGitGadget
alloc.c +4
-0+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?
Re: [PATCH v4] alloc: fix dangling pointer in alloc_state cleanup
Quoted 2 lines · ノウラ | Flare
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.
PATCH v5 1/1 alloc: fix dangling pointer in alloc_state cleanup
In reply to ノウラ | Flare via GitGitGadgetFrom: =?UTF-8?q?=E3=83=8E=E3=82=A6=E3=83=A9=20=7C=20Flare?=
<[email protected]>
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).
---
alloc: fix dangling pointer in alloc_state cleanup
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(-)
alloc.c +5 -2
@@ -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)
alloc.h +2 -2
@@ -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
object.c +10 -16
@@ -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
Signature · 1 line
gitgitgadget
Re: [PATCH v5] alloc: fix dangling pointer in alloc_state cleanup
Quoted 18 lines · ノウラ | Flare via GitGitGadget
index
377e80f5dd..3a5d0b2bd8100644alloc.c +4
-2@@ -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_; +
alloc.c struct alloc_state { · line 57
Quoted 10 lines
alloc.c +1
-0while (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.
Quoted 30 lines
alloc.h +2
-2@@ -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 object.c +3
-5@@ -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);
object.c void parsed_object_pool_clear(struct parsed_object_pool *o) · line 582
Quoted 10 lines
object.c +2
-5+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.
Re: [PATCH v5] alloc: fix dangling pointer in alloc_state cleanup
In reply to ノウラ | Flare via GitGitGadgetalloc.c struct alloc_state { · line 55
Quoted 13 lines · ノウラ | Flare via GitGitGadget
alloc.c +4
-1-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
Re: [PATCH v5] alloc: fix dangling pointer in alloc_state cleanup
Quoted 6 lines · Jeff King
It's probably not worth going back and forth on this too much, but I thought the happy medium was:
if (!s)
return;
Quoted 8 lines
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 <[email protected]>
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).
--- alloc.c | 10 ++++++++-- alloc.h | 4 ++-- object.c | 26 ++++++++++---------------- 3 files changed, 20 insertions(+), 20 deletions(-)
alloc.c +8 -2
@@ -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)
alloc.h +2 -2
@@ -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
object.c +10 -16
@@ -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); }
Signature · 1 line
2.51.0-314-g9a15cfd6dc
Re: [PATCH v5] alloc: fix dangling pointer in alloc_state cleanup
No. I am confused here.
Quoted 2 lines
It is a programming error, period. Do not silently return. That's not being defensive. That is sweeping a problem under the rug.
Yet
Quoted 1 line
+ 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.
Quoted 145 lines · Junio C Hamano
Quoted 6 lines · Jeff King
It's probably not worth going back and forth on this too much, but I thought the happy medium was:
if (!s) return;Quoted 8 lines
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 <[email protected]>
Subject: [PATCH] alloc: fix dangling pointer inalloc_statecleanupAll callers of
clear_alloc_state()immediately free what they cleared, so currently it does not hurt anybody that thealloc_stateis 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 functionalloc_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 <[email protected]>Helped-by Jeff King <[email protected]>Signed-off-by Junio C Hamano <[email protected]>--- alloc.c | 10 ++++++++-- alloc.h | 4 ++-- object.c | 26 ++++++++++---------------- 3 files changed, 20 insertions(+), 20 deletions(-)alloc.c +8
-2@@ -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)alloc.h +2
-2@@ -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 object.c +10
-16@@ -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); }
Re: [PATCH v5] alloc: fix dangling pointer in alloc_state cleanup
Quoted 7 lines · ノウラ | Flare
Quoted 1 line
+ 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
Quoted 6 lines
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.
Quoted 11 lines
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
Re: [PATCH v5] alloc: fix dangling pointer in alloc_state cleanup
Quoted 9 lines
Quoted 9 lines
Quoted 9 lines
Quoted 9 lines
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.
Re: [PATCH v5] alloc: fix dangling pointer in alloc_state cleanup
In reply to Junio C HamanoNo. I am confused here.
Quoted 2 lines
It is a programming error, period. Do not silently return. That's not being defensive. That is sweeping a problem under the rug.
Yet
Quoted 1 line
+ 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.
Quoted 145 lines · Junio C Hamano
Quoted 6 lines · Jeff King
It's probably not worth going back and forth on this too much, but I thought the happy medium was:
if (!s) return;Quoted 8 lines
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 <[email protected]>
Subject: [PATCH] alloc: fix dangling pointer inalloc_statecleanupAll callers of
clear_alloc_state()immediately free what they cleared, so currently it does not hurt anybody that thealloc_stateis 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 functionalloc_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 <[email protected]>Helped-by Jeff King <[email protected]>Signed-off-by Junio C Hamano <[email protected]>--- alloc.c | 10 ++++++++-- alloc.h | 4 ++-- object.c | 26 ++++++++++---------------- 3 files changed, 20 insertions(+), 20 deletions(-)alloc.c +8
-2@@ -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)alloc.h +2
-2@@ -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 object.c +10
-16@@ -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); }
Re: [PATCH v5] alloc: fix dangling pointer in alloc_state cleanup
By *s I am referring to *s_ so a sanity check with: if (!*s_) return;
Quoted 171 lines · ノウラ | Flare
No. I am confused here.
Quoted 2 lines
It is a programming error, period. Do not silently return. That's not being defensive. That is sweeping a problem under the rug.
Yet
Quoted 1 line
+ 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 idempotentSo, 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 bugFinally, I'd appreciate more explicit instructions.
Quoted 137 lines · Junio C Hamano
Quoted 6 lines · Jeff King
It's probably not worth going back and forth on this too much, but I thought the happy medium was:
if (!s)
return;Quoted 8 lines
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 <[email protected]>
Subject: [PATCH] alloc: fix dangling pointer inalloc_statecleanupAll callers of
clear_alloc_state()immediately free what they cleared, so currently it does not hurt anybody that thealloc_stateis 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 functionalloc_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 <[email protected]>Helped-by Jeff King <[email protected]>Signed-off-by Junio C Hamano <[email protected]>---
alloc.c | 10 ++++++++--
alloc.h | 4 ++--
object.c| 26 ++++++++++----------------3 files changed, 20 insertions(+), 20 deletions(-)alloc.c +8
-0@@ -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)alloc.h +2
-1@@ -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_); #endifobject.c +5
-6@@ -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);
}
Re: [PATCH v5] alloc: fix dangling pointer in alloc_state cleanup
Quoted 1 line
=> s is the double pointer (the address of the caller’s pointer)
For this part as well, it's s_ not s
Re: [PATCH v5] alloc: fix dangling pointer in alloc_state cleanup
In reply to ノウラ | FlareQuoted 1 line · ノウラ | Flare
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_'.
Re: [PATCH v5] alloc: fix dangling pointer in alloc_state cleanup
Quoted 12 lines · Junio C Hamano
Quoted 1 line · ノウラ | Flare
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.
Quoted 10 lines
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_stateobject. 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!
Re: [PATCH v5] alloc: fix dangling pointer in alloc_state cleanup
In reply to Junio C HamanoQuoted 13 lines · Junio C Hamano
Quoted 11 lines · Jeff King
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:
Quoted 6 lines
Quoted 3 lines
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.
Quoted 2 lines
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
PATCH v6 1/1 alloc: fix dangling pointer in alloc_state cleanup
In reply to ノウラ | Flare via GitGitGadgetFrom: =?UTF-8?q?=E3=83=8E=E3=82=A6=E3=83=A9=20=7C=20Flare?=
<[email protected]>
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).
---
alloc: fix dangling pointer in alloc_state cleanup
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).
+ Helped-by: Jeff King <[email protected]>
+ Signed-off-by: Junio C Hamano <[email protected]>
## 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(-)
alloc.c +8 -2
@@ -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)
alloc.h +2 -2
@@ -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
object.c +10 -16
@@ -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
Signature · 1 line
gitgitgadget
Re: [PATCH v6] alloc: fix dangling pointer in alloc_state cleanup
Quoted 24 lines · ノウラ | Flare via GitGitGadget
From: =?UTF-8?q?=E3=83=8E=E3=82=A6=E3=83=A9=20=7C=20Flare?=
<[email protected]>All callers of
clear_alloc_state()immediately free what they cleared, so currently it does not hurt anybody that thealloc_stateis 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 functionalloc_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 <[email protected]>Helped-by Jeff King <[email protected]>Signed-off-by Junio C Hamano <[email protected]>
Quoted 8 lines
---
alloc: fix dangling pointer inalloc_statecleanupcc Torsten Bögershausen [email protected] cc: Jeff King [email protected]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.
alloc.c struct alloc_state { · line 49
Quoted 2 lines
alloc.c +2
-0+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'.