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

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


The following commit(s) were added to refs/heads/master by this push:
     new 4ff22094bd [#12301] Fix StackOverflowError with internal parent and 
CI-friendly revision (#12323)
4ff22094bd is described below

commit 4ff22094bd645ef4fea671524fa8c81eb30e847b
Author: Guillaume Nodet <[email protected]>
AuthorDate: Fri Jun 19 11:00:54 2026 +0200

    [#12301] Fix StackOverflowError with internal parent and CI-friendly 
revision (#12323)
    
    * [#12301] Fix StackOverflowError with internal parent and CI-friendly 
revision
    
    Re-forward-port the GH-12301 fix from maven-4.0.x to master, using
    ThreadLocal<Set<Path>> instead of the shared ConcurrentHashMap.newKeySet()
    that caused a race condition with PhasingExecutor parallel model loading.
    
    The original forward-port (#12317) used a shared set to track which POM 
files
    were being read by doReadFileModel(). This prevented the StackOverflowError
    from GH-12301, but caused false cycle detection when parallel threads in
    loadFilePom() read the same model simultaneously — breaking
    ConsumerPomBuilderTest.testSimpleConsumer.
    
    The fix uses ThreadLocal so each thread tracks only its own call stack,
    preventing actual recursive cycles within the same thread without blocking
    legitimate parallel reads across threads.
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    
    * Refactor: pass activeModelReads through call stack instead of ThreadLocal
    
    Replace ThreadLocal<Set<Path>> with a stack-passed Set<Path> parameter
    threaded through readFileModel() → doReadFileModel() → 
getEnhancedProperties().
    Each readFileModel() entry point creates a fresh HashSet, avoiding any
    shared mutable state between concurrent model-loading threads.
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    
    ---------
    
    Co-authored-by: Claude Opus 4.6 <[email protected]>
---
 .../maven/impl/model/DefaultModelBuilder.java      | 375 +++++++++++----------
 ...301StackOverflowInternalParentRevisionTest.java |  44 +++
 .../.mvn/.gitkeep                                  |   0
 .../parent/pom.xml                                 |  29 ++
 .../gh-12301-stackoverflow-internal-parent/pom.xml |  38 +++
 5 files changed, 308 insertions(+), 178 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 7a5b84b7ee..28a917c948 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
@@ -686,7 +686,7 @@ String replaceCiFriendlyVersion(Map<String, String> 
properties, String version)
          * are available for CI-friendly version processing and repository URL 
interpolation.
          * It also includes directory-related properties that may be needed 
during profile activation.
          */
-        private Map<String, String> getEnhancedProperties(Model model, Path 
rootDirectory) {
+        private Map<String, String> getEnhancedProperties(Model model, Path 
rootDirectory, Set<Path> activeModelReads) {
             Map<String, String> properties = new HashMap<>();
 
             // Add directory-specific properties first, as they may be needed 
for profile activation
@@ -712,10 +712,14 @@ private Map<String, String> getEnhancedProperties(Model 
model, Path rootDirector
                 if (rootModelPath != null) {
                     // Check if the root model path is within the root 
directory to prevent infinite loops
                     // This can happen when a .mvn directory exists in a 
subdirectory and parent inference
-                    // tries to read models above the discovered root directory
-                    if (isParentWithinRootDirectory(rootModelPath, 
rootDirectory)) {
+                    // tries to read models above the discovered root 
directory.
+                    // Also skip if the root model is already being read in an 
outer call frame
+                    // to prevent StackOverflowError when a project has an 
internal parent in a
+                    // subdirectory with CI-friendly ${revision} and a .mvn/ 
root marker (GH-12301).
+                    if (isParentWithinRootDirectory(rootModelPath, 
rootDirectory)
+                            && 
!activeModelReads.contains(rootModelPath.normalize())) {
                         Model rootModel =
-                                
derive(Sources.buildSource(rootModelPath)).readFileModel();
+                                
derive(Sources.buildSource(rootModelPath)).readFileModel(activeModelReads);
                         properties.putAll(getPropertiesWithProfiles(rootModel, 
properties));
                     }
                 }
@@ -1528,48 +1532,42 @@ private List<Profile> getActiveProfiles(
         }
 
         Model readFileModel() throws ModelBuilderException {
-            Model model = cache(request.getSource(), FILE, 
this::doReadFileModel);
+            return readFileModel(new HashSet<>());
+        }
+
+        Model readFileModel(Set<Path> activeModelReads) throws 
ModelBuilderException {
+            Model model = cache(request.getSource(), FILE, () -> 
doReadFileModel(activeModelReads));
             // set the file model in the result outside the cache
             result.setFileModel(model);
             return model;
         }
 
         @SuppressWarnings("checkstyle:methodlength")
-        Model doReadFileModel() throws ModelBuilderException {
+        Model doReadFileModel(Set<Path> activeModelReads) throws 
ModelBuilderException {
             ModelSource modelSource = request.getSource();
             Model model;
             Path rootDirectory;
             boolean rootDirectoryFromSession = false;
             setSource(modelSource.getLocation());
             logger.debug("Reading file model from " + 
modelSource.getLocation());
+            Path sourcePath = modelSource.getPath();
+            Path normalizedPath = sourcePath != null ? sourcePath.normalize() 
: null;
+            boolean trackRead = normalizedPath != null && 
activeModelReads.add(normalizedPath);
             try {
-                boolean strict = isBuildRequest();
                 try {
-                    rootDirectory = request.getSession().getRootDirectory();
-                    rootDirectoryFromSession = true;
-                } catch (IllegalStateException ignore) {
-                    rootDirectory = modelSource.getPath();
-                    while (rootDirectory != null && 
!Files.isDirectory(rootDirectory)) {
-                        rootDirectory = rootDirectory.getParent();
-                    }
-                }
-                try (InputStream is = modelSource.openStream()) {
-                    model = modelProcessor.read(XmlReaderRequest.builder()
-                            .strict(strict)
-                            .location(modelSource.getLocation())
-                            .modelId(modelSource.getModelId())
-                            .path(modelSource.getPath())
-                            .rootDirectory(rootDirectory)
-                            .inputStream(is)
-                            .transformer(new InterningTransformer(session))
-                            .build());
-                } catch (XmlReaderException e) {
-                    if (!strict) {
-                        throw e;
+                    boolean strict = isBuildRequest();
+                    try {
+                        rootDirectory = 
request.getSession().getRootDirectory();
+                        rootDirectoryFromSession = true;
+                    } catch (IllegalStateException ignore) {
+                        rootDirectory = modelSource.getPath();
+                        while (rootDirectory != null && 
!Files.isDirectory(rootDirectory)) {
+                            rootDirectory = rootDirectory.getParent();
+                        }
                     }
                     try (InputStream is = modelSource.openStream()) {
                         model = modelProcessor.read(XmlReaderRequest.builder()
-                                .strict(false)
+                                .strict(strict)
                                 .location(modelSource.getLocation())
                                 .modelId(modelSource.getModelId())
                                 .path(modelSource.getPath())
@@ -1577,180 +1575,201 @@ Model doReadFileModel() throws ModelBuilderException {
                                 .inputStream(is)
                                 .transformer(new InterningTransformer(session))
                                 .build());
-                    } catch (XmlReaderException ne) {
-                        // still unreadable even in non-strict mode, rethrow 
original error
-                        throw e;
-                    }
+                    } catch (XmlReaderException e) {
+                        if (!strict) {
+                            throw e;
+                        }
+                        try (InputStream is = modelSource.openStream()) {
+                            model = 
modelProcessor.read(XmlReaderRequest.builder()
+                                    .strict(false)
+                                    .location(modelSource.getLocation())
+                                    .modelId(modelSource.getModelId())
+                                    .path(modelSource.getPath())
+                                    .rootDirectory(rootDirectory)
+                                    .inputStream(is)
+                                    .transformer(new 
InterningTransformer(session))
+                                    .build());
+                        } catch (XmlReaderException ne) {
+                            // still unreadable even in non-strict mode, 
rethrow original error
+                            throw e;
+                        }
 
+                        add(
+                                Severity.ERROR,
+                                Version.V20,
+                                "Malformed POM " + modelSource.getLocation() + 
": " + e.getMessage(),
+                                e);
+                    }
+                } catch (XmlReaderException e) {
                     add(
-                            Severity.ERROR,
-                            Version.V20,
-                            "Malformed POM " + modelSource.getLocation() + ": 
" + e.getMessage(),
+                            Severity.FATAL,
+                            Version.BASE,
+                            "Non-parseable POM " + modelSource.getLocation() + 
": " + e.getMessage(),
                             e);
-                }
-            } catch (XmlReaderException e) {
-                add(
-                        Severity.FATAL,
-                        Version.BASE,
-                        "Non-parseable POM " + modelSource.getLocation() + ": 
" + e.getMessage(),
-                        e);
-                throw newModelBuilderException();
-            } catch (IOException e) {
-                String msg = e.getMessage();
-                if (msg == null || msg.isEmpty()) {
-                    // NOTE: There's java.nio.charset.MalformedInputException 
and sun.io.MalformedInputException
-                    if 
(e.getClass().getName().endsWith("MalformedInputException")) {
-                        msg = "Some input bytes do not match the file 
encoding.";
-                    } else {
-                        msg = e.getClass().getSimpleName();
+                    throw newModelBuilderException();
+                } catch (IOException e) {
+                    String msg = e.getMessage();
+                    if (msg == null || msg.isEmpty()) {
+                        // NOTE: There's 
java.nio.charset.MalformedInputException and sun.io.MalformedInputException
+                        if 
(e.getClass().getName().endsWith("MalformedInputException")) {
+                            msg = "Some input bytes do not match the file 
encoding.";
+                        } else {
+                            msg = e.getClass().getSimpleName();
+                        }
                     }
+                    add(Severity.FATAL, Version.BASE, "Non-readable POM " + 
modelSource.getLocation() + ": " + msg, e);
+                    throw newModelBuilderException();
                 }
-                add(Severity.FATAL, Version.BASE, "Non-readable POM " + 
modelSource.getLocation() + ": " + msg, e);
-                throw newModelBuilderException();
-            }
 
-            if (model.getModelVersion() == null) {
-                String namespace = model.getNamespaceUri();
-                if (namespace != null && 
namespace.startsWith(NAMESPACE_PREFIX)) {
-                    model = 
model.withModelVersion(namespace.substring(NAMESPACE_PREFIX.length()));
+                if (model.getModelVersion() == null) {
+                    String namespace = model.getNamespaceUri();
+                    if (namespace != null && 
namespace.startsWith(NAMESPACE_PREFIX)) {
+                        model = 
model.withModelVersion(namespace.substring(NAMESPACE_PREFIX.length()));
+                    }
                 }
-            }
 
-            if (isBuildRequest()) {
-                model = model.withPomFile(modelSource.getPath());
-
-                Parent parent = model.getParent();
-                if (parent != null) {
-                    String groupId = parent.getGroupId();
-                    String artifactId = parent.getArtifactId();
-                    String version = parent.getVersion();
-                    String path = parent.getRelativePath();
-                    boolean versionContainsExpression = version != null && 
version.contains("${");
-                    if ((groupId == null || artifactId == null || version == 
null || versionContainsExpression)
-                            && (path == null || !path.isEmpty())) {
-                        Path pomFile = model.getPomFile();
-                        Path relativePath = Paths.get(path != null ? path : 
"..");
-                        Path pomPath = 
pomFile.resolveSibling(relativePath).normalize();
-                        if (Files.isDirectory(pomPath)) {
-                            pomPath = 
modelProcessor.locateExistingPom(pomPath);
-                        }
-                        if (pomPath != null && Files.isRegularFile(pomPath)) {
-                            if (rootDirectoryFromSession && 
!isParentWithinRootDirectory(pomPath, rootDirectory)) {
-                                add(
-                                        Severity.FATAL,
-                                        Version.BASE,
-                                        "Parent POM " + pomPath + " is located 
above the root directory "
-                                                + rootDirectory
-                                                + ". This setup is invalid 
when a .mvn directory exists in a subdirectory.",
-                                        parent.getLocation("relativePath"));
-                                throw newModelBuilderException();
+                if (isBuildRequest()) {
+                    model = model.withPomFile(modelSource.getPath());
+
+                    Parent parent = model.getParent();
+                    if (parent != null) {
+                        String groupId = parent.getGroupId();
+                        String artifactId = parent.getArtifactId();
+                        String version = parent.getVersion();
+                        String path = parent.getRelativePath();
+                        boolean versionContainsExpression = version != null && 
version.contains("${");
+                        if ((groupId == null || artifactId == null || version 
== null || versionContainsExpression)
+                                && (path == null || !path.isEmpty())) {
+                            Path pomFile = model.getPomFile();
+                            Path relativePath = Paths.get(path != null ? path 
: "..");
+                            Path pomPath = 
pomFile.resolveSibling(relativePath).normalize();
+                            if (Files.isDirectory(pomPath)) {
+                                pomPath = 
modelProcessor.locateExistingPom(pomPath);
                             }
+                            if (pomPath != null && 
Files.isRegularFile(pomPath)) {
+                                if (rootDirectoryFromSession && 
!isParentWithinRootDirectory(pomPath, rootDirectory)) {
+                                    add(
+                                            Severity.FATAL,
+                                            Version.BASE,
+                                            "Parent POM " + pomPath + " is 
located above the root directory "
+                                                    + rootDirectory
+                                                    + ". This setup is invalid 
when a .mvn directory exists in a subdirectory.",
+                                            
parent.getLocation("relativePath"));
+                                    throw newModelBuilderException();
+                                }
 
-                            Model parentModel =
-                                    
derive(Sources.buildSource(pomPath)).readFileModel();
-                            String parentGroupId = getGroupId(parentModel);
-                            String parentArtifactId = 
parentModel.getArtifactId();
-                            String parentVersion = getVersion(parentModel);
-                            if ((groupId == null || 
groupId.equals(parentGroupId))
-                                    && (artifactId == null || 
artifactId.equals(parentArtifactId))
-                                    && (version == null
-                                            || version.equals(parentVersion)
-                                            || versionContainsExpression)) {
-                                model = model.withParent(parent.with()
-                                        .groupId(parentGroupId)
-                                        .artifactId(parentArtifactId)
-                                        .version(parentVersion)
-                                        .build());
+                                Model parentModel =
+                                        
derive(Sources.buildSource(pomPath)).readFileModel(activeModelReads);
+                                String parentGroupId = getGroupId(parentModel);
+                                String parentArtifactId = 
parentModel.getArtifactId();
+                                String parentVersion = getVersion(parentModel);
+                                if ((groupId == null || 
groupId.equals(parentGroupId))
+                                        && (artifactId == null || 
artifactId.equals(parentArtifactId))
+                                        && (version == null
+                                                || 
version.equals(parentVersion)
+                                                || versionContainsExpression)) 
{
+                                    model = model.withParent(parent.with()
+                                            .groupId(parentGroupId)
+                                            .artifactId(parentArtifactId)
+                                            .version(parentVersion)
+                                            .build());
+                                } else {
+                                    mismatchRelativePathAndGA(model, parent, 
parentGroupId, parentArtifactId);
+                                }
                             } else {
-                                mismatchRelativePathAndGA(model, parent, 
parentGroupId, parentArtifactId);
-                            }
-                        } else {
-                            if 
(!MODEL_VERSION_4_0_0.equals(model.getModelVersion()) && path != null) {
-                                wrongParentRelativePath(model);
+                                if 
(!MODEL_VERSION_4_0_0.equals(model.getModelVersion()) && path != null) {
+                                    wrongParentRelativePath(model);
+                                }
                             }
                         }
                     }
-                }
 
-                // subprojects discovery
-                if (!hasSubprojectsDefined(model)
-                        // only discover subprojects if POM > 4.0.0
-                        && !MODEL_VERSION_4_0_0.equals(model.getModelVersion())
-                        // and if packaging is POM (we check type, but the 
session is not yet available,
-                        // we would require the project realm if we want to 
support extensions
-                        && Type.POM.equals(model.getPackaging())) {
-                    List<String> subprojects = new ArrayList<>();
-                    try (Stream<Path> files = 
Files.list(model.getProjectDirectory())) {
-                        for (Path f : files.toList()) {
-                            if (Files.isDirectory(f)) {
-                                Path subproject = 
modelProcessor.locateExistingPom(f);
-                                if (subproject != null) {
-                                    
subprojects.add(f.getFileName().toString());
+                    // subprojects discovery
+                    if (!hasSubprojectsDefined(model)
+                            // only discover subprojects if POM > 4.0.0
+                            && 
!MODEL_VERSION_4_0_0.equals(model.getModelVersion())
+                            // and if packaging is POM (we check type, but the 
session is not yet available,
+                            // we would require the project realm if we want 
to support extensions
+                            && Type.POM.equals(model.getPackaging())) {
+                        List<String> subprojects = new ArrayList<>();
+                        try (Stream<Path> files = 
Files.list(model.getProjectDirectory())) {
+                            for (Path f : files.toList()) {
+                                if (Files.isDirectory(f)) {
+                                    Path subproject = 
modelProcessor.locateExistingPom(f);
+                                    if (subproject != null) {
+                                        
subprojects.add(f.getFileName().toString());
+                                    }
                                 }
                             }
+                            if (!subprojects.isEmpty()) {
+                                model = model.withSubprojects(subprojects);
+                            }
+                        } catch (IOException e) {
+                            add(Severity.FATAL, Version.V41, "Error 
discovering subprojects", e);
                         }
-                        if (!subprojects.isEmpty()) {
-                            model = model.withSubprojects(subprojects);
-                        }
-                    } catch (IOException e) {
-                        add(Severity.FATAL, Version.V41, "Error discovering 
subprojects", e);
                     }
-                }
 
-                // Enhanced property resolution with profile activation for 
CI-friendly versions and repository URLs
-                // This includes directory properties, profile properties, and 
user properties
-                Map<String, String> properties = getEnhancedProperties(model, 
rootDirectory);
-
-                // CI friendly version processing with profile-aware properties
-                model = model.with()
-                        .version(replaceCiFriendlyVersion(properties, 
model.getVersion()))
-                        .parent(
-                                model.getParent() != null
-                                        ? model.getParent()
-                                                
.withVersion(replaceCiFriendlyVersion(
-                                                        properties,
-                                                        
model.getParent().getVersion()))
-                                        : null)
-                        .build();
+                    // Enhanced property resolution with profile activation 
for CI-friendly versions and repository URLs
+                    // This includes directory properties, profile properties, 
and user properties
+                    Map<String, String> properties = 
getEnhancedProperties(model, rootDirectory, activeModelReads);
+
+                    // CI friendly version processing with profile-aware 
properties
+                    model = model.with()
+                            .version(replaceCiFriendlyVersion(properties, 
model.getVersion()))
+                            .parent(
+                                    model.getParent() != null
+                                            ? model.getParent()
+                                                    
.withVersion(replaceCiFriendlyVersion(
+                                                            properties,
+                                                            
model.getParent().getVersion()))
+                                            : null)
+                            .build();
 
-                // Repository URL interpolation with the same profile-aware 
properties
-                UnaryOperator<String> callback = properties::get;
-                model = model.with()
-                        
.repositories(interpolateRepository(model.getRepositories(), callback))
-                        
.pluginRepositories(interpolateRepository(model.getPluginRepositories(), 
callback))
-                        .profiles(map(model.getProfiles(), 
this::interpolateRepository, callback))
-                        
.distributionManagement(interpolateRepository(model.getDistributionManagement(),
 callback))
-                        .build();
-                // Override model properties with user properties (use request 
properties
-                // to ensure consistency with model interpolation)
-                Map<String, String> userProps = request.getUserProperties();
-                Map<String, String> newProps = merge(model.getProperties(), 
userProps);
-                if (newProps != null) {
-                    model = model.withProperties(newProps);
+                    // Repository URL interpolation with the same 
profile-aware properties
+                    UnaryOperator<String> callback = properties::get;
+                    model = model.with()
+                            
.repositories(interpolateRepository(model.getRepositories(), callback))
+                            
.pluginRepositories(interpolateRepository(model.getPluginRepositories(), 
callback))
+                            .profiles(map(model.getProfiles(), 
this::interpolateRepository, callback))
+                            
.distributionManagement(interpolateRepository(model.getDistributionManagement(),
 callback))
+                            .build();
+                    // Override model properties with user properties (use 
request properties
+                    // to ensure consistency with model interpolation)
+                    Map<String, String> userProps = 
request.getUserProperties();
+                    Map<String, String> newProps = 
merge(model.getProperties(), userProps);
+                    if (newProps != null) {
+                        model = model.withProperties(newProps);
+                    }
+                    model = model.withProfiles(merge(model.getProfiles(), 
userProps));
                 }
-                model = model.withProfiles(merge(model.getProfiles(), 
userProps));
-            }
 
-            for (var transformer : transformers) {
-                model = transformer.transformFileModel(model);
-            }
+                for (var transformer : transformers) {
+                    model = transformer.transformFileModel(model);
+                }
 
-            setSource(model);
-            modelValidator.validateFileModel(
-                    session,
-                    model,
-                    isBuildRequest() ? ModelValidator.VALIDATION_LEVEL_STRICT 
: ModelValidator.VALIDATION_LEVEL_MINIMAL,
-                    this);
-            InternalSession internalSession = InternalSession.from(session);
-            if 
(Features.mavenMaven3Personality(internalSession.getSession().getConfigProperties())
-                    && Objects.equals(ModelBuilder.MODEL_VERSION_4_1_0, 
model.getModelVersion())) {
-                add(Severity.FATAL, Version.BASE, "Maven3 mode: no higher 
model version than 4.0.0 allowed");
-            }
-            if (hasFatalErrors()) {
-                throw newModelBuilderException();
-            }
+                setSource(model);
+                modelValidator.validateFileModel(
+                        session,
+                        model,
+                        isBuildRequest()
+                                ? ModelValidator.VALIDATION_LEVEL_STRICT
+                                : ModelValidator.VALIDATION_LEVEL_MINIMAL,
+                        this);
+                InternalSession internalSession = 
InternalSession.from(session);
+                if 
(Features.mavenMaven3Personality(internalSession.getSession().getConfigProperties())
+                        && Objects.equals(ModelBuilder.MODEL_VERSION_4_1_0, 
model.getModelVersion())) {
+                    add(Severity.FATAL, Version.BASE, "Maven3 mode: no higher 
model version than 4.0.0 allowed");
+                }
+                if (hasFatalErrors()) {
+                    throw newModelBuilderException();
+                }
 
-            return model;
+                return model;
+            } finally {
+                if (trackRead) {
+                    activeModelReads.remove(normalizedPath);
+                }
+            }
         }
 
         private DistributionManagement interpolateRepository(
diff --git 
a/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh12301StackOverflowInternalParentRevisionTest.java
 
b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh12301StackOverflowInternalParentRevisionTest.java
new file mode 100644
index 0000000000..dd24924ad1
--- /dev/null
+++ 
b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh12301StackOverflowInternalParentRevisionTest.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.it;
+
+import java.io.File;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * This is a test set for <a 
href="https://github.com/apache/maven/issues/12301";>Issue #12301</a>.
+ * Verifies that a project with an internal parent in a subdirectory using 
CI-friendly
+ * {@code ${revision}} and a {@code .mvn/} root marker does not cause a 
StackOverflowError.
+ */
+public class MavenITgh12301StackOverflowInternalParentRevisionTest extends 
AbstractMavenIntegrationTestCase {
+
+    @Test
+    public void testNoStackOverflowWithInternalParentAndRevision() throws 
Exception {
+        File testDir = 
extractResources("/gh-12301-stackoverflow-internal-parent");
+
+        Verifier verifier = newVerifier(testDir.getAbsolutePath());
+        verifier.setAutoclean(false);
+        verifier.deleteArtifacts("org.apache.maven.its.gh12301");
+        verifier.addCliArgument("-Drevision=1.0-SNAPSHOT");
+        verifier.addCliArgument("validate");
+        verifier.execute();
+        verifier.verifyErrorFreeLog();
+    }
+}
diff --git 
a/its/core-it-suite/src/test/resources/gh-12301-stackoverflow-internal-parent/.mvn/.gitkeep
 
b/its/core-it-suite/src/test/resources/gh-12301-stackoverflow-internal-parent/.mvn/.gitkeep
new file mode 100644
index 0000000000..e69de29bb2
diff --git 
a/its/core-it-suite/src/test/resources/gh-12301-stackoverflow-internal-parent/parent/pom.xml
 
b/its/core-it-suite/src/test/resources/gh-12301-stackoverflow-internal-parent/parent/pom.xml
new file mode 100644
index 0000000000..940ecc2231
--- /dev/null
+++ 
b/its/core-it-suite/src/test/resources/gh-12301-stackoverflow-internal-parent/parent/pom.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/maven-v4_0_0.xsd";>
+  <modelVersion>4.1.0</modelVersion>
+
+  <groupId>org.apache.maven.its.gh12301</groupId>
+  <artifactId>parent</artifactId>
+  <version>1.0-SNAPSHOT</version>
+  <packaging>pom</packaging>
+
+  <name>Maven Integration Test :: GH-12301 :: Parent</name>
+</project>
diff --git 
a/its/core-it-suite/src/test/resources/gh-12301-stackoverflow-internal-parent/pom.xml
 
b/its/core-it-suite/src/test/resources/gh-12301-stackoverflow-internal-parent/pom.xml
new file mode 100644
index 0000000000..e3a1d65be1
--- /dev/null
+++ 
b/its/core-it-suite/src/test/resources/gh-12301-stackoverflow-internal-parent/pom.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/maven-v4_0_0.xsd";>
+  <modelVersion>4.1.0</modelVersion>
+
+  <parent>
+    <groupId>org.apache.maven.its.gh12301</groupId>
+    <artifactId>parent</artifactId>
+    <version>${revision}</version>
+    <relativePath>parent</relativePath>
+  </parent>
+
+  <artifactId>root</artifactId>
+  <packaging>pom</packaging>
+
+  <name>Maven Integration Test :: GH-12301 :: Root</name>
+
+  <properties>
+    <revision>1.0-SNAPSHOT</revision>
+  </properties>
+</project>


Reply via email to