[llvm] 45d5d56 - [llvm-ar] fix inconsistent case sensitivity for path matching on Windows (#196541)
via llvm-commits
llvm-commits at lists.llvm.org
Mon Jun 8 00:58:07 PDT 2026
Author: Meow
Date: 2026-06-08T08:58:02+01:00
New Revision: 45d5d5669e1f9bd4bd1a2032e64a001500096e3f
URL: https://github.com/llvm/llvm-project/commit/45d5d5669e1f9bd4bd1a2032e64a001500096e3f
DIFF: https://github.com/llvm/llvm-project/commit/45d5d5669e1f9bd4bd1a2032e64a001500096e3f.diff
LOG: [llvm-ar] fix inconsistent case sensitivity for path matching on Windows (#196541)
When deleting or extracting with -N, member counting uses case sensitive
member arguments while path matching uses normalized paths.
This causes an issue (on Windows): if you have files: foo.txt and
FOO.txt, you can only extract one of them when using -N 1, and using -N
2 results in error.
Added:
llvm/test/tools/llvm-ar/count-case-sensitivity-windows.test
llvm/test/tools/llvm-ar/count-case-sensitivity.test
Modified:
llvm/docs/ReleaseNotes.md
llvm/tools/llvm-ar/llvm-ar.cpp
Removed:
################################################################################
diff --git a/llvm/docs/ReleaseNotes.md b/llvm/docs/ReleaseNotes.md
index 5cd88f4260011..f234e9b5d6433 100644
--- a/llvm/docs/ReleaseNotes.md
+++ b/llvm/docs/ReleaseNotes.md
@@ -313,6 +313,8 @@ Makes programs 10x faster by doing Special New Thing.
prefixes, making it an alias of the existing `-check-prefixes` option.
* Add `-mtune` option to `llc`.
* Add `-mtune` option to `opt`.
+* Fixed `llvm-ar` to correctly handle the `N` count modifier on Windows for archive members whose names
diff er only
+ in case (e.g. `FOO.OBJ` and `foo.obj`). Previously, `-N 2` would fail with "not found" even when two matching members existed.
### Changes to LLDB
diff --git a/llvm/test/tools/llvm-ar/count-case-sensitivity-windows.test b/llvm/test/tools/llvm-ar/count-case-sensitivity-windows.test
new file mode 100644
index 0000000000000..380a3396591ed
--- /dev/null
+++ b/llvm/test/tools/llvm-ar/count-case-sensitivity-windows.test
@@ -0,0 +1,50 @@
+# REQUIRES: system-windows
+
+## File instance counting should respect Windows case-insensitive name matching.
+## On Windows systems, `foo.txt` and `FOO.TXT` are considered equal (case-insensitive),
+## so `-N 2 foo.txt` should extract `FOO.TXT`.
+
+# RUN: rm -rf %t && mkdir %t && cd %t
+# RUN: mkdir a b
+# RUN: echo first > a/foo.txt
+# RUN: echo second > b/FOO.TXT
+# RUN: llvm-ar rc count-case.a a/foo.txt b/FOO.TXT
+
+# RUN: mkdir out1 out2
+# RUN: llvm-ar xN 1 --output out1 count-case.a Foo.txt
+# RUN: cat out1/foo.txt | FileCheck %s --check-prefix=EXTRACT-FIRST
+# RUN: llvm-ar xN 2 --output out2 count-case.a Foo.txt
+# RUN: cat out2/FOO.TXT | FileCheck %s --check-prefix=EXTRACT-SECOND
+# RUN: not llvm-ar xN 3 --output out2 count-case.a Foo.txt 2>&1 | FileCheck %s --check-prefix=EXTRACT-NOT-FOUND
+
+# EXTRACT-FIRST: first
+# EXTRACT-SECOND: second
+# EXTRACT-NOT-FOUND: error: 'Foo.txt' was not found
+
+# RUN: llvm-ar rc delete-count.a a/foo.txt b/FOO.TXT
+# RUN: llvm-ar dN 2 delete-count.a Foo.txt
+# RUN: llvm-ar t delete-count.a | FileCheck %s --check-prefix=DELETE-SECOND --implicit-check-not={{.}}
+# RUN: llvm-ar dN 1 delete-count.a Foo.txt
+# RUN: llvm-ar t delete-count.a | count 0
+
+# DELETE-SECOND: foo.txt
+
+## Test thin archive d option with P and N modifiers (full path matching).
+
+# RUN: echo one > a/file.txt
+# RUN: llvm-ar rc --thin archive-dPN.a a/file.txt a/FILE.txt
+# RUN: llvm-ar t archive-dPN.a | FileCheck %s --check-prefix=THIN-BEFORE --implicit-check-not={{.}}
+
+## Third does not exist; expect no-op.
+# RUN: llvm-ar dPN 3 --thin archive-dPN.a A/File.tXt
+# RUN: llvm-ar t archive-dPN.a | FileCheck %s --check-prefix=THIN-BEFORE --implicit-check-not={{.}}
+
+# RUN: llvm-ar dPN 2 --thin archive-dPN.a A/File.tXt
+# RUN: llvm-ar t archive-dPN.a | FileCheck %s --check-prefix=THIN-AFTER --implicit-check-not={{.}}
+# RUN: llvm-ar dPN 1 --thin archive-dPN.a A/File.tXt
+# RUN: llvm-ar t archive-dPN.a | count 0
+
+# THIN-BEFORE: a/file.txt
+# THIN-BEFORE: a/FILE.txt
+
+# THIN-AFTER: a/file.txt
diff --git a/llvm/test/tools/llvm-ar/count-case-sensitivity.test b/llvm/test/tools/llvm-ar/count-case-sensitivity.test
new file mode 100644
index 0000000000000..39d353a3acdb2
--- /dev/null
+++ b/llvm/test/tools/llvm-ar/count-case-sensitivity.test
@@ -0,0 +1,56 @@
+# UNSUPPORTED: system-windows
+
+## File instance counting should be case-sensitive on non-Windows platforms.
+## On non-Windows systems, `foo.txt` and `FOO.TXT` are distinct, so `-N 2 foo.txt`
+## should fail because only one file named `foo.txt` exists.
+
+# RUN: rm -rf %t && mkdir %t && cd %t
+# RUN: mkdir a b
+# RUN: echo first > a/foo.txt
+# RUN: echo second > b/FOO.TXT
+# RUN: llvm-ar rc count-case.a a/foo.txt b/FOO.TXT
+
+# RUN: mkdir out1 out2
+# RUN: llvm-ar xN 1 --output out1 count-case.a foo.txt
+# RUN: cat out1/foo.txt | FileCheck %s --check-prefix=EXTRACT-FIRST
+# RUN: not llvm-ar xN 2 --output out2 count-case.a foo.txt 2>&1 | FileCheck %s --check-prefix=EXTRACT-NOT-FOUND
+
+# EXTRACT-FIRST: first
+# EXTRACT-NOT-FOUND: error: 'foo.txt' was not found
+
+# RUN: llvm-ar rc delete-count.a a/foo.txt b/FOO.TXT
+# RUN: llvm-ar dN 2 delete-count.a FOO.TXT
+# RUN: llvm-ar t delete-count.a | FileCheck %s --check-prefix=ARCHIVE-BEFORE --implicit-check-not={{.}}
+
+# RUN: llvm-ar dN 1 delete-count.a FOO.TXT
+# RUN: llvm-ar t delete-count.a | FileCheck %s --check-prefix=ARCHIVE-AFTER --implicit-check-not={{.}}
+
+# ARCHIVE-BEFORE: foo.txt
+# ARCHIVE-BEFORE: FOO.TXT
+
+# ARCHIVE-AFTER: foo.txt
+
+## Test thin archive d option with P and N modifiers (full path matching).
+
+# RUN: echo one > a/file.txt
+# RUN: echo two > a/FILE.txt
+# RUN: llvm-ar rc --thin archive-dPN.a a/file.txt a/FILE.txt
+# RUN: llvm-ar t archive-dPN.a | FileCheck %s --check-prefix=THIN-BEFORE --implicit-check-not={{.}}
+
+## The path to `FILE.TXT` does not match because the directory name has a
diff erent case; expect no-op.
+# RUN: llvm-ar dPN 2 --thin archive-dPN.a A/FILE.txt
+# RUN: llvm-ar t archive-dPN.a | FileCheck %s --check-prefix=THIN-BEFORE --implicit-check-not={{.}}
+
+## The uppercase filename occurs only once; expect no-op.
+# RUN: llvm-ar dPN 2 --thin archive-dPN.a a/FILE.txt
+# RUN: llvm-ar t archive-dPN.a | FileCheck %s --check-prefix=THIN-BEFORE --implicit-check-not={{.}}
+
+# RUN: llvm-ar dPN 1 --thin archive-dPN.a a/FILE.txt
+# RUN: llvm-ar t archive-dPN.a | FileCheck %s --check-prefix=THIN-AFTER --implicit-check-not={{.}}
+# RUN: llvm-ar dPN 1 --thin archive-dPN.a a/file.txt
+# RUN: llvm-ar t archive-dPN.a | count 0
+
+# THIN-BEFORE: a/file.txt
+# THIN-BEFORE: a/FILE.txt
+
+# THIN-AFTER: a/file.txt
diff --git a/llvm/tools/llvm-ar/llvm-ar.cpp b/llvm/tools/llvm-ar/llvm-ar.cpp
index 320a903b59e87..bac99c548a178 100644
--- a/llvm/tools/llvm-ar/llvm-ar.cpp
+++ b/llvm/tools/llvm-ar/llvm-ar.cpp
@@ -713,8 +713,11 @@ static void performReadOperation(ArchiveOperation Operation,
});
if (I == Members.end())
continue;
- if (CountParam && ++MemberCount[Name] != CountParam)
- continue;
+ if (CountParam) {
+ std::string CountKey = normalizePath(*I);
+ if (++MemberCount[CountKey] != CountParam)
+ continue;
+ }
Members.erase(I);
}
@@ -854,14 +857,19 @@ static InsertAction computeInsertAction(ArchiveOperation Operation,
if (Operation == QuickAppend || Members.empty())
return IA_AddOldMember;
- auto MI = find_if(Members, [Name](StringRef Path) {
+ std::string CountKey;
+ auto MI = find_if(Members, [Name, &CountKey](StringRef Path) {
+ SmallString<128> MatchPath(Path);
if (Thin && !sys::path::is_absolute(Path)) {
Expected<std::string> PathOrErr =
computeArchiveRelativePath(ArchiveName, Path);
- return comparePaths(Name, PathOrErr ? *PathOrErr : Path);
- } else {
- return comparePaths(Name, Path);
+ if (PathOrErr)
+ MatchPath = *PathOrErr;
}
+ if (!comparePaths(Name, MatchPath))
+ return false;
+ CountKey = normalizePath(MatchPath);
+ return true;
});
if (MI == Members.end())
@@ -870,7 +878,7 @@ static InsertAction computeInsertAction(ArchiveOperation Operation,
Pos = MI;
if (Operation == Delete) {
- if (CountParam && ++MemberCount[Name] != CountParam)
+ if (CountParam && ++MemberCount[CountKey] != CountParam)
return IA_AddOldMember;
return IA_Delete;
}
More information about the llvm-commits
mailing list