mirror of
https://github.com/google/googletest.git
synced 2026-07-30 16:26:24 +08:00
Add StringView ctor to Matcher<std::string> and Matcher<const std::string&>
Currently, Matcher<StringView>::Matcher(StringView s) and
Matcher<const StringView&>::Matcher(StringView s) exist and explicitly copy
into a std::string so the matcher owns its data:
Matcher<internal::StringView>::Matcher(internal::StringView s) {
*this = Eq(std::string(s));
}
But the symmetric Matcher<const std::string&>::Matcher(StringView) and
Matcher<std::string>::Matcher(StringView) do not exist. That asymmetric gap
silently miscompiles when callers pass a string_view temporary into matchers
whose target type is std::string -- e.g.
Property(&Proto::string_field, std::format("{},{}", subkey, topic))
Property() builds Matcher<const std::string&> from the value. Since no
StringView constructor exists for Matcher<const std::string&>, the
string_view falls through to MatcherCastImpl, which builds a polymorphic
Eq(string_view). EqMatcher<string_view> stores the view by value, and the
matcher outlives the calling expression -- the underlying buffer (the
std::format temporary) is destroyed and the matcher dangles.
Allocator reuse in optimized builds reliably exposes this: subsequent
std::format calls reuse the same heap slot, and all stored matchers end up
pointing into the same recycled buffer with the last-written contents.
This patch adds the symmetric constructors that mirror the existing
Matcher<StringView>(StringView) impl: copy into a std::string via
Eq(std::string(s)) so the matcher owns its data.
Also adds:
- StringMatcherTest.CanBeImplicitlyConstructedFromStringView
- StringMatcherTest.StringViewCtorIsLifetimeSafe (regression test that
exercises the matcher after the source buffer is destroyed/mutated)
Signed-off-by: Jason Cui <jcui@nuro.ai>
This commit is contained in:
parent
d72f9c8aea
commit
5062aa991f
@ -272,6 +272,40 @@ TEST(StringViewMatcherTest, CanBeImplicitlyConstructedFromStringView) {
|
||||
EXPECT_TRUE(m2.Matches("cats"));
|
||||
EXPECT_FALSE(m2.Matches("dogs"));
|
||||
}
|
||||
|
||||
// Tests that a StringView object can be implicitly converted to a
|
||||
// Matcher<std::string> or Matcher<const std::string&>. This is the symmetric
|
||||
// counterpart of CanBeImplicitlyConstructedFromString above; without these
|
||||
// constructors, a StringView falls through to a polymorphic Eq matcher that
|
||||
// stores the view by value and dangles past the constructing call.
|
||||
TEST(StringMatcherTest, CanBeImplicitlyConstructedFromStringView) {
|
||||
Matcher<std::string> m1 = internal::StringView("cats");
|
||||
EXPECT_TRUE(m1.Matches("cats"));
|
||||
EXPECT_FALSE(m1.Matches("dogs"));
|
||||
|
||||
Matcher<const std::string&> m2 = internal::StringView("cats");
|
||||
EXPECT_TRUE(m2.Matches("cats"));
|
||||
EXPECT_FALSE(m2.Matches("dogs"));
|
||||
}
|
||||
|
||||
// Tests that the matcher copies the string data and is safe to use after the
|
||||
// underlying buffer the StringView was constructed from is destroyed.
|
||||
TEST(StringMatcherTest, StringViewCtorIsLifetimeSafe) {
|
||||
Matcher<std::string> m1;
|
||||
Matcher<const std::string&> m2;
|
||||
{
|
||||
std::string buffer = "cats";
|
||||
m1 = internal::StringView(buffer);
|
||||
m2 = internal::StringView(buffer);
|
||||
// Mutate buffer and let it go out of scope to ensure the matcher does not
|
||||
// alias |buffer|'s storage.
|
||||
buffer = "dogs";
|
||||
}
|
||||
EXPECT_TRUE(m1.Matches("cats"));
|
||||
EXPECT_FALSE(m1.Matches("dogs"));
|
||||
EXPECT_TRUE(m2.Matches("cats"));
|
||||
EXPECT_FALSE(m2.Matches("dogs"));
|
||||
}
|
||||
#endif // GTEST_INTERNAL_HAS_STRING_VIEW
|
||||
|
||||
// Tests that a std::reference_wrapper<std::string> object can be implicitly
|
||||
|
||||
@ -520,6 +520,13 @@ Matcher<const std::string&> : public internal::MatcherBase<const std::string&> {
|
||||
|
||||
// Allows the user to write "foo" instead of Eq("foo") sometimes.
|
||||
Matcher(const char* s); // NOLINT
|
||||
|
||||
#if GTEST_INTERNAL_HAS_STRING_VIEW
|
||||
// Allows the user to pass absl::string_views or std::string_views directly.
|
||||
// Copies into a std::string so the matcher owns its data and does not alias
|
||||
// a (possibly temporary) buffer in the caller.
|
||||
Matcher(internal::StringView s); // NOLINT
|
||||
#endif
|
||||
};
|
||||
|
||||
template <>
|
||||
@ -544,6 +551,13 @@ Matcher<std::string> : public internal::MatcherBase<std::string> {
|
||||
|
||||
// Allows the user to write "foo" instead of Eq("foo") sometimes.
|
||||
Matcher(const char* s); // NOLINT
|
||||
|
||||
#if GTEST_INTERNAL_HAS_STRING_VIEW
|
||||
// Allows the user to pass absl::string_views or std::string_views directly.
|
||||
// Copies into a std::string so the matcher owns its data and does not alias
|
||||
// a (possibly temporary) buffer in the caller.
|
||||
Matcher(internal::StringView s); // NOLINT
|
||||
#endif
|
||||
};
|
||||
|
||||
#if GTEST_INTERNAL_HAS_STRING_VIEW
|
||||
|
||||
@ -93,6 +93,18 @@ Matcher<internal::StringView>::Matcher(const char* s) {
|
||||
Matcher<internal::StringView>::Matcher(internal::StringView s) {
|
||||
*this = Eq(std::string(s));
|
||||
}
|
||||
|
||||
// Constructs a matcher that matches a const std::string& whose value is
|
||||
// equal to s. Copies into a std::string so the matcher owns its data.
|
||||
Matcher<const std::string&>::Matcher(internal::StringView s) {
|
||||
*this = Eq(std::string(s));
|
||||
}
|
||||
|
||||
// Constructs a matcher that matches a std::string whose value is equal to s.
|
||||
// Copies into a std::string so the matcher owns its data.
|
||||
Matcher<std::string>::Matcher(internal::StringView s) {
|
||||
*this = Eq(std::string(s));
|
||||
}
|
||||
#endif // GTEST_INTERNAL_HAS_STRING_VIEW
|
||||
|
||||
} // namespace testing
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user