Skip to content

Fix assertion error when a drive letter path is passed to command line #368

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

rhysd
Copy link
Contributor

@rhysd rhysd commented May 30, 2025

Fix #367

Joining paths with Path::join does not always return an absolute path even if the left hand side is an absolute path. We need to check it before calling path::normalize.

@rhysd
Copy link
Contributor Author

rhysd commented May 30, 2025

@lhecker Thanks for your review. I addressed the comment and force-pushed the change. Would you review it again?

@lhecker
Copy link
Member

lhecker commented May 30, 2025

I think I understand what the issue is now and I believe this needs a fundamentally different approach. When you call Path::join or PathBuf::push in Rust and the joining path is "C:" it'll return just "C:" but that's not how Windows works. "C:" means "the current working directory, but with a prefix of C:". For instance, "D:\foo\bar" joining with "C:" should result in "C:\foo\bar". I'm not sure why Rust does it differently... I'll need a bit to think about this.

@lhecker
Copy link
Member

lhecker commented May 30, 2025

One possible approach could be to replace normalize with a normalized_join method which joins a given absolute path and an absolute or relative path and forms a final absolute path. It would work just like PathBuf::push with the difference that we would resolve all . and all .. immediately without preserving them. We would also fix the behavior of path prefixes like C: that way.

@rhysd
Copy link
Contributor Author

rhysd commented May 30, 2025

Yeah, that's the behavior I roughly explained in #367. Path::join behavior is surprising in this case (I don't know the reason though).
Since drive letter paths like C: are special, I think Edit also should handle them specially. I'll reconsider the way on next Monday (in JST) because I don't have enough time today.

@jstarks
Copy link
Member

jstarks commented May 30, 2025

I think I understand what the issue is now and I believe this needs a fundamentally different approach. When you call Path::join or PathBuf::push in Rust and the joining path is "C:" it'll return just "C:" but that's not how Windows works. "C:" means "the current working directory, but with a prefix of C:". For instance, "D:\foo\bar" joining with "C:" should result in "C:\foo\bar". I'm not sure why Rust does it differently... I'll need a bit to think about this.

That's not right. C: does not mean the current working directory with a prefix of C:. It means C:'s current working directory:

  • If the current directory begins with C:, then this is just the current directory.
  • Otherwise, the value of the =C: environment variable, if it exists and contains a valid path. (This environment variable is generally only set by shells such as cmd.exe. It's not set by SetCurrentDirectory. Ain't compat grand?)
  • Otherwise,C:\

This is the behavior of the directory handling code in ntdll, shared across all Win32 APIs.

So, in this sense, C: is a third type of path, neither relative nor absolute--it is not (always) relative to the current path, but it is also does not specify a context-independent location within the file system.

So, Rust's join behavior seems fairly reasonable. Joining D:\foo\bar with C: should produce C:, and getting an absolute path back out of that will require an OS call to fetch the current directory and possibly environment. (You could make an argument that joining C:\foo\bar with C: should produce C:\foo\bar. And in some contexts, that might be what the user wants and expects. So maybe you'd want to special case that.)

@lhecker
Copy link
Member

lhecker commented May 30, 2025

Oh, I see... Thank you for the correction!
Since this method isn't bound to the behavior of others, I think we may just want to always go with the 3rd bullet point. I suspect that will be easy to implement and not that surprising of a behavior.

@rhysd
Copy link
Contributor Author

rhysd commented Jun 2, 2025

Yeah, so I think we have two options:

  • Remain the drive letter paths as-is like C: and make OS handle them (This is the behavior I originally implemented in this PR)
  • Resolve the drive letter paths to absolute paths using some Win32 API

Which one should we take considering the Edit's behavior?

@lhecker
Copy link
Member

lhecker commented Jun 2, 2025

I believe we should go with the 3rd options for now and simply append a \ on Windows if the result path is not absolute. We can then add a comment, that we may want to use GetFullPathNameW in the future, if this behavior ever becomes undesired. Calling GetFullPathNameW requires doing the WTF8 <> WTF16 conversion and I'd rather see first if anyone even notices the difference. 😅

@jstarks
Copy link
Member

jstarks commented Jun 2, 2025

I believe we should go with the 3rd options for now and simply append a \ on Windows if the result path is not absolute. We can then add a comment, that we may want to use GetFullPathNameW in the future, if this behavior ever becomes undesired. Calling GetFullPathNameW requires doing the WTF8 <> WTF16 conversion and I'd rather see first if anyone even notices the difference. 😅

Note that std::path::absolute already calls GetFullPathNameW on Windows.

@lhecker
Copy link
Member

lhecker commented Jun 2, 2025

Yep, exactly like the equivalent in C++. I'd of course also be fine with documenting it as "may want to use std::path::absolute on Windows" alternatively.

@jstarks
Copy link
Member

jstarks commented Jun 3, 2025

Yep, exactly like the equivalent in C++.

If you say so. I left the C++ world before std::filesystem made it to my build environment 🙂.

@rhysd
Copy link
Contributor Author

rhysd commented Jun 3, 2025

I believe we should go with the 3rd options for now and simply append a \ on Windows if the result path is not absolute.

Let me confirm the behavior before implementing it. I understood that C: is converted to C:\. However what happens if C:foo? C:foo\ is still not absolute. Do you intend C:\foo? (When the current directory is C:\a, C:b\c means C:\a\b\c)

@lhecker
Copy link
Member

lhecker commented Jun 3, 2025

These are the kinds of Windows edge cases that I bet do not come up much in practice today, so whatever we do will be such a rare occurrence that the choice now doesn't matter much. I'll leave this up to you. I do have a specific approach in mind, however, in case you aren't sure yourself.

@rhysd
Copy link
Contributor Author

rhysd commented Jun 3, 2025

Okay, I'll try to implement it. I think the workaround should be as simple as possible so I'll simply try to insert the path separator. Once the implementation is done I'll request your review.

@lhecker lhecker marked this pull request as draft June 17, 2025 14:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Assertion fails when passing C: as command line argument
3 participants