Module Name:    src
Committed By:   rillig
Date:           Tue Oct  6 08:13:28 UTC 2020

Modified Files:
        src/usr.bin/make: var.c

Log Message:
make(1): rework memory allocation for the name of variables

There's more to know about variable names than fits in a one-liner.
While here, enforce that the name is not modified by splitting it into
the established (var + var_freeIt) pattern.

Since the name is not modified and not freed in the middle of evaluating
an expression, there is no need to make a backup copy of it.  That code
had been necessary more than 12 years ago, but not anymore since the
code got a lot cleaner since then.


To generate a diff of this commit:
cvs rdiff -u -r1.569 -r1.570 src/usr.bin/make/var.c

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/usr.bin/make/var.c
diff -u src/usr.bin/make/var.c:1.569 src/usr.bin/make/var.c:1.570
--- src/usr.bin/make/var.c:1.569	Tue Oct  6 07:52:47 2020
+++ src/usr.bin/make/var.c	Tue Oct  6 08:13:27 2020
@@ -1,4 +1,4 @@
-/*	$NetBSD: var.c,v 1.569 2020/10/06 07:52:47 rillig Exp $	*/
+/*	$NetBSD: var.c,v 1.570 2020/10/06 08:13:27 rillig Exp $	*/
 
 /*
  * Copyright (c) 1988, 1989, 1990, 1993
@@ -121,7 +121,7 @@
 #include    "metachar.h"
 
 /*	"@(#)var.c	8.3 (Berkeley) 3/19/94" */
-MAKE_RCSID("$NetBSD: var.c,v 1.569 2020/10/06 07:52:47 rillig Exp $");
+MAKE_RCSID("$NetBSD: var.c,v 1.570 2020/10/06 08:13:27 rillig Exp $");
 
 #define VAR_DEBUG1(fmt, arg1) DEBUG1(VAR, fmt, arg1)
 #define VAR_DEBUG2(fmt, arg1, arg2) DEBUG2(VAR, fmt, arg1, arg2)
@@ -213,11 +213,31 @@ ENUM_FLAGS_RTTI_6(VarFlags,
 		  VAR_IN_USE, VAR_FROM_ENV,
 		  VAR_EXPORTED, VAR_REEXPORT, VAR_FROM_CMD, VAR_READONLY);
 
+/* Variables are defined using one of the VAR=value assignments.  Their
+ * value can be queried by expressions such as $V, ${VAR}, or with modifiers
+ * such as ${VAR:S,from,to,g:Q}.
+ *
+ * There are 3 kinds of variables: context variables, environment variables,
+ * undefined variables.
+ *
+ * Context variables are stored in a GNode.context.  The only way to undefine
+ * a context variable is using the .undef directive.  In particular, it must
+ * not be possible to undefine a variable during the evaluation of an
+ * expression, or Var.name might point nowhere.
+ *
+ * Environment variables are temporary.  They are returned by VarFind, and
+ * after using them, they must be freed using VarFreeEnv.
+ *
+ * Undefined variables occur during evaluation of variable expressions such
+ * as ${UNDEF:Ufallback} in Var_Parse and ApplyModifiers.
+ */
 typedef struct Var {
-    char          *name;	/* the variable's name; it is allocated for
-				 * environment variables and aliased to the
-				 * Hash_Entry name for all other variables,
-				 * and thus must not be modified */
+    /* The name of the variable, once set, doesn't change anymore.
+     * For context variables, it aliases the corresponding Hash_Entry name.
+     * For environment and undefined variables, it is allocated. */
+    const char *name;
+    void *name_freeIt;
+
     Buffer	  val;		/* its value */
     VarFlags	  flags;	/* miscellaneous status flags */
 } Var;
@@ -254,11 +274,12 @@ typedef enum {
 } VarPatternFlags;
 
 static Var *
-VarNew(char *name, const char *value, VarFlags flags)
+VarNew(const char *name, void *name_freeIt, const char *value, VarFlags flags)
 {
     size_t value_len = strlen(value);
     Var *var = bmake_malloc(sizeof *var);
     var->name = name;
+    var->name_freeIt = name_freeIt;
     Buf_Init(&var->val, value_len + 1);
     Buf_AddBytes(&var->val, value, value_len);
     var->flags = flags;
@@ -360,8 +381,10 @@ VarFind(const char *name, GNode *ctxt, V
     if (var == NULL && (flags & FIND_ENV)) {
 	char *env;
 
-	if ((env = getenv(name)) != NULL)
-	    return VarNew(bmake_strdup(name), env, VAR_FROM_ENV);
+	if ((env = getenv(name)) != NULL) {
+	    char *varname = bmake_strdup(name);
+	    return VarNew(varname, varname, env, VAR_FROM_ENV);
+	}
 
 	if (checkEnvFirst && (flags & FIND_GLOBAL) && ctxt != VAR_GLOBAL) {
 	    var = Hash_FindValue(&VAR_GLOBAL->context, name);
@@ -394,7 +417,7 @@ VarFreeEnv(Var *v, Boolean destroy)
 {
     if (!(v->flags & VAR_FROM_ENV))
 	return FALSE;
-    free(v->name);
+    free(v->name_freeIt);
     Buf_Destroy(&v->val, destroy);
     free(v);
     return TRUE;
@@ -406,7 +429,8 @@ static void
 VarAdd(const char *name, const char *val, GNode *ctxt, VarSet_Flags flags)
 {
     Hash_Entry *he = Hash_CreateEntry(&ctxt->context, name, NULL);
-    Var *v = VarNew(he->name, val, flags & VAR_SET_READONLY ? VAR_READONLY : 0);
+    Var *v = VarNew(he->name, NULL, val,
+		      flags & VAR_SET_READONLY ? VAR_READONLY : 0);
     Hash_SetValue(he, v);
     if (!(ctxt->flags & INTERNAL)) {
 	VAR_DEBUG3("%s:%s = %s\n", ctxt->name, name, val);
@@ -436,8 +460,7 @@ Var_Delete(const char *name, GNode *ctxt
 	    unsetenv(v->name);
 	if (strcmp(v->name, MAKE_EXPORTED) == 0)
 	    var_exportedVars = VAR_EXPORTED_NONE;
-	if (v->name != he->name)
-	    free(v->name);
+	assert(v->name_freeIt == NULL);
 	Hash_DeleteEntry(&ctxt->context, he);
 	Buf_Destroy(&v->val, TRUE);
 	free(v);
@@ -2807,7 +2830,6 @@ static ApplyModifierResult
 ApplyModifier_Assign(const char **pp, ApplyModifiersState *st)
 {
     GNode *v_ctxt;
-    char *sv_name;
     char delim;
     char *val;
     VarParseResult res;
@@ -2820,20 +2842,13 @@ ApplyModifier_Assign(const char **pp, Ap
 	return AMR_UNKNOWN;	/* "::<unrecognised>" */
 
 
-    if (st->v->name[0] == 0) {
+    if (st->v->name[0] == '\0') {
 	*pp = mod + 1;
 	return AMR_BAD;
     }
 
     v_ctxt = st->ctxt;		/* context where v belongs */
-    sv_name = NULL;
-    if (st->exprFlags & VEF_UNDEF) {
-	/*
-	 * We need to bmake_strdup() it in case ParseModifierPart() recurses.
-	 */
-	sv_name = st->v->name;
-	st->v->name = bmake_strdup(st->v->name);
-    } else if (st->ctxt != VAR_GLOBAL) {
+    if (!(st->exprFlags & VEF_UNDEF) && st->ctxt != VAR_GLOBAL) {
 	Var *gv = VarFind(st->v->name, st->ctxt, 0);
 	if (gv == NULL)
 	    v_ctxt = VAR_GLOBAL;
@@ -2854,11 +2869,6 @@ ApplyModifier_Assign(const char **pp, Ap
 
     delim = st->startc == '(' ? ')' : '}';
     res = ParseModifierPart(pp, delim, st->eflags, st, &val, NULL, NULL, NULL);
-    if (st->exprFlags & VEF_UNDEF) {
-	/* restore original name */
-	free(st->v->name);
-	st->v->name = sv_name;
-    }
     if (res != VPR_OK)
 	return AMR_CLEANUP;
 
@@ -3630,7 +3640,7 @@ Var_Parse(const char **pp, GNode *ctxt, 
 	     * At the end, after applying all modifiers, if the expression
 	     * is still undefined, Var_Parse will return an empty string
 	     * instead of the actually computed value. */
-	    v = VarNew(varname, "", 0);
+	    v = VarNew(varname, varname, "", 0);
 	    exprFlags = VEF_UNDEF;
 	} else
 	    free(varname);
@@ -3716,7 +3726,7 @@ Var_Parse(const char **pp, GNode *ctxt, 
 	}
 	if (nstr != Buf_GetAll(&v->val, NULL))
 	    Buf_Destroy(&v->val, TRUE);
-	free(v->name);
+	free(v->name_freeIt);
 	free(v);
     }
     *out_val = nstr;

Reply via email to