On 08/28/2017 10:32 PM, Martin Ågren wrote: > split_symref_update() receives a string-pointer `referent` and adds it > to the list of `affected_refnames`. The list simply holds on to the > pointers it is given, it does not copy the strings and it never frees > them. The `referent` string in split_symref_update() belongs to a string > buffer in the caller. After we return, the string will be leaked. > > In the next patch, we want to properly release the string buffer in the > caller, but we can't safely do so until we've made sure that > `affected_refnames` will not be holding on to a pointer to the string. > We could configure the list to handle its own resources, but it would > mean some alloc/free-churning. The list is already handling other > strings (through other code paths) which we do not need to worry about, > and we'd be memory-churning those strings too, completely unnecessary. > > Observe that split_symref_update() creates a `new_update`-object and > that `new_update->refname` is then a copy of `referent`. The difference > is, this copy will be freed, and it will be freed *after* > `affected_refnames` has been cleared. > > Rearrange the handling of `referent`, so that we don't add it directly > to `affected_refnames`. Instead, first just check whether `referent` > exists in the string list, and later add `new_update->refname`. > > Helped-by: Michael Haggerty <mhag...@alum.mit.edu> > Signed-off-by: Martin Ågren <martin.ag...@gmail.com> > --- > Thanks Junio and Michael for your comments on the first version. This > first patch is now completely different and much much better (thanks > Michael!). The commit message should also be better (sorry Junio...). > The second one has a new commit message, but the diff is the same. > > Martin > > refs/files-backend.c | 16 ++++++++++++---- > 1 file changed, 12 insertions(+), 4 deletions(-) > > diff --git a/refs/files-backend.c b/refs/files-backend.c > index 5cca55510..bdb0e22e5 100644 > --- a/refs/files-backend.c > +++ b/refs/files-backend.c > @@ -2140,13 +2140,12 @@ static int split_symref_update(struct files_ref_store > *refs, > > /* > * First make sure that referent is not already in the > - * transaction. This insertion is O(N) in the transaction > + * transaction. This check is O(N) in the transaction
The check is not O(N); it is O(lg N) because it is implemented via a binary search in the (sorted) `affected_refnames`. The insertion below is still O(N), because it has to shift entries later in the list to the right to make room for the new entry. > * size, but it happens at most once per symref in a > * transaction. > */ > - item = string_list_insert(affected_refnames, referent); > - if (item->util) { > - /* An entry already existed */ > + if (string_list_has_string(affected_refnames, referent)) { > + /* An entry already exists */ > strbuf_addf(err, > "multiple updates for '%s' (including one " > "via symref '%s') are not allowed", > @@ -2181,6 +2180,15 @@ static int split_symref_update(struct files_ref_store > *refs, > update->flags |= REF_LOG_ONLY | REF_NODEREF; > update->flags &= ~REF_HAVE_OLD; > > + /* > + * Add the referent. This insertion is O(N) in the transaction > + * size, but it happens at most once per symref in a > + * transaction. Make sure to add new_update->refname, which will > + * be valid as long as affected_refnames is in use, and NOT > + * referent, which might soon be freed by our caller. > + */ > + item = string_list_insert(affected_refnames, new_update->refname); > + assert(!item->util); We generally avoid using `assert()`. It would be preferable to use if (item->util) BUG("%s unexpectedly found in affected_refnames", new_update->refname); > item->util = new_update; > > return 0; > Otherwise, looks good! Michael