From e299ba188ef90097db490fdf1970a5ff2944606d Mon Sep 17 00:00:00 2001 From: Martin Desruisseaux Date: Wed, 29 Jul 2026 18:56:45 +0200 Subject: [PATCH 1/2] Remove the optimization which was skipping the path normalization. That optimization can produce false positive if the pattern appears in the base directory. For example if the base directory is "something/target/test-classes" and if an include or exclude pattern is "**/test-classes" where "test-classes" is another directory as a child of the base directory, the optimization would always consider that there is a match. --- .../org/apache/maven/impl/PathSelector.java | 43 ++----------------- 1 file changed, 4 insertions(+), 39 deletions(-) diff --git a/impl/maven-impl/src/main/java/org/apache/maven/impl/PathSelector.java b/impl/maven-impl/src/main/java/org/apache/maven/impl/PathSelector.java index 0f3d1a3c1259..bc90337c64ec 100644 --- a/impl/maven-impl/src/main/java/org/apache/maven/impl/PathSelector.java +++ b/impl/maven-impl/src/main/java/org/apache/maven/impl/PathSelector.java @@ -213,13 +213,6 @@ final class PathSelector implements PathMatcher { */ private final Path baseDirectory; - /** - * Whether paths must be relativized before being given to a matcher. If {@code true}, then every paths - * will be made relative to {@link #baseDirectory} for allowing patterns like {@code "foo/bar/*.java"} - * to work. As a slight optimization, we can skip this step if all patterns start with {@code "**"}. - */ - private final boolean needRelativize; - /** * Creates a new selector from the given includes and excludes. * @@ -240,7 +233,6 @@ private PathSelector( FileSystem fileSystem = baseDirectory.getFileSystem(); this.includes = matchers(fileSystem, includePatterns); this.excludes = matchers(fileSystem, excludePatterns); - needRelativize = needRelativize(includePatterns) || needRelativize(excludePatterns); } /** @@ -472,21 +464,6 @@ private static String[] normalizePatterns(final Collection patterns, fin return normalized.toArray(String[]::new); } - /** - * Returns {@code true} if at least one pattern requires path being relativized before to be matched. - * - * @param patterns include or exclude patterns - * @return whether at least one pattern require relativization - */ - private static boolean needRelativize(String[] patterns) { - for (String pattern : patterns) { - if (!pattern.startsWith(DEFAULT_SYNTAX + WILDCARD_FOR_ANY_PREFIX)) { - return true; - } - } - return false; - } - /** * Creates the path matchers for the given patterns. * The syntax (usually {@value #DEFAULT_SYNTAX}) must be specified for each pattern. @@ -504,16 +481,8 @@ private static PathMatcher[] matchers(final FileSystem fs, final String[] patter */ @SuppressWarnings("checkstyle:MissingSwitchDefault") private PathMatcher simplify() { - if (excludes.length == 0) { - switch (includes.length) { - case 0: - return INCLUDES_ALL; - case 1: - if (needRelativize) { - break; - } - return includes[0]; - } + if (excludes.length == 0 && includes.length == 0) { + return INCLUDES_ALL; } return this; } @@ -527,9 +496,7 @@ private PathMatcher simplify() { */ @Override public boolean matches(Path path) { - if (needRelativize) { - path = baseDirectory.relativize(path); - } + path = baseDirectory.relativize(path); return (includes.length == 0 || isMatched(path, includes)) && (excludes.length == 0 || !isMatched(path, excludes)); } @@ -648,9 +615,7 @@ public boolean matches(Path directory) { if (baseDirectory.equals(directory)) { return true; } - if (needRelativize) { - directory = baseDirectory.relativize(directory); - } + directory = baseDirectory.relativize(directory); if (isMatched(directory, dirExcludes)) { return false; } From c0e3145ac1d55f0a074a3f2ee0f0a9b2b76adc87 Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Wed, 29 Jul 2026 19:59:48 +0200 Subject: [PATCH 2/2] Add regression test for the false-positive when the base directory matches a pattern Constructs the exact scenario from the PR description: base directory "something/target/test-classes" with pattern "**/test-classes/**" would falsely match files in sibling directories when path relativization was skipped. Co-Authored-By: Claude Opus 4.6 --- .../apache/maven/impl/PathSelectorTest.java | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/impl/maven-impl/src/test/java/org/apache/maven/impl/PathSelectorTest.java b/impl/maven-impl/src/test/java/org/apache/maven/impl/PathSelectorTest.java index f541ee5e40ab..9e0f2e5078b9 100644 --- a/impl/maven-impl/src/test/java/org/apache/maven/impl/PathSelectorTest.java +++ b/impl/maven-impl/src/test/java/org/apache/maven/impl/PathSelectorTest.java @@ -159,4 +159,42 @@ public void testBraceAlternationOnlyWithExplicitGlob(@TempDir Path directory) th assertTrue(explicitGlob.matches(mainFile)); assertTrue(explicitGlob.matches(testFile)); } + + /** + * Regression test for #12617. + * Verifies that when the base directory path itself contains a segment matching a {@code **} pattern, + * files that are not actually under a matching child directory are not falsely included. + * + *

The removed optimization skipped path relativization when all patterns started with {@code "**"}, + * which caused the full (non-relativized) path to be matched against the pattern. If the base directory + * happened to contain the matching segment (e.g. base = {@code "something/target/test-classes"} with + * pattern "**/test-classes/**"), every file under the base directory would be + * falsely matched because the base directory itself satisfied the pattern.

+ */ + @Test + public void testNoFalsePositiveWhenBaseDirectoryMatchesPattern(@TempDir Path tempDir) throws IOException { + // Base directory path contains "test-classes" as a segment — this is the trigger for the false positive + Path baseDir = Files.createDirectories(tempDir.resolve("something/target/test-classes")); + + // A child directory that also happens to be named "test-classes" + Path testClassesChild = Files.createDirectories(baseDir.resolve("test-classes")); + // Another child directory with a different name + Path otherChild = Files.createDirectories(baseDir.resolve("other")); + + Path fileInTestClasses = Files.createFile(testClassesChild.resolve("MyTest.class")); + Path fileInOther = Files.createFile(otherChild.resolve("Other.class")); + + // Include pattern: anything under a "test-classes" directory + PathMatcher matcher = PathSelector.of(baseDir, List.of("**/test-classes/**"), null, false); + + // Should match: file is inside the test-classes/ child directory (relative: test-classes/MyTest.class) + assertTrue(matcher.matches(fileInTestClasses), "File inside test-classes/ child directory should be matched"); + + // Should NOT match: file is inside other/, not test-classes/. + // Before the fix, the non-relativized path "something/target/test-classes/other/Other.class" + // would satisfy "**/test-classes/**" because "test-classes" appears in the base directory path. + assertFalse( + matcher.matches(fileInOther), + "File in other/ should not be matched just because the base directory path contains 'test-classes'"); + } }