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

several improvements to Path::getAbsolutePath() #6542

Merged
merged 3 commits into from
Dec 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion lib/path.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -358,16 +358,26 @@ bool Path::isHeader(const std::string &path)

std::string Path::getAbsoluteFilePath(const std::string& filePath)
{
if (filePath.empty())
return "";

std::string absolute_path;
#ifdef _WIN32
char absolute[_MAX_PATH];
if (_fullpath(absolute, filePath.c_str(), _MAX_PATH))
absolute_path = absolute;
if (!absolute_path.empty() && absolute_path.back() == '\\')
absolute_path.pop_back();
#elif defined(__linux__) || defined(__sun) || defined(__hpux) || defined(__GNUC__) || defined(__CPPCHECK__)
char * absolute = realpath(filePath.c_str(), nullptr);
// simplify the path since any non-existent part has to exist even if discarded by ".."
std::string spath = Path::simplifyPath(filePath);
char * absolute = realpath(spath.c_str(), nullptr);
if (absolute)
absolute_path = absolute;
free(absolute);
// only throw on realpath() fialure to resolve a path when the given one was non-existent
if (!spath.empty() && absolute_path.empty() && !exists(spath))
throw std::runtime_error("path '" + filePath + "' does not exist");
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This makes sure we do not get unexpected results. This needs an integration test which triggers this though (I am hoping one of the existing ones will fail so I can derive from that).

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The only case where this could happen is in loadVisualStudioProperties() and that code looks quite shifty and need some proper testing. As Path::isAbsolute() also needs some work that will probably happen in that context.

#else
#error Platform absolute path function needed
#endif
Expand Down Expand Up @@ -411,6 +421,12 @@ bool Path::isDirectory(const std::string &path)
return file_type(path) == S_IFDIR;
}

bool Path::exists(const std::string &path)
{
const auto type = file_type(path);
return type == S_IFREG || type == S_IFDIR;
}

std::string Path::join(const std::string& path1, const std::string& path2) {
if (path1.empty() || path2.empty())
return path1 + path2;
Expand Down
7 changes: 7 additions & 0 deletions lib/path.h
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,13 @@ class CPPCHECKLIB Path {
*/
static bool isDirectory(const std::string &path);

/**
* @brief Checks if a given path exists (i.e. is a file or directory)
* @param path Path to be checked
* @return true if given path exists
*/
static bool exists(const std::string &path);
firewave marked this conversation as resolved.
Show resolved Hide resolved

/**
* join 2 paths with '/' separators
*/
Expand Down
97 changes: 97 additions & 0 deletions test/testpath.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ class TestPath : public TestFixture {
TEST_CASE(identifyWithCppProbe);
TEST_CASE(is_header);
TEST_CASE(simplifyPath);
TEST_CASE(getAbsolutePath);
TEST_CASE(exists);
}

void removeQuotationMarks() const {
Expand Down Expand Up @@ -437,6 +439,101 @@ class TestPath : public TestFixture {
ASSERT_EQUALS("//home/file.cpp", Path::simplifyPath("\\\\home\\test\\..\\file.cpp"));
ASSERT_EQUALS("//file.cpp", Path::simplifyPath("\\\\home\\..\\test\\..\\file.cpp"));
}

void getAbsolutePath() const {
const std::string cwd = Path::getCurrentPath();

ScopedFile file("testabspath.txt", "");
std::string expected = Path::toNativeSeparators(Path::join(cwd, "testabspath.txt"));

ASSERT_EQUALS(expected, Path::getAbsoluteFilePath("testabspath.txt"));
ASSERT_EQUALS(expected, Path::getAbsoluteFilePath("./testabspath.txt"));
ASSERT_EQUALS(expected, Path::getAbsoluteFilePath(".\\testabspath.txt"));
ASSERT_EQUALS(expected, Path::getAbsoluteFilePath("test/../testabspath.txt"));
ASSERT_EQUALS(expected, Path::getAbsoluteFilePath("test\\..\\testabspath.txt"));
ASSERT_EQUALS(expected, Path::getAbsoluteFilePath("./test/../testabspath.txt"));
ASSERT_EQUALS(expected, Path::getAbsoluteFilePath(".\\test\\../testabspath.txt"));

ASSERT_EQUALS(expected, Path::getAbsoluteFilePath(Path::join(cwd, "testabspath.txt")));

std::string cwd_up = Path::getPathFromFilename(cwd);
cwd_up.pop_back(); // remove trailing slash
ASSERT_EQUALS(cwd_up, Path::getAbsoluteFilePath(Path::join(cwd, "..")));
ASSERT_EQUALS(cwd_up, Path::getAbsoluteFilePath(Path::join(cwd, "../")));
ASSERT_EQUALS(cwd_up, Path::getAbsoluteFilePath(Path::join(cwd, "..\\")));
ASSERT_EQUALS(cwd_up, Path::getAbsoluteFilePath(Path::join(cwd, "./../")));
ASSERT_EQUALS(cwd_up, Path::getAbsoluteFilePath(Path::join(cwd, ".\\..\\")));

ASSERT_EQUALS(cwd, Path::getAbsoluteFilePath("."));
#ifndef _WIN32
TODO_ASSERT_EQUALS(cwd, "", Path::getAbsoluteFilePath("./"));
TODO_ASSERT_EQUALS(cwd, "", Path::getAbsoluteFilePath(".\\"));
#else
ASSERT_EQUALS(cwd, Path::getAbsoluteFilePath("./"));
ASSERT_EQUALS(cwd, Path::getAbsoluteFilePath(".\\"));
#endif

ASSERT_EQUALS("", Path::getAbsoluteFilePath(""));

#ifndef _WIN32
// the underlying realpath() call only returns something if the path actually exists
ASSERT_THROW_EQUALS_2(Path::getAbsoluteFilePath("testabspath2.txt"), std::runtime_error, "path 'testabspath2.txt' does not exist");
#else
ASSERT_EQUALS(Path::toNativeSeparators(Path::join(cwd, "testabspath2.txt")), Path::getAbsoluteFilePath("testabspath2.txt"));
#endif

#ifdef _WIN32
// determine an existing drive letter
std::string drive = Path::getCurrentPath().substr(0, 2);
ASSERT_EQUALS(drive + "", Path::getAbsoluteFilePath(drive + "\\"));
ASSERT_EQUALS(drive + "\\path", Path::getAbsoluteFilePath(drive + "\\path"));
ASSERT_EQUALS(drive + "\\path", Path::getAbsoluteFilePath(drive + "\\path\\"));
ASSERT_EQUALS(drive + "\\path\\files.txt", Path::getAbsoluteFilePath(drive + "\\path\\files.txt"));
ASSERT_EQUALS(drive + "", Path::getAbsoluteFilePath(drive + "//"));
ASSERT_EQUALS(drive + "\\path", Path::getAbsoluteFilePath(drive + "//path"));
ASSERT_EQUALS(drive + "\\path", Path::getAbsoluteFilePath(drive + "//path/"));
ASSERT_EQUALS(drive + "\\path\\files.txt", Path::getAbsoluteFilePath(drive + "//path/files.txt"));
ASSERT_EQUALS(drive + "\\path", Path::getAbsoluteFilePath(drive + "\\\\path"));
ASSERT_EQUALS(drive + "", Path::getAbsoluteFilePath(drive + "/"));
ASSERT_EQUALS(drive + "\\path", Path::getAbsoluteFilePath(drive + "/path"));

drive[0] = static_cast<char>(toupper(drive[0]));
ASSERT_EQUALS(drive + "\\path", Path::getAbsoluteFilePath(drive + "\\path"));
ASSERT_EQUALS(drive + "\\path", Path::getAbsoluteFilePath(drive + "/path"));

drive[0] = static_cast<char>(tolower(drive[0]));
ASSERT_EQUALS(drive + "\\path", Path::getAbsoluteFilePath(drive + "\\path"));
ASSERT_EQUALS(drive + "\\path", Path::getAbsoluteFilePath(drive + "/path"));

ASSERT_EQUALS("1:\\path\\files.txt", Path::getAbsoluteFilePath("1:\\path\\files.txt")); // treated as valid drive
ASSERT_EQUALS(
Path::toNativeSeparators(Path::join(Path::getCurrentPath(), "CC:\\path\\files.txt")),
Path::getAbsoluteFilePath("CC:\\path\\files.txt")); // treated as filename
ASSERT_EQUALS("1:\\path\\files.txt", Path::getAbsoluteFilePath("1:/path/files.txt")); // treated as valid drive
ASSERT_EQUALS(
Path::toNativeSeparators(Path::join(Path::getCurrentPath(), "CC:\\path\\files.txt")),
Path::getAbsoluteFilePath("CC:/path/files.txt")); // treated as filename
#endif

#ifndef _WIN32
ASSERT_THROW_EQUALS_2(Path::getAbsoluteFilePath("C:\\path\\files.txt"), std::runtime_error, "path 'C:\\path\\files.txt' does not exist");
#endif

// TODO: test UNC paths
// TODO: test with symlinks
}

void exists() const {
ScopedFile file("testpath.txt", "", "testpath");
ScopedFile file2("testpath2.txt", "");
ASSERT_EQUALS(true, Path::exists("testpath"));
ASSERT_EQUALS(true, Path::exists("testpath/testpath.txt"));
ASSERT_EQUALS(true, Path::exists("testpath2.txt"));

ASSERT_EQUALS(false, Path::exists("testpath2"));
ASSERT_EQUALS(false, Path::exists("testpath/testpath2.txt"));
ASSERT_EQUALS(false, Path::exists("testpath.txt"));
}
};

REGISTER_TEST(TestPath)
Loading