diff --git a/Code/Framework/AzCore/AzCore/IO/Path/Path.h b/Code/Framework/AzCore/AzCore/IO/Path/Path.h index 3a0ab4b59a..4e86e5ecce 100644 --- a/Code/Framework/AzCore/AzCore/IO/Path/Path.h +++ b/Code/Framework/AzCore/AzCore/IO/Path/Path.h @@ -277,7 +277,7 @@ namespace AZ::IO //! then their hash values are also equal //! For example : path "a//b" equals "a/b", the //! hash value of "a//b" would also equal the hash value of "a/b" - constexpr size_t hash_value(const PathView& pathToHash) noexcept; + size_t hash_value(const PathView& pathToHash) noexcept; // path.comparison constexpr bool operator==(const PathView& lhs, const PathView& rhs) noexcept; @@ -622,7 +622,7 @@ namespace AZ::IO //! For example : path "a//b" equals "a/b", the //! hash value of "a//b" would also equal the hash value of "a/b" template - constexpr size_t hash_value(const BasicPath& pathToHash); + size_t hash_value(const BasicPath& pathToHash); // path.append template diff --git a/Code/Framework/AzCore/AzCore/IO/Path/Path.inl b/Code/Framework/AzCore/AzCore/IO/Path/Path.inl index f899522916..b2e5051446 100644 --- a/Code/Framework/AzCore/AzCore/IO/Path/Path.inl +++ b/Code/Framework/AzCore/AzCore/IO/Path/Path.inl @@ -1951,7 +1951,7 @@ namespace AZ::IO } template - constexpr size_t hash_value(const BasicPath& pathToHash) + inline size_t hash_value(const BasicPath& pathToHash) { return AZStd::hash>{}(pathToHash); } @@ -2082,13 +2082,28 @@ namespace AZStd template <> struct hash { - constexpr size_t operator()(const AZ::IO::PathView& pathToHash) noexcept + /// Path is using FNV-1a algorithm 64 bit version. + static size_t hash_path(AZStd::string_view pathSegment, const char pathSeparator) + { + size_t hash = 14695981039346656037ULL; + constexpr size_t fnvPrime = 1099511628211ULL; + + for (const char first : pathSegment) + { + hash ^= static_cast((pathSeparator == AZ::IO::PosixPathSeparator) + ? first : tolower(first)); + hash *= fnvPrime; + } + return hash; + } + + size_t operator()(const AZ::IO::PathView& pathToHash) noexcept { auto pathParser = AZ::IO::parser::PathParser::CreateBegin(pathToHash.Native(), pathToHash.m_preferred_separator); size_t hash_value = 0; while (pathParser) { - AZStd::hash_combine(hash_value, AZStd::hash{}(*pathParser)); + AZStd::hash_combine(hash_value, hash_path(*pathParser, pathToHash.m_preferred_separator)); ++pathParser; } return hash_value; @@ -2097,7 +2112,7 @@ namespace AZStd template struct hash> { - constexpr size_t operator()(const AZ::IO::BasicPath& pathToHash) noexcept + const size_t operator()(const AZ::IO::BasicPath& pathToHash) noexcept { return AZStd::hash{}(pathToHash); } @@ -2108,11 +2123,11 @@ namespace AZStd template struct hash; } -// Explicit instantations of our support Path classes +// Explicit instantiations of our support Path classes namespace AZ::IO { // PathView hash - constexpr size_t hash_value(const PathView& pathToHash) noexcept + inline size_t hash_value(const PathView& pathToHash) noexcept { return AZStd::hash{}(pathToHash); } diff --git a/Code/Framework/AzCore/Tests/IO/Path/PathTests.cpp b/Code/Framework/AzCore/Tests/IO/Path/PathTests.cpp index 84136e024c..a81bbe1f44 100644 --- a/Code/Framework/AzCore/Tests/IO/Path/PathTests.cpp +++ b/Code/Framework/AzCore/Tests/IO/Path/PathTests.cpp @@ -182,6 +182,36 @@ namespace UnitTest AZStd::tuple(R"(foO/Bar)", "foo/bar") )); + using PathHashParamFixture = PathParamFixture; + TEST_P(PathHashParamFixture, HashOperator_HashesCaseInsensitiveForWindowsPaths) + { + AZ::IO::Path path1{ AZStd::get<0>(GetParam()), AZ::IO::WindowsPathSeparator }; + AZ::IO::Path path2{ AZStd::get<1>(GetParam()), AZ::IO::WindowsPathSeparator }; + size_t path1Hash = AZStd::hash{}(path1); + size_t path2Hash = AZStd::hash{}(path2); + EXPECT_EQ(path1Hash, path2Hash) << AZStd::string::format(R"(path1 "%s" should hash to path2 "%s"\n)", + path1.c_str(), path2.c_str()).c_str(); + } + + TEST_P(PathHashParamFixture, HashOperator_HashesCaseSensitiveForPosixPaths) + { + AZ::IO::Path path1{ AZStd::get<0>(GetParam()), AZ::IO::PosixPathSeparator }; + AZ::IO::Path path2{ AZStd::get<1>(GetParam()), AZ::IO::PosixPathSeparator }; + size_t path1Hash = AZStd::hash{}(path1); + size_t path2Hash = AZStd::hash{}(path2); + EXPECT_NE(path1Hash, path2Hash) << AZStd::string::format(R"(path1 "%s" should NOT hash to path2 "%s"\n)", + path1.c_str(), path2.c_str()).c_str(); + } + + INSTANTIATE_TEST_CASE_P( + HashPaths, + PathHashParamFixture, + ::testing::Values( + AZStd::tuple("C:/test/foo", R"(c:\test/foo)"), + AZStd::tuple(R"(D:\test/bar/baz//foo)", "d:/test/bar/baz\\\\\\foo"), + AZStd::tuple(R"(foO/Bar)", "foo/bar") + )); + class PathSingleParamFixture : public ScopedAllocatorSetupFixture , public ::testing::WithParamInterface>