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

llvm::sys::fs::access can spuriously fail on Windows when called with AccessMode::Exist #83046

Closed
z2oh opened this issue Feb 26, 2024 · 3 comments · Fixed by #83495
Closed

llvm::sys::fs::access can spuriously fail on Windows when called with AccessMode::Exist #83046

z2oh opened this issue Feb 26, 2024 · 3 comments · Fixed by #83495

Comments

@z2oh
Copy link
Contributor

z2oh commented Feb 26, 2024

We are regularly seeing this issue surface as a compiler crash on Apple's LLVM fork: apple#8224

std::error_code access(const Twine &Path, AccessMode Mode) {

I've been unable to reproduce this in isolation, but there seems to be some kind of race condition when calling GetFileAttributesW such that the call fails on a valid path with ERROR_ACCESS_DENIED. I've managed to work around this locally by avoiding the call to GetFileAttributesW when the requested access mode is AccessMode::Exist, and instead calling the shell function PathFileExistsW:

if (Mode == AccessMode::Exist) {
  if (::PathFileExistsW(PathUtf16.begin())) {
    return std::error_code();
  } else {
    return errc::no_such_file_or_directory;
  }
}

I've added some logging here and verified that this function returns correctly when GetFileAttributesW would have failed with ERROR_ACCESS_DENIED. This function requires pulling in shlwapi.h.

I'm happy to make a PR with this patch, but I wanted to make an issue first to seek alternate solutions, or to see if anyone had insight as to why GetFileAttributesW is failing in this way.

@z2oh
Copy link
Contributor Author

z2oh commented Feb 27, 2024

@tristanlabelle managed to reproduce this:

#include <Windows.h>
#include <thread>
#include <cassert>

int main() {
    static LPCWSTR filePath = L"%TEMP%";
    std::thread createFileThread([]()
    {
        while (true)
        {
            HANDLE h = CreateFileW(filePath, GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
            assert(h != NULL);
            Sleep(1000);
            assert(CloseHandle(h));
            Sleep(1000);
            assert(DeleteFileW(filePath));
            Sleep(1000);
        }
    });
    std::thread getFileAttributesThread([]()
    {
        while (true)
        {
            auto attributes = GetFileAttributesW(filePath);
            if (attributes == INVALID_FILE_ATTRIBUTES)
            {
                auto lastError = GetLastError();
                assert(lastError != ERROR_ACCESS_DENIED);
            }
        }
    });

    getFileAttributesThread.join();
}

Which fails after a few seconds on assert(lastError != ERROR_ACCESS_DENIED);. When I tested this, it occurred when the file was deleted, in which the expected error would be ERROR_FILE_NOT_FOUND, thus I don't think that GetFileAttributesW can be reliably used to check for existence of a file.

@aganea
Copy link
Member

aganea commented Feb 29, 2024

The reason this happens is because DeleteFileW does a deferred delete if the handle is already open, and any attempt to query the handle afterwards returns ERROR_ACCESS_DENIED. Most likely GetFileAttributesW opens a handle internally.

compnerd pushed a commit that referenced this issue Mar 27, 2024
…ttributesW` fails (#83495)

Fixes #83046

There is a race condition when calling `GetFileAttributesW` that can
cause it to return `ERROR_ACCESS_DENIED` on a path which exists, which
is unexpected for callers using this function to check for file
existence by passing `AccessMode::Exist`. This was manifesting as a
compiler crash on Windows downstream in the Swift compiler when using
the `-index-store-path` flag (more information in
apple#8224).

I looked for alternate APIs to avoid bringing in `shlwapi.h`, but didn't
see any good candidates. I'm not tied at all to this solution, any
feedback and alternative approaches are more than welcome.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants