Skip to content

Commit

Permalink
several improvements to Path::getAbsolutePath() (#6542)
Browse files Browse the repository at this point in the history
- fixed result on Linux if parts discarded by `..` do not exist
- small adjustments to Windows implementation to match Linux one
- throw from `getAbsolutePath()` if the given path does not exist
  • Loading branch information
firewave authored Dec 4, 2024
1 parent 1b1fab7 commit 640f82d
Show file tree
Hide file tree
Showing 3 changed files with 121 additions and 1 deletion.
18 changes: 17 additions & 1 deletion lib/path.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -359,16 +359,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");
#else
#error Platform absolute path function needed
#endif
Expand Down Expand Up @@ -412,6 +422,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);

/**
* 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)

0 comments on commit 640f82d

Please sign in to comment.