1
0
Fork 0
mirror of git://git.code.sf.net/p/cdesktopenv/code synced 2025-02-13 03:32:24 +00:00

Fix multiple bugs when using 'alias -p' to print aliases (#398)

This commit was originally intended to fix just one bug with shcomp's
handling of 'alias -p', but while fixing that I found a large number
of related issues in the alias command's -p, -t and -x options. The
current patch provides bugfixes for all of the bugs listed below:

1) Listing aliases in a script with 'alias -p' or 'alias' broke
   shcomp's bytecode output:
   https://github.com/ksh93/ksh/issues/87#issuecomment-813819122

2) Listing individual aliases with the -p option doesn't work:
      $ alias foo=bar bar=foo
      $ alias foo
      foo=bar
      $ alias -p foo  # No output

3) Listing specific tracked aliases with -pt does not display them
   in a reusable format, but rather adds another tracked alias:
      $ hash -r cat vi
      $ alias -pt vi  # No output
      $ alias -pt rm
      $ alias -t
      cat=/usr/bin/cat
      rm=/usr/bin/rm
      vi=/usr/bin/vi

4) Listing all tracked aliases with -pt does not output them in a
   reusable format (the resulting command printed only creates a
   normal alias, which is different from a tracked alias):
      $ hash -r cat
      $ alias -pt
      alias cat=/usr/bin/cat  # Expected 'alias -t cat'

5) Listing a non-existent alias with -p doesn't cause an error:
      $ unalias -a
      $ alias -p notanalias  # No output
      $ echo $?
      0
      $ alias notanalias
      notanalias: alias not found
      $ echo $?
      1
      $ hash -r
      $ alias -pt notacommand  # No output
      $ echo $?
      0

6) Attempting to list 256 non-existent aliases results in exit
   status zero:
      $ unalias -a
      $ alias $(awk -v ORS= 'BEGIN { for(i=0;i<256;i++) print "x "; }')
      x: alias not found
      --cut error message--
      $ echo $?
      0

Changes:

- typeset.c: Avoid printing anything while shcomp is compiling a
  script. This is needed because the alias command is run by shcomp
  to prevent parsing issues.

- b_alias(): To avoid adding tracked aliases with -pt, set
  tdata.aflag to '+' so that setall() and other related functions
  only list tracked aliases.

- b_alias(): Set tdata.pflag to 1 so that setall() and other
  functions recognize -p was passed.

- print_value(): Add support for listing specific aliases with
  'alias -p'.

- setall(): To avoid any issues with zombie tracked aliases (see also
  the regression tests) ignore tracked alias nodes marked with the
  NV_NOALIAS attribute. This bit is set for tracked alias nodes by
  the nv_rehash() function.

- setall(): For backward compatibility, continue incrementing the
  exit status for each invalid alias and tracked alias passed. This
  was already how alias behaved when listing aliases without -p, so
  using -p shouldn't cause a change in behavior:
      $ unalias -a
      $ alias foo bar
      foo: alias not found
      bar: alias not found
      $ echo $?
      2
  To fix bug 6, the exit status is set to one if an enforced 8-bit
  exit status would be zero.

- print_namval(): Set the prefix to 'alias -t' so that listing
  tracked aliases with 'alias -pt' works correctly.

- data/msg.c and include/name.h: Add an error message for when
  'alias -pt' doesn't find a tracked alias.

- tests/alias.sh: Add a ton of regression tests for the bugs fixed in
  this commit.
This commit is contained in:
Johnothan King 2021-12-26 16:25:05 -08:00 committed by Martijn Dekker
parent feeb62d15f
commit f7213f03a2
8 changed files with 245 additions and 10 deletions

24
NEWS
View file

@ -3,6 +3,30 @@ For full details, see the git log at: https://github.com/ksh93/ksh
Any uppercase BUG_* names are modernish shell bug IDs.
2021-12-26:
- Listing aliases or tracked aliases in a script no longer corrupts
shcomp's generated bytecode.
- Listing specific aliases with 'alias -p' and specific tracked aliases
with 'alias -pt' now works as documented. This means that the following
string of commands now works as you would expect:
$ hash -r; unalias -a
$ alias foo=bar; hash cat
$ alias -p foo; alias -pt cat
alias foo=bar
alias -t cat
- As a result of the above fix, listing all tracked aliases with 'alias -pt'
now prints commands that can be reused to recreate the tracked aliases.
- Attempting to list a non-existent alias or tracked alias with the -p option
now causes an error and sets the exit status to the number of non-existent
aliases passed.
- Attempting to list 256 non-existent aliases now errors out with the exit
status set to one.
2021-12-22:
- Process substitutions run in a profile script no longer print their

View file

@ -132,7 +132,7 @@ int b_alias(int argc,register char *argv[],Shbltin_t *context)
{
register unsigned flag = NV_NOARRAY|NV_NOSCOPE|NV_ASSIGN;
register Dt_t *troot;
register int rflag=0, n;
register int rflag=0, xflag=0, n;
struct tdata tdata;
NOT_USED(argc);
memset((void*)&tdata,0,sizeof(tdata));
@ -151,20 +151,26 @@ int b_alias(int argc,register char *argv[],Shbltin_t *context)
{
case 'p':
tdata.prefix = argv[0];
tdata.pflag = 1;
break;
case 't':
flag |= NV_TAGGED;
break;
case 'x':
/* obsolete, ignored */
xflag = 1;
break;
case 'r':
rflag=1;
break;
case ':':
if(sh.shcomp)
return(2); /* don't print usage info while shcomp is compiling */
errormsg(SH_DICT,2, "%s", opt_info.arg);
break;
case '?':
if(sh.shcomp)
return(2);
errormsg(SH_DICT,ERROR_usage(0), "%s", opt_info.arg);
return(2);
}
@ -179,12 +185,17 @@ int b_alias(int argc,register char *argv[],Shbltin_t *context)
if(flag&NV_TAGGED)
{
troot = sh_subtracktree(1); /* use hash table */
tdata.aflag = '-'; /* make setall() treat 'hash' like 'alias -t' */
if(tdata.pflag)
tdata.aflag = '+'; /* for 'alias -pt', don't add anything to the hash table */
else
tdata.aflag = '-'; /* make setall() treat 'hash' like 'alias -t' */
if(rflag) /* hash -r: clear hash table */
nv_scan(troot,nv_rehash,(void*)0,NV_TAGGED,NV_TAGGED);
}
else if(argv[1] && tdata.sh->subshell && !tdata.sh->subshare)
sh_subfork(); /* avoid affecting the parent shell's alias table */
if(xflag && (flag&NV_TAGGED))
return(0); /* do nothing for 'alias -tx' */
return(setall(argv,flag,troot,&tdata));
}
@ -597,6 +608,8 @@ static void print_value(Sfio_t *iop, Namval_t *np, struct tdata *tp)
sfwrite(iop,"}\n",2);
return;
}
if(tp->prefix && *tp->prefix=='a' && !nv_isattr(np,NV_TAGGED))
sfprintf(iop,"%s ", tp->prefix);
table = tp->sh->last_table;
sfputr(iop,nv_name(np),aflag=='+'?'\n':'=');
tp->sh->last_table = table;
@ -736,8 +749,16 @@ static int setall(char **argv,register int flag,Dt_t *troot,struct tdata *tp
continue;
}
np = nv_open(name,troot,nvflags|((nvflags&NV_ASSIGN)?0:NV_ARRAY)|((iarray|(nvflags&(NV_REF|NV_NOADD)==NV_REF))?NV_FARRAY:0));
if(!np)
if(!np || (troot==sh.track_tree && nv_isattr(np,NV_NOALIAS)))
{
if(troot==sh.alias_tree || troot==sh.track_tree)
{
if(!sh.shcomp)
sfprintf(sfstderr,sh_translate(troot==sh.alias_tree ? e_noalias: e_notrackedalias),name);
r++;
}
continue;
}
if(np->nvflag&NV_RDONLY && !tp->pflag
&& (flag & ~(NV_ASSIGN|NV_RDONLY|NV_EXPORT))) /* allow readonly/export on readonly vars */
{
@ -775,7 +796,8 @@ static int setall(char **argv,register int flag,Dt_t *troot,struct tdata *tp
{
if(troot!=shp->var_tree && (nv_isnull(np) || !print_namval(sfstdout,np,0,tp)))
{
sfprintf(sfstderr,sh_translate(e_noalias),name);
if(!sh.shcomp)
sfprintf(sfstderr,sh_translate(e_noalias),name);
r++;
}
if(!comvar && !iarray)
@ -979,6 +1001,14 @@ static int setall(char **argv,register int flag,Dt_t *troot,struct tdata *tp
print_all(sfstdout,troot,tp);
sfsync(sfstdout);
}
/* This is to handle cases where more than 255 non-existent
aliases were passed to the alias command. */
if(r>255)
{
r &= SH_EXITMASK;
if(r==0)
r = 1; /* ensure the exit status is at least 1 */
}
return(r);
}
@ -1404,6 +1434,8 @@ static int print_namval(Sfio_t *file,register Namval_t *np,register int flag, st
flag = '\n';
if(tp->noref && nv_isref(np))
return(0);
if(sh.shcomp)
return(1); /* print nothing while shcomp is compiling */
if(nv_isattr(np,NV_NOPRINT|NV_INTEGER)==NV_NOPRINT)
{
if(is_abuiltin(np))
@ -1418,7 +1450,9 @@ static int print_namval(Sfio_t *file,register Namval_t *np,register int flag, st
isfun = is_afunction(np);
if(tp->prefix)
{
outname = (*tp->prefix=='t' && (!nv_isnull(np) || nv_isattr(np,NV_FLOAT|NV_RDONLY|NV_BINARY|NV_RJUST|NV_NOPRINT)));
outname = (*tp->prefix=='t' && (!nv_isnull(np) || nv_isattr(np,NV_FLOAT|NV_RDONLY|NV_BINARY|NV_RJUST|NV_NOPRINT)));
if(tp->scanroot==sh.track_tree && *tp->prefix=='a')
tp->prefix = "alias -t";
if(indent && (isfun || outname || *tp->prefix!='t'))
{
sfnputc(file,'\t',indent);
@ -1427,7 +1461,7 @@ static int print_namval(Sfio_t *file,register Namval_t *np,register int flag, st
if(!isfun)
{
if(*tp->prefix=='t')
nv_attribute(np,tp->outfile,tp->prefix,tp->aflag);
nv_attribute(np,tp->outfile,tp->prefix,tp->aflag);
else
sfputr(file,tp->prefix,' ');
}

View file

@ -336,7 +336,7 @@ const char sh_optcont[] =
const char sh_optalarm[] = "r [varname seconds]";
const char sh_optalias[] =
"[-1c?\n@(#)$Id: alias (ksh 93u+m) 2020-06-10 $\n]"
"[-1c?\n@(#)$Id: alias (ksh 93u+m) 2021-12-26 $\n]"
"[--catalog?" SH_DICT "]"
"[+NAME?alias - define or display aliases]"
"[+DESCRIPTION?\balias\b creates or redefines alias definitions "
@ -363,7 +363,9 @@ const char sh_optalias[] =
"[t?Each \aname\a is looked up as a command in \b$PATH\b and its path is "
"added to the hash table as a 'tracked alias'. If no \aname\a is "
"given, this prints the hash table. See \bhash(1)\b.]"
"[x?Ignored, this option is obsolete.]"
"[x?This option is obsolete. In most contexts the \b-x\b option is ignored, "
"although when it's combined with \b-t\b it will make \balias\b do "
"nothing.]"
"\n"
"\n[name[=value]...]\n"
"\n"

View file

@ -115,6 +115,7 @@ const char e_nounattr[] = "cannot unset attribute C or A or a";
const char e_selfref[] = "%s: invalid self reference";
const char e_globalref[] = "%s: global reference cannot refer to local variable";
const char e_noalias[] = "%s: alias not found\n";
const char e_notrackedalias[] = "%s: tracked alias not found\n";
const char e_format[] = "%s: bad format";
const char e_redef[] = "%s: type cannot be redefined";
const char e_required[] = "%s: is a required element of %s";

View file

@ -242,6 +242,7 @@ extern const char e_restricted[];
extern const char e_ident[];
extern const char e_varname[];
extern const char e_noalias[];
extern const char e_notrackedalias[];
extern const char e_noarray[];
extern const char e_notenum[];
extern const char e_nounattr[];

View file

@ -21,7 +21,7 @@
#define SH_RELEASE_FORK "93u+m" /* only change if you develop a new ksh93 fork */
#define SH_RELEASE_SVER "1.0.0-beta.2" /* semantic version number: https://semver.org */
#define SH_RELEASE_DATE "2021-12-22" /* must be in this format for $((.sh.version)) */
#define SH_RELEASE_DATE "2021-12-26" /* must be in this format for $((.sh.version)) */
#define SH_RELEASE_CPYR "(c) 2020-2021 Contributors to ksh " SH_RELEASE_FORK
/* Scripts sometimes field-split ${.sh.version}, so don't change amount of whitespace. */

View file

@ -5712,7 +5712,9 @@ is given, the name
and value of the alias is printed.
The obsolete
.B \-x
option has no effect.
option has no effect in most contexts, although if it's used with
.B \-t
it will suppress all output.
The exit status is non-zero if a
.I name\^
is given, but no value, and no alias has been defined for the

View file

@ -104,5 +104,176 @@ unalias foo && err_exit 'unalias should return non-zero when a previously set al
err=$(set +x; { "$SHELL" -i -c 'unalias history; unalias r'; } 2>&1) && [[ -z $err ]] \
|| err_exit "the 'history' and 'r' aliases can't be removed (got $(printf %q "$err"))"
# ======
# Listing aliases in a script shouldn't break shcomp bytecode
exp="alias foo='bar('
alias four=4
alias three=3
alias two=2
foo='bar('
foo='bar('
four=4
three=3
two=2
foo='bar('
four=4
three=3
two=2
noalias: alias not found
ls=$(whence -p ls)
alias -t ls"
alias_script=$tmp/alias_script.sh
cat > "$alias_script" << EOF
unalias -a
alias foo='bar('
alias two=2
alias three=3
alias four=4
alias -p
alias foo
alias -x
alias
alias noalias
alias -t ls
alias -t
alias -pt
EOF
got=$(set +x; redirect 2>&1; $SHELL <($SHCOMP "$alias_script"))
[[ $exp == $got ]] || err_exit "Listing aliases corrupts shcomp bytecode" \
"(expected $(printf %q "$exp"), got $(printf %q "$got"))"
# Specifying an alias with 'alias -p' should print that alias in a reusable form
exp='alias foo=BAR'
got=$(
alias foo=BAR bar=FOO
alias -p foo
)
[[ $exp == $got ]] || err_exit "Specifying an alias with 'alias -p' doesn't work" \
"(expected $(printf %q "$exp"), got $(printf %q "$got"))"
got=$(
hash -r cat
alias -tx
alias -ptx
alias -ptx cat
alias -ptx nosuchcommand
)
[[ -z $got ]] || err_exit "The -x option should prevent output when combined with -t" \
"(got $(printf %q "$got"))"
# Listing members of the hash table with 'alias -pt' should work
exp='alias -t cat
vi: tracked alias not found
alias -t cat
alias -t chmod'
got=$(
set +x
redirect 2>&1
hash -r cat chmod
alias -pt cat vi # vi shouldn't be added to the hash table
alias -pt
)
[[ $exp == $got ]] || err_exit "Listing members of the hash table with 'alias -pt' doesn't work" \
"(expected $(printf %q "$exp"), got $(printf %q "$got"))"
# Attempting to list a non-existent alias or tracked alias should fail
# with an error message. Note that the exit status should match the
# number of aliases passed that don't exist.
exp='foo: alias not found
bar: alias not found
nosuchalias: alias not found'
got=$(
set +x
redirect 2>&1
unalias -a
alias foo bar nosuchalias
)
ret=$?
[[ $exp == $got ]] || err_exit "Listing non-existent aliases with 'alias' should fail with an error message" \
"(expected $(printf %q "$exp"), got $(printf %q "$got"))"
((ret == 3)) || err_exit "Listing non-existent aliases with 'alias' has wrong exit status" \
"(expected 3, got $ret)"
# Same as above, but tests alias -p with a different number
# of non-existent aliases.
exp='foo: alias not found
bar: alias not found
nosuchalias: alias not found
stillnoalias: alias not found'
got=$(
set +x
redirect 2>&1
unalias -a
alias -p foo bar nosuchalias stillnoalias
)
ret=$?
[[ $exp == $got ]] || err_exit "Listing non-existent aliases with 'alias -p' should fail with an error message" \
"(expected $(printf %q "$exp"), got $(printf %q "$got"))"
((ret == 4)) || err_exit "Listing non-existent aliases with 'alias -p' has wrong exit status" \
"(expected 4, got $ret)"
# Tracked aliases next.
exp='rm: tracked alias not found
ls: tracked alias not found'
got=$(
set +x
redirect 2>&1
hash -r
alias -pt rm ls
)
ret=$?
[[ $exp == $got ]] || err_exit "Listing non-existent tracked aliases with 'alias -pt' should fail with an error message" \
"(expected $(printf %q "$exp"), got $(printf %q "$got"))"
((ret == 2)) || err_exit "Listing non-existent tracked aliases with 'alias -pt' has wrong exit status" \
"(expected 2, got $ret)"
# Detect zombie aliases with 'alias'.
exp='vi: alias not found
chmod: alias not found'
got=$($SHELL -c '
hash vi chmod
hash -r
alias vi chmod
' 2>&1)
ret=$?
[[ $exp == $got ]] || err_exit "Listing removed tracked aliases with 'alias' should fail with an error message" \
"(expected $(printf %q "$exp"), got $(printf %q "$got"))"
((ret == 2)) || err_exit "Listing removed tracked aliases with 'alias' has wrong exit status" \
"(expected 2, got $ret)"
# Detect zombie aliases with 'alias -p'.
exp='vi: alias not found
chmod: alias not found'
got=$($SHELL -c '
hash vi chmod
hash -r
alias -p vi chmod
' 2>&1)
ret=$?
[[ $exp == $got ]] || err_exit "Listing removed tracked aliases with 'alias -p' should fail with an error message" \
"(expected $(printf %q "$exp"), got $(printf %q "$got"))"
((ret == 2)) || err_exit "Listing removed tracked aliases with 'alias -p' has wrong exit status" \
"(expected 2, got $ret)"
# Detect zombie tracked aliases.
exp='vi: tracked alias not found
chmod: tracked alias not found'
got=$($SHELL -c '
hash vi chmod
hash -r
alias -pt vi chmod
' 2>&1)
ret=$?
[[ $exp == $got ]] || err_exit "Listing removed tracked aliases with 'alias -pt' should fail with an error message" \
"(expected $(printf %q "$exp"), got $(printf %q "$got"))"
((ret == 2)) || err_exit "Listing removed tracked aliases with 'alias -pt' has wrong exit status" \
"(expected 2, got $ret)"
# The exit status on error must be >0, including when handling
# 256 non-existent aliases.
(unalias -a; alias $(integer -s i; for((i=0;i<256;i++)) do print -n "x "; done) 2> /dev/null)
got=$?
((got > 0)) || err_exit "Exit status is zero when alias is passed 256 non-existent aliases"
# ======
exit $((Errors<125?Errors:125))