From a382ac6cd17eedf535407d29bcfc3cef04bbc3bc Mon Sep 17 00:00:00 2001
From: Dmitry Goncharov <dgoncharov@users.sf.net>
Date: Sun, 4 Feb 2024 11:16:40 -0500
Subject: [PATCH] [SV 64803] Set origin for unmodified variables after -e

Ensure the origin of all variables inherited from the environment is
"environment override" if -e is given.  Previously only variables
that were set in the makefile had this origin.

PDS: Most of these changes are from Dmitry but I slightly modified
the algorithm: instead of rearranging the way in which MAKEFLAGS is
parsed we reset the env_override value to the default before we
re-parse MAKEFLAGS, then we set the origin of all env vars to the
correct value based on the new setting.

* NEWS: Mention the change for backward-compatibility.
* src/main.c (main): Ensure MAKEFLAGS is always marked special.
(reset_makeflags): Set env_overrides back to default before parsing
MAKEFLAGS.
(decode_switches): Call reset_env_override() to check for changes.
* src/variable.h (reset_env_override): Declare a new function.
* src/variable.c (reset_env_override): Go through all env variables
and ensure the origin is correct based on env_overrides.
(set_env_override): Helper function for the hash.
* tests/scripts/functions/foreach: Fix tests.
* tests/scripts/functions/let: Ditto.
* tests/scripts/functions/origin: Ditto.
* tests/scripts/options/dash-e: Add tests.
---
 NEWS                            |   5 ++
 src/main.c                      |  12 +++-
 src/variable.c                  |  34 +++++----
 src/variable.h                  |   1 +
 tests/scripts/functions/foreach |   4 +-
 tests/scripts/functions/let     |   2 +-
 tests/scripts/functions/origin  |   4 +-
 tests/scripts/options/dash-e    | 121 ++++++++++++++++++++++++++++++++
 8 files changed, 163 insertions(+), 20 deletions(-)

diff --git a/NEWS b/NEWS
index 676794d6..96d8ce33 100644
--- a/NEWS
+++ b/NEWS
@@ -37,6 +37,11 @@ https://sv.gnu.org/bugs/index.php?group=make&report_id=111&fix_release_id=111&se
   things like "ifeq ((foo,bar),)" are now syntax errors.  Use a variable to
   hide the comma if needed: "COMMA = ," / "ifeq ((foo$(COMMA)bar),)".
 
+* WARNING: Backward-incompatibility!
+  If -e is given all environment variables will now have an origin of
+  "environment override" even if they are not otherwise set in the makefile.
+  See https://savannah.gnu.org/bugs/index.php?64803
+
 * NOTE: Deprecated behavior.
   The check in GNU Make 4.3 for suffix rules with prerequisites didn't check
   single-suffix rules, only double-suffix rules.  Add the missing check.
diff --git a/src/main.c b/src/main.c
index 99011d9e..15104212 100644
--- a/src/main.c
+++ b/src/main.c
@@ -1445,6 +1445,7 @@ main (int argc, char **argv, char **envp)
 #endif
 
   /* Initialize the special variables.  */
+  define_variable_cname ("MAKEFLAGS", "", o_default, 0)->special = 1;
   define_variable_cname (".VARIABLES", "", o_default, 0)->special = 1;
   /* define_variable_cname (".TARGETS", "", o_default, 0)->special = 1; */
   define_variable_cname (".RECIPEPREFIX", "", o_default, 0)->special = 1;
@@ -3090,6 +3091,8 @@ handle_non_switch_argument (const char *arg, enum variable_origin origin)
 void
 reset_makeflags (enum variable_origin origin)
 {
+  /* Reset to default values.  */
+  env_overrides = 0;
   decode_env_switches (STRING_SIZE_TUPLE(MAKEFLAGS_NAME), origin);
   construct_include_path (include_dirs ? include_dirs->list : NULL);
   disable_builtins ();
@@ -3363,6 +3366,9 @@ decode_switches (int argc, const char **argv, enum variable_origin origin)
 
   /* Perform any special switch handling.  */
   run_silent = silent_flag;
+
+  /* Check variable priorities.  */
+  reset_env_override ();
 }
 
 /* Decode switches from environment variable ENVAR (which is LEN chars long).
@@ -3453,8 +3459,8 @@ quote_for_env (char *out, const char *in)
 }
 
 /* Disable builtin variables and rules, if -R or -r is specified.
- * This function is called at parse time whenever MAKEFLAGS is modified and
- * also when the parsing phase is over.  */
+   This function is called at parse time whenever MAKEFLAGS is modified and
+   also when the parsing phase is over.  */
 
 static
 void disable_builtins ()
@@ -3837,7 +3843,7 @@ die (int status)
         verify_file_data_base ();
 
       /* Unload plugins before jobserver integrity check in case a plugin
-       * participates in jobserver.  */
+         participates in jobserver.  */
       unload_all ();
 
       clean_jobserver (status);
diff --git a/src/variable.c b/src/variable.c
index 9d44cd4a..2026e987 100644
--- a/src/variable.c
+++ b/src/variable.c
@@ -564,9 +564,7 @@ lookup_variable (const char *name, size_t length)
                     *nptr++ = '$';
                   }
                 else
-                  {
-                    *nptr++ = *sptr;
-                  }
+                  *nptr++ = *sptr;
                 sptr++;
               }
 
@@ -704,12 +702,10 @@ initialize_file_variables (struct file *file, int reading)
                   v->flavor = f_simple;
                 }
               else
-                {
-                  v = do_variable_definition (
-                    &p->variable.fileinfo, p->variable.name, p->variable.value,
-                    p->variable.origin, p->variable.flavor,
-                    p->variable.conditional, s_pattern);
-                }
+                v = do_variable_definition (
+                  &p->variable.fileinfo, p->variable.name, p->variable.value,
+                  p->variable.origin, p->variable.flavor,
+                  p->variable.conditional, s_pattern);
 
               /* Also mark it as a per-target and copy export status. */
               v->per_target = p->variable.per_target;
@@ -1911,6 +1907,23 @@ warn_undefined (const char *name, size_t len)
                     (int)len, name));
     }
 }
+
+static void
+set_env_override (const void *item, void *arg UNUSED)
+{
+  struct variable *v = (struct variable *)item;
+  enum variable_origin old = env_overrides ? o_env : o_env_override;
+  enum variable_origin new = env_overrides ? o_env_override : o_env;
+
+  if (v->origin == old)
+    v->origin = new;
+}
+
+void
+reset_env_override ()
+{
+  hash_map_arg (&global_variable_set.table, set_env_override, NULL);
+}
 
 /* Print information for variable V, prefixing it with PREFIX.  */
 
@@ -1985,7 +1998,6 @@ print_variable (const void *item, void *arg)
     }
 }
 
-
 static void
 print_auto_variable (const void *item, void *arg)
 {
@@ -1995,7 +2007,6 @@ print_auto_variable (const void *item, void *arg)
     print_variable (item, arg);
 }
 
-
 static void
 print_noauto_variable (const void *item, void *arg)
 {
@@ -2005,7 +2016,6 @@ print_noauto_variable (const void *item, void *arg)
     print_variable (item, arg);
 }
 
-
 /* Print all the variables in SET.  PREFIX is printed before
    the actual variable definitions (everything else is comments).  */
 
diff --git a/src/variable.h b/src/variable.h
index f6f0cb32..9cedebcd 100644
--- a/src/variable.h
+++ b/src/variable.h
@@ -205,6 +205,7 @@ struct variable *define_variable_in_set (const char *name, size_t length,
                                          struct variable_set *set,
                                          const floc *flocp);
 void warn_undefined (const char* name, size_t length);
+void reset_env_override (void);
 
 /* Define a variable in the current variable set.  */
 
diff --git a/tests/scripts/functions/foreach b/tests/scripts/functions/foreach
index 27dae15a..3af37a23 100644
--- a/tests/scripts/functions/foreach
+++ b/tests/scripts/functions/foreach
@@ -28,8 +28,8 @@ all: auto for2
 auto : ; @echo $(av)
 for2: ; @echo $(fe)',
               '-e WHITE=WHITE CFLAGS=',
-              "undefined file default file environment default file command line override automatic automatic
-foo.o bletch.o null.o @.o garf.o .o    .o undefined.o file.o default.o file.o environment.o default.o file.o command.o line.o override.o automatic.o automatic.o");
+              "undefined file default file environment override default file command line override automatic automatic
+foo.o bletch.o null.o @.o garf.o .o    .o undefined.o file.o default.o file.o environment.o override.o default.o file.o command.o line.o override.o automatic.o automatic.o");
 
 # TEST 1: Test that foreach variables take precedence over global
 # variables in a global scope (like inside an eval).  Tests bug #11913
diff --git a/tests/scripts/functions/let b/tests/scripts/functions/let
index d2e4d822..8c6c1bcb 100644
--- a/tests/scripts/functions/let
+++ b/tests/scripts/functions/let
@@ -85,7 +85,7 @@ all: auto target
 auto: ; @echo $(let $(auto_var),,$(av)) $(av)
 $(let AR foo,bar foo ,$(eval $(value mktarget)))',
               '-e WHITE=WHITE CFLAGS=',
-              "automatic automatic automatic automatic automatic automatic automatic automatic automatic undefined default environment default file command line override automatic automatic
+              "automatic automatic automatic automatic automatic automatic automatic automatic automatic undefined default environment override default file command line override automatic automatic
 ar_foo _
 ");
 
diff --git a/tests/scripts/functions/origin b/tests/scripts/functions/origin
index 9b9fd56a..78e0e6f0 100644
--- a/tests/scripts/functions/origin
+++ b/tests/scripts/functions/origin
@@ -36,10 +36,10 @@ all: auto
 auto :
 > @echo $(av)',
     '-e WHITE=WHITE CFLAGS=',
-    'undefined default environment default file command line override automatic
+    'undefined default environment override default file command line override automatic
 undefined
 default
-environment
+environment override
 default
 file
 command line
diff --git a/tests/scripts/options/dash-e b/tests/scripts/options/dash-e
index e4659fb2..423081e3 100644
--- a/tests/scripts/options/dash-e
+++ b/tests/scripts/options/dash-e
@@ -21,4 +21,125 @@ recurse: ; @$(MAKE) -f #MAKEFILE#
               '-e --no-print-directory FOO=1 recurse',
               "FOO [command line]: 1\nFOO [command line]: 1\n#MAKE#[1]: 'all' is up to date.");
 
+# SV 64803.
+# Test that the origin of an env variable is 'enviroment override' when -e
+# is set and the makefile does not modify the variable.
+# First run the test without -e and then with -e.
+
+mkdir('lib', 0777);
+
+create_file('lib/makefile',
+'$(info in submake value=$(hello), origin=$(origin hello))
+all:; @echo "value=$(hello), origin=$(origin hello)"'."\n");
+
+# No -e.
+$ENV{hello} = 'world';
+run_make_test(q!
+.RECIPEPREFIX = >
+$(info value=$(hello), origin=$(origin hello))
+all:
+> @echo "value=$(hello), origin=$(origin hello)"
+> @$(MAKE) -C lib
+.PHONY: lib all
+!,
+              '-s',
+              "value=world, origin=environment\nvalue=world, origin=environment\n".
+              "in submake value=world, origin=environment\nvalue=world, origin=environment");
+
+# -e is specified on the command line.
+my @opts = ('-e', '--environment-overrides');
+for my $opt (@opts) {
+$ENV{hello} = 'world';
+run_make_test(q!
+.RECIPEPREFIX = >
+$(info value=$(hello), origin=$(origin hello))
+all:
+> @echo "value=$(hello), origin=$(origin hello)"
+> @$(MAKE) -C lib
+.PHONY: lib all
+!,
+              "-s $opt",
+              "value=world, origin=environment override\nvalue=world, origin=environment override\n".
+              "in submake value=world, origin=environment override\nvalue=world, origin=environment override");
+}
+
+# MAKEFLAGS from env affects top level make.
+$ENV{hello} = 'world';
+$ENV{MAKEFLAGS} = 'e';
+run_make_test(q!
+.RECIPEPREFIX = >
+$(info value=$(hello), origin=$(origin hello))
+all:
+> @echo "value=$(hello), origin=$(origin hello)"
+> @$(MAKE) -C lib
+.PHONY: lib all
+!,
+              "-s",
+              "value=world, origin=environment override\nvalue=world, origin=environment override\n".
+              "in submake value=world, origin=environment override\nvalue=world, origin=environment override");
+
+# -e is passed to submake on the command line.
+$ENV{hello} = 'world';
+run_make_test(q!
+.RECIPEPREFIX = >
+$(info value=$(hello), origin=$(origin hello))
+all:
+> @echo "value=$(hello), origin=$(origin hello)"
+> @$(MAKE) -e -C lib
+.PHONY: lib all
+!,
+              "-s",
+              "value=world, origin=environment\nvalue=world, origin=environment\n".
+              "in submake value=world, origin=environment override\nvalue=world, origin=environment override");
+
+# MAKEFLAGS is reset for submake.
+$ENV{hello} = 'world';
+run_make_test(q!
+.RECIPEPREFIX = >
+$(info value=$(hello), origin=$(origin hello))
+all:
+> @echo "value=$(hello), origin=$(origin hello)"
+> @$(MAKE) -C lib MAKEFLAGS=
+.PHONY: lib all
+!,
+              "-se",
+              "value=world, origin=environment override\nvalue=world, origin=environment override\n".
+              "in submake value=world, origin=environment\nvalue=world, origin=environment");
+
+# Some MAKEFLAGS in top make env.
+# This MAKEFLAGS does not conform to the format that make itself produces for
+# submake. However, make still parses and honors this MAKEFLAGS.
+# This test checks that make does not confuse 'e' in 'extramk' with '-e'.
+$ENV{MAKEFLAGS} = 'r -Iextramk -k bye=moon';
+$ENV{hello} = 'world';
+run_make_test(q!
+.RECIPEPREFIX = >
+$(info value=$(hello), origin=$(origin hello))
+all:
+> @echo "value=$(hello), origin=$(origin hello)"
+> @$(MAKE) -C lib
+.PHONY: lib all
+!,
+              "-s",
+              "value=world, origin=environment\nvalue=world, origin=environment\n".
+              "in submake value=world, origin=environment\nvalue=world, origin=environment");
+
+# Some MAKEFLAGS in top make env.
+# This MAKEFLAGS does not conform to the format that make itself produces for
+# submake. However, make still parses and honors this MAKEFLAGS.
+# This test checks that make detects '-e' in this MAKEFLAGS.
+$ENV{MAKEFLAGS} = 'r -Iextramk -ke bye=moon';
+$ENV{hello} = 'world';
+run_make_test(q!
+.RECIPEPREFIX = >
+$(info value=$(hello), origin=$(origin hello))
+all:
+> @echo "value=$(hello), origin=$(origin hello)"
+> @$(MAKE) -C lib
+.PHONY: lib all
+!,
+              "-s",
+              "value=world, origin=environment override\nvalue=world, origin=environment override\n".
+              "in submake value=world, origin=environment override\nvalue=world, origin=environment override");
+
 1;