You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by gn...@apache.org on 2026/08/24 05:15:01 UTC

(maven) branch backport/12735-to-maven-4.0.x created (now 2f6154e0dd)

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

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


      at 2f6154e0dd Make profile activation conditions locale-independent (#12735)

This branch includes the following new commits:

     new cc4ba8f529 Fix #12599: BuildPlanExecutor thread pool leak on constructor failure
     new 2f6154e0dd Make profile activation conditions locale-independent (#12735)

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



(maven) 01/02: Fix #12599: BuildPlanExecutor thread pool leak on constructor failure

Posted by gn...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit cc4ba8f5293a7142ba52c49f49380f765f756467
Author: ulofiai <ul...@protonmail.com>
AuthorDate: Mon Aug 24 13:02:56 2026 +0800

    Fix #12599: BuildPlanExecutor thread pool leak on constructor failure
    
    Build the initial plan before creating the PhasingExecutor. If plan
    construction throws, BuildContext now fails before allocating the
    executor, so cleanup cannot be skipped. The change is limited to the
    initialization order in BuildPlanExecutor.
    
    Closes #12748
---
 .../apache/maven/lifecycle/internal/concurrent/BuildPlanExecutor.java | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/impl/maven-core/src/main/java/org/apache/maven/lifecycle/internal/concurrent/BuildPlanExecutor.java b/impl/maven-core/src/main/java/org/apache/maven/lifecycle/internal/concurrent/BuildPlanExecutor.java
index 62136c0cc1..55c10f8fa4 100644
--- a/impl/maven-core/src/main/java/org/apache/maven/lifecycle/internal/concurrent/BuildPlanExecutor.java
+++ b/impl/maven-core/src/main/java/org/apache/maven/lifecycle/internal/concurrent/BuildPlanExecutor.java
@@ -211,10 +211,10 @@ class BuildContext implements AutoCloseable {
                     session.getProjects().size());
             // Propagate the parallel flag to the root session
             session.setParallel(threads > 1);
-            this.executor = new PhasingExecutor(Executors.newFixedThreadPool(threads, new BuildThreadFactory()));
 
-            // build initial plan
+            // Build the initial plan before creating the executor so constructor failures cannot leak it.
             this.plan = buildInitialPlan(taskSegments);
+            this.executor = new PhasingExecutor(Executors.newFixedThreadPool(threads, new BuildThreadFactory()));
         }
 
         BuildContext() {


(maven) 02/02: Make profile activation conditions locale-independent (#12735)

Posted by gn...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 2f6154e0dde3507f92f474766d7ac523d7802399
Author: Sepuri Sai Krishna <sa...@gmail.com>
AuthorDate: Mon Aug 24 10:32:55 2026 +0530

    Make profile activation conditions locale-independent (#12735)
    
    Use Locale.ROOT in ConditionFunctions.upper(), ConditionFunctions.lower(),
    and ConditionParser.toString() so that profile activation does not depend
    on the default locale of the machine running the build.
    
    Turkish locale maps i to dotted İ, causing upper('windows') to return
    WİNDOWS and profile conditions to silently fail. Under a locale whose
    default numbering system is not latin (e.g. hi-IN-u-nu-deva),
    toString(42.0) returns ४२ rather than 42.
    
    Adds regression tests under Turkish and Devanagari locales that fail
    without the production change.
    
    Closes #12735
---
 .../impl/model/profile/ConditionFunctions.java     | 11 +++++-
 .../maven/impl/model/profile/ConditionParser.java  |  8 +++-
 .../impl/model/profile/ConditionParserTest.java    | 44 ++++++++++++++++++++++
 3 files changed, 60 insertions(+), 3 deletions(-)

diff --git a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/profile/ConditionFunctions.java b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/profile/ConditionFunctions.java
index 1a90872739..baa6482379 100644
--- a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/profile/ConditionFunctions.java
+++ b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/profile/ConditionFunctions.java
@@ -19,6 +19,7 @@
 package org.apache.maven.impl.model.profile;
 
 import java.util.List;
+import java.util.Locale;
 
 import org.apache.maven.api.services.InterpolatorException;
 import org.apache.maven.api.services.ModelBuilderException;
@@ -65,6 +66,9 @@ public Object length(List<Object> args) {
 
     /**
      * Converts the given string to uppercase.
+     * <p>
+     * The conversion uses {@link Locale#ROOT} so that profile activation does not depend on the
+     * default locale of the machine running the build.
      *
      * @param args A list containing a single string argument
      * @return The uppercase version of the input string
@@ -75,11 +79,14 @@ public Object upper(List<Object> args) {
             throw new IllegalArgumentException("upper function requires exactly one argument");
         }
         String s = ConditionParser.toString(args.get(0));
-        return s.toUpperCase();
+        return s.toUpperCase(Locale.ROOT);
     }
 
     /**
      * Converts the given string to lowercase.
+     * <p>
+     * The conversion uses {@link Locale#ROOT} so that profile activation does not depend on the
+     * default locale of the machine running the build.
      *
      * @param args A list containing a single string argument
      * @return The lowercase version of the input string
@@ -90,7 +97,7 @@ public Object lower(List<Object> args) {
             throw new IllegalArgumentException("lower function requires exactly one argument");
         }
         String s = ConditionParser.toString(args.get(0));
-        return s.toLowerCase();
+        return s.toLowerCase(Locale.ROOT);
     }
 
     /**
diff --git a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/profile/ConditionParser.java b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/profile/ConditionParser.java
index 4b73b5217e..8faf5b5be0 100644
--- a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/profile/ConditionParser.java
+++ b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/profile/ConditionParser.java
@@ -20,6 +20,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.function.UnaryOperator;
 
@@ -554,6 +555,11 @@ private static Object compare(Object left, String operator, Object right) {
      * Converts an object to a string representation.
      * If the object is a {@code Double}, it formats it without any decimal places.
      * Otherwise, it uses the {@code String.valueOf} method.
+     * <p>
+     * Formatting uses {@link Locale#ROOT} so that profile activation does not depend on the
+     * default locale of the machine running the build. Without it, a locale whose default
+     * numbering system is not latin (for example {@code hi-IN-u-nu-deva}) would render
+     * {@code 42} as {@code ४२}.
      *
      * @param value the object to convert to a string
      * @return the string representation of the object
@@ -562,7 +568,7 @@ public static String toString(Object value) {
         if (value instanceof Double || value instanceof Float) {
             double doubleValue = ((Number) value).doubleValue();
             if (doubleValue == Math.floor(doubleValue) && !Double.isInfinite(doubleValue)) {
-                return String.format("%.0f", doubleValue);
+                return String.format(Locale.ROOT, "%.0f", doubleValue);
             }
         }
         return String.valueOf(value);
diff --git a/impl/maven-impl/src/test/java/org/apache/maven/impl/model/profile/ConditionParserTest.java b/impl/maven-impl/src/test/java/org/apache/maven/impl/model/profile/ConditionParserTest.java
index be1da09919..474c586d9e 100644
--- a/impl/maven-impl/src/test/java/org/apache/maven/impl/model/profile/ConditionParserTest.java
+++ b/impl/maven-impl/src/test/java/org/apache/maven/impl/model/profile/ConditionParserTest.java
@@ -18,6 +18,7 @@
  */
 package org.apache.maven.impl.model.profile;
 
+import java.util.Locale;
 import java.util.Map;
 import java.util.function.UnaryOperator;
 
@@ -94,6 +95,49 @@ void testCaseConversionFunctions() {
         assertEquals("world", parser.parse("lower('WORLD')"));
     }
 
+    /**
+     * The result of {@code upper()} and {@code lower()} must not depend on the default locale of
+     * the machine running the build. In the Turkish locale the default case mapping turns
+     * {@code i} into the dotted {@code İ} and {@code I} into the dotless {@code ı}, which would
+     * silently change whether a profile is activated.
+     */
+    @Test
+    void testCaseConversionFunctionsAreLocaleIndependent() {
+        Locale orig = Locale.getDefault();
+        try {
+            Locale[] locales = {Locale.ENGLISH, new Locale("tr")};
+            for (Locale locale : locales) {
+                Locale.setDefault(locale);
+                assertEquals("WINDOWS", parser.parse("upper('windows')"), "upper() in " + locale);
+                assertEquals("LINUX", parser.parse("upper('linux')"), "upper() in " + locale);
+                assertEquals("linux", parser.parse("lower('LINUX')"), "lower() in " + locale);
+                assertEquals("ci", parser.parse("lower('CI')"), "lower() in " + locale);
+            }
+        } finally {
+            Locale.setDefault(orig);
+        }
+    }
+
+    /**
+     * Numbers must render with latin digits regardless of the default locale, otherwise a
+     * condition comparing against a string literal would evaluate differently on a machine
+     * whose locale uses a non-latin numbering system.
+     */
+    @Test
+    void testNumberFormattingIsLocaleIndependent() {
+        Locale orig = Locale.getDefault();
+        try {
+            // this locale's default numbering system is Devanagari rather than latin
+            Locale.setDefault(Locale.forLanguageTag("hi-IN-u-nu-deva"));
+            assertEquals("42", ConditionParser.toString(42.0));
+            assertEquals("42", ConditionParser.toString(42.0f));
+            assertEquals("The answer is 42", parser.parse("'The answer is ' + 42.0"));
+            assertEquals(2, parser.parse("length(42.0)"));
+        } finally {
+            Locale.setDefault(orig);
+        }
+    }
+
     @Test
     void testConcatFunction() {
         assertEquals("HelloWorld", parser.parse("'Hello' + 'World'"));