commit 47de1003b9658fb1e1c7ecb4becf0738bb043b70
Author: Dmitry Goncharov <dgoncharov@users.sf.net>
Date:   Sat Feb 25 13:43:08 2023 -0500

    [SV 63840] Prevent match-anything rules from matching default suffixes.
    
    Match-anything rules are not supposed to apply to files with the default
    or user defined suffixes. Ensure that this restriction holds even when
    built-in rules are disabled.
    
    * src/default.c (set_default_suffixes): Define default_suffixes with extern
    linkage.
    (set_default_suffixes): Enter the default suffixes whether the built-in rules
    are enabled or not.  When the built-in rules are disabled, the default
    suffixes are still needed to disqualify match-anything rules.
    * src/read.c (record_files): Reset file->builtin for each user defined suffix.
    This makes a difference when the user defines suffixes that match the default
    suffixes.
    * src/rule.c (convert_default_suffixes): Declare extern default_suffixes.
    (convert_default_suffixes): Convert all default suffixes from array
    default_suffixes to dummy pattern rules.
    (convert_to_pattern): Call convert_default_suffixes.
    (convert_to_pattern): When built-in rules are disabled, convert suffix rules
    only for the suffixes defined in the makefile.
    * tests/scripts/features/matchanyrules: Add new tests.
    * tests/scripts/features/suffixrules: Ditto.

diff --git a/tests/scripts/features/matchanyrules b/tests/scripts/features/matchanyrules
new file mode 100644
index 00000000..84b7bb70
--- /dev/null
+++ b/tests/scripts/features/matchanyrules
@@ -0,0 +1,143 @@
+#                                                                    -*-perl-*-
+
+$description = "Test match anything rules.";
+
+$details = "";
+
+touch('hello.o');
+
+# Good match.
+run_make_test(q!
+hello:
+%: %.o; $(info $@ from $<)
+!, '', "hello from hello.o\n#MAKE#: 'hello' is up to date.\n");
+
+# Good match when the default rules are disabled.
+run_make_test(q!
+hello:
+%: %.o; $(info $@ from $<)
+!, '-r', "hello from hello.o\n#MAKE#: 'hello' is up to date.\n");
+
+# Good match when the default rules are disabled in the makefile.
+run_make_test(q!
+MAKEFLAGS=+-r
+hello:
+%: %.o; $(info $@ from $<)
+!, '', "hello from hello.o\n#MAKE#: 'hello' is up to date.\n");
+
+# Good match when the list of default suffixes is cleared.
+run_make_test(q!
+.SUFFIXES:
+hello:
+%: %.o; $(info $@ from $<)
+!, '', "hello from hello.o\n#MAKE#: 'hello' is up to date.\n");
+
+unlink('hello.o');
+
+
+# .out is at the beginning of the list of the default suffixes.
+# .el is at the end of the list.
+# .c is in the middle.
+# a.c is the shortest name that matches.
+
+my @files = ('hello.out', 'hello.c', 'hello.el', 'a.c');
+for my $f (@files) {
+touch("$f.o");
+
+# Match anything rules do not apply to files with specific data, such as .c.
+run_make_test("
+$f:
+%: %.o; \$(info \$@ from \$<)
+", '', "#MAKE#: Nothing to be done for '$f'.\n");
+
+# Match anything rules do not apply to files with specific data, such as .c,
+# when the default rules are disabled.
+run_make_test("
+$f:
+%: %.o; \$(info \$@ from \$<)
+", '-r', "#MAKE#: Nothing to be done for '$f'.\n");
+
+# Match anything rules do not apply to files with specific data, such as .c,
+# when the default rules are disabled in the makefile.
+run_make_test("
+MAKEFLAGS+=-r
+$f:
+%: %.o; \$(info \$@ from \$<)
+", '', "#MAKE#: Nothing to be done for '$f'.\n");
+
+# Match anything rules do not apply to files with specific data, such as .c,
+# when the list of default suffixes is cleared.
+run_make_test("
+.SUFFIXES:
+$f:
+%: %.o; \$(info \$@ from \$<)
+", '', "#MAKE#: Nothing to be done for '$f'.\n");
+
+# Match anything rules do not apply to files with specific data, such as .c,
+# when the list of default suffixes is cleared and the suffixes are defined by
+# the user.
+run_make_test("
+.SUFFIXES:
+.SUFFIXES: .c .o .out .a .el
+$f:
+%: %.o; \$(info \$@ from \$<)
+", '', "#MAKE#: Nothing to be done for '$f'.\n");
+
+unlink("$f.o");
+}
+
+touch('hello.x.o');
+
+# A user defined match-anything rule matches a file with an unknown suffix.
+
+run_make_test(q!
+hello.x:
+%: %.o; $(info $@ from $<)
+!, '', "hello.x from hello.x.o\n#MAKE#: 'hello.x' is up to date.\n");
+
+# A user defined match-anything rule matches a file with an unknown suffix
+# when built-in rules are disabled.
+
+run_make_test(q!
+hello.x:
+%: %.o; $(info $@ from $<)
+!, '-r', "hello.x from hello.x.o\n#MAKE#: 'hello.x' is up to date.\n");
+
+# A user defined match-anything rule matches a file with an unknown suffix
+# when built-in rules are disabled in the makefile.
+
+run_make_test(q!
+MAKEFLAGS+=-r
+hello.x:
+%: %.o; $(info $@ from $<)
+!, '', "hello.x from hello.x.o\n#MAKE#: 'hello.x' is up to date.\n");
+
+# A user defined match-anything rule matches a file with an unknown suffix
+# when the default suffixes are cleared.
+
+run_make_test(q!
+.SUFFIXES:
+hello.x:
+%: %.o; $(info $@ from $<)
+!, '', "hello.x from hello.x.o\n#MAKE#: 'hello.x' is up to date.\n");
+
+# Once the user adds the new suffix, match-anything rules no longer match.
+
+run_make_test(q!
+.SUFFIXES:
+.SUFFIXES: .x
+hello.x:
+%: %.o; $(info $@ from $<)
+!, '', "#MAKE#: Nothing to be done for 'hello.x'.\n");
+
+# Once the user adds the new suffix, match-anything rules no longer match.
+# This time built-in rules are disabled.
+
+run_make_test(q!
+.SUFFIXES:
+.SUFFIXES: .x
+hello.x:
+%: %.o; $(info $@ from $<)
+!, '-r', "#MAKE#: Nothing to be done for 'hello.x'.\n");
+
+1;
diff --git a/tests/scripts/features/suffixrules b/tests/scripts/features/suffixrules
index 5e969b2a..65cdf31b 100644
--- a/tests/scripts/features/suffixrules
+++ b/tests/scripts/features/suffixrules
@@ -154,11 +154,159 @@ all: hello.o
 run_make_test(q!
 all: hello.o
 MAKEFLAGS += -r
+.SUFFIXES:
 .SUFFIXES: .c .o
 .c.o:; $(info $@ user defined .c.o rule)
 !, '', "hello.o user defined .c.o rule\n#MAKE#: Nothing to be done for 'all'.\n");
 
-unlink('hello.c', 'hello.o');
+# Both .c and .o suffixes have to be defined.
+
+run_make_test(q!
+all: hello.o
+.SUFFIXES: .c
+.c.o:; $(info $@ user defined .c.o rule)
+!, '-r', "#MAKE#: *** No rule to make target 'hello.o', needed by 'all'.  Stop.\n", 512);
+
+# Both .c and .o suffixes have to be defined.
+
+run_make_test(q!
+MAKEFLAGS += -r
+all: hello.o
+.SUFFIXES: .c
+.c.o:; $(info $@ user defined .c.o rule)
+!, '', "#MAKE#: *** No rule to make target 'hello.o', needed by 'all'.  Stop.\n", 512);
+
+# Both .c and .o suffixes have to be defined.
+
+run_make_test(q!
+all: hello.o
+.SUFFIXES: .o
+.c.o:; $(info $@ user defined .c.o rule)
+!, '-r', "#MAKE#: *** No rule to make target 'hello.o', needed by 'all'.  Stop.\n", 512);
+
+# Both .c and .o suffixes have to be defined.
+
+run_make_test(q!
+MAKEFLAGS += -r
+all: hello.o
+.SUFFIXES: .o
+.c.o:; $(info $@ user defined .c.o rule)
+!, '', "#MAKE#: *** No rule to make target 'hello.o', needed by 'all'.  Stop.\n", 512);
+
+# When -r is specified a user defined suffix rule won't apply, unless the user
+# also defined the suffixes.
+
+run_make_test(q!
+all: hello.o
+.c.o:; $(info $@ user defined .c.o rule)
+!, '-r', "#MAKE#: *** No rule to make target 'hello.o', needed by 'all'.  Stop.\n", 512);
+
+# When -r is specified a user defined suffix rule won't apply, unless the user
+# also defined the suffixes.
+
+run_make_test(q!
+all: hello.o
+MAKEFLAGS += -r
+.c.o:; $(info $@ user defined .c.o rule)
+!, '', "#MAKE#: *** No rule to make target 'hello.o', needed by 'all'.  Stop.\n", 512);
+
+
+# Single suffix rules are converted to match-anything rules.
+# Therefore, single suffix rules don't apply to files with known suffixes.
+
+# Good match.
+touch('hello.c');
+unlink('hello.o');
+run_make_test('all: hello.o', 'COMPILE.c=@echo OUTPUT_OPTION=', 'hello.c');
+
+# The built-in double suffix rule beats this user defined single suffix rule, because
+# a single suffix rule is a match-anything rule.
+
+run_make_test(q!
+all: hello.o
+.c:; $(info $@ user defined .c rule)
+!, 'COMPILE.c=@echo OUTPUT_OPTION=', 'hello.c');
+
+# No match, because the list of default suffixes is cleared.
+
+run_make_test(q!
+all: hello.o
+.c:; $(info $@ user defined .c rule)
+!, '-r', "#MAKE#: *** No rule to make target 'hello.o', needed by 'all'.  Stop.\n", 512);
+
+# No match, because the list of default suffixes is cleared.
+
+run_make_test(q!
+all: hello.o
+MAKEFLAGS += -r
+.c.o:; $(info $@ user defined .c rule)
+!, '', "#MAKE#: *** No rule to make target 'hello.o', needed by 'all'.  Stop.\n", 512);
+
+# No match, because the list of default suffixes is cleared.
+
+run_make_test(q!
+.SUFFIXES:
+all: hello.o
+.c:; $(info $@ user defined .c rule)
+!, '', "#MAKE#: *** No rule to make target 'hello.o', needed by 'all'.  Stop.\n", 512);
+
+# User defined single suffix rule does not apply to hello.c, because .c is a
+# known suffix.
+
+touch('hello.c.o');
+unlink('hello.c');
+run_make_test(q!
+.SUFFIXES:
+.SUFFIXES: .o
+all: hello.c
+.o:; $(info $@ user defined .o rule)
+!, '-r', "#MAKE#: *** No rule to make target 'hello.c', needed by 'all'.  Stop.\n", 512);
+
+# User defined single suffix rule.
+
+touch('hello.c');
+run_make_test(q!
+.SUFFIXES:
+.SUFFIXES: .c
+all: hello
+.c:; $(info $@ user defined .c rule)
+!, '-r', "hello user defined .c rule\n#MAKE#: Nothing to be done for 'all'.\n");
+
+
+unlink('hello.c', 'hello.o', 'hello.c.o');
+
+# A user defined single suffix rule and new suffixes.
+
+touch('hello.q.x');
+
+run_make_test(q!
+.SUFFIXES:
+.SUFFIXES: .x
+all: hello.q
+.x:; $(info $@ user defined .x rule)
+!, '-r', "hello.q user defined .x rule\n#MAKE#: Nothing to be done for 'all'.\n");
+
+# This time single suffix .x rule does not apply, because .q is a known suffix.
+
+run_make_test(q!
+.SUFFIXES:
+.SUFFIXES: .x .q
+all: hello.q
+.x:; $(info $@ user defined .x rule)
+!, '-r', "#MAKE#: *** No rule to make target 'hello.q', needed by 'all'.  Stop.\n", 512);
+
+# A user defined single suffix rule and a new suffix.
+
+touch('hello.x');
+
+run_make_test(q!
+.SUFFIXES:
+.SUFFIXES: .x
+all: hello
+.x:; $(info $@ user defined .x rule)
+!, '-r', "hello user defined .x rule\n#MAKE#: Nothing to be done for 'all'.\n");
+
+unlink('hello', 'hello.x', 'hello.q.x');
 
 # Complete
 1;
