URL:
  <https://savannah.gnu.org/bugs/?68774>

                 Summary: Existing shared implicit prerequisite is
reclassified as intermediate, causing unnecessary rebuilds
                   Group: make
               Submitter: chuckweiss
               Submitted: Tue 13 Oct 2026 11:17:22 PM UTC
                Severity: 3 - Normal
                Priority: 5 - Normal
              Item Group: Bug
                  Status: None
                 Privacy: Public
             Assigned to: None
             Open/Closed: Open
         Discussion Lock: Unlocked
       Component Version: 4.4.1
        Operating System: POSIX-Based
           Fixed Release: None
           Triage Status: None


    _______________________________________________________

Follow-up Comments:


-------------------------------------------------------
Date: Tue 13 Oct 2026 11:17:22 PM UTC By: chuckweiss <chuckweiss>
*Issue summary*

GNU Make incorrectly reclassifies an existing shared implicit prerequisite as
intermediate, causing unnecessary rebuilds even when its recipe preserves its
timestamp. The issue also affects `.NOTINTERMEDIATE` declarations and can
result in deletion of a pre-existing prerequisite.

*Affected versions:*
- GNU Make 4.4.1
- Official HEAD b3802782de3eff2c0f1eda9e7c0befd8cd142162, reporting 4.4.90

*Test platforms:*
- macOS 15.5, arm64
- Linux, x86_64 (GNU Make 4.4.1)

GNU Make 3.81 does not reproduce the problem.

*Reproducer*

The attached repro.mk is standalone and uses only ordinary shell commands. It
defines two implicit rules (%.a and %.b) sharing a prerequisite (%.flags). The
shared prerequisite has a phony force dependency, but its recipe is a no-op.
The .PRECIOUS declaration prevents intermediate-file cleanup so the
unnecessary rebuild can be observed independently.

Contents of repro.mk:


.PHONY: all force
.PRECIOUS: %.flags

all: cached.a cached.b

%.flags: force
        @:

%.a: %.flags
        @echo REBUILD $@

%.b: %.flags
        @echo REBUILD $@


Run these commands in an empty directory containing repro.mk. Set MAKE to the
unpatched GNU Make executable being tested:


MAKE=gmake
touch cached.flags cached.a cached.b
touch -t 200001010000 cached.flags

for pass in 1 2; do
    printf 'RUN %s\n' "$pass"
    "$MAKE" --no-print-directory -f repro.mk -rR -j1
done


The loop invokes GNU Make twice as two separate processes against the same
files. Both the prerequisite and target recipes leave their files unchanged:
the former is a no-op, and the latter only prints a diagnostic message.
Consequently, all file timestamps remain identical between the two
invocations.

The second invocation demonstrates that the erroneous rebuild is reproducible
without any intervening file changes. It is not dependent on changes made by
the first invocation.

Actual output with both 4.4.1 and unpatched HEAD:


RUN 1
REBUILD cached.b
RUN 2
REBUILD cached.b


Expected output:


RUN 1
RUN 2


Both output files already exist and are newer than cached.flags. The forced
cached.flags recipe is a no-op, so it changes neither the file nor its
timestamp. Neither target should rebuild on either invocation.

*Root cause*

During the second implicit-rule search, pattern_search() reclassifies the
already-processed cached.flags prerequisite as intermediate. Dependency
checking then follows its phony force prerequisite rather than relying on the
unchanged timestamp, causing cached.b to rebuild.

*Additional findings*

The behavior also reproduces with .NOTINTERMEDIATE: %.flags and with global
.NOTINTERMEDIATE:, despite both declarations explicitly preventing
intermediate-file classification.

Additionally, removing .PRECIOUS: %.flags from the reproducer causes GNU Make
4.4.1 to delete the pre-existing cached.flags file after rebuilding cached.b:


REBUILD cached.b
rm cached.flags


This deletion was independently reproduced on Linux. The file existed before
Make was invoked, and its recipe did not modify it.

*Proposed fix and validation*

Attached fix.patch is a candidate fix against the HEAD revision above. It
prevents this reclassification while preserving newly generated
intermediate-file behavior.

All six added regression cases fail on unpatched HEAD and pass with the patch.
All 204 tests in the focused implicit-search, pattern-rule, INTERMEDIATE,
NOTINTERMEDIATE, and SECONDARY categories pass on patched HEAD.

*Attachments*
- repro.mk — Minimal standalone reproducer
- fix.patch — Proposed fix and regression tests






    _______________________________________________________
File Attachments:

Name: fix.patch                      Size: 2.0KiB
    <https://file.savannah.gnu.org/file/fix.patch?file_id=59094>

Name: repro.mk                       Size: 145B
    <https://file.savannah.gnu.org/file/repro.mk?file_id=59095>



    AGPL NOTICE

These attachments are served by Savane. You can download the corresponding
source code of Savane at
https://savannah.gnu.org/source/savane-4936aed2f86e11f70ec9b06b0570639b784662e4.tar.gz

    _______________________________________________________

Reply to this item at:

  <https://savannah.gnu.org/bugs/?68774>

_______________________________________________
Message sent via Savannah
https://savannah.gnu.org/

Attachment: signature.asc
Description: PGP signature

Reply via email to