typo DWIMery with alias broken (cd to random dir)
Hi there,
with current next (989ee58 plus local additions) it seems that typo DWIMery with aliases is broken, see below.
It appears that the typo DWIMery is broken only when there is a unique automatic DWIM substitution for a mistyped alias.
I haven't bisected yet, but I suspect this to be related to recent changes regarding the environment in which commands/aliases are started (though this happens without extra work trees), so I'm cc'ing an expert in that area. Funny, though, that my user name shows up...
I think the reason is that git.c's handle_alias() (or something else) calls restore_env() multiple times, and restore_env frees orig_cwd such that subsequent restore_env(0) with external_alias=0 tries to cd to a random location.
I have no idea whether orig_cwd=0 after freeing or something else would be the proper fix.
Michael
LANG=C git sss
WARNING: You called a Git command named 'sss', which does not exist. Continuing under the assumption that you meant 'ss'
in 2.0 seconds automatically...
fatal: could not move to [email protected]: No such file or directory [mjg@skimbleshanks git]✗ LANG=C git ss
## HEAD (no branch)
?? a
?? a.patch
?? c2d.sh
[mjg@skimbleshanks git]✓ LANG=C git statu -sb
git: 'statu' is not a git command. See 'git --help'.
Did you mean one of these?
status
stage
stash
[mjg@skimbleshanks git]✗ LANG=C git statuss -sb
WARNING: You called a Git command named 'statuss', which does not exist. Continuing under the assumption that you meant 'status'
in 2.0 seconds automatically...
## HEAD (no branch)
?? a
?? a.patch
?? c2d.sh
PATCH 1/1 git.c: fix help.autocorrect after 57ea712 breaks it
Commit 57ea712 (git.c: make sure we do not leak GIT_* to alias scripts - 2015-12-20) does not realize that handle_alias() can be called multiple times because of the forever loop in run_argv(). The commit breaks alias chains.
Suppose you have an alias "abc" that resolves to another alias "def", which finally resolve to "git status". handle_alias() is called twice. save_env() and restore_env() are also called twice. But because of the check save_env_before_alias in save_env(), we save once while trying to restore twice. Consequences are left for the reader's imagination.
Fortunately, you cannot make an alias of another alias. At least not yet. Unfortunately it can still happen with help.autocorrect, where your alias typo is treated as the first "alias", and it can be resolved to the second alias. Then boom.
Make sure we call save_env() and restore_env() in pairs. While at there, set orig_cwd to NULL after freeing it for hygiene.
--- git.c | 11 ++++++++--- t/t1300-repo-config.sh | 8 ++++++++ 2 files changed, 16 insertions(+), 3 deletions(-)
git.c +8 -3
@@ -25,14 +25,15 @@ static const char *env_names[] = { GIT_PREFIX_ENVIRONMENT };static char *orig_env[4 ]; -static int saved_env_before_alias; +static int saved_env_before_alias, saved;static void save_env_before_alias (void ) {int i; -if (saved_env_before_alias) -return ; +if (saved) +die ("BUG: uneven pair of save_env/restore_env calls" ); saved_env_before_alias =1 ; + saved =1 ; orig_cwd =xgetcwd ();for (i =0 ; i <ARRAY_SIZE (env_names); i++) { orig_env[i] =getenv (env_names[i]);
@@ -44,9 +45,13 @@ static void save_env_before_alias(void)static void restore_env (int external_alias) {int i; +if (saved !=1 ) +die ("BUG: uneven pair of save_env/restore_env calls" );if (!external_alias && orig_cwd &&chdir (orig_cwd))die_errno ("could not move to %s" , orig_cwd);free (orig_cwd); + orig_cwd =NULL ; + saved =0 ;for (i =0 ; i <ARRAY_SIZE (env_names); i++) {if (external_alias && !strcmp (env_names[i],GIT_PREFIX_ENVIRONMENT ))
t/t1300-repo-config.sh +8 -0
@@ -1201,4 +1201,12 @@ test_expect_success POSIXPERM,PERL 'preserves existing permissions' ' "die q(badrename) if ((stat(q(.git/config)))[2] & 07777) != 0600" ' +test_expect_success 'autocorrect and save_env/restore_env' ' + git config alias.ss status && + git config help.autocorrect 1 && + git sss --porcelain | grep actual >actual && + echo "?? actual" >expected && + test_cmp expected actual +' + test_done
Signature · 1 line
2.7.0.288.g1d8ad15
Re: [PATCH] git.c: fix help.autocorrect after 57ea712 breaks it
Nguyễn Thái Ngọc Duy venit, vidit, dixit 26.01.2016 14:26:
Quoted 16 lines
Commit 57ea712 (
git.c: make sure we do not leak GIT_* to alias scripts - 2015-12-20) does not realize thathandle_alias()can be called multiple times because of the forever loop inrun_argv(). The commit breaks alias chains.Suppose you have an alias "abc" that resolves to another alias "def", which finally resolve to "git status".
handle_alias()is called twice.save_env()andrestore_env()are also called twice. But because of the checksave_env_before_aliasinsave_env(), we save once while trying to restore twice. Consequences are left for the reader's imagination.Fortunately, you cannot make an alias of another alias. At least not yet. Unfortunately it can still happen with help.autocorrect, where your alias typo is treated as the first "alias", and it can be resolved to the second alias. Then boom.
Quoted 6 lines
Make sure we call
save_env()andrestore_env()in pairs. While at there, setorig_cwdto NULL after freeing it for hygiene.Reported-by Michael J Gruber <[email protected]>Signed-off-by Nguyễn Thái Ngọc Duy <[email protected]>---
The patch fixes it for me, thanks!
Michael
Re: [PATCH] git.c: fix help.autocorrect after 57ea712 breaks it
Quoted 20 lines · Michael J Gruber
Nguyễn Thái Ngọc Duy venit, vidit, dixit 26.01.2016 14:26:
Quoted 19 lines
Commit 57ea712 (
git.c: make sure we do not leak GIT_* to alias scripts - 2015-12-20) does not realize thathandle_alias()can be called multiple times because of the forever loop inrun_argv(). The commit breaks alias chains.Suppose you have an alias "abc" that resolves to another alias "def", which finally resolve to "git status".
handle_alias()is called twice.save_env()andrestore_env()are also called twice. But because of the checksave_env_before_aliasinsave_env(), we save once while trying to restore twice. Consequences are left for the reader's imagination.Fortunately, you cannot make an alias of another alias. At least not yet. Unfortunately it can still happen with help.autocorrect, where your alias typo is treated as the first "alias", and it can be resolved to the second alias. Then boom.
Make sure we call
save_env()andrestore_env()in pairs. While at there, setorig_cwdto NULL after freeing it for hygiene.
Quoted 7 lines
Quoted 3 lines
Reported-by Michael J Gruber <[email protected]>Signed-off-by Nguyễn Thái Ngọc Duy <[email protected]>---
The patch fixes it for me, thanks!
Michael
Thanks for noticing, and thanks for describing the problem very clearly and fixing it quickly.
Re: [PATCH] git.c: fix help.autocorrect after 57ea712 breaks it
In reply to Nguyễn Thái Ngọc DuyQuoted 16 lines · Nguyễn Thái Ngọc Duy
Commit 57ea712 (
git.c: make sure we do not leak GIT_* to alias scripts - 2015-12-20) does not realize thathandle_alias()can be called multiple times because of the forever loop inrun_argv(). The commit breaks alias chains.Suppose you have an alias "abc" that resolves to another alias "def", which finally resolve to "git status".
handle_alias()is called twice.save_env()andrestore_env()are also called twice. But because of the checksave_env_before_aliasinsave_env(), we save once while trying to restore twice. Consequences are left for the reader's imagination.Fortunately, you cannot make an alias of another alias. At least not yet. Unfortunately it can still happen with help.autocorrect, where your alias typo is treated as the first "alias", and it can be resolved to the second alias. Then boom.
Quoted 6 lines
Make sure we call
save_env()andrestore_env()in pairs. While at there, setorig_cwdto NULL after freeing it for hygiene.Reported-by Michael J Gruber <[email protected]>Signed-off-by Nguyễn Thái Ngọc Duy <[email protected]>---
I spoke too soon, I am afraid.
The log message talks about "we save once while trying to restore twice" being bad, and the fix is to make sure we save and restore in pairs. Before or after the patch, however, there is no change in the callers of save_env_before_alias() and restore_env(), and it is unclear how that is ensured.
It turns out that the callers are doing the right thing (assuming that calling save and restore in pairs is the right solution), and the culprit is in the save_env_before_alias() function that returns without saving when we were called. That is mentioned in the log, but the description of last paragraph leaves the impression that it was callers that needed fixing, which is misleading.
After the patch, we have "saved" and "saved_env_before_alias".
- The former is a protection against committing similar programming errors in the future (i.e. glorified assert()). Can we have it in a separate commit, perhaps on top of the real fix, to make the change for the real fix easier to understand?
- Do we still need the latter? saved_env_before_alias is set to 1 if we called save_env_before_alias() even once, which happens always (and only) at the beginning of handle_alias(), so that is equivalent to saying "have we ever called handle_alias()?"
And there is only one caller of handle_alias, in run_argv(), and it maintains yet another variable "done_alias" to keep track of the same information.
The only code that cares about saved_env_before_alias is handle_builtin(), but it is a glorified no-op if saved_env_before_alias() is set. And the only caller of this handle_builtin(), for which saved_env_before_alias matters, is the same run_argv().
I wonder if this would be better done as a multi-part series that goes like this:
[1/3] git: remove an early return from save_env_before_alias()
whose description would be the first two paragraphs of your
patch, with two line removal, i.e.
diff --git a/git.c b/git.c
index 98d4412..a57a4cb 100644
--- a/git.c
+++ b/git.c
@@ -30,8 +30,6 @@ static int saved_env_before_alias;
static void save_env_before_alias(void)
{
int i;
- if (saved_env_before_alias)
- return;
saved_env_before_alias = 1;
orig_cwd = xgetcwd();
for (i = 0; i < ARRAY_SIZE(env_names); i++) {
that should be a sufficient "fix" to the issue.[2/3] git: protect against unbalanced calls to {save,restore}_env()
We made sure that save_env_before_alias() does not skip
saving the environment when asked to (which led to double
free of orig_cwd in restore_env()) with the previous step.
Protect against future breakage where somebody adds new
callers of these functions in an unbalanced fashion.
diff --git a/git.c b/git.c
index a57a4cb..e39b972 100644
--- a/git.c
+++ b/git.c
@@ -26,11 +26,15 @@ static const char *env_names[] = {
};
static char *orig_env[4];
static int saved_env_before_alias;
+static int save_restore_env_balance;
static void save_env_before_alias(void)
{
int i;
saved_env_before_alias = 1;
+
+ assert(save_restore_env_balance == 0);
+ save_restore_env_balance = 1;
orig_cwd = xgetcwd();
for (i = 0; i < ARRAY_SIZE(env_names); i++) {
orig_env[i] = getenv(env_names[i]);
@@ -42,6 +46,9 @@ static void save_env_before_alias(void)
static void restore_env(int external_alias)
{
int i;
+
+ assert(save_restore_env_balance == 1);
+ save_restore_env_balance = 0;
if (!external_alias && orig_cwd && chdir(orig_cwd))
die_errno("could not move to %s", orig_cwd);
free(orig_cwd);[3/3] git: simplify environment save/restore logic
The only code that cares about the value of the global variable saved_env_before_alias after the previous fix is handle_builtin() that turns into a glorified no-op when the variable is true, so the logic could safely be lifted to its caller, i.e. the caller can refrain from calling it when the variable is set.
This variable tells us if save_env_before_alias() was called (with or without matching restore_env()), but the sole caller of the function, handle_alias(), always calls it the first as thing, so it essentially keeps track of the fact that handle_alias() has ever been called.
It turns out that handle_builtin() and handle_alias() are called only from one function in a way that the value of the variable matters, which is run_argv(), and it already keeps track of the fact that it called handle_alias().
So we can simplify the whole thing by:
- Change handle_builtin() to always make a direct call to the builtin implementation it finds, and make sure the caller refrains from calling it if handle_alias() has ever been called;
- Remove saved_env_before_alias variable, and instead use the local "done_alias" variable maintained inside run_argv() to make the above decision.
[4/3] git: plug leaks of saved environments
I think orig_env[] members if save_env() is called twice are leaked, so they would need to be free'd just like orig_cwd is freed in restore_env(). Optionally, you can assign NULL to them and also to orig_cwd after freeing.
The patch text for [3/3] would look like this.
git.c | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-)
git.c +14 -15
@@ -25,13 +25,11 @@ static const char *env_names[] = { GIT_PREFIX_ENVIRONMENT };static char *orig_env[4 ]; -static int saved_env_before_alias;static int save_restore_env_balance;static void save_env_before_alias (void ) {int i; - saved_env_before_alias =1 ;assert (save_restore_env_balance ==0 ); save_restore_env_balance =1 ;
@@ -533,16 +531,8 @@ static void handle_builtin(int argc, const char **argv) } builtin =get_builtin (cmd); -if (builtin) { -/* -* XXX: if we can figure out cases where it is _safe_ -* to do, we can avoid spawning a new process when -* saved_env_before_alias is true -* (i.e. setup_git_dir* has been run once) -*/ -if (!saved_env_before_alias) -exit (run_builtin (builtin, argc, argv)); - } +if (builtin) +exit (run_builtin (builtin, argc, argv)); }static void execv_dashed_external (const char **argv)
@@ -586,8 +576,17 @@ static int run_argv(int *argcp, const char ***argv)int done_alias =0 ;while (1 ) { -/* See if it's a builtin */ -handle_builtin (*argcp, *argv); +/* +* If we tried alias and futzed with our environment, +* it no longer is safe to invoke builtins directly in +* general. We have to spawn them as dashed externals. +* +* NEEDSWORK: if we can figure out cases +* where it is safe to do, we can avoid spawning a new +* process. +*/ +if (!done_alias) +handle_builtin (*argcp, *argv);/* .. then try the external ones */ execv_dashed_external (*argv);
@@ -598,9 +597,9 @@ static int run_argv(int *argcp, const char ***argv)*/ if (done_alias)break ; + done_alias =1 ;if (!handle_alias (argcp, argv))break ; - done_alias =1 ; }return done_alias;
Re: [PATCH] git.c: fix help.autocorrect after 57ea712 breaks it
Quoted 5 lines · Junio C Hamano
I spoke too soon, I am afraid.
...
I wonder if this would be better done as a multi-part series that goes like this:
...
So here is the first of the three-patch series to replace it.
-- >8 --
From: Junio C Hamano <[email protected]>
Date: Tue, 26 Jan 2016 11:46:53 -0800
Subject: [PATCH 1/3] git: remove an early return from save_env_before_alias()
When help.autocorrect is in effect, an attempt to auto-execute an uniquely corrected result of a misspelled alias will result in an irrelevant error message. The codepath that causes this issue calls save_env_before_alias() and restore_env() as a pair in handle_alias(), and that happens twice. A global variable orig_cwd is allocated and holds the return value of getcwd() in save_env_before_alias(), which is used in restore_env() to go back to that directory and freed.
However, save_env_before_alias() is not prepared to be called twice. It returns early when it knows it has already been called, leaving orig_cwd undefined, which is then checked in the second call to restore_env(), and by that time, the memory that used to hold the contents of orig_cwd is either freed or reused to hold something else, and this is fed to chdir(), causing it to fail.
Fix this by making sure save_env() does do the saving when called.
--- git.c | 2 -- 1 file changed, 2 deletions(-)
git.c +0 -2
@@ -30,8 +30,6 @@ static int saved_env_before_alias;static void save_env_before_alias (void ) {int i; -if (saved_env_before_alias) -return ; saved_env_before_alias =1 ; orig_cwd =xgetcwd ();for (i =0 ; i <ARRAY_SIZE (env_names); i++) {
Signature · 1 line
2.7.0-366-g065efda
PATCH 2/3 git: protect against unbalanced calls to {save,restore}_env()
We made sure that save_env_before_alias() does not skip saving the environment when asked to (which led to double free of orig_cwd in restore_env()) with the previous step. Protect against future breakage where somebody adds new callers of these functions in an unbalanced fashion.
---
git.c | 7 +++++++ 1 file changed, 7 insertions(+)
git.c +7 -0
@@ -26,11 +26,15 @@ static const char *env_names[] = { };static char *orig_env[4 ];static int saved_env_before_alias; +static int save_restore_env_balance;static void save_env_before_alias (void ) {int i; saved_env_before_alias =1 ; + +assert (save_restore_env_balance ==0 ); + save_restore_env_balance =1 ; orig_cwd =xgetcwd ();for (i =0 ; i <ARRAY_SIZE (env_names); i++) { orig_env[i] =getenv (env_names[i]);
@@ -42,6 +46,9 @@ static void save_env_before_alias(void)static void restore_env (int external_alias) {int i; + +assert (save_restore_env_balance ==1 ); + save_restore_env_balance =0 ;if (!external_alias && orig_cwd &&chdir (orig_cwd))die_errno ("could not move to %s" , orig_cwd);free (orig_cwd);
Signature · 1 line
2.7.0-368-g4610598
PATCH v2 2/3 git: protect against unbalanced calls to {save,restore}_env()
We made sure that save_env_before_alias() does not skip saving the environment when asked to (which led to use-after-free of orig_cwd in restore_env() in the buggy version) with the previous step.
Protect against future breakage where somebody adds new callers of these functions in an unbalanced fashion.
--- git.c | 7 +++++++ 1 file changed, 7 insertions(+)
git.c +7 -0
@@ -26,11 +26,15 @@ static const char *env_names[] = { };static char *orig_env[4 ];static int saved_env_before_alias; +static int save_restore_env_balance;static void save_env_before_alias (void ) {int i; saved_env_before_alias =1 ; + +assert (save_restore_env_balance ==0 ); + save_restore_env_balance =1 ; orig_cwd =xgetcwd ();for (i =0 ; i <ARRAY_SIZE (env_names); i++) { orig_env[i] =getenv (env_names[i]);
@@ -42,6 +46,9 @@ static void save_env_before_alias(void)static void restore_env (int external_alias) {int i; + +assert (save_restore_env_balance ==1 ); + save_restore_env_balance =0 ;if (!external_alias && orig_cwd &&chdir (orig_cwd))die_errno ("could not move to %s" , orig_cwd);free (orig_cwd);
Signature · 1 line
2.7.0-368-gb6e04f9
PATCH 3/3 git: simplify environment save/restore logic
In reply to Junio C HamanoThe only code that cares about the value of the global variable saved_env_before_alias after the previous fix is handle_builtin() that turns into a glorified no-op when the variable is true, so the logic could safely be lifted to its caller, i.e. the caller can refrain from calling it when the variable is set.
This variable tells us if save_env_before_alias() was called (with or without matching restore_env()), but the sole caller of the function, handle_alias(), always calls it the first as thing, so it essentially keeps track of the fact that handle_alias() has ever been called.
It turns out that handle_builtin() and handle_alias() are called only from one function in a way that the value of the variable matters, which is run_argv(), and it already keeps track of the fact that it called handle_alias().
So we can simplify the whole thing by:
- Change handle_builtin() to always make a direct call to the builtin implementation it finds, and make sure the caller refrains from calling it if handle_alias() has ever been called;
- Remove saved_env_before_alias variable, and instead use the local "done_alias" variable maintained inside run_argv() to make the above decision.
---
* I do not mean to say that it is unnecessary to plug the leak, which should be the fourth patch in this three-patch series, but it should rather be obvious to implement, so I omitted from this series, which is primarily meant to be "how about doing it this way" illustration.
git.c | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-)
git.c +14 -15
@@ -25,13 +25,11 @@ static const char *env_names[] = { GIT_PREFIX_ENVIRONMENT };static char *orig_env[4 ]; -static int saved_env_before_alias;static int save_restore_env_balance;static void save_env_before_alias (void ) {int i; - saved_env_before_alias =1 ;assert (save_restore_env_balance ==0 ); save_restore_env_balance =1 ;
@@ -533,16 +531,8 @@ static void handle_builtin(int argc, const char **argv) } builtin =get_builtin (cmd); -if (builtin) { -/* -* XXX: if we can figure out cases where it is _safe_ -* to do, we can avoid spawning a new process when -* saved_env_before_alias is true -* (i.e. setup_git_dir* has been run once) -*/ -if (!saved_env_before_alias) -exit (run_builtin (builtin, argc, argv)); - } +if (builtin) +exit (run_builtin (builtin, argc, argv)); }static void execv_dashed_external (const char **argv)
@@ -586,8 +576,17 @@ static int run_argv(int *argcp, const char ***argv)int done_alias =0 ;while (1 ) { -/* See if it's a builtin */ -handle_builtin (*argcp, *argv); +/* +* If we tried alias and futzed with our environment, +* it no longer is safe to invoke builtins directly in +* general. We have to spawn them as dashed externals. +* +* NEEDSWORK: if we can figure out cases +* where it is safe to do, we can avoid spawning a new +* process. +*/ +if (!done_alias) +handle_builtin (*argcp, *argv);/* .. then try the external ones */ execv_dashed_external (*argv);
@@ -598,9 +597,9 @@ static int run_argv(int *argcp, const char ***argv)*/ if (done_alias)break ; + done_alias =1 ;if (!handle_alias (argcp, argv))break ; - done_alias =1 ; }return done_alias;
Signature · 1 line
2.7.0-368-g4610598
PATCH v2 3/3 git: simplify environment save/restore logic
The only code that cares about the value of the global variable saved_env_before_alias after the previous fix is handle_builtin() that turns into a glorified no-op when the variable is true, so the logic could safely be lifted to its caller, i.e. the caller can refrain from calling it when the variable is set.
This variable tells us if save_env_before_alias() was called (with or without matching restore_env()), but the sole caller of the function, handle_alias(), always calls it as the first thing, so we can consider that the variable essentially keeps track of the fact that handle_alias() has ever been called.
It turns out that handle_builtin() and handle_alias() are called only from one function in a way that the value of the variable matters, which is run_argv(), and it already keeps track of the fact that it already called handle_alias().
So we can simplify the whole thing by:
- Change handle_builtin() to always make a direct call to the builtin implementation it finds, and make sure the caller refrains from calling it if handle_alias() has ever been called;
- Remove saved_env_before_alias variable, and instead use the local "done_alias" variable maintained inside run_argv() to make the same decision.
---
* Patches 2 and 3 are with updated log messages; their patch text did not change.
git.c | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-)
git.c +14 -15
@@ -25,13 +25,11 @@ static const char *env_names[] = { GIT_PREFIX_ENVIRONMENT };static char *orig_env[4 ]; -static int saved_env_before_alias;static int save_restore_env_balance;static void save_env_before_alias (void ) {int i; - saved_env_before_alias =1 ;assert (save_restore_env_balance ==0 ); save_restore_env_balance =1 ;
@@ -533,16 +531,8 @@ static void handle_builtin(int argc, const char **argv) } builtin =get_builtin (cmd); -if (builtin) { -/* -* XXX: if we can figure out cases where it is _safe_ -* to do, we can avoid spawning a new process when -* saved_env_before_alias is true -* (i.e. setup_git_dir* has been run once) -*/ -if (!saved_env_before_alias) -exit (run_builtin (builtin, argc, argv)); - } +if (builtin) +exit (run_builtin (builtin, argc, argv)); }static void execv_dashed_external (const char **argv)
@@ -586,8 +576,17 @@ static int run_argv(int *argcp, const char ***argv)int done_alias =0 ;while (1 ) { -/* See if it's a builtin */ -handle_builtin (*argcp, *argv); +/* +* If we tried alias and futzed with our environment, +* it no longer is safe to invoke builtins directly in +* general. We have to spawn them as dashed externals. +* +* NEEDSWORK: if we can figure out cases +* where it is safe to do, we can avoid spawning a new +* process. +*/ +if (!done_alias) +handle_builtin (*argcp, *argv);/* .. then try the external ones */ execv_dashed_external (*argv);
@@ -598,9 +597,9 @@ static int run_argv(int *argcp, const char ***argv)*/ if (done_alias)break ; + done_alias =1 ;if (!handle_alias (argcp, argv))break ; - done_alias =1 ; }return done_alias;
Signature · 1 line
2.7.0-368-gb6e04f9
Re: [PATCH v2 3/3] git: simplify environment save/restore logic
git.c static int run_argv(int *argcp, const char ***argv) · line 605
Quoted 11 lines · Junio C Hamano
git.c +1
-1@@ -598,9 +597,9 @@ static int run_argv(int *argcp, const char ***argv)*/ if (done_alias)break ; + done_alias =1 ;if (!handle_alias (argcp, argv))break ; - done_alias =1 ; }return done_alias;
This hunk shouldn't be there; it changes the return value from this function and breaks the whole thing.
What I will push out as part of 'pu' will have this fixed.
Re: [PATCH] git.c: fix help.autocorrect after 57ea712 breaks it
In reply to Junio C HamanoQuoted 9 lines · Junio C Hamano
Quoted 5 lines · Junio C Hamano
I spoke too soon, I am afraid.
...
I wonder if this would be better done as a multi-part series that goes like this:
...So here is the first of the three-patch series to replace it.
This is much better (the whole series, not just the first patch). We probably should add a test about help.autocorrect though, maybe in the first patch, because it's not tested at all in the test suite.
Signature · 1 line
Duy
Re: [PATCH] git.c: fix help.autocorrect after 57ea712 breaks it
Quoted 9 lines · Duy Nguyen
Quoted 8 lines · Junio C Hamano
Quoted 5 lines · Junio C Hamano
I spoke too soon, I am afraid.
...
I wonder if this would be better done as a multi-part series that goes like this:
...
Quoted 5 lines
Quoted 1 line
So here is the first of the three-patch series to replace it.
This is much better (the whole series, not just the first patch). We probably should add a test about help.autocorrect though, maybe in the first patch, because it's not tested at all in the test suite.
Patches welcome. Thanks.
PATCH v2 1/3 git: remove an early return from save_env_before_alias()
Quoted 11 lines · Junio C Hamano
Quoted 9 lines · Duy Nguyen
Quoted 8 lines · Junio C Hamano
Quoted 5 lines · Junio C Hamano
I spoke too soon, I am afraid.
...
I wonder if this would be better done as a multi-part series that goes like this:
...
Quoted 7 lines
Quoted 5 lines
Quoted 1 line
So here is the first of the three-patch series to replace it.
This is much better (the whole series, not just the first patch). We probably should add a test about help.autocorrect though, maybe in the first patch, because it's not tested at all in the test suite.
Patches welcome. Thanks.
Here is an updated 1/3; this feature does not fit very well to any category (it is certainly not basic, or command that is primarily about objects or worktree; I just picked "Git tools" t9xxx as that is the closest thing to "direct end user support").
-- >8 --
When help.autocorrect is in effect, an attempt to auto-execute an uniquely corrected result of a misspelt alias will result in an irrelevant error message. The codepath that causes this calls save_env_before_alias() and restore_env() in handle_alias(), and that happens twice. A global variable orig_cwd is allocated to hold the return value of getcwd() in save_env_before_alias(), which is then used in restore_env() to go back to that directory and finally free(3)'d there.
However, save_env_before_alias() is not prepared to be called twice. It returns early when it knows it has already been called, leaving orig_cwd undefined, which is then checked in the second call to restore_env(), and by that time, the memory that used to hold the contents of orig_cwd is either freed or reused to hold something else, and this is fed to chdir(2), causing it to fail. Even if it did not fail (i.e. reading of the already free'd piece of memory yielded a directory path that we can chdir(2) to), it then gets free(3)'d.
Fix this by making sure save_env() does do the saving when called.
While at it, add a minimal test for help.autocorrect facility.
--- git.c | 2 -- t/t9003-help-autocorrect.sh | 52 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 2 deletions(-) create mode 100755 t/t9003-help-autocorrect.sh
git.c +0 -2
@@ -30,8 +30,6 @@ static int saved_env_before_alias;static void save_env_before_alias (void ) {int i; -if (saved_env_before_alias) -return ; saved_env_before_alias =1 ; orig_cwd =xgetcwd ();for (i =0 ; i <ARRAY_SIZE (env_names); i++) {
t/t9003-help-autocorrect.sh new file +52 -0
52 lines not on this page · raw message
Signature · 1 line
2.7.0-368-gb6e04f9
PATCH v3 1/4 git: remove an early return from save_env_before_alias()
When help.autocorrect is in effect, an attempt to auto-execute an uniquely corrected result of a misspelt alias will result in an irrelevant error message. The codepath that causes this calls save_env_before_alias() and restore_env() in handle_alias(), and that happens twice. A global variable orig_cwd is allocated to hold the return value of getcwd() in save_env_before_alias(), which is then used in restore_env() to go back to that directory and finally free(3)'d there.
However, save_env_before_alias() is not prepared to be called twice. It returns early when it knows it has already been called, leaving orig_cwd undefined, which is then checked in the second call to restore_env(), and by that time, the memory that used to hold the contents of orig_cwd is either freed or reused to hold something else, and this is fed to chdir(2), causing it to fail. Even if it did not fail (i.e. reading of the already free'd piece of memory yielded a directory path that we can chdir(2) to), it then gets free(3)'d.
Fix this by making sure save_env() does do the saving when called.
While at it, add a minimal test for help.autocorrect facility.
---
* For another review round before deciding what to write in the upcoming "What's cooking" report.
git.c | 2 -- t/t9003-help-autocorrect.sh | 52 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 2 deletions(-) create mode 100755 t/t9003-help-autocorrect.sh
git.c +0 -2
@@ -30,8 +30,6 @@ static int saved_env_before_alias;static void save_env_before_alias (void ) {int i; -if (saved_env_before_alias) -return ; saved_env_before_alias =1 ; orig_cwd =xgetcwd ();for (i =0 ; i <ARRAY_SIZE (env_names); i++) {
t/t9003-help-autocorrect.sh new file +52 -0
52 lines not on this page · raw message
Signature · 1 line
2.7.0-391-gcd29568
PATCH v3 2/4 git: protect against unbalanced calls to {save,restore}_env()
In reply to Junio C HamanoWe made sure that save_env_before_alias() does not skip saving the environment when asked to (which led to use-after-free of orig_cwd in restore_env() in the buggy version) with the previous step.
Protect against future breakage where somebody adds new callers of these functions in an unbalanced fashion.
--- git.c | 7 +++++++ 1 file changed, 7 insertions(+)
git.c +7 -0
@@ -26,11 +26,15 @@ static const char *env_names[] = { };static char *orig_env[4 ];static int saved_env_before_alias; +static int save_restore_env_balance;static void save_env_before_alias (void ) {int i; saved_env_before_alias =1 ; + +assert (save_restore_env_balance ==0 ); + save_restore_env_balance =1 ; orig_cwd =xgetcwd ();for (i =0 ; i <ARRAY_SIZE (env_names); i++) { orig_env[i] =getenv (env_names[i]);
@@ -42,6 +46,9 @@ static void save_env_before_alias(void)static void restore_env (int external_alias) {int i; + +assert (save_restore_env_balance ==1 ); + save_restore_env_balance =0 ;if (!external_alias && orig_cwd &&chdir (orig_cwd))die_errno ("could not move to %s" , orig_cwd);free (orig_cwd);
Signature · 1 line
2.7.0-391-gcd29568
PATCH v3 3/4 git: simplify environment save/restore logic
In reply to Junio C HamanoThe only code that cares about the value of the global variable saved_env_before_alias after the previous fix is handle_builtin() that turns into a glorified no-op when the variable is true, so the logic could safely be lifted to its caller, i.e. the caller can refrain from calling it when the variable is set.
This variable tells us if save_env_before_alias() was called (with or without matching restore_env()), but the sole caller of the function, handle_alias(), always calls it as the first thing, so we can consider that the variable essentially keeps track of the fact that handle_alias() has ever been called.
It turns out that handle_builtin() and handle_alias() are called only from one function in a way that the value of the variable matters, which is run_argv(), and it already keeps track of the fact that it already called handle_alias().
So we can simplify the whole thing by:
- Change handle_builtin() to always make a direct call to the builtin implementation it finds, and make sure the caller refrains from calling it if handle_alias() has ever been called;
- Remove saved_env_before_alias variable, and instead use the local "done_alias" variable maintained inside run_argv() to make the same decision.
--- git.c | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-)
git.c +13 -14
@@ -25,13 +25,11 @@ static const char *env_names[] = { GIT_PREFIX_ENVIRONMENT };static char *orig_env[4 ]; -static int saved_env_before_alias;static int save_restore_env_balance;static void save_env_before_alias (void ) {int i; - saved_env_before_alias =1 ;assert (save_restore_env_balance ==0 ); save_restore_env_balance =1 ;
@@ -533,16 +531,8 @@ static void handle_builtin(int argc, const char **argv) } builtin =get_builtin (cmd); -if (builtin) { -/* -* XXX: if we can figure out cases where it is _safe_ -* to do, we can avoid spawning a new process when -* saved_env_before_alias is true -* (i.e. setup_git_dir* has been run once) -*/ -if (!saved_env_before_alias) -exit (run_builtin (builtin, argc, argv)); - } +if (builtin) +exit (run_builtin (builtin, argc, argv)); }static void execv_dashed_external (const char **argv)
@@ -586,8 +576,17 @@ static int run_argv(int *argcp, const char ***argv)int done_alias =0 ;while (1 ) { -/* See if it's a builtin */ -handle_builtin (*argcp, *argv); +/* +* If we tried alias and futzed with our environment, +* it no longer is safe to invoke builtins directly in +* general. We have to spawn them as dashed externals. +* +* NEEDSWORK: if we can figure out cases +* where it is safe to do, we can avoid spawning a new +* process. +*/ +if (!done_alias) +handle_builtin (*argcp, *argv);/* .. then try the external ones */ execv_dashed_external (*argv);
Signature · 1 line
2.7.0-391-gcd29568
PATCH v3 4/4 restore_env(): free the saved environment variable once we are done
In reply to Junio C HamanoJust like we free orig_cwd, which is the value of the original working directory saved in save_env_before_alias(), once we are done with it, the contents of orig_env[] array, saved in the save_env_before_alias() function should be freed; otherwise, the second and subsequent calls to save/restore pair will leak the memory allocated in save_env_before_alias().
---
* This is new.
git.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-)
git.c +4 -2
@@ -54,10 +54,12 @@ static void restore_env(int external_alias)if (external_alias && !strcmp (env_names[i],GIT_PREFIX_ENVIRONMENT ))continue ; -if (orig_env[i]) +if (orig_env[i]) {setenv (env_names[i], orig_env[i],1 ); -else +free (orig_env[i]); + }else {unsetenv (env_names[i]); + } } }
Signature · 1 line
2.7.0-391-gcd29568
Re: [PATCH v3 4/4] restore_env(): free the saved environment variable once we are done
Quoted 11 lines · Junio C Hamano
Just like we free
orig_cwd, which is the value of the original working directory saved insave_env_before_alias(), once we are done with it, the contents of orig_env[] array, saved in thesave_env_before_alias()function should be freed; otherwise, the second and subsequent calls to save/restore pair will leak the memory allocated insave_env_before_alias().Signed-off-by Junio C Hamano <[email protected]>---
git.c static void restore_env(int external_alias) · line 59
Quoted 8 lines
git.c +2
-2@@ -54,10 +54,12 @@ static void restore_env(int external_alias)if (external_alias && !strcmp (env_names[i],GIT_PREFIX_ENVIRONMENT ))continue ; -if (orig_env[i]) +if (orig_env[i]) {setenv (env_names[i], orig_env[i],1 ); -else +free (orig_env[i]);
Now that this is "well-protected"[1] against incorrect nesting, you don't worry about the dangling pointers in static orig_env[], right? (The same for the dangling pointer in static 'orig_cwd' after being freed a bit earlier in this function, correct?)
[1]: via assert()
Quoted 5 lines
git.c +2
-0+ }else {unsetenv (env_names[i]); + } } }
Re: [PATCH v3 4/4] restore_env(): free the saved environment variable once we are done
Quoted 7 lines · Eric Sunshine
Quoted 7 lines
git.c +1
-1@@ -54,10 +54,12 @@ static void restore_env(int external_alias)if (external_alias && !strcmp (env_names[i],GIT_PREFIX_ENVIRONMENT ))continue ; -if (orig_env[i]) +if (orig_env[i]) {
Quoted 8 lines
git.c static void restore_env(int external_alias) · line 59
Quoted 3 lines
git.c +1
-1setenv (env_names[i], orig_env[i],1 ); - else +free (orig_env[i]);Now that this is "well-protected"[1] against incorrect nesting, you don't worry about the dangling pointers in static orig_env[], right? (The same for the dangling pointer in static '
orig_cwd' after being freed a bit earlier in this function, correct?)
Correct.
I do not think we follow a style that requires "after freeing memory pointed at by a variable, the variable must be assigned NULL". Would it be a good idea? I do not see a point in it.
Thanks.
Re: [PATCH] git.c: fix help.autocorrect after 57ea712 breaks it
In reply to Nguyễn Thái Ngọc Duygit.c static const char *env_names[] = { · line 28
Quoted 10 lines · Nguyễn Thái Ngọc Duy
git.c +1
-1@@ -25,14 +25,15 @@ static const char *env_names[] = { GIT_PREFIX_ENVIRONMENT };static char *orig_env[4 ]; -static int saved_env_before_alias; +static int saved_env_before_alias, saved;
Even for a file-local static, this name is a bit too generic, isn't it? saved_env_count or something perhaps?
Quoted 43 lines
git.c +7
-2static void save_env_before_alias (void ) {int i; -if (saved_env_before_alias) -return ; +if (saved) +die ("BUG: uneven pair of save_env/restore_env calls" ); saved_env_before_alias =1 ; + saved =1 ; orig_cwd =xgetcwd ();for (i =0 ; i <ARRAY_SIZE (env_names); i++) { orig_env[i] =getenv (env_names[i]);@@ -44,9 +45,13 @@ static void save_env_before_alias(void)static void restore_env (int external_alias) {int i; +if (saved !=1 ) +die ("BUG: uneven pair of save_env/restore_env calls" );if (!external_alias && orig_cwd &&chdir (orig_cwd))die_errno ("could not move to %s" , orig_cwd);free (orig_cwd); + orig_cwd =NULL ; + saved =0 ;for (i =0 ; i <ARRAY_SIZE (env_names); i++) {if (external_alias && !strcmp (env_names[i],GIT_PREFIX_ENVIRONMENT ))t/t1300-repo-config.sh +8
-0@@ -1201,4 +1201,12 @@ test_expect_success POSIXPERM,PERL 'preserves existing permissions' ' "die q(badrename) if ((stat(q(.git/config)))[2] & 07777) != 0600" ' +test_expect_success 'autocorrect and save_env/restore_env' ' + git config alias.ss status && + git config help.autocorrect 1 && + git sss --porcelain | grep actual >actual && + echo "?? actual" >expected && + test_cmp expected actual +' + test_done
Quoted 13 lines · Michael J Gruber
Hi there,
with current next (989ee58 plus local additions) it seems that typo DWIMery with aliases is broken, see below.
It appears that the typo DWIMery is broken only when there is a unique automatic DWIM substitution for a mistyped alias.
I haven't bisected yet, but I suspect this to be related to recent changes regarding the environment in which commands/aliases are started (though this happens without extra work trees), so I'm cc'ing an expert in that area. Funny, though, that my user name shows up...
Quoted 7 lines
I think the reason is that git.c's
handle_alias()(or something else) callsrestore_env()multiple times, andrestore_envfreesorig_cwdsuch that subsequentrestore_env(0)with external_alias=0 tries to cd to a random location.I have no idea whether orig_cwd=0 after freeing or something else would be the proper fix.
I think the key is to reset saved_env_before_alias to zero in restore_env(). With that flag remains set, the second save_env turns no-op. orig_cwd should be reset too, but that's minor. It's working for me. I'll send a patch later.
Signature · 1 line
Duy