// Copyright 2014 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef CRASHPAD_CLIENT_SIMPLE_STRING_DICTIONARY_H_ #define CRASHPAD_CLIENT_SIMPLE_STRING_DICTIONARY_H_ #include #include #include #include #include "base/check_op.h" #include "base/strings/string_piece.h" #include "util/misc/implicit_cast.h" namespace crashpad { //! \brief A map/dictionary collection implementation using a fixed amount of //! storage, so that it does not perform any dynamic allocations for its //! operations. //! //! The actual map storage (TSimpleStringDictionary::Entry) is guaranteed to be //! POD, so that it can be transmitted over various IPC mechanisms. //! //! The template parameters control the amount of storage used for the key, //! value, and map. The \a KeySize and \a ValueSize are measured in bytes, not //! glyphs, and include space for a trailing `NUL` byte. This gives space for //! `KeySize - 1` and `ValueSize - 1` characters in an entry. \a NumEntries is //! the total number of entries that will fit in the map. template class TSimpleStringDictionary { public: //! \brief Constant and publicly accessible versions of the template //! parameters. //! \{ static const size_t key_size = KeySize; static const size_t value_size = ValueSize; static const size_t num_entries = NumEntries; //! \} //! \brief A single entry in the map. struct Entry { //! \brief The entry’s key. //! //! This string is always `NUL`-terminated. If this is a 0-length //! `NUL`-terminated string, the entry is inactive. char key[KeySize]; //! \brief The entry’s value. //! //! This string is always `NUL`-terminated. char value[ValueSize]; //! \brief Returns the validity of the entry. //! //! If #key is an empty string, the entry is considered inactive, and this //! method returns `false`. Otherwise, returns `true`. bool is_active() const { return key[0] != '\0'; } }; //! \brief An iterator to traverse all of the active entries in a //! TSimpleStringDictionary. class Iterator { public: explicit Iterator(const TSimpleStringDictionary& map) : map_(map), current_(0) { } Iterator(const Iterator&) = delete; Iterator& operator=(const Iterator&) = delete; //! \brief Returns the next entry in the map, or `nullptr` if at the end of //! the collection. const Entry* Next() { while (current_ < map_.num_entries) { const Entry* entry = &map_.entries_[current_++]; if (entry->is_active()) { return entry; } } return nullptr; } private: const TSimpleStringDictionary& map_; size_t current_; }; TSimpleStringDictionary() : entries_() { } TSimpleStringDictionary(const TSimpleStringDictionary& other) { *this = other; } TSimpleStringDictionary& operator=(const TSimpleStringDictionary& other) { memcpy(entries_, other.entries_, sizeof(entries_)); return *this; } //! \brief Returns the number of active key/value pairs. The upper limit for //! this is \a NumEntries. size_t GetCount() const { size_t count = 0; for (size_t i = 0; i < num_entries; ++i) { if (entries_[i].is_active()) { ++count; } } return count; } //! \brief Given \a key, returns its corresponding value. //! //! \param[in] key The key to look up. This must not be `nullptr`, nor an //! empty string. It must not contain embedded `NUL`s. //! //! \return The corresponding value for \a key, or if \a key is not found, //! `nullptr`. const char* GetValueForKey(base::StringPiece key) const { DCHECK(key.data()); DCHECK(key.size()); DCHECK_EQ(key.find('\0', 0), base::StringPiece::npos); if (!key.data() || !key.size()) { return nullptr; } const Entry* entry = GetConstEntryForKey(key); if (!entry) { return nullptr; } return entry->value; } //! \brief Stores \a value into \a key, replacing the existing value if \a key //! is already present. //! //! If \a key is not yet in the map and the map is already full (containing //! \a NumEntries active entries), this operation silently fails. //! //! \param[in] key The key to store. This must not be `nullptr`, nor an empty //! string. It must not contain embedded `NUL`s. //! \param[in] value The value to store. If `nullptr`, \a key is removed from //! the map. Must not contain embedded `NUL`s. void SetKeyValue(base::StringPiece key, base::StringPiece value) { if (!value.data()) { RemoveKey(key); return; } DCHECK(key.data()); DCHECK(key.size()); DCHECK_EQ(key.find('\0', 0), base::StringPiece::npos); if (!key.data() || !key.size()) { return; } // |key| must not be an empty string. DCHECK_NE(key[0], '\0'); if (key[0] == '\0') { return; } // |value| must not contain embedded NULs. DCHECK_EQ(value.find('\0', 0), base::StringPiece::npos); Entry* entry = GetEntryForKey(key); // If it does not yet exist, attempt to insert it. if (!entry) { for (size_t i = 0; i < num_entries; ++i) { if (!entries_[i].is_active()) { entry = &entries_[i]; SetFromStringPiece(key, entry->key, key_size); break; } } } // If the map is out of space, |entry| will be nullptr. if (!entry) { return; } #ifndef NDEBUG // Sanity check that the key only appears once. int count = 0; for (size_t i = 0; i < num_entries; ++i) { if (EntryKeyEquals(key, entries_[i])) { ++count; } } DCHECK_EQ(count, 1); #endif SetFromStringPiece(value, entry->value, value_size); } //! \brief Removes \a key from the map. //! //! If \a key is not found, this is a no-op. //! //! \param[in] key The key of the entry to remove. This must not be `nullptr`, //! nor an empty string. It must not contain embedded `NUL`s. void RemoveKey(base::StringPiece key) { DCHECK(key.data()); DCHECK(key.size()); DCHECK_EQ(key.find('\0', 0), base::StringPiece::npos); if (!key.data() || !key.size()) { return; } Entry* entry = GetEntryForKey(key); if (entry) { entry->key[0] = '\0'; entry->value[0] = '\0'; } DCHECK_EQ(GetEntryForKey(key), implicit_cast(nullptr)); } private: static void SetFromStringPiece(base::StringPiece src, char* dst, size_t dst_size) { size_t copy_len = std::min(dst_size - 1, src.size()); src.copy(dst, copy_len); dst[copy_len] = '\0'; } static bool EntryKeyEquals(base::StringPiece key, const Entry& entry) { if (key.size() >= KeySize) return false; // Test for a NUL terminator and early out if it's absent. if (entry.key[key.size()] != '\0') return false; // As there's a NUL terminator at the right position in the entries // string, strncmp can do the rest. return strncmp(key.data(), entry.key, key.size()) == 0; } const Entry* GetConstEntryForKey(base::StringPiece key) const { for (size_t i = 0; i < num_entries; ++i) { if (EntryKeyEquals(key, entries_[i])) { return &entries_[i]; } } return nullptr; } Entry* GetEntryForKey(base::StringPiece key) { return const_cast(GetConstEntryForKey(key)); } Entry entries_[NumEntries]; }; //! \brief A TSimpleStringDictionary with default template parameters. //! //! For historical reasons this specialized version is available with the same //! size factors as a previous implementation. using SimpleStringDictionary = TSimpleStringDictionary<256, 256, 64>; static_assert(std::is_standard_layout::value, "SimpleStringDictionary must be standard layout"); } // namespace crashpad #endif // CRASHPAD_CLIENT_SIMPLE_STRING_DICTIONARY_H_