diff --git a/apache-maven/src/assembly/maven/bin/mvn b/apache-maven/src/assembly/maven/bin/mvn index 1a8e6a2fdccc..914f61a42b78 100755 --- a/apache-maven/src/assembly/maven/bin/mvn +++ b/apache-maven/src/assembly/maven/bin/mvn @@ -275,7 +275,7 @@ handle_args() { handle_args "$@" MAVEN_MAIN_CLASS=${MAVEN_MAIN_CLASS:=org.apache.maven.cling.MavenCling} -# Build command string for eval +# Build base command string for eval (only contains Maven-controlled values) cmd="\"$JAVACMD\" \ $MAVEN_OPTS \ $MAVEN_DEBUG_OPTS \ @@ -289,14 +289,12 @@ cmd="\"$JAVACMD\" \ $LAUNCHER_CLASS \ $MAVEN_ARGS" -# Add remaining arguments with proper quoting -for arg in "$@"; do - cmd="$cmd \"$arg\"" -done - if [ -n "$MAVEN_DEBUG_SCRIPT" ]; then echo "[DEBUG] Launching JVM with command:" >&2 - echo "[DEBUG] $cmd" >&2 + echo "[DEBUG] $cmd" "$@" >&2 fi -eval exec "$cmd" +# User arguments ("$@") are passed directly to preserve literal values +# like ${...} Maven property placeholders without shell expansion. +# Only the base command uses eval for MAVEN_OPTS word splitting. +eval exec "$cmd" '"$@"' diff --git a/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh11978PlaceholderInCliArgTest.java b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh11978PlaceholderInCliArgTest.java new file mode 100644 index 000000000000..eac9f09dd3e4 --- /dev/null +++ b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh11978PlaceholderInCliArgTest.java @@ -0,0 +1,107 @@ +/* + * 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.nio.file.Path; +import java.util.Properties; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * This is a test set for gh-11978. + * + * Verifies that the launcher script does not expand ${...} patterns + * in CLI arguments. Regression test for the {@code eval exec} shell expansion + * that broke any argument containing Maven property placeholders. + * + *

Real-world scenario

+ * + *

This bug was originally surfaced by maven-surefire-plugin integration tests. + * Surefire allows users to declare system properties for the forked test JVM with + * placeholders that surefire substitutes at fork time, e.g. + * {@code -DtestProperty=testValue_${surefire.threadNumber}_${surefire.forkNumber}}. + * The intended flow is: + *

    + *
  1. The user invokes {@code mvn} with the literal placeholder on the CLI.
  2. + *
  3. The launcher script must pass the literal {@code ${...}} verbatim to + * the JVM as a system property value.
  4. + *
  5. The surefire plugin reads the system property, performs its own + * interpolation when forking each test JVM, replacing + * {@code ${surefire.threadNumber}} and {@code ${surefire.forkNumber}} + * with the actual fork/thread numbers.
  6. + *
  7. Each forked test JVM thus sees a unique substituted value.
  8. + *
+ * + *

The bug broke step 2: the launcher script's {@code eval exec} re-parsed + * the command string and invoked shell variable expansion on {@code ${...}}. + * Names containing dots (such as {@code surefire.threadNumber}) are invalid + * shell variable names, so the shell aborted with {@code bad substitution} + * before Maven even started, breaking all surefire forked-test ITs that relied + * on this pattern. + * + *

Test design

+ * + *

The placeholder name contains dots, mirroring surefire's real usage and + * deliberately producing an invalid shell variable name. Without the fix: + *

+ * With the fix: + * + * + *

Note: this test cannot easily replicate surefire's late-binding interpolation + * (step 3 above) because Maven's standard property interpolation does not + * recursively resolve POM properties for placeholders embedded inside a system + * property value. Surefire performs that substitution in its own plugin code at + * fork time. The signal that proves the fix works is the absence of a + * {@code bad substitution} shell error, not the resolved value itself. + */ +class MavenITgh11978PlaceholderInCliArgTest extends AbstractMavenIntegrationTestCase { + + @Test + void testIt() throws Exception { + Path basedir = extractResources("/gh-11978-placeholder-in-cli-arg") + .getAbsoluteFile() + .toPath(); + + Verifier verifier = newVerifier(basedir.toString()); + verifier.setForkJvm(true); // NOTE: We want to go through the launcher script + // The placeholder name contains dots, which is invalid as a shell variable name. + // Without the fix, the shell's `eval exec` aborts with "bad substitution". + // With the fix, the literal ${...} arrives at Maven. + verifier.addCliArgument("-Dtest.placeholder=value_${some.maven.placeholder}_end"); + verifier.addCliArgument("validate"); + verifier.execute(); + verifier.verifyErrorFreeLog(); // key check: fails immediately on shell crash + + // Sanity check: the value flowed through Maven, which resolves the unknown + // ${some.maven.placeholder} to empty during recursive interpolation. + Properties props = verifier.loadProperties("target/pom.properties"); + assertEquals("-value__end-", props.getProperty("project.properties.pom.placeholder")); + } +} diff --git a/its/core-it-suite/src/test/resources/gh-11978-placeholder-in-cli-arg/pom.xml b/its/core-it-suite/src/test/resources/gh-11978-placeholder-in-cli-arg/pom.xml new file mode 100644 index 000000000000..b55ef8a12088 --- /dev/null +++ b/its/core-it-suite/src/test/resources/gh-11978-placeholder-in-cli-arg/pom.xml @@ -0,0 +1,58 @@ + + + + 4.0.0 + + org.apache.maven.its.gh11978 + test + 1.0 + + Maven Integration Test :: GH-11978 + Verify that the launcher script does not expand ${...} placeholders in CLI arguments. + + + -${test.placeholder}- + + + + + + org.apache.maven.its.plugins + maven-it-plugin-expression + 2.1-SNAPSHOT + + + test + + eval + + validate + + target/pom.properties + + project/properties + + + + + + + +