Repository: ninja-build/ninja (C++)

Issue:
choking on lots of short paths (ninja: fatal: path has too many components)
ninja version 1.9.0 on win (msys2, mingw64)

The directories are 5 levels deep, but a lot of files are listed for implied dependencies on a rule.

Is ninja limiting the number of dependencies intentionally? Is something about my usage makes it thinks that's one big path? Is it just a bug?

```
$ ninja
ninja: fatal: path has too many components : lib/Cython/CodeWriter.py lib/Cython/Coverage.py lib/Cython/Debugging.py lib/Cython/Shadow.py lib/Cython/StringIOTree.py lib/Cython/Utils.py lib/Cython/__init__.py lib/Cython/Compiler/AnalysedTreeTransforms.py lib/Cython/Compiler/Annotate.py lib/Cython/Compiler/AutoDocTransforms.py lib/Cython/Compiler/Buffer.py lib/Cython/Compiler/Builtin.py lib/Cython/Compiler/CmdLine.py lib/Cython/Compiler/Code.pxd lib/Cython/Compiler/Code.py lib/Cython/Compiler/CodeGeneration.py lib/Cython/Compiler/CythonScope.py lib/Cython/Compiler/DebugFlags.py lib/Cython/Compiler/Errors.py lib/Cython/Compiler/ExprNodes.py lib/Cython/Compiler/FlowControl.pxd lib/Cython/Compiler/FlowControl.py lib/Cython/Compiler/FusedNode.py lib/Cython/Compiler/Future.py lib/Cython/Compiler/Interpreter.py lib/Cython/Compiler/Lexicon.py lib/Cython/Compiler/Main.py lib/Cython/Compiler/MemoryView.py lib/Cython/Compiler/ModuleNode.py lib/Cython/Compiler/Naming.py lib/Cython/Compiler/Nodes.py lib/Cython/Compiler/Optimize.py lib/Cython/Compiler/Options.py lib/Cython/Compiler/ParseTreeTransforms.pxd lib/Cython/Compiler/ParseTreeTransforms.py lib/Cython/Compiler/Parsing.pxd lib/Cython/Compiler/Parsing.py lib/Cython/Compiler/Pipeline.py lib/Cython/Compiler/PyrexTypes.py lib/Cython/Compiler/Pythran.py lib/Cython/Compiler/Scanning.pxd lib/Cython/Compiler/Scanning.py lib/Cython/Compiler/StringEncoding.py lib/Cython/Compiler/Symtab.py lib/Cython/Compiler/TreeFragment.py lib/Cython/Compiler/TreePath.py lib/Cython/Compiler/TypeInference.py lib/Cython/Compiler/TypeSlots.py lib/Cython/Compiler/UtilityCode.py lib/Cython/Compiler/UtilNodes.py lib/Cython/Compiler/Version.py lib/Cython/Compiler/Visitor.pxd lib/Cython/Compiler/Visitor.py lib/Cython/Compiler/__init__.py lib/Cython/Includes/openmp.pxd lib/Cython/Includes/cpython/array.pxd lib/Cython/Includes/cpython/bool.pxd lib/Cython/Includes/cpython/buffer.pxd lib/Cython/Includes/cpython/bytes.pxd lib/Cython/Includes/cpython/cobject.pxd lib/Cython/Includes/cpython/complex.pxd lib/Cython/Includes/cpython/datetime.pxd lib/Cython/Includes/cpython/dict.pxd lib/Cython/Includes/cpython/exc.pxd lib/Cython/Includes/cpython/float.pxd lib/Cython/Includes/cpython/function.pxd lib/Cython/Includes/cpython/getargs.pxd lib/Cython/Includes/cpython/instance.pxd lib/Cython/Includes/cpython/int.pxd lib/Cython/Includes/cpython/iterator.pxd lib/Cython/Includes/cpython/list.pxd lib/Cython/Includes/cpython/long.pxd lib/Cython/Includes/cpython/longintrepr.pxd lib/Cython/Includes/cpython/mapping.pxd lib/Cython/Includes/cpython/mem.pxd lib/Cython/Includes/cpython/method.pxd lib/Cython/Includes/cpython/module.pxd lib/Cython/Includes/cpython/number.pxd lib/Cython/Includes/cpython/object.pxd lib/Cython/Includes/cpython/oldbuffer.pxd lib/Cython/Includes/cpython/pycapsule.pxd lib/Cython/Includes/cpython/pystate.pxd lib/Cython/Includes/cpython/pythread.pxd lib/Cython/Includes/cpython/ref.pxd lib/Cython/Includes/cpython/sequence.pxd lib/Cython/Includes/cpython/set.pxd lib/Cython/Includes/cpython/slice.pxd lib/Cython/Includes/cpython/string.pxd lib/Cython/Includes/cpython/tuple.pxd lib/Cython/Includes/cpython/type.pxd lib/Cython/Includes/cpython/unicode.pxd lib/Cython/Includes/cpython/version.pxd lib/Cython/Includes/cpython/weakref.pxd lib/Cython/Includes/cpython/__init__.pxd lib/Cython/Includes/libc/errno.pxd lib/Cython/Includes/libc/float.pxd lib/Cython/Includes/libc/limits.pxd lib/Cython/Includes/libc/locale.pxd lib/Cython/Includes/libc/math.pxd lib/Cython/Includes/libc/setjmp.pxd lib/Cython/Includes/libc/signal.pxd lib/Cython/Includes/libc/stddef.pxd lib/Cython/Includes/libc/stdint.pxd lib/Cython/Includes/libc/stdio.pxd lib/Cython/Includes/libc/stdlib.pxd lib/Cython/Includes/libc/string.pxd lib/Cython/Includes/libc/time.pxd lib/Cython/Includes/libc/__init__.pxd lib/Cython/Includes/libcpp/algorithm.pxd lib/Cython/Includes/libcpp/cast.pxd lib/Cython/Includes/libcpp/complex.pxd lib/Cython/Includes/libcpp/deque.pxd lib/Cython/Includes/libcpp/functional.pxd lib/Cython/Includes/libcpp/iterator.pxd lib/Cython/Includes/libcpp/limits.pxd lib/Cython/Includes/libcpp/list.pxd lib/Cython/Includes/libcpp/map.pxd lib/Cython/Includes/libcpp/memory.pxd lib/Cython/Includes/libcpp/pair.pxd lib/Cython/Includes/libcpp/queue.pxd lib/Cython/Includes/libcpp/set.pxd lib/Cython/Includes/libcpp/stack.pxd lib/Cython/Includes/libcpp/string.pxd lib/Cython/Includes/libcpp/typeindex.pxd lib/Cython/Includes/libcpp/typeinfo.pxd lib/Cython/Includes/libcpp/unordered_map.pxd lib/Cython/Includes/libcpp/unordered_set.pxd lib/Cython/Includes/libcpp/utility.pxd lib/Cython/Includes/libcpp/vector.pxd lib/Cython/Includes/libcpp/__init__.pxd lib/Cython/Includes/numpy/math.pxd lib/Cython/Includes/numpy/__init__.pxd lib/Cython/Includes/posix/dlfcn.pxd lib/Cython/Includes/posix/fcntl.pxd lib/Cython/Includes/posix/ioctl.pxd lib/Cython/Includes/posix/mman.pxd lib/Cython/Includes/posix/resource.pxd lib/Cython/Includes/posix/select.pxd lib/Cython/Includes/posix/signal.pxd lib/Cython/Includes/posix/stat.pxd lib/Cython/Includes/posix/stdio.pxd lib/Cython/Includes/posix/stdlib.pxd lib/Cython/Includes/posix/strings.pxd lib/Cython/Includes/posix/time.pxd lib/Cython/Includes/posix/types.pxd lib/Cython/Includes/posix/unistd.pxd lib/Cython/Includes/posix/wait.pxd lib/Cython/Includes/posix/__init__.pxd lib/Cython/Parser/ConcreteSyntaxTree.pyx lib/Cython/Parser/Grammar lib/Cython/Parser/__init__.py lib/Cython/Plex/Actions.py lib/Cython/Plex/DFA.py lib/Cython/Plex/Errors.py lib/Cython/Plex/Lexicons.py lib/Cython/Plex/Machines.py lib/Cython/Plex/Regexps.py lib/Cython/Plex/Scanners.py lib/Cython/Plex/Timing.py lib/Cython/Plex/Traditional.py lib/Cython/Plex/Transitions.py lib/Cython/Plex/__init__.py lib/Cython/Tempita/compat3.py lib/Cython/Tempita/token.py lib/Cython/Tempita/tokenize.py lib/Cython/Tempita/_looper.py lib/Cython/Tempita/_tempita.py lib/Cython/Tempita/__init__.py lib/Cython/Utility/arrayarray.h lib/Cython/Utility/AsyncGen.c lib/Cython/Utility/Buffer.c lib/Cython/Utility/Builtins.c lib/Cython/Utility/Capsule.c lib/Cython/Utility/CConvert.pyx lib/Cython/Utility/CMath.c lib/Cython/Utility/CommonStructures.c lib/Cython/Utility/Coroutine.c lib/Cython/Utility/CpdefEnums.pyx lib/Cython/Utility/CppConvert.pyx lib/Cython/Utility/CppSupport.cpp lib/Cython/Utility/CythonFunction.c lib/Cython/Utility/Embed.c lib/Cython/Utility/Exceptions.c lib/Cython/Utility/ExtensionTypes.c lib/Cython/Utility/FunctionArguments.c lib/Cython/Utility/ImportExport.c lib/Cython/Utility/MemoryView.pyx lib/Cython/Utility/MemoryView_C.c lib/Cython/Utility/ModuleSetupCode.c lib/Cython/Utility/ObjectHandling.c lib/Cython/Utility/Optimize.c lib/Cython/Utility/Overflow.c lib/Cython/Utility/Printing.c lib/Cython/Utility/Profile.c lib/Cython/Utility/StringTools.c lib/Cython/Utility/TestCythonScope.pyx lib/Cython/Utility/TestCyUtilityLoader.pyx lib/Cython/Utility/TestUtilityLoader.c lib/Cython/Utility/TypeConversion.c lib/Cython/Utility/__init__.py
```

`build.ninja`:
```
cython-deps = lib/Cython/CodeWriter.py lib/Cython/Coverage.py lib/Cython/Debugging.py lib/Cython/Shadow.py lib/Cython/StringIOTree.py lib/Cython/Utils.py lib/Cython/__init__.py lib/Cython/Compiler/AnalysedTreeTransforms.py lib/Cython/Compiler/Annotate.py lib/Cython/Compiler/AutoDocTransforms.py lib/Cython/Compiler/Buffer.py lib/Cython/Compiler/Builtin.py lib/Cython/Compiler/CmdLine.py lib/Cython/Compiler/Code.pxd lib/Cython/Compiler/Code.py lib/Cython/Compiler/CodeGeneration.py lib/Cython/Compiler/CythonScope.py lib/Cython/Compiler/DebugFlags.py lib/Cython/Compiler/Errors.py lib/Cython/Compiler/ExprNodes.py lib/Cython/Compiler/FlowControl.pxd lib/Cython/Compiler/FlowControl.py lib/Cython/Compiler/FusedNode.py lib/Cython/Compiler/Future.py lib/Cython/Compiler/Interpreter.py lib/Cython/Compiler/Lexicon.py lib/Cython/Compiler/Main.py lib/Cython/Compiler/MemoryView.py lib/Cython/Compiler/ModuleNode.py lib/Cython/Compiler/Naming.py lib/Cython/Compiler/Nodes.py lib/Cython/Compiler/Optimize.py lib/Cython/Compiler/Options.py lib/Cython/Compiler/ParseTreeTransforms.pxd lib/Cython/Compiler/ParseTreeTransforms.py lib/Cython/Compiler/Parsing.pxd lib/Cython/Compiler/Parsing.py lib/Cython/Compiler/Pipeline.py lib/Cython/Compiler/PyrexTypes.py lib/Cython/Compiler/Pythran.py lib/Cython/Compiler/Scanning.pxd lib/Cython/Compiler/Scanning.py lib/Cython/Compiler/StringEncoding.py lib/Cython/Compiler/Symtab.py lib/Cython/Compiler/TreeFragment.py lib/Cython/Compiler/TreePath.py lib/Cython/Compiler/TypeInference.py lib/Cython/Compiler/TypeSlots.py lib/Cython/Compiler/UtilityCode.py lib/Cython/Compiler/UtilNodes.py lib/Cython/Compiler/Version.py lib/Cython/Compiler/Visitor.pxd lib/Cython/Compiler/Visitor.py lib/Cython/Compiler/__init__.py lib/Cython/Includes/openmp.pxd lib/Cython/Includes/cpython/array.pxd lib/Cython/Includes/cpython/bool.pxd lib/Cython/Includes/cpython/buffer.pxd lib/Cython/Includes/cpython/bytes.pxd lib/Cython/Includes/cpython/cobject.pxd lib/Cython/Includes/cpython/complex.pxd lib/Cython/Includes/cpython/datetime.pxd lib/Cython/Includes/cpython/dict.pxd lib/Cython/Includes/cpython/exc.pxd lib/Cython/Includes/cpython/float.pxd lib/Cython/Includes/cpython/function.pxd lib/Cython/Includes/cpython/getargs.pxd lib/Cython/Includes/cpython/instance.pxd lib/Cython/Includes/cpython/int.pxd lib/Cython/Includes/cpython/iterator.pxd lib/Cython/Includes/cpython/list.pxd lib/Cython/Includes/cpython/long.pxd lib/Cython/Includes/cpython/longintrepr.pxd lib/Cython/Includes/cpython/mapping.pxd lib/Cython/Includes/cpython/mem.pxd lib/Cython/Includes/cpython/method.pxd lib/Cython/Includes/cpython/module.pxd lib/Cython/Includes/cpython/number.pxd lib/Cython/Includes/cpython/object.pxd lib/Cython/Includes/cpython/oldbuffer.pxd lib/Cython/Includes/cpython/pycapsule.pxd lib/Cython/Includes/cpython/pystate.pxd lib/Cython/Includes/cpython/pythread.pxd lib/Cython/Includes/cpython/ref.pxd lib/Cython/Includes/cpython/sequence.pxd lib/Cython/Includes/cpython/set.pxd lib/Cython/Includes/cpython/slice.pxd lib/Cython/Includes/cpython/string.pxd lib/Cython/Includes/cpython/tuple.pxd lib/Cython/Includes/cpython/type.pxd lib/Cython/Includes/cpython/unicode.pxd lib/Cython/Includes/cpython/version.pxd lib/Cython/Includes/cpython/weakref.pxd lib/Cython/Includes/cpython/__init__.pxd lib/Cython/Includes/libc/errno.pxd lib/Cython/Includes/libc/float.pxd lib/Cython/Includes/libc/limits.pxd lib/Cython/Includes/libc/locale.pxd lib/Cython/Includes/libc/math.pxd lib/Cython/Includes/libc/setjmp.pxd lib/Cython/Includes/libc/signal.pxd lib/Cython/Includes/libc/stddef.pxd lib/Cython/Includes/libc/stdint.pxd lib/Cython/Includes/libc/stdio.pxd lib/Cython/Includes/libc/stdlib.pxd lib/Cython/Includes/libc/string.pxd lib/Cython/Includes/libc/time.pxd lib/Cython/Includes/libc/__init__.pxd lib/Cython/Includes/libcpp/algorithm.pxd lib/Cython/Includes/libcpp/cast.pxd lib/Cython/Includes/libcpp/complex.pxd lib/Cython/Includes/libcpp/deque.pxd lib/Cython/Includes/libcpp/functional.pxd lib/Cython/Includes/libcpp/iterator.pxd lib/Cython/Includes/libcpp/limits.pxd lib/Cython/Includes/libcpp/list.pxd lib/Cython/Includes/libcpp/map.pxd lib/Cython/Includes/libcpp/memory.pxd lib/Cython/Includes/libcpp/pair.pxd lib/Cython/Includes/libcpp/queue.pxd lib/Cython/Includes/libcpp/set.pxd lib/Cython/Includes/libcpp/stack.pxd lib/Cython/Includes/libcpp/string.pxd lib/Cython/Includes/libcpp/typeindex.pxd lib/Cython/Includes/libcpp/typeinfo.pxd lib/Cython/Includes/libcpp/unordered_map.pxd lib/Cython/Includes/libcpp/unordered_set.pxd lib/Cython/Includes/libcpp/utility.pxd lib/Cython/Includes/libcpp/vector.pxd lib/Cython/Includes/libcpp/__init__.pxd lib/Cython/Includes/numpy/math.pxd lib/Cython/Includes/numpy/__init__.pxd lib/Cython/Includes/posix/dlfcn.pxd lib/Cython/Includes/posix/fcntl.pxd lib/Cython/Includes/posix/ioctl.pxd lib/Cython/Includes/posix/mman.pxd lib/Cython/Includes/posix/resource.pxd lib/Cython/Includes/posix/select.pxd lib/Cython/Includes/posix/signal.pxd lib/Cython/Includes/posix/stat.pxd lib/Cython/Includes/posix/stdio.pxd lib/Cython/Includes/posix/stdlib.pxd lib/Cython/Includes/posix/strings.pxd lib/Cython/Includes/posix/time.pxd lib/Cython/Includes/posix/types.pxd lib/Cython/Includes/posix/unistd.pxd lib/Cython/Includes/posix/wait.pxd lib/Cython/Includes/posix/__init__.pxd lib/Cython/Parser/ConcreteSyntaxTree.pyx lib/Cython/Parser/Grammar lib/Cython/Parser/__init__.py lib/Cython/Plex/Actions.py lib/Cython/Plex/DFA.py lib/Cython/Plex/Errors.py lib/Cython/Plex/Lexicons.py lib/Cython/Plex/Machines.py lib/Cython/Plex/Regexps.py lib/Cython/Plex/Scanners.py lib/Cython/Plex/Timing.py lib/Cython/Plex/Traditional.py lib/Cython/Plex/Transitions.py lib/Cython/Plex/__init__.py lib/Cython/Tempita/compat3.py lib/Cython/Tempita/token.py lib/Cython/Tempita/tokenize.py lib/Cython/Tempita/_looper.py lib/Cython/Tempita/_tempita.py lib/Cython/Tempita/__init__.py lib/Cython/Utility/arrayarray.h lib/Cython/Utility/AsyncGen.c lib/Cython/Utility/Buffer.c lib/Cython/Utility/Builtins.c lib/Cython/Utility/Capsule.c lib/Cython/Utility/CConvert.pyx lib/Cython/Utility/CMath.c lib/Cython/Utility/CommonStructures.c lib/Cython/Utility/Coroutine.c lib/Cython/Utility/CpdefEnums.pyx lib/Cython/Utility/CppConvert.pyx lib/Cython/Utility/CppSupport.cpp lib/Cython/Utility/CythonFunction.c lib/Cython/Utility/Embed.c lib/Cython/Utility/Exceptions.c lib/Cython/Utility/ExtensionTypes.c lib/Cython/Utility/FunctionArguments.c lib/Cython/Utility/ImportExport.c lib/Cython/Utility/MemoryView.pyx lib/Cython/Utility/MemoryView_C.c lib/Cython/Utility/ModuleSetupCode.c lib/Cython/Utility/ObjectHandling.c lib/Cython/Utility/Optimize.c lib/Cython/Utility/Overflow.c lib/Cython/Utility/Printing.c lib/Cython/Utility/Profile.c lib/Cython/Utility/StringTools.c lib/Cython/Utility/TestCythonScope.pyx lib/Cython/Utility/TestCyUtilityLoader.pyx lib/Cython/Utility/TestUtilityLoader.c lib/Cython/Utility/TypeConversion.c lib/Cython/Utility/__init__.py

build src/mod/_pyx.c: cython src/mod/_pyx.pyx | $cython-deps
```
ninja thinks the root directory doesn't exist
Given a build statement that declares a dependency on the root directory:
```
rule my_rule
    command = echo foo
build foo: my_rule /
```
ninja outputs:
```
ninja: error: '', needed by 'foo', missing and no known rule to make it
```

Reproduced on ninja 1.10.2 and latest master, on macOS.

Yes, I know that depending on the root directory isn't a particularly sensible thing to do. :) In my real use case it's an order-only dependency, so slightly less insane, but still unnecessary; it's just the product of a quirk in my build.ninja generator.  Still, there's no reason it shouldn't work, as far as I know.


Discussion: See #1161.
Maybe we could fallback to a "slow" version of CanonicalizePath when the maximum is reached (i.e. one with a heap allocated array).
Also having what appears to be the same/similar issue in a Visual Studio 2019 CMake project once I link against `Qt5` (`target_link_libraries(CMakeProjectHelloWorld Qt5::Widgets)`):

```
ninja: fatal: path has too many components : E:\vcpkg\installed\x64-windows-static\debug\lib\Qt5Cored.lib "E:\Windows Kits\10\lib\10.0.18362.0\um\x64\mpr.lib" "E:\Windows Kits\10\lib\10.0.18362.0\um\x64\netapi32.lib" "E:\Windows Kits\10\lib\10.0.18362.0\um\x64\userenv.lib" "E:\Windows Kits\10\lib\10.0.18362.0\um\x64\version.lib" "E:\Windows Kits\10\lib\10.0.18362.0\um\x64\ws2_32.lib" "E:\Windows Kits\10\lib\10.0.18362.0\um\x64\kernel32.lib" "E:\Windows Kits\10\lib\10.0.18362.0\um\x64\user32.lib" "E:\Windows Kits\10\lib\10.0.18362.0\user32.lib" "E:\\Windows Kits\\10\\lib\\10.0.18362.0\\um\\x64\\shell32.lib" "E:\\Windows Kits\\10\\lib\\10.0.18362.0\\um\\x64\\uuid.lib" "E:\\Windows Kits\\10\\lib\\10.0.18362.0\\um\\x64\\ole32.lib" "E:\\Windows Kits\\10\\lib\\10.0.18362.0\\um\\x64\\advapi32.lib" "E:\\Windows Kits\\10\\lib\\10.0.18362.0\\um\\x64\\winmm.lib"
```

The `ninja.exe` bundled with Visual Studio at `E:\Program Files (x86)\Microsoft Visual Studio\2019\Community\Common7\IDE\CommonExtensions\Microsoft\CMake\Ninja\ninja.exe` was `1.8.2` but even if I replaced this with `1.10.0` I am encountering the same issue.
I have just hit this limit. On Linux.

I did a script counting the slashes, and the issue is not reaching the path separator limit (as in #1161 or #1181). My maximum is 17 slashes.

However the "path" is more then 10MB long and has 133782 paths (semicolons).
Strange this is that this happens on a build server in a build container on one server, but not on others.

edit:
I found the issue in my CMake, the dependency list of a target was very long (wrong glob). But nevertheless the error is very hard to fix.
I just ran into this limit on Windows using CMake 3.14.5 and ninja.
Number of files in path: 33
Length of path in bytes: 1049089





Fix (patch):
diff --git a/src/util.cc b/src/util.cc
index 0553de346c..5f67fcf403 100644
--- a/src/util.cc
+++ b/src/util.cc
@@ -143,20 +143,19 @@ void CanonicalizePath(char* path, size_t* len, uint64_t* slash_bits) {
     return;
   }
 
-  const int kMaxPathComponents = 60;
-  char* components[kMaxPathComponents];
-  int component_count = 0;
-
   char* start = path;
   char* dst = start;
+  char* dst_start = dst;
   const char* src = start;
   const char* end = start + *len;
+  const char* src_next;
 
+  // For absolute paths, skip the leading directory separator
+  // as this one should never be removed from the result.
   if (IsPathSeparator(*src)) {
 #ifdef _WIN32
-
-    // network path starts with //
-    if (*len > 1 && IsPathSeparator(*(src + 1))) {
+    // Windows network path starts with //
+    if (src + 2 <= end && IsPathSeparator(src[1])) {
       src += 2;
       dst += 2;
     } else {
@@ -167,50 +166,126 @@ void CanonicalizePath(char* path, size_t* len, uint64_t* slash_bits) {
     ++src;
     ++dst;
 #endif
+    dst_start = dst;
+  } else {
+    // For relative paths, skip any leading ../ as these are quite common
+    // to reference source files in build plans, and doing this here makes
+    // the loop work below faster in general.
+    while (src + 3 <= end && src[0] == '.' && src[1] == '.' &&
+           IsPathSeparator(src[2])) {
+      src += 3;
+      dst += 3;
+    }
   }
 
-  while (src < end) {
-    if (*src == '.') {
-      if (src + 1 == end || IsPathSeparator(src[1])) {
-        // '.' component; eliminate.
-        src += 2;
-        continue;
-      } else if (src[1] == '.' && (src + 2 == end || IsPathSeparator(src[2]))) {
-        // '..' component.  Back up if possible.
+  // Loop over all components of the paths _except_ the last one, in
+  // order to simplify the loop's code and make it faster.
+  int component_count = 0;
+  char* dst0 = dst;
+  for (; src < end; src = src_next) {
+#ifndef _WIN32
+    // Use memchr() for faster lookups thanks to optimized C library
+    // implementation. `hyperfine canon_perftest` shows a significant
+    // difference (e,g, 484ms vs 437ms).
+    const char* next_sep =
+        static_cast<const char*>(::memchr(src, '/', end - src));
+    if (!next_sep) {
+      // This is the last component, will be handled out of the loop.
+      break;
+    }
+#else
+    // Need to check for both '/' and '\\' so do not use memchr().
+    // Cannot use strpbrk() because end[0] can be \0 or something else!
+    const char* next_sep = src;
+    while (next_sep != end && !IsPathSeparator(*next_sep))
+      ++next_sep;
+    if (next_sep == end) {
+      // This is the last component, will be handled out of the loop.
+      break;
+    }
+#endif
+    // Position for next loop iteration.
+    src_next = next_sep + 1;
+    // Length of the component, excluding trailing directory.
+    size_t component_len = next_sep - src;
+
+    if (component_len <= 2) {
+      if (component_len == 0) {
+        continue;  // Ignore empty component, e.g. 'foo//bar' -> 'foo/bar'.
+      }
+      if (src[0] == '.') {
+        if (component_len == 1) {
+          continue;  // Ignore '.' component, e.g. './foo' -> 'foo'.
+        } else if (src[1] == '.') {
+          // Process the '..' component if found. Back up if possible.
+          if (component_count > 0) {
+            // Move back to start of previous component.
+            --component_count;
+            while (--dst > dst0 && !IsPathSeparator(dst[-1])) {
+              // nothing to do here, decrement happens before condition check.
+            }
+          } else {
+            dst[0] = '.';
+            dst[1] = '.';
+            dst[2] = src[2];
+            dst += 3;
+          }
+          continue;
+        }
+      }
+    }
+    ++component_count;
+
+    // Copy or skip component, including trailing directory separator.
+    if (dst != src) {
+      ::memmove(dst, src, src_next - src);
+    }
+    dst += src_next - src;
+  }
+
+  // Handling the last component that does not have a trailing separator.
+  // The logic here is _slightly_ different since there is no trailing
+  // directory separator.
+  size_t component_len = end - src;
+  do {
+    if (component_len == 0)
+      break;  // Ignore empty component (e.g. 'foo//' -> 'foo/')
+    if (src[0] == '.') {
+      if (component_len == 1)
+        break;  // Ignore trailing '.' (e.g. 'foo/.' -> 'foo/')
+      if (src[1] == '.') {
+        // Handle '..'. Back up if possible.
         if (component_count > 0) {
-          dst = components[component_count - 1];
-          src += 3;
-          --component_count;
+          while (--dst > dst0 && !IsPathSeparator(dst[-1])) {
+            // nothing to do here, decrement happens before condition check.
+          }
         } else {
-          *dst++ = *src++;
-          *dst++ = *src++;
-          *dst++ = *src++;
+          dst[0] = '.';
+          dst[1] = '.';
+          dst += 2;
+          // No separator to add here.
         }
-        continue;
+        break;
       }
     }
-
-    if (IsPathSeparator(*src)) {
-      src++;
-      continue;
+    // Skip or copy last component, no trailing separator.
+    if (dst != src) {
+      ::memmove(dst, src, component_len);
     }
+    dst += component_len;
+  } while (0);
 
-    if (component_count == kMaxPathComponents)
-      Fatal("path has too many components : %s", path);
-    components[component_count] = dst;
-    ++component_count;
-
-    while (src != end && !IsPathSeparator(*src))
-      *dst++ = *src++;
-    *dst++ = *src++;  // Copy '/' or final \0 character as well.
-  }
+  // Remove trailing path separator if any, but keep the initial
+  // path separator(s) if there was one (or two on Windows).
+  if (dst > dst_start && IsPathSeparator(dst[-1]))
+    dst--;
 
   if (dst == start) {
+    // Handle special cases like "aa/.." -> "."
     *dst++ = '.';
-    *dst++ = '\0';
   }
 
-  *len = dst - start - 1;
+  *len = dst - start;  // dst points after the trailing char here.
 #ifdef _WIN32
   uint64_t bits = 0;
   uint64_t bits_mask = 1;