diff mbox series

Fix maybe-uninitialized warnings found by gcc 9 -flto

Message ID 20190905082459.26816-1-s-beyer@gmx.net (mailing list archive)
State New, archived
Headers show
Series Fix maybe-uninitialized warnings found by gcc 9 -flto | expand

Commit Message

Stephan Beyer Sept. 5, 2019, 8:24 a.m. UTC
Compiler heuristics for detection of potentially uninitialized variables
may change between compiler versions and enabling link-time optimization
may find new warnings.  Indeed, compiling with gcc 9.2.1 and enabled
link-time optimization feature resulted in a few hits that are fixed by
this patch in the most naïve way.  This allows to compile git using the
DEVELOPER=1 switch (which sets -Werror) and using the -flto flag.

Signed-off-by: Stephan Beyer <s-beyer@gmx.net>
---
 builtin/am.c               | 2 +-
 builtin/pack-objects.c     | 2 +-
 bulk-checkin.c             | 2 ++
 fast-import.c              | 3 ++-
 t/helper/test-read-cache.c | 2 +-
 5 files changed, 7 insertions(+), 4 deletions(-)

--
2.23.0.38.g7ab3f3815a

Comments

René Scharfe Sept. 5, 2019, 5:08 p.m. UTC | #1
Am 05.09.19 um 10:24 schrieb Stephan Beyer:
> Compiler heuristics for detection of potentially uninitialized variables
> may change between compiler versions and enabling link-time optimization
> may find new warnings.  Indeed, compiling with gcc 9.2.1 and enabled
> link-time optimization feature resulted in a few hits that are fixed by
> this patch in the most naïve way.  This allows to compile git using the
> DEVELOPER=1 switch (which sets -Werror) and using the -flto flag.
>
> Signed-off-by: Stephan Beyer <s-beyer@gmx.net>
> ---
>  builtin/am.c               | 2 +-
>  builtin/pack-objects.c     | 2 +-
>  bulk-checkin.c             | 2 ++
>  fast-import.c              | 3 ++-
>  t/helper/test-read-cache.c | 2 +-
>  5 files changed, 7 insertions(+), 4 deletions(-)
>
> diff --git a/builtin/am.c b/builtin/am.c
> index 1aea657a7f..ab914fd46e 100644
> --- a/builtin/am.c
> +++ b/builtin/am.c
> @@ -1266,7 +1266,7 @@ static int get_mail_commit_oid(struct object_id *commit_id, const char *mail)
>  static void get_commit_info(struct am_state *state, struct commit *commit)
>  {
>  	const char *buffer, *ident_line, *msg;
> -	size_t ident_len;
> +	size_t ident_len = 0;
>  	struct ident_split id;
>
>  	buffer = logmsg_reencode(commit, NULL, get_commit_output_encoding());

Further context:

	ident_line = find_commit_header(buffer, "author", &ident_len);

	if (split_ident_line(&id, ident_line, ident_len) < 0)
		die(_("invalid ident line: %.*s"), (int)ident_len, ident_line);

find_commit_header() can return NULL.  split_ident_line() won't handle
that well.  So I think what's missing here is a NULL check.  If the
compiler is smart enough then that should silence the initialization
warning.

> diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c
> index 76ce906946..d0c03b0e9b 100644
> --- a/builtin/pack-objects.c
> +++ b/builtin/pack-objects.c
> @@ -1171,7 +1171,7 @@ static int add_object_entry(const struct object_id *oid, enum object_type type,
>  {
>  	struct packed_git *found_pack = NULL;
>  	off_t found_offset = 0;
> -	uint32_t index_pos;
> +	uint32_t index_pos = 0;
>
>  	display_progress(progress_state, ++nr_seen);
>

Further context:

	if (have_duplicate_entry(oid, exclude, &index_pos))
		return 0;

	if (!want_object_in_pack(oid, exclude, &found_pack, &found_offset)) {
		/* The pack is missing an object, so it will not have closure */
		if (write_bitmap_index) {
			if (write_bitmap_index != WRITE_BITMAP_QUIET)
				warning(_(no_closure_warning));
			write_bitmap_index = 0;
		}
		return 0;
	}

	create_object_entry(oid, type, pack_name_hash(name),
			    exclude, name && no_try_delta(name),
			    index_pos, found_pack, found_offset);

So we call have_duplicate_entry() and if it returns 0 then we might
end up using index_pos.  So when does it return 0?

static int have_duplicate_entry(const struct object_id *oid,
				int exclude,
				uint32_t *index_pos)
{
	struct object_entry *entry;

	entry = packlist_find(&to_pack, oid, index_pos);
	if (!entry)
		return 0;

OK, it does that if packlist_find() returns NULL.  When does it do
that?
struct object_entry *packlist_find(struct packing_data *pdata,
				   const struct object_id *oid,
				   uint32_t *index_pos)
{
	uint32_t i;
	int found;

	if (!pdata->index_size)
		return NULL;

	i = locate_object_entry_hash(pdata, oid, &found);

	if (index_pos)
		*index_pos = i;

	if (!found)
		return NULL;

So if the packing list is empty then it returns NULL without setting
index_pos.  Hmm.  It does set it in all other cases, no matter if oid is
found or not.  Is it really a good idea to make that exception?  I
suspect always setting index_pos here would silence the compiler as well
and fix the issue closer to its root.

But I may be missing something, this code looks complicated.

> diff --git a/bulk-checkin.c b/bulk-checkin.c
> index 39ee7d6107..87fa28c227 100644
> --- a/bulk-checkin.c
> +++ b/bulk-checkin.c
> @@ -200,6 +200,8 @@ static int deflate_to_pack(struct bulk_checkin_state *state,
>  	struct hashfile_checkpoint checkpoint;
>  	struct pack_idx_entry *idx = NULL;
>
> +	checkpoint.offset = 0;
> +
>  	seekback = lseek(fd, 0, SEEK_CUR);
>  	if (seekback == (off_t) -1)
>  		return error("cannot find the current offset");

Omitting further context, even though it would help, but this reply is
long enough already.  It seems the compiler got confused -- I can't see
an execution path that would use an uninitialized offset.  If idx is
NULL then the function is exited early, and if it's not then offset is
initialized.  But perhaps I'm missing something.

Anyway, my points are that simply initializing might not always be the
best fix, and that more context would help reviewers of such a patch,
but only if functions are reasonably short and it's not necessary to
follow the rabbit into a call chain hole.

Didn't check the other cases.

René
Junio C Hamano Sept. 5, 2019, 5:41 p.m. UTC | #2
Stephan Beyer <s-beyer@gmx.net> writes:

> diff --git a/fast-import.c b/fast-import.c
> index b44d6a467e..58f73f9105 100644
> --- a/fast-import.c
> +++ b/fast-import.c
> @@ -903,7 +903,8 @@ static int store_object(
>  	struct object_entry *e;
>  	unsigned char hdr[96];
>  	struct object_id oid;
> -	unsigned long hdrlen, deltalen;
> +	unsigned long hdrlen;
> +	unsigned long deltalen = 0;
>  	git_hash_ctx c;
>  	git_zstream s;

[in my attempt to imitate Réne...]

In this function, deltalen is used only when delta != NULL, i.e.

	if (delta) {
		s.next_in = delta;
		s.avail_in = deltalen;
	} else {
		s.next_in = (void *)dat->buf;
		s.avail_in = dat->len;
	}
	...
	if (delta) {
		...
		hdrlen = encode_in_pack_object_header(hdr, sizeof(hdr),
						      OBJ_OFS_DELTA, deltalen);
	...

Could delta become non-NULL without deltalen getting set?  We see
these before all uses of delta/deltalen in this function.

	if (last && last->data.len && last->data.buf && last->depth < max_depth
		&& dat->len > the_hash_algo->rawsz) {

		delta_count_attempts_by_type[type]++;
		delta = diff_delta(last->data.buf, last->data.len,
			dat->buf, dat->len,
			&deltalen, dat->len - the_hash_algo->rawsz);
	} else
		delta = NULL;

If diff_delta() returns non-NULL without touching deltalen, we'd be
in trouble.  We see this in delta.h

static inline void *
diff_delta(const void *src_buf, unsigned long src_bufsize,
	   const void *trg_buf, unsigned long trg_bufsize,
	   unsigned long *delta_size, unsigned long max_delta_size)
{
	struct delta_index *index = create_delta_index(src_buf, src_bufsize);
	if (index) {
		void *delta = create_delta(index, trg_buf, trg_bufsize,
					   delta_size, max_delta_size);
		free_delta_index(index);
		return delta;
	}
	return NULL;
}

so the question is if create_delta() can return non-NULL without
touching delta_size.  In diff-delta.c::create_delta(), *delta_size
is assigned once at the very end, when the function returns a
pointer to an allocated memory 'out'.  All the "return" statement
other than that last one literally returns "NULL".

So it seems that this is a case the compiler getting confused.
Jeff King Sept. 5, 2019, 5:53 p.m. UTC | #3
On Thu, Sep 05, 2019 at 07:08:49PM +0200, René Scharfe wrote:

> Anyway, my points are that simply initializing might not always be the
> best fix, and that more context would help reviewers of such a patch,
> but only if functions are reasonably short and it's not necessary to
> follow the rabbit into a call chain hole.

I started looking at these, too, and came to the same conclusion.  Some
of these are pointing to real bugs, and just silencing the warnings
misses the opportunity to find them.

I'll comment on the ones I did look at.

> Further context:
> 
> 	ident_line = find_commit_header(buffer, "author", &ident_len);
> 
> 	if (split_ident_line(&id, ident_line, ident_len) < 0)
> 		die(_("invalid ident line: %.*s"), (int)ident_len, ident_line);
> 
> find_commit_header() can return NULL.  split_ident_line() won't handle
> that well.  So I think what's missing here is a NULL check.  If the
> compiler is smart enough then that should silence the initialization
> warning.

Yeah, this one is a segfault waiting to happen, I think, if the author
line is missing.

> > diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c
> > index 76ce906946..d0c03b0e9b 100644
> > --- a/builtin/pack-objects.c
> > +++ b/builtin/pack-objects.c
> > @@ -1171,7 +1171,7 @@ static int add_object_entry(const struct object_id *oid, enum object_type type,
> >  {
> >  	struct packed_git *found_pack = NULL;
> >  	off_t found_offset = 0;
> > -	uint32_t index_pos;
> > +	uint32_t index_pos = 0;
> [...]
> So if the packing list is empty then it returns NULL without setting
> index_pos.  Hmm.  It does set it in all other cases, no matter if oid is
> found or not.  Is it really a good idea to make that exception?  I
> suspect always setting index_pos here would silence the compiler as well
> and fix the issue closer to its root.

Yeah, this is a weird one. That index_pos is actually a position in the
current hash table. And in the first object we see in pack-objects'
input, we definitely _do_ end up with a nonsense index_pos, which then
gets passed to packlist_alloc().

But since we also need to grow the hash table during that allocation, we
don't use index_pos at all. So setting index_pos to something known like
"0" is kind of weird, in that we don't have a hash position at all (the
table doesn't exist!). But it's probably the least bad thing. If we do
that, it should happen in packlist_find().

I have to agree this whole "passing around index_pos" thing looks
complicated. It seems like we're just saving ourselves one hash lookup
on insertion (and it's not even an expensive hash, since we're reusing
the oid).

I suspect the whole thing would also be a lot simpler using one of our
existing hash implementations.

> Didn't check the other cases.

The only other one I looked at was:

>> int cmd__read_cache(int argc, const char **argv)
>> {
>>-       int i, cnt = 1, namelen;
>>+       int i, cnt = 1, namelen = 0;

I actually saw this one the other day, because it triggered for me when
compiling with SANITIZE=address. AFAICT it's a false positive. "name" is
always NULL unless skip_prefix() returns true, in which case we always
set "namelen". And we only look at "namelen" if "name" is non-NULL.

This one doesn't even require LTO, because skip_prefix() is an inline
function. I'm not sure why the compiler gets confused here. I don't mind
initializing namelen to 0 to silence it, though (we already set name to
NULL, so this would just match).

-Peff
Junio C Hamano Sept. 5, 2019, 5:56 p.m. UTC | #4
Stephan Beyer <s-beyer@gmx.net> writes:

> diff --git a/t/helper/test-read-cache.c b/t/helper/test-read-cache.c
> index 7e79b555de..ef0963e2f4 100644
> --- a/t/helper/test-read-cache.c
> +++ b/t/helper/test-read-cache.c
> @@ -4,7 +4,7 @@
>
>  int cmd__read_cache(int argc, const char **argv)
>  {
> -	int i, cnt = 1, namelen;
> +	int i, cnt = 1, namelen = 0;
>  	const char *name = NULL;
>
>  	if (argc > 1 && skip_prefix(argv[1], "--print-and-refresh=", &name)) {
		namelen = strlen(name);

The above is the only assignment to namelen in this function, and
namelen is used like so:

		if (name) {
			...
			pos = index_name_pos(&the_index, name, namelen);

So somebody does not realize that skip_prefix() returns true only
when it touches name.  But skip_prefix() is inline and visible to
the compiler, and it is quite clear that name is only touched when 
the function returns non-zero.

static inline int skip_prefix(const char *str, const char *prefix,
			      const char **out)
{
	do {
		if (!*prefix) {
			*out = str;
			return 1;
		}
	} while (*str++ == *prefix++);
	return 0;
}

So it looks like it is another case of compiler getting confused.
Jeff King Sept. 5, 2019, 6:03 p.m. UTC | #5
On Thu, Sep 05, 2019 at 10:56:12AM -0700, Junio C Hamano wrote:

> Stephan Beyer <s-beyer@gmx.net> writes:
> 
> > diff --git a/t/helper/test-read-cache.c b/t/helper/test-read-cache.c
> > index 7e79b555de..ef0963e2f4 100644
> > --- a/t/helper/test-read-cache.c
> > +++ b/t/helper/test-read-cache.c
> > @@ -4,7 +4,7 @@
> >
> >  int cmd__read_cache(int argc, const char **argv)
> >  {
> > -	int i, cnt = 1, namelen;
> > +	int i, cnt = 1, namelen = 0;
> >  	const char *name = NULL;
> >
> >  	if (argc > 1 && skip_prefix(argv[1], "--print-and-refresh=", &name)) {
> 		namelen = strlen(name);
> 
> The above is the only assignment to namelen in this function, and
> namelen is used like so:
> 
> 		if (name) {
> 			...
> 			pos = index_name_pos(&the_index, name, namelen);
> 
> So somebody does not realize that skip_prefix() returns true only
> when it touches name.  But skip_prefix() is inline and visible to
> the compiler, and it is quite clear that name is only touched when 
> the function returns non-zero.

I said earlier that I wouldn't mind seeing "namelen = 0" here. But I
think there is a much more direct solution: keeping the assignment and
point of use closer together. That makes it more clear both to the
compiler and to a human when we expect the variable to be valid. In
fact, since it's only used once, we can drop the variable altogther. :)

diff --git a/t/helper/test-read-cache.c b/t/helper/test-read-cache.c
index 7e79b555de..244977a29b 100644
--- a/t/helper/test-read-cache.c
+++ b/t/helper/test-read-cache.c
@@ -4,11 +4,10 @@
 
 int cmd__read_cache(int argc, const char **argv)
 {
-	int i, cnt = 1, namelen;
+	int i, cnt = 1;
 	const char *name = NULL;
 
 	if (argc > 1 && skip_prefix(argv[1], "--print-and-refresh=", &name)) {
-		namelen = strlen(name);
 		argc--;
 		argv++;
 	}
@@ -24,7 +23,7 @@ int cmd__read_cache(int argc, const char **argv)
 
 			refresh_index(&the_index, REFRESH_QUIET,
 				      NULL, NULL, NULL);
-			pos = index_name_pos(&the_index, name, namelen);
+			pos = index_name_pos(&the_index, name, strlen(name));
 			if (pos < 0)
 				die("%s not in index", name);
 			printf("%s is%s up to date\n", name,
Junio C Hamano Sept. 5, 2019, 6:22 p.m. UTC | #6
Jeff King <peff@peff.net> writes:

> I said earlier that I wouldn't mind seeing "namelen = 0" here. But I
> think there is a much more direct solution: keeping the assignment and
> point of use closer together. That makes it more clear both to the
> compiler and to a human when we expect the variable to be valid. In
> fact, since it's only used once, we can drop the variable altogther. :)

Yeah, that sounds like a nice solution.

> diff --git a/t/helper/test-read-cache.c b/t/helper/test-read-cache.c
> index 7e79b555de..244977a29b 100644
> --- a/t/helper/test-read-cache.c
> +++ b/t/helper/test-read-cache.c
> @@ -4,11 +4,10 @@
>  
>  int cmd__read_cache(int argc, const char **argv)
>  {
> -	int i, cnt = 1, namelen;
> +	int i, cnt = 1;
>  	const char *name = NULL;
>  
>  	if (argc > 1 && skip_prefix(argv[1], "--print-and-refresh=", &name)) {
> -		namelen = strlen(name);
>  		argc--;
>  		argv++;
>  	}
> @@ -24,7 +23,7 @@ int cmd__read_cache(int argc, const char **argv)
>  
>  			refresh_index(&the_index, REFRESH_QUIET,
>  				      NULL, NULL, NULL);
> -			pos = index_name_pos(&the_index, name, namelen);
> +			pos = index_name_pos(&the_index, name, strlen(name));
>  			if (pos < 0)
>  				die("%s not in index", name);
>  			printf("%s is%s up to date\n", name,
René Scharfe Sept. 5, 2019, 7:09 p.m. UTC | #7
Am 05.09.19 um 19:53 schrieb Jeff King:
>>> int cmd__read_cache(int argc, const char **argv)
>>> {
>>> -       int i, cnt = 1, namelen;
>>> +       int i, cnt = 1, namelen = 0;
>
> I actually saw this one the other day, because it triggered for me when
> compiling with SANITIZE=address. AFAICT it's a false positive. "name" is
> always NULL unless skip_prefix() returns true, in which case we always
> set "namelen". And we only look at "namelen" if "name" is non-NULL.
>
> This one doesn't even require LTO, because skip_prefix() is an inline
> function. I'm not sure why the compiler gets confused here.

Yes, that's curious.

> I don't mind
> initializing namelen to 0 to silence it, though (we already set name to
> NULL, so this would just match).

Pushing the strlen() call into the loop and getting rid of namelen should
work as well -- and I'd be surprised if this had a measurable performance
impact.

René
Junio C Hamano Sept. 5, 2019, 7:25 p.m. UTC | #8
René Scharfe <l.s.r@web.de> writes:

> Am 05.09.19 um 19:53 schrieb Jeff King:
>>>> int cmd__read_cache(int argc, const char **argv)
>>>> {
>>>> -       int i, cnt = 1, namelen;
>>>> +       int i, cnt = 1, namelen = 0;
>>
>> I actually saw this one the other day, because it triggered for me when
>> compiling with SANITIZE=address. AFAICT it's a false positive. "name" is
>> always NULL unless skip_prefix() returns true, in which case we always
>> set "namelen". And we only look at "namelen" if "name" is non-NULL.
>>
>> This one doesn't even require LTO, because skip_prefix() is an inline
>> function. I'm not sure why the compiler gets confused here.
>
> Yes, that's curious.
>
>> I don't mind
>> initializing namelen to 0 to silence it, though (we already set name to
>> NULL, so this would just match).
>
> Pushing the strlen() call into the loop and getting rid of namelen should
> work as well -- and I'd be surprised if this had a measurable performance
> impact.

Yeah, we are making strlen() call on a constant "name" in a loop
over argv[].  I do not think it matters in this case, either.
René Scharfe Sept. 5, 2019, 7:39 p.m. UTC | #9
Am 05.09.19 um 21:25 schrieb Junio C Hamano:
> René Scharfe <l.s.r@web.de> writes:
>
>> Am 05.09.19 um 19:53 schrieb Jeff King:
>>>>> int cmd__read_cache(int argc, const char **argv)
>>>>> {
>>>>> -       int i, cnt = 1, namelen;
>>>>> +       int i, cnt = 1, namelen = 0;
>>>
>>> I actually saw this one the other day, because it triggered for me when
>>> compiling with SANITIZE=address. AFAICT it's a false positive. "name" is
>>> always NULL unless skip_prefix() returns true, in which case we always
>>> set "namelen". And we only look at "namelen" if "name" is non-NULL.
>>>
>>> This one doesn't even require LTO, because skip_prefix() is an inline
>>> function. I'm not sure why the compiler gets confused here.
>>
>> Yes, that's curious.
>>
>>> I don't mind
>>> initializing namelen to 0 to silence it, though (we already set name to
>>> NULL, so this would just match).
>>
>> Pushing the strlen() call into the loop and getting rid of namelen should
>> work as well -- and I'd be surprised if this had a measurable performance
>> impact.
>
> Yeah, we are making strlen() call on a constant "name" in a loop
> over argv[].  I do not think it matters in this case, either.

The loop count is either 1 or argv[1] interpreted as a number, i.e. it could
be very high.  Its body consists of an index load and writing a number to a
file, though -- a strlen() call on the name of that file should go unnoticed
amid that activity.  (I didn't measure it, though.)

René
Junio C Hamano Sept. 5, 2019, 7:50 p.m. UTC | #10
René Scharfe <l.s.r@web.de> writes:

> The loop count is either 1 or argv[1] interpreted as a number, i.e. it could
> be very high.  Its body consists of an index load and writing a number to a
> file, though -- a strlen() call on the name of that file should go unnoticed
> amid that activity.  (I didn't measure it, though.)

Yup, I didn't either but we reasoned along the same line.
Jeff King Sept. 5, 2019, 10:48 p.m. UTC | #11
On Thu, Sep 05, 2019 at 10:24:59AM +0200, Stephan Beyer wrote:

> Compiler heuristics for detection of potentially uninitialized variables
> may change between compiler versions and enabling link-time optimization
> may find new warnings.  Indeed, compiling with gcc 9.2.1 and enabled
> link-time optimization feature resulted in a few hits that are fixed by
> this patch in the most naïve way.  This allows to compile git using the
> DEVELOPER=1 switch (which sets -Werror) and using the -flto flag.

Lots of discussion in this thread. Let's try to turn it into some
patches. :)

After the patches below, I can compile cleanly with gcc 9.2.1 using
-flto with both -O2 and -O3 (some of the cases only seemed to trigger
for me with -O3).

I've ordered them in decreasing value. The first one is a real bugfix,
the second is a related cleanup. The next 3 are appeasing the compiler,
but I think are a good idea (but note I went more for root causes than
your originals). The last one is perhaps more controversial, but IMHO
worth doing.

  [1/6]: git-am: handle missing "author" when parsing commit
  [2/6]: pack-objects: use object_id in packlist_alloc()
  [3/6]: bulk-checkin: zero-initialize hashfile_checkpoint
  [4/6]: diff-delta: set size out-parameter to 0 for NULL delta
  [5/6]: test-read-cache: drop namelen variable
  [6/6]: pack-objects: drop packlist index_pos optimization

 builtin/am.c               |  4 +++-
 builtin/pack-objects.c     | 33 ++++++++++++++-------------------
 bulk-checkin.c             |  2 +-
 diff-delta.c               |  2 ++
 pack-bitmap-write.c        |  2 +-
 pack-bitmap.c              |  2 +-
 pack-objects.c             | 20 ++++++++++----------
 pack-objects.h             |  6 ++----
 t/helper/test-read-cache.c |  5 ++---
 9 files changed, 36 insertions(+), 40 deletions(-)

-Peff
Stephan Beyer Sept. 5, 2019, 10:53 p.m. UTC | #12
On 9/6/19 12:48 AM, Jeff King wrote:
> On Thu, Sep 05, 2019 at 10:24:59AM +0200, Stephan Beyer wrote:
>
>> Compiler heuristics for detection of potentially uninitialized variables
>> may change between compiler versions and enabling link-time optimization
>> may find new warnings.  Indeed, compiling with gcc 9.2.1 and enabled
>> link-time optimization feature resulted in a few hits that are fixed by
>> this patch in the most naïve way.  This allows to compile git using the
>> DEVELOPER=1 switch (which sets -Werror) and using the -flto flag.
>
> Lots of discussion in this thread. Let's try to turn it into some
> patches. :)

I thought the same and just sent my version of it. :D

Stephan
Jeff King Sept. 5, 2019, 10:58 p.m. UTC | #13
On Fri, Sep 06, 2019 at 12:53:49AM +0200, Stephan Beyer wrote:

> On 9/6/19 12:48 AM, Jeff King wrote:
> > On Thu, Sep 05, 2019 at 10:24:59AM +0200, Stephan Beyer wrote:
> >
> >> Compiler heuristics for detection of potentially uninitialized variables
> >> may change between compiler versions and enabling link-time optimization
> >> may find new warnings.  Indeed, compiling with gcc 9.2.1 and enabled
> >> link-time optimization feature resulted in a few hits that are fixed by
> >> this patch in the most naïve way.  This allows to compile git using the
> >> DEVELOPER=1 switch (which sets -Werror) and using the -flto flag.
> >
> > Lots of discussion in this thread. Let's try to turn it into some
> > patches. :)
> 
> I thought the same and just sent my version of it. :D

Yeah, I see that our mails crossed. :) I like some of my versions
better, but I'm OK with either (or a mix-and-match).

-Peff
Stephan Beyer Sept. 5, 2019, 11:10 p.m. UTC | #14
On 9/6/19 12:58 AM, Jeff King wrote:
> On Fri, Sep 06, 2019 at 12:53:49AM +0200, Stephan Beyer wrote:
>
>> On 9/6/19 12:48 AM, Jeff King wrote:
>>> On Thu, Sep 05, 2019 at 10:24:59AM +0200, Stephan Beyer wrote:
>>>
>>>> Compiler heuristics for detection of potentially uninitialized variables
>>>> may change between compiler versions and enabling link-time optimization
>>>> may find new warnings.  Indeed, compiling with gcc 9.2.1 and enabled
>>>> link-time optimization feature resulted in a few hits that are fixed by
>>>> this patch in the most naïve way.  This allows to compile git using the
>>>> DEVELOPER=1 switch (which sets -Werror) and using the -flto flag.
>>>
>>> Lots of discussion in this thread. Let's try to turn it into some
>>> patches. :)
>>
>> I thought the same and just sent my version of it. :D
>
> Yeah, I see that our mails crossed. :) I like some of my versions
> better, but I'm OK with either (or a mix-and-match).

I took a quick glance at yours. I also noticed the issue you address in
[PATCH 2/6], but I was unsure if this is the way to go (I'm only
occasionally reading on this list). I would prefer your patch series,
with maybe one exception...

The thing is: I had *exactly* the same commit like your [PATCH 6/6]
(except for the commit message and for the number), but I dropped it.
Why? Because I had the feeling (no particular instance though) that the
second locate_object_entry_hash() for each insertion *can* indeed take
"too much" time. Also, I was wondering, if the "found = 1" case should
be catched as a BUG("should not happen") or something.

I don't care much, though. The performance impact should probably be
checked carefully.

Stephan
Jeff King Sept. 6, 2019, 1:24 a.m. UTC | #15
On Fri, Sep 06, 2019 at 01:10:46AM +0200, Stephan Beyer wrote:

> I took a quick glance at yours. I also noticed the issue you address in
> [PATCH 2/6], but I was unsure if this is the way to go (I'm only
> occasionally reading on this list). I would prefer your patch series,
> with maybe one exception...

Yeah, we've been slowly removing these old "unsigned char *" references
(see a7db4c193d98f for the latest round touching this same code -- I'm
actually surprised I missed this one back then, as that's when
packlist_find() got converted).

> The thing is: I had *exactly* the same commit like your [PATCH 6/6]
> (except for the commit message and for the number), but I dropped it.
> Why? Because I had the feeling (no particular instance though) that the
> second locate_object_entry_hash() for each insertion *can* indeed take
> "too much" time. Also, I was wondering, if the "found = 1" case should
> be catched as a BUG("should not happen") or something.
> 
> I don't care much, though. The performance impact should probably be
> checked carefully.

I did measure it, like this:

  # Do the traversal separately. This would make any difference in the
  # pack-objects hash code stand out _more_, plus it makes it cheaper to
  # run multiple trials.
  git rev-list --objects --all >input

  # Make sure stderr is redirected to avoid progress, which again would
  # amplify any differences.
  git pack-objects --stdout --delta-base-offset <input >/dev/null 2>&1

Running on linux.git, I got:

  [before]
  Benchmark #1: git pack-objects --stdout --delta-base-offset <input >/dev/null 2>&1
    Time (mean ± σ):     26.225 s ±  0.233 s    [User: 24.089 s, System: 4.867 s]
    Range (min … max):   25.915 s … 26.555 s    10 runs

  [after]
  Benchmark #1: git pack-objects --stdout --delta-base-offset <input >/dev/null 2>&1
    Time (mean ± σ):     26.129 s ±  0.170 s    [User: 24.003 s, System: 4.958 s]
    Range (min … max):   25.974 s … 26.570 s    10 runs

So actually faster after, though not statistically significant. ;)

The BUG() on "found==1" might be worth doing.

-Peff
diff mbox series

Patch

diff --git a/builtin/am.c b/builtin/am.c
index 1aea657a7f..ab914fd46e 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -1266,7 +1266,7 @@  static int get_mail_commit_oid(struct object_id *commit_id, const char *mail)
 static void get_commit_info(struct am_state *state, struct commit *commit)
 {
 	const char *buffer, *ident_line, *msg;
-	size_t ident_len;
+	size_t ident_len = 0;
 	struct ident_split id;

 	buffer = logmsg_reencode(commit, NULL, get_commit_output_encoding());
diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c
index 76ce906946..d0c03b0e9b 100644
--- a/builtin/pack-objects.c
+++ b/builtin/pack-objects.c
@@ -1171,7 +1171,7 @@  static int add_object_entry(const struct object_id *oid, enum object_type type,
 {
 	struct packed_git *found_pack = NULL;
 	off_t found_offset = 0;
-	uint32_t index_pos;
+	uint32_t index_pos = 0;

 	display_progress(progress_state, ++nr_seen);

diff --git a/bulk-checkin.c b/bulk-checkin.c
index 39ee7d6107..87fa28c227 100644
--- a/bulk-checkin.c
+++ b/bulk-checkin.c
@@ -200,6 +200,8 @@  static int deflate_to_pack(struct bulk_checkin_state *state,
 	struct hashfile_checkpoint checkpoint;
 	struct pack_idx_entry *idx = NULL;

+	checkpoint.offset = 0;
+
 	seekback = lseek(fd, 0, SEEK_CUR);
 	if (seekback == (off_t) -1)
 		return error("cannot find the current offset");
diff --git a/fast-import.c b/fast-import.c
index b44d6a467e..58f73f9105 100644
--- a/fast-import.c
+++ b/fast-import.c
@@ -903,7 +903,8 @@  static int store_object(
 	struct object_entry *e;
 	unsigned char hdr[96];
 	struct object_id oid;
-	unsigned long hdrlen, deltalen;
+	unsigned long hdrlen;
+	unsigned long deltalen = 0;
 	git_hash_ctx c;
 	git_zstream s;

diff --git a/t/helper/test-read-cache.c b/t/helper/test-read-cache.c
index 7e79b555de..ef0963e2f4 100644
--- a/t/helper/test-read-cache.c
+++ b/t/helper/test-read-cache.c
@@ -4,7 +4,7 @@ 

 int cmd__read_cache(int argc, const char **argv)
 {
-	int i, cnt = 1, namelen;
+	int i, cnt = 1, namelen = 0;
 	const char *name = NULL;

 	if (argc > 1 && skip_prefix(argv[1], "--print-and-refresh=", &name)) {