This is an automated email from the ASF dual-hosted git repository.

gnodet pushed a commit to branch maven-4.0.x
in repository https://gitbox.apache.org/repos/asf/maven.git


The following commit(s) were added to refs/heads/maven-4.0.x by this push:
     new 1b53468bfe Fix #12074: prevent false parent cycle with shade plugin's 
dependency-reduced-pom.xml (#12079)
1b53468bfe is described below

commit 1b53468bfe32771351d4c28d4f677c68240cb32f
Author: Guillaume Nodet <[email protected]>
AuthorDate: Tue May 19 15:33:49 2026 +0200

    Fix #12074: prevent false parent cycle with shade plugin's 
dependency-reduced-pom.xml (#12079)
    
    In readParentLocally(), the GA mismatch check ran after readAsParentModel()
    which recursively resolves the candidate's parent chain. When a generated 
POM
    (e.g., dependency-reduced-pom.xml) sits alongside the project POM and the
    default relative path resolves to the wrong POM, the recursive resolution
    adds the shared parent's model ID to the chain — triggering a false cycle.
    
    Move the GA check before the recursive call by reading only the file model
    first. This matches the behavior of the Maven 3 model builder which checked
    GA before recursing.
    
    Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
---
 .../maven/impl/model/DefaultModelBuilder.java      | 26 +++++----
 .../maven/impl/model/ParentCycleDetectionTest.java | 63 ++++++++++++++++++++++
 2 files changed, 78 insertions(+), 11 deletions(-)

diff --git 
a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelBuilder.java
 
b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelBuilder.java
index 7e155fa4dd..3c4cd3c4b0 100644
--- 
a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelBuilder.java
+++ 
b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelBuilder.java
@@ -1116,6 +1116,21 @@ private Model readParentLocally(
 
             try {
                 ModelBuilderSessionState derived = derive(candidateSource);
+
+                // Check GA match BEFORE readAsParentModel() which recursively 
resolves
+                // the candidate's parent chain and can trigger false cycle 
detection (GH-12074).
+                Model fileModel = derived.readFileModel();
+                String fileGroupId = getGroupId(fileModel);
+                String fileArtifactId = fileModel.getArtifactId();
+
+                if (fileGroupId == null
+                        || !fileGroupId.equals(parent.getGroupId())
+                        || fileArtifactId == null
+                        || !fileArtifactId.equals(parent.getArtifactId())) {
+                    mismatchRelativePathAndGA(childModel, fileGroupId, 
fileArtifactId);
+                    return null;
+                }
+
                 Model candidateModel = 
derived.readAsParentModel(profileActivationContext, parentChain);
                 // Add profiles from parent, preserving model ID tracking
                 for (Map.Entry<String, List<Profile>> entry :
@@ -1123,19 +1138,8 @@ private Model readParentLocally(
                     addActivePomProfiles(entry.getKey(), entry.getValue());
                 }
 
-                String groupId = getGroupId(candidateModel);
-                String artifactId = candidateModel.getArtifactId();
                 String version = getVersion(candidateModel);
 
-                // Ensure that relative path and GA match, if both are provided
-                if (groupId == null
-                        || !groupId.equals(parent.getGroupId())
-                        || artifactId == null
-                        || !artifactId.equals(parent.getArtifactId())) {
-                    mismatchRelativePathAndGA(childModel, groupId, artifactId);
-                    return null;
-                }
-
                 if (version != null && parent.getVersion() != null && 
!version.equals(parent.getVersion())) {
                     try {
                         VersionRange parentRange = 
versionParser.parseVersionRange(parent.getVersion());
diff --git 
a/impl/maven-impl/src/test/java/org/apache/maven/impl/model/ParentCycleDetectionTest.java
 
b/impl/maven-impl/src/test/java/org/apache/maven/impl/model/ParentCycleDetectionTest.java
index f0db48c998..c133ffb1ab 100644
--- 
a/impl/maven-impl/src/test/java/org/apache/maven/impl/model/ParentCycleDetectionTest.java
+++ 
b/impl/maven-impl/src/test/java/org/apache/maven/impl/model/ParentCycleDetectionTest.java
@@ -187,6 +187,69 @@ void testDirectCycleDetection(@TempDir Path tempDir) 
throws IOException {
         }
     }
 
+    /**
+     * Reproduces GH-12074: dependency-reduced-pom.xml from shade plugin 
causes false parent cycle.
+     *
+     * When a POM's parent relative path resolves to a POM with a different GA 
(e.g., the
+     * default ".." from dependency-reduced-pom.xml resolves to a sibling 
project POM), the model
+     * builder should detect the GA mismatch early and skip local resolution — 
not recursively
+     * read the candidate's parents and trigger a false cycle.
+     */
+    @Test
+    void testNoFalseCycleWhenRelativePathResolvesToWrongPom(@TempDir Path 
tempDir) throws IOException {
+        Files.createDirectories(tempDir.resolve(".mvn"));
+
+        // Root POM found by resolving ".." from the project directory.
+        // It has a different GA than the expected parent but shares the same 
parent reference.
+        Path rootPom = tempDir.resolve("pom.xml");
+        Files.writeString(rootPom, """
+            <project xmlns="http://maven.apache.org/POM/4.0.0";>
+                <modelVersion>4.0.0</modelVersion>
+                <parent>
+                    <groupId>test</groupId>
+                    <artifactId>parent</artifactId>
+                    <version>1.0</version>
+                    <relativePath/>
+                </parent>
+                <artifactId>root</artifactId>
+            </project>
+            """);
+
+        // Project POM in a subdirectory, referencing the same parent.
+        // Default relativePath ".." resolves to rootPom above, which has GA 
test:root (not test:parent).
+        Path projectPom = tempDir.resolve("project").resolve("pom.xml");
+        Files.createDirectories(projectPom.getParent());
+        Files.writeString(projectPom, """
+            <project xmlns="http://maven.apache.org/POM/4.0.0";>
+                <modelVersion>4.0.0</modelVersion>
+                <parent>
+                    <groupId>test</groupId>
+                    <artifactId>parent</artifactId>
+                    <version>1.0</version>
+                </parent>
+                <artifactId>project</artifactId>
+            </project>
+            """);
+
+        // Use BUILD_EFFECTIVE to match what the shade plugin triggers via 
compat ProjectBuilder
+        ModelBuilderRequest request = ModelBuilderRequest.builder()
+                .session(session)
+                .source(Sources.buildSource(projectPom))
+                .requestType(ModelBuilderRequest.RequestType.BUILD_EFFECTIVE)
+                .build();
+
+        try {
+            modelBuilder.newSession().build(request);
+        } catch (StackOverflowError error) {
+            fail("StackOverflowError — cycle detection not working");
+        } catch (ModelBuilderException exception) {
+            // Parent not found externally is expected; a cycle error is not
+            if (exception.getMessage().contains("cycle")) {
+                fail("False parent cycle detected: " + exception.getMessage());
+            }
+        }
+    }
+
     @Test
     void testMultipleModulesWithSameParentDoNotCauseCycle(@TempDir Path 
tempDir) throws IOException {
         // Create .mvn directory to mark root

Reply via email to