Subject: Re: [PATCH 02/19] global: assign non-const strings as required

From: Patrick Steinhardt <[email-masked]>
Date: Fri, 31 May 2024 15:00:35 +0200

On Thu, May 30, 2024 at 12:38:45PM -0700, Junio C Hamano wrote:
> Patrick Steinhardt <[email-masked]> writes:
> 
> > Well, we do. Not in `get_fetch_map()`, but in `query_refspecs()`. It
> > does weird stuff where it writes the result into either `src` or `dst`
> > depending on which of these fields is provided by the caller. Which
> > means that one of the fields would typically be a constant, whereas the
> > other one will be allocated.
> 
> Yes, <src, dst> is used as a pair of <key, value> to query one with
> the other (i.e. "where does this one go?" "where does this one come
> from?").
> 
> But we are not talking about const-ness of the member (iow, once you
> point a string with the member, you cannot repoint the pointer to
> another string), but we are talking about const-ness of the string
> that is pointed by the member (iow, not "char const *src" but "const
> char *src"), no?  If I ask "I have this src, where does it go?" with
> a refspec element filled with src, the dst member may need to be
> updated to point at the string that is the answer of the query, but
> that still can be done with "const char *src, *dst", can't it?  That
> was what I was wondering.

Well, yes, the pointers can be updated. But that doesn't solve the
underlying problem that the interface is not well-prepared to track
ownership of its inputs and outputs.

The root problem is that `struct refspec_item` is being abused for
multiple purposes. One of these purposes it to store parsed refspec
items. Another purpose is to actually serve as a query. The requirements
for those two purposes regarding the lifetime of associated strings is
different:

  - When using it for storage purposes the strings should be owned by
    the structure itself. Consequently, the lifetime should be managed
    by the structure too, namely via `refspec_item_clear()`.

  - When using it for parsing purposes the input string is owned by the
    caller, whereas the output string is owned by the callee and then
    handed over to the caller. Here we cannot rely on the structure
    itself to manage the lifetimes of both strings, at least not in the
    current form.

Now of course, we could amend `struct refspec_item` to grow two
additional fields `src_to_free` and `dst_to_free`. But that only doubles
down on this misdesigned interface.

The proper solution in my opinion is to detangle those two usecases and
split them out into two independent interfaces.

> And again you are conflating "allocated" with "read-write" here.  It
> is often convenient if a variable that points at an allocated string
> is of type "char *" and not "const char *", because you do not cast
> it when calling free().  But if you want to make a structure member
> or a variable that holds an allocated string responsible for
> _owning_ the piece of memory, then you need to consistently have the
> member point at an allocated piece of memory (or NULL), no?  What
> this patch does, i.e. prepare an on-stack refspec_str[] array that
> is initialized from a constant string, and have .src member point at
> it, would not make .src freeable.  In other words, .src pointing at
> an allocated piece of string "some of the time" alone is not a good
> justification to make it a non-const pointer, I would think.

That's fair enough. I'm trying to work around a broken interface, but do
not solve the underlying issue.

Patrick