From 3c116a5931ef9e236de527d9659c7fa2cab4005c Mon Sep 17 00:00:00 2001 From: rameel Date: Thu, 19 Mar 2026 20:33:14 +0500 Subject: [PATCH 1/4] Ensure MatchFlags.Auto is resolved before calling PathHelper methods --- Ramstack.Globbing/Traversal/FileTreeAsyncEnumerable.cs | 7 ++++--- Ramstack.Globbing/Traversal/FileTreeEnumerable.cs | 8 ++++---- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/Ramstack.Globbing/Traversal/FileTreeAsyncEnumerable.cs b/Ramstack.Globbing/Traversal/FileTreeAsyncEnumerable.cs index bb1f977..0b39c2c 100644 --- a/Ramstack.Globbing/Traversal/FileTreeAsyncEnumerable.cs +++ b/Ramstack.Globbing/Traversal/FileTreeAsyncEnumerable.cs @@ -86,6 +86,7 @@ private async IAsyncEnumerable EnumerateAsync(CancellationTokenSource? { try { + var flags = Files.AdjustMatchFlags(Flags); var chars = ArrayPool.Shared.Rent(FileTreeEnumerable.DefaultBufferCapacity); var queue = new Queue<(TEntry Directory, string Path)>(); @@ -98,15 +99,15 @@ private async IAsyncEnumerable EnumerateAsync(CancellationTokenSource? var name = FileNameSelector(entry); var fullName = FileTreeHelper.GetFullName(ref chars, e.Path, name); - if (PathHelper.IsMatch(fullName, Excludes, Flags)) + if (PathHelper.IsMatch(fullName, Excludes, flags)) continue; if (ShouldRecursePredicate == null || ShouldRecursePredicate(entry)) - if (PathHelper.IsPartialMatch(fullName, Patterns, Flags)) + if (PathHelper.IsPartialMatch(fullName, Patterns, flags)) queue.Enqueue((entry, fullName.ToString())); if (ShouldIncludePredicate == null || ShouldIncludePredicate(entry)) - if (PathHelper.IsMatch(fullName, Patterns, Flags)) + if (PathHelper.IsMatch(fullName, Patterns, flags)) yield return ResultSelector(entry); } } diff --git a/Ramstack.Globbing/Traversal/FileTreeEnumerable.cs b/Ramstack.Globbing/Traversal/FileTreeEnumerable.cs index eb907b4..5ace249 100644 --- a/Ramstack.Globbing/Traversal/FileTreeEnumerable.cs +++ b/Ramstack.Globbing/Traversal/FileTreeEnumerable.cs @@ -77,8 +77,8 @@ IEnumerator IEnumerable.GetEnumerator() => private IEnumerable Enumerate() { + var flags = Files.AdjustMatchFlags(Flags); var chars = ArrayPool.Shared.Rent(DefaultBufferCapacity); - var queue = new Queue<(TEntry Directory, string Path)>(); queue.Enqueue((_directory, "")); @@ -89,15 +89,15 @@ private IEnumerable Enumerate() var name = FileNameSelector(entry); var fullName = FileTreeHelper.GetFullName(ref chars, e.Path, name); - if (PathHelper.IsMatch(fullName, Excludes, Flags)) + if (PathHelper.IsMatch(fullName, Excludes, flags)) continue; if (ShouldRecursePredicate == null || ShouldRecursePredicate(entry)) - if (PathHelper.IsPartialMatch(fullName, Patterns, Flags)) + if (PathHelper.IsPartialMatch(fullName, Patterns, flags)) queue.Enqueue((entry, fullName.ToString())); if (ShouldIncludePredicate == null || ShouldIncludePredicate(entry)) - if (PathHelper.IsMatch(fullName, Patterns, Flags)) + if (PathHelper.IsMatch(fullName, Patterns, flags)) yield return ResultSelector(entry); } } From 23324706c6e6054f19ab185022ac9b8c814a7f48 Mon Sep 17 00:00:00 2001 From: rameel Date: Thu, 19 Mar 2026 20:37:28 +0500 Subject: [PATCH 2/4] Enforce explicit MatchFlags in PathHelper methods --- Ramstack.Globbing/Internal/PathHelper.cs | 35 ++++++++++++++++++++---- 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/Ramstack.Globbing/Internal/PathHelper.cs b/Ramstack.Globbing/Internal/PathHelper.cs index a03a128..33b1aa7 100644 --- a/Ramstack.Globbing/Internal/PathHelper.cs +++ b/Ramstack.Globbing/Internal/PathHelper.cs @@ -21,13 +21,17 @@ internal static class PathHelper /// /// The path to match for a match. /// An array of patterns to match against the path. - /// The matching options to use. + /// The matching options to use. + /// This parameter must be explicitly set to either or . + /// is not allowed and will result in an assertion failure. /// /// if the path matches any of the patterns; /// otherwise, . /// public static bool IsMatch(ReadOnlySpan path, string[] patterns, MatchFlags flags) { + Debug.Assert(flags != MatchFlags.Auto); + foreach (var pattern in patterns) if (Matcher.IsMatch(path, pattern, flags)) return true; @@ -40,13 +44,17 @@ public static bool IsMatch(ReadOnlySpan path, string[] patterns, MatchFlag /// /// The path to be partially matched. /// An array of patterns to match against the path. - /// The matching options to use. + /// The matching options to use. + /// This parameter must be explicitly set to either or . + /// is not allowed and will result in an assertion failure. /// /// if the path partially matches any of the patterns; /// otherwise, . /// public static bool IsPartialMatch(ReadOnlySpan path, string[] patterns, MatchFlags flags) { + Debug.Assert(flags != MatchFlags.Auto); + var count = CountPathSegments(path, flags); foreach (var pattern in patterns) @@ -60,12 +68,16 @@ public static bool IsPartialMatch(ReadOnlySpan path, string[] patterns, Ma /// Counts the number of segments in the specified path. /// /// The path to count segments for. - /// The flags indicating the type of path separators to match. + /// The flags indicating the type of path separators to match. + /// This parameter must be explicitly set to either or . + /// is not allowed and will result in an assertion failure. /// /// The number of segments in the path. /// public static int CountPathSegments(scoped ReadOnlySpan path, MatchFlags flags) { + Debug.Assert(flags != MatchFlags.Auto); + var count = 0; var iterator = new PathSegmentIterator(); ref var s = ref Unsafe.AsRef(in MemoryMarshal.GetReference(path)); @@ -92,13 +104,16 @@ public static int CountPathSegments(scoped ReadOnlySpan path, MatchFlags f /// Returns a partial pattern from the specified pattern string based on the specified depth. /// /// The pattern string to extract from. - /// The flags indicating the type of path separators to match. + /// The flags indicating the type of path separators to match. + /// This parameter must be explicitly set to either or . + /// is not allowed and will result in an assertion failure. /// The depth level to extract the partial pattern up to. /// /// A representing the partial pattern. /// public static ReadOnlySpan GetPartialPattern(string pattern, MatchFlags flags, int depth) { + Debug.Assert(flags != MatchFlags.Auto); Debug.Assert(depth >= 1); if (depth < 1) @@ -227,12 +242,16 @@ static void ConvertPathToPosixStyleImpl(ref char p, nint length) /// /// Creates a 256-bit bitmask that allows escaping characters based on the specified flags. /// - /// The flags indicating the type of path separators to match. + /// The flags indicating the type of path separators to match. + /// This parameter must be explicitly set to either or . + /// is not allowed and will result in an assertion failure. /// /// A 256-bit bitmask for escaping characters. /// private static Vector256 CreateBackslash256Bitmask(MatchFlags flags) { + Debug.Assert(flags != MatchFlags.Auto); + var mask = Vector256.Zero; if (flags == MatchFlags.Windows) mask = Vector256.AllBitsSet; @@ -243,12 +262,16 @@ private static Vector256 CreateBackslash256Bitmask(MatchFlags flags) /// /// Creates a 128-bit bitmask that allows escaping characters based on the specified flags. /// - /// The flags indicating the type of path separators to match. + /// The flags indicating the type of path separators to match. + /// This parameter must be explicitly set to either or . + /// is not allowed and will result in an assertion failure. /// /// A 128-bit bitmask for escaping characters. /// private static Vector128 CreateBackslash128Bitmask(MatchFlags flags) { + Debug.Assert(flags != MatchFlags.Auto); + var mask = Vector128.Zero; if (flags == MatchFlags.Windows) mask = Vector128.AllBitsSet; From 24b9d4b5b1fd9c97157714900cb9a8bb9c3ddb02 Mon Sep 17 00:00:00 2001 From: rameel Date: Thu, 19 Mar 2026 20:42:00 +0500 Subject: [PATCH 3/4] Rename AdjustMatchFlags to ResolveMatchFlags --- .../Traversal/DirectoryInfoExtensions.cs | 6 +++--- .../Traversal/FileTreeAsyncEnumerable.cs | 2 +- Ramstack.Globbing/Traversal/FileTreeEnumerable.cs | 2 +- Ramstack.Globbing/Traversal/Files.Utilities.cs | 11 ++++++----- Ramstack.Globbing/Traversal/Files.cs | 2 +- 5 files changed, 12 insertions(+), 11 deletions(-) diff --git a/Ramstack.Globbing/Traversal/DirectoryInfoExtensions.cs b/Ramstack.Globbing/Traversal/DirectoryInfoExtensions.cs index c8ed791..d2a8b87 100644 --- a/Ramstack.Globbing/Traversal/DirectoryInfoExtensions.cs +++ b/Ramstack.Globbing/Traversal/DirectoryInfoExtensions.cs @@ -753,7 +753,7 @@ public static IEnumerable EnumerateFileSystemInfos(this Director private static IEnumerable EnumerateFiles(string path, string[] patterns, string[] excludes, MatchFlags flags, SearchTarget target, EnumerationOptions options) { - flags = Files.AdjustMatchFlags(flags); + flags = Files.ResolveMatchFlags(flags); return new FileSystemEnumerable(path, (ref entry) => (FileInfo)entry.ToFileSystemInfo(), options) { @@ -764,7 +764,7 @@ private static IEnumerable EnumerateFiles(string path, string[] patter private static IEnumerable EnumerateDirectories(string path, string[] patterns, string[] excludes, MatchFlags flags, SearchTarget target, EnumerationOptions options) { - flags = Files.AdjustMatchFlags(flags); + flags = Files.ResolveMatchFlags(flags); return new FileSystemEnumerable(path, (ref entry) => (DirectoryInfo)entry.ToFileSystemInfo(), options) { @@ -775,7 +775,7 @@ private static IEnumerable EnumerateDirectories(string path, stri private static IEnumerable EnumerateInfos(string path, string[] patterns, string[] excludes, MatchFlags flags, SearchTarget target, EnumerationOptions options) { - flags = Files.AdjustMatchFlags(flags); + flags = Files.ResolveMatchFlags(flags); return new FileSystemEnumerable(path, (ref entry) => entry.ToFileSystemInfo(), options) { diff --git a/Ramstack.Globbing/Traversal/FileTreeAsyncEnumerable.cs b/Ramstack.Globbing/Traversal/FileTreeAsyncEnumerable.cs index 0b39c2c..d026261 100644 --- a/Ramstack.Globbing/Traversal/FileTreeAsyncEnumerable.cs +++ b/Ramstack.Globbing/Traversal/FileTreeAsyncEnumerable.cs @@ -86,7 +86,7 @@ private async IAsyncEnumerable EnumerateAsync(CancellationTokenSource? { try { - var flags = Files.AdjustMatchFlags(Flags); + var flags = Files.ResolveMatchFlags(Flags); var chars = ArrayPool.Shared.Rent(FileTreeEnumerable.DefaultBufferCapacity); var queue = new Queue<(TEntry Directory, string Path)>(); diff --git a/Ramstack.Globbing/Traversal/FileTreeEnumerable.cs b/Ramstack.Globbing/Traversal/FileTreeEnumerable.cs index 5ace249..41f5643 100644 --- a/Ramstack.Globbing/Traversal/FileTreeEnumerable.cs +++ b/Ramstack.Globbing/Traversal/FileTreeEnumerable.cs @@ -77,7 +77,7 @@ IEnumerator IEnumerable.GetEnumerator() => private IEnumerable Enumerate() { - var flags = Files.AdjustMatchFlags(Flags); + var flags = Files.ResolveMatchFlags(Flags); var chars = ArrayPool.Shared.Rent(DefaultBufferCapacity); var queue = new Queue<(TEntry Directory, string Path)>(); queue.Enqueue((_directory, "")); diff --git a/Ramstack.Globbing/Traversal/Files.Utilities.cs b/Ramstack.Globbing/Traversal/Files.Utilities.cs index 58f29fe..166ed6c 100644 --- a/Ramstack.Globbing/Traversal/Files.Utilities.cs +++ b/Ramstack.Globbing/Traversal/Files.Utilities.cs @@ -88,15 +88,16 @@ internal static bool ShouldRecurse(ref FileSystemEntry entry, string[] patterns, } /// - /// Adjusts the provided match flags based on the current operating system's directory separator character. + /// Resolves the provided match flags by converting + /// to a concrete value based on the current operating system's directory separator convention. /// /// The initial match flags to resolve. /// - /// The adjusted match flags. If the initial flags are , the method returns - /// for Windows systems and for Unix-like systems; - /// otherwise, it returns the provided flags. + /// The resolved match flags. If the input is , returns + /// on Windows systems and + /// on Unix-like systems. Otherwise, returns the original flags unchanged. /// - internal static MatchFlags AdjustMatchFlags(MatchFlags flags) + internal static MatchFlags ResolveMatchFlags(MatchFlags flags) { if (flags == MatchFlags.Auto) return Path.DirectorySeparatorChar == '\\' diff --git a/Ramstack.Globbing/Traversal/Files.cs b/Ramstack.Globbing/Traversal/Files.cs index f04def3..21a643e 100644 --- a/Ramstack.Globbing/Traversal/Files.cs +++ b/Ramstack.Globbing/Traversal/Files.cs @@ -753,7 +753,7 @@ public static IEnumerable EnumerateFileSystemEntries(string path, string private static IEnumerable EnumerateEntries(string path, string[] patterns, string[] excludes, MatchFlags flags, SearchTarget target, EnumerationOptions options) { - flags = AdjustMatchFlags(flags); + flags = ResolveMatchFlags(flags); return new FileSystemEnumerable(Path.GetFullPath(path), (ref entry) => entry.ToFullPath(), options) { From cc65961f43df122c5444a50f5f2de224436ed8b6 Mon Sep 17 00:00:00 2001 From: rameel Date: Thu, 19 Mar 2026 20:49:22 +0500 Subject: [PATCH 4/4] Enforce explicit MatchFlags --- Ramstack.Globbing/Traversal/Files.Utilities.cs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/Ramstack.Globbing/Traversal/Files.Utilities.cs b/Ramstack.Globbing/Traversal/Files.Utilities.cs index 166ed6c..f2da1f5 100644 --- a/Ramstack.Globbing/Traversal/Files.Utilities.cs +++ b/Ramstack.Globbing/Traversal/Files.Utilities.cs @@ -21,7 +21,9 @@ partial class Files /// A file system entry reference. /// An array of glob patterns to match against the names of files. /// Optional array of glob patterns to exclude files. - /// The matching options to use. + /// The matching options to use. + /// This parameter must be explicitly set to either or . + /// is not allowed and will result in an assertion failure. /// The search target. /// /// if the specified file system entry should be included in the results; @@ -29,6 +31,8 @@ partial class Files /// internal static bool ShouldInclude(ref FileSystemEntry entry, string[] patterns, string[] excludes, MatchFlags flags, SearchTarget target) { + Debug.Assert(flags != MatchFlags.Auto); + char[]? rented = null; var current = entry.IsDirectory @@ -60,13 +64,17 @@ internal static bool ShouldInclude(ref FileSystemEntry entry, string[] patterns, /// A file system entry reference. /// An array of glob patterns to match against the names of files. /// Optional array of glob patterns to exclude files. - /// The matching options to use. Default is . + /// The matching options to use. + /// This parameter must be explicitly set to either or . + /// is not allowed and will result in an assertion failure. /// /// if the specified directory entry should be recursed into; /// otherwise, . /// internal static bool ShouldRecurse(ref FileSystemEntry entry, string[] patterns, string[] excludes, MatchFlags flags) { + Debug.Assert(flags != MatchFlags.Auto); + char[]? rented = null; var length = GetRelativePathLength(ref entry); @@ -145,6 +153,8 @@ private static int GetRelativePathLength(ref FileSystemEntry entry) private static void UpdatePathSeparators(scoped Span path, MatchFlags flags) { + Debug.Assert(flags != MatchFlags.Auto); + // To enable escaping in Windows systems, we convert backslashes (\) to forward slashes (/). // This is safe because in Windows, backslashes are only used as path separators. // Otherwise, the backslash (\) in the path will be treated as an escape character,