mirror of
https://github.com/esphome/esphome.git
synced 2026-09-11 15:27:33 +00:00
[core] Let StringRef carry a null pointer at zero length
The generated api messages start their encode only string fields that way. starts_with, compare and str() no longer hand a null pointer to memcmp or the std::string range constructor when there is nothing to compare or copy, the class comment states the contract, and gtest cases pin every member on a null, empty view.
This commit is contained in:
@@ -22,6 +22,10 @@ namespace esphome {
|
||||
* pointer. When it is default constructed, it has empty string. You can freely copy or move around this struct, but
|
||||
* never free its pointer. str() function can be used to export the content as std::string. StringRef is adopted from
|
||||
* <https://github.com/nghttp2/nghttp2/blob/29cbf8b83ff78faf405d1086b16adc09a8772eca/src/template.h#L376>
|
||||
*
|
||||
* A StringRef may carry a null pointer while its length is zero (the generated api messages start their encode only
|
||||
* string fields that way). Every member treats that as the empty string; only c_str() hands the null pointer on, so
|
||||
* callers that print or copy through c_str() must check empty() first.
|
||||
*/
|
||||
class StringRef {
|
||||
public:
|
||||
@@ -78,7 +82,7 @@ class StringRef {
|
||||
|
||||
/// True if the view begins with the given prefix (std::string::starts_with-like)
|
||||
bool starts_with(const StringRef &prefix) const {
|
||||
return len_ >= prefix.len_ && std::memcmp(base_, prefix.base_, prefix.len_) == 0;
|
||||
return len_ >= prefix.len_ && (prefix.len_ == 0 || std::memcmp(base_, prefix.base_, prefix.len_) == 0);
|
||||
}
|
||||
bool starts_with(const char *prefix) const { return this->starts_with(StringRef(prefix)); }
|
||||
bool starts_with(const std::string &prefix) const { return this->starts_with(StringRef(prefix)); }
|
||||
@@ -92,14 +96,15 @@ class StringRef {
|
||||
return actual;
|
||||
}
|
||||
|
||||
std::string str() const { return std::string(base_, len_); }
|
||||
std::string str() const { return len_ == 0 ? std::string() : std::string(base_, len_); }
|
||||
const uint8_t *byte() const { return reinterpret_cast<const uint8_t *>(base_); }
|
||||
|
||||
operator std::string() const { return str(); }
|
||||
|
||||
/// Compare (compatible with std::string::compare)
|
||||
int compare(const StringRef &other) const {
|
||||
int result = std::memcmp(base_, other.base_, std::min(len_, other.len_));
|
||||
size_type common = std::min(len_, other.len_);
|
||||
int result = common == 0 ? 0 : std::memcmp(base_, other.base_, common);
|
||||
if (result != 0)
|
||||
return result;
|
||||
if (len_ < other.len_)
|
||||
|
||||
@@ -59,4 +59,39 @@ TEST(StringRefStartsWith, RefOverloadComparesOnlyTheViewedLength) {
|
||||
EXPECT_TRUE(ref.starts_with(prefix));
|
||||
}
|
||||
|
||||
// The generated api messages start their encode only string fields as a null pointer with zero
|
||||
// length; every member must treat that exactly like the default constructed empty string.
|
||||
TEST(StringRefNullEmpty, BehavesAsEmptyString) {
|
||||
const StringRef null_empty{nullptr, 0};
|
||||
const StringRef empty;
|
||||
EXPECT_TRUE(null_empty.empty());
|
||||
EXPECT_EQ(null_empty.size(), 0u);
|
||||
EXPECT_EQ(null_empty.c_str(), nullptr);
|
||||
EXPECT_TRUE(null_empty == empty);
|
||||
EXPECT_TRUE(null_empty == "");
|
||||
EXPECT_TRUE(null_empty == std::string());
|
||||
EXPECT_EQ(null_empty.compare(empty), 0);
|
||||
EXPECT_EQ(null_empty.compare(""), 0);
|
||||
EXPECT_LT(null_empty.compare("a"), 0);
|
||||
EXPECT_TRUE(null_empty.starts_with(""));
|
||||
EXPECT_FALSE(null_empty.starts_with("a"));
|
||||
EXPECT_EQ(null_empty.str(), std::string());
|
||||
EXPECT_EQ(null_empty.substr(0), std::string());
|
||||
EXPECT_EQ(null_empty.find('a'), std::string::npos);
|
||||
EXPECT_EQ(null_empty.find("a"), std::string::npos);
|
||||
char buf[4] = "xyz";
|
||||
EXPECT_EQ(null_empty.copy(buf, sizeof(buf)), 0u);
|
||||
EXPECT_EQ(null_empty.begin(), null_empty.end());
|
||||
}
|
||||
|
||||
TEST(StringRefNullEmpty, ComparesAgainstText) {
|
||||
const StringRef null_empty{nullptr, 0};
|
||||
const StringRef text("abc", 3);
|
||||
EXPECT_FALSE(null_empty == text);
|
||||
EXPECT_FALSE(text == null_empty);
|
||||
EXPECT_LT(null_empty.compare(text), 0);
|
||||
EXPECT_GT(text.compare(null_empty), 0);
|
||||
EXPECT_TRUE(text.starts_with(null_empty));
|
||||
}
|
||||
|
||||
} // namespace esphome::core::testing
|
||||
|
||||
Reference in New Issue
Block a user