Skip to content
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

Improve path normalization and judgment on Windows #191

Closed
wants to merge 8 commits into from

Conversation

stevapple
Copy link

@stevapple stevapple commented Feb 23, 2021

  • Fixes the problem of .. being ignored during normalizing;
  • Avoids crash when validating an empty path.

@tomerd
Copy link
Contributor

tomerd commented Feb 23, 2021

@swift-ci test

@tomerd
Copy link
Contributor

tomerd commented Feb 23, 2021

@compnerd, this seems reasonable to me

Sources/TSCBasic/Path.swift Outdated Show resolved Hide resolved
Sources/TSCBasic/Path.swift Show resolved Hide resolved
Sources/TSCBasic/Path.swift Outdated Show resolved Hide resolved
Sources/TSCBasic/Path.swift Show resolved Hide resolved
#else
pathSeparator = "/"
#endif
precondition(path.first != pathSeparator)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This precondition doesn't hold. \path is valid as a relative path. It is relative to the current drive.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although it is literally "relative", Windows Shell (and Swift which depends on it) regards \path \?\path \\share\path as absolute paths (according to the response of PathIsRelativeW). Personally I think these paths should be treated the same with a regular absolute path. For example, \path\..\.. indicates \ like C:\path\..\.. indicates C:\, but path\..\.. indicates ...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UNC paths are always absolute. It is just a drive relative path which is not absolute. That distinction is important as it impacts the lexical traversal of the path string.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PathIsRelativeW regards \path as an absolute path. Also, if we treat it as relative, behavior regarding .. will be unaligned.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ping @compnerd

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still think that the differences are material and need to be addressed. Paths in Windows do not behave like Unix, and so you will have differences. e.g.

path1 = "C:\Users\compnerd"
path2 = "D:"
path1 + path2 is not `C:\Users\compnerd\D:" but rather "C:\Windows\System32\WindowsMetadata"

because the current directory on D: happened to be "....\Windows\System32\WindowsMetadata"

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’ll look into more critical cases here.

However, if I regard \path as a relative path, it will change the behavior of a bunch of APIs (while this PR focuses on bug fixes), so that change definitely needs to be addressed in a separate PR.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO I believe this PR is fully capable of fixing faulty behavior without changing the current judgement of relative paths and absolute paths. To fully support Windows path system, we’d better work on another one which may contain a complete refactoring.

What this PR mainly addressed and fixed is the problem of .. being ignored, and of crashing on an empty path, which are critical bugs for tools like SwiftPM.

Sources/TSCBasic/Path.swift Show resolved Hide resolved
Sources/TSCBasic/Path.swift Show resolved Hide resolved
@stevapple stevapple changed the title Handle empty path on Windows Fix path normalization and judgment on Windows Feb 25, 2021
Sources/TSCBasic/Path.swift Outdated Show resolved Hide resolved
Sources/TSCBasic/Path.swift Outdated Show resolved Hide resolved
#else
pathSeparator = "/"
#endif
precondition(path.first != pathSeparator)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UNC paths are always absolute. It is just a drive relative path which is not absolute. That distinction is important as it impacts the lexical traversal of the path string.

Sources/TSCBasic/Path.swift Show resolved Hide resolved
#else
pathSeparator = "/"
#endif
precondition(path.first != pathSeparator)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still think that the differences are material and need to be addressed. Paths in Windows do not behave like Unix, and so you will have differences. e.g.

path1 = "C:\Users\compnerd"
path2 = "D:"
path1 + path2 is not `C:\Users\compnerd\D:" but rather "C:\Windows\System32\WindowsMetadata"

because the current directory on D: happened to be "....\Windows\System32\WindowsMetadata"

@stevapple stevapple changed the title Fix path normalization and judgment on Windows Improve path normalization and judgment on Windows Mar 6, 2021
@stevapple
Copy link
Author

stevapple commented Mar 6, 2021

If possible, I’d like to completely refactor Path using swift-system after FilePath API is accepted. I don’t see the need to handle those tricky cases repeatedly in separate packages.

@tomerd
Copy link
Contributor

tomerd commented Apr 7, 2021

If possible, I’d like to completely refactor Path using swift-system after FilePath API is accepted. I don’t see the need to handle those tricky cases repeatedly in separate packages.

@stevapple we'd love to see PRs to that end, its definitely the right direction

@stevapple
Copy link
Author

CC @compnerd

Regardless of any future improvements, the bug fixed in this PR is critical for correcting SwiftPM behavior on Swift 5.4 and later. I’d like to see it merged into release/5.4 before the official release.

@stevapple
Copy link
Author

@tomerd Could you merge this or touch @compnerd ? He seems out of track of this PR.
But it’s critical for SwiftPM on Windows in Swift 5.4. We’re running out of time.

@compnerd
Copy link
Member

I think that this change is way too far for the 5.4 release.

I think that it is important to address the issues with the model though.

@stevapple
Copy link
Author

I’d expect System to bring the fully-functional path parsing.

Shipping a SwiftPM which cannot resolve .package(path: "../AnotherPackage") with 5.4 is definitely not what we’d like to see.

@stevapple
Copy link
Author

@ddunbar May you take a look at this PR? :) I believe that's a critical one for the 5.4 release.

Also @tomerd here because this mainly solves a bug in SwiftPM.

@tomerd
Copy link
Contributor

tomerd commented Apr 19, 2021

I’d expect System to bring the fully-functional path parsing.

I propose a phased approach:

First, please work with @compnerd to get this PR to a mergeable state.

Later, we should start landing PRs that refactor TSC to use SwiftSystem.

Shipping a SwiftPM which cannot resolve .package(path: "../AnotherPackage") with 5.4 is definitely not what we’d like to see.

its too late for 5.4.0, but once we merge this PR into main, we should open a PR against the 5.4 branch so that if/when we roll a 5.4.1 dot release we can include it

@stevapple
Copy link
Author

First, please work with @compnerd to get this PR to a mergeable state.

I’m just missing the point to evolve this PR. I believe in its current state it’s making things better (but not perfect yet) without introducing new problems.

@compnerd Can you make a detailed review on the codes?

@stevapple
Copy link
Author

Any future directions? @compnerd

@compnerd
Copy link
Member

I don't think that all the previous comments on the change have been addressed yet.

@stevapple
Copy link
Author

stevapple commented May 11, 2021

The current implementation of TSC does not meet the Windows specifications. More sadly, it will give false outputs even with regular cases like ../anotherPackage (due to the poor PathCanonicalize API).

I would address again that this PR is only meant to correct the behavior for regular and critical cases. This module needs a complete rewrite if you expect it to fully support Windows paths. This is just a capable bug fix — and it's enough to make Swift tools robuster.

@compnerd
Copy link
Member

I'm not sure that this is the right way to handle that. It just behaves differently after this change. That doesn't make it more robust. I agree with you that this is in need of a rewrite - and really, this seems like the ideal case and time to migrate the path handling to Swift System. The 5.5 release branch is already in slushy freeze, so we have plenty of time for the next release to get the implementation migrated and corrected.

@stevapple
Copy link
Author

It just behaves differently after this change.

I would like to know which cases this PR breaks... I think we'd better get a workaround for 5.4, and then get to rewrite it with Swift System for the 5.5 release.

@compnerd
Copy link
Member

I would like to know which cases this PR breaks... I think we'd better get a workaround for 5.4, and then get to rewrite it with Swift System for the 5.5 release.

It already is too late for all of those. Such a large change for 5.4 is entirely out of the question - it is already finalized. I think that such a change for 5.5 is unreasonable. It is too far in the cycle to make that type of change. This would be best slated for the next release for which we have plenty of time to do by means of replacing the path handling with Swift System.

@stevapple
Copy link
Author

It already is too late for all of those. Such a large change for 5.4 is entirely out of the question - it is already finalized.

Any critical bug fix should be allowed. I don’t see this PR introducing new behavior AFAK (as for the result) and the bugs of old implementation are leading to unexpected crashes and extremely strange behavior.

@stevapple
Copy link
Author

stevapple commented May 11, 2021

Why I regard it as critical — Local debugging relies largely on relative paths, and for now we’re not even able to set the path of a dependency package to ../package. This is unacceptable for user experience and will trouble toolchain development on Windows too.

Since we’re able to fix it, why not? Letting the bug alone with 5.4 while there’re still plenty of 5.4.xs to come…?

@stevapple
Copy link
Author

stevapple commented May 27, 2021

import WinSDK

let path = CommandLine.argc > 1 ? CommandLine.arguments[1] : "..\\swift-tools-support-core"
print("user input: " + path)
var buffer: [WCHAR] = Array<WCHAR>(repeating: 0, count: Int(MAX_PATH + 1))
_ = path.withCString(encodedAs: UTF16.self) { PathCanonicalizeW(&buffer, $0) }
print("normalized (current): " + String(decodingCString: buffer, as: UTF16.self))

var parts: [String] = []
var capacity = 0
for part in path.split(separator: "\\") {
    switch part.count {
    case 0: continue
    case 1 where part.first == ".": continue
    case 2 where part.first == "." && part.last == ".":
        guard let prev = parts.last else { fallthrough }
        if !(prev.count == 2 && prev.first == "." && prev.last == ".") {
            parts.removeLast()
            capacity -= prev.count
            continue
        }
        fallthrough
    default:
        parts.append(String(part))
        capacity += part.count
    }
}
capacity += max(parts.count - 1, 0)
var result = ""
result.reserveCapacity(capacity)
var iter = parts.makeIterator()
if let first = iter.next() {
    result.append(contentsOf: first)
    while let next = iter.next() {
        result.append("\\")
        result.append(contentsOf: next)
    }
}
print("normalized (updated): " + (result.isEmpty ? "." : result))

Here is a standalone script to demonstrate the behavioral changes on Windows before and after this patch. I'd treat this as a huge and urgent improvement. The current one is so buggy that it needs to be replaced ASAP on all the active branches.

C:\Users\stevapple>normalize.exe                                 
user input: ..\swift-tools-support-core
normalized (current): swift-tools-support-core
normalized (updated): ..\swift-tools-support-core

C:\Users\stevapple>normalize.exe ..\hello\hello\..\my-package
user input: ..\hello\hello\..\my-package
normalized (current): hello\my-package
normalized (updated): ..\hello\my-package

C:\Users\stevapple>normalize.exe hello\..\..\hello2\..\my-package   
user input: hello\..\..\hello2\..\my-package
normalized (current): \my-package
normalized (updated): ..\my-package

C:\Users\stevapple>normalize.exe .\hello\..\.\hello
user input: .\hello\..\.\hello
normalized (current): \hello
normalized (updated): hello

CC: @compnerd @abertelrud

@compnerd
Copy link
Member

compnerd commented May 27, 2021

Why I regard it as critical — Local debugging relies largely on relative paths, and for now we’re not even able to set the path of a dependency package to ../package. This is unacceptable for user experience and will trouble toolchain development on Windows too.

I strongly disagree with this statement and see this as purely hyperbole, which really does not add much to the discussion. I definitely am using SPM actively both for local and CI purposes, it definitely works fine for local debugging. (I would note that it is not just personal experience, others have reported using SPM on Windows on the Forums, and I've run into other cases on GitHub where packages had started using it).

Here is a standalone script to demonstrate the behavioral changes on Windows before and after this patch. I'd treat this as a huge and urgent improvement. The current one is so buggy that it needs to be replaced ASAP on all the active branches.

That doesn't change the fact that the handling does not work for a variety of paths on Windows. This is a very large change that fixes a very particular case (which would require you to go outside of the source tree, which is really not how SPM normally operates). Thus, this is trying to fix a corner case for a corner case. I think that the efforts are better spent on porting to System.

Alternatively, I would be fine with temporarily unifying the paths with Foundation.

@stevapple
Copy link
Author

stevapple commented Jun 6, 2021

Succeeded by #219

@elsh
Copy link
Contributor

elsh commented Jul 12, 2021

Closing as it's superseded by #219

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.

4 participants