From: Junio C Hamano <hidden> Date: 2021-03-30 05:51:30
Ævar Arnfjörð Bjarmason [off-list ref] writes:
Fix a regression in 89e4202f982 ([PATCH] Parse tags for absent
objects, 2005-06-21) (yes, that ancient!) and correctly report an
error on a tag like:
object <a tree hash>
type commit
As:
error: object <a tree hash> is tree, not a commit
Instead of our long-standing misbehavior of inverting the two, and
reporting:
error: object <a tree hash> is commit, not a tree
Which, as can be trivially seen with 'git cat-file -t <a tree hash>'
is incorrect.
Hmph, I've always thought it is just "supposed to be a" missing in
the sentence ;-)
Hence the non-intuitive solution of adding a
lookup_{blob,commit,tag,tree}_type() function. It's to distinguish
calls from parse_object_buffer() where we actually know the type, from
a parse_tag_buffer() where we're just guessing about the type.
I think it makes sense to allow the caller to express distinction
between "I know that this object is a blob, because I just read its
object header" and "Another object tells me that this object must be
a blob, because it is in a tree entry whose mode bits are 100644".
I wish we found a set of names better than lookup_<type>_type() for
that, though. It's just between
lookup_tag_type(r, oid, OBJ_NONE);
lookup_tag_type(r, oid, OBJ_TAG);
I cannot quite tell which one is which. I also wonder if the last
arg should just be a boolean ("I know it is a tag" vs "I heard it
must be a tag").
From: Jeff King <hidden> Date: 2021-03-31 11:02:47
On Mon, Mar 29, 2021 at 10:50:18PM -0700, Junio C Hamano wrote:
Ævar Arnfjörð Bjarmason [off-list ref] writes:
quoted
Fix a regression in 89e4202f982 ([PATCH] Parse tags for absent
objects, 2005-06-21) (yes, that ancient!) and correctly report an
error on a tag like:
object <a tree hash>
type commit
As:
error: object <a tree hash> is tree, not a commit
Instead of our long-standing misbehavior of inverting the two, and
reporting:
error: object <a tree hash> is commit, not a tree
Which, as can be trivially seen with 'git cat-file -t <a tree hash>'
is incorrect.
Hmph, I've always thought it is just "supposed to be a" missing in
the sentence ;-)
So going with the discussion elsewhere in the thread, I'd probably say
something like:
error: object <oid> seen as both a commit and a tree
which precisely says what we do know, without implying which is correct.
Ævar's patch tries to improve the case where we might _know_ which is
correct (because we're actually parsing the object contents), but of
course it covers only a fraction of cases. I'm not really opposed to
that per se, but I probably wouldn't bother myself.
Side note: this is all making the assumption that what is in the
object itself is "correct", but of course that is not necessarily
true, even. All of these cases are the result of bugs, so it is
possible that the bug was in the writing of the original object
contents, and not the object that is referring to it. Likewise, I'd
imagine an easy way to get into this situation is with a bogus
refs/replace object that switches type.
quoted
Hence the non-intuitive solution of adding a
lookup_{blob,commit,tag,tree}_type() function. It's to distinguish
calls from parse_object_buffer() where we actually know the type, from
a parse_tag_buffer() where we're just guessing about the type.
I think it makes sense to allow the caller to express distinction
between "I know that this object is a blob, because I just read its
object header" and "Another object tells me that this object must be
a blob, because it is in a tree entry whose mode bits are 100644".
I wish we found a set of names better than lookup_<type>_type() for
that, though. It's just between
lookup_tag_type(r, oid, OBJ_NONE);
lookup_tag_type(r, oid, OBJ_TAG);
I cannot quite tell which one is which. I also wonder if the last
arg should just be a boolean ("I know it is a tag" vs "I heard it
must be a tag").
Yeah, I also found that very confusing. AFAICT lookup_tag_type() would
only ever see OBJ_NONE or OBJ_TAG. Making it more than a boolean makes
both the interface and implementation more complicated.
I also think the manual handling of OBJ_NONE in each lookup_* function
is confusing. They all call object_as_type() because the point of that
function is both to type-check the struct and to convert it away from
OBJ_NONE.
If we handled this error there, then I think it would be much more
natural, because we'd have already covered the OBJ_NONE case, and
because it's already the place we're emitting the existing error. E.g.:
@@ -169,10 +169,16 @@ void *object_as_type(struct object *obj, enum object_type type, int quiet)returnobj;}else{-if(!quiet)-error(_("object %s is a %s, not a %s"),-oid_to_hex(&obj->oid),-type_name(obj->type),type_name(type));+if(!(flags&OBJECT_AS_TYPE_QUIET)){+if(flags&OBJECT_AS_TYPE_EXPECT_PARSED)+error(_("object %s is a %s, but was referred to as a %s"),+oid_to_hex(&obj->oid),type_name(obj->type),+type_name(type));+else+error(_("object %s referred to as both a %s and a %s"),+oid_to_hex(&obj->oid),+type_name(obj->type),type_name(type));+}returnNULL;}}-Peff
On Mon, Mar 29, 2021 at 10:50:18PM -0700, Junio C Hamano wrote:
quoted
Ævar Arnfjörð Bjarmason [off-list ref] writes:
quoted
Fix a regression in 89e4202f982 ([PATCH] Parse tags for absent
objects, 2005-06-21) (yes, that ancient!) and correctly report an
error on a tag like:
object <a tree hash>
type commit
As:
error: object <a tree hash> is tree, not a commit
Instead of our long-standing misbehavior of inverting the two, and
reporting:
error: object <a tree hash> is commit, not a tree
Which, as can be trivially seen with 'git cat-file -t <a tree hash>'
is incorrect.
Hmph, I've always thought it is just "supposed to be a" missing in
the sentence ;-)
So going with the discussion elsewhere in the thread, I'd probably say
something like:
error: object <oid> seen as both a commit and a tree
which precisely says what we do know, without implying which is correct.
Ævar's patch tries to improve the case where we might _know_ which is
correct (because we're actually parsing the object contents), but of
course it covers only a fraction of cases. I'm not really opposed to
that per se, but I probably wouldn't bother myself.
What fraction of cases? As far as I can tell it covers all cases where
we get this error.
If there is a case like what you're describing I haven't found it.
I.e. it happens when we have an un-parsed "struct object" whose type is
inferred, and parse it to find out it's not what we expected.
It's not ambigious at all what the object actually is. It's just that
the previous code was leaking the *assumption* about the type at the
time of emitting the error, due to an apparent oversight with parsed
v.s. non-parsed.
Or in other words, we're leaking the implementation detail that we
pre-allocated an object struct of a given type in anticipation of
holding a parsed version of that object soon.
Side note: this is all making the assumption that what is in the
object itself is "correct", but of course that is not necessarily
true, even. All of these cases are the result of bugs, so it is
possible that the bug was in the writing of the original object
contents, and not the object that is referring to it. Likewise, I'd
imagine an easy way to get into this situation is with a bogus
refs/replace object that switches type.
Perhaps, I haven't tested that in any detail.
quoted
quoted
Hence the non-intuitive solution of adding a
lookup_{blob,commit,tag,tree}_type() function. It's to distinguish
calls from parse_object_buffer() where we actually know the type, from
a parse_tag_buffer() where we're just guessing about the type.
I think it makes sense to allow the caller to express distinction
between "I know that this object is a blob, because I just read its
object header" and "Another object tells me that this object must be
a blob, because it is in a tree entry whose mode bits are 100644".
I wish we found a set of names better than lookup_<type>_type() for
that, though. It's just between
lookup_tag_type(r, oid, OBJ_NONE);
lookup_tag_type(r, oid, OBJ_TAG);
I cannot quite tell which one is which. I also wonder if the last
arg should just be a boolean ("I know it is a tag" vs "I heard it
must be a tag").
Yeah, I also found that very confusing. AFAICT lookup_tag_type() would
only ever see OBJ_NONE or OBJ_TAG. Making it more than a boolean makes
both the interface and implementation more complicated.
I don't feel strongly either way, but one concern here is that these are
very hot functions, and maybe it's better to give the compiler a better
chance to work with them without considering an extra argument, but I
haven't tested that...
quoted hunk
I also think the manual handling of OBJ_NONE in each lookup_* function
is confusing. They all call object_as_type() because the point of that
function is both to type-check the struct and to convert it away from
OBJ_NONE.
If we handled this error there, then I think it would be much more
natural, because we'd have already covered the OBJ_NONE case, and
because it's already the place we're emitting the existing error. E.g.:
@@ -169,10 +169,16 @@ void *object_as_type(struct object *obj, enum object_type type, int quiet)returnobj;}else{-if(!quiet)-error(_("object %s is a %s, not a %s"),-oid_to_hex(&obj->oid),-type_name(obj->type),type_name(type));+if(!(flags&OBJECT_AS_TYPE_QUIET)){+if(flags&OBJECT_AS_TYPE_EXPECT_PARSED)+error(_("object %s is a %s, but was referred to as a %s"),+oid_to_hex(&obj->oid),type_name(obj->type),+type_name(type));+else+error(_("object %s referred to as both a %s and a %s"),+oid_to_hex(&obj->oid),+type_name(obj->type),type_name(type));+}returnNULL;}}
Per the above I don't understand how you think there's any uncertainty
here.
If I'm right and there isn't then first of all I don't see how we could
emit 1/2 of those errors. The whole problem here is that we don't know
the type of the un-parsed object (and presumably don't want to eagerly
know, it would mean hitting the object store).
But when we do know why would we beat around the bush and say "was
referred to as X and Y" once we know what it is.
AFAICT there's no more reason to think that parse_object_buffer() will
be wrong about the type than "git cat-file -t" will be. They both use
the same underlying functions to get that information.
From: Jeff King <hidden> Date: 2021-03-31 18:59:56
On Wed, Mar 31, 2021 at 08:31:16PM +0200, Ævar Arnfjörð Bjarmason wrote:
quoted
Ævar's patch tries to improve the case where we might _know_ which is
correct (because we're actually parsing the object contents), but of
course it covers only a fraction of cases. I'm not really opposed to
that per se, but I probably wouldn't bother myself.
What fraction of cases? As far as I can tell it covers all cases where
we get this error.
If there is a case like what you're describing I haven't found it.
It would happen any time somebody calls lookup_foo() because they saw an
object referenced, but _doesn't_ parse it. And then somebody later calls
lookup_bar() in the same way. Neither of them consulted the actual
object database.
Try this with your patches:
-- >8 --
git init repo
cd repo
# just for making things deterministic
export GIT_COMMITTER_NAME='A U Thor'
export GIT_COMMITTER_EMAIL='author@example.com'
export GIT_COMMITTER_DATE='@1234567890 +0000'
blob=$(echo foo | git hash-object -w --stdin)
git tag -m 'tag of blob' tag-of-blob $blob
git update-ref refs/tags/tag-of-commit $(
git cat-file tag tag-of-blob |
sed s/blob/commit/g |
git hash-object -w --stdin -t tag
)
git update-ref refs/tags/tag-of-tree $(
git cat-file tag tag-of-blob |
sed s/blob/tree/g |
git hash-object -w --stdin -t tag
)
git fsck
-- >8 --
That fsck produces (257cc5642 is the blob):
error: object 257cc5642cb1a054f08cc83f2d943e56fd3ebe99 is a blob, not a commit
error: 257cc5642cb1a054f08cc83f2d943e56fd3ebe99: object could not be parsed: .git/objects/25/7cc5642cb1a054f08cc83f2d943e56fd3ebe99
error: object 257cc5642cb1a054f08cc83f2d943e56fd3ebe99 is a commit, not a tree
error: bad tag pointer to 257cc5642cb1a054f08cc83f2d943e56fd3ebe99 in aaff0d42df150e1a734f6a8516878b2ea315ee0a
error: aaff0d42df150e1a734f6a8516878b2ea315ee0a: object could not be parsed: .git/objects/aa/ff0d42df150e1a734f6a8516878b2ea315ee0a
error: object 257cc5642cb1a054f08cc83f2d943e56fd3ebe99 is a commit, not a blob
error: bad tag pointer to 257cc5642cb1a054f08cc83f2d943e56fd3ebe99 in bbd2b7077cd91ee6175cdc0e4c477c25c230cdc7
error: bbd2b7077cd91ee6175cdc0e4c477c25c230cdc7: object could not be parsed: .git/objects/bb/d2b7077cd91ee6175cdc0e4c477c25c230cdc7
So we claim "is X, not Y" in multiple directions for the same object.
It might just be that there are spots in the fsck code that need to be
adjusted to use your new function (if they are indeed parsing the
referred-to object). But there are lots of places that don't actually
parse the object at the moment they're parsing the tag. E.g.:
$ git for-each-ref --format='%(*objectname)'
error: object 257cc5642cb1a054f08cc83f2d943e56fd3ebe99 is a commit, not a tree
error: bad tag pointer to 257cc5642cb1a054f08cc83f2d943e56fd3ebe99 in aaff0d42df150e1a734f6a8516878b2ea315ee0a
Segmentation fault
Neither of those types is the correct one. And the segfault is just a
bonus! :)
I'd expect similar cases with parsing commit parents and tree pointers.
And probably tree entries whose modes are wrong.
I.e. it happens when we have an un-parsed "struct object" whose type is
inferred, and parse it to find out it's not what we expected.
It's not ambigious at all what the object actually is. It's just that
the previous code was leaking the *assumption* about the type at the
time of emitting the error, due to an apparent oversight with parsed
v.s. non-parsed.
Or in other words, we're leaking the implementation detail that we
pre-allocated an object struct of a given type in anticipation of
holding a parsed version of that object soon.
Right. In the case that you are indeed parsing the object later, you can
say definitively "it is X in the odb, but seen as Y previously". But we
do not always hit the "is X, not Y" error when parsing the object. It
might be caused by two of these "pre-allocations" (though really I think
it is not just an implementation detail; the pre-allocation happened
because some other object referred to us as a given type, so it really
is a corruption in the repository. Just not in the object we mention).
quoted
@@ -169,10 +169,16 @@ void *object_as_type(struct object *obj, enum object_type type, int quiet) return obj; } else {- if (!quiet)- error(_("object %s is a %s, not a %s"),- oid_to_hex(&obj->oid),- type_name(obj->type), type_name(type));+ if (!(flags & OBJECT_AS_TYPE_QUIET)) {+ if (flags & OBJECT_AS_TYPE_EXPECT_PARSED)+ error(_("object %s is a %s, but was referred to as a %s"),+ oid_to_hex(&obj->oid), type_name(obj->type),+ type_name(type));+ else+ error(_("object %s referred to as both a %s and a %s"),+ oid_to_hex(&obj->oid),+ type_name(obj->type), type_name(type));+ } return NULL; } }
Per the above I don't understand how you think there's any uncertainty
here.
If I'm right and there isn't then first of all I don't see how we could
emit 1/2 of those errors. The whole problem here is that we don't know
the type of the un-parsed object (and presumably don't want to eagerly
know, it would mean hitting the object store).
Forgetting for a moment how to trigger it with actual Git commands, the
root of the problem is that:
lookup_tree(&oid);
lookup_blob(&oid);
is going to produce an error message. But we cannot know which object
type is wrong and which is right (if any). So we'd want to produce the
"referred to as both" message.
_If_ the caller happens to know that it has just parsed the object
contents and got a tree, then it would call lookup_parsed_tree(&oid),
which would pass along OBJECT_AS_TYPE_EXPECT_PARSED, and produce the
other message.
In practice, of course those two lookup_foo() calls are not right next
to each other. But they may be triggered on an identical oid by two
references from different objects.
But when we do know why would we beat around the bush and say "was
referred to as X and Y" once we know what it is.
AFAICT there's no more reason to think that parse_object_buffer() will
be wrong about the type than "git cat-file -t" will be. They both use
the same underlying functions to get that information.
My point is that we are not always coming from parse_object_buffer()
when we see these error messages.
-Peff
On Wed, Mar 31, 2021 at 08:31:16PM +0200, Ævar Arnfjörð Bjarmason wrote:
quoted
quoted
Ævar's patch tries to improve the case where we might _know_ which is
correct (because we're actually parsing the object contents), but of
course it covers only a fraction of cases. I'm not really opposed to
that per se, but I probably wouldn't bother myself.
What fraction of cases? As far as I can tell it covers all cases where
we get this error.
If there is a case like what you're describing I haven't found it.
It would happen any time somebody calls lookup_foo() because they saw an
object referenced, but _doesn't_ parse it. And then somebody later calls
lookup_bar() in the same way. Neither of them consulted the actual
object database.
Try this with your patches:
-- >8 --
git init repo
cd repo
# just for making things deterministic
export GIT_COMMITTER_NAME='A U Thor'
export GIT_COMMITTER_EMAIL='author@example.com'
export GIT_COMMITTER_DATE='@1234567890 +0000'
blob=$(echo foo | git hash-object -w --stdin)
git tag -m 'tag of blob' tag-of-blob $blob
git update-ref refs/tags/tag-of-commit $(
git cat-file tag tag-of-blob |
sed s/blob/commit/g |
git hash-object -w --stdin -t tag
)
git update-ref refs/tags/tag-of-tree $(
git cat-file tag tag-of-blob |
sed s/blob/tree/g |
git hash-object -w --stdin -t tag
)
git fsck
-- >8 --
That fsck produces (257cc5642 is the blob):
error: object 257cc5642cb1a054f08cc83f2d943e56fd3ebe99 is a blob, not a commit
error: 257cc5642cb1a054f08cc83f2d943e56fd3ebe99: object could not be parsed: .git/objects/25/7cc5642cb1a054f08cc83f2d943e56fd3ebe99
error: object 257cc5642cb1a054f08cc83f2d943e56fd3ebe99 is a commit, not a tree
error: bad tag pointer to 257cc5642cb1a054f08cc83f2d943e56fd3ebe99 in aaff0d42df150e1a734f6a8516878b2ea315ee0a
error: aaff0d42df150e1a734f6a8516878b2ea315ee0a: object could not be parsed: .git/objects/aa/ff0d42df150e1a734f6a8516878b2ea315ee0a
error: object 257cc5642cb1a054f08cc83f2d943e56fd3ebe99 is a commit, not a blob
error: bad tag pointer to 257cc5642cb1a054f08cc83f2d943e56fd3ebe99 in bbd2b7077cd91ee6175cdc0e4c477c25c230cdc7
error: bbd2b7077cd91ee6175cdc0e4c477c25c230cdc7: object could not be parsed: .git/objects/bb/d2b7077cd91ee6175cdc0e4c477c25c230cdc7
So we claim "is X, not Y" in multiple directions for the same object.
It might just be that there are spots in the fsck code that need to be
adjusted to use your new function (if they are indeed parsing the
referred-to object). But there are lots of places that don't actually
parse the object at the moment they're parsing the tag. E.g.:
$ git for-each-ref --format='%(*objectname)'
error: object 257cc5642cb1a054f08cc83f2d943e56fd3ebe99 is a commit, not a tree
error: bad tag pointer to 257cc5642cb1a054f08cc83f2d943e56fd3ebe99 in aaff0d42df150e1a734f6a8516878b2ea315ee0a
Segmentation fault
Neither of those types is the correct one. And the segfault is just a
bonus! :)
I'd expect similar cases with parsing commit parents and tree pointers.
And probably tree entries whose modes are wrong.
So the segfault happens without my patches, but the change is that
before we'd always get it wrong and say "commit, not a tree", but now
we'll get it right some of the time. Patching the relevant object.c code
to emit different messages from the various functions shows that it's
the oid_is_type*() functions that get it right, but object_as_type() is
wrong as before.
So that's certainly something I missed.
But are there any cases where it makes things worse? Or is it just that
it's not a full fix in all cases, but only a partial one?
quoted
I.e. it happens when we have an un-parsed "struct object" whose type is
inferred, and parse it to find out it's not what we expected.
It's not ambigious at all what the object actually is. It's just that
the previous code was leaking the *assumption* about the type at the
time of emitting the error, due to an apparent oversight with parsed
v.s. non-parsed.
Or in other words, we're leaking the implementation detail that we
pre-allocated an object struct of a given type in anticipation of
holding a parsed version of that object soon.
Right. In the case that you are indeed parsing the object later, you can
say definitively "it is X in the odb, but seen as Y previously". But we
do not always hit the "is X, not Y" error when parsing the object. It
might be caused by two of these "pre-allocations" (though really I think
it is not just an implementation detail; the pre-allocation happened
because some other object referred to us as a given type, so it really
is a corruption in the repository. Just not in the object we mention).
Indeed, the goal is to emit a sensible message on-the-fly when we see
that corruption.
quoted
quoted
@@ -169,10 +169,16 @@ void *object_as_type(struct object *obj, enum object_type type, int quiet) return obj; } else {- if (!quiet)- error(_("object %s is a %s, not a %s"),- oid_to_hex(&obj->oid),- type_name(obj->type), type_name(type));+ if (!(flags & OBJECT_AS_TYPE_QUIET)) {+ if (flags & OBJECT_AS_TYPE_EXPECT_PARSED)+ error(_("object %s is a %s, but was referred to as a %s"),+ oid_to_hex(&obj->oid), type_name(obj->type),+ type_name(type));+ else+ error(_("object %s referred to as both a %s and a %s"),+ oid_to_hex(&obj->oid),+ type_name(obj->type), type_name(type));+ } return NULL; } }
Per the above I don't understand how you think there's any uncertainty
here.
If I'm right and there isn't then first of all I don't see how we could
emit 1/2 of those errors. The whole problem here is that we don't know
the type of the un-parsed object (and presumably don't want to eagerly
know, it would mean hitting the object store).
Forgetting for a moment how to trigger it with actual Git commands, the
root of the problem is that:
lookup_tree(&oid);
lookup_blob(&oid);
is going to produce an error message. But we cannot know which object
type is wrong and which is right (if any). So we'd want to produce the
"referred to as both" message.
_If_ the caller happens to know that it has just parsed the object
contents and got a tree, then it would call lookup_parsed_tree(&oid),
which would pass along OBJECT_AS_TYPE_EXPECT_PARSED, and produce the
other message.
In practice, of course those two lookup_foo() calls are not right next
to each other. But they may be triggered on an identical oid by two
references from different objects.
[...]
quoted
But when we do know why would we beat around the bush and say "was
referred to as X and Y" once we know what it is.
AFAICT there's no more reason to think that parse_object_buffer() will
be wrong about the type than "git cat-file -t" will be. They both use
the same underlying functions to get that information.
My point is that we are not always coming from parse_object_buffer()
when we see these error messages.
If my solution of relying on the parsed v.s. non-parsed shouldn't we
just devolve to a full object info lookup when emitting the error? It's
more expensive, but we're emitting an error anyway...
From: Jeff King <hidden> Date: 2021-04-01 07:55:43
On Wed, Mar 31, 2021 at 10:46:22PM +0200, Ævar Arnfjörð Bjarmason wrote:
quoted
Neither of those types is the correct one. And the segfault is just a
bonus! :)
I'd expect similar cases with parsing commit parents and tree pointers.
And probably tree entries whose modes are wrong.
So the segfault happens without my patches,
Yeah, sorry if that was unclear. It is definitely a pre-existing bug.
but the change is that
before we'd always get it wrong and say "commit, not a tree", but now
we'll get it right some of the time. Patching the relevant object.c code
to emit different messages from the various functions shows that it's
the oid_is_type*() functions that get it right, but object_as_type() is
wrong as before.
So that's certainly something I missed.
But are there any cases where it makes things worse? Or is it just that
it's not a full fix in all cases, but only a partial one?
Right, I don't think your patch is making anything worse. It's just that
it does not cover all cases where we see an object as two different
types. Nor can it, since it is relying on code paths that actually parse
the object, and not all of them do.
quoted
My point is that we are not always coming from parse_object_buffer()
when we see these error messages.
If my solution of relying on the parsed v.s. non-parsed shouldn't we
just devolve to a full object info lookup when emitting the error? It's
more expensive, but we're emitting an error anyway...
That's certainly one option (that I suggested earlier in [0]). If we go
that route, then we do not need any of this "the caller passes in an
extra bit to say that it is parsing the object, and it found a tree",
because the error routine in object_as_type() would consult the odb
itself.
But I still think it does not make the error messages fully useful. We
might say "object X is really a tree in the odb, but we previously saw
it as a commit". But we will still have to return NULL from
lookup_tree(), so whatever containing object referenced X, _even though
it has the correct type_, will be the one to propagate the failure up
the stack. It was whoever was responsible for that "previously saw" that
is actually corrupt, and we no longer know who that was.
Which is why I wonder if it is worth even bothering to put a lot of
effort in here. If the issue is just that "X is a foo, not a bar" is
sometimes misleading, then we could solve that by simply making the
message more precise ("we saw X as a foo and a bar; one of them is
wrong"). Even if we could know _which_ is wrong with respect to what's
in the object contents, it isn't all that helpful without being able to
tell the user which object reference was the one that led us to the
wrong conclusion.
-Peff
[0] https://lore.kernel.org/git/YGBHH7sAVsPpVKWd@coredump.intra.peff.net/
From: Jeff King <hidden> Date: 2021-04-01 08:33:34
On Thu, Apr 01, 2021 at 03:54:56AM -0400, Jeff King wrote:
On Wed, Mar 31, 2021 at 10:46:22PM +0200, Ævar Arnfjörð Bjarmason wrote:
quoted
quoted
Neither of those types is the correct one. And the segfault is just a
bonus! :)
I'd expect similar cases with parsing commit parents and tree pointers.
And probably tree entries whose modes are wrong.
So the segfault happens without my patches,
Yeah, sorry if that was unclear. It is definitely a pre-existing bug.
Here's a patch to fix it. This is mostly orthogonal to your patch
series. It happens to use a similar recipe to reproduce, but that is not
the only way to do it, and the fix and the test shouldn't conflict
textually or semantically.
-- >8 --
Subject: [PATCH] ref-filter: fix NULL check for parse object failure
After we run parse_object_buffer() to get an object's contents, we try
to check that the return value wasn't NULL. However, since our "struct
object" is a pointer-to-pointer, and we assign like:
*obj = parse_object_buffer(...);
it's not correct to check:
if (!obj)
That will always be true, since our double pointer will continue to
point to the single pointer (which is itself NULL). This is a regression
that was introduced by aa46a0da30 (ref-filter: use oid_object_info() to
get object, 2018-07-17); since that commit we'll segfault on a parse
failure, as we try to look at the NULL object pointer.
There are many ways a parse could fail, but most of them are hard to set
up in the tests (it's easy to make a bogus object, but update-ref will
refuse to point to it). The test here uses a tag which points to a wrong
object type. A parse of just the broken tag object will succeed, but
seeing both tag objects in the same process will lead to a parse error
(since we'll see the pointed-to object as both types).
Signed-off-by: Jeff King <redacted>
---
ref-filter.c | 2 +-
t/t6300-for-each-ref.sh | 10 ++++++++++
2 files changed, 11 insertions(+), 1 deletion(-)
@@ -1608,7 +1608,7 @@ static int get_object(struct ref_array_item *ref, int deref, struct object **objif(oi->info.contentp){*obj=parse_object_buffer(the_repository,&oi->oid,oi->type,oi->size,oi->content,&eaten);-if(!obj){+if(!*obj){if(!eaten)free(oi->content);returnstrbuf_addf_ret(err,-1,_("parse_object_buffer failed on %s for %s"),
From: Jeff King <redacted>
After we run parse_object_buffer() to get an object's contents, we try
to check that the return value wasn't NULL. However, since our "struct
object" is a pointer-to-pointer, and we assign like:
*obj = parse_object_buffer(...);
it's not correct to check:
if (!obj)
That will always be true, since our double pointer will continue to
point to the single pointer (which is itself NULL). This is a regression
that was introduced by aa46a0da30 (ref-filter: use oid_object_info() to
get object, 2018-07-17); since that commit we'll segfault on a parse
failure, as we try to look at the NULL object pointer.
There are many ways a parse could fail, but most of them are hard to set
up in the tests (it's easy to make a bogus object, but update-ref will
refuse to point to it).
A minimal stand-alone test can be found at, but let's use the newly
amended t3800-mktag.sh tests to test these cases exhaustively on all
sorts of bad tags.
1. http://lore.kernel.org/git/YGWFGMdGcKeaqCQF@coredump.intra.peff.net
Signed-off-by: Jeff King <redacted>
Signed-off-by: Ævar Arnfjörð Bjarmason <redacted>
---
ref-filter.c | 2 +-
t/t3800-mktag.sh | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
@@ -1608,7 +1608,7 @@ static int get_object(struct ref_array_item *ref, int deref, struct object **objif(oi->info.contentp){*obj=parse_object_buffer(the_repository,&oi->oid,oi->type,oi->size,oi->content,&eaten);-if(!obj){+if(!*obj){if(!eaten)free(oi->content);returnstrbuf_addf_ret(err,-1,_("parse_object_buffer failed on %s for %s"),
Add a test to check that "for-each-ref" fails on a repository with a
bad tag, this test intentionally uses "! " instead of "test_must_fail
" to hide a segfault. We'll fix the underlying bug in a subsequent
commit and convert it to "test_must_fail".
Signed-off-by: Ævar Arnfjörð Bjarmason <redacted>
---
t/t3800-mktag.sh | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
Change the mktag --no-strict test to actually test success under
--no-strict, that test was added in 06ce79152be (mktag: add a
--[no-]strict option, 2021-01-06).
It doesn't make sense to check that we have the same failure except
when we want --no-strict, by doing that we're assuming that the
behavior will be different under --no-strict, bun nothing was testing
for that.
We should instead assert that --strict is the same as --no-strict,
except in the cases where we've declared that it's not.
Signed-off-by: Ævar Arnfjörð Bjarmason <redacted>
---
t/t3800-mktag.sh | 2 ++
1 file changed, 2 insertions(+)
Change check_verify_failure() helper to parse out options from
$@. This makes it easier to add new options in the future. See
06ce79152be (mktag: add a --[no-]strict option, 2021-01-06) for the
initial implementation.
Let's also replace "" quotes with '' for the test body, the varables
we need are eval'd into the body, so there's no need for the quoting
confusion.
Signed-off-by: Ævar Arnfjörð Bjarmason <redacted>
---
t/t3800-mktag.sh | 43 +++++++++++++++++++++++++++++++------------
1 file changed, 31 insertions(+), 12 deletions(-)
@@ -12,15 +12,29 @@ test_description='git mktag: tag object verify test'# given in the expect.pat file. check_verify_failure(){-test_expect_success"$1""-test_must_failgitmktag<tag.sig2>message&&-grep'$2'message&&-iftest'$3'!='--no-strict'+subject=$1&&+message=$2&&+shift2&&++no_strict=&&+whiletest$#!=0+do+case"$1"in+--no-strict)+no_strict=yes+;;+esac&&+shift+done&&++test_expect_success"fail with [--[no-]strict]: $subject"'+test_must_failgitmktag<tag.sig2>err&&+iftest-z"$no_strict"then-test_must_failgitmktag--no-strict<tag.sig2>message.no-strict&&-grep'$2'message.no-strict+test_must_failgitmktag<tag.sig2>err2&&+test_cmperrerr2fi-"+'} test_expect_mktag_success(){
@@ -257,7 +272,8 @@ This is filler EOF check_verify_failure'"tagger" line label check #1'\-'^error:.* missingTaggerEntry:''--no-strict'+'^error:.* missingTaggerEntry:'\+--no-strict############################################################# 12. tagger line label check #2
@@ -272,7 +288,8 @@ This is filler EOF check_verify_failure'"tagger" line label check #2'\-'^error:.* missingTaggerEntry:''--no-strict'+'^error:.* missingTaggerEntry:'\+--no-strict############################################################# 13. allow missing tag author name like fsck
@@ -301,7 +318,8 @@ tagger T A Gger < EOF check_verify_failure'disallow malformed tagger'\-'^error:.* badEmail:''--no-strict'+'^error:.* badEmail:'\+--no-strict############################################################# 15. allow empty tag email
@@ -425,7 +443,8 @@ this line should not be here EOF check_verify_failure'detect invalid header entry'\-'^error:.* extraHeaderEntry:''--no-strict'+'^error:.* extraHeaderEntry:'\+--no-strict test_expect_success'invalid header entry config & fsck''test_must_failgitmktag<tag.sig&&
On Thu, Apr 01, 2021 at 03:54:56AM -0400, Jeff King wrote:
quoted
On Wed, Mar 31, 2021 at 10:46:22PM +0200, Ævar Arnfjörð Bjarmason wrote:
quoted
quoted
Neither of those types is the correct one. And the segfault is just a
bonus! :)
I'd expect similar cases with parsing commit parents and tree pointers.
And probably tree entries whose modes are wrong.
So the segfault happens without my patches,
Yeah, sorry if that was unclear. It is definitely a pre-existing bug.
Here's a patch to fix it. This is mostly orthogonal to your patch
series. It happens to use a similar recipe to reproduce, but that is not
the only way to do it, and the fix and the test shouldn't conflict
textually or semantically.
Here's a proposed v2. We test the same case, but I thought it made
sense to test this more exhaustively.
The v1 will also leave t6300 in a bad state for whoever adds the next
test, trivial to fix with a test_create_repo, but this seems better.
Jeff King (1):
ref-filter: fix NULL check for parse object failure
Ævar Arnfjörð Bjarmason (4):
mktag tests: parse out options in helper
mktag tests: invert --no-strict test
mktag tests: do fsck on failure
mktag tests: test for maybe segfaulting for-each-ref
ref-filter.c | 2 +-
t/t3800-mktag.sh | 90 +++++++++++++++++++++++++++++++++++++++---------
2 files changed, 75 insertions(+), 17 deletions(-)
Range-diff:
-: ----------- > 1: 45e0f100613 mktag tests: parse out options in helper
-: ----------- > 2: dd71740447d mktag tests: invert --no-strict test
-: ----------- > 3: 688d7456843 mktag tests: do fsck on failure
-: ----------- > 4: 403024b1cca mktag tests: test for maybe segfaulting for-each-ref
1: 9358541ce1f ! 5: 2ffe8f9fe3c ref-filter: fix NULL check for parse object failure
@@ Commit message
There are many ways a parse could fail, but most of them are hard to set
up in the tests (it's easy to make a bogus object, but update-ref will
- refuse to point to it). The test here uses a tag which points to a wrong
- object type. A parse of just the broken tag object will succeed, but
- seeing both tag objects in the same process will lead to a parse error
- (since we'll see the pointed-to object as both types).
+ refuse to point to it).
+
+ A minimal stand-alone test can be found at, but let's use the newly
+ amended t3800-mktag.sh tests to test these cases exhaustively on all
+ sorts of bad tags.
+
+ 1. http://lore.kernel.org/git/YGWFGMdGcKeaqCQF@coredump.intra.peff.net
Signed-off-by: Jeff King [off-list ref]
+ Signed-off-by: Ævar Arnfjörð Bjarmason [off-list ref]
## ref-filter.c ##
@@ ref-filter.c: static int get_object(struct ref_array_item *ref, int deref, struct object **obj
@@ ref-filter.c: static int get_object(struct ref_array_item *ref, int deref, struc
free(oi->content);
return strbuf_addf_ret(err, -1, _("parse_object_buffer failed on %s for %s"),
- ## t/t6300-for-each-ref.sh ##
-@@ t/t6300-for-each-ref.sh: test_expect_success 'for-each-ref --ignore-case works on multiple sort keys' '
- test_cmp expect actual
- '
+ ## t/t3800-mktag.sh ##
+@@ t/t3800-mktag.sh: check_verify_failure () {
+ git -C bad-tag for-each-ref "$tag_ref" >actual &&
+ test_cmp expected actual &&
+ # segfaults!
+- ! git -C bad-tag for-each-ref --format="%(*objectname)"
++ test_must_fail git -C bad-tag for-each-ref --format="%(*objectname)"
+ '
+ }
-+test_expect_success 'for-each-ref reports broken tags' '
-+ git tag -m "good tag" broken-tag-good HEAD &&
-+ git cat-file tag broken-tag-good >good &&
-+ sed s/commit/blob/ <good >bad &&
-+ bad=$(git hash-object -w -t tag bad) &&
-+ git update-ref refs/tags/broken-tag-bad $bad &&
-+ test_must_fail git for-each-ref --format="%(*objectname)" \
-+ refs/tags/broken-tag-*
-+'
-+
- test_done
--
2.31.1.474.g72d45d12706
Change the check_verify_failure() function to do an fsck of the bad
object on failure.
Due to how fsck works and walks the graph the failure will be
different if the object is reachable, so we might succeed before we've
created the ref, let's make sure we always fail after it's created.
Signed-off-by: Ævar Arnfjörð Bjarmason <redacted>
---
t/t3800-mktag.sh | 51 ++++++++++++++++++++++++++++++++++++++----------
1 file changed, 41 insertions(+), 10 deletions(-)
@@ -35,7 +40,25 @@ check_verify_failure () {test_cmperrerr2elsegitmktag--no-strict<tag.sig-fi+fi&&++test_when_finished"rm -rf bad-tag"&&+test_create_repobad-tag&&+bad_tag=$(git-Cbad-taghash-object-ttag-w--stdin--literally<tag.sig)&&+iftest-n"$fsck_obj_ok"+then+git-Cbad-tagfsck+else+test_must_failgit-Cbad-tagfsck>out2>err+fi&&++# Do update-ref anyway to see if it segfaults+tag_ref=refs/tags/bad_tag&&+test_might_failgit-Cbad-tagupdate-ref"$tag_ref""$bad_tag"&&+# The update-ref command itself might fail, but we are+# not testing that+echo"$bad_tag">"bad-tag/.git/$tag_ref"&&+test_must_failgit-Cbad-tagfsck'}
@@ -183,7 +206,8 @@ tagger . <> 0 +0000 EOF check_verify_failure'verify object (hash/type) check -- correct type, nonexisting object'\-'^fatal: could not read tagged object'+'^fatal: could not read tagged object'\+--fsck-obj-ok cat>tag.sig<<EOF object$head
@@ -275,7 +302,8 @@ EOF check_verify_failure'"tagger" line label check #1'\'^error:.* missingTaggerEntry:'\---no-strict+--no-strict\+--fsck-obj-ok############################################################# 12. tagger line label check #2
@@ -291,7 +319,8 @@ EOF check_verify_failure'"tagger" line label check #2'\'^error:.* missingTaggerEntry:'\---no-strict+--no-strict\+--fsck-obj-ok############################################################# 13. allow missing tag author name like fsck
From: Ramsay Jones <hidden> Date: 2021-04-01 19:20:15
On Thu, Apr 01, 2021 at 03:56:30PM +0200, Ævar Arnfjörð Bjarmason wrote:
From: Jeff King <redacted>
[snip]
A minimal stand-alone test can be found at, but let's use the newly
... can be found at, ... Hmm, missing test number?
ATB,
Ramsay Jones
amended t3800-mktag.sh tests to test these cases exhaustively on all
sorts of bad tags.
1. http://lore.kernel.org/git/YGWFGMdGcKeaqCQF@coredump.intra.peff.net
Signed-off-by: Jeff King <redacted>
Signed-off-by: Ævar Arnfjörð Bjarmason <redacted>
---
ref-filter.c | 2 +-
t/t3800-mktag.sh | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)