git/list[1] front-page[2] threads[3] people[4] search[5] about
 

Re: [PATCH] for_each_string_list_item(): behave correctly for empty list

From
SZEDER Gábor <szeder.dev@gmail.com>
Date
Sep 16, 2017, 11:51 UTC
Message-ID
<20170916115118.15490-1-szeder.dev@gmail.com>
In-Reply-To
<b8951886-feab-a87a-9683-3c155cfa98a8@alum.mit.edu>
Show 13 quoted lines
> >> It would be a pain to have to change the signature of this macro, and
> >> we'd prefer not to add overhead to each iteration of the loop. So
> >> instead, whenever `list->items` is NULL, initialize `item` to point at
> >> a dummy `string_list_item` created for the purpose.
> > 
> > What signature change do you mean?  I don't understand what this
> > paragraph is alluding to.
> 
> I was thinking that one solution would be for the caller to provide a
> `size_t` variable for the macro's use as a counter (since I don't see a
> way for the macro to declare its own counter). The options are pretty
> limited because whatever the macro expands to has to play the same
> syntactic role as `for (...; ...; ...)`.

Another option to consider is to squeeze in an if-else before the for loop header to handle the empty list case like this:

diff --git a/string-list.h b/string-list.h
index 29bfb7ae4..9eed47de0 100644
--- a/string-list.h
+++ b/string-list.h
@@ -32,8 +32,11 @@ void string_list_clear_func(struct string_list *list, string_list_clear_func_t c
 typedef int (*string_list_each_func_t)(struct string_list_item *, void *);
 int for_each_string_list(struct string_list *list,
 			 string_list_each_func_t, void *cb_data);
-#define for_each_string_list_item(item,list) \
-	for (item = (list)->items; item < (list)->items + (list)->nr; ++item)
+#define for_each_string_list_item(item,list) 	\
+	if ((list)->items == NULL) {		\
+		/* empty list, do nothing */	\
+	} else					\
+		for (item = (list)->items; item < (list)->items + (list)->nr; ++item)
 
 /*
  * Apply want to each item in list, retaining only the ones for which

This way there would be neither additional overhead in each iteration
nor a new global.

Alas, there is a catch.  We can't use curly braces in the macro's else
branch, because the macro would contain only the opening brace but not
the closing one, which must come after the end of the loop's body.
This means that the modified macro couldn't be used in if-else
branches which themselves don't have curly braces, because it causes
ambiguity:

  if (condition)
      for_each_string_list_item(item, list)
          a_simple_oneliner(item);

Our coding guidelines encourage this style for one-liner loop bodies,
and there is indeed one such place in our codebase, so the following
hunk is needed as well:

diff --git a/send-pack.c b/send-pack.c
index 11d6f3d98..00fa1622f 100644
--- a/send-pack.c
+++ b/send-pack.c
@@ -295,9 +295,10 @@ static int generate_push_cert(struct strbuf *req_buf,
 	}
 	if (push_cert_nonce[0])
 		strbuf_addf(&cert, "nonce %s\n", push_cert_nonce);
-	if (args->push_options)
+	if (args->push_options) {
 		for_each_string_list_item(item, args->push_options)
 			strbuf_addf(&cert, "push-option %s\n", item->string);
+	}
 	strbuf_addstr(&cert, "\n");
 
 	for (ref = remote_refs; ref; ref = ref->next) {


Luckily, reasonably modern compilers warn about such ambiguity, so
perhaps this is an acceptable compromise?


> > [...]
> > Does the following alternate fix work?  I think I prefer it because
> > it doesn't require introducing a new global. [...]
> >  #define for_each_string_list_item(item,list) \
> > -	for (item = (list)->items; item < (list)->items + (list)->nr; ++item)
> > +	for (item = (list)->items; \
> > +	     (list)->items && item < (list)->items + (list)->nr; \
> > +	     ++item)
> 
> This is the possibility that I was referring to as "add[ing] overhead to
> each iteration of the loop". I'd rather not add an extra test-and-branch
> to every iteration of a loop in which `list->items` is *not* NULL, which
> your solution appears to do. Or are compilers routinely able to optimize
> the check out?
> 
> The new global might be aesthetically unpleasant, but it only costs two
> words of memory, so I don't see it as a big disadvantage.
> 
> Another, more invasive change would be to initialize
> `string_list::items` to *always* point at `dummy_string_list_item`,
> rather similar to how `strbuf_slopbuf` is pointed at by empty `strbuf`s.
> But I really don't think the effort would be justified.
Previous: Michael HaggertyNext: Michael Haggerty
Message 4 of 29 in “for_each_string_list_item(): behave correctly for empty list”
  1. for_each_string_list_item(): behave correctly for empty listMichael Haggerty, Sep 15, 2017
  2. Jonathan NiederSep 15, 2017
  3. Michael HaggertySep 16, 2017
  4. SZEDER GáborSep 16, 2017
  5. Michael HaggertySep 17, 2017
  6. Kaartic SivaraamSep 19, 2017
  7. Junio C HamanoSep 20, 2017
  8. Jonathan NiederSep 20, 2017
  9. Junio C HamanoSep 20, 2017
  10. Jonathan NiederSep 20, 2017
  11. Junio C HamanoSep 20, 2017
  12. for_each_string_list_item: avoid undefined behavior for empty listJonathan Nieder, Sep 20, 2017
  13. Junio C HamanoSep 20, 2017
  14. Michael HaggertySep 20, 2017
  15. Kaartic SivaraamSep 20, 2017
  16. doc: camelCase the config variables to improve readabilityKaartic Sivaraam, Sep 20, 2017
  17. Andreas SchwabSep 20, 2017
  18. Jonathan NiederSep 20, 2017
  19. Andreas SchwabSep 20, 2017
  20. Junio C HamanoSep 21, 2017
  21. Andreas SchwabSep 21, 2017
  22. Kaartic SivaraamSep 20, 2017
  23. Junio C HamanoSep 17, 2017
  24. Michael HaggertySep 17, 2017
  25. Junio C HamanoSep 18, 2017
  26. Stefan BellerSep 19, 2017
  27. Michael HaggertySep 19, 2017
  28. SZEDER GáborSep 19, 2017
  29. SZEDER GáborSep 19, 2017

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.