diff --git a/ChangeLog b/ChangeLog index 6d8a8228969d..8b6e0b2ea512 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,52 @@ +2024-06-25 Simon J Gerraty + + * VERSION (_MAKE_VERSION): 20240625 + Merge with NetBSD make, pick up + o job.c: ensure shellPath is always duped, avoid upsetting free() + +2024-06-16 Simon J Gerraty + + * VERSION (_MAKE_VERSION): 20240616 + Merge with NetBSD make, pick up + o clean up collection of context information for error messages + o in warnings, move the word "warning" to the front + o var.c: throw an error on attempt to override an internal + read-only variable + +2024-06-10 Simon J Gerraty + + * VERSION (_MAKE_VERSION): 20240610 + Merge with NetBSD make, pick up + o for.c: remove redundant shortcut for building the .for loop body + +2024-06-02 Simon J Gerraty + + * VERSION (_MAKE_VERSION): 20240602 + Merge with NetBSD make, pick up + o rename some VarEvalMode constants to better match debug names. + o var.c: avoid out-of-bounds read when parsing indirect modifiers. + +2024-06-01 Simon J Gerraty + + * VERSION (_MAKE_VERSION): 20240601 + Merge with NetBSD make, pick up + o add .export-all rather than allow .export with no argument + which can happen accidentally. + o if lua is available, run check-expect.lua after unit-tests + o main.c: use snprintf rather than strncpy + fix memory leak when purging realpath cache. + +2024-05-28 Simon J Gerraty + + * VERSION (_MAKE_VERSION): 20240528 + Merge with NetBSD make, pick up + o fix a number of memory leaks + o replace magic numbers with POSIX FILENO constants + o hash.c: remove dead code from HashTable_DeleteEntry + o main.c: when complaining about unusable .OBJDIR + call PrintOnError if MAKE_DEBUG_OBJDIR_CHECK_WRITABLE is true. + o parse.c: use fewer technical terms in debug message for dependency + 2024-05-20 Simon J Gerraty * VERSION (_MAKE_VERSION): diff --git a/VERSION b/VERSION index 4af36cf84624..81837cc6765f 100644 --- a/VERSION +++ b/VERSION @@ -1,2 +1,2 @@ # keep this compatible with sh and make -_MAKE_VERSION=20240520 +_MAKE_VERSION=20240625 diff --git a/arch.c b/arch.c index 4e52532c780a..d8b467e02874 100644 --- a/arch.c +++ b/arch.c @@ -1,4 +1,4 @@ -/* $NetBSD: arch.c,v 1.217 2024/04/27 20:41:32 rillig Exp $ */ +/* $NetBSD: arch.c,v 1.219 2024/06/02 15:31:25 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 @@ -147,7 +147,7 @@ struct ar_hdr { #include "dir.h" /* "@(#)arch.c 8.2 (Berkeley) 1/2/94" */ -MAKE_RCSID("$NetBSD: arch.c,v 1.217 2024/04/27 20:41:32 rillig Exp $"); +MAKE_RCSID("$NetBSD: arch.c,v 1.219 2024/06/02 15:31:25 rillig Exp $"); typedef struct List ArchList; typedef struct ListNode ArchListNode; @@ -204,7 +204,7 @@ ArchFree(Arch *a) HashIter hi; HashIter_Init(&hi, &a->members); - while (HashIter_Next(&hi) != NULL) + while (HashIter_Next(&hi)) free(hi.entry->value); free(a->name); @@ -257,7 +257,8 @@ Arch_ParseArchive(char **pp, GNodeList *gns, GNode *scope) bool isError; /* XXX: is expanded twice: once here and once below */ - result = Var_Parse(&nested_p, scope, VARE_UNDEFERR); + result = Var_Parse(&nested_p, scope, + VARE_EVAL_DEFINED); /* TODO: handle errors */ isError = result.str == var_Error; FStr_Done(&result); @@ -272,7 +273,7 @@ Arch_ParseArchive(char **pp, GNodeList *gns, GNode *scope) spec[cp++ - spec] = '\0'; if (expandLib) - Var_Expand(&lib, scope, VARE_UNDEFERR); + Var_Expand(&lib, scope, VARE_EVAL_DEFINED); for (;;) { /* @@ -296,7 +297,7 @@ Arch_ParseArchive(char **pp, GNodeList *gns, GNode *scope) const char *nested_p = cp; result = Var_Parse(&nested_p, scope, - VARE_UNDEFERR); + VARE_EVAL_DEFINED); /* TODO: handle errors */ isError = result.str == var_Error; FStr_Done(&result); @@ -341,7 +342,7 @@ Arch_ParseArchive(char **pp, GNodeList *gns, GNode *scope) char *p; const char *unexpandedMem = mem.str; - Var_Expand(&mem, scope, VARE_UNDEFERR); + Var_Expand(&mem, scope, VARE_EVAL_DEFINED); /* * Now form an archive spec and recurse to deal with diff --git a/bmake.1 b/bmake.1 index d4e937424286..eb30d2173098 100644 --- a/bmake.1 +++ b/bmake.1 @@ -1,4 +1,4 @@ -.\" $NetBSD: make.1,v 1.375 2024/03/10 02:53:37 sjg Exp $ +.\" $NetBSD: make.1,v 1.377 2024/06/01 06:26:36 sjg Exp $ .\" .\" Copyright (c) 1990, 1993 .\" The Regents of the University of California. All rights reserved. @@ -29,7 +29,7 @@ .\" .\" from: @(#)make.1 8.4 (Berkeley) 3/19/94 .\" -.Dd March 9, 2024 +.Dd June 1, 2024 .Dt BMAKE 1 .Os .Sh NAME @@ -1143,9 +1143,19 @@ This mode can be used to detect undeclared dependencies between files. Used to create files in a separate directory, see .Va .OBJDIR . .It Va MAKE_OBJDIR_CHECK_WRITABLE -Used to force a separate directory for the created files, -even if that directory is not writable, see -.Va .OBJDIR . +When true, +.Nm +will check that +.Va .OBJDIR +is writable, and issue a warning if not. +.It Va MAKE_DEBUG_OBJDIR_CHECK_WRITABLE +When true and +.Nm +is warning about an unwritable +.Va .OBJDIR , +report the variables listed in +.Va MAKE_PRINT_VAR_ON_ERROR +to help debug. .It Va MAKEOBJDIRPREFIX Used to create files in a separate directory, see .Va .OBJDIR . @@ -1951,12 +1961,7 @@ The directives for exporting and unexporting variables are: .Bl -tag -width Ds .It Ic .export Ar variable No ... Export the specified global variable. -If no variable list is provided, all globals are exported -except for internal variables (those that start with -.Ql \&. ) . -This is not affected by the -.Fl X -flag, so should be used with caution. +.Pp For compatibility with other make programs, .Cm export Ar variable\| Ns Cm \&= Ns Ar value (without leading dot) is also accepted. @@ -1964,6 +1969,12 @@ For compatibility with other make programs, Appending a variable name to .Va .MAKE.EXPORTED is equivalent to exporting a variable. +.It Ic .export-all +Export all globals except for internal variables (those that start with +.Ql \&. ) . +This is not affected by the +.Fl X +flag, so should be used with caution. .It Ic .export-env Ar variable No ... The same as .Ql .export , diff --git a/bmake.cat1 b/bmake.cat1 index f2b05878e7e2..456885bc634c 100644 --- a/bmake.cat1 +++ b/bmake.cat1 @@ -756,8 +756,13 @@ VVAARRIIAABBLLEE AASSSSIIGGNNMMEENNTTSS Used to create files in a separate directory, see _._O_B_J_D_I_R. _M_A_K_E___O_B_J_D_I_R___C_H_E_C_K___W_R_I_T_A_B_L_E - Used to force a separate directory for the created files, even if - that directory is not writable, see _._O_B_J_D_I_R. + When true, bbmmaakkee will check that _._O_B_J_D_I_R is writable, and issue a + warning if not. + + _M_A_K_E___D_E_B_U_G___O_B_J_D_I_R___C_H_E_C_K___W_R_I_T_A_B_L_E + When true and bbmmaakkee is warning about an unwritable _._O_B_J_D_I_R, + report the variables listed in _M_A_K_E___P_R_I_N_T___V_A_R___O_N___E_R_R_O_R to help + debug. _M_A_K_E_O_B_J_D_I_R_P_R_E_F_I_X Used to create files in a separate directory, see _._O_B_J_D_I_R. @@ -1230,16 +1235,19 @@ DDIIRREECCTTIIVVEESS The directives for exporting and unexporting variables are: ..eexxppoorrtt _v_a_r_i_a_b_l_e ... - Export the specified global variable. If no variable list is - provided, all globals are exported except for internal variables - (those that start with `.'). This is not affected by the --XX - flag, so should be used with caution. For compatibility with - other make programs, eexxppoorrtt _v_a_r_i_a_b_l_e==_v_a_l_u_e (without leading dot) - is also accepted. + Export the specified global variable. + + For compatibility with other make programs, eexxppoorrtt _v_a_r_i_a_b_l_e==_v_a_l_u_e + (without leading dot) is also accepted. Appending a variable name to _._M_A_K_E_._E_X_P_O_R_T_E_D is equivalent to exporting a variable. + ..eexxppoorrtt--aallll + Export all globals except for internal variables (those that + start with `.'). This is not affected by the --XX flag, so should + be used with caution. + ..eexxppoorrtt--eennvv _v_a_r_i_a_b_l_e ... The same as `.export', except that the variable is not appended to _._M_A_K_E_._E_X_P_O_R_T_E_D. This allows exporting a value to the @@ -1780,4 +1788,4 @@ BBUUGGSS attempt to suppress a cascade of unnecessary errors, can result in a seemingly unexplained `*** Error code 6' -FreeBSD 13.2-RELEASE-p10 March 9, 2024 FreeBSD 13.2-RELEASE-p10 +FreeBSD 13.2-RELEASE-p11 June 1, 2024 FreeBSD 13.2-RELEASE-p11 diff --git a/compat.c b/compat.c index 3a8b1c4b2640..5d1b3ab52344 100644 --- a/compat.c +++ b/compat.c @@ -1,4 +1,4 @@ -/* $NetBSD: compat.c,v 1.255 2024/04/20 10:18:55 rillig Exp $ */ +/* $NetBSD: compat.c,v 1.259 2024/06/15 20:02:45 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. @@ -94,7 +94,7 @@ #include "pathnames.h" /* "@(#)compat.c 8.2 (Berkeley) 3/19/94" */ -MAKE_RCSID("$NetBSD: compat.c,v 1.255 2024/04/20 10:18:55 rillig Exp $"); +MAKE_RCSID("$NetBSD: compat.c,v 1.259 2024/06/15 20:02:45 rillig Exp $"); static GNode *curTarg = NULL; static pid_t compatChild; @@ -203,6 +203,24 @@ UseShell(const char *cmd MAKE_ATTR_UNUSED) #endif } +static int +Compat_Spawn(const char **av) +{ + int pid = vfork(); + if (pid < 0) + Fatal("Could not fork"); + + if (pid == 0) { +#ifdef USE_META + if (useMeta) + meta_compat_child(); +#endif + (void)execvp(av[0], (char *const *)UNCONST(av)); + execDie("exec", av[0]); + } + return pid; +} + /* * Execute the next command for a target. If the command returns an error, * the node's made field is set to ERROR and creation stops. @@ -225,21 +243,18 @@ Compat_RunCommand(const char *cmdp, GNode *gn, StringListNode *ln) volatile bool errCheck; /* Check errors */ WAIT_T reason; /* Reason for child's death */ WAIT_T status; /* Description of child's death */ - pid_t cpid; /* Child actually found */ pid_t retstat; /* Result of wait */ - const char **volatile av; /* Argument vector for thing to exec */ + const char **av; /* Arguments for the child process */ char **volatile mav; /* Copy of the argument vector for freeing */ bool useShell; /* True if command should be executed using a * shell */ - const char *volatile cmd = cmdp; + const char *cmd = cmdp; silent = (gn->type & OP_SILENT) != OP_NONE; errCheck = !(gn->type & OP_IGNORE); doIt = false; - EvalStack_Push(gn->name, NULL, NULL); - cmdStart = Var_Subst(cmd, gn, VARE_WANTRES); - EvalStack_Pop(); + cmdStart = Var_SubstInTarget(cmd, gn); /* TODO: handle errors */ if (cmdStart[0] == '\0') { @@ -264,11 +279,13 @@ Compat_RunCommand(const char *cmdp, GNode *gn, StringListNode *ln) * usual '$$'. */ Lst_Append(&endNode->commands, cmdStart); - return true; + goto register_command; } } if (strcmp(cmdStart, "...") == 0) { gn->type |= OP_SAVE_CMDS; + register_command: + Parse_RegisterCommand(cmdStart); return true; } @@ -288,7 +305,7 @@ Compat_RunCommand(const char *cmdp, GNode *gn, StringListNode *ln) while (ch_isspace(*cmd)) cmd++; if (cmd[0] == '\0') - return true; + goto register_command; useShell = UseShell(cmd); @@ -298,7 +315,7 @@ Compat_RunCommand(const char *cmdp, GNode *gn, StringListNode *ln) } if (!doIt && !GNode_ShouldExecute(gn)) - return true; + goto register_command; DEBUG1(JOB, "Execute: '%s'\n", cmd); @@ -333,19 +350,7 @@ Compat_RunCommand(const char *cmdp, GNode *gn, StringListNode *ln) Var_ReexportVars(gn); - compatChild = cpid = vfork(); - if (cpid < 0) - Fatal("Could not fork"); - - if (cpid == 0) { -#ifdef USE_META - if (useMeta) - meta_compat_child(); -#endif - (void)execvp(av[0], (char *const *)UNCONST(av)); - execDie("exec", av[0]); - } - + compatChild = Compat_Spawn(av); free(mav); free(bp); @@ -355,11 +360,11 @@ Compat_RunCommand(const char *cmdp, GNode *gn, StringListNode *ln) #ifdef USE_META if (useMeta) - meta_compat_parent(cpid); + meta_compat_parent(compatChild); #endif /* The child is off and running. Now all we can do is wait... */ - while ((retstat = wait(&reason)) != cpid) { + while ((retstat = wait(&reason)) != compatChild) { if (retstat > 0) JobReapChild(retstat, reason, false); /* not ours? */ if (retstat == -1 && errno != EINTR) diff --git a/cond.c b/cond.c index 5001677303e2..a6a73fe337dd 100644 --- a/cond.c +++ b/cond.c @@ -1,4 +1,4 @@ -/* $NetBSD: cond.c,v 1.363 2024/04/23 22:51:28 rillig Exp $ */ +/* $NetBSD: cond.c,v 1.365 2024/06/02 15:31:25 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. @@ -91,7 +91,7 @@ #include "dir.h" /* "@(#)cond.c 8.2 (Berkeley) 1/2/94" */ -MAKE_RCSID("$NetBSD: cond.c,v 1.363 2024/04/23 22:51:28 rillig Exp $"); +MAKE_RCSID("$NetBSD: cond.c,v 1.365 2024/06/02 15:31:25 rillig Exp $"); /* * Conditional expressions conform to this grammar: @@ -222,8 +222,8 @@ ParseWord(const char **pp, bool doEval) break; if (ch == '$') { VarEvalMode emode = doEval - ? VARE_UNDEFERR - : VARE_PARSE_ONLY; + ? VARE_EVAL_DEFINED + : VARE_PARSE; /* * TODO: make Var_Parse complain about undefined * variables. @@ -396,9 +396,9 @@ CondParser_StringExpr(CondParser *par, const char *start, const char *p; bool atStart; /* true means an expression outside quotes */ - emode = doEval && quoted ? VARE_WANTRES - : doEval ? VARE_UNDEFERR - : VARE_PARSE_ONLY; + emode = doEval && quoted ? VARE_EVAL + : doEval ? VARE_EVAL_DEFINED + : VARE_PARSE; p = par->p; atStart = p == start; @@ -651,8 +651,7 @@ CondParser_FuncCallEmpty(CondParser *par, bool doEval, Token *out_token) return false; p--; /* Make p[1] point to the '('. */ - val = Var_Parse(&p, SCOPE_CMDLINE, - doEval ? VARE_WANTRES : VARE_PARSE_ONLY); + val = Var_Parse(&p, SCOPE_CMDLINE, doEval ? VARE_EVAL : VARE_PARSE); /* TODO: handle errors */ if (val.str == var_Error) @@ -736,8 +735,10 @@ CondParser_ComparisonOrLeaf(CondParser *par, bool doEval) arg = ParseWord(&p, doEval); assert(arg[0] != '\0'); - if (*p == '=' || *p == '!' || *p == '<' || *p == '>') + if (*p == '=' || *p == '!' || *p == '<' || *p == '>') { + free(arg); return CondParser_Comparison(par, doEval); + } par->p = p; /* diff --git a/dir.c b/dir.c index de50b2faf511..860697120db0 100644 --- a/dir.c +++ b/dir.c @@ -1,4 +1,4 @@ -/* $NetBSD: dir.c,v 1.290 2024/05/20 19:14:12 sjg Exp $ */ +/* $NetBSD: dir.c,v 1.294 2024/05/31 05:50:11 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. @@ -132,7 +132,7 @@ #include "job.h" /* "@(#)dir.c 8.2 (Berkeley) 1/2/94" */ -MAKE_RCSID("$NetBSD: dir.c,v 1.290 2024/05/20 19:14:12 sjg Exp $"); +MAKE_RCSID("$NetBSD: dir.c,v 1.294 2024/05/31 05:50:11 rillig Exp $"); /* * A search path is a list of CachedDir structures. A CachedDir has in it the @@ -501,6 +501,18 @@ Dir_InitDot(void) Dir_SetPATH(); /* initialize */ } +#ifdef CLEANUP +static void +FreeCachedTable(HashTable *tbl) +{ + HashIter hi; + HashIter_Init(&hi, tbl); + while (HashIter_Next(&hi)) + free(hi.entry->value); + HashTable_Done(tbl); +} +#endif + /* Clean up the directories module. */ void Dir_End(void) @@ -511,8 +523,8 @@ Dir_End(void) CachedDir_Assign(&dotLast, NULL); SearchPath_Clear(&dirSearchPath); OpenDirs_Done(&openDirs); - HashTable_Done(&mtimes); - HashTable_Done(&lmtimes); + FreeCachedTable(&mtimes); + FreeCachedTable(&lmtimes); #endif } @@ -568,7 +580,7 @@ Dir_SetSYSPATH(void) CachedDirListNode *ln; SearchPath *path = Lst_IsEmpty(&sysIncPath->dirs) ? defSysIncPath : sysIncPath; - + Var_ReadOnly(".SYSPATH", false); Global_Delete(".SYSPATH"); for (ln = path->dirs.first; ln != NULL; ln = ln->next) { @@ -644,7 +656,7 @@ DirMatchFiles(const char *pattern, CachedDir *dir, StringList *expansions) */ HashIter_InitSet(&hi, &dir->files); - while (HashIter_Next(&hi) != NULL) { + while (HashIter_Next(&hi)) { const char *base = hi.entry->key; StrMatchResult res = Str_Match(base, pattern); /* TODO: handle errors from res.error */ @@ -864,6 +876,7 @@ SearchPath_ExpandMiddle(SearchPath *path, const char *pattern, (void)SearchPath_Add(partPath, dirpath); DirExpandPath(wildcardComponent + 1, partPath, expansions); SearchPath_Free(partPath); + free(dirpath); } /* diff --git a/for.c b/for.c index 91d9b8e13ce6..1563c2313963 100644 --- a/for.c +++ b/for.c @@ -1,4 +1,4 @@ -/* $NetBSD: for.c,v 1.179 2024/04/01 12:33:27 rillig Exp $ */ +/* $NetBSD: for.c,v 1.182 2024/06/07 18:57:30 rillig Exp $ */ /* * Copyright (c) 1992, The Regents of the University of California. @@ -58,7 +58,7 @@ #include "make.h" /* "@(#)for.c 8.1 (Berkeley) 6/6/93" */ -MAKE_RCSID("$NetBSD: for.c,v 1.179 2024/04/01 12:33:27 rillig Exp $"); +MAKE_RCSID("$NetBSD: for.c,v 1.182 2024/06/07 18:57:30 rillig Exp $"); typedef struct ForLoop { @@ -156,7 +156,8 @@ ForLoop_ParseVarnames(ForLoop *f, const char **pp) cpp_skip_whitespace(&p); if (*p == '\0') { Parse_Error(PARSE_FATAL, "missing `in' in for"); - f->vars.len = 0; + while (f->vars.len > 0) + free(*(char **)Vector_Pop(&f->vars)); return; } @@ -166,7 +167,8 @@ ForLoop_ParseVarnames(ForLoop *f, const char **pp) "invalid character '%c' " "in .for loop variable name", p[len]); - f->vars.len = 0; + while (f->vars.len > 0) + free(*(char **)Vector_Pop(&f->vars)); return; } } @@ -195,7 +197,7 @@ ForLoop_ParseItems(ForLoop *f, const char *p) cpp_skip_whitespace(&p); - items = Var_Subst(p, SCOPE_GLOBAL, VARE_WANTRES); + items = Var_Subst(p, SCOPE_GLOBAL, VARE_EVAL); /* TODO: handle errors */ f->items = Substring_Words(items, false); @@ -329,23 +331,6 @@ ExprLen(const char *s, const char *e) return 0; } -/* - * The .for loop substitutes the items as ${:U...}, which means - * that characters that break this syntax must be backslash-escaped. - */ -static bool -NeedsEscapes(Substring value, char endc) -{ - const char *p; - - for (p = value.start; p != value.end; p++) { - if (*p == ':' || *p == '$' || *p == '\\' || *p == endc || - *p == '\n') - return true; - } - return false; -} - /* * While expanding the body of a .for loop, write the item as a ${:U...} * expression, escaping characters as needed. The result is later unescaped @@ -357,11 +342,6 @@ AddEscaped(Buffer *cmds, Substring item, char endc) const char *p; char ch; - if (!NeedsEscapes(item, endc)) { - Buf_AddRange(cmds, item.start, item.end); - return; - } - for (p = item.start; p != item.end;) { ch = *p; if (ch == '$') { diff --git a/hash.c b/hash.c index 88d41c2c0f6f..bc7279d7a994 100644 --- a/hash.c +++ b/hash.c @@ -1,4 +1,4 @@ -/* $NetBSD: hash.c,v 1.74 2023/12/19 19:33:39 rillig Exp $ */ +/* $NetBSD: hash.c,v 1.78 2024/06/05 22:06:53 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. @@ -74,7 +74,7 @@ #include "make.h" /* "@(#)hash.c 8.1 (Berkeley) 6/6/93" */ -MAKE_RCSID("$NetBSD: hash.c,v 1.74 2023/12/19 19:33:39 rillig Exp $"); +MAKE_RCSID("$NetBSD: hash.c,v 1.78 2024/06/05 22:06:53 rillig Exp $"); /* * The ratio of # entries to # buckets at which we rebuild the table to @@ -288,24 +288,19 @@ void HashTable_DeleteEntry(HashTable *t, HashEntry *he) { HashEntry **ref = &t->buckets[he->hash & t->bucketsMask]; - HashEntry *p; - for (; (p = *ref) != NULL; ref = &p->next) { - if (p == he) { - *ref = p->next; - free(p); - t->numEntries--; - return; - } - } - abort(); + for (; *ref != he; ref = &(*ref)->next) + continue; + *ref = he->next; + free(he); + t->numEntries--; } /* - * Return the next entry in the hash table, or NULL if the end of the table - * is reached. + * Place the next entry from the hash table in hi->entry, or return false if + * the end of the table is reached. */ -HashEntry * +bool HashIter_Next(HashIter *hi) { HashTable *t = hi->table; @@ -318,11 +313,11 @@ HashIter_Next(HashIter *hi) while (he == NULL) { /* find the next nonempty chain */ if (hi->nextBucket >= bucketsSize) - return NULL; + return false; he = buckets[hi->nextBucket++]; } hi->entry = he; - return he; + return true; } void diff --git a/hash.h b/hash.h index 2bee685b7ebb..9201c65025c7 100644 --- a/hash.h +++ b/hash.h @@ -1,4 +1,4 @@ -/* $NetBSD: hash.h,v 1.48 2023/12/19 19:33:39 rillig Exp $ */ +/* $NetBSD: hash.h,v 1.50 2024/06/01 10:10:50 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. @@ -140,7 +140,7 @@ void HashTable_Set(HashTable *, const char *, void *); void HashTable_DeleteEntry(HashTable *, HashEntry *); void HashTable_DebugStats(HashTable *, const char *); -HashEntry *HashIter_Next(HashIter *); +bool HashIter_Next(HashIter *) MAKE_ATTR_USE; MAKE_INLINE void HashSet_Init(HashSet *set) diff --git a/job.c b/job.c index 7fafec0e4c8c..d260e21911aa 100644 --- a/job.c +++ b/job.c @@ -1,4 +1,4 @@ -/* $NetBSD: job.c,v 1.471 2024/05/07 18:26:22 sjg Exp $ */ +/* $NetBSD: job.c,v 1.477 2024/06/25 05:18:38 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. @@ -154,7 +154,7 @@ #include "trace.h" /* "@(#)job.c 8.2 (Berkeley) 3/19/94" */ -MAKE_RCSID("$NetBSD: job.c,v 1.471 2024/05/07 18:26:22 sjg Exp $"); +MAKE_RCSID("$NetBSD: job.c,v 1.477 2024/06/25 05:18:38 rillig Exp $"); /* * A shell defines how the commands are run. All commands for a target are @@ -914,9 +914,7 @@ JobWriteCommand(Job *job, ShellWriter *wr, StringListNode *ln, const char *ucmd) run = GNode_ShouldExecute(job->node); - EvalStack_Push(job->node->name, NULL, NULL); - xcmd = Var_Subst(ucmd, job->node, VARE_WANTRES); - EvalStack_Pop(); + xcmd = Var_SubstInTarget(ucmd, job->node); /* TODO: handle errors */ xcmdStart = xcmd; @@ -1043,11 +1041,10 @@ JobSaveCommands(Job *job) * variables such as .TARGET, .IMPSRC. It is not intended to * expand the other variables as well; see deptgt-end.mk. */ - EvalStack_Push(job->node->name, NULL, NULL); - expanded_cmd = Var_Subst(cmd, job->node, VARE_WANTRES); - EvalStack_Pop(); + expanded_cmd = Var_SubstInTarget(cmd, job->node); /* TODO: handle errors */ Lst_Append(&Targ_GetEndNode()->commands, expanded_cmd); + Parse_RegisterCommand(expanded_cmd); } } @@ -1082,7 +1079,7 @@ DebugFailedJob(const Job *job) debug_printf("\t%s\n", cmd); if (strchr(cmd, '$') != NULL) { - char *xcmd = Var_Subst(cmd, job->node, VARE_WANTRES); + char *xcmd = Var_Subst(cmd, job->node, VARE_EVAL); debug_printf("\t=> %s\n", xcmd); free(xcmd); } @@ -1468,11 +1465,11 @@ JobExec(Job *job, char **argv) * was marked close-on-exec, we must clear that bit in the * new input. */ - if (dup2(fileno(job->cmdFILE), 0) == -1) + if (dup2(fileno(job->cmdFILE), STDIN_FILENO) == -1) execDie("dup2", "job->cmdFILE"); - if (fcntl(0, F_SETFD, 0) == -1) + if (fcntl(STDIN_FILENO, F_SETFD, 0) == -1) execDie("fcntl clear close-on-exec", "stdin"); - if (lseek(0, 0, SEEK_SET) == -1) + if (lseek(STDIN_FILENO, 0, SEEK_SET) == -1) execDie("lseek to 0", "stdin"); if (job->node->type & (OP_MAKE | OP_SUBMAKE)) { @@ -1489,18 +1486,18 @@ JobExec(Job *job, char **argv) * Set up the child's output to be routed through the pipe * we've created for it. */ - if (dup2(job->outPipe, 1) == -1) + if (dup2(job->outPipe, STDOUT_FILENO) == -1) execDie("dup2", "job->outPipe"); /* * The output channels are marked close on exec. This bit - * was duplicated by the dup2(on some systems), so we have + * was duplicated by dup2 (on some systems), so we have * to clear it before routing the shell's error output to * the same place as its standard output. */ - if (fcntl(1, F_SETFD, 0) == -1) + if (fcntl(STDOUT_FILENO, F_SETFD, 0) == -1) execDie("clear close-on-exec", "stdout"); - if (dup2(1, 2) == -1) + if (dup2(STDOUT_FILENO, STDERR_FILENO) == -1) execDie("dup2", "1, 2"); /* @@ -2155,7 +2152,7 @@ InitShellNameAndPath(void) #ifdef DEFSHELL_CUSTOM if (shellName[0] == '/') { - shellPath = shellName; + shellPath = bmake_strdup(shellName); shellName = str_basename(shellPath); return; } @@ -2212,7 +2209,7 @@ Job_SetPrefix(void) Global_Set(".MAKE.JOB.PREFIX", "---"); targPrefix = Var_Subst("${.MAKE.JOB.PREFIX}", - SCOPE_GLOBAL, VARE_WANTRES); + SCOPE_GLOBAL, VARE_EVAL); /* TODO: handle errors */ } @@ -2502,7 +2499,8 @@ Job_ParseShell(char *line) } } } else { - shellPath = path; + free(UNCONST(shellPath)); + shellPath = bmake_strdup(path); shellName = newShell.name != NULL ? newShell.name : str_basename(path); if (!fullSpec) { diff --git a/main.c b/main.c index 6c97d648e5a2..de2f486c15d9 100644 --- a/main.c +++ b/main.c @@ -1,4 +1,4 @@ -/* $NetBSD: main.c,v 1.616 2024/05/19 17:55:54 sjg Exp $ */ +/* $NetBSD: main.c,v 1.624 2024/06/02 15:31:26 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 @@ -111,7 +111,7 @@ #include "trace.h" /* "@(#)main.c 8.3 (Berkeley) 3/19/94" */ -MAKE_RCSID("$NetBSD: main.c,v 1.616 2024/05/19 17:55:54 sjg Exp $"); +MAKE_RCSID("$NetBSD: main.c,v 1.624 2024/06/02 15:31:26 rillig Exp $"); #if defined(MAKE_NATIVE) __COPYRIGHT("@(#) Copyright (c) 1988, 1989, 1990, 1993 " "The Regents of the University of California. " @@ -372,7 +372,7 @@ MainParseArgChdir(const char *argvalue) stat(curdir, &sb) != -1 && sa.st_ino == sb.st_ino && sa.st_dev == sb.st_dev) - strncpy(curdir, argvalue, MAXPATHLEN); + snprintf(curdir, MAXPATHLEN, "%s", argvalue); ignorePWD = true; } @@ -743,6 +743,10 @@ Main_SetObjdir(bool writable, const char *fmt, ...) if ((writable && access(path, W_OK) != 0) || chdir(path) != 0) { (void)fprintf(stderr, "%s: warning: %s: %s.\n", progname, path, strerror(errno)); + /* Allow debugging how we got here - not always obvious */ + if (GetBooleanExpr("${MAKE_DEBUG_OBJDIR_CHECK_WRITABLE}", + false)) + PrintOnError(NULL, ""); return false; } @@ -766,7 +770,7 @@ SetVarObjdir(bool writable, const char *var, const char *suffix) return false; } - Var_Expand(&path, SCOPE_GLOBAL, VARE_WANTRES); + Var_Expand(&path, SCOPE_GLOBAL, VARE_EVAL); (void)Main_SetObjdir(writable, "%s%s", path.str, suffix); @@ -808,8 +812,7 @@ siginfo(int signo MAKE_ATTR_UNUSED) static void MakeMode(void) { - char *mode = Var_Subst("${.MAKE.MODE:tl}", - SCOPE_GLOBAL, VARE_WANTRES); + char *mode = Var_Subst("${.MAKE.MODE:tl}", SCOPE_GLOBAL, VARE_EVAL); /* TODO: handle errors */ if (mode[0] != '\0') { @@ -832,14 +835,14 @@ static void PrintVar(const char *varname, bool expandVars) { if (strchr(varname, '$') != NULL) { - char *evalue = Var_Subst(varname, SCOPE_GLOBAL, VARE_WANTRES); + char *evalue = Var_Subst(varname, SCOPE_GLOBAL, VARE_EVAL); /* TODO: handle errors */ printf("%s\n", evalue); free(evalue); } else if (expandVars) { char *expr = str_concat3("${", varname, "}"); - char *evalue = Var_Subst(expr, SCOPE_GLOBAL, VARE_WANTRES); + char *evalue = Var_Subst(expr, SCOPE_GLOBAL, VARE_EVAL); /* TODO: handle errors */ free(expr); printf("%s\n", evalue); @@ -865,7 +868,7 @@ GetBooleanExpr(const char *expr, bool fallback) char *value; bool res; - value = Var_Subst(expr, SCOPE_GLOBAL, VARE_WANTRES); + value = Var_Subst(expr, SCOPE_GLOBAL, VARE_EVAL); /* TODO: handle errors */ res = ParseBoolean(value, fallback); free(value); @@ -1050,7 +1053,7 @@ HandlePWD(const struct stat *curdir_st) if (stat(pwd, &pwd_st) == 0 && curdir_st->st_ino == pwd_st.st_ino && curdir_st->st_dev == pwd_st.st_dev) - (void)strncpy(curdir, pwd, MAXPATHLEN); + snprintf(curdir, MAXPATHLEN, "%s", pwd); ignore_pwd: FStr_Done(&makeobjdir); @@ -1140,9 +1143,9 @@ static void InitVarMake(const char *argv0) { const char *make = argv0; + char pathbuf[MAXPATHLEN]; if (argv0[0] != '/' && strchr(argv0, '/') != NULL) { - char pathbuf[MAXPATHLEN]; const char *abspath = cached_realpath(argv0, pathbuf); struct stat st; if (abspath != NULL && abspath[0] == '/' && @@ -1230,7 +1233,7 @@ InitMaxJobs(void) !Var_Exists(SCOPE_GLOBAL, ".MAKE.JOBS")) return; - value = Var_Subst("${.MAKE.JOBS}", SCOPE_GLOBAL, VARE_WANTRES); + value = Var_Subst("${.MAKE.JOBS}", SCOPE_GLOBAL, VARE_EVAL); /* TODO: handle errors */ n = (int)strtol(value, NULL, 0); if (n < 1) { @@ -1265,7 +1268,7 @@ InitVpath(void) if (!Var_Exists(SCOPE_CMDLINE, "VPATH")) return; - vpath = Var_Subst("${VPATH}", SCOPE_CMDLINE, VARE_WANTRES); + vpath = Var_Subst("${VPATH}", SCOPE_CMDLINE, VARE_EVAL); /* TODO: handle errors */ path = vpath; do { @@ -1302,7 +1305,7 @@ ReadFirstDefaultMakefile(void) StringList makefiles = LST_INIT; StringListNode *ln; char *prefs = Var_Subst("${.MAKE.MAKEFILE_PREFERENCE}", - SCOPE_CMDLINE, VARE_WANTRES); + SCOPE_CMDLINE, VARE_EVAL); /* TODO: handle errors */ AppendWords(&makefiles, prefs); @@ -1513,7 +1516,7 @@ main_PrepareMaking(void) /* In particular suppress .depend for '-r -V .OBJDIR -f /dev/null' */ if (!opts.noBuiltins || opts.printVars == PVM_NONE) { makeDependfile = Var_Subst("${.MAKE.DEPENDFILE}", - SCOPE_CMDLINE, VARE_WANTRES); + SCOPE_CMDLINE, VARE_EVAL); if (makeDependfile[0] != '\0') { /* TODO: handle errors */ doing_depend = true; @@ -1605,9 +1608,9 @@ main_CleanUp(void) meta_finish(); #endif Suff_End(); + Var_End(); Targ_End(); Arch_End(); - Var_End(); Parse_End(); Dir_End(); Job_End(); @@ -1988,22 +1991,19 @@ execDie(const char *af, const char *av) static void purge_relative_cached_realpaths(void) { - HashEntry *he, *next; HashIter hi; + bool more; HashIter_Init(&hi, &cached_realpaths); - he = HashIter_Next(&hi); - while (he != NULL) { - next = HashIter_Next(&hi); + more = HashIter_Next(&hi); + while (more) { + HashEntry *he = hi.entry; + more = HashIter_Next(&hi); if (he->key[0] != '/') { DEBUG1(DIR, "cached_realpath: purging %s\n", he->key); + free(he->value); HashTable_DeleteEntry(&cached_realpaths, he); - /* - * XXX: What about the allocated he->value? Either - * free them or document why they cannot be freed. - */ } - he = next; } } @@ -2017,9 +2017,7 @@ cached_realpath(const char *pathname, char *resolved) rp = HashTable_FindValue(&cached_realpaths, pathname); if (rp != NULL) { - /* a hit */ - strncpy(resolved, rp, MAXPATHLEN); - resolved[MAXPATHLEN - 1] = '\0'; + snprintf(resolved, MAXPATHLEN, "%s", rp); return resolved; } @@ -2107,7 +2105,7 @@ PrintOnError(GNode *gn, const char *msg) { char *errorVarsValues = Var_Subst( "${MAKE_PRINT_VAR_ON_ERROR:@v@$v='${$v}'\n@}", - SCOPE_GLOBAL, VARE_WANTRES); + SCOPE_GLOBAL, VARE_EVAL); /* TODO: handle errors */ printf("%s", errorVarsValues); free(errorVarsValues); @@ -2137,10 +2135,11 @@ Main_ExportMAKEFLAGS(bool first) flags = Var_Subst( "${.MAKEFLAGS} ${.MAKEOVERRIDES:O:u:@v@$v=${$v:Q}@}", - SCOPE_CMDLINE, VARE_WANTRES); + SCOPE_CMDLINE, VARE_EVAL); /* TODO: handle errors */ if (flags[0] != '\0') setenv("MAKEFLAGS", flags, 1); + free(flags); } char * @@ -2154,7 +2153,7 @@ getTmpdir(void) /* Honor $TMPDIR if it is valid, strip a trailing '/'. */ tmpdir = Var_Subst("${TMPDIR:tA:U" _PATH_TMP ":S,/$,,W}/", - SCOPE_GLOBAL, VARE_WANTRES); + SCOPE_GLOBAL, VARE_EVAL); /* TODO: handle errors */ if (stat(tmpdir, &st) < 0 || !S_ISDIR(st.st_mode)) { diff --git a/make.1 b/make.1 index da093c1ba5b7..b7748023e1ab 100644 --- a/make.1 +++ b/make.1 @@ -1,4 +1,4 @@ -.\" $NetBSD: make.1,v 1.375 2024/03/10 02:53:37 sjg Exp $ +.\" $NetBSD: make.1,v 1.377 2024/06/01 06:26:36 sjg Exp $ .\" .\" Copyright (c) 1990, 1993 .\" The Regents of the University of California. All rights reserved. @@ -29,7 +29,7 @@ .\" .\" from: @(#)make.1 8.4 (Berkeley) 3/19/94 .\" -.Dd March 9, 2024 +.Dd June 1, 2024 .Dt MAKE 1 .Os .Sh NAME @@ -1143,9 +1143,19 @@ This mode can be used to detect undeclared dependencies between files. Used to create files in a separate directory, see .Va .OBJDIR . .It Va MAKE_OBJDIR_CHECK_WRITABLE -Used to force a separate directory for the created files, -even if that directory is not writable, see -.Va .OBJDIR . +When true, +.Nm +will check that +.Va .OBJDIR +is writable, and issue a warning if not. +.It Va MAKE_DEBUG_OBJDIR_CHECK_WRITABLE +When true and +.Nm +is warning about an unwritable +.Va .OBJDIR , +report the variables listed in +.Va MAKE_PRINT_VAR_ON_ERROR +to help debug. .It Va MAKEOBJDIRPREFIX Used to create files in a separate directory, see .Va .OBJDIR . @@ -1951,12 +1961,7 @@ The directives for exporting and unexporting variables are: .Bl -tag -width Ds .It Ic .export Ar variable No ... Export the specified global variable. -If no variable list is provided, all globals are exported -except for internal variables (those that start with -.Ql \&. ) . -This is not affected by the -.Fl X -flag, so should be used with caution. +.Pp For compatibility with other make programs, .Cm export Ar variable\| Ns Cm \&= Ns Ar value (without leading dot) is also accepted. @@ -1964,6 +1969,12 @@ For compatibility with other make programs, Appending a variable name to .Va .MAKE.EXPORTED is equivalent to exporting a variable. +.It Ic .export-all +Export all globals except for internal variables (those that start with +.Ql \&. ) . +This is not affected by the +.Fl X +flag, so should be used with caution. .It Ic .export-env Ar variable No ... The same as .Ql .export , diff --git a/make.c b/make.c index 53f7b35754ba..b784e812453b 100644 --- a/make.c +++ b/make.c @@ -1,4 +1,4 @@ -/* $NetBSD: make.c,v 1.262 2024/01/05 23:22:06 rillig Exp $ */ +/* $NetBSD: make.c,v 1.264 2024/06/02 15:31:26 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 @@ -104,7 +104,7 @@ #include "job.h" /* "@(#)make.c 8.1 (Berkeley) 6/6/93" */ -MAKE_RCSID("$NetBSD: make.c,v 1.262 2024/01/05 23:22:06 rillig Exp $"); +MAKE_RCSID("$NetBSD: make.c,v 1.264 2024/06/02 15:31:26 rillig Exp $"); /* Sequence # to detect recursion. */ static unsigned int checked_seqno = 1; @@ -127,8 +127,8 @@ debug_printf(const char *fmt, ...) va_end(ap); } -static const char * -GNodeType_ToString(GNodeType type, void **freeIt) +static char * +GNodeType_ToString(GNodeType type) { Buffer buf; @@ -166,11 +166,13 @@ GNodeType_ToString(GNodeType type, void **freeIt) ADD(OP_DEPS_FOUND); ADD(OP_MARK); #undef ADD - return buf.len == 0 ? "none" : (*freeIt = Buf_DoneData(&buf)); + if (buf.len == 0) + Buf_AddStr(&buf, "none"); + return Buf_DoneData(&buf); } -static const char * -GNodeFlags_ToString(GNodeFlags flags, void **freeIt) +static char * +GNodeFlags_ToString(GNodeFlags flags) { Buffer buf; @@ -184,24 +186,22 @@ GNodeFlags_ToString(GNodeFlags flags, void **freeIt) Buf_AddFlag(&buf, flags.doneAllsrc, "DONE_ALLSRC"); Buf_AddFlag(&buf, flags.cycle, "CYCLE"); Buf_AddFlag(&buf, flags.doneCycle, "DONECYCLE"); - return buf.len == 0 ? "none" : (*freeIt = Buf_DoneData(&buf)); + if (buf.len == 0) + Buf_AddStr(&buf, "none"); + return Buf_DoneData(&buf); } void GNode_FprintDetails(FILE *f, const char *prefix, const GNode *gn, const char *suffix) { - void *type_freeIt = NULL; - void *flags_freeIt = NULL; + char *type = GNodeType_ToString(gn->type); + char *flags = GNodeFlags_ToString(gn->flags); fprintf(f, "%s%s, type %s, flags %s%s", - prefix, - GNodeMade_Name(gn->made), - GNodeType_ToString(gn->type, &type_freeIt), - GNodeFlags_ToString(gn->flags, &flags_freeIt), - suffix); - free(type_freeIt); - free(flags_freeIt); + prefix, GNodeMade_Name(gn->made), type, flags, suffix); + free(type); + free(flags); } bool @@ -443,7 +443,7 @@ Make_HandleUse(GNode *cgn, GNode *pgn) gn->uname = gn->name; else free(gn->name); - gn->name = Var_Subst(gn->uname, pgn, VARE_WANTRES); + gn->name = Var_Subst(gn->uname, pgn, VARE_EVAL); /* TODO: handle errors */ if (gn->uname != NULL && strcmp(gn->name, gn->uname) != 0) { /* See if we have a target for this node. */ diff --git a/make.h b/make.h index b7fcc77b91b3..e9ff48536c97 100644 --- a/make.h +++ b/make.h @@ -1,4 +1,4 @@ -/* $NetBSD: make.h,v 1.333 2024/05/07 18:26:22 sjg Exp $ */ +/* $NetBSD: make.h,v 1.339 2024/06/15 20:02:45 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 @@ -938,6 +938,15 @@ void Targ_PrintType(GNodeType); void Targ_PrintGraph(int); void Targ_Propagate(void); const char *GNodeMade_Name(GNodeMade) MAKE_ATTR_USE; +#ifdef CLEANUP +void Parse_RegisterCommand(char *); +#else +/* ARGSUSED */ +MAKE_INLINE +void Parse_RegisterCommand(char *cmd MAKE_ATTR_UNUSED) +{ +} +#endif /* var.c */ void Var_Init(void); @@ -951,7 +960,7 @@ typedef enum VarEvalMode { * TODO: Document what Var_Parse and Var_Subst return in this mode. * As of 2021-03-15, they return unspecified, inconsistent results. */ - VARE_PARSE_ONLY, + VARE_PARSE, /* * Parse text in which '${...}' and '$(...)' are not parsed as @@ -962,25 +971,13 @@ typedef enum VarEvalMode { VARE_PARSE_BALANCED, /* Parse and evaluate the expression. */ - VARE_WANTRES, + VARE_EVAL, /* * Parse and evaluate the expression. It is an error if a * subexpression evaluates to undefined. */ - VARE_UNDEFERR, - - /* - * Parse and evaluate the expression. Keep '$$' as '$$' instead of - * reducing it to a single '$'. Subexpressions that evaluate to - * undefined expand to an empty string. - * - * Used in variable assignments using the ':=' operator. It allows - * multiple such assignments to be chained without accidentally - * expanding '$$file' to '$file' in the first assignment and - * interpreting it as '${f}' followed by 'ile' in the next assignment. - */ - VARE_EVAL_KEEP_DOLLAR, + VARE_EVAL_DEFINED, /* * Parse and evaluate the expression. Keep undefined variables as-is @@ -993,13 +990,13 @@ typedef enum VarEvalMode { * # way) is still undefined, the updated CFLAGS becomes * # "-I.. $(.INCLUDES)". */ - VARE_EVAL_KEEP_UNDEF, + VARE_EVAL_KEEP_UNDEFINED, /* * Parse and evaluate the expression. Keep '$$' as '$$' and preserve * undefined subexpressions. */ - VARE_KEEP_DOLLAR_UNDEF + VARE_EVAL_KEEP_DOLLAR_AND_UNDEFINED } VarEvalMode; typedef enum VarSetFlags { @@ -1018,6 +1015,8 @@ typedef enum VarSetFlags { } VarSetFlags; typedef enum VarExportMode { + /* .export-all */ + VEM_ALL, /* .export-env */ VEM_ENV, /* .export: Initial export or update an already exported variable. */ @@ -1027,6 +1026,9 @@ typedef enum VarExportMode { } VarExportMode; void Var_Delete(GNode *, const char *); +#ifdef CLEANUP +void Var_DeleteAll(GNode *scope); +#endif void Var_Undef(const char *); void Var_Set(GNode *, const char *, const char *); void Var_SetExpand(GNode *, const char *, const char *); @@ -1039,6 +1041,7 @@ FStr Var_Value(GNode *, const char *) MAKE_ATTR_USE; const char *GNode_ValueDirect(GNode *, const char *) MAKE_ATTR_USE; FStr Var_Parse(const char **, GNode *, VarEvalMode); char *Var_Subst(const char *, GNode *, VarEvalMode); +char *Var_SubstInTarget(const char *, GNode *); void Var_Expand(FStr *, GNode *, VarEvalMode); void Var_Stats(void); void Var_Dump(GNode *); @@ -1053,8 +1056,6 @@ void Global_Append(const char *, const char *); void Global_Delete(const char *); void Global_Set_ReadOnly(const char *, const char *); -void EvalStack_Push(const char *, const char *, const char *); -void EvalStack_Pop(void); const char *EvalStack_Details(void); /* util.c */ diff --git a/meta.c b/meta.c index dea3ed12d965..84f928785c19 100644 --- a/meta.c +++ b/meta.c @@ -1,4 +1,4 @@ -/* $NetBSD: meta.c,v 1.208 2024/04/27 17:33:46 rillig Exp $ */ +/* $NetBSD: meta.c,v 1.210 2024/06/02 15:31:26 rillig Exp $ */ /* * Implement 'meta' mode. @@ -326,7 +326,7 @@ is_submake(const char *cmd, GNode *gn) p_len = strlen(p_make); } if (strchr(cmd, '$') != NULL) { - mp = Var_Subst(cmd, gn, VARE_WANTRES); + mp = Var_Subst(cmd, gn, VARE_EVAL); /* TODO: handle errors */ cmd = mp; } @@ -372,7 +372,7 @@ printCMD(const char *ucmd, FILE *fp, GNode *gn) { FStr xcmd = FStr_InitRefer(ucmd); - Var_Expand(&xcmd, gn, VARE_WANTRES); + Var_Expand(&xcmd, gn, VARE_EVAL); fprintf(fp, "CMD %s\n", xcmd.str); FStr_Done(&xcmd); } @@ -481,7 +481,7 @@ meta_create(BuildMon *pbm, GNode *gn) if (metaVerbose) { /* Describe the target we are building */ - char *mp = Var_Subst("${" MAKE_META_PREFIX "}", gn, VARE_WANTRES); + char *mp = Var_Subst("${" MAKE_META_PREFIX "}", gn, VARE_EVAL); /* TODO: handle errors */ if (mp[0] != '\0') fprintf(stdout, "%s\n", mp); @@ -618,7 +618,7 @@ meta_mode_init(const char *make_mode) * We consider ourselves master of all within ${.MAKE.META.BAILIWICK} */ metaBailiwickStr = Var_Subst("${.MAKE.META.BAILIWICK:O:u:tA}", - SCOPE_GLOBAL, VARE_WANTRES); + SCOPE_GLOBAL, VARE_EVAL); /* TODO: handle errors */ AppendWords(&metaBailiwick, metaBailiwickStr); /* @@ -627,7 +627,7 @@ meta_mode_init(const char *make_mode) Global_Append(MAKE_META_IGNORE_PATHS, "/dev /etc /proc /tmp /var/run /var/tmp ${TMPDIR}"); metaIgnorePathsStr = Var_Subst("${" MAKE_META_IGNORE_PATHS ":O:u:tA}", - SCOPE_GLOBAL, VARE_WANTRES); + SCOPE_GLOBAL, VARE_EVAL); /* TODO: handle errors */ AppendWords(&metaIgnorePaths, metaIgnorePathsStr); @@ -777,7 +777,7 @@ meta_job_output(Job *job, char *cp, const char *nl) char *cp2; meta_prefix = Var_Subst("${" MAKE_META_PREFIX "}", - SCOPE_GLOBAL, VARE_WANTRES); + SCOPE_GLOBAL, VARE_EVAL); /* TODO: handle errors */ if ((cp2 = strchr(meta_prefix, '$')) != NULL) meta_prefix_len = (size_t)(cp2 - meta_prefix); @@ -967,7 +967,7 @@ meta_ignore(GNode *gn, const char *p) */ Var_Set(gn, ".p.", p); expr = "${" MAKE_META_IGNORE_PATTERNS ":@m@${.p.:M$m}@}"; - pm = Var_Subst(expr, gn, VARE_WANTRES); + pm = Var_Subst(expr, gn, VARE_EVAL); /* TODO: handle errors */ if (pm[0] != '\0') { #ifdef DEBUG_META_MODE @@ -986,7 +986,7 @@ meta_ignore(GNode *gn, const char *p) snprintf(fname, sizeof fname, "${%s:L:${%s:ts:}}", p, MAKE_META_IGNORE_FILTER); - fm = Var_Subst(fname, gn, VARE_WANTRES); + fm = Var_Subst(fname, gn, VARE_EVAL); /* TODO: handle errors */ if (*fm == '\0') { #ifdef DEBUG_META_MODE @@ -1046,7 +1046,7 @@ meta_filter_cmd(GNode *gn, char *s) Var_Set(gn, META_CMD_FILTER_VAR, s); s = Var_Subst( "${" META_CMD_FILTER_VAR ":${" MAKE_META_CMP_FILTER ":ts:}}", - gn, VARE_WANTRES); + gn, VARE_EVAL); return s; } @@ -1514,7 +1514,7 @@ meta_oodate(GNode *gn, bool oodate) DEBUG2(META, "%s: %u: cannot compare command using .OODATE\n", fname, lineno); } - cmd = Var_Subst(cmd, gn, VARE_UNDEFERR); + cmd = Var_Subst(cmd, gn, VARE_EVAL_DEFINED); /* TODO: handle errors */ if ((cp = strchr(cmd, '\n')) != NULL) { @@ -1650,7 +1650,8 @@ void meta_compat_child(void) { meta_job_child(NULL); - if (dup2(childPipe[1], 1) < 0 || dup2(1, 2) < 0) + if (dup2(childPipe[1], STDOUT_FILENO) < 0 + || dup2(STDOUT_FILENO, STDERR_FILENO) < 0) execDie("dup2", "pipe"); } diff --git a/mk/ChangeLog b/mk/ChangeLog index 4c3f4f4572c9..0e521d9ecce6 100644 --- a/mk/ChangeLog +++ b/mk/ChangeLog @@ -1,3 +1,16 @@ +2024-06-22 Simon J Gerraty + + * install-mk (MK_VERSION): 20240616 + + * dirdeps.mk: apply DEP_DIRDEPS_BUILD_DIR_FILTER after we have + computed build dirs, since some filters cannot be easily expressed via + DEP_DIRDEPS_FILTER. + +2024-05-31 Simon J Gerraty + + * dirdeps.mk: move reset of DIRDEPS_EXPORT_VARS + until after we a finished with it if building a cache. + 2024-05-04 Simon J Gerraty * install-mk (MK_VERSION): 20240504 diff --git a/mk/dirdeps.mk b/mk/dirdeps.mk index 7a9ecd881d7b..2a9d2ac92102 100644 --- a/mk/dirdeps.mk +++ b/mk/dirdeps.mk @@ -1,4 +1,4 @@ -# $Id: dirdeps.mk,v 1.167 2024/05/06 20:41:08 sjg Exp $ +# $Id: dirdeps.mk,v 1.170 2024/06/24 02:21:00 sjg Exp $ # SPDX-License-Identifier: BSD-2-Clause # @@ -139,7 +139,7 @@ # DIRDEPS_EXPORT_VARS (DEP_EXPORT_VARS) # It is discouraged, but sometimes necessary for a # Makefile.depend file to influence the environment. -# Doing this is correctly (especially if using DIRDEPS_CACHE) is +# Doing this correctly (especially if using DIRDEPS_CACHE) is # tricky so a Makefile.depend file can set DIRDEPS_EXPORT_VARS # and dirdeps.mk will do the deed: # @@ -695,9 +695,22 @@ DEP_DIRDEPS_FILTER = \ ${DIRDEPS_FILTER.${DEP_TARGET_SPEC}:U} \ ${TARGET_SPEC_VARS:@v@${DIRDEPS_FILTER.${DEP_$v}:U}@} \ ${DIRDEPS_FILTER:U} + .if empty(DEP_DIRDEPS_FILTER) # something harmless -DEP_DIRDEPS_FILTER = U +DEP_DIRDEPS_FILTER = u +.endif + +# this is applied after we have computed build dirs +# so everything is fully qualified and starts with ${SRCTOP}/ +DEP_DIRDEPS_BUILD_DIR_FILTER = \ + ${DIRDEPS_BUILD_DIR_FILTER.${DEP_TARGET_SPEC}:U} \ + ${TARGET_SPEC_VARS:@v@${DIRDEPS_BUILD_DIR_FILTER.${DEP_$v}:U}@} \ + ${DIRDEPS_BUILD_DIR_FILTER:U} + +.if empty(DEP_DIRDEPS_BUILD_DIR_FILTER) +# something harmless +DEP_DIRDEPS_BUILD_DIR_FILTER = u .endif # this is what we start with @@ -717,6 +730,7 @@ __qual_depdirs += ${__hostdpadd} .if ${_debug_reldir} .info DEP_DIRDEPS_FILTER=${DEP_DIRDEPS_FILTER:ts:} +.info DEP_DIRDEPS_BUILD_DIR_FILTER=${DEP_DIRDEPS_BUILD_DIR_FILTER:ts:} .info depdirs=${__depdirs:S,^${SRCTOP}/,,:${DEBUG_DIRDEPS_LIST_FILTER:U:N/:ts:}} .info qualified=${__qual_depdirs:S,^${SRCTOP}/,,:${DEBUG_DIRDEPS_LIST_FILTER:U:N/:ts:}} .info unqualified=${__unqual_depdirs:S,^${SRCTOP}/,,:${DEBUG_DIRDEPS_LIST_FILTER:U:N/:ts:}} @@ -736,7 +750,8 @@ _build_dirs += \ # make sure we do not mess with qualifying "host" entries _build_dirs := ${_build_dirs:M*.host*:${M_dep_qual_fixes.host:ts:}} \ ${_build_dirs:N*.host*:${M_dep_qual_fixes:ts:}} -_build_dirs := ${_build_dirs:O:u} +# some filters can only be applied now +_build_dirs := ${_build_dirs:${DEP_DIRDEPS_BUILD_DIR_FILTER:ts:}:O:u} .if ${_debug_reldir} .info _build_dirs=${_build_dirs:${DEBUG_DIRDEPS_LIST_FILTER:U:N/:ts:}} .endif @@ -746,6 +761,11 @@ _build_dirs := ${_build_dirs:O:u} _build_all_dirs += ${_build_dirs} ${_build_xtra_dirs} _build_all_dirs := ${_build_all_dirs:O:u} +# we prefer DIRDEPS_EXPORT_VARS +.if empty(DIRDEPS_EXPORT_VARS) && !empty(DEP_EXPORT_VARS) +DIRDEPS_EXPORT_VARS = ${DEP_EXPORT_VARS} +.endif + # Normally if doing make -V something, # we do not want to waste time chasing DIRDEPS # but if we want to count the number of Makefile.depend* read, we do. @@ -757,10 +777,9 @@ _cache_script = echo '\# ${DEP_RELDIR}.${DEP_TARGET_SPEC}'; # guard against _new_dirdeps being too big for a single command line _new_dirdeps := ${_build_all_dirs:@x@${target($x):?:$x}@:S,^${SRCTOP}/,,} _cache_xtra_deps := ${_build_xtra_dirs:S,^${SRCTOP}/,,} -.if !empty(DIRDEPS_EXPORT_VARS) || !empty(DEP_EXPORT_VARS) +.if !empty(DIRDEPS_EXPORT_VARS) # Discouraged, but there are always exceptions. # Handle it here rather than explain how. -DIRDEPS_EXPORT_VARS ?= ${DEP_EXPORT_VARS} _cache_xvars := echo; ${DIRDEPS_EXPORT_VARS:@v@echo '$v = ${$v}';@} echo '.export ${DIRDEPS_EXPORT_VARS}'; echo; _cache_script += ${_cache_xvars} .endif @@ -774,12 +793,6 @@ ${_build_all_dirs}: _DIRDEP_USE .info ${DEP_RELDIR}.${DEP_TARGET_SPEC}: needs: ${_build_dirs:S,^${SRCTOP}/,,:${DEBUG_DIRDEPS_LIST_FILTER:U:N/:ts:}} .endif -.if !empty(DIRDEPS_EXPORT_VARS) || !empty(DEP_EXPORT_VARS) -.export ${DIRDEPS_EXPORT_VARS} ${DEP_EXPORT_VARS} -DIRDEPS_EXPORT_VARS = -DEP_EXPORT_VARS = -.endif - # this builds the dependency graph .for m in ${_machines} .if ${BUILD_DIRDEPS_CACHE} == "yes" && !empty(_build_dirs) @@ -835,6 +848,15 @@ ${_this_dir}.$m: ${_build_dirs:M*.$m:N${_this_dir}.$m} .endif +.if !empty(DIRDEPS_EXPORT_VARS) +.if ${BUILD_DIRDEPS_CACHE} == "no" +.export ${DIRDEPS_EXPORT_VARS} +.endif +# Reset these, we are done with them for this iteration. +DIRDEPS_EXPORT_VARS = +DEP_EXPORT_VARS = +.endif + # Now find more dependencies - and recurse. .for d in ${_build_all_dirs} .if !target(_dirdeps_checked.$d) diff --git a/mk/install-mk b/mk/install-mk index c2962ffee6dc..bb150e3041a3 100644 --- a/mk/install-mk +++ b/mk/install-mk @@ -59,7 +59,7 @@ # Simon J. Gerraty # RCSid: -# $Id: install-mk,v 1.254 2024/05/06 20:41:08 sjg Exp $ +# $Id: install-mk,v 1.255 2024/06/24 02:21:00 sjg Exp $ # # @(#) Copyright (c) 1994-2024 Simon J. Gerraty # @@ -74,7 +74,7 @@ # sjg@crufty.net # -MK_VERSION=20240504 +MK_VERSION=20240616 OWNER= GROUP= MODE=444 diff --git a/parse.c b/parse.c index 70c6d6fe5157..3fbc71163a66 100644 --- a/parse.c +++ b/parse.c @@ -1,4 +1,4 @@ -/* $NetBSD: parse.c,v 1.723 2024/05/19 20:09:40 sjg Exp $ */ +/* $NetBSD: parse.c,v 1.731 2024/06/15 19:43:56 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 @@ -121,7 +121,7 @@ #include "pathnames.h" /* "@(#)parse.c 8.3 (Berkeley) 3/19/94" */ -MAKE_RCSID("$NetBSD: parse.c,v 1.723 2024/05/19 20:09:40 sjg Exp $"); +MAKE_RCSID("$NetBSD: parse.c,v 1.731 2024/06/15 19:43:56 rillig Exp $"); /* Detects a multiple-inclusion guard in a makefile. */ typedef enum { @@ -234,9 +234,9 @@ static GNodeList *targets; #ifdef CLEANUP /* * All shell commands for all targets, in no particular order and possibly - * with duplicates. Kept in a separate list since the commands from .USE or - * .USEBEFORE nodes are shared with other GNodes, thereby giving up the - * easily understandable ownership over the allocated strings. + * with duplicate values. Kept in a separate list since the commands from + * .USE or .USEBEFORE nodes are shared with other GNodes, thereby giving up + * the easily understandable ownership over the allocated strings. */ static StringList targCmds = LST_INIT; #endif @@ -537,9 +537,9 @@ ParseVErrorInternal(FILE *f, bool useVars, const GNode *gn, (void)fprintf(f, "%s: ", progname); PrintLocation(f, useVars, gn); - fprintf(f, "%s", EvalStack_Details()); if (level == PARSE_WARNING) (void)fprintf(f, "warning: "); + fprintf(f, "%s", EvalStack_Details()); (void)vfprintf(f, fmt, ap); (void)fprintf(f, "\n"); (void)fflush(f); @@ -619,7 +619,7 @@ HandleMessage(ParseErrorLevel level, const char *levelName, const char *umsg) return; } - xmsg = Var_Subst(umsg, SCOPE_CMDLINE, VARE_WANTRES); + xmsg = Var_Subst(umsg, SCOPE_CMDLINE, VARE_EVAL); /* TODO: handle errors */ Parse_Error(level, "%s", xmsg); @@ -652,7 +652,7 @@ LinkSource(GNode *pgn, GNode *cgn, bool isSpecial) Lst_Append(&cgn->parents, pgn); if (DEBUG(PARSE)) { - debug_printf("# LinkSource: added child %s - %s\n", + debug_printf("Target \"%s\" depends on \"%s\"\n", pgn->name, cgn->name); Targ_PrintNode(pgn, 0); Targ_PrintNode(cgn, 0); @@ -925,8 +925,7 @@ ParseDependencyTargetWord(char **pp, const char *lstart) break; if (*p == '$') { - FStr val = Var_Parse(&p, SCOPE_CMDLINE, - VARE_PARSE_ONLY); + FStr val = Var_Parse(&p, SCOPE_CMDLINE, VARE_PARSE); /* TODO: handle errors */ FStr_Done(&val); } else @@ -1279,13 +1278,12 @@ IncludeFile(const char *file, bool isSystem, bool depinc, bool silent) } if (SkipGuarded(fullname)) - return; + goto done; if ((fd = open(fullname, O_RDONLY)) == -1) { if (!silent) Parse_Error(PARSE_FATAL, "Cannot open %s", fullname); - free(fullname); - return; + goto done; } buf = LoadFile(fullname, fd); @@ -1294,6 +1292,7 @@ IncludeFile(const char *file, bool isSystem, bool depinc, bool silent) Parse_PushInput(fullname, 1, 0, buf, NULL); if (depinc) doing_depend = depinc; /* only turn it on */ +done: free(fullname); } @@ -1811,7 +1810,7 @@ VarCheckSyntax(VarAssignOp op, const char *uvalue, GNode *scope) if (opts.strict) { if (op != VAR_SUBST && strchr(uvalue, '$') != NULL) { char *parsedValue = Var_Subst(uvalue, - scope, VARE_PARSE_ONLY); + scope, VARE_PARSE); /* TODO: handle errors */ free(parsedValue); } @@ -1837,7 +1836,8 @@ VarAssign_EvalSubst(GNode *scope, const char *name, const char *uvalue, if (!Var_ExistsExpand(scope, name)) Var_SetExpand(scope, name, ""); - evalue = Var_Subst(uvalue, scope, VARE_KEEP_DOLLAR_UNDEF); + evalue = Var_Subst(uvalue, scope, + VARE_EVAL_KEEP_DOLLAR_AND_UNDEFINED); /* TODO: handle errors */ Var_SetExpand(scope, name, evalue); @@ -1854,7 +1854,7 @@ VarAssign_EvalShell(const char *name, const char *uvalue, GNode *scope, char *output, *error; cmd = FStr_InitRefer(uvalue); - Var_Expand(&cmd, SCOPE_CMDLINE, VARE_UNDEFERR); + Var_Expand(&cmd, SCOPE_CMDLINE, VARE_EVAL_DEFINED); output = Cmd_Exec(cmd.str, &error); Var_SetExpand(scope, name, output); @@ -2036,7 +2036,7 @@ ParseInclude(char *directive) *p = '\0'; - Var_Expand(&file, SCOPE_CMDLINE, VARE_WANTRES); + Var_Expand(&file, SCOPE_CMDLINE, VARE_EVAL); IncludeFile(file.str, endc == '>', directive[0] == 'd', silent); FStr_Done(&file); } @@ -2245,7 +2245,7 @@ ParseTraditionalInclude(char *line) pp_skip_whitespace(&file); - all_files = Var_Subst(file, SCOPE_CMDLINE, VARE_WANTRES); + all_files = Var_Subst(file, SCOPE_CMDLINE, VARE_EVAL); /* TODO: handle errors */ for (file = all_files; !done; file = p + 1) { @@ -2288,7 +2288,7 @@ ParseGmakeExport(char *line) /* * Expand the value before putting it in the environment. */ - value = Var_Subst(value, SCOPE_CMDLINE, VARE_WANTRES); + value = Var_Subst(value, SCOPE_CMDLINE, VARE_EVAL); /* TODO: handle errors */ setenv(variable, value, 1); @@ -2319,9 +2319,15 @@ ParseEOF(void) Cond_EndFile(); - if (curFile->guardState == GS_DONE) - HashTable_Set(&guards, curFile->name.str, curFile->guard); - else if (curFile->guard != NULL) { + if (curFile->guardState == GS_DONE) { + HashEntry *he = HashTable_CreateEntry(&guards, + curFile->name.str, NULL); + if (he->value != NULL) { + free(((Guard *)he->value)->name); + free(he->value); + } + HashEntry_Set(he, curFile->guard); + } else if (curFile->guard != NULL) { free(curFile->guard->name); free(curFile->guard); } @@ -2684,6 +2690,13 @@ FinishDependencyGroup(void) targets = NULL; } +#ifdef CLEANUP +void Parse_RegisterCommand(char *cmd) +{ + Lst_Append(&targCmds, cmd); +} +#endif + /* Add the command to each target from the current dependency spec. */ static void ParseLine_ShellCommand(const char *p) @@ -2706,9 +2719,7 @@ ParseLine_ShellCommand(const char *p) GNode *gn = ln->datum; GNode_AddCommand(gn, cmd); } -#ifdef CLEANUP - Lst_Append(&targCmds, cmd); -#endif + Parse_RegisterCommand(cmd); } } @@ -2764,6 +2775,8 @@ ParseDirective(char *line) Var_Undef(arg); else if (Substring_Equals(dir, "export")) Var_Export(VEM_PLAIN, arg); + else if (Substring_Equals(dir, "export-all")) + Var_Export(VEM_ALL, arg); else if (Substring_Equals(dir, "export-env")) Var_Export(VEM_ENV, arg); else if (Substring_Equals(dir, "export-literal")) @@ -2884,7 +2897,7 @@ ParseDependencyLine(char *line) * empty string var_Error, which cannot be detected in the result of * Var_Subst. */ - emode = opts.strict ? VARE_WANTRES : VARE_UNDEFERR; + emode = opts.strict ? VARE_EVAL : VARE_EVAL_DEFINED; expanded_line = Var_Subst(line, SCOPE_CMDLINE, emode); /* TODO: handle errors */ @@ -2990,7 +3003,7 @@ Parse_End(void) assert(includes.len == 0); Vector_Done(&includes); HashIter_Init(&hi, &guards); - while (HashIter_Next(&hi) != NULL) { + while (HashIter_Next(&hi)) { Guard *guard = hi.entry->value; free(guard->name); free(guard); diff --git a/suff.c b/suff.c index de00cc4e8581..e1621eeab378 100644 --- a/suff.c +++ b/suff.c @@ -1,4 +1,4 @@ -/* $NetBSD: suff.c,v 1.378 2024/02/07 06:43:02 rillig Exp $ */ +/* $NetBSD: suff.c,v 1.380 2024/06/02 15:31:26 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 @@ -115,7 +115,7 @@ #include "dir.h" /* "@(#)suff.c 8.4 (Berkeley) 3/21/94" */ -MAKE_RCSID("$NetBSD: suff.c,v 1.378 2024/02/07 06:43:02 rillig Exp $"); +MAKE_RCSID("$NetBSD: suff.c,v 1.380 2024/06/02 15:31:26 rillig Exp $"); typedef List SuffixList; typedef ListNode SuffixListNode; @@ -1223,6 +1223,7 @@ ExpandWildcards(GNodeListNode *cln, GNode *pgn) DEBUG1(SUFF, "%s...", name); gn = Targ_GetNode(name); + free(name); /* Insert gn before the original child. */ Lst_InsertBefore(&pgn->children, cln, gn); @@ -1273,7 +1274,7 @@ ExpandChildrenRegular(char *p, GNode *pgn, GNodeList *members) } else if (*p == '$') { /* Skip over the expression. */ const char *nested_p = p; - FStr junk = Var_Parse(&nested_p, pgn, VARE_PARSE_ONLY); + FStr junk = Var_Parse(&nested_p, pgn, VARE_PARSE); /* TODO: handle errors */ if (junk.str == var_Error) { Parse_Error(PARSE_FATAL, @@ -1343,7 +1344,7 @@ ExpandChildren(GNodeListNode *cln, GNode *pgn) } DEBUG1(SUFF, "Expanding \"%s\"...", cgn->name); - expanded = Var_Subst(cgn->name, pgn, VARE_UNDEFERR); + expanded = Var_Subst(cgn->name, pgn, VARE_EVAL_DEFINED); /* TODO: handle errors */ { diff --git a/targ.c b/targ.c index 35745cd21fe0..5fa9247cdedb 100644 --- a/targ.c +++ b/targ.c @@ -1,4 +1,4 @@ -/* $NetBSD: targ.c,v 1.181 2024/04/27 17:33:47 rillig Exp $ */ +/* $NetBSD: targ.c,v 1.183 2024/05/25 21:07:48 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 @@ -107,7 +107,7 @@ #include "dir.h" /* "@(#)targ.c 8.2 (Berkeley) 3/19/94" */ -MAKE_RCSID("$NetBSD: targ.c,v 1.181 2024/04/27 17:33:47 rillig Exp $"); +MAKE_RCSID("$NetBSD: targ.c,v 1.183 2024/05/25 21:07:48 rillig Exp $"); /* * All target nodes that appeared on the left-hand side of one of the @@ -219,6 +219,8 @@ GNode_New(const char *name) static void GNode_Free(GNode *gn) { + Var_DeleteAll(gn); + free(gn->name); free(gn->uname); free(gn->path); @@ -236,20 +238,6 @@ GNode_Free(GNode *gn) Lst_Done(&gn->order_succ); Lst_Done(&gn->cohorts); - /* - * Do not free the variables themselves, even though they are owned - * by this node. - * - * XXX: For the nodes that represent targets or sources (and not - * SCOPE_GLOBAL), it should be safe to free the variables as well, - * since each node manages the memory for all its variables itself. - * - * XXX: The GNodes that are only used as variable scopes (SCOPE_CMD, - * SCOPE_GLOBAL, SCOPE_INTERNAL) are not freed at all (see Var_End, - * where they are not mentioned). These may be freed if their - * variable values are indeed not used anywhere else (see Trace_Init - * for the only suspicious use). - */ HashTable_Done(&gn->vars); /* diff --git a/unit-tests/Makefile b/unit-tests/Makefile index 5960a0621ddd..1b073af009db 100644 --- a/unit-tests/Makefile +++ b/unit-tests/Makefile @@ -1,6 +1,6 @@ -# $Id: Makefile,v 1.216 2024/04/30 16:42:50 sjg Exp $ +# $Id: Makefile,v 1.219 2024/06/01 16:14:47 sjg Exp $ # -# $NetBSD: Makefile,v 1.344 2024/04/30 16:41:32 sjg Exp $ +# $NetBSD: Makefile,v 1.347 2024/06/01 15:54:40 sjg Exp $ # # Unit tests for make(1) # @@ -745,11 +745,11 @@ all: ${OUTFILES} CLEANFILES= *.rawout *.out *.status *.tmp *.core *.tmp CLEANFILES+= obj*.[och] lib*.a # posix1.mk CLEANFILES+= issue* .[ab]* # suffixes.mk -CLEANDIRS= dir dummy # posix1.mk +CLEANDIRS= dir dummy *.tmp # posix1.mk clean: - rm -f ${CLEANFILES} rm -rf ${CLEANDIRS} + rm -f ${CLEANFILES} TEST_MAKE?= ${.MAKE} TOOL_SED?= sed @@ -864,6 +864,11 @@ test: ${OUTFILES} .PHONY echo "Failed tests: $${failed}" ; false ; \ else \ echo "All tests passed" ; \ + lua=${LUA:Ulua} ; \ + have_lua=$$("$$lua" -e 'print "yes"' 2>&1) ; \ + if [ "$$have_lua" = "yes" -a -s ${.CURDIR}/check-expect.lua ]; then \ + (cd ${.CURDIR} && "$$lua" ./check-expect.lua *.mk); \ + fi; \ fi accept: diff --git a/unit-tests/cond-func-empty.mk b/unit-tests/cond-func-empty.mk index 057b175a7693..d377a84ff574 100644 --- a/unit-tests/cond-func-empty.mk +++ b/unit-tests/cond-func-empty.mk @@ -1,4 +1,4 @@ -# $NetBSD: cond-func-empty.mk,v 1.24 2023/12/19 19:33:40 rillig Exp $ +# $NetBSD: cond-func-empty.mk,v 1.25 2024/06/02 15:31:26 rillig Exp $ # # Tests for the empty() function in .if conditions, which tests an # expression for emptiness. @@ -104,10 +104,10 @@ WORD= word # Now the variable named " " gets a non-empty value, which demonstrates that # neither leading nor trailing spaces are trimmed in the argument of the -# function. If the spaces were trimmed, the variable name would be "" and -# that variable is indeed undefined. Since CondParser_FuncCallEmpty calls -# Var_Parse without VARE_UNDEFERR, the value of the undefined variable "" -# would be returned as an empty string. +# function. If the spaces were trimmed, the variable name would be "", and +# that variable is indeed undefined. Since CondParser_FuncCallEmpty allows +# subexpressions to be based on undefined variables, the value of the +# undefined variable "" would be returned as an empty string. ${:U }= space .if empty( ) . error @@ -194,15 +194,15 @@ ${:U WORD }= variable name with spaces # wrong variable name should have been discarded quickly after parsing it, to # prevent it from doing any harm. # -# The expression was expanded, and this was wrong. The -# expansion was done without VARE_WANTRES (called VARF_WANTRES back then) -# though. This had the effect that the ${:U1} from the value of VARNAME -# expanded to an empty string. This in turn created the seemingly recursive -# definition VARNAME=${VARNAME}, and that definition was never meant to be -# expanded. +# The expression was evaluated, and this was wrong. The evaluation was done +# without VARE_EVAL (called VARF_WANTRES back then) though. This had the +# effect that the ${:U1} from the value of VARNAME evaluated to an empty +# string. This in turn created the seemingly recursive definition +# VARNAME=${VARNAME}, and that definition was evaluated even though it was +# never meant to be evaluated. # -# This was fixed by expanding nested expressions in the variable name -# only if the flag VARE_WANTRES is given. +# This was fixed by evaluating nested expressions in the variable name only +# when the whole expression was evaluated as well. VARNAME= ${VARNAME${:U1}} .if defined(VARNAME${:U2}) && !empty(VARNAME${:U2}) .endif diff --git a/unit-tests/dep-duplicate.exp b/unit-tests/dep-duplicate.exp index 039145f8fd97..dc3b40214d85 100644 --- a/unit-tests/dep-duplicate.exp +++ b/unit-tests/dep-duplicate.exp @@ -1,4 +1,4 @@ -make: "dep-duplicate.inc" line 1: warning: duplicate script for target "all" ignored +make: "dep-duplicate.tmp" line 1: warning: duplicate script for target "all" ignored make: "dep-duplicate.main" line 3: warning: using previous script for "all" defined here main-output exit status 0 diff --git a/unit-tests/dep-duplicate.mk b/unit-tests/dep-duplicate.mk index 6f64ba1c1981..8b05b9fb062d 100644 --- a/unit-tests/dep-duplicate.mk +++ b/unit-tests/dep-duplicate.mk @@ -1,4 +1,4 @@ -# $NetBSD: dep-duplicate.mk,v 1.3 2022/01/20 19:24:53 rillig Exp $ +# $NetBSD: dep-duplicate.mk,v 1.4 2024/05/25 21:11:30 rillig Exp $ # # Test for a target whose commands are defined twice. This generates a # warning, not an error, so ensure that the correct commands are kept. @@ -13,9 +13,9 @@ all: .PHONY echo '# empty line 1'; \ echo '# empty line 2'; \ echo 'all:; @echo main-output'; \ - echo '.include "dep-duplicate.inc"' + echo '.include "dep-duplicate.tmp"' - @exec > dep-duplicate.inc; \ + @exec > dep-duplicate.tmp; \ echo 'all:; @echo inc-output' # The main file must be specified using a relative path, just like the @@ -24,4 +24,4 @@ all: .PHONY @${MAKE} -r -f dep-duplicate.main @rm -f dep-duplicate.main - @rm -f dep-duplicate.inc + @rm -f dep-duplicate.tmp diff --git a/unit-tests/dep-var.exp b/unit-tests/dep-var.exp index cc229d32e6d4..a12c2e7a8e96 100755 --- a/unit-tests/dep-var.exp +++ b/unit-tests/dep-var.exp @@ -16,9 +16,9 @@ Result of ${:U\$)} is "$)" (eval-defined, defined) Global: .ALLTARGETS = all ${DEF2} a-${DEF2}-b ${UNDEF3} 1-${INDIRECT_1}-1 $$) undef1 Global: .ALLTARGETS = all ${DEF2} a-${DEF2}-b ${UNDEF3} 1-${INDIRECT_1}-1 $$) undef1 def2 Global: .ALLTARGETS = all ${DEF2} a-${DEF2}-b ${UNDEF3} 1-${INDIRECT_1}-1 $$) undef1 def2 a-def2-b -Var_Parse: $INDIRECT_2-2-1 $): (parse-only) +Var_Parse: $INDIRECT_2-2-1 $): (parse) Global: .ALLTARGETS = all ${DEF2} a-${DEF2}-b ${UNDEF3} 1-${INDIRECT_1}-1 $$) undef1 def2 a-def2-b 1-2-$INDIRECT_2-2-1 -Var_Parse: $): (parse-only) +Var_Parse: $): (parse) Global: .ALLTARGETS = all ${DEF2} a-${DEF2}-b ${UNDEF3} 1-${INDIRECT_1}-1 $$) undef1 def2 a-def2-b 1-2-$INDIRECT_2-2-1 $) Global: .MAKEFLAGS = -r -k -d v -d Global: .MAKEFLAGS = -r -k -d v -d 0 diff --git a/unit-tests/dep-var.mk b/unit-tests/dep-var.mk index 8c1636bbdec2..fa720c3af610 100755 --- a/unit-tests/dep-var.mk +++ b/unit-tests/dep-var.mk @@ -1,4 +1,4 @@ -# $NetBSD: dep-var.mk,v 1.11 2023/12/19 19:33:40 rillig Exp $ +# $NetBSD: dep-var.mk,v 1.12 2024/06/02 15:31:26 rillig Exp $ # # Tests for variable references in dependency declarations. # @@ -84,8 +84,8 @@ all: $$$$) # in normal mode since ParseDependency does not handle any errors after # calling Var_Parse. # expect: Var_Parse: ${:U\$)}: (eval-defined) -# expect: Var_Parse: $INDIRECT_2-2-1 $): (parse-only) -# expect: Var_Parse: $): (parse-only) +# expect: Var_Parse: $INDIRECT_2-2-1 $): (parse) +# expect: Var_Parse: $): (parse) undef1 def2 a-def2-b 1-2-$$INDIRECT_2-2-1 ${:U\$)}: @echo ${.TARGET:Q} diff --git a/unit-tests/directive-export-literal.exp b/unit-tests/directive-export-literal.exp index c5557e363666..baece9e32217 100644 --- a/unit-tests/directive-export-literal.exp +++ b/unit-tests/directive-export-literal.exp @@ -1,2 +1,5 @@ value with ${UNEXPANDED} expression +value literal +value indirect +value ${indirect:L} exit status 0 diff --git a/unit-tests/directive-export-literal.mk b/unit-tests/directive-export-literal.mk index 5fafa4a7282d..105af0102b83 100644 --- a/unit-tests/directive-export-literal.mk +++ b/unit-tests/directive-export-literal.mk @@ -1,4 +1,4 @@ -# $NetBSD: directive-export-literal.mk,v 1.7 2020/12/13 01:07:54 rillig Exp $ +# $NetBSD: directive-export-literal.mk,v 1.8 2024/06/01 18:44:05 rillig Exp $ # # Tests for the .export-literal directive, which exports a variable value # without expanding it. @@ -9,5 +9,28 @@ UT_VAR= value with ${UNEXPANDED} expression .export-literal # oops: missing argument +# After a variable whose value does not contain a '$' is exported, a following +# .export-literal can be skipped, to avoid a setenv call, which may leak +# memory on some platforms. +UT_TWICE_LITERAL= value literal +.export UT_TWICE_LITERAL +.export-literal UT_TWICE_LITERAL + +# XXX: After an .export, an .export-literal has no effect, even when the +# variable value contains a '$'. +UT_TWICE_EXPR= value ${indirect:L} +.export UT_TWICE_EXPR +.export-literal UT_TWICE_EXPR + +# After an .export, an .unexport resets the variable's exported state, +# re-enabling a later .export-literal. +UT_TWICE_EXPR_UNEXPORT= value ${indirect:L} +.export UT_TWICE_EXPR_UNEXPORT +.unexport UT_TWICE_EXPR_UNEXPORT +.export-literal UT_TWICE_EXPR_UNEXPORT + all: @echo "$$UT_VAR" + @echo "$$UT_TWICE_LITERAL" + @echo "$$UT_TWICE_EXPR" + @echo "$$UT_TWICE_EXPR_UNEXPORT" diff --git a/unit-tests/directive-export.exp b/unit-tests/directive-export.exp index 774a814570e3..02271508cf42 100644 --- a/unit-tests/directive-export.exp +++ b/unit-tests/directive-export.exp @@ -1,4 +1,5 @@ -make: "directive-export.mk" line 56: 00:00:00 -make: "directive-export.mk" line 61: 00:00:00 -make: "directive-export.mk" line 64: 16:00:00 +make: "directive-export.mk" line 36: warning: .export requires an argument. +make: "directive-export.mk" line 60: 00:00:00 +make: "directive-export.mk" line 65: 00:00:00 +make: "directive-export.mk" line 68: 16:00:00 exit status 0 diff --git a/unit-tests/directive-export.mk b/unit-tests/directive-export.mk index 08109814bcfd..d2ed7a1a1efb 100644 --- a/unit-tests/directive-export.mk +++ b/unit-tests/directive-export.mk @@ -1,4 +1,4 @@ -# $NetBSD: directive-export.mk,v 1.10 2023/11/19 09:45:19 rillig Exp $ +# $NetBSD: directive-export.mk,v 1.12 2024/06/01 10:06:23 rillig Exp $ # # Tests for the .export directive. # @@ -28,7 +28,11 @@ VAR= value $$ ${INDIRECT} . error .endif -# No syntactical argument means to export all variables. +# Before var.c 1.1117 from 2024-06-01, a plain ".export" without a syntactical +# argument exported all global variables. This case could be triggered +# unintentionally by writing a line of the form ".export ${VARNAMES}" to a +# makefile, when VARNAMES was an empty list. +# expect+1: warning: .export requires an argument. .export # An empty argument means no additional variables to export. diff --git a/unit-tests/directive-for-empty.exp b/unit-tests/directive-for-empty.exp index 5cc3ac846b36..f0389d329341 100644 --- a/unit-tests/directive-for-empty.exp +++ b/unit-tests/directive-for-empty.exp @@ -1,7 +1,4 @@ make: "directive-for-empty.mk" line 22: 2 -make: "directive-for-empty.mk" line 38: Missing argument for ".error" -make: "directive-for-empty.mk" line 38: Missing argument for ".error" -make: "directive-for-empty.mk" line 38: Missing argument for ".error" For: end for 1 For: loop body with i = value: # The identifier 'empty' can only be used in conditions such as .if, .ifdef or @@ -22,6 +19,4 @@ CPPFLAGS+= -Dmessage="empty(i)" # condition directives, they can also occur in the modifier ':?', see # varmod-ifelse.mk. CPPFLAGS+= -Dmacro="${empty(i):?empty:not-empty}" -make: Fatal errors encountered -- cannot continue -make: stopped in unit-tests -exit status 1 +exit status 0 diff --git a/unit-tests/directive-for-empty.mk b/unit-tests/directive-for-empty.mk index 1c4cb0f1ad27..886f7430c682 100644 --- a/unit-tests/directive-for-empty.mk +++ b/unit-tests/directive-for-empty.mk @@ -1,4 +1,4 @@ -# $NetBSD: directive-for-empty.mk,v 1.3 2023/11/19 21:47:52 rillig Exp $ +# $NetBSD: directive-for-empty.mk,v 1.4 2024/05/31 07:13:12 rillig Exp $ # # Tests for .for loops containing conditions of the form 'empty(var:...)'. # @@ -26,23 +26,23 @@ # In conditions, the function call to 'empty' does not look like an # expression, therefore it is not replaced. Since there is no global variable -# named 'i', this expression makes for a leaky abstraction. If the .for +# named 'i', this condition makes for a leaky abstraction. If the .for # variables were real variables, calling 'empty' would work on them as well. .for i in 11 12 13 # Asking for an empty iteration variable does not make sense as the .for loop # splits the iteration items into words, and such a word cannot be empty. -. if empty(i) -# expect+3: Missing argument for ".error" -# expect+2: Missing argument for ".error" -# expect+1: Missing argument for ".error" -. error # due to the leaky abstraction +. if !empty(i) +. error # not reached, due to the leaky abstraction . endif -# The typical way of using 'empty' with variables from .for loops is pattern -# matching using the modifiers ':M' or ':N'. +# The typical way of mistakenly using 'empty' with variables from .for loops +# is pattern matching using the modifiers ':M' or ':N'. . if !empty(i:M*2*) -. if ${i} != "12" -. error -. endif +. error +. endif +# Instead of the 'empty' function, the variables from .for loops can be +# queried using conditions of the form '${var:...} != ""'. +. if $i == "12" && ${i:M*2*} != "12" +. error . endif .endfor @@ -122,3 +122,5 @@ CPPFLAGS+= -Dmacro="${empty(i):?empty:not-empty}" # TODO: Add code that demonstrates the current interaction between variables # from .for loops and the modifiers mentioned above. + +all: diff --git a/unit-tests/directive-for-errors.mk b/unit-tests/directive-for-errors.mk index 1bd4f31d383a..e64bb97b6560 100644 --- a/unit-tests/directive-for-errors.mk +++ b/unit-tests/directive-for-errors.mk @@ -1,4 +1,4 @@ -# $NetBSD: directive-for-errors.mk,v 1.10 2024/04/20 10:18:55 rillig Exp $ +# $NetBSD: directive-for-errors.mk,v 1.11 2024/06/01 11:24:11 rillig Exp $ # # Tests for error handling in .for loops. @@ -41,7 +41,7 @@ ${:U\$}= dollar # see whether the "variable" '$' is local ${:U\\}= backslash # see whether the "variable" '\' is local # expect+1: invalid character '$' in .for loop variable name -.for $ \ in 1 2 3 4 +.for a b $ \ in 1 2 3 4 . info Dollar $$ ${$} $($) and backslash $\ ${\} $(\). .endfor diff --git a/unit-tests/export-all.mk b/unit-tests/export-all.mk index 0d741083441b..bf1ecd5716e9 100644 --- a/unit-tests/export-all.mk +++ b/unit-tests/export-all.mk @@ -1,4 +1,4 @@ -# $NetBSD: export-all.mk,v 1.5 2020/10/24 08:50:17 rillig Exp $ +# $NetBSD: export-all.mk,v 1.6 2024/06/01 06:26:36 sjg Exp $ UT_OK= good UT_F= fine @@ -15,7 +15,7 @@ UT_BADDIR= ${${here}/../${here:T}:L:${M_tAbad}:T} # this will be ok UT_OKDIR= ${${here}/../${here:T}:L:${M_tA}:T} -.export +.export-all FILTER_CMD= grep ^UT_ .include "export.mk" diff --git a/unit-tests/opt-debug-hash.exp b/unit-tests/opt-debug-hash.exp index 194b08daa5f2..d8b60450e222 100644 --- a/unit-tests/opt-debug-hash.exp +++ b/unit-tests/opt-debug-hash.exp @@ -1,4 +1,4 @@ -make: "opt-debug-hash.mk" line 12: Missing argument for ".error" +make: "opt-debug-hash.mk" line 13: Missing argument for ".error" make: Fatal errors encountered -- cannot continue HashTable targets: size=16 numEntries=0 maxchain=0 HashTable Global variables: size=16 numEntries= maxchain=3 diff --git a/unit-tests/opt-debug-hash.mk b/unit-tests/opt-debug-hash.mk index a1b21e145bf0..88edbedd6fd9 100644 --- a/unit-tests/opt-debug-hash.mk +++ b/unit-tests/opt-debug-hash.mk @@ -1,4 +1,4 @@ -# $NetBSD: opt-debug-hash.mk,v 1.4 2023/06/01 20:56:35 rillig Exp $ +# $NetBSD: opt-debug-hash.mk,v 1.5 2024/05/31 07:13:12 rillig Exp $ # # Tests for the -dh command line option, which adds debug logging for # hash tables. Even more detailed logging is available by compiling @@ -6,7 +6,8 @@ .MAKEFLAGS: -dh -# Force a parse error, to demonstrate the newline character in the diagnostic -# that had been missing before parse.c 1.655 from 2022-01-22. +# Force a parse error, to demonstrate the newline character in the "cannot +# continue" diagnostic that had been missing before parse.c 1.655 from +# 2022-01-22. # expect+1: Missing argument for ".error" .error diff --git a/unit-tests/parse-var.mk b/unit-tests/parse-var.mk index b35726e76efc..5c693f8efd1c 100644 --- a/unit-tests/parse-var.mk +++ b/unit-tests/parse-var.mk @@ -1,4 +1,4 @@ -# $NetBSD: parse-var.mk,v 1.9 2023/11/19 21:47:52 rillig Exp $ +# $NetBSD: parse-var.mk,v 1.10 2024/06/02 15:31:26 rillig Exp $ # # Tests for parsing expressions. # @@ -20,11 +20,11 @@ # # VarEvalMode: # parse +# parse-balanced # eval -# eval-undeferr -# eval-keep-dollar -# eval-keep-undef -# eval-keep-dollar-undef +# eval-defined +# eval-keep-undefined +# eval-keep-dollar-and-undefined # # Global mode: # without -dL diff --git a/unit-tests/recursive.mk b/unit-tests/recursive.mk index b97c4b37eabb..e1bde51138ca 100644 --- a/unit-tests/recursive.mk +++ b/unit-tests/recursive.mk @@ -1,4 +1,4 @@ -# $NetBSD: recursive.mk,v 1.7 2023/10/19 18:24:33 rillig Exp $ +# $NetBSD: recursive.mk,v 1.8 2024/06/02 15:31:26 rillig Exp $ # # In -dL mode, a variable may get expanded before it makes sense. # This would stop make from doing anything since the "recursive" error @@ -6,7 +6,7 @@ # # The purpose of evaluating that variable early was just to detect # whether there are unclosed variables. The variable value is therefore -# parsed with VARE_PARSE_ONLY for that purpose. +# parsed with VARE_PARSE for that purpose. # .MAKEFLAGS: -dL diff --git a/unit-tests/shell-csh.mk b/unit-tests/shell-csh.mk index 47313563d22b..21517d5dbd78 100644 --- a/unit-tests/shell-csh.mk +++ b/unit-tests/shell-csh.mk @@ -1,4 +1,4 @@ -# $NetBSD: shell-csh.mk,v 1.8 2021/04/04 09:58:51 rillig Exp $ +# $NetBSD: shell-csh.mk,v 1.9 2024/05/25 15:37:17 rillig Exp $ # # Tests for using a C shell for running the commands. @@ -6,7 +6,7 @@ CSH!= which csh 2> /dev/null || true # The shell path must be an absolute path. # This is only obvious in parallel mode since in compat mode, -# simple commands are executed via execve directly. +# simple commands are executed via execvp directly. .if ${CSH} != "" .SHELL: name="csh" path="${CSH}" .endif diff --git a/unit-tests/suff-incomplete.exp b/unit-tests/suff-incomplete.exp index acb5f0542dbe..a0603de34f11 100644 --- a/unit-tests/suff-incomplete.exp +++ b/unit-tests/suff-incomplete.exp @@ -17,7 +17,7 @@ ParseDependency(.a.c: ${.PREFIX}.dependency) defining transformation from `.a' to `.c' inserting ".a" (1) at end of list inserting ".c" (3) at end of list -# LinkSource: added child .a.c - ${.PREFIX}.dependency +Target ".a.c" depends on "${.PREFIX}.dependency" # .a.c, unmade, type OP_DEPENDS|OP_TRANSFORM, flags none # ${.PREFIX}.dependency, unmade, type none, flags none Parsing line 23: .DEFAULT: diff --git a/unit-tests/suff-main-several.exp b/unit-tests/suff-main-several.exp index d19a392f5962..8aa1ff4bf17d 100644 --- a/unit-tests/suff-main-several.exp +++ b/unit-tests/suff-main-several.exp @@ -58,7 +58,7 @@ ParseDependency(suff-main-several.1:) Parsing line 39: : Making ${.TARGET} out of nothing. Parsing line 40: next-main: suff-main-several.{2,3,4} ParseDependency(next-main: suff-main-several.{2,3,4}) -# LinkSource: added child next-main - suff-main-several.{2,3,4} +Target "next-main" depends on "suff-main-several.{2,3,4}" # next-main, unmade, type OP_DEPENDS|OP_HAS_COMMANDS, flags none # suff-main-several.{2,3,4}, unmade, type none, flags none Parsing line 42: .MAKEFLAGS: -d0 -dg1 diff --git a/unit-tests/var-eval-short.exp b/unit-tests/var-eval-short.exp index 6b42c27e22bc..4b553857df29 100644 --- a/unit-tests/var-eval-short.exp +++ b/unit-tests/var-eval-short.exp @@ -2,26 +2,26 @@ make: "var-eval-short.mk" line 46: while evaluating "${:Uword:@${FAIL}@expr@}": make: "var-eval-short.mk" line 46: Malformed conditional (0 && ${:Uword:@${FAIL}@expr@}) Parsing line 159: .if 0 && ${0:?${FAIL}then:${FAIL}else} CondParser_Eval: 0 && ${0:?${FAIL}then:${FAIL}else} -Var_Parse: ${0:?${FAIL}then:${FAIL}else} (parse-only) +Var_Parse: ${0:?${FAIL}then:${FAIL}else} (parse) Parsing modifier ${0:?...} -Var_Parse: ${FAIL}then:${FAIL}else} (parse-only) +Var_Parse: ${FAIL}then:${FAIL}else} (parse) Modifier part: "${FAIL}then" -Var_Parse: ${FAIL}else} (parse-only) +Var_Parse: ${FAIL}else} (parse) Modifier part: "${FAIL}else" -Result of ${0:?${FAIL}then:${FAIL}else} is "" (parse-only, defined) +Result of ${0:?${FAIL}then:${FAIL}else} is "" (parse, defined) Parsing line 167: DEFINED= defined Global: DEFINED = defined Parsing line 168: .if 0 && ${DEFINED:L:?${FAIL}then:${FAIL}else} CondParser_Eval: 0 && ${DEFINED:L:?${FAIL}then:${FAIL}else} -Var_Parse: ${DEFINED:L:?${FAIL}then:${FAIL}else} (parse-only) +Var_Parse: ${DEFINED:L:?${FAIL}then:${FAIL}else} (parse) Parsing modifier ${DEFINED:L} -Result of ${DEFINED:L} is "defined" (parse-only, regular) +Result of ${DEFINED:L} is "defined" (parse, regular) Parsing modifier ${DEFINED:?...} -Var_Parse: ${FAIL}then:${FAIL}else} (parse-only) +Var_Parse: ${FAIL}then:${FAIL}else} (parse) Modifier part: "${FAIL}then" -Var_Parse: ${FAIL}else} (parse-only) +Var_Parse: ${FAIL}else} (parse) Modifier part: "${FAIL}else" -Result of ${DEFINED:?${FAIL}then:${FAIL}else} is "defined" (parse-only, regular) +Result of ${DEFINED:?${FAIL}then:${FAIL}else} is "defined" (parse, regular) Parsing line 170: .MAKEFLAGS: -d0 ParseDependency(.MAKEFLAGS: -d0) Global: .MAKEFLAGS = -r -k -d cpv -d diff --git a/unit-tests/varmod-head.mk b/unit-tests/varmod-head.mk index f1a135cb328d..03d338d42742 100644 --- a/unit-tests/varmod-head.mk +++ b/unit-tests/varmod-head.mk @@ -1,4 +1,4 @@ -# $NetBSD: varmod-head.mk,v 1.5 2022/07/10 21:11:49 rillig Exp $ +# $NetBSD: varmod-head.mk,v 1.6 2024/06/01 18:44:05 rillig Exp $ # # Tests for the :H variable modifier, which returns the dirname of # each of the words in the variable value. @@ -61,4 +61,10 @@ _!= echo "The modifier ':H' generates an empty word." 1>&2; echo . error .endif +# If the ':H' is not directly followed by a delimiting ':' or '}', the +# ':from=to' modifier is tried as a fallback. +.if ${:U Head :Head=replaced} != "replaced" +. error +.endif + all: .PHONY diff --git a/unit-tests/varmod-ifelse.mk b/unit-tests/varmod-ifelse.mk index 3bf433027950..3cb2bdf8e855 100644 --- a/unit-tests/varmod-ifelse.mk +++ b/unit-tests/varmod-ifelse.mk @@ -1,4 +1,4 @@ -# $NetBSD: varmod-ifelse.mk,v 1.28 2024/04/23 22:51:28 rillig Exp $ +# $NetBSD: varmod-ifelse.mk,v 1.29 2024/06/02 15:31:26 rillig Exp $ # # Tests for the ${cond:?then:else} variable modifier, which evaluates either # the then-expression or the else-expression, depending on the condition. @@ -77,7 +77,7 @@ COND:= ${${UNDEF} == "":?bad-assign:bad-assign} # conditional expression". # # XXX: The left-hand side is enclosed in quotes. This results in Var_Parse -# being called without VARE_UNDEFERR. When ApplyModifier_IfElse +# being called without VARE_EVAL_DEFINED. When ApplyModifier_IfElse # returns AMR_CLEANUP as result, Var_Parse returns varUndefined since the # value of the expression is still undefined. CondParser_String is # then supposed to do proper error handling, but since varUndefined is local diff --git a/unit-tests/varmod-loop.exp b/unit-tests/varmod-loop.exp index 6aef3fe86857..ccfcd0b2dc06 100644 --- a/unit-tests/varmod-loop.exp +++ b/unit-tests/varmod-loop.exp @@ -1,12 +1,12 @@ -Parsing line 91: USE_8_DOLLARS= ${:U1:@var@${8_DOLLARS}@} ${8_DOLLARS} $$$$$$$$ -Parsing line 92: .if ${USE_8_DOLLARS} != "\$\$\$\$ \$\$\$\$ \$\$\$\$" +Parsing line 89: USE_8_DOLLARS= ${:U1:@var@${8_DOLLARS}@} ${8_DOLLARS} $$$$$$$$ +Parsing line 90: .if ${USE_8_DOLLARS} != "\$\$\$\$ \$\$\$\$ \$\$\$\$" CondParser_Eval: ${USE_8_DOLLARS} != "\$\$\$\$ \$\$\$\$ \$\$\$\$" Comparing "$$$$ $$$$ $$$$" != "$$$$ $$$$ $$$$" -Parsing line 96: SUBST_CONTAINING_LOOP:= ${USE_8_DOLLARS} -Parsing line 118: .if ${SUBST_CONTAINING_LOOP} != "\$\$ \$\$\$\$ \$\$\$\$" +Parsing line 94: SUBST_CONTAINING_LOOP:= ${USE_8_DOLLARS} +Parsing line 116: .if ${SUBST_CONTAINING_LOOP} != "\$\$ \$\$\$\$ \$\$\$\$" CondParser_Eval: ${SUBST_CONTAINING_LOOP} != "\$\$ \$\$\$\$ \$\$\$\$" Comparing "$$ $$$$ $$$$" != "$$ $$$$ $$$$" -Parsing line 121: .MAKEFLAGS: -d0 +Parsing line 119: .MAKEFLAGS: -d0 ParseDependency(.MAKEFLAGS: -d0) :varname-overwriting-target: :x1y x2y x3y: :: mod-loop-dollar:1: diff --git a/unit-tests/varmod-loop.mk b/unit-tests/varmod-loop.mk index 64cc6ca85043..4b4d4d32a058 100644 --- a/unit-tests/varmod-loop.mk +++ b/unit-tests/varmod-loop.mk @@ -1,4 +1,4 @@ -# $NetBSD: varmod-loop.mk,v 1.24 2023/11/19 21:47:52 rillig Exp $ +# $NetBSD: varmod-loop.mk,v 1.26 2024/06/02 15:31:26 rillig Exp $ # # Tests for the expression modifier ':@var@body@', which replaces each word of # the expression with the expanded body, which may contain references to the @@ -82,10 +82,8 @@ mod-loop-dollar: 8_DOLLARS= $$$$$$$$ # This string literal is written with 8 dollars, and this is saved as the # variable value. But as soon as this value is evaluated, it goes through -# Var_Subst, which replaces each '$$' with a single '$'. This could be -# prevented by VARE_EVAL_KEEP_DOLLAR, but that flag is usually removed -# before expanding subexpressions. See ApplyModifier_Loop and -# ParseModifierPart for examples. +# Var_Subst, which replaces each '$$' with a single '$'. +# See ApplyModifier_Loop and ParseModifierPart for examples. # .MAKEFLAGS: -dcp USE_8_DOLLARS= ${:U1:@var@${8_DOLLARS}@} ${8_DOLLARS} $$$$$$$$ @@ -95,11 +93,11 @@ USE_8_DOLLARS= ${:U1:@var@${8_DOLLARS}@} ${8_DOLLARS} $$$$$$$$ # SUBST_CONTAINING_LOOP:= ${USE_8_DOLLARS} # The ':=' assignment operator evaluates the variable value using the mode -# VARE_KEEP_DOLLAR_UNDEF, which means that some dollar signs are preserved, -# but not all. The dollar signs in the top-level expression and in the -# indirect ${8_DOLLARS} are preserved. +# VARE_EVAL_KEEP_DOLLAR_AND_UNDEFINED, which means that some dollar signs are +# preserved, but not all. The dollar signs in the top-level expression and in +# the indirect ${8_DOLLARS} are preserved. # -# The variable modifier :@var@ does not preserve the dollar signs though, no +# The modifier :@var@ does not preserve the dollar signs though, no # matter in which context it is evaluated. What happens in detail is: # First, the modifier part "${8_DOLLARS}" is parsed without expanding it. # Next, each word of the value is expanded on its own, and at this moment diff --git a/unit-tests/varmod-match-escape.exp b/unit-tests/varmod-match-escape.exp index d6bd804a7e89..cf5aeab5b39d 100755 --- a/unit-tests/varmod-match-escape.exp +++ b/unit-tests/varmod-match-escape.exp @@ -34,8 +34,8 @@ make: "varmod-match-escape.mk" line 43: warning: XXX: Oops Global: .MAKEFLAGS = -r -k -d cv -d Global: .MAKEFLAGS = -r -k -d cv -d 0 make: "varmod-match-escape.mk" line 69: while evaluating "${:U\$:M\$} != """: Dollar followed by nothing -make: "varmod-match-escape.mk" line 110: while evaluating variable "WORDS": warning: Unfinished character list in pattern '[A-]' of modifier ':M' -make: "varmod-match-escape.mk" line 110: while evaluating variable "WORDS": warning: Unfinished character list in pattern '[^A-]' of modifier ':M' +make: "varmod-match-escape.mk" line 110: warning: while evaluating variable "WORDS": Unfinished character list in pattern '[A-]' of modifier ':M' +make: "varmod-match-escape.mk" line 110: warning: while evaluating variable "WORDS": Unfinished character list in pattern '[^A-]' of modifier ':M' make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/unit-tests/varmod-match-escape.mk b/unit-tests/varmod-match-escape.mk index d39ece1cba2c..fba42ef03054 100755 --- a/unit-tests/varmod-match-escape.mk +++ b/unit-tests/varmod-match-escape.mk @@ -1,4 +1,4 @@ -# $NetBSD: varmod-match-escape.mk,v 1.13 2024/04/20 10:18:55 rillig Exp $ +# $NetBSD: varmod-match-escape.mk,v 1.14 2024/06/15 19:43:56 rillig Exp $ # # As of 2020-08-01, the :M and :N modifiers interpret backslashes differently, # depending on whether there was an expression somewhere before the @@ -105,8 +105,8 @@ EXP.[^A-]]= a EXP.[^A-]]]= a] .for pattern in [A-] [A-]] [A-]]] [^A-] [^A-]] [^A-]]] -# expect+2: while evaluating variable "WORDS": warning: Unfinished character list in pattern '[A-]' of modifier ':M' -# expect+1: while evaluating variable "WORDS": warning: Unfinished character list in pattern '[^A-]' of modifier ':M' +# expect+2: warning: while evaluating variable "WORDS": Unfinished character list in pattern '[A-]' of modifier ':M' +# expect+1: warning: while evaluating variable "WORDS": Unfinished character list in pattern '[^A-]' of modifier ':M' . if ${WORDS:M${pattern}} != ${EXP.${pattern}} . warning ${pattern}: ${WORDS:M${pattern}} != ${EXP.${pattern}} . endif diff --git a/unit-tests/varmod-match.exp b/unit-tests/varmod-match.exp index 3425eae2d421..ddf02c561e65 100644 --- a/unit-tests/varmod-match.exp +++ b/unit-tests/varmod-match.exp @@ -1,14 +1,14 @@ -make: "varmod-match.mk" line 290: while evaluating variable "WORDS": warning: Unfinished character list in pattern 'a[' of modifier ':M' -make: "varmod-match.mk" line 298: while evaluating variable "WORDS": warning: Unfinished character list in pattern 'a[^' of modifier ':M' -make: "varmod-match.mk" line 306: while evaluating variable "WORDS": warning: Unfinished character list in pattern '[-x1-3' of modifier ':M' -make: "varmod-match.mk" line 314: while evaluating variable "WORDS": warning: Unfinished character list in pattern '*[-x1-3' of modifier ':M' -make: "varmod-match.mk" line 323: while evaluating variable "WORDS": warning: Unfinished character list in pattern '[^-x1-3' of modifier ':M' -make: "varmod-match.mk" line 337: while evaluating variable "WORDS": warning: Unfinished character list in pattern '?[\' of modifier ':M' -make: "varmod-match.mk" line 345: while evaluating variable "WORDS": warning: Unfinished character range in pattern '[x-' of modifier ':M' -make: "varmod-match.mk" line 357: while evaluating variable "WORDS": warning: Unfinished character range in pattern '[^x-' of modifier ':M' -make: "varmod-match.mk" line 365: while evaluating variable " : :: ": warning: Unfinished character list in pattern '[' of modifier ':M' -make: "varmod-match.mk" line 365: while evaluating variable " : :: ": Unknown modifier "]" -make: "varmod-match.mk" line 365: Malformed conditional (${ ${:U\:} ${:U\:\:} :L:M[:]} != ":") +make: "varmod-match.mk" line 289: warning: while evaluating variable "WORDS": Unfinished character list in pattern 'a[' of modifier ':M' +make: "varmod-match.mk" line 297: warning: while evaluating variable "WORDS": Unfinished character list in pattern 'a[^' of modifier ':M' +make: "varmod-match.mk" line 305: warning: while evaluating variable "WORDS": Unfinished character list in pattern '[-x1-3' of modifier ':M' +make: "varmod-match.mk" line 313: warning: while evaluating variable "WORDS": Unfinished character list in pattern '*[-x1-3' of modifier ':M' +make: "varmod-match.mk" line 322: warning: while evaluating variable "WORDS": Unfinished character list in pattern '[^-x1-3' of modifier ':M' +make: "varmod-match.mk" line 336: warning: while evaluating variable "WORDS": Unfinished character list in pattern '?[\' of modifier ':M' +make: "varmod-match.mk" line 344: warning: while evaluating variable "WORDS": Unfinished character range in pattern '[x-' of modifier ':M' +make: "varmod-match.mk" line 356: warning: while evaluating variable "WORDS": Unfinished character range in pattern '[^x-' of modifier ':M' +make: "varmod-match.mk" line 364: warning: while evaluating variable " : :: ": Unfinished character list in pattern '[' of modifier ':M' +make: "varmod-match.mk" line 364: while evaluating variable " : :: ": Unknown modifier "]" +make: "varmod-match.mk" line 364: Malformed conditional (${ ${:U\:} ${:U\:\:} :L:M[:]} != ":") make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/unit-tests/varmod-match.mk b/unit-tests/varmod-match.mk index e91ddc7ce04b..2cdd38f04d03 100644 --- a/unit-tests/varmod-match.mk +++ b/unit-tests/varmod-match.mk @@ -1,4 +1,4 @@ -# $NetBSD: varmod-match.mk,v 1.22 2024/04/23 22:51:28 rillig Exp $ +# $NetBSD: varmod-match.mk,v 1.24 2024/06/15 19:43:56 rillig Exp $ # # Tests for the ':M' modifier, which keeps only those words that match the # given pattern. @@ -42,13 +42,12 @@ . error .endif -# A pattern that ends with '*' is anchored at the -# beginning. +# A pattern that does not start with '*' is anchored at the beginning. .if ${a aa aaa b ba baa bab:L:Ma*} != "a aa aaa" . error .endif -# A pattern that starts with '*' is anchored at the end. +# A pattern that does not end with '*' is anchored at the end. .if ${a aa aaa b ba baa bab:L:M*a} != "a aa aaa ba baa" . error .endif @@ -286,7 +285,7 @@ ${:U*}= asterisk # [ Incomplete empty character list, never matches. WORDS= a a[ -# expect+1: while evaluating variable "WORDS": warning: Unfinished character list in pattern 'a[' of modifier ':M' +# expect+1: warning: while evaluating variable "WORDS": Unfinished character list in pattern 'a[' of modifier ':M' .if ${WORDS:Ma[} != "" . error .endif @@ -294,7 +293,7 @@ WORDS= a a[ # [^ Incomplete negated empty character list, matches any single # character. WORDS= a a[ aX -# expect+1: while evaluating variable "WORDS": warning: Unfinished character list in pattern 'a[^' of modifier ':M' +# expect+1: warning: while evaluating variable "WORDS": Unfinished character list in pattern 'a[^' of modifier ':M' .if ${WORDS:Ma[^} != "a[ aX" . error .endif @@ -302,7 +301,7 @@ WORDS= a a[ aX # [-x1-3 Incomplete character list, matches those elements that can be # parsed without lookahead. WORDS= - + x xx 0 1 2 3 4 [x1-3 -# expect+1: while evaluating variable "WORDS": warning: Unfinished character list in pattern '[-x1-3' of modifier ':M' +# expect+1: warning: while evaluating variable "WORDS": Unfinished character list in pattern '[-x1-3' of modifier ':M' .if ${WORDS:M[-x1-3} != "- x 1 2 3" . error .endif @@ -310,7 +309,7 @@ WORDS= - + x xx 0 1 2 3 4 [x1-3 # *[-x1-3 Incomplete character list after a wildcard, matches those # words that end with one of the characters from the list. WORDS= - + x xx 0 1 2 3 4 00 01 10 11 000 001 010 011 100 101 110 111 [x1-3 -# expect+1: while evaluating variable "WORDS": warning: Unfinished character list in pattern '*[-x1-3' of modifier ':M' +# expect+1: warning: while evaluating variable "WORDS": Unfinished character list in pattern '*[-x1-3' of modifier ':M' .if ${WORDS:M*[-x1-3} != "- x xx 1 2 3 01 11 001 011 101 111 [x1-3" . warning ${WORDS:M*[-x1-3} .endif @@ -319,7 +318,7 @@ WORDS= - + x xx 0 1 2 3 4 00 01 10 11 000 001 010 011 100 101 110 111 [x1-3 # Incomplete negated character list, matches any character # except those elements that can be parsed without lookahead. WORDS= - + x xx 0 1 2 3 4 [x1-3 -# expect+1: while evaluating variable "WORDS": warning: Unfinished character list in pattern '[^-x1-3' of modifier ':M' +# expect+1: warning: while evaluating variable "WORDS": Unfinished character list in pattern '[^-x1-3' of modifier ':M' .if ${WORDS:M[^-x1-3} != "+ 0 4" . error .endif @@ -333,7 +332,7 @@ WORDS= - + x xx 0 1 2 3 4 [x1-3 # '\', as there is no following space that could be escaped. WORDS= \\ \a ${:Ux\\} PATTERN= ${:U?[\\} -# expect+1: while evaluating variable "WORDS": warning: Unfinished character list in pattern '?[\' of modifier ':M' +# expect+1: warning: while evaluating variable "WORDS": Unfinished character list in pattern '?[\' of modifier ':M' .if ${WORDS:M${PATTERN}} != "\\\\ x\\" . error .endif @@ -341,7 +340,7 @@ PATTERN= ${:U?[\\} # [x- Incomplete character list containing an incomplete character # range, matches only the 'x'. WORDS= [x- x x- y -# expect+1: while evaluating variable "WORDS": warning: Unfinished character range in pattern '[x-' of modifier ':M' +# expect+1: warning: while evaluating variable "WORDS": Unfinished character range in pattern '[x-' of modifier ':M' .if ${WORDS:M[x-} != "x" . error .endif @@ -353,13 +352,13 @@ WORDS= [x- x x- y # XXX: Even matches strings that are longer than a single # character. WORDS= [x- x x- y yyyyy -# expect+1: while evaluating variable "WORDS": warning: Unfinished character range in pattern '[^x-' of modifier ':M' +# expect+1: warning: while evaluating variable "WORDS": Unfinished character range in pattern '[^x-' of modifier ':M' .if ${WORDS:M[^x-} != "[x- y yyyyy" . error .endif # [:] matches never since the ':' starts the next modifier -# expect+3: while evaluating variable " : :: ": warning: Unfinished character list in pattern '[' of modifier ':M' +# expect+3: warning: while evaluating variable " : :: ": Unfinished character list in pattern '[' of modifier ':M' # expect+2: while evaluating variable " : :: ": Unknown modifier "]" # expect+1: Malformed conditional (${ ${:U\:} ${:U\:\:} :L:M[:]} != ":") .if ${ ${:U\:} ${:U\:\:} :L:M[:]} != ":" @@ -375,7 +374,7 @@ WORDS= [x- x x- y yyyyy # out-of-bounds read beyond the indirect ':M' modifiers. # # The argument to the inner ':U' is unescaped to 'M\'. -# This 'M\' becomes an # indirect modifier ':M' with the pattern '\'. +# This 'M\' becomes an indirect modifier ':M' with the pattern '\'. # The pattern '\' never matches. .if ${:U:${:UM\\}} . error diff --git a/unit-tests/varmod-sysv.exp b/unit-tests/varmod-sysv.exp index 6a3728c3c0fc..fe88ebd0a306 100644 --- a/unit-tests/varmod-sysv.exp +++ b/unit-tests/varmod-sysv.exp @@ -145,6 +145,8 @@ pre-middle-suffix pre%ffix=NPre% "NPre-middle-su" suffix pre%ffix=NPre%NS "suffix" prefix pre%ffix=NPre%NS "prefix" pre-middle-suffix pre%ffix=NPre%NS "NPre-middle-suNS" +make: Unfinished modifier for "error" ('}' missing) +make: "varmod-sysv.mk" line 259: Malformed conditional (${error:L:from=$(})) make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/unit-tests/varmod-sysv.mk b/unit-tests/varmod-sysv.mk index 0f92e1df7032..d37c33e47229 100644 --- a/unit-tests/varmod-sysv.mk +++ b/unit-tests/varmod-sysv.mk @@ -1,4 +1,4 @@ -# $NetBSD: varmod-sysv.mk,v 1.16 2023/11/19 21:47:52 rillig Exp $ +# $NetBSD: varmod-sysv.mk,v 1.17 2024/06/01 18:44:05 rillig Exp $ # # Tests for the variable modifier ':from=to', which replaces the suffix # "from" with "to". It can also use '%' as a wildcard. @@ -252,4 +252,12 @@ INDIRECT= 1:${VALUE} 2:$${VALUE} 4:$$$${VALUE} . endfor .endfor + +# The error case of an unfinished ':from=to' modifier after the '=' requires +# an expression that is missing the closing '}'. +# expect+1: Malformed conditional (${error:L:from=$(})) +.if ${error:L:from=$(}) +.endif + + all: diff --git a/unit-tests/varmod-tail.mk b/unit-tests/varmod-tail.mk index 05eae481fe3e..614c1af2a0b4 100644 --- a/unit-tests/varmod-tail.mk +++ b/unit-tests/varmod-tail.mk @@ -1,8 +1,16 @@ -# $NetBSD: varmod-tail.mk,v 1.4 2020/12/20 22:57:40 rillig Exp $ +# $NetBSD: varmod-tail.mk,v 1.5 2024/06/01 18:44:05 rillig Exp $ # # Tests for the :T variable modifier, which returns the basename of each of # the words in the variable value. + +# If the ':T' is not directly followed by a delimiting ':' or '}', the +# ':from=to' modifier is tried as a fallback. +.if ${:U Tail :Tail=replaced} != "replaced" +. error +.endif + + all: .for path in a/b/c def a.b.c a.b/c a a.a .gitignore a a.a trailing/ @echo "tail (basename) of '"${path:Q}"' is '"${path:T:Q}"'" diff --git a/unit-tests/varmod-to-separator.exp b/unit-tests/varmod-to-separator.exp index 6bdd6f0c2d43..586b19f123b5 100644 --- a/unit-tests/varmod-to-separator.exp +++ b/unit-tests/varmod-to-separator.exp @@ -2,24 +2,28 @@ make: "varmod-to-separator.mk" line 155: while evaluating variable "WORDS": Inva make: "varmod-to-separator.mk" line 155: Malformed conditional (${WORDS:[1..3]:ts\400:tu}) make: "varmod-to-separator.mk" line 171: while evaluating variable "WORDS": Invalid character number at "100:tu}" make: "varmod-to-separator.mk" line 171: Malformed conditional (${WORDS:[1..3]:ts\x100:tu}) +make: "varmod-to-separator.mk" line 180: while evaluating variable "word": Invalid character number at ",}" +make: "varmod-to-separator.mk" line 180: Malformed conditional (${word:L:ts\x,}) +make: "varmod-to-separator.mk" line 187: while evaluating variable "word": Invalid character number at "112233445566778899}" +make: "varmod-to-separator.mk" line 187: Malformed conditional (${word:L:ts\x112233445566778899}) make: Bad modifier ":ts\-300" for variable "WORDS" -make: "varmod-to-separator.mk" line 179: Malformed conditional (${WORDS:[1..3]:ts\-300:tu}) +make: "varmod-to-separator.mk" line 192: Malformed conditional (${WORDS:[1..3]:ts\-300:tu}) make: Bad modifier ":ts\8" for variable "1 2 3" -make: "varmod-to-separator.mk" line 188: Malformed conditional (${1 2 3:L:ts\8:tu}) +make: "varmod-to-separator.mk" line 201: Malformed conditional (${1 2 3:L:ts\8:tu}) make: Bad modifier ":ts\100L" for variable "1 2 3" -make: "varmod-to-separator.mk" line 196: Malformed conditional (${1 2 3:L:ts\100L}) +make: "varmod-to-separator.mk" line 209: Malformed conditional (${1 2 3:L:ts\100L}) make: Bad modifier ":ts\x40g" for variable "1 2 3" -make: "varmod-to-separator.mk" line 204: Malformed conditional (${1 2 3:L:ts\x40g}) +make: "varmod-to-separator.mk" line 217: Malformed conditional (${1 2 3:L:ts\x40g}) make: Bad modifier ":tx" for variable "WORDS" -make: "varmod-to-separator.mk" line 214: Malformed conditional (${WORDS:tx}) +make: "varmod-to-separator.mk" line 227: Malformed conditional (${WORDS:tx}) make: Bad modifier ":ts\X" for variable "WORDS" -make: "varmod-to-separator.mk" line 223: Malformed conditional (${WORDS:ts\X}) +make: "varmod-to-separator.mk" line 236: Malformed conditional (${WORDS:ts\X}) make: Bad modifier ":t\X" for variable "WORDS" -make: "varmod-to-separator.mk" line 232: Malformed conditional (${WORDS:t\X} != "anything") +make: "varmod-to-separator.mk" line 245: Malformed conditional (${WORDS:t\X} != "anything") make: Bad modifier ":ts\69" for variable "" -make: "varmod-to-separator.mk" line 249: Malformed conditional (${:Ua b:ts\69}) -make: "varmod-to-separator.mk" line 258: while evaluating "${:Ua b:ts\x1F60E}": Invalid character number at "1F60E}" -make: "varmod-to-separator.mk" line 258: Malformed conditional (${:Ua b:ts\x1F60E}) +make: "varmod-to-separator.mk" line 262: Malformed conditional (${:Ua b:ts\69}) +make: "varmod-to-separator.mk" line 271: while evaluating "${:Ua b:ts\x1F60E}": Invalid character number at "1F60E}" +make: "varmod-to-separator.mk" line 271: Malformed conditional (${:Ua b:ts\x1F60E}) make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/unit-tests/varmod-to-separator.mk b/unit-tests/varmod-to-separator.mk index 8b8aeb4d3c33..ec48dacbb60c 100644 --- a/unit-tests/varmod-to-separator.mk +++ b/unit-tests/varmod-to-separator.mk @@ -1,4 +1,4 @@ -# $NetBSD: varmod-to-separator.mk,v 1.14 2024/04/20 10:18:55 rillig Exp $ +# $NetBSD: varmod-to-separator.mk,v 1.15 2024/06/01 18:44:05 rillig Exp $ # # Tests for the :ts variable modifier, which joins the words of the variable # using an arbitrary character as word separator. @@ -174,6 +174,19 @@ WORDS= one two three four five six . warning The separator \x100 is accepted even though it is out of bounds. .endif +# The number after ':ts\x' must be hexadecimal. +# expect+2: while evaluating variable "word": Invalid character number at ",}" +# expect+1: Malformed conditional (${word:L:ts\x,}) +.if ${word:L:ts\x,} +.endif + +# The hexadecimal number must be in the range of 'unsigned long' on all +# supported platforms. +# expect+2: while evaluating variable "word": Invalid character number at "112233445566778899}" +# expect+1: Malformed conditional (${word:L:ts\x112233445566778899}) +.if ${word:L:ts\x112233445566778899} +.endif + # Negative numbers are not allowed for the separator character. # expect+1: Malformed conditional (${WORDS:[1..3]:ts\-300:tu}) .if ${WORDS:[1..3]:ts\-300:tu} diff --git a/unit-tests/varmod-undefined.mk b/unit-tests/varmod-undefined.mk index fd56ffd35e30..4ab6db81351c 100644 --- a/unit-tests/varmod-undefined.mk +++ b/unit-tests/varmod-undefined.mk @@ -1,4 +1,4 @@ -# $NetBSD: varmod-undefined.mk,v 1.9 2023/11/19 21:47:52 rillig Exp $ +# $NetBSD: varmod-undefined.mk,v 1.11 2024/06/03 02:46:29 sjg Exp $ # # Tests for the :U variable modifier, which returns the given string # if the variable is undefined. @@ -7,6 +7,9 @@ # directive-for.mk # varmod-defined.mk +# this test depends on +.MAKE.SAVE_DOLLARS= yes + # The pattern ${:Uword} is heavily used when expanding .for loops. # # This is how an expanded .for loop looks like. @@ -53,6 +56,10 @@ .if ${:U \: \} \$ \\ \a \b \n } != " : } \$ \\ \\a \\b \\n " . error .endif +# An expression enclosed in quotes may be based on an undefined variable. +.if "${:U \: \} \$ \\ \a \b \n }" != " : } \$ \\ \\a \\b \\n " +. error +.endif # Even after the :U modifier has been applied, the expression still remembers # that it originated from an undefined variable, and the :U modifier can @@ -64,5 +71,43 @@ . error .endif -all: - @:; + +# VARE_PARSE +.if 0 && ${:U . \: \} \$ \\ ${EXPR}} +. error +.endif + +# VARE_EVAL_KEEP_DOLLAR_AND_UNDEFINED +SUBST:= ${:U . \: \} \$ \\ ${EXPR}} +${:U }= +EXPR= +.if ${SUBST} != " . : } \\ " +. error +.endif + +8_DOLLAR= $$$$$$$$ +.if ${8_DOLLAR} != "\$\$\$\$" +. error +.endif +.if ${:U${8_DOLLAR}} != "\$\$\$\$" +. error +.endif +.if ${x:L:@_@${8_DOLLAR}@} != "\$\$\$\$" +. error +.endif +EXPR:= ${8_DOLLAR} +.if ${EXPR} != "\$\$\$\$" +. error +.endif +EXPR:= ${:U${8_DOLLAR}} +.if ${EXPR} != "\$\$\$\$" +. error +.endif +# VARE_EVAL_KEEP_UNDEFINED +EXPR:= ${x:L:@_@${8_DOLLAR}@} +.if ${EXPR} != "\$\$" +. error +.endif + + +all: .PHONY diff --git a/unit-tests/varmod.exp b/unit-tests/varmod.exp index 22a7019dfc1a..e619555d56cb 100644 --- a/unit-tests/varmod.exp +++ b/unit-tests/varmod.exp @@ -3,6 +3,31 @@ make: "varmod.mk" line 101: Invalid variable name ':', at "$:L} != """ make: "varmod.mk" line 107: while evaluating "${:Uword:@word@${word}$@} != "word"": Dollar followed by nothing make: "varmod.mk" line 117: while evaluating variable "VAR": Missing delimiter ':' after modifier "P" make: "varmod.mk" line 119: Missing argument for ".error" +make: Bad modifier ":[99333000222000111000]" for variable "word" +make: "varmod.mk" line 125: Malformed conditional (${word:L:[99333000222000111000]}) +make: Bad modifier ":[2147483648]" for variable "word" +make: "varmod.mk" line 128: Malformed conditional (${word:L:[2147483648]}) +make: "varmod.mk" line 135: while evaluating variable "word": Invalid number "99333000222000111000}" for ':range' modifier +make: "varmod.mk" line 135: Malformed conditional (${word:L:range=99333000222000111000}) +make: "varmod.mk" line 143: while evaluating "${:${:Ugmtime=\\}}": Invalid time value "\" +make: "varmod.mk" line 143: Malformed conditional (${:${:Ugmtime=\\}}) +make: "varmod.mk" line 158: while evaluating variable "VAR": Dollar followed by nothing +make: "varmod.mk" line 164: while evaluating variable "VAR": Dollar followed by nothing +make: "varmod.mk" line 164: while evaluating variable "VAR": Dollar followed by nothing +make: "varmod.mk" line 174: while evaluating variable "word": Dollar followed by nothing +make: Bad modifier ":[$]" for variable "word" +make: "varmod.mk" line 179: Malformed conditional (${word:[$]}) +make: "varmod.mk" line 196: while evaluating variable "VAR": Dollar followed by nothing +make: "varmod.mk" line 196: while evaluating variable "VAR": Invalid variable name '}', at "$} != "set"" +make: "varmod.mk" line 200: while evaluating "${:Ufallback$} != "fallback"": Invalid variable name '}', at "$} != "fallback"" +make: "varmod.mk" line 205: while evaluating variable "%y": Invalid time value "1000$" +make: "varmod.mk" line 205: Malformed conditional (${%y:L:gmtime=1000$}) +make: "varmod.mk" line 212: while evaluating variable "%y": Invalid time value "1000$" +make: "varmod.mk" line 212: Malformed conditional (${%y:L:localtime=1000$}) +make: "varmod.mk" line 218: while evaluating variable "word": Dollar followed by nothing +make: "varmod.mk" line 222: while evaluating variable "word": Dollar followed by nothing +make: "varmod.mk" line 227: while evaluating variable ".": Invalid argument 'fallback$' for modifier ':mtime' +make: "varmod.mk" line 227: Malformed conditional (${.:L:mtime=fallback$}) make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/unit-tests/varmod.mk b/unit-tests/varmod.mk index c749dfb9659d..783f2e329b4a 100644 --- a/unit-tests/varmod.mk +++ b/unit-tests/varmod.mk @@ -1,11 +1,11 @@ -# $NetBSD: varmod.mk,v 1.11 2024/04/20 10:18:56 rillig Exp $ +# $NetBSD: varmod.mk,v 1.15 2024/06/06 20:41:50 rillig Exp $ # # Tests for variable modifiers, such as :Q, :S,from,to or :Ufallback. # # See also: # varparse-errors.mk -# As of 2022-08-06, the possible behaviors during parsing are: +# As of 2024-06-05, the possible behaviors during parsing are: # # * `strict`: the parsing style used by most modifiers: # * either uses `ParseModifierPart` or parses the modifier literal @@ -46,9 +46,9 @@ # | `U` | individual | custom parser | N/A | # | `[` | strict | | no | # | `_` | individual | strcspn | yes | -# | `gmtime` | strict | only literal value | yes | +# | `gmtime` | strict | | yes | # | `hash` | strict | | N/A | -# | `localtime` | strict | only literal value | yes | +# | `localtime` | strict | | yes | # | `q` | strict | | yes | # | `range` | strict | | N/A | # | `sh` | strict | | N/A | @@ -119,4 +119,119 @@ VAR= STOP . error .endif -all: # nothing +# Test the word selection modifier ':[n]' with a very large number that is +# larger than ULONG_MAX for any supported platform. +# expect+1: Malformed conditional (${word:L:[99333000222000111000]}) +.if ${word:L:[99333000222000111000]} +.endif +# expect+1: Malformed conditional (${word:L:[2147483648]}) +.if ${word:L:[2147483648]} +.endif + +# Test the range generation modifier ':range=n' with a very large number that +# is larger than SIZE_MAX for any supported platform. +# expect+2: Malformed conditional (${word:L:range=99333000222000111000}) +# expect+1: while evaluating variable "word": Invalid number "99333000222000111000}" for ':range' modifier +.if ${word:L:range=99333000222000111000} +.endif + +# In an indirect modifier, the delimiter is '\0', which at the same time marks +# the end of the string. The sequence '\\' '\0' is not an escaped delimiter, +# as it would be wrong to skip past the end of the string. +# expect+2: while evaluating "${:${:Ugmtime=\\}}": Invalid time value "\" +# expect+1: Malformed conditional (${:${:Ugmtime=\\}}) +.if ${:${:Ugmtime=\\}} +. error +.endif + +# Test a '$' at the end of a modifier part, for all modifiers in the order +# listed in ApplyModifier. +# +# The only modifier parts where an unescaped '$' makes sense at the end are +# the 'from' parts of the ':S' and ':C' modifiers. In all other modifier +# parts, an unescaped '$' is an undocumented and discouraged edge case, as it +# means the same as an escaped '$'. +.if ${:U:!printf '%s\n' $!} != "\$" +. error +.endif +# expect+1: while evaluating variable "VAR": Dollar followed by nothing +.if ${VAR::=value$} != "" || ${VAR} != "value" +. error +.endif +${:U }= +# expect+2: while evaluating variable "VAR": Dollar followed by nothing +# expect+1: while evaluating variable "VAR": Dollar followed by nothing +.if ${VAR::+=appended$} != "" || ${VAR} != "valueappended" +. error +.endif +.if ${1:?then$:else$} != "then\$" +. error +.endif +.if ${0:?then$:else$} != "else\$" +. error +.endif +# expect+1: while evaluating variable "word": Dollar followed by nothing +.if ${word:L:@w@$w$@} != "word" +. error +.endif +# expect: make: Bad modifier ":[$]" for variable "word" +# expect+1: Malformed conditional (${word:[$]}) +.if ${word:[$]} +. error +.else +. error +.endif +VAR_DOLLAR= VAR$$ +.if ${word:L:_=VAR$} != "word" || ${${VAR_DOLLAR}} != "word" +. error +.endif +.if ${word:L:C,d$,m,} != "worm" +. error +.endif +.if ${word:L:C,d,$,} != "wor\$" +. error +.endif +# expect+2: while evaluating variable "VAR": Invalid variable name '}', at "$} != "set"" +# expect+1: while evaluating variable "VAR": Dollar followed by nothing +.if ${VAR:Dset$} != "set" +. error +.endif +# expect+1: while evaluating "${:Ufallback$} != "fallback"": Invalid variable name '}', at "$} != "fallback"" +.if ${:Ufallback$} != "fallback" +. error +.endif +# expect+2: Malformed conditional (${%y:L:gmtime=1000$}) +# expect+1: while evaluating variable "%y": Invalid time value "1000$" +.if ${%y:L:gmtime=1000$} +. error +.else +. error +.endif +# expect+2: Malformed conditional (${%y:L:localtime=1000$}) +# expect+1: while evaluating variable "%y": Invalid time value "1000$" +.if ${%y:L:localtime=1000$} +. error +.else +. error +.endif +# expect+1: while evaluating variable "word": Dollar followed by nothing +.if ${word:L:Mw*$} != "word" +. error +.endif +# expect+1: while evaluating variable "word": Dollar followed by nothing +.if ${word:L:NX*$} != "word" +. error +.endif +# expect+2: while evaluating variable ".": Invalid argument 'fallback$' for modifier ':mtime' +# expect+1: Malformed conditional (${.:L:mtime=fallback$}) +.if ${.:L:mtime=fallback$} +. error +.else +. error +.endif +.if ${word:L:S,d$,m,} != "worm" +. error +.endif +.if ${word:L:S,d,m$,} != "worm\$" +. error +.endif diff --git a/unit-tests/varname-dot-make-level.exp b/unit-tests/varname-dot-make-level.exp index 39a9383953dd..963b6a3a4f96 100644 --- a/unit-tests/varname-dot-make-level.exp +++ b/unit-tests/varname-dot-make-level.exp @@ -1 +1,4 @@ +level 1: variable 0, env 1 +level 2: variable 1, env 2 +level 3: variable 2, env 3 exit status 0 diff --git a/unit-tests/varname-dot-make-level.mk b/unit-tests/varname-dot-make-level.mk index c4f2c0db7da6..246890b23171 100644 --- a/unit-tests/varname-dot-make-level.mk +++ b/unit-tests/varname-dot-make-level.mk @@ -1,8 +1,22 @@ -# $NetBSD: varname-dot-make-level.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: varname-dot-make-level.mk,v 1.3 2024/06/01 18:44:05 rillig Exp $ # -# Tests for the special .MAKE.LEVEL variable. +# Tests for the special .MAKE.LEVEL variable, which informs about the +# recursion level. It is related to the environment variable MAKELEVEL, +# even though they don't have the same value. -# TODO: Implementation +level_1: .PHONY + @printf 'level 1: variable %s, env %s\n' ${.MAKE.LEVEL} "$$${.MAKE.LEVEL.ENV}" + @${MAKE} -f ${MAKEFILE} level_2 -all: - @:; +level_2: .PHONY + @printf 'level 2: variable %s, env %s\n' ${.MAKE.LEVEL} "$$${.MAKE.LEVEL.ENV}" + @${MAKE} -f ${MAKEFILE} level_3 + +level_3: .PHONY + @printf 'level 3: variable %s, env %s\n' ${.MAKE.LEVEL} "$$${.MAKE.LEVEL.ENV}" + +# The .unexport-env directive clears the environment, except for the +# MAKE_LEVEL variable. +.if make(level_2) +.unexport-env +.endif diff --git a/unit-tests/varname-dot-newline.exp b/unit-tests/varname-dot-newline.exp index 684fb5799752..74b69a5f250b 100644 --- a/unit-tests/varname-dot-newline.exp +++ b/unit-tests/varname-dot-newline.exp @@ -1,3 +1,8 @@ +make: "varname-dot-newline.mk" line 28: Cannot overwrite ".newline" as it is read-only +make: "varname-dot-newline.mk" line 30: Cannot append to ".newline" as it is read-only +make: "varname-dot-newline.mk" line 32: Cannot delete ".newline" as it is read-only +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests first second backslash newline: <\ diff --git a/unit-tests/varname-dot-newline.mk b/unit-tests/varname-dot-newline.mk index 1940dc2a990d..69ab5af4cd6c 100644 --- a/unit-tests/varname-dot-newline.mk +++ b/unit-tests/varname-dot-newline.mk @@ -1,4 +1,4 @@ -# $NetBSD: varname-dot-newline.mk,v 1.6 2023/01/26 20:48:18 sjg Exp $ +# $NetBSD: varname-dot-newline.mk,v 1.7 2024/06/15 22:06:31 rillig Exp $ # # Tests for the special .newline variable, which contains a single newline # character (U+000A). @@ -20,12 +20,23 @@ BACKSLASH_NEWLINE:= \${.newline} NEWLINE:= ${.newline} +.if make(try-to-modify) +# A '?=' assignment is fine. This pattern can be used to provide the variable +# to older or other variants of make that don't know that variable. +.newline?= fallback +# expect+1: Cannot overwrite ".newline" as it is read-only .newline= overwritten +# expect+1: Cannot append to ".newline" as it is read-only +.newline+= appended +# expect+1: Cannot delete ".newline" as it is read-only +.undef .newline +.endif .if ${.newline} != ${NEWLINE} . error The .newline variable can be overwritten. It should be read-only. .endif all: + @${MAKE} -f ${MAKEFILE} try-to-modify || true @echo 'first${.newline}second' @echo 'backslash newline: <${BACKSLASH_NEWLINE}>' diff --git a/unit-tests/varname-dot-objdir.exp b/unit-tests/varname-dot-objdir.exp index 39a9383953dd..430e2c898172 100644 --- a/unit-tests/varname-dot-objdir.exp +++ b/unit-tests/varname-dot-objdir.exp @@ -1 +1,2 @@ +: purge-cache was reached. exit status 0 diff --git a/unit-tests/varname-dot-objdir.mk b/unit-tests/varname-dot-objdir.mk index e662e8ac56fa..0f53165bba41 100644 --- a/unit-tests/varname-dot-objdir.mk +++ b/unit-tests/varname-dot-objdir.mk @@ -1,8 +1,15 @@ -# $NetBSD: varname-dot-objdir.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: varname-dot-objdir.mk,v 1.3 2024/06/01 11:06:17 rillig Exp $ # # Tests for the special .OBJDIR variable. # TODO: Implementation all: - @:; + # Add an entry to the cached_realpath table, to test cleaning up + # that table in purge_relative_cached_realpaths. + # Having a ':=' assignment in the command line is construed but works + # well enough to reach the code. + @${MAKE} -f ${MAKEFILE} 'VAR:=$${:U.:tA}' purge-cache + +purge-cache: + : ${.TARGET} was reached. diff --git a/unit-tests/varparse-errors.mk b/unit-tests/varparse-errors.mk index edb02ef0b957..f40e7206b0c3 100644 --- a/unit-tests/varparse-errors.mk +++ b/unit-tests/varparse-errors.mk @@ -1,4 +1,4 @@ -# $NetBSD: varparse-errors.mk,v 1.12 2024/04/20 10:18:56 rillig Exp $ +# $NetBSD: varparse-errors.mk,v 1.13 2024/06/02 15:31:26 rillig Exp $ # Tests for parsing and evaluating all kinds of expressions. # @@ -24,7 +24,7 @@ ERR_BAD_MOD= An ${:Uindirect:Z} expression with an unknown modifier. ERR_EVAL= An evaluation error ${:Uvalue:C,.,\3,}. # In a conditional, an expression that is not enclosed in quotes is -# expanded using the mode VARE_UNDEFERR. +# expanded using the mode VARE_EVAL_DEFINED. # The variable itself must be defined. # It may refer to undefined variables though. .if ${REF_UNDEF} != "A reference to an undefined variable." diff --git a/var.c b/var.c index ec0a23e05a67..2cd0d8d9c168 100644 --- a/var.c +++ b/var.c @@ -1,4 +1,4 @@ -/* $NetBSD: var.c,v 1.1109 2024/05/07 18:26:22 sjg Exp $ */ +/* $NetBSD: var.c,v 1.1121 2024/06/15 22:06:30 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 @@ -143,7 +143,7 @@ #include "metachar.h" /* "@(#)var.c 8.3 (Berkeley) 3/19/94" */ -MAKE_RCSID("$NetBSD: var.c,v 1.1109 2024/05/07 18:26:22 sjg Exp $"); +MAKE_RCSID("$NetBSD: var.c,v 1.1121 2024/06/15 22:06:30 rillig Exp $"); /* * Variables are defined using one of the VAR=value assignments. Their @@ -202,6 +202,12 @@ typedef struct Var { */ bool readOnly:1; + /* + * The variable is read-only and immune to the .NOREADONLY special + * target. Any attempt to modify it results in an error. + */ + bool readOnlyLoud:1; + /* * The variable is currently being accessed by Var_Parse or Var_Subst. * This temporary marker is used to avoid endless recursion. @@ -264,10 +270,15 @@ typedef struct SepBuf { char sep; } SepBuf; +typedef enum { + VSK_TARGET, + VSK_VARNAME, + VSK_EXPR +} EvalStackElementKind; + typedef struct { - const char *target; - const char *varname; - const char *expr; + EvalStackElementKind kind; + const char *str; } EvalStackElement; typedef struct { @@ -289,11 +300,11 @@ char var_Error[] = ""; /* * Special return value for Var_Parse, indicating an undefined variable in - * a case where VARE_UNDEFERR is not set. This undefined variable is + * a case where VARE_EVAL_DEFINED is not set. This undefined variable is * typically a dynamic variable such as ${.TARGET}, whose expansion needs to * be deferred until it is defined in an actual target. * - * See VARE_EVAL_KEEP_UNDEF. + * See VARE_EVAL_KEEP_UNDEFINED. */ static char varUndefined[] = ""; @@ -335,11 +346,10 @@ GNode *SCOPE_INTERNAL; static VarExportedMode var_exportedVars = VAR_EXPORTED_NONE; static const char VarEvalMode_Name[][32] = { - "parse-only", + "parse", "parse-balanced", "eval", "eval-defined", - "eval-keep-dollar", "eval-keep-undefined", "eval-keep-dollar-and-undefined", }; @@ -347,21 +357,20 @@ static const char VarEvalMode_Name[][32] = { static EvalStack evalStack; -void -EvalStack_Push(const char *target, const char *expr, const char *varname) +static void +EvalStack_Push(EvalStackElementKind kind, const char *str) { if (evalStack.len >= evalStack.cap) { evalStack.cap = 16 + 2 * evalStack.cap; evalStack.elems = bmake_realloc(evalStack.elems, evalStack.cap * sizeof(*evalStack.elems)); } - evalStack.elems[evalStack.len].target = target; - evalStack.elems[evalStack.len].expr = expr; - evalStack.elems[evalStack.len].varname = varname; + evalStack.elems[evalStack.len].kind = kind; + evalStack.elems[evalStack.len].str = str; evalStack.len++; } -void +static void EvalStack_Pop(void) { assert(evalStack.len > 0); @@ -378,21 +387,12 @@ EvalStack_Details(void) buf->len = 0; for (i = 0; i < evalStack.len; i++) { EvalStackElement *elem = evalStack.elems + i; - if (elem->target != NULL) { - Buf_AddStr(buf, "in target \""); - Buf_AddStr(buf, elem->target); - Buf_AddStr(buf, "\": "); - } - if (elem->expr != NULL) { - Buf_AddStr(buf, "while evaluating \""); - Buf_AddStr(buf, elem->expr); - Buf_AddStr(buf, "\": "); - } - if (elem->varname != NULL) { - Buf_AddStr(buf, "while evaluating variable \""); - Buf_AddStr(buf, elem->varname); - Buf_AddStr(buf, "\": "); - } + Buf_AddStr(buf, + elem->kind == VSK_TARGET ? "in target \"" : + elem->kind == VSK_EXPR ? "while evaluating \"" : + "while evaluating variable \""); + Buf_AddStr(buf, elem->str); + Buf_AddStr(buf, "\": "); } return buf->len > 0 ? buf->data : ""; } @@ -410,6 +410,7 @@ VarNew(FStr name, const char *value, var->shortLived = shortLived; var->fromEnvironment = fromEnvironment; var->readOnly = readOnly; + var->readOnlyLoud = false; var->inUse = false; var->exported = false; var->reexport = false; @@ -569,6 +570,12 @@ Var_Delete(GNode *scope, const char *varname) } v = he->value; + if (v->readOnlyLoud) { + Parse_Error(PARSE_FATAL, + "Cannot delete \"%s\" as it is read-only", + v->name.str); + return; + } if (v->readOnly) { DEBUG2(VAR, "%s: ignoring delete '%s' as it is read-only\n", scope->name, varname); @@ -593,6 +600,20 @@ Var_Delete(GNode *scope, const char *varname) free(v); } +#ifdef CLEANUP +void +Var_DeleteAll(GNode *scope) +{ + HashIter hi; + HashIter_Init(&hi, &scope->vars); + while (HashIter_Next(&hi)) { + Var *v = hi.entry->value; + Buf_Done(&v->val); + free(v); + } +} +#endif + /* * Undefine one or more variables from the global scope. * The argument is expanded exactly once and then split into words. @@ -610,7 +631,7 @@ Var_Undef(const char *arg) return; } - expanded = Var_Subst(arg, SCOPE_GLOBAL, VARE_WANTRES); + expanded = Var_Subst(arg, SCOPE_GLOBAL, VARE_EVAL); if (expanded == var_Error) { /* TODO: Make this part of the code reachable. */ Parse_Error(PARSE_FATAL, @@ -677,7 +698,7 @@ ExportVarEnv(Var *v, GNode *scope) /* XXX: name is injected without escaping it */ expr = str_concat3("${", name, "}"); - val = Var_Subst(expr, scope, VARE_WANTRES); + val = Var_Subst(expr, scope, VARE_EVAL); if (scope != SCOPE_GLOBAL) { /* we will need to re-export the global version */ v = VarFind(name, SCOPE_GLOBAL, false); @@ -778,7 +799,7 @@ Var_ReexportVars(GNode *scope) /* Ouch! Exporting all variables at once is crazy. */ HashIter_Init(&hi, &SCOPE_GLOBAL->vars); - while (HashIter_Next(&hi) != NULL) { + while (HashIter_Next(&hi)) { Var *var = hi.entry->value; ExportVar(var->name.str, scope, VEM_ENV); } @@ -786,7 +807,7 @@ Var_ReexportVars(GNode *scope) } xvarnames = Var_Subst("${.MAKE.EXPORTED:O:u}", SCOPE_GLOBAL, - VARE_WANTRES); + VARE_EVAL); /* TODO: handle errors */ if (xvarnames[0] != '\0') { Words varnames = Str_Words(xvarnames, false); @@ -826,7 +847,7 @@ ExportVars(const char *varnames, bool isExport, VarExportMode mode) static void ExportVarsExpand(const char *uvarnames, bool isExport, VarExportMode mode) { - char *xvarnames = Var_Subst(uvarnames, SCOPE_GLOBAL, VARE_WANTRES); + char *xvarnames = Var_Subst(uvarnames, SCOPE_GLOBAL, VARE_EVAL); /* TODO: handle errors */ ExportVars(xvarnames, isExport, mode); free(xvarnames); @@ -836,9 +857,12 @@ ExportVarsExpand(const char *uvarnames, bool isExport, VarExportMode mode) void Var_Export(VarExportMode mode, const char *varnames) { - if (mode == VEM_PLAIN && varnames[0] == '\0') { + if (mode == VEM_ALL) { var_exportedVars = VAR_EXPORTED_ALL; /* use with caution! */ return; + } else if (mode == VEM_PLAIN && varnames[0] == '\0') { + Parse_Error(PARSE_WARNING, ".export requires an argument."); + return; } ExportVarsExpand(varnames, true, mode); @@ -901,7 +925,7 @@ GetVarnamesToUnexport(bool isEnv, const char *arg, if (what != UNEXPORT_NAMED) { char *expanded = Var_Subst("${.MAKE.EXPORTED:O:u}", - SCOPE_GLOBAL, VARE_WANTRES); + SCOPE_GLOBAL, VARE_EVAL); /* TODO: handle errors */ varnames = FStr_InitOwn(expanded); } @@ -932,7 +956,7 @@ UnexportVar(Substring varname, UnexportWhat what) /* XXX: v->name is injected without escaping it */ char *expr = str_concat3( "${.MAKE.EXPORTED:N", v->name.str, "}"); - char *filtered = Var_Subst(expr, SCOPE_GLOBAL, VARE_WANTRES); + char *filtered = Var_Subst(expr, SCOPE_GLOBAL, VARE_EVAL); /* TODO: handle errors */ Global_Set(".MAKE.EXPORTED", filtered); free(filtered); @@ -1025,6 +1049,12 @@ Var_SetWithFlags(GNode *scope, const char *name, const char *val, } v = VarAdd(name, val, scope, flags); } else { + if (v->readOnlyLoud) { + Parse_Error(PARSE_FATAL, + "Cannot overwrite \"%s\" as it is read-only", + name); + return; + } if (v->readOnly && !(flags & VAR_SET_READONLY)) { DEBUG3(VAR, "%s: ignoring '%s = %s' as it is read-only\n", @@ -1089,7 +1119,7 @@ Var_SetExpand(GNode *scope, const char *name, const char *val) assert(val != NULL); - Var_Expand(&varname, scope, VARE_WANTRES); + Var_Expand(&varname, scope, VARE_EVAL); if (varname.str[0] == '\0') { DEBUG4(VAR, @@ -1117,7 +1147,8 @@ Global_Delete(const char *name) void Global_Set_ReadOnly(const char *name, const char *value) { - Var_SetWithFlags(SCOPE_GLOBAL, name, value, VAR_SET_READONLY); + Var_SetWithFlags(SCOPE_GLOBAL, name, value, VAR_SET_NONE); + VarFind(name, SCOPE_GLOBAL, false)->readOnlyLoud = true; } /* @@ -1135,6 +1166,10 @@ Var_Append(GNode *scope, const char *name, const char *val) if (v == NULL) { Var_SetWithFlags(scope, name, val, VAR_SET_NONE); + } else if (v->readOnlyLoud) { + Parse_Error(PARSE_FATAL, + "Cannot append to \"%s\" as it is read-only", name); + return; } else if (v->readOnly) { DEBUG3(VAR, "%s: ignoring '%s += %s' as it is read-only\n", scope->name, name, val); @@ -1172,7 +1207,7 @@ Var_AppendExpand(GNode *scope, const char *name, const char *val) assert(val != NULL); - Var_Expand(&xname, scope, VARE_WANTRES); + Var_Expand(&xname, scope, VARE_EVAL); if (xname.str != name && xname.str[0] == '\0') DEBUG4(VAR, "%s: ignoring '%s += %s' " @@ -1215,7 +1250,7 @@ Var_ExistsExpand(GNode *scope, const char *name) FStr varname = FStr_InitRefer(name); bool exists; - Var_Expand(&varname, scope, VARE_WANTRES); + Var_Expand(&varname, scope, VARE_EVAL); exists = Var_Exists(scope, varname.str); FStr_Done(&varname); return exists; @@ -1281,37 +1316,33 @@ GNode_ValueDirect(GNode *gn, const char *name) static VarEvalMode VarEvalMode_WithoutKeepDollar(VarEvalMode emode) { - if (emode == VARE_KEEP_DOLLAR_UNDEF) - return VARE_EVAL_KEEP_UNDEF; - if (emode == VARE_EVAL_KEEP_DOLLAR) - return VARE_WANTRES; - return emode; + return emode == VARE_EVAL_KEEP_DOLLAR_AND_UNDEFINED + ? VARE_EVAL_KEEP_UNDEFINED : emode; } static VarEvalMode VarEvalMode_UndefOk(VarEvalMode emode) { - return emode == VARE_UNDEFERR ? VARE_WANTRES : emode; + return emode == VARE_EVAL_DEFINED ? VARE_EVAL : emode; } static bool VarEvalMode_ShouldEval(VarEvalMode emode) { - return emode != VARE_PARSE_ONLY; + return emode != VARE_PARSE; } static bool VarEvalMode_ShouldKeepUndef(VarEvalMode emode) { - return emode == VARE_EVAL_KEEP_UNDEF || - emode == VARE_KEEP_DOLLAR_UNDEF; + return emode == VARE_EVAL_KEEP_UNDEFINED || + emode == VARE_EVAL_KEEP_DOLLAR_AND_UNDEFINED; } static bool VarEvalMode_ShouldKeepDollar(VarEvalMode emode) { - return emode == VARE_EVAL_KEEP_DOLLAR || - emode == VARE_KEEP_DOLLAR_UNDEF; + return emode == VARE_EVAL_KEEP_DOLLAR_AND_UNDEFINED; } @@ -1440,7 +1471,7 @@ ModifyWord_SysVSubst(Substring word, SepBuf *buf, void *data) } rhs = FStr_InitRefer(args->rhs); - Var_Expand(&rhs, args->scope, VARE_WANTRES); + Var_Expand(&rhs, args->scope, VARE_EVAL); percent = args->lhsPercent ? strchr(rhs.str, '%') : NULL; @@ -2084,7 +2115,7 @@ static bool IsEscapedModifierPart(const char *p, char delim, struct ModifyWord_SubstArgs *subst) { - if (p[0] != '\\') + if (p[0] != '\\' || p[1] == '\0') return false; if (p[1] == delim || p[1] == '\\' || p[1] == '$') return true; @@ -2143,13 +2174,23 @@ ParseModifierPartBalanced(const char **pp, LazyBuf *part) } } -/* See ParseModifierPart for the documentation. */ +/* + * Parse a part of a modifier such as the "from" and "to" in :S/from/to/ or + * the "var" or "replacement ${var}" in :@var@replacement ${var}@, up to and + * including the next unescaped delimiter. The delimiter, as well as the + * backslash or the dollar, can be escaped with a backslash. + * + * Return true if parsing succeeded, together with the parsed (and possibly + * expanded) part. In that case, pp points right after the delimiter. The + * delimiter is not included in the part though. + */ static bool -ParseModifierPartSubst( +ParseModifierPart( + /* The parsing position, updated upon return */ const char **pp, - /* If true, parse up to but excluding the next ':' or ch->endc. */ - bool whole, - char delim, + char end1, + char end2, + /* Mode for evaluating nested expressions. */ VarEvalMode emode, ModChain *ch, LazyBuf *part, @@ -2165,16 +2206,11 @@ ParseModifierPartSubst( struct ModifyWord_SubstArgs *subst ) { - const char *p; - char end1, end2; + const char *p = *pp; - p = *pp; LazyBuf_Init(part, p); - - end1 = whole ? ':' : delim; - end2 = whole ? ch->endc : delim; while (*p != '\0' && *p != end1 && *p != end2) { - if (IsEscapedModifierPart(p, delim, subst)) { + if (IsEscapedModifierPart(p, end2, subst)) { LazyBuf_Add(part, p[1]); p += 2; } else if (*p != '$') { /* Unescaped, simple text */ @@ -2183,7 +2219,7 @@ ParseModifierPartSubst( else LazyBuf_Add(part, *p); p++; - } else if (p[1] == delim) { /* Unescaped '$' at end */ + } else if (p[1] == end2) { /* Unescaped '$' at end */ if (out_pflags != NULL) out_pflags->anchorEnd = true; else @@ -2202,7 +2238,7 @@ ParseModifierPartSubst( LazyBuf_Done(part); return false; } - if (!whole) + if (end1 == end2) (*pp)++; { @@ -2214,32 +2250,6 @@ ParseModifierPartSubst( return true; } -/* - * Parse a part of a modifier such as the "from" and "to" in :S/from/to/ or - * the "var" or "replacement ${var}" in :@var@replacement ${var}@, up to and - * including the next unescaped delimiter. The delimiter, as well as the - * backslash or the dollar, can be escaped with a backslash. - * - * Return true if parsing succeeded, together with the parsed (and possibly - * expanded) part. In that case, pp points right after the delimiter. The - * delimiter is not included in the part though. - */ -static bool -ParseModifierPart( - /* The parsing position, updated upon return */ - const char **pp, - /* Parsing stops at this delimiter */ - char delim, - /* Mode for evaluating nested expressions. */ - VarEvalMode emode, - ModChain *ch, - LazyBuf *part -) -{ - return ParseModifierPartSubst(pp, false, delim, emode, ch, part, - NULL, NULL); -} - MAKE_INLINE bool IsDelimiter(char c, const ModChain *ch) { @@ -2384,7 +2394,8 @@ ApplyModifier_Loop(const char **pp, ModChain *ch) args.scope = expr->scope; (*pp)++; /* Skip the first '@' */ - if (!ParseModifierPart(pp, '@', VARE_PARSE_ONLY, ch, &tvarBuf)) + if (!ParseModifierPart(pp, '@', '@', VARE_PARSE, + ch, &tvarBuf, NULL, NULL)) return AMR_CLEANUP; tvar = LazyBuf_DoneGet(&tvarBuf); args.var = tvar.str; @@ -2393,11 +2404,12 @@ ApplyModifier_Loop(const char **pp, ModChain *ch) "In the :@ modifier, the variable name \"%s\" " "must not contain a dollar", args.var); - return AMR_CLEANUP; + goto cleanup_tvar; } - if (!ParseModifierPart(pp, '@', VARE_PARSE_BALANCED, ch, &strBuf)) - return AMR_CLEANUP; + if (!ParseModifierPart(pp, '@', '@', VARE_PARSE_BALANCED, + ch, &strBuf, NULL, NULL)) + goto cleanup_tvar; str = LazyBuf_DoneGet(&strBuf); args.body = str.str; @@ -2416,6 +2428,10 @@ done: FStr_Done(&tvar); FStr_Done(&str); return AMR_OK; + +cleanup_tvar: + FStr_Done(&tvar); + return AMR_CLEANUP; } static void @@ -2448,7 +2464,7 @@ ParseModifier_Defined(const char **pp, ModChain *ch, bool shouldEval, if (*p == '$') { FStr val = Var_Parse(&p, ch->expr->scope, - shouldEval ? ch->expr->emode : VARE_PARSE_ONLY); + shouldEval ? ch->expr->emode : VARE_PARSE); /* TODO: handle errors */ if (shouldEval) LazyBuf_AddStr(buf, val.str); @@ -2478,6 +2494,7 @@ ApplyModifier_Defined(const char **pp, ModChain *ch) Expr_Define(expr); if (shouldEval) Expr_SetValue(expr, Substring_Str(LazyBuf_Get(&buf))); + LazyBuf_Done(&buf); return AMR_OK; } @@ -2535,7 +2552,7 @@ ApplyModifier_Time(const char **pp, ModChain *ch) const char *p = args + 1; LazyBuf buf; FStr arg; - if (!ParseModifierPartSubst(&p, true, '\0', ch->expr->emode, + if (!ParseModifierPart(&p, ':', ch->endc, ch->expr->emode, ch, &buf, NULL, NULL)) return AMR_CLEANUP; arg = LazyBuf_DoneGet(&buf); @@ -2617,7 +2634,8 @@ ApplyModifier_ShellCommand(const char **pp, ModChain *ch) FStr cmd; (*pp)++; - if (!ParseModifierPart(pp, '!', expr->emode, ch, &cmdBuf)) + if (!ParseModifierPart(pp, '!', '!', expr->emode, + ch, &cmdBuf, NULL, NULL)) return AMR_CLEANUP; cmd = LazyBuf_DoneGet(&cmdBuf); @@ -2934,13 +2952,13 @@ ApplyModifier_Subst(const char **pp, ModChain *ch) (*pp)++; } - if (!ParseModifierPartSubst(pp, - false, delim, ch->expr->emode, ch, &lhsBuf, &args.pflags, NULL)) + if (!ParseModifierPart(pp, delim, delim, ch->expr->emode, + ch, &lhsBuf, &args.pflags, NULL)) return AMR_CLEANUP; args.lhs = LazyBuf_Get(&lhsBuf); - if (!ParseModifierPartSubst(pp, - false, delim, ch->expr->emode, ch, &rhsBuf, NULL, &args)) { + if (!ParseModifierPart(pp, delim, delim, ch->expr->emode, + ch, &rhsBuf, NULL, &args)) { LazyBuf_Done(&lhsBuf); return AMR_CLEANUP; } @@ -2977,11 +2995,13 @@ ApplyModifier_Regex(const char **pp, ModChain *ch) *pp += 2; - if (!ParseModifierPart(pp, delim, ch->expr->emode, ch, &reBuf)) + if (!ParseModifierPart(pp, delim, delim, ch->expr->emode, + ch, &reBuf, NULL, NULL)) return AMR_CLEANUP; re = LazyBuf_DoneGet(&reBuf); - if (!ParseModifierPart(pp, delim, ch->expr->emode, ch, &replaceBuf)) { + if (!ParseModifierPart(pp, delim, delim, ch->expr->emode, + ch, &replaceBuf, NULL, NULL)) { FStr_Done(&re); return AMR_CLEANUP; } @@ -3058,7 +3078,7 @@ ApplyModifier_ToSep(const char **pp, ModChain *ch) /* * Even in parse-only mode, apply the side effects, since the side * effects are neither observable nor is there a performance penalty. - * Checking for wantRes for every single piece of code in here + * Checking for VARE_EVAL for every single piece of code in here * would make the code in this function too hard to read. */ @@ -3210,7 +3230,8 @@ ApplyModifier_Words(const char **pp, ModChain *ch) FStr arg; (*pp)++; /* skip the '[' */ - if (!ParseModifierPart(pp, ']', expr->emode, ch, &argBuf)) + if (!ParseModifierPart(pp, ']', ']', expr->emode, + ch, &argBuf, NULL, NULL)) return AMR_CLEANUP; arg = LazyBuf_DoneGet(&argBuf); p = arg.str; @@ -3434,8 +3455,8 @@ ApplyModifier_IfElse(const char **pp, ModChain *ch) LazyBuf thenBuf; LazyBuf elseBuf; - VarEvalMode then_emode = VARE_PARSE_ONLY; - VarEvalMode else_emode = VARE_PARSE_ONLY; + VarEvalMode then_emode = VARE_PARSE; + VarEvalMode else_emode = VARE_PARSE; CondResult cond_rc = CR_TRUE; /* just not CR_ERROR */ if (Expr_ShouldEval(expr)) { @@ -3447,10 +3468,12 @@ ApplyModifier_IfElse(const char **pp, ModChain *ch) } (*pp)++; /* skip past the '?' */ - if (!ParseModifierPart(pp, ':', then_emode, ch, &thenBuf)) + if (!ParseModifierPart(pp, ':', ':', then_emode, + ch, &thenBuf, NULL, NULL)) return AMR_CLEANUP; - if (!ParseModifierPart(pp, ch->endc, else_emode, ch, &elseBuf)) { + if (!ParseModifierPart(pp, ch->endc, ch->endc, else_emode, + ch, &elseBuf, NULL, NULL)) { LazyBuf_Done(&thenBuf); return AMR_CLEANUP; } @@ -3530,7 +3553,8 @@ found_op: *pp = mod + (op[0] != '=' ? 3 : 2); - if (!ParseModifierPart(pp, ch->endc, expr->emode, ch, &buf)) + if (!ParseModifierPart(pp, ch->endc, ch->endc, expr->emode, + ch, &buf, NULL, NULL)) return AMR_CLEANUP; val = LazyBuf_DoneGet(&buf); @@ -3689,13 +3713,12 @@ ApplyModifier_SysV(const char **pp, ModChain *ch) if (!IsSysVModifier(mod, ch->startc, ch->endc)) return AMR_UNKNOWN; - if (!ParseModifierPart(pp, '=', expr->emode, ch, &lhsBuf)) + if (!ParseModifierPart(pp, '=', '=', expr->emode, + ch, &lhsBuf, NULL, NULL)) return AMR_CLEANUP; - /* - * The SysV modifier lasts until the end of the expression. - */ - if (!ParseModifierPart(pp, ch->endc, expr->emode, ch, &rhsBuf)) { + if (!ParseModifierPart(pp, ch->endc, ch->endc, expr->emode, + ch, &rhsBuf, NULL, NULL)) { LazyBuf_Done(&lhsBuf); return AMR_CLEANUP; } @@ -3755,9 +3778,8 @@ ApplyModifier_SunShell(const char **pp, ModChain *ch) static bool ShouldLogInSimpleFormat(const Expr *expr) { - return (expr->emode == VARE_WANTRES || - expr->emode == VARE_UNDEFERR) && - expr->defined == DEF_REGULAR; + return (expr->emode == VARE_EVAL || expr->emode == VARE_EVAL_DEFINED) + && expr->defined == DEF_REGULAR; } static void @@ -4245,7 +4267,7 @@ ParseVarnameShort(char varname, const char **pp, GNode *scope, val = UndefinedShortVarValue(varname, scope); if (val == NULL) - val = emode == VARE_UNDEFERR ? var_Error : varUndefined; + val = emode == VARE_EVAL_DEFINED ? var_Error : varUndefined; if (opts.strict && val == var_Error) { Parse_Error(PARSE_FATAL, @@ -4290,7 +4312,7 @@ EvalUndefined(bool dynamic, const char *start, const char *p, if (dynamic) return FStr_InitOwn(bmake_strsedup(start, p)); - if (emode == VARE_UNDEFERR && opts.strict) { + if (emode == VARE_EVAL_DEFINED && opts.strict) { Parse_Error(PARSE_FATAL, "Variable \"%.*s\" is undefined", (int)Substring_Length(varname), varname.start); @@ -4298,7 +4320,7 @@ EvalUndefined(bool dynamic, const char *start, const char *p, } return FStr_InitRefer( - emode == VARE_UNDEFERR ? var_Error : varUndefined); + emode == VARE_EVAL_DEFINED ? var_Error : varUndefined); } /* @@ -4451,7 +4473,7 @@ Var_Parse_U(const char **pp, VarEvalMode emode, FStr *out_value) if (*p != '}') return false; - *out_value = emode == VARE_PARSE_ONLY + *out_value = emode == VARE_PARSE ? FStr_InitRefer("") : FStr_InitOwn(bmake_strsedup(*pp + 4, p)); *pp = p + 1; @@ -4480,13 +4502,13 @@ Var_Parse_U(const char **pp, VarEvalMode emode, FStr *out_value) * return The value of the expression, never NULL. * return var_Error if there was a parse error. * return var_Error if the base variable of the expression was - * undefined, emode is VARE_UNDEFERR, and none of + * undefined, emode is VARE_EVAL_DEFINED, and none of * the modifiers turned the undefined expression into a * defined expression. * XXX: It is not guaranteed that an error message has * been printed. * return varUndefined if the base variable of the expression - * was undefined, emode was not VARE_UNDEFERR, + * was undefined, emode was not VARE_EVAL_DEFINED, * and none of the modifiers turned the undefined * expression into a defined expression. * XXX: It is not guaranteed that an error message has @@ -4569,9 +4591,9 @@ Var_Parse(const char **pp, GNode *scope, VarEvalMode emode) expr.value = FStr_InitRefer(v->val.data); if (expr.name[0] != '\0') - EvalStack_Push(NULL, NULL, expr.name); + EvalStack_Push(VSK_VARNAME, expr.name); else - EvalStack_Push(NULL, start, NULL); + EvalStack_Push(VSK_EXPR, start); /* * Before applying any modifiers, expand any nested expressions from @@ -4615,7 +4637,7 @@ Var_Parse(const char **pp, GNode *scope, VarEvalMode emode) * instead. */ Expr_SetValueRefer(&expr, - emode == VARE_UNDEFERR + emode == VARE_EVAL_DEFINED ? var_Error : varUndefined); } } @@ -4749,6 +4771,16 @@ Var_Subst(const char *str, GNode *scope, VarEvalMode emode) return Buf_DoneData(&res); } +char * +Var_SubstInTarget(const char *str, GNode *scope) +{ + char *res; + EvalStack_Push(VSK_TARGET, scope->name); + res = Var_Subst(str, scope, VARE_EVAL); + EvalStack_Pop(); + return res; +} + void Var_Expand(FStr *str, GNode *scope, VarEvalMode emode) { @@ -4804,7 +4836,7 @@ Var_Dump(GNode *scope) Vector_Init(&vec, sizeof(const char *)); HashIter_Init(&hi, &scope->vars); - while (HashIter_Next(&hi) != NULL) + while (HashIter_Next(&hi)) *(const char **)Vector_Push(&vec) = hi.entry->key; varnames = vec.items;