commit 821f2fb508de5135a5264499d69bfbffad2fb670
Author: Dmitry Goncharov <dgoncharov@users.sf.net>
Date:   Thu Oct 26 19:52:57 2023 -0400

    [SV 64822] Fix appending to a pattern specific variable.
    
    Appending to a pattern specific variable produces an incorrect value in the
    presence of a command line definition or an env override of the variable.
    
    The following takes place.
    1. At parse time record_target_var sets the value of a pattern specific
    variable to the value defined on command line or to the value of the env
    override.
    2. Later, at build time, recursively_expand_for_file appends this value of the
    variable (set in record_target_var) to the command line value again, regardless
    of the origin of the variable.
    
    This patch modifies recursively_expand_for_file to avoid appending, unless the
    origin of the variable beats or equals the origin of one of the parent
    definitions of this variable.
    
    * src/expand.c (recursively_expand_for_file): Avoid appending to a
    pattern-specific variable, unless the origin of this pattern-specific variable
    beats or equals the origin of one of the parent definitions of this variable.
    * tests/scripts/variables/append: Add tests.
    
    Reported by Markus Oberhumer <markus@oberhumer.com>.

diff --git a/tests/scripts/variables/append b/tests/scripts/variables/append
new file mode 100644
index 00000000..34c8cd05
--- /dev/null
+++ b/tests/scripts/variables/append
@@ -0,0 +1,271 @@
+#                                                                    -*-perl-*-
+
+use warnings;
+
+my $description = "Test appending to variables of various origins.";
+my $details =
+"The fix for sv 64822 went to recursively_expand_for_file."
+."There are three code paths that lead to recursively_expand_for_file."
+." - export of a variable to target envrinment."
+." - expansion of a substitution reference."
+." - other expansions of a variable that was appended to."
+."Test target env, substitution reference and regular expansion in parse and "
+."build modes.";
+
+sub test
+{
+    my $input = $_[0];
+    my $parse_time_answer = $_[1];
+    my $build_time_answer = $_[2];
+    my $opt = $_[3];
+    my $cmdline = $_[4];
+    my $export = $_[5];
+
+    run_make_test("
+v1+=file
+override v2+=override
+all: v3+=target
+all: override v4+=target_override
+al%: v5+=pattern
+al%: override v6+=pattern_override
+
+$export
+
+\$(info $input)
+all:; \$(info $input)
+",
+    "$opt $cmdline",
+    "$parse_time_answer\n$build_time_answer\n#MAKE#: 'all' is up to date.\n");
+}
+
+sub export_vars_to_env
+{
+    $ENV{'v1'} = 'env';
+    $ENV{'v2'} = 'env';
+    $ENV{'v3'} = 'env';
+    $ENV{'v4'} = 'env';
+    $ENV{'v5'} = 'env';
+    $ENV{'v6'} = 'env';
+}
+
+sub delete_vars_from_env
+{
+    delete $ENV{'v1'};
+    delete $ENV{'v2'};
+    delete $ENV{'v3'};
+    delete $ENV{'v4'};
+    delete $ENV{'v5'};
+    delete $ENV{'v6'};
+}
+
+my $input =
+' v1=$(v1), v1 origin = $(origin v1),'
+.' v2=$(v2), v2 origin = $(origin v2),'
+.' v3=$(v3), v3 origin = $(origin v3),'
+.' v4=$(v4), v4 origin = $(origin v4),'
+.' v5=$(v5), v5 origin = $(origin v5),'
+.' v6=$(v6), v6 origin = $(origin v6)';
+
+my $subref_input =
+' v1=$(v1:X=Y), v1 origin = $(origin v1),'
+.' v2=$(v2:X=Y), v2 origin = $(origin v2),'
+.' v3=$(v3:X=Y), v3 origin = $(origin v3),'
+.' v4=$(v4:X=Y), v4 origin = $(origin v4),'
+.' v5=$(v5:X=Y), v5 origin = $(origin v5),'
+.' v6=$(v6:X=Y), v6 origin = $(origin v6)';
+
+my $target_env_input =
+' v1=$(shell echo $$v1), v1 origin = $(origin v1),'
+.' v2=$(shell echo $$v2), v2 origin = $(origin v2),'
+.' v3=$(shell echo $$v3), v3 origin = $(origin v3),'
+.' v4=$(shell echo $$v4), v4 origin = $(origin v4),'
+.' v5=$(shell echo $$v5), v5 origin = $(origin v5),'
+.' v6=$(shell echo $$v6), v6 origin = $(origin v6)';
+
+my $parse_time_answer;
+my $build_time_answer;
+my $opt;
+my $cmdline;
+
+# Test 1.
+# No env or command line definitions.
+
+delete_vars_from_env();
+
+$parse_time_answer=
+'v1=file, v1 origin = file,'
+.' v2=override, v2 origin = override,'
+.' v3=, v3 origin = undefined,'
+.' v4=, v4 origin = undefined,'
+.' v5=, v5 origin = undefined,'
+.' v6=, v6 origin = undefined';
+
+$build_time_answer=
+'v1=file, v1 origin = file,'
+.' v2=override, v2 origin = override,'
+.' v3=target, v3 origin = file,'
+.' v4=target_override, v4 origin = override,'
+.' v5=pattern, v5 origin = file,'
+.' v6=pattern_override, v6 origin = override';
+
+$opt = '';
+$cmdline = '';
+
+# Test regular expansion.
+test($input, $parse_time_answer, $build_time_answer, $opt, $cmdline, '');
+
+# Test substitution reference.
+# Parse and build time answers are the same.
+test($subref_input, $parse_time_answer, $build_time_answer, $opt, $cmdline, '');
+
+# Test export to target env.
+# Parse and build time answers are the same.
+test($target_env_input, $parse_time_answer, $build_time_answer, $opt, $cmdline, 'export');
+
+# Test 2.
+# The same variables are present in env.
+
+$parse_time_answer=
+'v1=env file, v1 origin = file,'
+.' v2=env override, v2 origin = override,'
+.' v3=env, v3 origin = environment,'
+.' v4=env, v4 origin = environment,'
+.' v5=env, v5 origin = environment,'
+.' v6=env, v6 origin = environment';
+
+$build_time_answer=
+'v1=env file, v1 origin = file,'
+.' v2=env override, v2 origin = override,'
+.' v3=env target, v3 origin = file,'
+.' v4=env target_override, v4 origin = override,'
+.' v5=env pattern, v5 origin = file,'
+.' v6=env pattern_override, v6 origin = override';
+
+# Test regular expansion.
+export_vars_to_env();
+test($input, $parse_time_answer, $build_time_answer, $opt, $cmdline, '');
+
+# Test substitution reference.
+# Parse and build time answers are the same.
+export_vars_to_env();
+test($subref_input, $parse_time_answer, $build_time_answer, $opt, $cmdline, '');
+
+# Test export to target env.
+# Parse and build time answers are the same.
+export_vars_to_env();
+test($target_env_input, $parse_time_answer, $build_time_answer, $opt, $cmdline, 'export');
+
+# Test 3.
+# The same variables are present in env and -e is in effect.
+
+$parse_time_answer=
+'v1=env, v1 origin = environment override,'
+.' v2=env override, v2 origin = override,'
+.' v3=env, v3 origin = environment override,'
+.' v4=env, v4 origin = environment override,'
+.' v5=env, v5 origin = environment override,'
+.' v6=env, v6 origin = environment override';
+
+$build_time_answer=
+'v1=env, v1 origin = environment override,'
+.' v2=env override, v2 origin = override,'
+.' v3=env, v3 origin = environment override,'
+.' v4=env target_override, v4 origin = override,'
+.' v5=env, v5 origin = environment override,'
+.' v6=env pattern_override, v6 origin = override';
+
+$opt = '-e';
+
+# Test regular expansion.
+export_vars_to_env();
+test($input, $parse_time_answer, $build_time_answer, $opt, $cmdline, '');
+
+# Test substitution reference.
+# Parse and build time answers are the same.
+export_vars_to_env();
+test($subref_input, $parse_time_answer, $build_time_answer, $opt, $cmdline, '');
+
+# Test export to target env.
+# Parse and build time answers are the same.
+export_vars_to_env();
+test($target_env_input, $parse_time_answer, $build_time_answer, $opt, $cmdline, 'export');
+
+# Test 4.
+# The same variables are defined on the command line.
+
+delete_vars_from_env();
+
+$parse_time_answer=
+'v1=cmd, v1 origin = command line,'
+.' v2=cmd override, v2 origin = override,'
+.' v3=cmd, v3 origin = command line,'
+.' v4=cmd, v4 origin = command line,'
+.' v5=cmd, v5 origin = command line,'
+.' v6=cmd, v6 origin = command line';
+
+$build_time_answer=
+'v1=cmd, v1 origin = command line,'
+.' v2=cmd override, v2 origin = override,'
+.' v3=cmd, v3 origin = command line,'
+.' v4=cmd target_override, v4 origin = override,'
+.' v5=cmd, v5 origin = command line,'
+.' v6=cmd pattern_override, v6 origin = override';
+
+$opt='';
+$cmdline=' -- v1=cmd v2=cmd v3=cmd v4=cmd v5=cmd v6=cmd';
+
+# Test regular expansion.
+test($input, $parse_time_answer, $build_time_answer, $opt, $cmdline, '');
+
+# Test substitution reference.
+# Parse and build time answers are the same.
+test($subref_input, $parse_time_answer, $build_time_answer, $opt, $cmdline, '');
+
+# Test export to target env.
+# Parse and build time answers are the same.
+test($target_env_input, $parse_time_answer, $build_time_answer, $opt, $cmdline, 'export');
+
+# Test 5.
+# The same variables are defined on the command line and present in env.
+# Parse and built time answers as the same as when the variables are not
+# present in env.
+
+$opt='';
+
+# Test regular expansion.
+export_vars_to_env();
+test($input, $parse_time_answer, $build_time_answer, $opt, $cmdline, '');
+
+# Test substitution reference.
+# Parse and build time answers are the same.
+export_vars_to_env();
+test($subref_input, $parse_time_answer, $build_time_answer, $opt, $cmdline, '');
+
+# Test export to target env.
+# Parse and build time answers are the same.
+export_vars_to_env();
+test($target_env_input, $parse_time_answer, $build_time_answer, $opt, $cmdline, 'export');
+
+# Test 6.
+# The same variables are defined on the command line and present in env and -e
+# is in effect.
+# Parse and built time answers as the same as when the variables are not
+# present in env.
+
+$opt='-e';
+
+# Test regular expansion.
+export_vars_to_env();
+test($input, $parse_time_answer, $build_time_answer, $opt, $cmdline, '');
+
+# Test substitution reference.
+# Parse and build time answers are the same.
+export_vars_to_env();
+test($subref_input, $parse_time_answer, $build_time_answer, $opt, $cmdline, '');
+
+# Test export to target env.
+# Parse and build time answers are the same.
+export_vars_to_env();
+test($target_env_input, $parse_time_answer, $build_time_answer, $opt, $cmdline, 'export');
+
+1;
