Skip to content

Commit dbf552a

Browse files
committed
Fix scoping of disciplines for certain special variables
Example reproducer: function pathlocal { typeset PATH=LocalValue print -r -- "in pathlocal: $PATH" } function PATH.get { .sh.value=MainShellValue } print -r -- "in main: $PATH" pathlocal Expected output: in main: MainShellValue in pathlocal: LocalValue Actual output (before this commit): in main: MainShellValue in pathlocal: MainShellValue Analysis: Normally, when a local variable is defined using 'typeset' in a ksh function, it is initialised from scratch as a regular variable, even if it is a special variable in the local scope. For example, after 'typeset RANDOM' in a ksh function, the local RANDOM variable will not produce pseudorandom numbers. The same applies to user-defined shell discipline functions. They apply to the variable in the global scope, not to local variables, unless they are defined in the local scope for that local variable. However, for reasons of security and correctness, certain variables must keep their standard C discipline functions even when redefined in a local scope. These currently include IFS, PATH, SHELL, FPATH, CDPATH, SECONDS, ENV, and all the locale-related variables (LANG, LC_*). So these keep their default special properties even as local variables. This is not very consistent, but it's necessary, for example, to properly enforce the 'restricted' shell option, or to be able to have different IFS field splitting behaviour or set a different locale temporarily in a local scope. These variables are defined in nv_cover() in init.c. This function returns a pointer to the variable's current discipline function stack if it is one of these specific variables, otherwise NULL. Two code points in name.c use this to simply copy the discipline function stack pointer from the global scope to the local scope. And therein lies the problem. If an additional custom discipline (usually a shell discipline function) is defined for the variable, that one will carry over to the local scope, too. And that is both incorrect, because the local variable is a new variable even though it has the same name, and inconsistent, because variables not listed in nv_cover() don't work that way. src/cmd/ksh93/sh/init.c: - Rename nv_cover() to nv_enforcedisc() for clarity. - nv_enforcedisc(): Don't return a pointer to the listed variables' existing discipline function stack (nvfun), but instead return a pointer to their standard C discipline, as assigned by nv_init(). src/cmd/ksh93/sh/{name,subshell}.c: - In nv_setlist() and nv_create(), where the nvfun pointer was copied, instead call nv_stack() to initialise their discipline function stack using the pointer returned by nv_enforcedisc(); this is what nv_init() does for their global scopes. - No change is needed for the other nv_cover() calls (except for calling the new name), because they only check for a non-NULL result, and that hasn't changed. src/cmd/ksh93/sh.1: - Document the behaviour of special properties in local scopes.
1 parent dd2a393 commit dbf552a

File tree

8 files changed

+91
-21
lines changed

8 files changed

+91
-21
lines changed

NEWS

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@ This documents significant changes in the dev branch of ksh 93u+m.
22
For full details, see the git log at: https://github.com/ksh93/ksh
33
Uppercase BUG_* IDs are shell bug IDs as used by the Modernish shell library.
44

5+
2026-01-28:
6+
7+
- Fixed incorrect behaviour that occurred when IFS, PATH, SHELL,
8+
FPATH, CDPATH, SECONDS, ENV, LANG, LC_ALL, LC_CTYPE, LC_MESSAGES,
9+
LC_COLLATE, LC_NUMERIC, or LC_TIME were declared as a local
10+
variable within a function using 'typeset' while a custom shell
11+
discipline function was defined for them in the global scope.
12+
513
2026-01-21:
614

715
- Fixed several IFS field splitting bugs in read(1) that occurred

src/cmd/ksh93/include/name.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ struct Ufunction
144144

145145
extern int array_maxindex(Namval_t*);
146146
extern char *nv_endsubscript(Namval_t*, char*, int);
147-
extern Namfun_t *nv_cover(Namval_t*);
147+
extern Namfun_t *nv_enforcedisc(Namval_t*);
148148
extern int nv_arrayisset(Namval_t*, Namarr_t*);
149149
extern int nv_arraysettype(Namval_t*, Namval_t*,const char*,int);
150150
extern int nv_aimax(Namval_t*);

src/cmd/ksh93/include/version.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
#include <ast_release.h>
1919
#include "git.h"
2020

21-
#define SH_RELEASE_DATE "2026-01-21" /* must be in this format for $((.sh.version)) */
21+
#define SH_RELEASE_DATE "2026-01-28" /* must be in this format for $((.sh.version)) */
2222
/*
2323
* This comment keeps SH_RELEASE_DATE a few lines away from SH_RELEASE_SVER to avoid
2424
* merge conflicts when cherry-picking dev branch commits onto a release branch.

src/cmd/ksh93/sh.1

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2553,6 +2553,20 @@ and
25532553
are also
25542554
set by
25552555
.IR login (1).
2556+
.PP
2557+
The shell variables listed in this section will normally lose any special
2558+
properties if redeclared in a local scope. The exceptions to this rule are
2559+
.BR CDPATH ,
2560+
.BR ENV ,
2561+
.BR FPATH ,
2562+
.BR IFS ,
2563+
.BR PATH ,
2564+
.BR SECONDS ,
2565+
.BR SHELL ,
2566+
and all the locale-related variables
2567+
.RB ( LANG ,
2568+
.BR LC_* ),
2569+
which have their special properties reset to default in that case.
25562570
.SS Field Splitting.
25572571
After parameter expansion and command substitution,
25582572
the results of substitutions are scanned for the field separator

src/cmd/ksh93/sh/init.c

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1550,14 +1550,27 @@ void sh_reinit(void)
15501550
}
15511551

15521552
/*
1553-
* return discipline function tree pointer if a local variable of this name should share the parent's discipline function(s)
1553+
* Return default discipline function tree pointer for variables that must
1554+
* have their standard disciplines reset when redefined as local variables
15541555
*/
1555-
Namfun_t *nv_cover(Namval_t *np)
1556-
{
1557-
if(np==IFSNOD || np==PATHNOD || np==SHELLNOD || np==FPATHNOD || np==CDPNOD || np==SECONDS || np==ENVNOD)
1558-
return np->nvfun;
1559-
if(np==LCALLNOD || np==LCTYPENOD || np==LCMSGNOD || np==LCCOLLNOD || np==LCNUMNOD || np==LCTIMENOD || np==LANGNOD)
1560-
return np->nvfun;
1556+
Namfun_t *nv_enforcedisc(Namval_t *np)
1557+
{
1558+
Init_t *ip = sh.init_context;
1559+
if(np==IFSNOD) return &ip->IFS_init.hdr;
1560+
if(np==PATHNOD) return &ip->PATH_init;
1561+
if(np==SHELLNOD) return &ip->SHELL_init;
1562+
if(np==FPATHNOD) return &ip->FPATH_init;
1563+
if(np==CDPNOD) return &ip->CDPATH_init;
1564+
if(np==SECONDS) return &ip->SECONDS_init;
1565+
if(np==ENVNOD) return &ip->ENV_init;
1566+
/* locale */
1567+
if(np==LCALLNOD) return &ip->LC_ALL_init;
1568+
if(np==LCTYPENOD) return &ip->LC_TYPE_init;
1569+
if(np==LCMSGNOD) return &ip->LC_MSG_init;
1570+
if(np==LCCOLLNOD) return &ip->LC_COLL_init;
1571+
if(np==LCNUMNOD) return &ip->LC_NUM_init;
1572+
if(np==LCTIMENOD) return &ip->LC_TIME_init;
1573+
if(np==LANGNOD) return &ip->LANG_init;
15611574
return NULL;
15621575
}
15631576

src/cmd/ksh93/sh/name.c

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -594,11 +594,13 @@ void nv_setlist(struct argnod *arg,int flags, Namval_t *typ)
594594
*eqp = '\0';
595595
if((np = nv_search(cp,dtvnext(vartree),0)) && np->nvflag)
596596
{
597+
Namfun_t *fp;
597598
mp = nv_search(cp,vartree,NV_ADD|NV_NOSCOPE);
598599
mp->nvname = np->nvname; /* put_lang() (init.c) compares nvname pointers */
599600
mp->nvflag = np->nvflag;
600601
mp->nvsize = np->nvsize;
601-
mp->nvfun = nv_cover(np);
602+
if (fp = nv_enforcedisc(np))
603+
nv_stack(mp, fp);
602604
}
603605
*eqp = '=';
604606
}
@@ -881,18 +883,24 @@ Namval_t *nv_create(const char *name, Dt_t *root, int flags, Namfun_t *dp)
881883
}
882884
else if(nq)
883885
{
884-
if(nv_isnull(np) && c!='.' && ((np->nvfun=nv_cover(nq)) || nq==OPTINDNOD))
886+
if(nv_isnull(np) && c!='.')
885887
{
886-
np->nvname = nq->nvname;
888+
Namfun_t *fp = nv_enforcedisc(nq);
889+
if (fp)
890+
nv_stack(np,fp);
891+
if (fp || nq==OPTINDNOD)
892+
{
893+
np->nvname = nq->nvname;
887894
#if SHOPT_NAMESPACE
888-
if(sh.namespace && nv_dict(sh.namespace)==sh.var_tree && nv_isattr(nq,NV_EXPORT))
889-
nv_onattr(np,NV_EXPORT);
895+
if(sh.namespace && nv_dict(sh.namespace)==sh.var_tree && nv_isattr(nq,NV_EXPORT))
896+
nv_onattr(np,NV_EXPORT);
890897
#endif /* SHOPT_NAMESPACE */
891-
if(nq==OPTINDNOD)
892-
{
893-
np->nvfun = nq->nvfun;
894-
np->nvalue = &sh.st.optindex;
895-
nv_onattr(np,NV_INTEGER|NV_NOFREE);
898+
if(nq==OPTINDNOD)
899+
{
900+
np->nvfun = nq->nvfun;
901+
np->nvalue = &sh.st.optindex;
902+
nv_onattr(np,NV_INTEGER|NV_NOFREE);
903+
}
896904
}
897905
}
898906
flags |= NV_NOSCOPE;
@@ -2291,7 +2299,7 @@ static void table_unset(Dt_t *root, int flags, Dt_t *oroot)
22912299
{
22922300
if(nq=dtsearch(oroot,np))
22932301
{
2294-
if(nv_cover(nq))
2302+
if(nv_enforcedisc(nq))
22952303
{
22962304
unsigned int subshell = sh.subshell;
22972305
sh.subshell = 0;

src/cmd/ksh93/sh/subshell.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -359,7 +359,7 @@ static void nv_restore(struct subshell *sp)
359359
flags |= NV_NOFREE;
360360
}
361361
mp->nvflag = np->nvflag|(flags&NV_MINIMAL);
362-
if(nv_cover(mp))
362+
if(nv_enforcedisc(mp))
363363
nv_putval(mp,nv_getval(np),NV_RDONLY);
364364
else
365365
mp->nvalue = np->nvalue;

src/cmd/ksh93/tests/variables.sh

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1769,5 +1769,32 @@ got=$(./issue861.sh 2>&1)
17691769
"(got status $e, $(printf %q "$got"))"
17701770
unset i
17711771
1772+
# ======'"
1773+
1774+
# Problem: the global discipline function kept applying to the local variable
1775+
# for certain special variables. This was a design problem with nv_cover() in
1776+
# init.c which copied the nvfun pointer, i.e., the entire discipline function
1777+
# tree. (Note: nv_cover() is now renamed to nv_enforcedisc().)
1778+
# Here we can only test nv_enforcedisc() variables without value constraints.
1779+
1780+
exp=$'in main: MainShellValue\nin pathlocal: LocalValue'
1781+
for v in IFS PATH SHELL FPATH CDPATH ENV
1782+
do got=$(eval "
1783+
function pathlocal
1784+
{
1785+
typeset $v=LocalValue
1786+
print -r -- \"in pathlocal: \$$v\"
1787+
}
1788+
function $v.get
1789+
{
1790+
.sh.value=MainShellValue
1791+
}
1792+
print -r -- \"in main: \$$v\"
1793+
pathlocal
1794+
")
1795+
[[ $got == "$exp" ]] || err_exit "$v discipline not scoped properly" \
1796+
"(expected $(printf %q "$exp"), got $(printf %q "$got"))"
1797+
done
1798+
17721799
# ======
17731800
exit $((Errors<125?Errors:125))

0 commit comments

Comments
 (0)