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 ec69fbf852 Avoid reflective InputSource modelId mutation (#12147)
ec69fbf852 is described below

commit ec69fbf852d39834349c0fc9836193802b8c2451
Author: Silva Dev BR <[email protected]>
AuthorDate: Sat Jun 13 19:09:40 2026 -0300

    Avoid reflective InputSource modelId mutation (#12147)
    
    * Avoid reflective modelId mutation in InputSource
    
    * Track POM depth during modelId extraction
    
    * Cover parent modelId dependency nesting
    
    * Fix extractModelId thread safety, early exit, and static methods
    
    - Remove InputFactoryHolder singleton: create XMLInputFactory per call
      (consistent with DefaultXmlService and PomXmlRootDetector). The cached
      factory set IS_COALESCING and IS_REPLACING_ENTITY_REFERENCES which are
      unnecessary for GAV extraction, and sharing the factory is not
      guaranteed thread-safe by the StAX spec.
    - Fix early exit: only break when all three direct coordinates are found.
      The previous condition broke early on parent fallbacks, missing direct
      groupId/version that appear after artifactId in non-standard ordering.
    - Make extractModelId(InputStream) and extractModelId(Reader) static.
    - Add tests for direct coordinates overriding parent and null extraction.
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    
    * Rename reader to xmlReader in extractModelId(InputStream)
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    
    * Fix InputSource location to use path URI, not request location
    
    MavenStaxReader passes InputSource.getLocation() as the StAX systemId
    to StreamSource. The request location can be a GAV coordinate (e.g.
    "org.test:parent-pom:1.0") for remote parents, which is not a valid
    URL and causes MalformedURLException during entity resolution. Restore
    the original behavior: use path.toUri().toString() or null.
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    
    ---------
    
    Co-authored-by: Guillaume Nodet <[email protected]>
    Co-authored-by: Claude Opus 4.6 <[email protected]>
---
 .../apache/maven/impl/DefaultModelXmlFactory.java  | 153 ++++++++++++++++++++-
 .../maven/impl/model/DefaultModelBuilder.java      |  15 --
 .../maven/impl/DefaultModelXmlFactoryTest.java     | 127 +++++++++++++++++
 3 files changed, 277 insertions(+), 18 deletions(-)

diff --git 
a/impl/maven-impl/src/main/java/org/apache/maven/impl/DefaultModelXmlFactory.java
 
b/impl/maven-impl/src/main/java/org/apache/maven/impl/DefaultModelXmlFactory.java
index e7a699e3b0..e18e723d8d 100644
--- 
a/impl/maven-impl/src/main/java/org/apache/maven/impl/DefaultModelXmlFactory.java
+++ 
b/impl/maven-impl/src/main/java/org/apache/maven/impl/DefaultModelXmlFactory.java
@@ -18,6 +18,15 @@
  */
 package org.apache.maven.impl;
 
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLStreamConstants;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamReader;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.CharArrayReader;
+import java.io.CharArrayWriter;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.io.Reader;
@@ -88,10 +97,31 @@ private Model doRead(XmlReaderRequest request) throws 
XmlReaderException {
             throw new IllegalArgumentException("path, url, reader or 
inputStream must be non null");
         }
         try {
+            String modelId = request.getModelId();
+
+            if (modelId == null) {
+                if (inputStream != null) {
+                    ByteArrayOutputStream baos = new ByteArrayOutputStream();
+                    inputStream.transferTo(baos);
+                    byte[] buf = baos.toByteArray();
+                    modelId = extractModelId(new ByteArrayInputStream(buf));
+                    inputStream = new ByteArrayInputStream(buf);
+                } else if (reader != null) {
+                    CharArrayWriter caw = new CharArrayWriter();
+                    reader.transferTo(caw);
+                    char[] buf = caw.toCharArray();
+                    modelId = extractModelId(new CharArrayReader(buf));
+                    reader = new CharArrayReader(buf);
+                } else if (path != null) {
+                    try (InputStream is = Files.newInputStream(path)) {
+                        modelId = extractModelId(is);
+                    }
+                }
+            }
+
             InputSource source = null;
-            if (request.getModelId() != null || request.getLocation() != null) 
{
-                source = new InputSource(
-                        request.getModelId(), path != null ? 
path.toUri().toString() : null);
+            if (modelId != null || request.getLocation() != null) {
+                source = new InputSource(modelId, path != null ? 
path.toUri().toString() : null);
             }
             MavenStaxReader xml = request.getTransformer() != null
                     ? new MavenStaxReader(request.getTransformer()::transform)
@@ -115,6 +145,123 @@ private Model doRead(XmlReaderRequest request) throws 
XmlReaderException {
         }
     }
 
+    private static String extractModelId(InputStream inputStream) {
+        try {
+            XMLStreamReader xmlReader = 
XMLInputFactory.newFactory().createXMLStreamReader(inputStream);
+            try {
+                return extractModelId(xmlReader);
+            } finally {
+                xmlReader.close();
+            }
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    private static String extractModelId(Reader reader) {
+        try {
+            XMLStreamReader xmlReader = 
XMLInputFactory.newFactory().createXMLStreamReader(reader);
+            try {
+                return extractModelId(xmlReader);
+            } finally {
+                xmlReader.close();
+            }
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    private static String extractModelId(XMLStreamReader reader) throws 
XMLStreamException {
+        String groupId = null;
+        String artifactId = null;
+        String version = null;
+        String parentGroupId = null;
+        String parentVersion = null;
+
+        int depth = 0;
+        boolean inProject = false;
+        boolean inParent = false;
+        String currentElement = null;
+
+        while (reader.hasNext()) {
+            int event = reader.next();
+
+            if (event == XMLStreamConstants.START_ELEMENT) {
+                depth++;
+                String localName = reader.getLocalName();
+
+                if (depth == 1 && "project".equals(localName)) {
+                    inProject = true;
+                } else if (inProject && depth == 2 && 
"parent".equals(localName)) {
+                    inParent = true;
+                } else if (inProject
+                        && (depth == 2 || (depth == 3 && inParent))
+                        && ("groupId".equals(localName)
+                                || "artifactId".equals(localName)
+                                || "version".equals(localName))) {
+                    currentElement = localName;
+                }
+            } else if (event == XMLStreamConstants.END_ELEMENT) {
+                String localName = reader.getLocalName();
+
+                if ("parent".equals(localName)) {
+                    inParent = false;
+                } else if ("project".equals(localName)) {
+                    break;
+                }
+                currentElement = null;
+                depth--;
+            } else if (event == XMLStreamConstants.CHARACTERS && 
currentElement != null) {
+                String text = reader.getText().trim();
+                if (!text.isEmpty()) {
+                    if (inParent) {
+                        switch (currentElement) {
+                            case "groupId":
+                                parentGroupId = text;
+                                break;
+                            case "version":
+                                parentVersion = text;
+                                break;
+                            default:
+                                break;
+                        }
+                    } else {
+                        switch (currentElement) {
+                            case "groupId":
+                                groupId = text;
+                                break;
+                            case "artifactId":
+                                artifactId = text;
+                                break;
+                            case "version":
+                                version = text;
+                                break;
+                            default:
+                                break;
+                        }
+                    }
+                }
+            }
+
+            if (groupId != null && artifactId != null && version != null) {
+                break;
+            }
+        }
+
+        if (groupId == null) {
+            groupId = parentGroupId;
+        }
+        if (version == null) {
+            version = parentVersion;
+        }
+
+        if (groupId != null && artifactId != null && version != null) {
+            return groupId + ":" + artifactId + ":" + version;
+        }
+
+        return null;
+    }
+
     @Override
     public void write(XmlWriterRequest<Model> request) throws 
XmlWriterException {
         requireNonNull(request, "request");
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 b7e2b51e44..ebbb61a227 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
@@ -21,7 +21,6 @@
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
-import java.lang.reflect.Field;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
@@ -68,7 +67,6 @@
 import org.apache.maven.api.model.DistributionManagement;
 import org.apache.maven.api.model.Exclusion;
 import org.apache.maven.api.model.InputLocation;
-import org.apache.maven.api.model.InputSource;
 import org.apache.maven.api.model.Model;
 import org.apache.maven.api.model.Parent;
 import org.apache.maven.api.model.Profile;
@@ -1536,19 +1534,6 @@ Model doReadFileModel() throws ModelBuilderException {
                             "Malformed POM " + modelSource.getLocation() + ": 
" + e.getMessage(),
                             e);
                 }
-
-                InputLocation loc = model.getLocation("");
-                InputSource v4src = loc != null ? loc.getSource() : null;
-                if (v4src != null) {
-                    try {
-                        Field field = 
InputSource.class.getDeclaredField("modelId");
-                        field.setAccessible(true);
-                        field.set(v4src, ModelProblemUtils.toId(model));
-                    } catch (Throwable t) {
-                        // TODO: use a lazy source ?
-                        throw new IllegalStateException("Unable to set modelId 
on InputSource", t);
-                    }
-                }
             } catch (XmlReaderException e) {
                 add(
                         Severity.FATAL,
diff --git 
a/impl/maven-impl/src/test/java/org/apache/maven/impl/DefaultModelXmlFactoryTest.java
 
b/impl/maven-impl/src/test/java/org/apache/maven/impl/DefaultModelXmlFactoryTest.java
index 8a8b4b21b0..dbdbfd454f 100644
--- 
a/impl/maven-impl/src/test/java/org/apache/maven/impl/DefaultModelXmlFactoryTest.java
+++ 
b/impl/maven-impl/src/test/java/org/apache/maven/impl/DefaultModelXmlFactoryTest.java
@@ -18,10 +18,13 @@
  */
 package org.apache.maven.impl;
 
+import java.io.ByteArrayInputStream;
 import java.io.StringReader;
 import java.io.StringWriter;
+import java.nio.charset.StandardCharsets;
 import java.util.function.Function;
 
+import org.apache.maven.api.model.InputSource;
 import org.apache.maven.api.model.Model;
 import org.apache.maven.api.services.xml.XmlReaderException;
 import org.apache.maven.api.services.xml.XmlReaderRequest;
@@ -31,6 +34,8 @@
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
@@ -122,6 +127,128 @@ void testMalformedModelVersion() throws Exception {
         assertEquals("invalid.version", model.getModelVersion());
     }
 
+    @Test
+    void testExtractsModelIdIntoInputSource() throws Exception {
+        String xml = """
+                <project xmlns="http://maven.apache.org/POM/4.0.0";>
+                  <modelVersion>4.0.0</modelVersion>
+                  <groupId>org.example</groupId>
+                  <artifactId>child</artifactId>
+                  <version>1</version>
+                </project>""";
+
+        Model model = factory.read(XmlReaderRequest.builder()
+                .reader(new StringReader(xml))
+                .location("pom.xml")
+                .strict(true)
+                .build());
+
+        InputSource source = model.getLocation("").getSource();
+        assertNotNull(source);
+        assertEquals("org.example:child:1", source.getModelId());
+    }
+
+    @Test
+    void testExtractsModelIdUsingParentFallback() throws Exception {
+        String xml = """
+                <project xmlns="http://maven.apache.org/POM/4.0.0";>
+                  <modelVersion>4.0.0</modelVersion>
+                  <parent>
+                    <groupId>org.example</groupId>
+                    <artifactId>parent</artifactId>
+                    <version>1</version>
+                  </parent>
+                  <artifactId>child</artifactId>
+                </project>""";
+
+        Model model = factory.read(XmlReaderRequest.builder()
+                .reader(new StringReader(xml))
+                .location("pom.xml")
+                .strict(true)
+                .build());
+
+        InputSource source = model.getLocation("").getSource();
+        assertNotNull(source);
+        assertEquals("org.example:child:1", source.getModelId());
+    }
+
+    @Test
+    void testExtractsParentModelIdWithoutUsingDependencyCoordinates() throws 
Exception {
+        String xml = """
+                <project xmlns="http://maven.apache.org/POM/4.0.0";>
+                  <modelVersion>4.0.0</modelVersion>
+                  <parent>
+                    <groupId>org.example</groupId>
+                    <artifactId>parent</artifactId>
+                    <version>1</version>
+                  </parent>
+                  <artifactId>child</artifactId>
+                  <dependencies>
+                    <dependency>
+                      <groupId>dep.group</groupId>
+                      <artifactId>dep-art</artifactId>
+                      <version>2.0</version>
+                    </dependency>
+                  </dependencies>
+                </project>""";
+
+        Model model = factory.read(XmlReaderRequest.builder()
+                .inputStream(new 
ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8)))
+                .location("pom.xml")
+                .strict(true)
+                .build());
+
+        InputSource source = model.getLocation("").getSource();
+        assertNotNull(source);
+        assertEquals("org.example:child:1", source.getModelId());
+    }
+
+    @Test
+    void testExtractsDirectCoordinatesOverParent() throws Exception {
+        String xml = """
+                <project xmlns="http://maven.apache.org/POM/4.0.0";>
+                  <modelVersion>4.0.0</modelVersion>
+                  <parent>
+                    <groupId>org.parent</groupId>
+                    <artifactId>parent</artifactId>
+                    <version>1.0</version>
+                  </parent>
+                  <artifactId>child</artifactId>
+                  <groupId>org.child</groupId>
+                  <version>2.0</version>
+                </project>""";
+
+        Model model = factory.read(XmlReaderRequest.builder()
+                .reader(new StringReader(xml))
+                .location("pom.xml")
+                .strict(true)
+                .build());
+
+        InputSource source = model.getLocation("").getSource();
+        assertNotNull(source);
+        assertEquals("org.child:child:2.0", source.getModelId());
+    }
+
+    @Test
+    void testMissingArtifactIdProducesNullModelId() throws Exception {
+        String xml = """
+                <project xmlns="http://maven.apache.org/POM/4.0.0";>
+                  <modelVersion>4.0.0</modelVersion>
+                  <groupId>org.example</groupId>
+                  <version>1</version>
+                </project>""";
+
+        Model model = factory.read(XmlReaderRequest.builder()
+                .reader(new StringReader(xml))
+                .location("pom.xml")
+                .strict(true)
+                .build());
+
+        InputSource source = model.getLocation("").getSource();
+        assertNotNull(source);
+        assertNull(source.getModelId());
+    }
+
     @Test
     void testWriteWithoutFormatterDisablesLocationTracking() throws Exception {
         // minimal valid model we can round-trip

Reply via email to