Wed Feb 3 08:40:47 2021 UTC ()
make: fix double expansion when appending to a new variable


(rillig)
diff -r1.792 -r1.793 src/usr.bin/make/var.c
diff -r1.3 -r1.4 src/usr.bin/make/unit-tests/var-op-append.exp
diff -r1.7 -r1.8 src/usr.bin/make/unit-tests/var-op-append.mk
diff -r1.6 -r1.7 src/usr.bin/make/unit-tests/varname-empty.exp

cvs diff -r1.792 -r1.793 src/usr.bin/make/var.c (expand / switch to unified diff)

--- src/usr.bin/make/var.c 2021/02/03 08:08:18 1.792
+++ src/usr.bin/make/var.c 2021/02/03 08:40:47 1.793
@@ -1,14 +1,14 @@ @@ -1,14 +1,14 @@
1/* $NetBSD: var.c,v 1.792 2021/02/03 08:08:18 rillig Exp $ */ 1/* $NetBSD: var.c,v 1.793 2021/02/03 08:40:47 rillig Exp $ */
2 2
3/* 3/*
4 * Copyright (c) 1988, 1989, 1990, 1993 4 * Copyright (c) 1988, 1989, 1990, 1993
5 * The Regents of the University of California. All rights reserved. 5 * The Regents of the University of California. All rights reserved.
6 * 6 *
7 * This code is derived from software contributed to Berkeley by 7 * This code is derived from software contributed to Berkeley by
8 * Adam de Boor. 8 * Adam de Boor.
9 * 9 *
10 * Redistribution and use in source and binary forms, with or without 10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions 11 * modification, are permitted provided that the following conditions
12 * are met: 12 * are met:
13 * 1. Redistributions of source code must retain the above copyright 13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer. 14 * notice, this list of conditions and the following disclaimer.
@@ -121,27 +121,27 @@ @@ -121,27 +121,27 @@
121#include <regex.h> 121#include <regex.h>
122#endif 122#endif
123#include <errno.h> 123#include <errno.h>
124#include <inttypes.h> 124#include <inttypes.h>
125#include <limits.h> 125#include <limits.h>
126#include <time.h> 126#include <time.h>
127 127
128#include "make.h" 128#include "make.h"
129#include "dir.h" 129#include "dir.h"
130#include "job.h" 130#include "job.h"
131#include "metachar.h" 131#include "metachar.h"
132 132
133/* "@(#)var.c 8.3 (Berkeley) 3/19/94" */ 133/* "@(#)var.c 8.3 (Berkeley) 3/19/94" */
134MAKE_RCSID("$NetBSD: var.c,v 1.792 2021/02/03 08:08:18 rillig Exp $"); 134MAKE_RCSID("$NetBSD: var.c,v 1.793 2021/02/03 08:40:47 rillig Exp $");
135 135
136typedef enum VarFlags { 136typedef enum VarFlags {
137 VAR_NONE = 0, 137 VAR_NONE = 0,
138 138
139 /* 139 /*
140 * The variable's value is currently being used by Var_Parse or 140 * The variable's value is currently being used by Var_Parse or
141 * Var_Subst. This marker is used to avoid endless recursion. 141 * Var_Subst. This marker is used to avoid endless recursion.
142 */ 142 */
143 VAR_IN_USE = 0x01, 143 VAR_IN_USE = 0x01,
144 144
145 /* 145 /*
146 * The variable comes from the environment. 146 * The variable comes from the environment.
147 * These variables are not registered in any GNode, therefore they 147 * These variables are not registered in any GNode, therefore they
@@ -906,26 +906,32 @@ Var_UnExport(Boolean isEnv, const char * @@ -906,26 +906,32 @@ Var_UnExport(Boolean isEnv, const char *
906 FStr varnames; 906 FStr varnames;
907 907
908 GetVarnamesToUnexport(isEnv, arg, &varnames, &what); 908 GetVarnamesToUnexport(isEnv, arg, &varnames, &what);
909 UnexportVars(&varnames, what); 909 UnexportVars(&varnames, what);
910 FStr_Done(&varnames); 910 FStr_Done(&varnames);
911} 911}
912 912
913/* Set the variable to the value; the name is not expanded. */ 913/* Set the variable to the value; the name is not expanded. */
914static void 914static void
915SetVar(const char *name, const char *val, GNode *ctxt, VarSetFlags flags) 915SetVar(const char *name, const char *val, GNode *ctxt, VarSetFlags flags)
916{ 916{
917 Var *v; 917 Var *v;
918 918
 919 assert(val != NULL);
 920 if (name[0] == '\0') {
 921 DEBUG0(VAR, "SetVar: variable name is empty - ignored\n");
 922 return;
 923 }
 924
919 if (ctxt == VAR_GLOBAL) { 925 if (ctxt == VAR_GLOBAL) {
920 v = VarFind(name, VAR_CMDLINE, FALSE); 926 v = VarFind(name, VAR_CMDLINE, FALSE);
921 if (v != NULL) { 927 if (v != NULL) {
922 if (v->flags & VAR_FROM_CMD) { 928 if (v->flags & VAR_FROM_CMD) {
923 DEBUG3(VAR, "%s:%s = %s ignored!\n", 929 DEBUG3(VAR, "%s:%s = %s ignored!\n",
924 ctxt->name, name, val); 930 ctxt->name, name, val);
925 return; 931 return;
926 } 932 }
927 VarFreeEnv(v, TRUE); 933 VarFreeEnv(v, TRUE);
928 } 934 }
929 } 935 }
930 936
931 /* 937 /*
@@ -1024,32 +1030,27 @@ Var_SetWithFlags(const char *name, const @@ -1024,32 +1030,27 @@ Var_SetWithFlags(const char *name, const
1024 * name name of the variable to set, is expanded once 1030 * name name of the variable to set, is expanded once
1025 * val value to give to the variable 1031 * val value to give to the variable
1026 * ctxt context in which to set it 1032 * ctxt context in which to set it
1027 */ 1033 */
1028void 1034void
1029Var_Set(const char *name, const char *val, GNode *ctxt) 1035Var_Set(const char *name, const char *val, GNode *ctxt)
1030{ 1036{
1031 Var_SetWithFlags(name, val, ctxt, VAR_SET_NONE); 1037 Var_SetWithFlags(name, val, ctxt, VAR_SET_NONE);
1032} 1038}
1033 1039
1034void 1040void
1035Global_Set(const char *name, const char *value) 1041Global_Set(const char *name, const char *value)
1036{ 1042{
1037 assert(value != NULL); 1043 SetVar(name, value, VAR_GLOBAL, VAR_SET_NONE);
1038 
1039 if (name[0] == '\0') 
1040 DEBUG0(VAR, "Variable name empty - ignored\n"); 
1041 else 
1042 SetVar(name, value, VAR_GLOBAL, VAR_SET_NONE); 
1043} 1044}
1044 1045
1045void 1046void
1046Global_SetExpand(const char *name, const char *value) 1047Global_SetExpand(const char *name, const char *value)
1047{ 1048{
1048 Var_Set(name, value, VAR_GLOBAL); 1049 Var_Set(name, value, VAR_GLOBAL);
1049} 1050}
1050 1051
1051/* 1052/*
1052 * The variable of the given name has the given value appended to it in the 1053 * The variable of the given name has the given value appended to it in the
1053 * given context. 1054 * given context.
1054 * 1055 *
1055 * If the variable doesn't exist, it is created. Otherwise the strings are 1056 * If the variable doesn't exist, it is created. Otherwise the strings are
@@ -1083,28 +1084,27 @@ Var_Append(const char *name, const char  @@ -1083,28 +1084,27 @@ Var_Append(const char *name, const char
1083 name = name_freeIt; 1084 name = name_freeIt;
1084 if (name[0] == '\0') { 1085 if (name[0] == '\0') {
1085 DEBUG2(VAR, "Var_Append(\"%s\", \"%s\", ...) " 1086 DEBUG2(VAR, "Var_Append(\"%s\", \"%s\", ...) "
1086 "name expands to empty string - ignored\n", 1087 "name expands to empty string - ignored\n",
1087 unexpanded_name, val); 1088 unexpanded_name, val);
1088 free(name_freeIt); 1089 free(name_freeIt);
1089 return; 1090 return;
1090 } 1091 }
1091 } 1092 }
1092 1093
1093 v = VarFind(name, ctxt, ctxt == VAR_GLOBAL); 1094 v = VarFind(name, ctxt, ctxt == VAR_GLOBAL);
1094 1095
1095 if (v == NULL) { 1096 if (v == NULL) {
1096 /* XXX: name is expanded for the second time */ 1097 SetVar(name, val, ctxt, VAR_SET_NONE);
1097 Var_Set(name, val, ctxt); 
1098 } else if (v->flags & VAR_READONLY) { 1098 } else if (v->flags & VAR_READONLY) {
1099 DEBUG1(VAR, "Ignoring append to %s since it is read-only\n", 1099 DEBUG1(VAR, "Ignoring append to %s since it is read-only\n",
1100 name); 1100 name);
1101 } else if (ctxt == VAR_CMDLINE || !(v->flags & VAR_FROM_CMD)) { 1101 } else if (ctxt == VAR_CMDLINE || !(v->flags & VAR_FROM_CMD)) {
1102 Buf_AddByte(&v->val, ' '); 1102 Buf_AddByte(&v->val, ' ');
1103 Buf_AddStr(&v->val, val); 1103 Buf_AddStr(&v->val, val);
1104 1104
1105 DEBUG3(VAR, "%s:%s = %s\n", ctxt->name, name, v->val.data); 1105 DEBUG3(VAR, "%s:%s = %s\n", ctxt->name, name, v->val.data);
1106 1106
1107 if (v->flags & VAR_FROM_ENV) { 1107 if (v->flags & VAR_FROM_ENV) {
1108 /* 1108 /*
1109 * If the original variable came from the environment, 1109 * If the original variable came from the environment,
1110 * we have to install it in the global context (we 1110 * we have to install it in the global context (we

cvs diff -r1.3 -r1.4 src/usr.bin/make/unit-tests/var-op-append.exp (expand / switch to unified diff)

--- src/usr.bin/make/unit-tests/var-op-append.exp 2021/02/02 16:18:16 1.3
+++ src/usr.bin/make/unit-tests/var-op-append.exp 2021/02/03 08:40:47 1.4
@@ -1,7 +1,7 @@ @@ -1,7 +1,7 @@
1Var_Parse: ${:U\$\$\$\$\$\$\$\$} with VARE_WANTRES 1Var_Parse: ${:U\$\$\$\$\$\$\$\$} with VARE_WANTRES
2Applying ${:U...} to "" (VARE_WANTRES, none, VES_UNDEF) 2Applying ${:U...} to "" (VARE_WANTRES, none, VES_UNDEF)
3Result of ${:U\$\$\$\$\$\$\$\$} is "$$$$$$$$" (VARE_WANTRES, none, VES_DEF) 3Result of ${:U\$\$\$\$\$\$\$\$} is "$$$$$$$$" (VARE_WANTRES, none, VES_DEF)
4Global:VAR.$$$$ = dollars 4Global:VAR.$$$$$$$$ = dollars
5Global:.MAKEFLAGS = -r -k -d v -d 5Global:.MAKEFLAGS = -r -k -d v -d
6Global:.MAKEFLAGS = -r -k -d v -d 0 6Global:.MAKEFLAGS = -r -k -d v -d 0
7exit status 0 7exit status 0

cvs diff -r1.7 -r1.8 src/usr.bin/make/unit-tests/var-op-append.mk (expand / switch to unified diff)

--- src/usr.bin/make/unit-tests/var-op-append.mk 2020/10/30 20:36:33 1.7
+++ src/usr.bin/make/unit-tests/var-op-append.mk 2021/02/03 08:40:47 1.8
@@ -1,14 +1,14 @@ @@ -1,14 +1,14 @@
1# $NetBSD: var-op-append.mk,v 1.7 2020/10/30 20:36:33 rillig Exp $ 1# $NetBSD: var-op-append.mk,v 1.8 2021/02/03 08:40:47 rillig Exp $
2# 2#
3# Tests for the += variable assignment operator, which appends to a variable, 3# Tests for the += variable assignment operator, which appends to a variable,
4# creating it if necessary. 4# creating it if necessary.
5 5
6# Appending to an undefined variable is possible. 6# Appending to an undefined variable is possible.
7# The variable is created, and no extra space is added before the value. 7# The variable is created, and no extra space is added before the value.
8VAR+= one 8VAR+= one
9.if ${VAR} != "one" 9.if ${VAR} != "one"
10. error 10. error
11.endif 11.endif
12 12
13# Appending to an existing variable adds a single space and the value. 13# Appending to an existing variable adds a single space and the value.
14VAR+= two 14VAR+= two
@@ -22,27 +22,25 @@ VAR+= # empty @@ -22,27 +22,25 @@ VAR+= # empty
22. error 22. error
23.endif 23.endif
24 24
25# Variable names may contain '+', and this character is also part of the 25# Variable names may contain '+', and this character is also part of the
26# '+=' assignment operator. As far as possible, the '+' is interpreted as 26# '+=' assignment operator. As far as possible, the '+' is interpreted as
27# part of the assignment operator. 27# part of the assignment operator.
28# 28#
29# See Parse_DoVar 29# See Parse_DoVar
30C++= value 30C++= value
31.if ${C+} != "value" || defined(C++) 31.if ${C+} != "value" || defined(C++)
32. error 32. error
33.endif 33.endif
34 34
35# Try out how often the variable name is expanded when appending to a 35# Before var.c 1.793 from 2021-02-03, the variable name of a newly created
36# nonexistent variable. 36# variable was expanded two times in a row, which was unexpected but
37# As of 2020-10-30, that's two times. 37# irrelevant in practice since variable names containing dollars lead to
38# XXX: That's one time too often. 38# strange side effects in several other places as well.
39# See Var_Append, the call to Var_Set. 
40.MAKEFLAGS: -dv 39.MAKEFLAGS: -dv
41VAR.${:U\$\$\$\$\$\$\$\$}+= dollars 40VAR.${:U\$\$\$\$\$\$\$\$}+= dollars
42.MAKEFLAGS: -d0 41.MAKEFLAGS: -d0
43.if ${VAR.${:U\$\$\$\$}} != "dollars" 42.if ${VAR.${:U\$\$\$\$\$\$\$\$}} != "dollars"
44. error 43. error
45.endif 44.endif
46 45
47all: 46all:
48 @:; 

cvs diff -r1.6 -r1.7 src/usr.bin/make/unit-tests/varname-empty.exp (expand / switch to unified diff)

--- src/usr.bin/make/unit-tests/varname-empty.exp 2021/02/03 08:34:15 1.6
+++ src/usr.bin/make/unit-tests/varname-empty.exp 2021/02/03 08:40:47 1.7
@@ -10,27 +10,27 @@ Result of ${MAKE_OBJDIR_CHECK_WRITABLE:U @@ -10,27 +10,27 @@ Result of ${MAKE_OBJDIR_CHECK_WRITABLE:U
10Global:.OBJDIR = <curdir> 10Global:.OBJDIR = <curdir>
11Global:delete .PATH (not found) 11Global:delete .PATH (not found)
12Global:.PATH = . 12Global:.PATH = .
13Global:.PATH = . <curdir> 13Global:.PATH = . <curdir>
14Global:.TARGETS =  14Global:.TARGETS =
15Internal:MAKEFILE = varname-empty.mk 15Internal:MAKEFILE = varname-empty.mk
16Global:.MAKE.MAKEFILES = varname-empty.mk 16Global:.MAKE.MAKEFILES = varname-empty.mk
17Global:.PARSEDIR = <curdir> 17Global:.PARSEDIR = <curdir>
18Global:.PARSEFILE = varname-empty.mk 18Global:.PARSEFILE = varname-empty.mk
19Global:delete .INCLUDEDFROMDIR (not found) 19Global:delete .INCLUDEDFROMDIR (not found)
20Global:delete .INCLUDEDFROMFILE (not found) 20Global:delete .INCLUDEDFROMFILE (not found)
21Var_Set("", "default", ...) name expands to empty string - ignored 21Var_Set("", "default", ...) name expands to empty string - ignored
22Var_Set("", "assigned", ...) name expands to empty string - ignored 22Var_Set("", "assigned", ...) name expands to empty string - ignored
23Var_Set("", "appended", ...) name expands to empty string - ignored 23SetVar: variable name is empty - ignored
24Var_Set("", "", ...) name expands to empty string - ignored 24Var_Set("", "", ...) name expands to empty string - ignored
25Var_Set("", "subst", ...) name expands to empty string - ignored 25Var_Set("", "subst", ...) name expands to empty string - ignored
26Var_Set("", "shell-output", ...) name expands to empty string - ignored 26Var_Set("", "shell-output", ...) name expands to empty string - ignored
27Var_Parse: ${:Ufallback} != "fallback" with VARE_UNDEFERR|VARE_WANTRES 27Var_Parse: ${:Ufallback} != "fallback" with VARE_UNDEFERR|VARE_WANTRES
28Applying ${:U...} to "" (VARE_UNDEFERR|VARE_WANTRES, none, VES_UNDEF) 28Applying ${:U...} to "" (VARE_UNDEFERR|VARE_WANTRES, none, VES_UNDEF)
29Result of ${:Ufallback} is "fallback" (VARE_UNDEFERR|VARE_WANTRES, none, VES_DEF) 29Result of ${:Ufallback} is "fallback" (VARE_UNDEFERR|VARE_WANTRES, none, VES_DEF)
30Var_Parse: ${:U} with VARE_WANTRES 30Var_Parse: ${:U} with VARE_WANTRES
31Applying ${:U} to "" (VARE_WANTRES, none, VES_UNDEF) 31Applying ${:U} to "" (VARE_WANTRES, none, VES_UNDEF)
32Result of ${:U} is "" (VARE_WANTRES, none, VES_DEF) 32Result of ${:U} is "" (VARE_WANTRES, none, VES_DEF)
33Var_Set("${:U}", "assigned indirectly", ...) name expands to empty string - ignored 33Var_Set("${:U}", "assigned indirectly", ...) name expands to empty string - ignored
34Var_Parse: ${:Ufallback} != "fallback" with VARE_UNDEFERR|VARE_WANTRES 34Var_Parse: ${:Ufallback} != "fallback" with VARE_UNDEFERR|VARE_WANTRES
35Applying ${:U...} to "" (VARE_UNDEFERR|VARE_WANTRES, none, VES_UNDEF) 35Applying ${:U...} to "" (VARE_UNDEFERR|VARE_WANTRES, none, VES_UNDEF)
36Result of ${:Ufallback} is "fallback" (VARE_UNDEFERR|VARE_WANTRES, none, VES_DEF) 36Result of ${:Ufallback} is "fallback" (VARE_UNDEFERR|VARE_WANTRES, none, VES_DEF)