git@vger.kernel.org mailing list mirror (one of many)
 help / color / mirror / code / Atom feed
* [PATCH] for_each_string_list_item(): behave correctly for empty list
@ 2017-09-15 16:00 Michael Haggerty
  2017-09-15 18:43 ` Jonathan Nieder
  2017-09-17  0:59 ` Junio C Hamano
  0 siblings, 2 replies; 29+ messages in thread
From: Michael Haggerty @ 2017-09-15 16:00 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Alex Riesen, git, Michael Haggerty

If you pass a newly-initialized or newly-cleared `string_list` to
`for_each_string_list_item()`, then the latter does

    for (
            item = (list)->items; /* note, this is NULL */
            item < (list)->items + (list)->nr; /* note: NULL + 0 */
            ++item)

Even though this probably works almost everywhere, it is undefined
behavior, and it could plausibly cause highly-optimizing compilers to
misbehave.

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.

This problem was noticed by Coverity.

Signed-off-by: Michael Haggerty <mhagger@alum.mit.edu>
---
Just a little thing I noticed in a Coverity report. This macro has
been broken since it was first introduced, in 2010.

This patch applies against maint. It is also available from my Git
fork [1] as branch `iter-empty-string-list`.

Michael

[1] https://github.com/mhagger/git

 string-list.c | 2 ++
 string-list.h | 7 +++++--
 2 files changed, 7 insertions(+), 2 deletions(-)

diff --git a/string-list.c b/string-list.c
index 806b4c8723..7eacf6037f 100644
--- a/string-list.c
+++ b/string-list.c
@@ -1,6 +1,8 @@
 #include "cache.h"
 #include "string-list.h"
 
+struct string_list_item dummy_string_list_item;
+
 void string_list_init(struct string_list *list, int strdup_strings)
 {
 	memset(list, 0, sizeof(*list));
diff --git a/string-list.h b/string-list.h
index 29bfb7ae45..79bb78d80a 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)
+extern struct string_list_item dummy_string_list_item;
+#define for_each_string_list_item(item,list)                                 \
+	for (item = (list)->items ? (list)->items : &dummy_string_list_item; \
+	     item < (list)->items + (list)->nr;                              \
+	     ++item)
 
 /*
  * Apply want to each item in list, retaining only the ones for which
-- 
2.14.1


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

* Re: [PATCH] for_each_string_list_item(): behave correctly for empty list
  2017-09-15 16:00 [PATCH] for_each_string_list_item(): behave correctly for empty list Michael Haggerty
@ 2017-09-15 18:43 ` Jonathan Nieder
  2017-09-16  4:06   ` Michael Haggerty
  2017-09-17  0:59 ` Junio C Hamano
  1 sibling, 1 reply; 29+ messages in thread
From: Jonathan Nieder @ 2017-09-15 18:43 UTC (permalink / raw)
  To: Michael Haggerty; +Cc: Junio C Hamano, Alex Riesen, git

Hi,

Michael Haggerty wrote:

> If you pass a newly-initialized or newly-cleared `string_list` to
> `for_each_string_list_item()`, then the latter does
>
>     for (
>             item = (list)->items; /* note, this is NULL */
>             item < (list)->items + (list)->nr; /* note: NULL + 0 */
>             ++item)
>
> Even though this probably works almost everywhere, it is undefined
> behavior, and it could plausibly cause highly-optimizing compilers to
> misbehave.

Wait, NULL + 0 is undefined behavior?

*checks the standard*  C99 section 6.5.6.8 says

	"If both the pointer operand and the result point to elements
	of the same array object, or one past the last element of the
	array object, the evaluation shall not produce an overflow;
	otherwise, the behavior is undefined."

C99 section 7.17.3 says

	"NULL

	which expands to an implementation-defined null pointer constant"

6.3.2.3.3 says

	"An integer constant expression with the value 0, or such an
	expression cast to type void *, is called a null pointer
	constant.  If a null pointer constant is converted to a
	pointer type, the resulting pointer, called a null pointer, is
	guaranteed to compare unequal to a pointer to any object or
	function."

NULL doesn't point to anything so it looks like adding 0 to a null
pointer is indeed undefined.  (As a piece of trivia, strictly speaking
NULL + 0 would be undefined on some implementations and defined on
others, since an implementation is permitted to #define NULL to 0.)

So Coverity is not just warning because it is not able to guarantee
that list->nr is 0.  Huh.

> 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.

> This problem was noticed by Coverity.
>
> Signed-off-by: Michael Haggerty <mhagger@alum.mit.edu>
> ---
[...]
>  string-list.c | 2 ++
>  string-list.h | 7 +++++--
>  2 files changed, 7 insertions(+), 2 deletions(-)

Does the following alternate fix work?  I think I prefer it because
it doesn't require introducing a new global.

Thanks,
Jonathan

diff --git i/string-list.h w/string-list.h
index 29bfb7ae45..dae33fbb89 100644
--- i/string-list.h
+++ w/string-list.h
@@ -33,7 +33,9 @@ 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)
+	for (item = (list)->items; \
+	     (list)->items && item < (list)->items + (list)->nr; \
+	     ++item)
 
 /*
  * Apply want to each item in list, retaining only the ones for which

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

* Re: [PATCH] for_each_string_list_item(): behave correctly for empty list
  2017-09-15 18:43 ` Jonathan Nieder
@ 2017-09-16  4:06   ` Michael Haggerty
  2017-09-16 11:51     ` SZEDER Gábor
  2017-09-19 14:38     ` Kaartic Sivaraam
  0 siblings, 2 replies; 29+ messages in thread
From: Michael Haggerty @ 2017-09-16  4:06 UTC (permalink / raw)
  To: Jonathan Nieder; +Cc: Junio C Hamano, Alex Riesen, git

On 09/15/2017 08:43 PM, Jonathan Nieder wrote:
> Michael Haggerty wrote:
> 
>> If you pass a newly-initialized or newly-cleared `string_list` to
>> `for_each_string_list_item()`, then the latter does
>>
>>     for (
>>             item = (list)->items; /* note, this is NULL */
>>             item < (list)->items + (list)->nr; /* note: NULL + 0 */
>>             ++item)
>>
>> Even though this probably works almost everywhere, it is undefined
>> behavior, and it could plausibly cause highly-optimizing compilers to
>> misbehave.
> 
> Wait, NULL + 0 is undefined behavior?
> 
> *checks the standard*  [...]
> NULL doesn't point to anything so it looks like adding 0 to a null
> pointer is indeed undefined.

Thanks for the legal work :-)

>                             (As a piece of trivia, strictly speaking
> NULL + 0 would be undefined on some implementations and defined on
> others, since an implementation is permitted to #define NULL to 0.)

Isn't that the very definition of "undefined behavior", in the sense of
a language standard?

> [...]
>> 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 (...; ...; ...)`.

> [...]
> 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.

Michael

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

* Re: [PATCH] for_each_string_list_item(): behave correctly for empty list
  2017-09-16  4:06   ` Michael Haggerty
@ 2017-09-16 11:51     ` SZEDER Gábor
  2017-09-17 10:19       ` Michael Haggerty
  2017-09-19 14:38     ` Kaartic Sivaraam
  1 sibling, 1 reply; 29+ messages in thread
From: SZEDER Gábor @ 2017-09-16 11:51 UTC (permalink / raw)
  To: Michael Haggerty
  Cc: SZEDER Gábor, Jonathan Nieder, Junio C Hamano, Alex Riesen,
	git


> >> 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.


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

* Re: [PATCH] for_each_string_list_item(): behave correctly for empty list
  2017-09-15 16:00 [PATCH] for_each_string_list_item(): behave correctly for empty list Michael Haggerty
  2017-09-15 18:43 ` Jonathan Nieder
@ 2017-09-17  0:59 ` Junio C Hamano
  2017-09-17 10:24   ` Michael Haggerty
  1 sibling, 1 reply; 29+ messages in thread
From: Junio C Hamano @ 2017-09-17  0:59 UTC (permalink / raw)
  To: Michael Haggerty; +Cc: Alex Riesen, git

Michael Haggerty <mhagger@alum.mit.edu> writes:

> If you pass a newly-initialized or newly-cleared `string_list` to
> `for_each_string_list_item()`, then the latter does
>
>     for (
>             item = (list)->items; /* note, this is NULL */
>             item < (list)->items + (list)->nr; /* note: NULL + 0 */
>             ++item)
>
> Even though this probably works almost everywhere, it is undefined
> behavior, and it could plausibly cause highly-optimizing compilers to
> misbehave.
> ...
> 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.
> ...
> -#define for_each_string_list_item(item,list) \
> -	for (item = (list)->items; item < (list)->items + (list)->nr; ++item)
> +extern struct string_list_item dummy_string_list_item;
> +#define for_each_string_list_item(item,list)                                 \
> +	for (item = (list)->items ? (list)->items : &dummy_string_list_item; \
> +	     item < (list)->items + (list)->nr;                              \
> +	     ++item)

Sorry, but I am confused.

So when (list)->items is NULL, the loop termination condition that
used to be

	NULL < NULL + 0

that was problematic because NULL + 0 is problematic now becomes

	&dummy < NULL + 0

in the new code?  What made NULL + 0 not problematic now?

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

* Re: [PATCH] for_each_string_list_item(): behave correctly for empty list
  2017-09-16 11:51     ` SZEDER Gábor
@ 2017-09-17 10:19       ` Michael Haggerty
  0 siblings, 0 replies; 29+ messages in thread
From: Michael Haggerty @ 2017-09-17 10:19 UTC (permalink / raw)
  To: SZEDER Gábor; +Cc: Jonathan Nieder, Junio C Hamano, Alex Riesen, git

On 09/16/2017 01:51 PM, SZEDER Gábor wrote:
>>>> 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);

It's not ambiguous as far as the language standard is concerned. The
latter is clear that an `else` binds to the nearest `if`. The problem is
that this is a common programmer error, so compilers "helpfully" warn
about it even though it would do exactly what we want.

> 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?

This change kindof goes *against* our coding guidelines, so it's not
ideal either, but I suppose we could probably live with it.

Michael

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

* Re: [PATCH] for_each_string_list_item(): behave correctly for empty list
  2017-09-17  0:59 ` Junio C Hamano
@ 2017-09-17 10:24   ` Michael Haggerty
  2017-09-18  0:37     ` Junio C Hamano
  0 siblings, 1 reply; 29+ messages in thread
From: Michael Haggerty @ 2017-09-17 10:24 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Alex Riesen, git, SZEDER Gábor

On 09/17/2017 02:59 AM, Junio C Hamano wrote:
> Michael Haggerty <mhagger@alum.mit.edu> writes:
> 
>> If you pass a newly-initialized or newly-cleared `string_list` to
>> `for_each_string_list_item()`, then the latter does
>>
>>     for (
>>             item = (list)->items; /* note, this is NULL */
>>             item < (list)->items + (list)->nr; /* note: NULL + 0 */
>>             ++item)
>>
>> Even though this probably works almost everywhere, it is undefined
>> behavior, and it could plausibly cause highly-optimizing compilers to
>> misbehave.
>> ...
>> 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.
>> ...
>> -#define for_each_string_list_item(item,list) \
>> -	for (item = (list)->items; item < (list)->items + (list)->nr; ++item)
>> +extern struct string_list_item dummy_string_list_item;
>> +#define for_each_string_list_item(item,list)                                 \
>> +	for (item = (list)->items ? (list)->items : &dummy_string_list_item; \
>> +	     item < (list)->items + (list)->nr;                              \
>> +	     ++item)
> 
> Sorry, but I am confused.
> 
> So when (list)->items is NULL, the loop termination condition that
> used to be
> 
> 	NULL < NULL + 0
> 
> that was problematic because NULL + 0 is problematic now becomes
> 
> 	&dummy < NULL + 0
> 
> in the new code?  What made NULL + 0 not problematic now?

*sigh* of course you're right. I should know better than to "fire off a
quick fix to the mailing list".

I guess the two proposals that are still in the running for rescuing
this macro are Jonathan's and Gábor's. I have no strong preference
either way.

Michael

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

* Re: [PATCH] for_each_string_list_item(): behave correctly for empty list
  2017-09-17 10:24   ` Michael Haggerty
@ 2017-09-18  0:37     ` Junio C Hamano
  2017-09-19  0:08       ` Stefan Beller
  0 siblings, 1 reply; 29+ messages in thread
From: Junio C Hamano @ 2017-09-18  0:37 UTC (permalink / raw)
  To: Michael Haggerty; +Cc: Alex Riesen, git, SZEDER Gábor

Michael Haggerty <mhagger@alum.mit.edu> writes:

> *sigh* of course you're right. I should know better than to "fire off a
> quick fix to the mailing list".
>
> I guess the two proposals that are still in the running for rescuing
> this macro are Jonathan's and Gábor's. I have no strong preference
> either way.

If somebody is writing this outisde a macro as a one-shot thing, the
most natural and readable way I would imagine would be

	if (the list is empty)
        	;
	else
		for (each item in the list)
			work on item

I would think.  That "work on item" part may not be a single
expression statement and instead be a compound statement inside a
pair of braces {}.  Making a shorter version, i.e.

	if (!the list is empty)
		for (each item in the list)
			work on item

into a macro probably has syntax issues around cascading if/else
chain, e.g.

	if (condition caller cares about)
		for_each_string_list_item() {
			do this thing
		}
	else
		do something else

would expand to

	if (condition caller cares about)
		if (!the list is empty)
			for (each item in the list) {
				do this thing
			}
	else
		do something else

which is wrong.  But I couldn't think of a way to break the longer
one with the body of the macro in the "else" clause in a similar
way.  An overly helpful compiler might say

	if (condition caller cares about)
		if (the list is empty)
			;
		else
			for (each item in the list) {
				do this thing
			}
	else
		do something else

that it wants a pair of {} around the then-clause of the outer if;
if we can find a way to squelch such warnings only with this
construct that comes from the macro, then this solution may be ideal.

If we cannot do that, then

	for (item = (list)->items; /* could be NULL */
	     (list)->items && item < (list)->items + (list)->nr;
	     item++)
		work on item

may be an obvious way to write it without any such syntax worries,
but I am unclear how a "undefined behaviour" contaminate the code
around it.  My naive reading of the termination condition of the
above is:

	"(list)->items &&" clearly means that (list)->items is not
	NULL in what follows it, i.e. (list->items + (list)->nr
	cannot be a NULL + 0, so we are not allowed to make demon
	fly out of your nose.

but I wonder if this alternative reading is allowed:

	(list)->items is not assigned to in this expression and is
	used in a subexpression "(list)->items + (list)->nr" here;
	for that subexpression not to be "undefined", it cannot be
	NULL, so we can optimize out "do this only (list)->items is
	not NULL" part.

which takes us back to where we started X-<.  So I dunno.

I am hoping that this last one is not allowed and we can use the
"same condition is checked every time we loop" version that hides
the uglyness inside the macro.

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

* Re: [PATCH] for_each_string_list_item(): behave correctly for empty list
  2017-09-18  0:37     ` Junio C Hamano
@ 2017-09-19  0:08       ` Stefan Beller
  2017-09-19  6:51         ` Michael Haggerty
  0 siblings, 1 reply; 29+ messages in thread
From: Stefan Beller @ 2017-09-19  0:08 UTC (permalink / raw)
  To: Junio C Hamano, SZEDER Gábor, Jonathan Nieder
  Cc: Michael Haggerty, Alex Riesen, git@vger.kernel.org

> I am hoping that this last one is not allowed and we can use the
> "same condition is checked every time we loop" version that hides
> the uglyness inside the macro.

By which you are referring to Jonathans solution posted.
Maybe we can combine the two solutions (checking for thelist
to not be NULL once, by Jonathan) and using an outer structure
(SZEDERs solution) by replacing the condition by a for loop,
roughly (untested):

#define for_each_string_list_item(item,list) \
-       for (item = (list)->items; item < (list)->items + (list)->nr; ++item)
+    for (; list; list = NULL)
+        for (item = (list)->items; item < (list)->items + (list)->nr; ++item)

as that would not mingle with any dangling else clause.
It is also just one statement, such that

    if (bla)
      for_each_string_list_item {
        baz(item);
      }
    else
      foo;

still works.

Are there downsides to this combined approach?

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

* Re: [PATCH] for_each_string_list_item(): behave correctly for empty list
  2017-09-19  0:08       ` Stefan Beller
@ 2017-09-19  6:51         ` Michael Haggerty
  2017-09-19 13:38           ` SZEDER Gábor
  0 siblings, 1 reply; 29+ messages in thread
From: Michael Haggerty @ 2017-09-19  6:51 UTC (permalink / raw)
  To: Stefan Beller, Junio C Hamano, SZEDER Gábor, Jonathan Nieder
  Cc: Alex Riesen, git@vger.kernel.org

On 09/19/2017 02:08 AM, Stefan Beller wrote:
>> I am hoping that this last one is not allowed and we can use the
>> "same condition is checked every time we loop" version that hides
>> the uglyness inside the macro.
> 
> By which you are referring to Jonathans solution posted.
> Maybe we can combine the two solutions (checking for thelist
> to not be NULL once, by Jonathan) and using an outer structure
> (SZEDERs solution) by replacing the condition by a for loop,
> roughly (untested):
> 
> #define for_each_string_list_item(item,list) \
> -       for (item = (list)->items; item < (list)->items + (list)->nr; ++item)
> +    for (; list; list = NULL)
> +        for (item = (list)->items; item < (list)->items + (list)->nr; ++item)
> 
> as that would not mingle with any dangling else clause.
> It is also just one statement, such that
> 
>     if (bla)
>       for_each_string_list_item {
>         baz(item);
>       }
>     else
>       foo;
> 
> still works.
> 
> Are there downsides to this combined approach?

On the plus side, it's pleasantly devious; I wouldn't have thought of
using a `for` loop for the initial test. But it doesn't work as written,
because (1) we don't need to guard against `list` being NULL, but rather
`list->items`; and (2) we don't have the liberty to set `list = NULL`
(or `list->items = NULL`, because `list` is owned by the caller and we
shouldn't modify it.

The following is a bit closer:

#define for_each_string_list_item(item,list) \
	for (item = (list)->items; item; item = NULL) \
        	for (; item < (list)->items + (list)->nr; ++item)

But I think that also fails, because a callsite that does

	for_each_string_list_item(myitem, mylist)
		if (myitem.util)
			break;

would expect that `myitem` is still set after breaking out of the loop,
whereas the outer `for` loop would reset it to NULL.

If `break` were an expression we could do something like

#define for_each_string_list_item(item,list) \
	for (item = (list)->items; item; break) \
        	for (; item < (list)->items + (list)->nr; ++item)

So I think we're still left with the suggestions of Jonathan or Gábor.
Or the bigger change of initializing `string_list::items` to point at an
empty sentinal array (similar to `strbuf_slopbuf`) rather than NULL.
Personally, I think that Jonathan's approach makes the most sense,
unless somebody wants to jump in an implement a `string_list_slopbuf`.

By the way, I wonder if any open-coded loops over `string_lists` make
the same mistake as the macro?

Michael

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

* Re: [PATCH] for_each_string_list_item(): behave correctly for empty list
  2017-09-19  6:51         ` Michael Haggerty
@ 2017-09-19 13:38           ` SZEDER Gábor
  2017-09-19 13:45             ` SZEDER Gábor
  0 siblings, 1 reply; 29+ messages in thread
From: SZEDER Gábor @ 2017-09-19 13:38 UTC (permalink / raw)
  To: Michael Haggerty
  Cc: Stefan Beller, Junio C Hamano, Jonathan Nieder, Alex Riesen,
	git@vger.kernel.org

On Tue, Sep 19, 2017 at 8:51 AM, Michael Haggerty <mhagger@alum.mit.edu> wrote:
> On 09/19/2017 02:08 AM, Stefan Beller wrote:
>>> I am hoping that this last one is not allowed and we can use the
>>> "same condition is checked every time we loop" version that hides
>>> the uglyness inside the macro.
>>
>> By which you are referring to Jonathans solution posted.
>> Maybe we can combine the two solutions (checking for thelist
>> to not be NULL once, by Jonathan) and using an outer structure
>> (SZEDERs solution) by replacing the condition by a for loop,
>> roughly (untested):
>>
>> #define for_each_string_list_item(item,list) \
>> -       for (item = (list)->items; item < (list)->items + (list)->nr; ++item)
>> +    for (; list; list = NULL)
>> +        for (item = (list)->items; item < (list)->items + (list)->nr; ++item)
>>
>> as that would not mingle with any dangling else clause.
>> It is also just one statement, such that
>>
>>     if (bla)
>>       for_each_string_list_item {
>>         baz(item);
>>       }
>>     else
>>       foo;
>>
>> still works.
>>
>> Are there downsides to this combined approach?
>
> On the plus side, it's pleasantly devious; I wouldn't have thought of
> using a `for` loop for the initial test. But it doesn't work as written,
> because (1) we don't need to guard against `list` being NULL, but rather
> `list->items`; and (2) we don't have the liberty to set `list = NULL`
> (or `list->items = NULL`, because `list` is owned by the caller and we
> shouldn't modify it.
>
> The following is a bit closer:
>
> #define for_each_string_list_item(item,list) \
>         for (item = (list)->items; item; item = NULL) \
>                 for (; item < (list)->items + (list)->nr; ++item)
>
> But I think that also fails, because a callsite that does
>
>         for_each_string_list_item(myitem, mylist)
>                 if (myitem.util)
>                         break;
>
> would expect that `myitem` is still set after breaking out of the loop,
> whereas the outer `for` loop would reset it to NULL.
>
> If `break` were an expression we could do something like
>
> #define for_each_string_list_item(item,list) \
>         for (item = (list)->items; item; break) \
>                 for (; item < (list)->items + (list)->nr; ++item)

A bit "futuristic" option along these lines could be something like
this, using a scoped loop variable in the outer loop to ensure that
it's executed at most once:

  #define for_each_string_list_item(item,list) \
      for (int f_e_s_l_i = 1; (list)->items && f_e_s_l_i; f_e_s_l_i = 0) \
          for (item = (list)->items; item < (list)->items + (list)->nr; ++item)

The high number of underscores are an attempt to make reasonably sure
that the macro's loop variable doesn't shadow any variable in its
callers or isn't being shadowed in the loop body, which might(?)
trigger warnings in some compilers.

Alas we don't allow scoping the loop variable in for loops, and even a
test balloon patch didn't make it into git.git.

  https://public-inbox.org/git/20170719181956.15845-1-sbeller@google.com/T/#u


> So I think we're still left with the suggestions of Jonathan or Gábor.
> Or the bigger change of initializing `string_list::items` to point at an
> empty sentinal array (similar to `strbuf_slopbuf`) rather than NULL.
> Personally, I think that Jonathan's approach makes the most sense,
> unless somebody wants to jump in an implement a `string_list_slopbuf`.
>
> By the way, I wonder if any open-coded loops over `string_lists` make
> the same mistake as the macro?

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

* Re: [PATCH] for_each_string_list_item(): behave correctly for empty list
  2017-09-19 13:38           ` SZEDER Gábor
@ 2017-09-19 13:45             ` SZEDER Gábor
  0 siblings, 0 replies; 29+ messages in thread
From: SZEDER Gábor @ 2017-09-19 13:45 UTC (permalink / raw)
  To: Michael Haggerty
  Cc: Stefan Beller, Junio C Hamano, Jonathan Nieder, Alex Riesen,
	git@vger.kernel.org

On Tue, Sep 19, 2017 at 3:38 PM, SZEDER Gábor <szeder.dev@gmail.com> wrote:
> A bit "futuristic" option along these lines could be something like
> this, using a scoped loop variable in the outer loop to ensure that
> it's executed at most once:
>
>   #define for_each_string_list_item(item,list) \
>       for (int f_e_s_l_i = 1; (list)->items && f_e_s_l_i; f_e_s_l_i = 0) \
>           for (item = (list)->items; item < (list)->items + (list)->nr; ++item)
>
> The high number of underscores are an attempt to make reasonably sure
> that the macro's loop variable doesn't shadow any variable in its
> callers or isn't being shadowed in the loop body, which might(?)
> trigger warnings in some compilers.

Well, and a poor attempt at that, because, of course, the loop
variable would still be shadowed in nested for_each_string_list_item
loops...  And our codebase has these loops nested in
entry.c:finish_delayed_checkout().

OTOH, we don't seem to care too much about shadowed variables, since
building with -Wshadow gives 91 warnings in current master...

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

* Re: [PATCH] for_each_string_list_item(): behave correctly for empty list
  2017-09-16  4:06   ` Michael Haggerty
  2017-09-16 11:51     ` SZEDER Gábor
@ 2017-09-19 14:38     ` Kaartic Sivaraam
  2017-09-20  1:38       ` Junio C Hamano
  2017-09-20  2:30       ` Jonathan Nieder
  1 sibling, 2 replies; 29+ messages in thread
From: Kaartic Sivaraam @ 2017-09-19 14:38 UTC (permalink / raw)
  To: Michael Haggerty, Jonathan Nieder; +Cc: Junio C Hamano, Alex Riesen, git

On Saturday 16 September 2017 09:36 AM, Michael Haggerty wrote:
>> 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?

It seems at least 'gcc' is able to optimize this out even with a -O1
and 'clang' optimizes this out with a -O2. Taking a sneak peek at
the 'Makefile' shows that our default is -O2.

For a proof, see https://godbolt.org/g/CPt73L

---
Kaartic

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

* Re: [PATCH] for_each_string_list_item(): behave correctly for empty list
  2017-09-19 14:38     ` Kaartic Sivaraam
@ 2017-09-20  1:38       ` Junio C Hamano
  2017-09-20  1:43         ` Jonathan Nieder
  2017-09-20  2:30       ` Jonathan Nieder
  1 sibling, 1 reply; 29+ messages in thread
From: Junio C Hamano @ 2017-09-20  1:38 UTC (permalink / raw)
  To: Kaartic Sivaraam; +Cc: Michael Haggerty, Jonathan Nieder, Alex Riesen, git

Kaartic Sivaraam <kaarticsivaraam91196@gmail.com> writes:

> On Saturday 16 September 2017 09:36 AM, Michael Haggerty wrote:
>>> 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?
>
> It seems at least 'gcc' is able to optimize this out even with a -O1
> and 'clang' optimizes this out with a -O2. Taking a sneak peek at
> the 'Makefile' shows that our default is -O2.

But doesn't the versions of gcc and clang currently available do the
right thing with the current code without this change anyway?  I've
been operating under the assumption that this is to future-proof the
code even when the compilers change to use the "NULL+0 is undefined"
as an excuse to make demons fly out of your nose, so unfortunately I
do not think it is not so huge a plus to find that the current
compilers do the right thing to the code with proposed updates.


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

* Re: [PATCH] for_each_string_list_item(): behave correctly for empty list
  2017-09-20  1:38       ` Junio C Hamano
@ 2017-09-20  1:43         ` Jonathan Nieder
  2017-09-20  5:14           ` Junio C Hamano
  0 siblings, 1 reply; 29+ messages in thread
From: Jonathan Nieder @ 2017-09-20  1:43 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Kaartic Sivaraam, Michael Haggerty, Alex Riesen, git

Hi,

Junio C Hamano wrote:
> Kaartic Sivaraam <kaarticsivaraam91196@gmail.com> writes:
>> On Saturday 16 September 2017 09:36 AM, Michael Haggerty wrote:
>>> Jonathan Nieder wrote:

>>>> 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?
>>
>> It seems at least 'gcc' is able to optimize this out even with a -O1
>> and 'clang' optimizes this out with a -O2. Taking a sneak peek at
>> the 'Makefile' shows that our default is -O2.
>
> But doesn't the versions of gcc and clang currently available do the
> right thing with the current code without this change anyway?  I've
> been operating under the assumption that this is to future-proof the
> code even when the compilers change to use the "NULL+0 is undefined"
> as an excuse to make demons fly out of your nose, so unfortunately I
> do not think it is not so huge a plus to find that the current
> compilers do the right thing to the code with proposed updates.

I think you and Kaartic are talking about different things.  Kaartic
was checking that this wouldn't introduce a performance regression
(thanks!).  You are concerned about whether the C standard and common
practice treat the resulting code as exhibiting undefined behavior.

Fortunately the C standard is pretty clear about this.  The undefined
behavior here is at run time, not compile time.  As you suggested in
an earlier reply, the 'list->items &&' effectively guards the
'list->items + list->nr' to prevent that undefined behavior.

I'll send a patch with a commit message saying so to try to close out
this discussion.

Thanks,
Jonathan

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

* Re: [PATCH] for_each_string_list_item(): behave correctly for empty list
  2017-09-19 14:38     ` Kaartic Sivaraam
  2017-09-20  1:38       ` Junio C Hamano
@ 2017-09-20  2:30       ` Jonathan Nieder
  2017-09-20  3:54         ` Junio C Hamano
  2017-09-20  7:35         ` [PATCH] for_each_string_list_item(): behave correctly " Kaartic Sivaraam
  1 sibling, 2 replies; 29+ messages in thread
From: Jonathan Nieder @ 2017-09-20  2:30 UTC (permalink / raw)
  To: Kaartic Sivaraam; +Cc: Michael Haggerty, Junio C Hamano, Alex Riesen, git

Hi,

Kaartic Sivaraam wrote:
> On Saturday 16 September 2017 09:36 AM, Michael Haggerty wrote:
>> Jonathan Nieder wrote:

>>> 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?
>
> I t seems at least 'gcc' is able to optimize this out even with a -O1
> and 'clang' optimizes this out with a -O2. Taking a sneak peek at
> the 'Makefile' shows that our default is -O2.
>
> For a proof, see https://godbolt.org/g/CPt73L

From that link:

    for ( ;valid_int && *valid_int < 10; (*valid_int)++) {
        printf("Valid instance");
    }

Both gcc and clang are able to optimize out the 'valid_int &&' because
it is dereferenced on the RHS of the &&.

For comparison, 'item < (list)->items + (list)->nr' does not
dereference (list)->items.  So that optimization doesn't apply here.

A smart compiler could be able to take advantage of there being no
object pointed to by a null pointer, which means

	item < (list)->items + (list)->nr

is always false when (list)->items is NULL, which in turn makes a
'(list)->items &&' test redundant.  But a quick test with gcc 4.8.4
-O2 finds that at least this compiler does not contain such an
optimization.  The overhead Michael Haggerty mentioned is real.

Thanks and hope that helps,
Jonathan

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

* Re: [PATCH] for_each_string_list_item(): behave correctly for empty list
  2017-09-20  2:30       ` Jonathan Nieder
@ 2017-09-20  3:54         ` Junio C Hamano
  2017-09-20  5:27           ` [PATCH v2] for_each_string_list_item: avoid undefined behavior " Jonathan Nieder
  2017-09-20  7:35         ` [PATCH] for_each_string_list_item(): behave correctly " Kaartic Sivaraam
  1 sibling, 1 reply; 29+ messages in thread
From: Junio C Hamano @ 2017-09-20  3:54 UTC (permalink / raw)
  To: Jonathan Nieder; +Cc: Kaartic Sivaraam, Michael Haggerty, Alex Riesen, git

Jonathan Nieder <jrnieder@gmail.com> writes:

> ...  But a quick test with gcc 4.8.4
> -O2 finds that at least this compiler does not contain such an
> optimization.  The overhead Michael Haggerty mentioned is real.

Still, I have a feeling that users of string_list wouldn't care 
the overhead of single pointer NULL-ness check.

 - apply.c collects conflicted paths and reports them with fprintf().

 - builtin/clean.c uses the function to walk the list of paths to be
   removed, and either does a human interaction (for "-i" codepath)
   or goes to the filesystem to remove things.

 - builtin/config.c uses it in get_urlmatch() in preparation for
   doing network-y things.

 - builtin/describe.c walks the list of exclude and include patterns
   to run wildmatch on the candidate reference name to filter it out.

 ...

In all of these examples, what happens for each item in the loop
seems to me far heavier than the overhead this macro adds.

So...



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

* Re: [PATCH] for_each_string_list_item(): behave correctly for empty list
  2017-09-20  1:43         ` Jonathan Nieder
@ 2017-09-20  5:14           ` Junio C Hamano
  0 siblings, 0 replies; 29+ messages in thread
From: Junio C Hamano @ 2017-09-20  5:14 UTC (permalink / raw)
  To: Jonathan Nieder; +Cc: Kaartic Sivaraam, Michael Haggerty, Alex Riesen, git

Jonathan Nieder <jrnieder@gmail.com> writes:

> I'll send a patch with a commit message saying so to try to close out
> this discussion.

Thanks.  One less thing we have to worry about ;-)

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

* [PATCH v2] for_each_string_list_item: avoid undefined behavior for empty list
  2017-09-20  3:54         ` Junio C Hamano
@ 2017-09-20  5:27           ` Jonathan Nieder
  2017-09-20  5:40             ` Junio C Hamano
                               ` (4 more replies)
  0 siblings, 5 replies; 29+ messages in thread
From: Jonathan Nieder @ 2017-09-20  5:27 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Kaartic Sivaraam, Michael Haggerty, Alex Riesen, git

From: Michael Haggerty <mhagger@alum.mit.edu>

If you pass a newly initialized or newly cleared `string_list` to
`for_each_string_list_item()`, then the latter does

    for (
            item = (list)->items; /* NULL */
            item < (list)->items + (list)->nr; /* NULL + 0 */
            ++item)

Even though this probably works almost everywhere, it is undefined
behavior, and it could plausibly cause highly-optimizing compilers to
misbehave.  C99 section 6.5.6 paragraph 8 explains:

    If both the pointer operand and the result point to elements
    of the same array object, or one past the last element of the
    array object, the evaluation shall not produce an overflow;
    otherwise, the behavior is undefined.

and (6.3.2.3.3) a null pointer does not point to anything.

Guard the loop with a NULL check to make the intent crystal clear to
even the most pedantic compiler.  A suitably clever compiler could let
the NULL check only run in the first iteration, but regardless, this
overhead is likely to be dwarfed by the work to be done on each item.

This problem was noticed by Coverity.

[jn: using a NULL check instead of a placeholder empty list;
 fleshed out the commit message based on mailing list discussion]

Signed-off-by: Michael Haggerty <mhagger@alum.mit.edu>
Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>
---
 string-list.h | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

Junio C Hamano wrote:
> Jonathan Nieder <jrnieder@gmail.com> writes:

>> ...  But a quick test with gcc 4.8.4
>> -O2 finds that at least this compiler does not contain such an
>> optimization.  The overhead Michael Haggerty mentioned is real.
>
> Still, I have a feeling that users of string_list wouldn't care
> the overhead of single pointer NULL-ness check.
>
>  - apply.c collects conflicted paths and reports them with fprintf().
>
>  - builtin/clean.c uses the function to walk the list of paths to be
>    removed, and either does a human interaction (for "-i" codepath)
>    or goes to the filesystem to remove things.
>
>  - builtin/config.c uses it in get_urlmatch() in preparation for
>    doing network-y things.
>
>  - builtin/describe.c walks the list of exclude and include patterns
>    to run wildmatch on the candidate reference name to filter it out.
>
>  ...
>
> In all of these examples, what happens for each item in the loop
> seems to me far heavier than the overhead this macro adds.

Yes, agreed.  As a small tweak,

   #define for_each_string_list_item(item, list) \
	for (item = ...; item && ...; ...)

produces nicer assembly than

   #define for_each_string_list_item(item, list) \
	for (item = ...; list->items && ...; ...)

(By the way, the potential optimization I described isn't valid: we
know that when item == NULL and list->items == NULL, list->nr is
always zero, but the compiler has no way to know that.  So it can't
eliminate the NULL test.  For comparison, a suitably smart compiler
should be able to eliminate a 'list->nr != 0 &&' guard if 'list'
doesn't escape in the loop body.)

Recapping the other proposed fixes:

A. Make it an invariant of string_list that items is never NULL and
   update string_list_init et al to use an empty array.  This is
   pretty painless until you notice some other structs that embed
   string_list without using STRING_LIST_INIT.  Updating all those
   would be too painful.

B. #define for_each_string_list_item(item, list) \
	if (list->items) \
		for (item = ...; ...; ... )

   This breaks a caller like
	if (foo)
		for_each_string_list_item(item, list)
			...
	else
		...

   making it a non-starter.

C. As Gábor suggested,
   #define for_each_string_list_item(item, list) \
   	if (!list->items) \
		; /* nothing to do */ \
	else \
		for (item = ...; ...; ...)

   This handles the caller from (B) correctly.  But it produces
   compiler warnings for a caller like

	if (foo)
		for_each_string_list_item(item, list)
			...

   There is only one instance of that construct in git today.  It
   looks nicer anyway with braces, so this approach would also be
   promising.

D. Eliminate for_each_string_list_item and let callers just do

	unsigned int i;
	for (i = 0; i < list->nr; i++) {
		struct string_list_item *item = list->items[i];
		...
	}

   Having to declare item is unnecessarily verbose, decreasing the
   appeal of this option.  I think I like it anyway, but I wasn't able
   to convince coccinelle to do it.

E. Use subtraction instead of addition:
   #define for_each_string_list_item(item, list) \
   	for (item = ...; \
	     (item == list->items ? 0 : item - list->items) < nr; \
	     item++)

   I expected the compiler to figure out that this is a long way of writing
   (item - list->items), but at least with gcc 4.8.4 -O2, no such
   luck.  This generates uglier assembly than the NULL check.

diff --git a/string-list.h b/string-list.h
index 29bfb7ae45..79ae567cbc 100644
--- a/string-list.h
+++ b/string-list.h
@@ -32,8 +32,10 @@ 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)            \
+	for (item = (list)->items;                      \
+	     item && item < (list)->items + (list)->nr; \
+	     ++item)
 
 /*
  * Apply want to each item in list, retaining only the ones for which
-- 
2.14.1.821.g8fa685d3b7


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

* Re: [PATCH v2] for_each_string_list_item: avoid undefined behavior for empty list
  2017-09-20  5:27           ` [PATCH v2] for_each_string_list_item: avoid undefined behavior " Jonathan Nieder
@ 2017-09-20  5:40             ` Junio C Hamano
  2017-09-20  7:00             ` Michael Haggerty
                               ` (3 subsequent siblings)
  4 siblings, 0 replies; 29+ messages in thread
From: Junio C Hamano @ 2017-09-20  5:40 UTC (permalink / raw)
  To: Jonathan Nieder; +Cc: Kaartic Sivaraam, Michael Haggerty, Alex Riesen, git

Jonathan Nieder <jrnieder@gmail.com> writes:

> D. Eliminate for_each_string_list_item and let callers just do
>
> 	unsigned int i;
> 	for (i = 0; i < list->nr; i++) {
> 		struct string_list_item *item = list->items[i];
> 		...
> 	}
>
>    Having to declare item is unnecessarily verbose, decreasing the
>    appeal of this option.  I think I like it anyway, but I wasn't able
>    to convince coccinelle to do it.

When using the macro, item still needs to be declared outside by the
user, so it's not all that unpleasant, though.

> E. Use subtraction instead of addition:
>    #define for_each_string_list_item(item, list) \
>    	for (item = ...; \
> 	     (item == list->items ? 0 : item - list->items) < nr; \
> 	     item++)
>
>    I expected the compiler to figure out that this is a long way of writing
>    (item - list->items), but at least with gcc 4.8.4 -O2, no such
>    luck.  This generates uglier assembly than the NULL check.

Yuck.  You cannot easily unsee such an ugliness X-<.

The patch and explanation above --- looked quite nicely written.
Will queue.

Thanks.

> diff --git a/string-list.h b/string-list.h
> index 29bfb7ae45..79ae567cbc 100644
> --- a/string-list.h
> +++ b/string-list.h
> @@ -32,8 +32,10 @@ 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)            \
> +	for (item = (list)->items;                      \
> +	     item && item < (list)->items + (list)->nr; \
> +	     ++item)
>  
>  /*
>   * Apply want to each item in list, retaining only the ones for which

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

* Re: [PATCH v2] for_each_string_list_item: avoid undefined behavior for empty list
  2017-09-20  5:27           ` [PATCH v2] for_each_string_list_item: avoid undefined behavior " Jonathan Nieder
  2017-09-20  5:40             ` Junio C Hamano
@ 2017-09-20  7:00             ` Michael Haggerty
  2017-09-20  7:40             ` Kaartic Sivaraam
                               ` (2 subsequent siblings)
  4 siblings, 0 replies; 29+ messages in thread
From: Michael Haggerty @ 2017-09-20  7:00 UTC (permalink / raw)
  To: Jonathan Nieder, Junio C Hamano; +Cc: Kaartic Sivaraam, Alex Riesen, git

On 09/20/2017 07:27 AM, Jonathan Nieder wrote:
> From: Michael Haggerty <mhagger@alum.mit.edu>
> 
> If you pass a newly initialized or newly cleared `string_list` to
> `for_each_string_list_item()`, then the latter does
> 
>     for (
>             item = (list)->items; /* NULL */
>             item < (list)->items + (list)->nr; /* NULL + 0 */
>             ++item)
> 
> Even though this probably works almost everywhere, it is undefined
> behavior, and it could plausibly cause highly-optimizing compilers to
> misbehave.  C99 section 6.5.6 paragraph 8 explains:
> 
>     If both the pointer operand and the result point to elements
>     of the same array object, or one past the last element of the
>     array object, the evaluation shall not produce an overflow;
>     otherwise, the behavior is undefined.
> 
> and (6.3.2.3.3) a null pointer does not point to anything.
> 
> Guard the loop with a NULL check to make the intent crystal clear to
> even the most pedantic compiler.  A suitably clever compiler could let
> the NULL check only run in the first iteration, but regardless, this
> overhead is likely to be dwarfed by the work to be done on each item.
> 
> This problem was noticed by Coverity.
> 
> [jn: using a NULL check instead of a placeholder empty list;
>  fleshed out the commit message based on mailing list discussion]

Thanks for taking this over. This version LGTM.

> [...]
Michael

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

* Re: [PATCH] for_each_string_list_item(): behave correctly for empty list
  2017-09-20  2:30       ` Jonathan Nieder
  2017-09-20  3:54         ` Junio C Hamano
@ 2017-09-20  7:35         ` Kaartic Sivaraam
  1 sibling, 0 replies; 29+ messages in thread
From: Kaartic Sivaraam @ 2017-09-20  7:35 UTC (permalink / raw)
  To: Jonathan Nieder; +Cc: Michael Haggerty, Junio C Hamano, Alex Riesen, git

Hi,

Though this thread seems to have reached a conclusion, I just wanted to
know what I was missing about the optimisation.

On Wednesday 20 September 2017 08:00 AM, Jonathan Nieder wrote:
> From that link:
>      for ( ;valid_int && *valid_int < 10; (*valid_int)++) {
>          printf("Valid instance");
>      }
>
> Both gcc and clang are able to optimize out the 'valid_int &&' because
> it is dereferenced on the RHS of the &&.
>
> For comparison, 'item < (list)->items + (list)->nr' does not
> dereference (list)->items.  So that optimization doesn't apply here.
>
> A smart compiler could be able to take advantage of there being no
> object pointed to by a null pointer, which means
>
> 	item < (list)->items + (list)->nr
>
> is always false when (list)->items is NULL, which in turn makes a
> '(list)->items &&' test redundant.  But a quick test with gcc 4.8.4
> -O2 finds that at least this compiler does not contain such an
> optimization.  The overhead Michael Haggerty mentioned is real.
>

I thought the compiler optimized that check out of the loop because the
check was "invariant" across loop runs. IOW, the values used in the check
didn't change across loop runs so the compiler thought it's better to do
the check once outside the loop rather than doing it each time inside
the loop. I guess this is some kind of "loop unswitching"[1]. I don't 
see how
dereferencing influences the optimization here.

Just to be sure, I tried once more to see whether the compiler optimizes 
this
or not. This time with a more similar example and even using the macro 
of concern.
Surprisingly, the compiler did optimize the check out of the loop. This 
time both
'gcc' and 'clang' with an -O1 !

https://godbolt.org/g/Y6rHc1
https://godbolt.org/g/EMrftw

So, is the overhead still real or am I missing something?

[1] : https://en.wikipedia.org/wiki/Loop_unswitching

---
Kaartic

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

* Re: [PATCH v2] for_each_string_list_item: avoid undefined behavior for empty list
  2017-09-20  5:27           ` [PATCH v2] for_each_string_list_item: avoid undefined behavior " Jonathan Nieder
  2017-09-20  5:40             ` Junio C Hamano
  2017-09-20  7:00             ` Michael Haggerty
@ 2017-09-20  7:40             ` Kaartic Sivaraam
  2017-09-20 12:22             ` [PATCH v2] doc: camelCase the config variables to improve readability Kaartic Sivaraam
  2017-09-20 16:28             ` [PATCH v2] for_each_string_list_item: avoid undefined behavior for empty list Andreas Schwab
  4 siblings, 0 replies; 29+ messages in thread
From: Kaartic Sivaraam @ 2017-09-20  7:40 UTC (permalink / raw)
  To: Jonathan Nieder, Junio C Hamano; +Cc: Michael Haggerty, Alex Riesen, git

On Wednesday 20 September 2017 10:57 AM, Jonathan Nieder wrote:
> Guard the loop with a NULL check to make the intent crystal clear to
> even the most pedantic compiler.  A suitably clever compiler could let
> the NULL check only run in the first iteration,

Noted this just now. So, the overhead doesn't occur when the compilers
are clever enough. And as I said in my previous email to this thread, at
least 'gcc' and 'clang' seem to be clever enough.
> ... but regardless, this
> overhead is likely to be dwarfed by the work to be done on each item.

:-) That's of course seems to be true.


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

* [PATCH v2] doc: camelCase the config variables to improve readability
  2017-09-20  5:27           ` [PATCH v2] for_each_string_list_item: avoid undefined behavior " Jonathan Nieder
                               ` (2 preceding siblings ...)
  2017-09-20  7:40             ` Kaartic Sivaraam
@ 2017-09-20 12:22             ` Kaartic Sivaraam
  2017-09-20 16:28             ` [PATCH v2] for_each_string_list_item: avoid undefined behavior for empty list Andreas Schwab
  4 siblings, 0 replies; 29+ messages in thread
From: Kaartic Sivaraam @ 2017-09-20 12:22 UTC (permalink / raw)
  To: git

A few configuration variable names of Git are composite words. References
to such variables in manpages are hard to read because they use all-lowercase
names, without indicating where each word ends and begins.

Improve its readability by using camelCase instead.  Git treats these
names case-insensitively so this does not affect functionality. This
also ensures consistency with other parts of the docs that use camelCase
fo refer to configuration variable names.

Signed-off-by: Kaartic Sivaraam <kaarticsivaraam91196@gmail.com>
Reviewed-by: Jonathan Nieder <jrnieder@gmail.com>
---
 Documentation/git-branch.txt | 4 ++--
 Documentation/git-tag.txt    | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt
index e292737b9c5ab..58f1e5c9c74e1 100644
--- a/Documentation/git-branch.txt
+++ b/Documentation/git-branch.txt
@@ -92,10 +92,10 @@ OPTIONS
 	all changes made to the branch ref, enabling use of date
 	based sha1 expressions such as "<branchname>@\{yesterday}".
 	Note that in non-bare repositories, reflogs are usually
-	enabled by default by the `core.logallrefupdates` config option.
+	enabled by default by the `core.logAllRefUpdates` config option.
 	The negated form `--no-create-reflog` only overrides an earlier
 	`--create-reflog`, but currently does not negate the setting of
-	`core.logallrefupdates`.
+	`core.logAllRefUpdates`.
 
 -f::
 --force::
diff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt
index 543fb425ee7c1..95e9f391d88fc 100644
--- a/Documentation/git-tag.txt
+++ b/Documentation/git-tag.txt
@@ -174,7 +174,7 @@ This option is only applicable when listing tags without annotation lines.
 	`core.logAllRefUpdates` in linkgit:git-config[1].
 	The negated form `--no-create-reflog` only overrides an earlier
 	`--create-reflog`, but currently does not negate the setting of
-	`core.logallrefupdates`.
+	`core.logAllRefUpdates`.
 
 <tagname>::
 	The name of the tag to create, delete, or describe.

--
https://github.com/git/git/pull/407

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

* Re: [PATCH v2] for_each_string_list_item: avoid undefined behavior for empty list
  2017-09-20  5:27           ` [PATCH v2] for_each_string_list_item: avoid undefined behavior " Jonathan Nieder
                               ` (3 preceding siblings ...)
  2017-09-20 12:22             ` [PATCH v2] doc: camelCase the config variables to improve readability Kaartic Sivaraam
@ 2017-09-20 16:28             ` Andreas Schwab
  2017-09-20 17:31               ` Jonathan Nieder
  4 siblings, 1 reply; 29+ messages in thread
From: Andreas Schwab @ 2017-09-20 16:28 UTC (permalink / raw)
  To: Jonathan Nieder
  Cc: Junio C Hamano, Kaartic Sivaraam, Michael Haggerty, Alex Riesen,
	git

On Sep 19 2017, Jonathan Nieder <jrnieder@gmail.com> wrote:

> B. #define for_each_string_list_item(item, list) \
> 	if (list->items) \
> 		for (item = ...; ...; ... )
>
>    This breaks a caller like
> 	if (foo)
> 		for_each_string_list_item(item, list)
> 			...
> 	else
> 		...
>
>    making it a non-starter.

That can be fixed with a dangling else.

Andreas.

-- 
Andreas Schwab, schwab@linux-m68k.org
GPG Key fingerprint = 58CA 54C7 6D53 942B 1756  01D3 44D5 214B 8276 4ED5
"And now for something completely different."

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

* Re: [PATCH v2] for_each_string_list_item: avoid undefined behavior for empty list
  2017-09-20 16:28             ` [PATCH v2] for_each_string_list_item: avoid undefined behavior for empty list Andreas Schwab
@ 2017-09-20 17:31               ` Jonathan Nieder
  2017-09-20 21:51                 ` Andreas Schwab
  0 siblings, 1 reply; 29+ messages in thread
From: Jonathan Nieder @ 2017-09-20 17:31 UTC (permalink / raw)
  To: Andreas Schwab
  Cc: Junio C Hamano, Kaartic Sivaraam, Michael Haggerty, Alex Riesen,
	git

Andreas Schwab wrote:
> On Sep 19 2017, Jonathan Nieder <jrnieder@gmail.com> wrote:

>> B. #define for_each_string_list_item(item, list) \
>> 	if (list->items) \
>> 		for (item = ...; ...; ... )
>>
>>    This breaks a caller like
>> 	if (foo)
>> 		for_each_string_list_item(item, list)
>> 			...
>> 	else
>> 		...
>>
>>    making it a non-starter.
>
> That can be fixed with a dangling else.

I believe the fix you're referring to is option C, from the same email
you are replying to.  If not, please correct me.

Thanks,
Jonathan

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

* Re: [PATCH v2] for_each_string_list_item: avoid undefined behavior for empty list
  2017-09-20 17:31               ` Jonathan Nieder
@ 2017-09-20 21:51                 ` Andreas Schwab
  2017-09-21  1:12                   ` Junio C Hamano
  0 siblings, 1 reply; 29+ messages in thread
From: Andreas Schwab @ 2017-09-20 21:51 UTC (permalink / raw)
  To: Jonathan Nieder
  Cc: Junio C Hamano, Kaartic Sivaraam, Michael Haggerty, Alex Riesen,
	git

On Sep 20 2017, Jonathan Nieder <jrnieder@gmail.com> wrote:

> Andreas Schwab wrote:
>> On Sep 19 2017, Jonathan Nieder <jrnieder@gmail.com> wrote:
>
>>> B. #define for_each_string_list_item(item, list) \
>>> 	if (list->items) \
>>> 		for (item = ...; ...; ... )
>>>
>>>    This breaks a caller like
>>> 	if (foo)
>>> 		for_each_string_list_item(item, list)
>>> 			...
>>> 	else
>>> 		...
>>>
>>>    making it a non-starter.
>>
>> That can be fixed with a dangling else.
>
> I believe the fix you're referring to is option C, from the same email
> you are replying to.  If not, please correct me.

A variant thereof, yes.

Andreas.

-- 
Andreas Schwab, schwab@linux-m68k.org
GPG Key fingerprint = 58CA 54C7 6D53 942B 1756  01D3 44D5 214B 8276 4ED5
"And now for something completely different."

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

* Re: [PATCH v2] for_each_string_list_item: avoid undefined behavior for empty list
  2017-09-20 21:51                 ` Andreas Schwab
@ 2017-09-21  1:12                   ` Junio C Hamano
  2017-09-21 15:39                     ` Andreas Schwab
  0 siblings, 1 reply; 29+ messages in thread
From: Junio C Hamano @ 2017-09-21  1:12 UTC (permalink / raw)
  To: Andreas Schwab
  Cc: Jonathan Nieder, Kaartic Sivaraam, Michael Haggerty, Alex Riesen,
	git

Andreas Schwab <schwab@linux-m68k.org> writes:

> On Sep 20 2017, Jonathan Nieder <jrnieder@gmail.com> wrote:
>
>> Andreas Schwab wrote:
>>> On Sep 19 2017, Jonathan Nieder <jrnieder@gmail.com> wrote:
>>
>>>> B. #define for_each_string_list_item(item, list) \
>>>> 	if (list->items) \
>>>> 		for (item = ...; ...; ... )
>>>>
>>>>    This breaks a caller like
>>>> 	if (foo)
>>>> 		for_each_string_list_item(item, list)
>>>> 			...
>>>> 	else
>>>> 		...
>>>>
>>>>    making it a non-starter.
>>>
>>> That can be fixed with a dangling else.
>>
>> I believe the fix you're referring to is option C, from the same email
>> you are replying to.  If not, please correct me.
>
> A variant thereof, yes.

Now you make me curious.  How would that variant be different from
option C. in Jonathan's message?  Perhaps that different version may
be a solution to work around the potential issue mentioned in the
description of option C.?


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

* Re: [PATCH v2] for_each_string_list_item: avoid undefined behavior for empty list
  2017-09-21  1:12                   ` Junio C Hamano
@ 2017-09-21 15:39                     ` Andreas Schwab
  0 siblings, 0 replies; 29+ messages in thread
From: Andreas Schwab @ 2017-09-21 15:39 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Jonathan Nieder, Kaartic Sivaraam, Michael Haggerty, Alex Riesen,
	git

On Sep 21 2017, Junio C Hamano <gitster@pobox.com> wrote:

> Now you make me curious.  How would that variant be different from
> option C. in Jonathan's message?

Only in the parity of the condition.

Andreas.

-- 
Andreas Schwab, schwab@linux-m68k.org
GPG Key fingerprint = 58CA 54C7 6D53 942B 1756  01D3 44D5 214B 8276 4ED5
"And now for something completely different."

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

end of thread, other threads:[~2017-09-21 15:39 UTC | newest]

Thread overview: 29+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2017-09-15 16:00 [PATCH] for_each_string_list_item(): behave correctly for empty list Michael Haggerty
2017-09-15 18:43 ` Jonathan Nieder
2017-09-16  4:06   ` Michael Haggerty
2017-09-16 11:51     ` SZEDER Gábor
2017-09-17 10:19       ` Michael Haggerty
2017-09-19 14:38     ` Kaartic Sivaraam
2017-09-20  1:38       ` Junio C Hamano
2017-09-20  1:43         ` Jonathan Nieder
2017-09-20  5:14           ` Junio C Hamano
2017-09-20  2:30       ` Jonathan Nieder
2017-09-20  3:54         ` Junio C Hamano
2017-09-20  5:27           ` [PATCH v2] for_each_string_list_item: avoid undefined behavior " Jonathan Nieder
2017-09-20  5:40             ` Junio C Hamano
2017-09-20  7:00             ` Michael Haggerty
2017-09-20  7:40             ` Kaartic Sivaraam
2017-09-20 12:22             ` [PATCH v2] doc: camelCase the config variables to improve readability Kaartic Sivaraam
2017-09-20 16:28             ` [PATCH v2] for_each_string_list_item: avoid undefined behavior for empty list Andreas Schwab
2017-09-20 17:31               ` Jonathan Nieder
2017-09-20 21:51                 ` Andreas Schwab
2017-09-21  1:12                   ` Junio C Hamano
2017-09-21 15:39                     ` Andreas Schwab
2017-09-20  7:35         ` [PATCH] for_each_string_list_item(): behave correctly " Kaartic Sivaraam
2017-09-17  0:59 ` Junio C Hamano
2017-09-17 10:24   ` Michael Haggerty
2017-09-18  0:37     ` Junio C Hamano
2017-09-19  0:08       ` Stefan Beller
2017-09-19  6:51         ` Michael Haggerty
2017-09-19 13:38           ` SZEDER Gábor
2017-09-19 13:45             ` SZEDER Gábor

Code repositories for project(s) associated with this public inbox

	https://80x24.org/mirrors/git.git

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 read-only IMAP folder(s) and NNTP newsgroup(s).