Implement serialization for NoVarySearchCache
Add a specialization of net::PickleTraits for net::NoVarySearchCache so that it can be serialized and deserialized to/from a base::Pickle. This will be used to implement persistence. Since we need to be able to serialize base::Time objects for this, add a new header file "net/base/pickle_base_types.h" which adds serialization support for types from //base that we need. This uses the deprecated FromInternalValue()/ToInternalValue() methods to allow migrating existing serialization code in //net to use PickleTraits without changing the serialization format. BUG=399562754,382394774 Change-Id: Ic81083995b7661749fcfd84e83841f5944ec3818 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6341087 Reviewed-by: Kenichi Ishibashi <bashi@chromium.org> Commit-Queue: Adam Rice <ricea@chromium.org> Reviewed-by: Daniel Cheng <dcheng@chromium.org> Cr-Commit-Position: refs/heads/main@{#1435857}
This commit is contained in:

committed by
Chromium LUCI CQ

parent
07d1b87030
commit
94f4065f85
@@ -251,6 +251,7 @@ component("net") {
|
|||||||
"base/parse_number.cc",
|
"base/parse_number.cc",
|
||||||
"base/parse_number.h",
|
"base/parse_number.h",
|
||||||
"base/pickle.h",
|
"base/pickle.h",
|
||||||
|
"base/pickle_base_types.h",
|
||||||
"base/pickle_traits.h",
|
"base/pickle_traits.h",
|
||||||
"base/platform_mime_util.h",
|
"base/platform_mime_util.h",
|
||||||
"base/port_util.cc",
|
"base/port_util.cc",
|
||||||
@@ -2674,6 +2675,7 @@ target(_test_target_type, "net_unittests") {
|
|||||||
"base/network_interfaces_unittest.cc",
|
"base/network_interfaces_unittest.cc",
|
||||||
"base/network_isolation_key_unittest.cc",
|
"base/network_isolation_key_unittest.cc",
|
||||||
"base/parse_number_unittest.cc",
|
"base/parse_number_unittest.cc",
|
||||||
|
"base/pickle_base_types_unittest.cc",
|
||||||
"base/pickle_unittest.cc",
|
"base/pickle_unittest.cc",
|
||||||
"base/port_util_unittest.cc",
|
"base/port_util_unittest.cc",
|
||||||
"base/prioritized_dispatcher_unittest.cc",
|
"base/prioritized_dispatcher_unittest.cc",
|
||||||
|
43
net/base/pickle_base_types.h
Normal file
43
net/base/pickle_base_types.h
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
// Copyright 2025 The Chromium Authors
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
// Serialization and deserialization code for some //base types that are used in
|
||||||
|
// //net. This can't be put in //base because //base can't depend on //net.
|
||||||
|
|
||||||
|
#ifndef NET_BASE_PICKLE_BASE_TYPES_H_
|
||||||
|
#define NET_BASE_PICKLE_BASE_TYPES_H_
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
#include "base/time/time.h"
|
||||||
|
#include "net/base/pickle_traits.h"
|
||||||
|
|
||||||
|
namespace net {
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct PickleTraits<base::Time> {
|
||||||
|
static void Serialize(base::Pickle& pickle, const base::Time& time) {
|
||||||
|
// For compatibility with existing serialization code in //net, use the
|
||||||
|
// deprecated `ToInternalValue()` method.
|
||||||
|
pickle.WriteInt64(time.ToInternalValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::optional<base::Time> Deserialize(base::PickleIterator& iter) {
|
||||||
|
int64_t time_as_int64;
|
||||||
|
if (!iter.ReadInt64(&time_as_int64)) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
return base::Time::FromInternalValue(time_as_int64);
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t PickleSize(const base::Time& time) {
|
||||||
|
return EstimatePickleSize(int64_t{0});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace net
|
||||||
|
|
||||||
|
#endif // NET_BASE_PICKLE_BASE_TYPES_H_
|
38
net/base/pickle_base_types_unittest.cc
Normal file
38
net/base/pickle_base_types_unittest.cc
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
// Copyright 2025 The Chromium Authors
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
#include "net/base/pickle_base_types.h"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
#include "base/pickle.h"
|
||||||
|
#include "base/time/time.h"
|
||||||
|
#include "net/base/pickle.h"
|
||||||
|
#include "testing/gmock/include/gmock/gmock.h"
|
||||||
|
#include "testing/gtest/include/gtest/gtest.h"
|
||||||
|
|
||||||
|
namespace net {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
using ::testing::Optional;
|
||||||
|
|
||||||
|
TEST(PickleBaseTypesTest, Time) {
|
||||||
|
base::Time nov1994;
|
||||||
|
ASSERT_TRUE(
|
||||||
|
base::Time::FromUTCString("Tue, 15 Nov 1994 12:45:26 GMT", &nov1994));
|
||||||
|
static const auto kCases = std::to_array<base::Time>(
|
||||||
|
{base::Time(), base::Time::Max(), base::Time::UnixEpoch(), nov1994});
|
||||||
|
for (base::Time test_case : kCases) {
|
||||||
|
SCOPED_TRACE(test_case);
|
||||||
|
base::Pickle pickle;
|
||||||
|
WriteToPickle(pickle, test_case);
|
||||||
|
EXPECT_EQ(EstimatePickleSize(test_case), pickle.payload_size());
|
||||||
|
EXPECT_THAT(ReadValueFromPickle<base::Time>(pickle), Optional(test_case));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
} // namespace net
|
@@ -4,8 +4,10 @@
|
|||||||
|
|
||||||
#include "net/http/no_vary_search_cache.h"
|
#include "net/http/no_vary_search_cache.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <compare>
|
#include <compare>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <limits>
|
||||||
#include <tuple>
|
#include <tuple>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
@@ -15,7 +17,10 @@
|
|||||||
#include "base/memory/weak_ptr.h"
|
#include "base/memory/weak_ptr.h"
|
||||||
#include "base/metrics/histogram_macros.h"
|
#include "base/metrics/histogram_macros.h"
|
||||||
#include "base/notreached.h"
|
#include "base/notreached.h"
|
||||||
|
#include "base/numerics/safe_conversions.h"
|
||||||
#include "base/time/time.h"
|
#include "base/time/time.h"
|
||||||
|
#include "net/base/pickle.h"
|
||||||
|
#include "net/base/pickle_base_types.h"
|
||||||
#include "net/http/http_cache.h"
|
#include "net/http/http_cache.h"
|
||||||
#include "net/http/http_no_vary_search_data.h"
|
#include "net/http/http_no_vary_search_data.h"
|
||||||
|
|
||||||
@@ -184,7 +189,13 @@ class NoVarySearchCache::QueryString final
|
|||||||
|
|
||||||
// Removes this object from both lists and deletes it.
|
// Removes this object from both lists and deletes it.
|
||||||
void RemoveAndDelete() {
|
void RemoveAndDelete() {
|
||||||
LruNode::RemoveFromList();
|
// During deserialization a QueryString is not inserted into the `lru_` list
|
||||||
|
// until the end. If deserialization fails before then, it can be deleted
|
||||||
|
// without ever being inserted into the `lru_` list.
|
||||||
|
if (LruNode::next()) {
|
||||||
|
CHECK(LruNode::previous());
|
||||||
|
LruNode::RemoveFromList();
|
||||||
|
}
|
||||||
QueryStringListNode::RemoveFromList();
|
QueryStringListNode::RemoveFromList();
|
||||||
delete this;
|
delete this;
|
||||||
}
|
}
|
||||||
@@ -200,9 +211,7 @@ class NoVarySearchCache::QueryString final
|
|||||||
|
|
||||||
const std::optional<std::string>& query() const { return query_; }
|
const std::optional<std::string>& query() const { return query_; }
|
||||||
|
|
||||||
QueryStringList& query_string_list_ref() {
|
QueryStringList& query_string_list_ref() { return *query_string_list_ref_; }
|
||||||
return query_string_list_ref_.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
base::Time insertion_time() const { return insertion_time_; }
|
base::Time insertion_time() const { return insertion_time_; }
|
||||||
|
|
||||||
@@ -225,12 +234,21 @@ class NoVarySearchCache::QueryString final
|
|||||||
return EraseHandle(weak_factory_.GetWeakPtr());
|
return EraseHandle(weak_factory_.GetWeakPtr());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void set_query_string_list_ref(
|
||||||
|
base::PassKey<NoVarySearchCache::QueryStringList>,
|
||||||
|
QueryStringList* query_string_list) {
|
||||||
|
query_string_list_ref_ = query_string_list;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
friend struct PickleTraits<NoVarySearchCache::QueryStringList>;
|
||||||
|
|
||||||
QueryString(std::optional<std::string_view> query,
|
QueryString(std::optional<std::string_view> query,
|
||||||
QueryStringList& query_string_list)
|
QueryStringList& query_string_list,
|
||||||
|
base::Time insertion_time = base::Time::Now())
|
||||||
: query_(query),
|
: query_(query),
|
||||||
query_string_list_ref_(query_string_list),
|
query_string_list_ref_(&query_string_list),
|
||||||
insertion_time_(base::Time::Now()) {}
|
insertion_time_(insertion_time) {}
|
||||||
|
|
||||||
// Must only be called from RemoveAndDelete().
|
// Must only be called from RemoveAndDelete().
|
||||||
~QueryString() = default;
|
~QueryString() = default;
|
||||||
@@ -254,8 +272,9 @@ class NoVarySearchCache::QueryString final
|
|||||||
const std::optional<std::string> query_;
|
const std::optional<std::string> query_;
|
||||||
|
|
||||||
// `query_string_list_ref_` allows the keys for this entry to be located in
|
// `query_string_list_ref_` allows the keys for this entry to be located in
|
||||||
// the cache so that it can be erased efficiently.
|
// the cache so that it can be erased efficiently. It is modified when a
|
||||||
const raw_ref<QueryStringList> query_string_list_ref_;
|
// QueryStringList object is moved.
|
||||||
|
raw_ptr<QueryStringList> query_string_list_ref_ = nullptr;
|
||||||
|
|
||||||
// `insertion_time_` breaks ties when there are multiple possible matches. The
|
// `insertion_time_` breaks ties when there are multiple possible matches. The
|
||||||
// most recent entry will be used as it is most likely to still exist in the
|
// most recent entry will be used as it is most likely to still exist in the
|
||||||
@@ -300,9 +319,17 @@ bool NoVarySearchCache::EraseHandle::IsGoneForTesting() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
NoVarySearchCache::NoVarySearchCache(size_t max_size) : max_size_(max_size) {
|
NoVarySearchCache::NoVarySearchCache(size_t max_size) : max_size_(max_size) {
|
||||||
CHECK_GT(max_size_, 0u);
|
CHECK_GE(max_size_, 1u);
|
||||||
|
// We can't serialize if `max_size` won't fit in an int.
|
||||||
|
CHECK(base::IsValueInRangeForNumericType<int>(max_size));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NoVarySearchCache::NoVarySearchCache(NoVarySearchCache&& rhs)
|
||||||
|
: map_(std::move(rhs.map_)),
|
||||||
|
lru_(std::move(rhs.lru_)),
|
||||||
|
size_(std::exchange(rhs.size_, 0u)),
|
||||||
|
max_size_(rhs.max_size_) {}
|
||||||
|
|
||||||
NoVarySearchCache::~NoVarySearchCache() {
|
NoVarySearchCache::~NoVarySearchCache() {
|
||||||
map_.clear();
|
map_.clear();
|
||||||
// Clearing the map should have freed all the QueryString objects.
|
// Clearing the map should have freed all the QueryString objects.
|
||||||
@@ -423,7 +450,7 @@ bool NoVarySearchCache::ClearData(UrlFilterType filter_type,
|
|||||||
// then erase them.
|
// then erase them.
|
||||||
// TODO(https://crbug.com/382394774): Make this algorithm more efficient.
|
// TODO(https://crbug.com/382394774): Make this algorithm more efficient.
|
||||||
std::vector<QueryString*> pending_erase;
|
std::vector<QueryString*> pending_erase;
|
||||||
for (const auto& [cache_key, data_map] : map_) {
|
for (auto& [cache_key, data_map] : map_) {
|
||||||
const std::string base_url_string =
|
const std::string base_url_string =
|
||||||
HttpCache::GetResourceURLFromHttpCacheKey(cache_key.value());
|
HttpCache::GetResourceURLFromHttpCacheKey(cache_key.value());
|
||||||
const GURL base_url(base_url_string);
|
const GURL base_url(base_url_string);
|
||||||
@@ -458,9 +485,27 @@ bool NoVarySearchCache::IsTopLevelMapEmptyForTesting() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
NoVarySearchCache::QueryStringList::QueryStringList(const BaseURLCacheKey& key)
|
NoVarySearchCache::QueryStringList::QueryStringList(const BaseURLCacheKey& key)
|
||||||
: key_ref(key) {}
|
: key_ref(&key) {}
|
||||||
|
|
||||||
|
NoVarySearchCache::QueryStringList::QueryStringList() = default;
|
||||||
|
|
||||||
|
NoVarySearchCache::QueryStringList::QueryStringList(QueryStringList&& rhs)
|
||||||
|
: list(std::move(rhs.list)) {
|
||||||
|
// We should not move a list after the key references have been assigned.
|
||||||
|
CHECK(!rhs.nvs_data_ref);
|
||||||
|
CHECK(!rhs.key_ref);
|
||||||
|
// We have to patch up all the references to `rhs` in our QueryString objects
|
||||||
|
// to point to us instead.
|
||||||
|
ForEachQueryString(list, [&](QueryString* query_string) {
|
||||||
|
query_string->set_query_string_list_ref(base::PassKey<QueryStringList>(),
|
||||||
|
this);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
NoVarySearchCache::QueryStringList::~QueryStringList() {
|
NoVarySearchCache::QueryStringList::~QueryStringList() {
|
||||||
|
// The `list.head()` check works around the unfortunate fact that moving from
|
||||||
|
// a base::LinkedList leaves it in an invalid state where `list.empty()` is
|
||||||
|
// false.
|
||||||
while (!list.empty()) {
|
while (!list.empty()) {
|
||||||
list.head()->value()->ToQueryString()->RemoveAndDelete();
|
list.head()->value()->ToQueryString()->RemoveAndDelete();
|
||||||
}
|
}
|
||||||
@@ -481,11 +526,11 @@ void NoVarySearchCache::EraseQuery(QueryString* query_string) {
|
|||||||
const QueryStringList& query_strings = query_string->query_string_list_ref();
|
const QueryStringList& query_strings = query_string->query_string_list_ref();
|
||||||
query_string->RemoveAndDelete();
|
query_string->RemoveAndDelete();
|
||||||
if (query_strings.list.empty()) {
|
if (query_strings.list.empty()) {
|
||||||
const HttpNoVarySearchData* nvs_data_ref = query_strings.nvs_data_ref.get();
|
const HttpNoVarySearchData& nvs_data_ref = *query_strings.nvs_data_ref;
|
||||||
const BaseURLCacheKey& key_ref = query_strings.key_ref.get();
|
const BaseURLCacheKey& key_ref = *query_strings.key_ref;
|
||||||
const auto map_it = map_.find(key_ref);
|
const auto map_it = map_.find(key_ref);
|
||||||
CHECK(map_it != map_.end());
|
CHECK(map_it != map_.end());
|
||||||
const size_t removed_count = map_it->second.erase(*nvs_data_ref);
|
const size_t removed_count = map_it->second.erase(nvs_data_ref);
|
||||||
CHECK_EQ(removed_count, 1u);
|
CHECK_EQ(removed_count, 1u);
|
||||||
if (map_it->second.empty()) {
|
if (map_it->second.empty()) {
|
||||||
map_.erase(map_it);
|
map_.erase(map_it);
|
||||||
@@ -495,20 +540,18 @@ void NoVarySearchCache::EraseQuery(QueryString* query_string) {
|
|||||||
|
|
||||||
// static
|
// static
|
||||||
void NoVarySearchCache::FindQueryStringsInTimeRange(
|
void NoVarySearchCache::FindQueryStringsInTimeRange(
|
||||||
const DataMapType& data_map,
|
DataMapType& data_map,
|
||||||
base::Time delete_begin,
|
base::Time delete_begin,
|
||||||
base::Time delete_end,
|
base::Time delete_end,
|
||||||
std::vector<QueryString*>& matches) {
|
std::vector<QueryString*>& matches) {
|
||||||
for (const auto& [_, query_string_list] : data_map) {
|
for (auto& [_, query_string_list] : data_map) {
|
||||||
for (auto* node = query_string_list.list.head();
|
ForEachQueryString(query_string_list.list, [&](QueryString* query_string) {
|
||||||
node != query_string_list.list.end(); node = node->next()) {
|
|
||||||
QueryString* query_string = node->value()->ToQueryString();
|
|
||||||
const base::Time insertion_time = query_string->insertion_time();
|
const base::Time insertion_time = query_string->insertion_time();
|
||||||
if ((delete_begin.is_null() || delete_begin <= insertion_time) &&
|
if ((delete_begin.is_null() || delete_begin <= insertion_time) &&
|
||||||
(delete_end.is_max() || delete_end > insertion_time)) {
|
(delete_end.is_max() || delete_end > insertion_time)) {
|
||||||
matches.push_back(query_string);
|
matches.push_back(query_string);
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -531,4 +574,189 @@ NoVarySearchCache::FindQueryStringInList(QueryStringList& query_strings,
|
|||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// static
|
||||||
|
void NoVarySearchCache::ForEachQueryString(
|
||||||
|
base::LinkedList<QueryStringListNode>& list,
|
||||||
|
base::FunctionRef<void(QueryString*)> f) {
|
||||||
|
for (auto* node = list.head(); node != list.end(); node = node->next()) {
|
||||||
|
QueryString* query_string = node->value()->ToQueryString();
|
||||||
|
f(query_string);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// static
|
||||||
|
void NoVarySearchCache::ForEachQueryString(
|
||||||
|
const base::LinkedList<QueryStringListNode>& list,
|
||||||
|
base::FunctionRef<void(const QueryString*)> f) {
|
||||||
|
for (auto* node = list.head(); node != list.end(); node = node->next()) {
|
||||||
|
const QueryString* query_string = node->value()->ToQueryString();
|
||||||
|
f(query_string);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct PickleTraits<NoVarySearchCache::QueryStringList> {
|
||||||
|
static void Serialize(
|
||||||
|
base::Pickle& pickle,
|
||||||
|
const NoVarySearchCache::QueryStringList& query_strings) {
|
||||||
|
// base::LinkedList doesn't keep an element count, so we need to count them
|
||||||
|
// ourselves.
|
||||||
|
size_t size = 0u;
|
||||||
|
for (auto* node = query_strings.list.head();
|
||||||
|
node != query_strings.list.end(); node = node->next()) {
|
||||||
|
++size;
|
||||||
|
}
|
||||||
|
WriteToPickle(pickle, base::checked_cast<int>(size));
|
||||||
|
NoVarySearchCache::ForEachQueryString(
|
||||||
|
query_strings.list,
|
||||||
|
[&](const NoVarySearchCache::QueryString* query_string) {
|
||||||
|
WriteToPickle(pickle, query_string->query_,
|
||||||
|
query_string->insertion_time_);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::optional<NoVarySearchCache::QueryStringList> Deserialize(
|
||||||
|
base::PickleIterator& iter) {
|
||||||
|
NoVarySearchCache::QueryStringList query_string_list;
|
||||||
|
size_t size = 0;
|
||||||
|
if (!iter.ReadLength(&size)) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
for (size_t i = 0; i < size; ++i) {
|
||||||
|
// QueryString is not movable or copyable, so it won't work well with
|
||||||
|
// PickleTraits. Deserialize it inline instead.
|
||||||
|
auto result =
|
||||||
|
ReadValuesFromPickle<std::optional<std::string>, base::Time>(iter);
|
||||||
|
if (!result) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
auto [query, insertion_time] = std::move(result).value();
|
||||||
|
if (query && query->find('#') != std::string_view::npos) {
|
||||||
|
// A '#' character must not appear in the query.
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
auto* query_string = new NoVarySearchCache::QueryString(
|
||||||
|
std::move(query), query_string_list, insertion_time);
|
||||||
|
// Serialization happens from head to tail, so to deserialize in the same
|
||||||
|
// order, we add elements at the tail of the list.
|
||||||
|
query_string_list.list.Append(query_string);
|
||||||
|
}
|
||||||
|
return query_string_list;
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t PickleSize(
|
||||||
|
const NoVarySearchCache::QueryStringList& query_strings) {
|
||||||
|
size_t estimate = EstimatePickleSize(int{});
|
||||||
|
NoVarySearchCache::ForEachQueryString(
|
||||||
|
query_strings.list,
|
||||||
|
[&](const NoVarySearchCache::QueryString* query_string) {
|
||||||
|
estimate += EstimatePickleSize(query_string->query_,
|
||||||
|
query_string->insertion_time_);
|
||||||
|
});
|
||||||
|
return estimate;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct PickleTraits<NoVarySearchCache::BaseURLCacheKey> {
|
||||||
|
static void Serialize(base::Pickle& pickle,
|
||||||
|
const NoVarySearchCache::BaseURLCacheKey& key) {
|
||||||
|
WriteToPickle(pickle, *key);
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::optional<NoVarySearchCache::BaseURLCacheKey> Deserialize(
|
||||||
|
base::PickleIterator& iter) {
|
||||||
|
NoVarySearchCache::BaseURLCacheKey key;
|
||||||
|
if (!ReadPickleInto(iter, *key)) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t PickleSize(const NoVarySearchCache::BaseURLCacheKey& key) {
|
||||||
|
return EstimatePickleSize(*key);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// static
|
||||||
|
void PickleTraits<NoVarySearchCache>::Serialize(
|
||||||
|
base::Pickle& pickle,
|
||||||
|
const NoVarySearchCache& cache) {
|
||||||
|
// `size_t` is different sizes on 32-bit and 64-bit platforms. For a
|
||||||
|
// consistent format, serialize as int. This will crash if someone creates a
|
||||||
|
// NoVarySearchCache which supports over 2 billion entries, which would be a
|
||||||
|
// terrible idea anyway.
|
||||||
|
int max_size_as_int = base::checked_cast<int>(cache.max_size_);
|
||||||
|
int size_as_int = base::checked_cast<int>(cache.size_);
|
||||||
|
|
||||||
|
// `lru_` is reconstructed during deserialization and so doesn't need to be
|
||||||
|
// stored explicitly.
|
||||||
|
WriteToPickle(pickle, size_as_int, max_size_as_int, cache.map_);
|
||||||
|
}
|
||||||
|
|
||||||
|
// static
|
||||||
|
std::optional<NoVarySearchCache> PickleTraits<NoVarySearchCache>::Deserialize(
|
||||||
|
base::PickleIterator& iter) {
|
||||||
|
const std::optional<int> maybe_size = ReadValueFromPickle<int>(iter);
|
||||||
|
if (!maybe_size || *maybe_size < 0) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
const size_t size = static_cast<size_t>(*maybe_size);
|
||||||
|
const std::optional<int> maybe_max_size = ReadValueFromPickle<int>(iter);
|
||||||
|
if (!maybe_max_size || *maybe_max_size < 1) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
const size_t max_size = static_cast<size_t>(*maybe_max_size);
|
||||||
|
|
||||||
|
if (size > max_size) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
NoVarySearchCache cache(max_size);
|
||||||
|
cache.size_ = size;
|
||||||
|
if (!ReadPickleInto(iter, cache.map_)) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
using QueryString = NoVarySearchCache::QueryString;
|
||||||
|
// Get a list of every QueryString object in the map so that we can sort
|
||||||
|
// them to reconstruct the `lru_` list.
|
||||||
|
std::vector<QueryString*> all_query_strings;
|
||||||
|
all_query_strings.reserve(size);
|
||||||
|
for (auto& [base_url_cache_key, data_map] : cache.map_) {
|
||||||
|
for (auto& [nvs_data, query_string_list] : data_map) {
|
||||||
|
query_string_list.nvs_data_ref = &nvs_data;
|
||||||
|
query_string_list.key_ref = &base_url_cache_key;
|
||||||
|
NoVarySearchCache::ForEachQueryString(
|
||||||
|
query_string_list.list, [&](QueryString* query_string) {
|
||||||
|
all_query_strings.push_back(query_string);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (size != all_query_strings.size()) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort by `insertion_time`, which we use as an approximation of `use_time`
|
||||||
|
// during deserialization on the assumption that it won't make much
|
||||||
|
// difference.
|
||||||
|
std::ranges::sort(all_query_strings, std::less<base::Time>(),
|
||||||
|
[](QueryString* qs) { return qs->insertion_time(); });
|
||||||
|
|
||||||
|
// Insert each entry at the head of the list, so that the oldest entry ends
|
||||||
|
// up at the tail.
|
||||||
|
for (QueryString* qs : all_query_strings) {
|
||||||
|
qs->LruNode::InsertBefore(cache.lru_.head());
|
||||||
|
}
|
||||||
|
|
||||||
|
return cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
// static
|
||||||
|
size_t PickleTraits<NoVarySearchCache>::PickleSize(
|
||||||
|
const NoVarySearchCache& cache) {
|
||||||
|
// `size_` and `max_size_` are pickled as ints.
|
||||||
|
return EstimatePickleSize(int{}, int{}, cache.map_);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace net
|
} // namespace net
|
||||||
|
@@ -17,6 +17,7 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "base/containers/linked_list.h"
|
#include "base/containers/linked_list.h"
|
||||||
|
#include "base/functional/function_ref.h"
|
||||||
#include "base/memory/raw_ptr.h"
|
#include "base/memory/raw_ptr.h"
|
||||||
#include "base/memory/raw_ref.h"
|
#include "base/memory/raw_ref.h"
|
||||||
#include "base/memory/stack_allocated.h"
|
#include "base/memory/stack_allocated.h"
|
||||||
@@ -24,6 +25,7 @@
|
|||||||
#include "base/types/strong_alias.h"
|
#include "base/types/strong_alias.h"
|
||||||
#include "net/base/does_url_match_filter.h"
|
#include "net/base/does_url_match_filter.h"
|
||||||
#include "net/base/net_export.h"
|
#include "net/base/net_export.h"
|
||||||
|
#include "net/base/pickle_traits.h"
|
||||||
#include "net/http/http_no_vary_search_data.h"
|
#include "net/http/http_no_vary_search_data.h"
|
||||||
#include "net/http/http_request_info.h"
|
#include "net/http/http_request_info.h"
|
||||||
#include "url/gurl.h"
|
#include "url/gurl.h"
|
||||||
@@ -90,9 +92,13 @@ class NET_EXPORT_PRIVATE NoVarySearchCache {
|
|||||||
// bytes.
|
// bytes.
|
||||||
explicit NoVarySearchCache(size_t max_size);
|
explicit NoVarySearchCache(size_t max_size);
|
||||||
|
|
||||||
// Not copyable, assignable or movable.
|
// Move-constructible to permit deserialization and passing between threads.
|
||||||
|
NoVarySearchCache(NoVarySearchCache&&);
|
||||||
|
|
||||||
|
// Not copyable or assignable.
|
||||||
NoVarySearchCache(const NoVarySearchCache&) = delete;
|
NoVarySearchCache(const NoVarySearchCache&) = delete;
|
||||||
NoVarySearchCache& operator=(const NoVarySearchCache&) = delete;
|
NoVarySearchCache& operator=(const NoVarySearchCache&) = delete;
|
||||||
|
NoVarySearchCache& operator=(NoVarySearchCache&&) = delete;
|
||||||
|
|
||||||
~NoVarySearchCache();
|
~NoVarySearchCache();
|
||||||
|
|
||||||
@@ -137,23 +143,39 @@ class NET_EXPORT_PRIVATE NoVarySearchCache {
|
|||||||
bool IsTopLevelMapEmptyForTesting() const;
|
bool IsTopLevelMapEmptyForTesting() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
class LruNode;
|
friend struct PickleTraits<NoVarySearchCache>;
|
||||||
class QueryStringListNode;
|
|
||||||
|
struct QueryStringList;
|
||||||
|
friend struct PickleTraits<NoVarySearchCache::QueryStringList>;
|
||||||
|
|
||||||
using BaseURLCacheKey =
|
using BaseURLCacheKey =
|
||||||
base::StrongAlias<struct BaseURLCacheKeyTagType, std::string>;
|
base::StrongAlias<struct BaseURLCacheKeyTagType, std::string>;
|
||||||
|
friend struct PickleTraits<NoVarySearchCache::BaseURLCacheKey>;
|
||||||
|
|
||||||
|
class LruNode;
|
||||||
|
class QueryStringListNode;
|
||||||
|
|
||||||
struct QueryStringList {
|
struct QueryStringList {
|
||||||
base::LinkedList<QueryStringListNode> list;
|
base::LinkedList<QueryStringListNode> list;
|
||||||
// nvs_data_ref can't be raw_ref because it needs to be lazily initialized
|
// nvs_data_ref can't be raw_ref because it needs to be lazily initialized
|
||||||
// after the QueryStringList has been added to the map.
|
// after the QueryStringList has been added to the map.
|
||||||
raw_ptr<const HttpNoVarySearchData> nvs_data_ref;
|
raw_ptr<const HttpNoVarySearchData> nvs_data_ref = nullptr;
|
||||||
raw_ref<const BaseURLCacheKey> key_ref;
|
|
||||||
|
// key_ref can't be raw_ref because it needs to be added in a second pass
|
||||||
|
// during deserialization.
|
||||||
|
raw_ptr<const BaseURLCacheKey> key_ref = nullptr;
|
||||||
|
|
||||||
// The referent of this reference has to be the actual key in the map. It is
|
// The referent of this reference has to be the actual key in the map. It is
|
||||||
// not sufficient for the value to match, because the lifetime has to be the
|
// not sufficient for the value to match, because the lifetime has to be the
|
||||||
// same.
|
// same.
|
||||||
explicit QueryStringList(const BaseURLCacheKey& key);
|
explicit QueryStringList(const BaseURLCacheKey& key);
|
||||||
|
|
||||||
|
// Needed during deserialization.
|
||||||
|
QueryStringList();
|
||||||
|
|
||||||
|
// Only used during deserialization. This is O(N) in the size of `list`.
|
||||||
|
QueryStringList(QueryStringList&&);
|
||||||
|
|
||||||
// base::LinkedList<> does not do memory management, so make sure the
|
// base::LinkedList<> does not do memory management, so make sure the
|
||||||
// contents of `list` are deleted on destruction.
|
// contents of `list` are deleted on destruction.
|
||||||
~QueryStringList();
|
~QueryStringList();
|
||||||
@@ -178,8 +200,11 @@ class NET_EXPORT_PRIVATE NoVarySearchCache {
|
|||||||
void EraseQuery(QueryString* query_string);
|
void EraseQuery(QueryString* query_string);
|
||||||
|
|
||||||
// Scans all the QueryStrings in `data_map` to find ones in the range
|
// Scans all the QueryStrings in `data_map` to find ones in the range
|
||||||
// [delete_begin, delete_end) and appends them to `matches`.
|
// [delete_begin, delete_end) and appends them to `matches`. `data_map` is
|
||||||
static void FindQueryStringsInTimeRange(const DataMapType& data_map,
|
// mutable to reflect that it is returning mutable pointers to QueryString
|
||||||
|
// objects that it owns. The returned QueryString objects are mutable so the
|
||||||
|
// caller can erase them.
|
||||||
|
static void FindQueryStringsInTimeRange(DataMapType& data_map,
|
||||||
base::Time delete_begin,
|
base::Time delete_begin,
|
||||||
base::Time delete_end,
|
base::Time delete_end,
|
||||||
std::vector<QueryString*>& matches);
|
std::vector<QueryString*>& matches);
|
||||||
@@ -190,6 +215,15 @@ class NET_EXPORT_PRIVATE NoVarySearchCache {
|
|||||||
const GURL& url,
|
const GURL& url,
|
||||||
const HttpNoVarySearchData& nvs_data);
|
const HttpNoVarySearchData& nvs_data);
|
||||||
|
|
||||||
|
// Calls f(query_string_ptr) for every QueryString in `list`.
|
||||||
|
static void ForEachQueryString(base::LinkedList<QueryStringListNode>& list,
|
||||||
|
base::FunctionRef<void(QueryString*)> f);
|
||||||
|
|
||||||
|
// Calls f(const_query_string_ptr) for every QueryString in `list`.
|
||||||
|
static void ForEachQueryString(
|
||||||
|
const base::LinkedList<QueryStringListNode>& list,
|
||||||
|
base::FunctionRef<void(const QueryString*)> f);
|
||||||
|
|
||||||
// The main cache data structure.
|
// The main cache data structure.
|
||||||
OuterMapType map_;
|
OuterMapType map_;
|
||||||
|
|
||||||
@@ -203,6 +237,16 @@ class NET_EXPORT_PRIVATE NoVarySearchCache {
|
|||||||
const size_t max_size_;
|
const size_t max_size_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct NET_EXPORT_PRIVATE PickleTraits<NoVarySearchCache> {
|
||||||
|
static void Serialize(base::Pickle& pickle, const NoVarySearchCache& cache);
|
||||||
|
|
||||||
|
static std::optional<NoVarySearchCache> Deserialize(
|
||||||
|
base::PickleIterator& iter);
|
||||||
|
|
||||||
|
static size_t PickleSize(const NoVarySearchCache& cache);
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace net
|
} // namespace net
|
||||||
|
|
||||||
#endif // NET_HTTP_NO_VARY_SEARCH_CACHE_H_
|
#endif // NET_HTTP_NO_VARY_SEARCH_CACHE_H_
|
||||||
|
@@ -10,6 +10,7 @@
|
|||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
|
#include "base/pickle.h"
|
||||||
#include "base/strings/strcat.h"
|
#include "base/strings/strcat.h"
|
||||||
#include "base/test/scoped_feature_list.h"
|
#include "base/test/scoped_feature_list.h"
|
||||||
#include "base/test/task_environment.h"
|
#include "base/test/task_environment.h"
|
||||||
@@ -18,6 +19,8 @@
|
|||||||
#include "net/base/features.h"
|
#include "net/base/features.h"
|
||||||
#include "net/base/load_flags.h"
|
#include "net/base/load_flags.h"
|
||||||
#include "net/base/network_isolation_key.h"
|
#include "net/base/network_isolation_key.h"
|
||||||
|
#include "net/base/pickle.h"
|
||||||
|
#include "net/base/pickle_traits.h"
|
||||||
#include "net/base/schemeful_site.h"
|
#include "net/base/schemeful_site.h"
|
||||||
#include "net/http/http_cache.h"
|
#include "net/http/http_cache.h"
|
||||||
#include "net/http/http_response_headers.h"
|
#include "net/http/http_response_headers.h"
|
||||||
@@ -171,6 +174,19 @@ TEST_P(NoVarySearchCacheTest, InsertLookupErase) {
|
|||||||
EXPECT_TRUE(cache().IsTopLevelMapEmptyForTesting());
|
EXPECT_TRUE(cache().IsTopLevelMapEmptyForTesting());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_P(NoVarySearchCacheTest, MoveConstruct) {
|
||||||
|
Insert("a=b", "key-order");
|
||||||
|
|
||||||
|
NoVarySearchCache new_cache = std::move(cache());
|
||||||
|
|
||||||
|
EXPECT_TRUE(new_cache.Lookup(TestRequest("a=b")));
|
||||||
|
|
||||||
|
// NOLINTNEXTLINE(bugprone-use-after-move)
|
||||||
|
EXPECT_EQ(cache().GetSizeForTesting(), 0u);
|
||||||
|
// NOLINTNEXTLINE(bugprone-use-after-move)
|
||||||
|
EXPECT_TRUE(cache().IsTopLevelMapEmptyForTesting());
|
||||||
|
}
|
||||||
|
|
||||||
// An asan build will find leaks, but this test works on any build.
|
// An asan build will find leaks, but this test works on any build.
|
||||||
TEST_P(NoVarySearchCacheTest, QueryNotLeaked) {
|
TEST_P(NoVarySearchCacheTest, QueryNotLeaked) {
|
||||||
std::optional<NoVarySearchCache::LookupResult> result;
|
std::optional<NoVarySearchCache::LookupResult> result;
|
||||||
@@ -185,10 +201,16 @@ TEST_P(NoVarySearchCacheTest, QueryNotLeaked) {
|
|||||||
EXPECT_TRUE(result->erase_handle.IsGoneForTesting());
|
EXPECT_TRUE(result->erase_handle.IsGoneForTesting());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string QueryWithIParameter(size_t i) {
|
||||||
|
return "i=" + base::NumberToString(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr std::string_view kVaryOnIParameter = "params, except=(\"i\")";
|
||||||
|
|
||||||
TEST_P(NoVarySearchCacheTest, OldestItemIsEvicted) {
|
TEST_P(NoVarySearchCacheTest, OldestItemIsEvicted) {
|
||||||
for (size_t i = 0; i < kMaxSize + 1; ++i) {
|
for (size_t i = 0; i < kMaxSize + 1; ++i) {
|
||||||
std::string query = "i=" + base::NumberToString(i);
|
std::string query = QueryWithIParameter(i);
|
||||||
Insert(query, "params, except=(\"i\")");
|
Insert(query, kVaryOnIParameter);
|
||||||
EXPECT_TRUE(Exists(query));
|
EXPECT_TRUE(Exists(query));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,8 +221,8 @@ TEST_P(NoVarySearchCacheTest, OldestItemIsEvicted) {
|
|||||||
|
|
||||||
TEST_P(NoVarySearchCacheTest, RecentlyUsedItemIsNotEvicted) {
|
TEST_P(NoVarySearchCacheTest, RecentlyUsedItemIsNotEvicted) {
|
||||||
for (size_t i = 0; i < kMaxSize + 1; ++i) {
|
for (size_t i = 0; i < kMaxSize + 1; ++i) {
|
||||||
std::string query = "i=" + base::NumberToString(i);
|
std::string query = QueryWithIParameter(i);
|
||||||
Insert(query, "params, except=(\"i\")");
|
Insert(query, kVaryOnIParameter);
|
||||||
EXPECT_TRUE(Exists(query));
|
EXPECT_TRUE(Exists(query));
|
||||||
// Exists() calls Lookup(), which makes an entry "used".
|
// Exists() calls Lookup(), which makes an entry "used".
|
||||||
EXPECT_TRUE(Exists("i=0"));
|
EXPECT_TRUE(Exists("i=0"));
|
||||||
@@ -213,11 +235,10 @@ TEST_P(NoVarySearchCacheTest, RecentlyUsedItemIsNotEvicted) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST_P(NoVarySearchCacheTest, MostRecentlyUsedItemIsNotEvicted) {
|
TEST_P(NoVarySearchCacheTest, MostRecentlyUsedItemIsNotEvicted) {
|
||||||
static constexpr char kNoVarySearchValue[] = "params, except=(\"i\")";
|
const auto query = QueryWithIParameter;
|
||||||
const auto query = [](int i) { return "i=" + base::NumberToString(i); };
|
|
||||||
// Fill the cache.
|
// Fill the cache.
|
||||||
for (size_t i = 0; i < kMaxSize; ++i) {
|
for (size_t i = 0; i < kMaxSize; ++i) {
|
||||||
Insert(query(i), kNoVarySearchValue);
|
Insert(query(i), kVaryOnIParameter);
|
||||||
}
|
}
|
||||||
EXPECT_EQ(cache().GetSizeForTesting(), kMaxSize);
|
EXPECT_EQ(cache().GetSizeForTesting(), kMaxSize);
|
||||||
|
|
||||||
@@ -226,7 +247,7 @@ TEST_P(NoVarySearchCacheTest, MostRecentlyUsedItemIsNotEvicted) {
|
|||||||
|
|
||||||
// Evict kMaxSize - 1 items.
|
// Evict kMaxSize - 1 items.
|
||||||
for (size_t i = kMaxSize; i < kMaxSize * 2 - 1; ++i) {
|
for (size_t i = kMaxSize; i < kMaxSize * 2 - 1; ++i) {
|
||||||
Insert(query(i), kNoVarySearchValue);
|
Insert(query(i), kVaryOnIParameter);
|
||||||
EXPECT_TRUE(Exists(query(i)));
|
EXPECT_TRUE(Exists(query(i)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -236,11 +257,10 @@ TEST_P(NoVarySearchCacheTest, MostRecentlyUsedItemIsNotEvicted) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST_P(NoVarySearchCacheTest, LeastRecentlyUsedItemIsEvicted) {
|
TEST_P(NoVarySearchCacheTest, LeastRecentlyUsedItemIsEvicted) {
|
||||||
static constexpr char kNoVarySearchValue[] = "params, except=(\"i\")";
|
const auto query = QueryWithIParameter;
|
||||||
const auto query = [](int i) { return "i=" + base::NumberToString(i); };
|
|
||||||
// Fill the cache.
|
// Fill the cache.
|
||||||
for (size_t i = 0; i < kMaxSize; ++i) {
|
for (size_t i = 0; i < kMaxSize; ++i) {
|
||||||
Insert(query(i), kNoVarySearchValue);
|
Insert(query(i), kVaryOnIParameter);
|
||||||
}
|
}
|
||||||
EXPECT_EQ(cache().GetSizeForTesting(), kMaxSize);
|
EXPECT_EQ(cache().GetSizeForTesting(), kMaxSize);
|
||||||
|
|
||||||
@@ -250,7 +270,7 @@ TEST_P(NoVarySearchCacheTest, LeastRecentlyUsedItemIsEvicted) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Evict one item.
|
// Evict one item.
|
||||||
Insert(query(kMaxSize), kNoVarySearchValue);
|
Insert(query(kMaxSize), kVaryOnIParameter);
|
||||||
|
|
||||||
// Verify it was the least-recently-used item.
|
// Verify it was the least-recently-used item.
|
||||||
EXPECT_FALSE(Exists(query(kMaxSize - 1)));
|
EXPECT_FALSE(Exists(query(kMaxSize - 1)));
|
||||||
@@ -319,8 +339,7 @@ TEST_P(NoVarySearchCacheTest, InsertWithBaseURLMatchingEvicted) {
|
|||||||
cache().MaybeInsert(my_test_request("will-be-evicted"),
|
cache().MaybeInsert(my_test_request("will-be-evicted"),
|
||||||
TestHeaders("key-order"));
|
TestHeaders("key-order"));
|
||||||
for (size_t i = 1; i < kMaxSize; ++i) {
|
for (size_t i = 1; i < kMaxSize; ++i) {
|
||||||
std::string query = "i=" + base::NumberToString(i);
|
Insert(QueryWithIParameter(i), kVaryOnIParameter);
|
||||||
Insert(query, "params, except=(\"i\")");
|
|
||||||
}
|
}
|
||||||
EXPECT_EQ(cache().GetSizeForTesting(), kMaxSize);
|
EXPECT_EQ(cache().GetSizeForTesting(), kMaxSize);
|
||||||
|
|
||||||
@@ -336,8 +355,7 @@ TEST_P(NoVarySearchCacheTest, InsertWithBaseURLMatchingEvicted) {
|
|||||||
TEST_P(NoVarySearchCacheTest, InsertWithNoVarySearchValueMatchingEvicted) {
|
TEST_P(NoVarySearchCacheTest, InsertWithNoVarySearchValueMatchingEvicted) {
|
||||||
Insert("will-be-evicted", "params=(\"ignored\")");
|
Insert("will-be-evicted", "params=(\"ignored\")");
|
||||||
for (size_t i = 1; i < kMaxSize; ++i) {
|
for (size_t i = 1; i < kMaxSize; ++i) {
|
||||||
std::string query = "i=" + base::NumberToString(i);
|
Insert(QueryWithIParameter(i), kVaryOnIParameter);
|
||||||
Insert(query, "params, except=(\"i\")");
|
|
||||||
}
|
}
|
||||||
EXPECT_EQ(cache().GetSizeForTesting(), kMaxSize);
|
EXPECT_EQ(cache().GetSizeForTesting(), kMaxSize);
|
||||||
|
|
||||||
@@ -746,6 +764,114 @@ TEST_P(NoVarySearchCacheTest, ClearDataNoMatch) {
|
|||||||
EXPECT_TRUE(Exists("a=1"));
|
EXPECT_TRUE(Exists("a=1"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::optional<NoVarySearchCache> TestPickleRoundTrip(
|
||||||
|
const NoVarySearchCache& cache) {
|
||||||
|
base::Pickle pickle;
|
||||||
|
WriteToPickle(pickle, cache);
|
||||||
|
// The estimate of PickeSize should always be correct.
|
||||||
|
EXPECT_EQ(EstimatePickleSize(cache), pickle.payload_size());
|
||||||
|
auto maybe_cache = ReadValueFromPickle<NoVarySearchCache>(pickle);
|
||||||
|
if (!maybe_cache) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
EXPECT_EQ(cache.GetSizeForTesting(), maybe_cache->GetSizeForTesting());
|
||||||
|
return maybe_cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_P(NoVarySearchCacheTest, SerializeDeserializeEmpty) {
|
||||||
|
EXPECT_TRUE(TestPickleRoundTrip(cache()));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_P(NoVarySearchCacheTest, SerializeDeserializeSimple) {
|
||||||
|
Insert("b=1", "key-order");
|
||||||
|
Insert("c&d", "key-order");
|
||||||
|
Insert("f=3", "params=(\"a\")");
|
||||||
|
|
||||||
|
auto new_cache = TestPickleRoundTrip(cache());
|
||||||
|
ASSERT_TRUE(new_cache);
|
||||||
|
|
||||||
|
const auto lookup = [&](std::string_view params) {
|
||||||
|
return new_cache->Lookup(TestRequest(params));
|
||||||
|
};
|
||||||
|
|
||||||
|
auto maybe_handle1 = lookup("b=1");
|
||||||
|
auto maybe_handle2 = lookup("d&c");
|
||||||
|
auto maybe_handle3 = lookup("f=3&a=7");
|
||||||
|
|
||||||
|
ASSERT_TRUE(maybe_handle1);
|
||||||
|
ASSERT_TRUE(maybe_handle2);
|
||||||
|
ASSERT_TRUE(maybe_handle3);
|
||||||
|
|
||||||
|
new_cache->Erase(std::move(maybe_handle1->erase_handle));
|
||||||
|
new_cache->Erase(std::move(maybe_handle2->erase_handle));
|
||||||
|
new_cache->Erase(std::move(maybe_handle3->erase_handle));
|
||||||
|
|
||||||
|
EXPECT_EQ(new_cache->GetSizeForTesting(), 0u);
|
||||||
|
EXPECT_TRUE(new_cache->IsTopLevelMapEmptyForTesting());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_P(NoVarySearchCacheTest, SerializeDeserializeFull) {
|
||||||
|
for (size_t i = 0; i < kMaxSize; ++i) {
|
||||||
|
Insert(QueryWithIParameter(i), kVaryOnIParameter);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto new_cache = TestPickleRoundTrip(cache());
|
||||||
|
ASSERT_TRUE(new_cache);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < kMaxSize; ++i) {
|
||||||
|
EXPECT_TRUE(new_cache->Lookup(TestRequest(QueryWithIParameter(i))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_P(NoVarySearchCacheTest, DeserializeBadSizes) {
|
||||||
|
struct TestCase {
|
||||||
|
std::string_view test_description;
|
||||||
|
int size;
|
||||||
|
int max_size;
|
||||||
|
int map_size;
|
||||||
|
};
|
||||||
|
static constexpr auto kTestCases = std::to_array<TestCase>({
|
||||||
|
{"Negative size", -1, 1, 0},
|
||||||
|
{"Size larger than max_size", 2, 1, 0},
|
||||||
|
{"Size bigger than map contents", 1, 1, 0},
|
||||||
|
{"Negative max_size", 0, -1, 0},
|
||||||
|
{"Zero max_size", 0, 0, 0},
|
||||||
|
{"Negative map size", 0, 1, -1},
|
||||||
|
{"Map size larger than map contents", 0, 1, 1},
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const auto& test_case : kTestCases) {
|
||||||
|
SCOPED_TRACE(test_case.test_description);
|
||||||
|
base::Pickle pickle;
|
||||||
|
// This uses the fact that containers use an integer for size.
|
||||||
|
WriteToPickle(pickle, test_case.size, test_case.max_size,
|
||||||
|
test_case.map_size);
|
||||||
|
EXPECT_FALSE(ReadValueFromPickle<NoVarySearchCache>(pickle));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A truncated Pickle should never deserialize to a NoVarySearchCache object.
|
||||||
|
// This tests covers many different checks for bad data during deserialization.
|
||||||
|
TEST_P(NoVarySearchCacheTest, TruncatedPickle) {
|
||||||
|
Insert("a=9&b=1", "params=(\"a\")");
|
||||||
|
Insert("a=8&b=2", "params=(\"a\")");
|
||||||
|
Insert("f=3", "params, except=(\"f\")");
|
||||||
|
Insert("", "params, except=(\"f\")");
|
||||||
|
|
||||||
|
base::Pickle pickle;
|
||||||
|
WriteToPickle(pickle, cache());
|
||||||
|
|
||||||
|
// Go up in increments of 4 bytes because a Pickle with a size that is not a
|
||||||
|
// multiple of 4 is invalid in a way that is not interesting to this test.
|
||||||
|
for (size_t bytes = 4u; bytes < pickle.payload_size(); bytes += 4) {
|
||||||
|
SCOPED_TRACE(bytes);
|
||||||
|
base::Pickle truncated;
|
||||||
|
truncated.WriteBytes(pickle.payload_bytes().first(bytes));
|
||||||
|
EXPECT_FALSE(ReadValueFromPickle<NoVarySearchCache>(truncated));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
INSTANTIATE_TEST_SUITE_P(All,
|
INSTANTIATE_TEST_SUITE_P(All,
|
||||||
NoVarySearchCacheTest,
|
NoVarySearchCacheTest,
|
||||||
::testing::Bool(),
|
::testing::Bool(),
|
||||||
|
Reference in New Issue
Block a user