Re: [PATCH] rebase --autosquash: fix a potential segfault
- From
Johannes Schindelin <johannes.schindelin@gmx.de>
- Date
- May 8, 2020, 23:45 UTC
- Message-ID
- <nycvar.QRO.7.76.6.2005090112530.56@tvgsbejvaqbjf.bet>
- In-Reply-To
- <20200507191714.GA25306@coredump.intra.peff.net>
Hi Peff,
On Thu, 7 May 2020, Jeff King wrote:
Show 21 quoted lines
> On Wed, May 06, 2020 at 11:35:48PM +0200, Johannes Schindelin wrote: > > > > >> > + next[i] = next[i2]; > > > >> > + next[i2] = i; > > > >> > + continue; > > > >> > + } > > > >> > > > >> I do have one question, though. What happens if we add a second > > > >> fixup-of-a-fixup? > > > > > > > > Thanks for asking this question, I was a little curious about it, too. > > > > > > Interesting that three people looked at the same patch and asked the > > > same question in different ways ;-) > > > > Indeed! > > > > I am very grateful, as I had missed that, and it helped me figure out a > > better way to do it, and v2 looks a lot nicer, too. > > OK, so your v2 addresses that. Does that mean it was broken in v1?
Yes.
> If so, then why didn't my test reveal it?
Let's disect this:
i hash oneline #0 1234 foo #1 5678 !fixup foo #2 abcd !fixup 5678 #3 dbaf !fixup 5678
Let's follow the original code, i.e. before my v1:
When #1 is processed, i.e. when `i == 1`, it finds `i2 == 0` as target. So it sets `next[0]` as well as `tail[0]` to 1.
Then #2 is processed, i.e. `i == 2`, and it finds `i2 == 1` as target. It sets `next[1]` as well as `tail[1]` to 2.
Now #3 is processed, i.e. it also finds `i2 == 1` as target, so it looks at next[1], sees that it is already non-negative, so it sets `next[tail[1]]`, i.e. `next[2]` to 3. It also sets `tail[1]` to 3, but nobody cares about that because there is no further todo command.
Now, let's follow the code with my v1:
It actually does the same as before! Why, you ask? Because at no stage is there any non-negative `next[j]` whose corresponding `tail[j]` is negative. (Except after #3 was processed, at that stage, `next[2]` is non-negative but `tail[2]` still is negative, but as I said, noone cares because there are no remaining todo commands.)
So the crucial part to trigger this bug is to have a regular `fixup! <oneline>` _between_ the `fixup! <oneline>` and the `fixup! <hash>` targeting the latter. So I think I can modify your example accordingly:
1234 foo 5678 fixup! foo 90ab fixup! foo abcd fixup! 5678 dbaf fixup! 5678
Or using your actual shell commands:
git commit -m base --allow-empty git commit --squash HEAD -m 'this is the first squash' --allow-empty s=$(git rev-parse HEAD) git commit --fixup HEAD^ --allow-empty # This is the crucial command git commit -m "squash! $s" -m 'this is the second squash' --allow-empty git commit -m "squash! $s" -m 'this is the third squash' --allow-empty git rebase -ki --autosquash --root
Note the cricual command `git commit --fixup HEAD^`. When processing that, `i == 2` and `i2 == 0` (as for `i == 1`), and before v1, this would have set `next[1]` but `tail[0]`! With v1, this would have led to #4 and #5 being exchanged. With v2, the role of `tail` would have been extended to not only talk about the beginning of a fixup/squash chain, but about _any_ target of a fixup/squash, even if it is in the middle of a chain.
So why does this work? Why does it still do the right thing _even after_ inserting a fixup in the middle of a chain?
That's the beauty: if I insert anything in the middle of it, the `tail` of the actual beginning of the fixup/squash chain won't need to be changed. It still points to the end of that chain.
All I need to ensure is that item `i` is not just appended to the "chain" starting at `i2`, but that it is _inserted_ at the end of that chain in case that it is actually part of a larger chain, i.e. that its `next[i]` is set correctly before making it the immediate successor of the target commit. Since all of the elements in `next` and `tail` are initialized to `-1` (i.e. "no next fixup/squash item after this"), it will even do the right thing when it should actually append: it will set `next[i]` to `-1` in that case.
> I'm not really doubting that your v2 works so much as trying to > un-confuse myself about the whole situation (which in turn might lead to > a more intelligent review).
I wish I was quicker in my responses because I think that this is really helpful a conversation. By "forcing my hand" on a thorough explanation, you really help me get clarity for myself about the actual underlying issues. So even if I still think that v2 is correct after writing up the above explanation, the degree of my confidence increased substantially.
Thanks, Dscho