| // Copyright (c) 2019 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <cinttypes> |
| #include <cstdlib> |
| #include <iostream> |
| #include <map> |
| #include <memory> |
| #include <string> |
| |
| #include "base/at_exit.h" |
| #include "base/callback.h" |
| #include "base/command_line.h" |
| #include "base/files/file_path.h" |
| #include "base/files/file_util.h" |
| #include "base/files/scoped_temp_dir.h" |
| #include "base/logging.h" |
| #include "base/macros.h" |
| #include "base/memory/ref_counted.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "base/test/scoped_task_environment.h" |
| #include "base/time/time.h" |
| #include "net/base/cache_type.h" |
| #include "net/base/io_buffer.h" |
| #include "net/base/net_errors.h" |
| #include "net/base/test_completion_callback.h" |
| #include "net/disk_cache/backend_cleanup_tracker.h" |
| #include "net/disk_cache/blockfile/backend_impl.h" |
| #include "net/disk_cache/disk_cache.h" |
| #include "net/disk_cache/disk_cache_fuzzer.pb.h" |
| #include "net/disk_cache/disk_cache_test_util.h" |
| #include "net/disk_cache/memory/mem_backend_impl.h" |
| #include "net/disk_cache/simple/simple_backend_impl.h" |
| #include "net/disk_cache/simple/simple_file_tracker.h" |
| #include "net/disk_cache/simple/simple_index.h" |
| #include "testing/libfuzzer/proto/lpm_interface.h" |
| |
| // To get a good idea of what a test case is doing, just run the libfuzzer |
| // target with LPM_DUMP_NATIVE_INPUT=1 prefixed. This will trigger all the |
| // prints below and will convey exactly what the test case is doing: use this |
| // instead of trying to print the protobuf as text. |
| |
| // For code coverage: |
| // python ./tools/code_coverage/coverage.py disk_cache_lpm_fuzzer -b |
| // out/coverage -o out/report -c 'out/coverage/disk_cache_lpm_fuzzer |
| // -runs=0 -workers=24 corpus_disk_cache_simple' -f net/disk_cache |
| |
| void IOCallback(std::string io_type, int rv); |
| |
| namespace { |
| const uint32_t kMaxSizeKB = 128; // 128KB maximum. |
| const uint32_t kMaxSize = kMaxSizeKB * 1024; |
| const uint32_t kMaxEntrySize = kMaxSize * 2; |
| const uint32_t kNumStreams = 3; // All caches seem to have 3 streams. TODO do |
| // other specialized caches have this? |
| const uint64_t kFirstSavedTime = |
| 5; // Totally random number chosen by dice roll. ;) |
| const uint32_t kMaxNumMillisToWait = 2019; |
| const int kMaxFdsSimpleCache = 10; |
| |
| #define IOTYPES_APPLY(F) \ |
| F(WriteData) \ |
| F(ReadData) \ |
| F(WriteSparseData) \ |
| F(ReadSparseData) \ |
| F(DoomAllEntries) \ |
| F(DoomEntriesSince) \ |
| F(DoomEntriesBetween) \ |
| F(GetAvailableRange) \ |
| F(DoomKey) |
| |
| enum class IOType { |
| #define ENUM_ENTRY(IO_TYPE) IO_TYPE, |
| IOTYPES_APPLY(ENUM_ENTRY) |
| #undef ENUM_ENTRY |
| }; |
| |
| struct InitGlobals { |
| InitGlobals() { |
| base::CommandLine::Init(0, nullptr); |
| |
| print_comms_ = ::getenv("LPM_DUMP_NATIVE_INPUT"); |
| |
| // Mark this thread as an IO_THREAD with MOCK_TIME, and ensure that every |
| // other thread's time is driven from this thread's MOCK_TIME. |
| scoped_task_environment_ = std::make_unique< |
| base::test::ScopedTaskEnvironment>( |
| base::test::ScopedTaskEnvironment::MainThreadType::IO_MOCK_TIME, |
| base::test::ScopedTaskEnvironment::NowSource::MAIN_THREAD_MOCK_TIME); |
| |
| // Work around for a DCHECK issue: DCHECK(!TimeTicks::Now().is_null()) |
| // Since MOCK_TIME starts at 0 the starting time is considered null. |
| // So make it non-zero. |
| scoped_task_environment_->FastForwardBy( |
| base::TimeDelta::FromMilliseconds(1)); |
| |
| // Disable noisy logging as per "libFuzzer in Chrome" documentation: |
| // testing/libfuzzer/getting_started.md#Disable-noisy-error-message-logging. |
| logging::SetMinLogLevel(logging::LOG_FATAL); |
| |
| // Re-using this buffer for write operations may technically be against |
| // IOBuffer rules but it shouldn't cause any actual problems. |
| buffer_ = |
| base::MakeRefCounted<net::IOBuffer>(static_cast<size_t>(kMaxEntrySize)); |
| CacheTestFillBuffer(buffer_->data(), kMaxEntrySize, false); |
| |
| #define CREATE_IO_CALLBACK(IO_TYPE) \ |
| io_callbacks_.push_back(base::BindRepeating(&IOCallback, #IO_TYPE)); |
| IOTYPES_APPLY(CREATE_IO_CALLBACK) |
| #undef CREATE_IO_CALLBACK |
| } |
| |
| // This allows us to mock time for all threads. |
| std::unique_ptr<base::test::ScopedTaskEnvironment> scoped_task_environment_; |
| |
| // Used as a pre-filled buffer for all writes. |
| scoped_refptr<net::IOBuffer> buffer_; |
| |
| // Should we print debugging info? |
| bool print_comms_; |
| |
| // List of IO callbacks. They do nothing (except maybe print) but are used by |
| // all async entry operations. |
| std::vector<base::RepeatingCallback<void(int)>> io_callbacks_; |
| }; |
| |
| InitGlobals* init_globals = new InitGlobals(); |
| } // namespace |
| |
| class DiskCacheLPMFuzzer { |
| public: |
| DiskCacheLPMFuzzer() { |
| CHECK(temp_dir_.CreateUniqueTempDir()); |
| cache_path_ = temp_dir_.GetPath(); |
| } |
| |
| ~DiskCacheLPMFuzzer(); |
| |
| void RunCommands(const disk_cache_fuzzer::FuzzCommands& commands); |
| |
| private: |
| struct EntryInfo { |
| EntryInfo() = default; |
| disk_cache::Entry* entry_ptr = nullptr; |
| std::unique_ptr<net::TestCompletionCallback> tcb; |
| |
| DISALLOW_COPY_AND_ASSIGN(EntryInfo); |
| }; |
| void RunTaskForTest(base::OnceClosure closure); |
| |
| // Waits for an entry to be ready. Only should be called if there is a pending |
| // callback for this entry; i.e. ei->tcb != nullptr. |
| // Also takes the rv that the cache entry creation functions return, and does |
| // not wait if rv != net::ERR_IO_PENDING (and would never have called the |
| // callback). |
| int WaitOnEntry(EntryInfo* ei, int rv = net::ERR_IO_PENDING); |
| |
| // Used as a callback for entry-opening backend calls. Will record the entry |
| // in the map as usable and will release any entry-specific calls waiting for |
| // the entry to be ready. |
| void OpenCacheEntryCallback( |
| scoped_refptr<base::RefCountedData<disk_cache::EntryWithOpened>> |
| entry_result, |
| uint64_t entry_id, |
| bool async, |
| bool set_is_sparse, |
| int rv); |
| |
| // Waits for the entry to finish opening, in the async case. Then, if the |
| // entry is successfully open (callback returns net::OK, or was already |
| // successfully opened), check if the entry_ptr == nullptr. If so, the |
| // entry has been closed. |
| bool IsValidEntry(EntryInfo* ei); |
| |
| // Closes any non-nullptr entries in open_cache_entries_. |
| void CloseAllRemainingEntries(); |
| |
| void HandleSetMaxSize(const disk_cache_fuzzer::SetMaxSize&); |
| void CreateBackend( |
| disk_cache_fuzzer::FuzzCommands::CacheBackend cache_backend, |
| uint32_t mask, |
| net::CacheType type, |
| bool simple_cache_wait_for_index); |
| |
| // Places to keep our cache files. |
| base::FilePath cache_path_; |
| base::ScopedTempDir temp_dir_; |
| |
| // Pointers to our backend. Only one of block_impl_, simple_cache_impl_, and |
| // mem_cache_ are active at one time. |
| std::unique_ptr<disk_cache::Backend> cache_; |
| disk_cache::BackendImpl* block_impl_ = nullptr; |
| std::unique_ptr<disk_cache::SimpleFileTracker> simple_file_tracker_; |
| disk_cache::SimpleBackendImpl* simple_cache_impl_ = nullptr; |
| disk_cache::MemBackendImpl* mem_cache_ = nullptr; |
| |
| // Maximum size of the cache, that we have currently set. |
| uint32_t max_size_ = kMaxSize; |
| |
| // This "consistent hash table" keeys track of the keys we've added to the |
| // backend so far. This should always be indexed by a "key_id" from a |
| // protobuf. |
| std::map<uint64_t, std::string> created_cache_entries_; |
| // This "consistent hash table" keeps track of all opened entries we have from |
| // the backend, and also contains some nullptr's where entries were already |
| // closed. This should always be indexed by an "entry_id" from a protobuf. |
| // When destructed, we close all entries that are still open in order to avoid |
| // memory leaks. |
| std::map<uint64_t, EntryInfo> open_cache_entries_; |
| // This "consistent hash table" keeps track of all times we have saved, so |
| // that we can call backend methods like DoomEntriesSince or |
| // DoomEntriesBetween with sane timestamps. This should always be indexed by a |
| // "time_id" from a protobuf. |
| std::map<uint64_t, base::Time> saved_times_; |
| // This "consistent hash table" keeps tack of all the iterators we have open |
| // from the backend. This should always be indexed by a "it_id" from a |
| // protobuf. |
| std::map<uint64_t, std::unique_ptr<disk_cache::Backend::Iterator>> |
| open_iterators_; |
| |
| // This maps keeps track of the sparsity of each entry, using their pointers. |
| // TODO(mpdenton) remove if CreateEntry("Key0"); WriteData("Key0", index = 2, |
| // ...); WriteSparseData("Key0", ...); is supposed to be valid. |
| // Then we can just use CouldBeSparse before the WriteData. |
| std::map<disk_cache::Entry*, bool> sparse_entry_tracker_; |
| }; |
| |
| #define MAYBE_PRINT \ |
| if (init_globals->print_comms_) \ |
| std::cout |
| |
| inline base::RepeatingCallback<void(int)> GetIOCallback(IOType iot) { |
| return init_globals->io_callbacks_[static_cast<int>(iot)]; |
| } |
| |
| std::string ToKey(uint64_t key_num) { |
| return "Key" + std::to_string(key_num); |
| } |
| |
| net::RequestPriority GetRequestPriority( |
| disk_cache_fuzzer::RequestPriority lpm_pri) { |
| CHECK(net::MINIMUM_PRIORITY <= static_cast<int>(lpm_pri) && |
| static_cast<int>(lpm_pri) <= net::MAXIMUM_PRIORITY); |
| return static_cast<net::RequestPriority>(lpm_pri); |
| } |
| |
| net::CacheType GetCacheTypeAndPrint( |
| disk_cache_fuzzer::FuzzCommands::CacheType type, |
| disk_cache_fuzzer::FuzzCommands::CacheBackend backend) { |
| switch (type) { |
| case disk_cache_fuzzer::FuzzCommands::APP_CACHE: |
| MAYBE_PRINT << "Cache type = APP_CACHE." << std::endl; |
| return net::CacheType::APP_CACHE; |
| break; |
| case disk_cache_fuzzer::FuzzCommands::MEDIA_CACHE: |
| MAYBE_PRINT << "Cache type = MEDIA_CACHE." << std::endl; |
| return net::CacheType::MEDIA_CACHE; |
| break; |
| case disk_cache_fuzzer::FuzzCommands::SHADER_CACHE: |
| MAYBE_PRINT << "Cache type = SHADER_CACHE." << std::endl; |
| return net::CacheType::SHADER_CACHE; |
| break; |
| case disk_cache_fuzzer::FuzzCommands::PNACL_CACHE: |
| // Simple cache won't handle PNACL_CACHE. |
| if (backend == disk_cache_fuzzer::FuzzCommands::SIMPLE) { |
| MAYBE_PRINT << "Cache type = DISK_CACHE." << std::endl; |
| return net::CacheType::DISK_CACHE; |
| } |
| MAYBE_PRINT << "Cache type = PNACL_CACHE." << std::endl; |
| return net::CacheType::PNACL_CACHE; |
| break; |
| case disk_cache_fuzzer::FuzzCommands::GENERATED_CODE_CACHE: |
| MAYBE_PRINT << "Cache type = GENERATED_CODE_CACHE." << std::endl; |
| return net::CacheType::GENERATED_CODE_CACHE; |
| break; |
| case disk_cache_fuzzer::FuzzCommands::DISK_CACHE: |
| MAYBE_PRINT << "Cache type = DISK_CACHE." << std::endl; |
| return net::CacheType::DISK_CACHE; |
| break; |
| } |
| } |
| |
| void IOCallback(std::string io_type, int rv) { |
| MAYBE_PRINT << " [Async IO (" << io_type << ") = " << rv << "]" << std::endl; |
| } |
| |
| /* |
| * Consistent hashing inspired map for fuzzer state. |
| * If we stored open cache entries in a hash table mapping cache_entry_id -> |
| * disk_cache::Entry*, then it would be highly unlikely that any subsequent |
| * "CloseEntry" or "WriteData" etc. command would come up with an ID that would |
| * correspond to a valid entry in the hash table. The optimal solution is for |
| * libfuzzer to generate CloseEntry commands with an ID that matches the ID of a |
| * previous OpenEntry command. But libfuzzer is stateless and should stay that |
| * way. |
| * |
| * On the other hand, if we stored entries in a vector, and on a CloseEntry |
| * command we took the entry at CloseEntry.id % (size of entries vector), we |
| * would always generate correct CloseEntries. This is good, but all |
| * dumb/general minimization techniques stop working, because deleting a single |
| * OpenEntry command changes the indexes of every entry in the vector from then |
| * on. |
| * |
| * So, we use something that's more stable for minimization: consistent hashing. |
| * Basically, when we see a CloseEntry.id, we take the entry in the table that |
| * has the next highest id (wrapping when there is no higher entry). |
| * |
| * This makes us resilient to deleting irrelevant OpenEntry commands. But, if we |
| * delete from the table on CloseEntry commands, we still screw up all the |
| * indexes during minimization. We'll get around this by not deleting entries |
| * after CloseEntry commands, but that will result in a slightly less efficient |
| * fuzzer, as if there are many closed entries in the table, many of the *Entry |
| * commands will be useless. It seems like a decent balance between generating |
| * useful fuzz commands and effective minimization. |
| */ |
| template <typename T> |
| typename std::map<uint64_t, T>::iterator GetNextValue( |
| typename std::map<uint64_t, T>* entries, |
| uint64_t val) { |
| auto iter = entries->lower_bound(val); |
| if (iter != entries->end()) |
| return iter; |
| // Wrap to 0 |
| iter = entries->lower_bound(0); |
| if (iter != entries->end()) |
| return iter; |
| |
| return entries->end(); |
| } |
| |
| void DiskCacheLPMFuzzer::RunTaskForTest(base::OnceClosure closure) { |
| if (!block_impl_) { |
| std::move(closure).Run(); |
| return; |
| } |
| |
| net::TestCompletionCallback cb; |
| int rv = block_impl_->RunTaskForTest(std::move(closure), cb.callback()); |
| CHECK_EQ(cb.GetResult(rv), net::OK); |
| } |
| |
| // Resets the cb in the map so that WriteData and other calls that work on an |
| // entry don't wait for its result. |
| void DiskCacheLPMFuzzer::OpenCacheEntryCallback( |
| scoped_refptr<base::RefCountedData<disk_cache::EntryWithOpened>> |
| entry_result, |
| uint64_t entry_id, |
| bool async, |
| bool set_is_sparse, |
| int rv) { |
| // TODO(mpdenton) if this fails should we delete the entry entirely? |
| // Would need to mark it for deletion and delete it later, as |
| // IsValidEntry might be waiting for it. |
| EntryInfo* ei = &open_cache_entries_[entry_id]; |
| |
| // It's important to write the pointer here and not let it be set directly |
| // by OpenEntry & co., since the callback is when the item is transferred to |
| // the client, so there may be a window in between the write and callback |
| // where the backend is destroyed, invalidating the transfer. |
| // Writes can also happen on a different thread, so unless the read is |
| // sequenced with it, it would be a race, and ei->entry_ptr is read quite a |
| // bit. |
| ei->entry_ptr = entry_result->data.entry; |
| ei->tcb->callback().Run(rv); |
| // If this entry was just created, set it as sparse if applicable. |
| if (set_is_sparse && ei->entry_ptr) { |
| sparse_entry_tracker_[ei->entry_ptr] = true; |
| } |
| if (async && ei->entry_ptr) { |
| MAYBE_PRINT << " [Async opening of cache entry for \"" |
| << ei->entry_ptr->GetKey() << "\" callback (rv = " << rv << ")]" |
| << std::endl; |
| } |
| } |
| |
| int DiskCacheLPMFuzzer::WaitOnEntry(EntryInfo* ei, int rv) { |
| CHECK(ei->tcb); |
| rv = ei->tcb->GetResult(rv); |
| // Reset the callback so nobody accidentally waits on a callback that never |
| // comes. |
| ei->tcb.reset(); |
| return rv; |
| } |
| |
| bool DiskCacheLPMFuzzer::IsValidEntry(EntryInfo* ei) { |
| if (ei->tcb) { |
| // If we have a callback, we are the first to access this async-created |
| // entry. Wait for it, and then delete it so nobody waits on it again. |
| return WaitOnEntry(ei) == net::OK; |
| } |
| // entry_ptr will be nullptr if the entry has been closed. |
| return ei->entry_ptr != nullptr; |
| } |
| |
| /* |
| * Async implementation: |
| 1. RunUntilIdle at the top of the loop to handle any callbacks we've been |
| posted from the backend thread. |
| 2. Only the entry creation functions have important callbacks. The good thing |
| is backend destruction will cancel these operations. The entry creation |
| functions simply need to keep the entry_ptr* alive until the callback is |
| posted, and then need to make sure the entry_ptr is added to the map in order |
| to Close it in the destructor. |
| As for iterators, it's unclear whether closing an iterator will cancel |
| callbacks. |
| |
| Problem: WriteData (and similar) calls will fail on the entry_id until the |
| callback happens. So, I should probably delay these calls or otherwise will |
| have very unreliable test cases. These are the options: |
| 1. Queue up WriteData (etc.) calls in some map, such that when the OpenEntry |
| callback runs, the WriteData calls will all run. |
| 2. Just sit there and wait for the entry to be ready. |
| |
| #2 is probably best as it doesn't prevent any interesting cases and is much |
| simpler. |
| */ |
| |
| void DiskCacheLPMFuzzer::RunCommands( |
| const disk_cache_fuzzer::FuzzCommands& commands) { |
| uint32_t mask = |
| commands.has_set_mask() ? (commands.set_mask() ? 0x1 : 0xf) : 0; |
| net::CacheType type = |
| GetCacheTypeAndPrint(commands.cache_type(), commands.cache_backend()); |
| CreateBackend(commands.cache_backend(), mask, type, |
| commands.simple_cache_wait_for_index()); |
| MAYBE_PRINT << "CreateBackend()" << std::endl; |
| |
| if (commands.has_set_max_size()) { |
| HandleSetMaxSize(commands.set_max_size()); |
| } |
| |
| { |
| base::Time curr_time = base::Time::Now(); |
| saved_times_[kFirstSavedTime] = curr_time; |
| // MAYBE_PRINT << "Saved initial time " << curr_time << std::endl; |
| } |
| |
| for (const disk_cache_fuzzer::FuzzCommand& command : |
| commands.fuzz_commands()) { |
| // Handle any callbacks that other threads may have posted to us in the |
| // meantime, so any successful async OpenEntry's (etc.) add their |
| // entry_ptr's to the map. |
| init_globals->scoped_task_environment_->RunUntilIdle(); |
| |
| switch (command.fuzz_command_oneof_case()) { |
| case disk_cache_fuzzer::FuzzCommand::kSetMaxSize: { |
| HandleSetMaxSize(command.set_max_size()); |
| break; |
| } |
| case disk_cache_fuzzer::FuzzCommand::kCreateEntry: { |
| if (!cache_) |
| continue; |
| |
| const disk_cache_fuzzer::CreateEntry& ce = command.create_entry(); |
| uint64_t key_id = ce.key_id(); |
| uint64_t entry_id = ce.entry_id(); |
| net::RequestPriority pri = GetRequestPriority(ce.pri()); |
| bool async = ce.async(); |
| bool is_sparse = ce.is_sparse(); |
| |
| if (open_cache_entries_.find(entry_id) != open_cache_entries_.end()) |
| continue; // Don't overwrite a currently open cache entry. |
| |
| std::string key_str = ToKey(key_id); |
| created_cache_entries_[key_id] = key_str; |
| |
| auto entry_result = base::MakeRefCounted< |
| base::RefCountedData<disk_cache::EntryWithOpened>>(); |
| disk_cache::EntryWithOpened* entry_result_ptr = &entry_result->data; |
| EntryInfo* entry_info = &open_cache_entries_[entry_id]; |
| |
| entry_info->tcb = std::make_unique<net::TestCompletionCallback>(); |
| net::CompletionOnceCallback cb = base::BindOnce( |
| &DiskCacheLPMFuzzer::OpenCacheEntryCallback, base::Unretained(this), |
| entry_result, entry_id, async, is_sparse); |
| |
| MAYBE_PRINT << "CreateEntry(\"" << key_str |
| << "\", set_is_sparse = " << is_sparse |
| << ") = " << std::flush; |
| int rv = cache_->CreateEntry(key_str, pri, &entry_result_ptr->entry, |
| std::move(cb)); |
| if (!async || rv != net::ERR_IO_PENDING) { |
| rv = WaitOnEntry(entry_info, rv); |
| // Ensure we mark sparsity, even if the callback never ran. |
| if (rv == net::OK) { |
| entry_info->entry_ptr = entry_result_ptr->entry; |
| sparse_entry_tracker_[entry_info->entry_ptr] = is_sparse; |
| } |
| MAYBE_PRINT << rv << std::endl; |
| } else if (rv == net::ERR_IO_PENDING) { |
| MAYBE_PRINT << "net::ERR_IO_PENDING (async)" << std::endl; |
| } |
| break; |
| } |
| case disk_cache_fuzzer::FuzzCommand::kOpenEntry: { |
| if (!cache_) |
| continue; |
| |
| const disk_cache_fuzzer::OpenEntry& oe = command.open_entry(); |
| uint64_t key_id = oe.key_id(); |
| uint64_t entry_id = oe.entry_id(); |
| net::RequestPriority pri = GetRequestPriority(oe.pri()); |
| bool async = oe.async(); |
| |
| if (created_cache_entries_.empty()) |
| continue; |
| |
| if (open_cache_entries_.find(entry_id) != open_cache_entries_.end()) |
| continue; // Don't overwrite a currently open cache entry. |
| |
| auto entry_result = base::MakeRefCounted< |
| base::RefCountedData<disk_cache::EntryWithOpened>>(); |
| disk_cache::EntryWithOpened* entry_result_ptr = &entry_result->data; |
| EntryInfo* entry_info = &open_cache_entries_[entry_id]; |
| |
| entry_info->tcb = std::make_unique<net::TestCompletionCallback>(); |
| net::CompletionOnceCallback cb = base::BindOnce( |
| &DiskCacheLPMFuzzer::OpenCacheEntryCallback, base::Unretained(this), |
| entry_result, entry_id, async, false); |
| |
| auto key_it = GetNextValue(&created_cache_entries_, key_id); |
| MAYBE_PRINT << "OpenEntry(\"" << key_it->second |
| << "\") = " << std::flush; |
| int rv = cache_->OpenEntry(key_it->second, pri, |
| &entry_result_ptr->entry, std::move(cb)); |
| if (!async || rv != net::ERR_IO_PENDING) { |
| rv = WaitOnEntry(entry_info, rv); |
| if (rv == net::OK) |
| entry_info->entry_ptr = entry_result_ptr->entry; |
| MAYBE_PRINT << rv << std::endl; |
| } else { |
| MAYBE_PRINT << "net::ERR_IO_PENDING (async)" << std::endl; |
| } |
| break; |
| } |
| case disk_cache_fuzzer::FuzzCommand::kOpenOrCreateEntry: { |
| if (!cache_) |
| continue; |
| |
| const disk_cache_fuzzer::OpenOrCreateEntry& ooce = |
| command.open_or_create_entry(); |
| uint64_t key_id = ooce.key_id(); |
| uint64_t entry_id = ooce.entry_id(); |
| net::RequestPriority pri = GetRequestPriority(ooce.pri()); |
| bool async = ooce.async(); |
| bool is_sparse = ooce.is_sparse(); |
| |
| if (open_cache_entries_.find(entry_id) != open_cache_entries_.end()) |
| continue; // Don't overwrite a currently open cache entry. |
| |
| std::string key_str; |
| // If our proto tells us to create a new entry, create a new entry, just |
| // with OpenOrCreateEntry. |
| if (ooce.create_new()) { |
| // Use a possibly new key. |
| key_str = ToKey(key_id); |
| created_cache_entries_[key_id] = key_str; |
| } else { |
| if (created_cache_entries_.empty()) |
| continue; |
| auto key_it = GetNextValue(&created_cache_entries_, key_id); |
| key_str = key_it->second; |
| } |
| |
| // Setup for callbacks. |
| auto entry_result = base::MakeRefCounted< |
| base::RefCountedData<disk_cache::EntryWithOpened>>(); |
| disk_cache::EntryWithOpened* entry_result_ptr = &entry_result->data; |
| |
| EntryInfo* entry_info = &open_cache_entries_[entry_id]; |
| |
| entry_info->tcb = std::make_unique<net::TestCompletionCallback>(); |
| net::CompletionOnceCallback cb = base::BindOnce( |
| &DiskCacheLPMFuzzer::OpenCacheEntryCallback, base::Unretained(this), |
| entry_result, entry_id, async, is_sparse); |
| |
| // Will only be set as sparse if it is created and not opened. |
| MAYBE_PRINT << "OpenOrCreateEntry(\"" << key_str |
| << "\", set_is_sparse = " << is_sparse |
| << ") = " << std::flush; |
| int rv = cache_->OpenOrCreateEntry(key_str, pri, entry_result_ptr, |
| std::move(cb)); |
| if (!async || rv != net::ERR_IO_PENDING) { |
| rv = WaitOnEntry(entry_info, rv); |
| // We still hold a reference to |entry_result| so we can use it. |
| entry_info->entry_ptr = entry_result_ptr->entry; |
| // Ensure we mark sparsity, even if the callback never ran. |
| if (rv == net::OK && !entry_result_ptr->opened) |
| sparse_entry_tracker_[entry_info->entry_ptr] = is_sparse; |
| MAYBE_PRINT << rv << ", opened = " << entry_result_ptr->opened |
| << std::endl; |
| } else { |
| MAYBE_PRINT << "net::ERR_IO_PENDING (async)" << std::endl; |
| } |
| break; |
| } |
| case disk_cache_fuzzer::FuzzCommand::kCloseEntry: { |
| if (open_cache_entries_.empty()) |
| continue; |
| |
| auto entry_it = GetNextValue(&open_cache_entries_, |
| command.close_entry().entry_id()); |
| if (!IsValidEntry(&entry_it->second)) |
| continue; |
| |
| MAYBE_PRINT << "CloseEntry(\"" << entry_it->second.entry_ptr->GetKey() |
| << "\")" << std::endl; |
| entry_it->second.entry_ptr->Close(); |
| |
| // Set the entry_ptr to nullptr to ensure no one uses it anymore. |
| entry_it->second.entry_ptr = nullptr; |
| break; |
| } |
| case disk_cache_fuzzer::FuzzCommand::kDoomEntry: { |
| if (open_cache_entries_.empty()) |
| continue; |
| |
| auto entry_it = |
| GetNextValue(&open_cache_entries_, command.doom_entry().entry_id()); |
| if (!IsValidEntry(&entry_it->second)) |
| continue; |
| |
| MAYBE_PRINT << "DoomEntry(\"" << entry_it->second.entry_ptr->GetKey() |
| << "\")" << std::endl; |
| entry_it->second.entry_ptr->Doom(); |
| break; |
| } |
| case disk_cache_fuzzer::FuzzCommand::kWriteData: { |
| if (open_cache_entries_.empty()) |
| continue; |
| |
| const disk_cache_fuzzer::WriteData& wd = command.write_data(); |
| auto entry_it = GetNextValue(&open_cache_entries_, wd.entry_id()); |
| if (!IsValidEntry(&entry_it->second)) |
| continue; |
| |
| int index = 0; // if it's sparse, these non-sparse aware streams must |
| // read from stream 0 according to the spec. |
| // Implementations might have weaker constraints. |
| if (!sparse_entry_tracker_[entry_it->second.entry_ptr]) |
| index = wd.index() % kNumStreams; |
| uint32_t offset = wd.offset() % kMaxEntrySize; |
| size_t size = wd.size() % kMaxEntrySize; |
| bool async = wd.async(); |
| |
| net::TestCompletionCallback tcb; |
| net::CompletionOnceCallback cb = |
| !async ? tcb.callback() : GetIOCallback(IOType::WriteData); |
| |
| MAYBE_PRINT << "WriteData(\"" << entry_it->second.entry_ptr->GetKey() |
| << "\", index = " << index << ", offset = " << offset |
| << ", size = " << size << ", truncate = " << wd.truncate() |
| << ")" << std::flush; |
| int rv = entry_it->second.entry_ptr->WriteData( |
| index, offset, init_globals->buffer_.get(), size, std::move(cb), |
| wd.truncate()); |
| if (!async) |
| rv = tcb.GetResult(rv); |
| MAYBE_PRINT << " = " << rv << std::endl; |
| break; |
| } |
| case disk_cache_fuzzer::FuzzCommand::kReadData: { |
| if (open_cache_entries_.empty()) |
| continue; |
| |
| const disk_cache_fuzzer::ReadData& wd = command.read_data(); |
| auto entry_it = GetNextValue(&open_cache_entries_, wd.entry_id()); |
| if (!IsValidEntry(&entry_it->second)) |
| continue; |
| |
| int index = 0; // if it's sparse, these non-sparse aware streams must |
| // read from stream 0 according to the spec. |
| // Implementations might weaker constraints? |
| if (!sparse_entry_tracker_[entry_it->second.entry_ptr]) |
| index = wd.index() % kNumStreams; |
| uint32_t offset = wd.offset() % kMaxEntrySize; |
| size_t size = wd.size() % kMaxEntrySize; |
| bool async = wd.async(); |
| scoped_refptr<net::IOBuffer> buffer = |
| base::MakeRefCounted<net::IOBuffer>(size); |
| |
| net::TestCompletionCallback tcb; |
| net::CompletionOnceCallback cb = |
| !async ? tcb.callback() : GetIOCallback(IOType::ReadData); |
| |
| MAYBE_PRINT << "ReadData(\"" << entry_it->second.entry_ptr->GetKey() |
| << "\", index = " << index << ", offset = " << offset |
| << ", size = " << size << ")" << std::flush; |
| int rv = entry_it->second.entry_ptr->ReadData( |
| index, offset, buffer.get(), size, std::move(cb)); |
| if (!async) |
| rv = tcb.GetResult(rv); |
| MAYBE_PRINT << " = " << rv << std::endl; |
| break; |
| } |
| case disk_cache_fuzzer::FuzzCommand::kWriteSparseData: { |
| if (open_cache_entries_.empty()) |
| continue; |
| |
| const disk_cache_fuzzer::WriteSparseData& wsd = |
| command.write_sparse_data(); |
| auto entry_it = GetNextValue(&open_cache_entries_, wsd.entry_id()); |
| if (!IsValidEntry(&entry_it->second) || |
| !sparse_entry_tracker_[entry_it->second.entry_ptr]) |
| continue; |
| |
| uint64_t offset = wsd.offset(); |
| if (wsd.cap_offset()) |
| offset %= kMaxEntrySize; |
| size_t size = wsd.size() % kMaxEntrySize; |
| bool async = wsd.async(); |
| |
| net::TestCompletionCallback tcb; |
| net::CompletionOnceCallback cb = |
| !async ? tcb.callback() : GetIOCallback(IOType::WriteSparseData); |
| MAYBE_PRINT << "WriteSparseData(\"" |
| << entry_it->second.entry_ptr->GetKey() |
| << "\", offset = " << offset << ", size = " << size << ")" |
| << std::flush; |
| int rv = entry_it->second.entry_ptr->WriteSparseData( |
| offset, init_globals->buffer_.get(), size, std::move(cb)); |
| if (!async) |
| rv = tcb.GetResult(rv); |
| MAYBE_PRINT << " = " << rv << std::endl; |
| break; |
| } |
| case disk_cache_fuzzer::FuzzCommand::kReadSparseData: { |
| if (open_cache_entries_.empty()) |
| continue; |
| |
| const disk_cache_fuzzer::ReadSparseData& rsd = |
| command.read_sparse_data(); |
| auto entry_it = GetNextValue(&open_cache_entries_, rsd.entry_id()); |
| if (!IsValidEntry(&entry_it->second) || |
| !sparse_entry_tracker_[entry_it->second.entry_ptr]) |
| continue; |
| |
| uint64_t offset = rsd.offset(); |
| if (rsd.cap_offset()) |
| offset %= kMaxEntrySize; |
| size_t size = rsd.size() % kMaxEntrySize; |
| bool async = rsd.async(); |
| scoped_refptr<net::IOBuffer> buffer = |
| base::MakeRefCounted<net::IOBuffer>(size); |
| |
| net::TestCompletionCallback tcb; |
| net::CompletionOnceCallback cb = |
| !async ? tcb.callback() : GetIOCallback(IOType::ReadSparseData); |
| |
| MAYBE_PRINT << "ReadSparseData(\"" |
| << entry_it->second.entry_ptr->GetKey() |
| << "\", offset = " << offset << ", size = " << size << ")" |
| << std::flush; |
| int rv = entry_it->second.entry_ptr->ReadSparseData( |
| offset, buffer.get(), size, std::move(cb)); |
| if (!async) |
| rv = tcb.GetResult(rv); |
| MAYBE_PRINT << " = " << rv << std::endl; |
| break; |
| } |
| case disk_cache_fuzzer::FuzzCommand::kDoomAllEntries: { |
| if (!cache_) |
| continue; |
| bool async = command.doom_all_entries().async(); |
| |
| net::TestCompletionCallback tcb; |
| net::CompletionOnceCallback cb = |
| !async ? tcb.callback() : GetIOCallback(IOType::DoomAllEntries); |
| MAYBE_PRINT << "DoomAllEntries()" << std::flush; |
| int rv = cache_->DoomAllEntries(std::move(cb)); |
| if (!async) |
| rv = tcb.GetResult(rv); |
| MAYBE_PRINT << " = " << rv << std::endl; |
| break; |
| } |
| case disk_cache_fuzzer::FuzzCommand::kFlushQueueForTest: { |
| // Blockfile-cache specific method. |
| if (!block_impl_) |
| return; |
| |
| net::TestCompletionCallback cb; |
| MAYBE_PRINT << "FlushQueueForTest()" << std::endl; |
| int rv = block_impl_->FlushQueueForTest(cb.callback()); |
| CHECK_EQ(cb.GetResult(rv), net::OK); |
| break; |
| } |
| case disk_cache_fuzzer::FuzzCommand::kCreateIterator: { |
| if (!cache_) |
| continue; |
| uint64_t it_id = command.create_iterator().it_id(); |
| MAYBE_PRINT << "CreateIterator(), id = " << it_id << std::endl; |
| open_iterators_[it_id] = cache_->CreateIterator(); |
| break; |
| } |
| case disk_cache_fuzzer::FuzzCommand::kIteratorOpenNextEntry: { |
| const disk_cache_fuzzer::IteratorOpenNextEntry& ione = |
| command.iterator_open_next_entry(); |
| |
| uint64_t it_id = ione.it_id(); |
| uint64_t entry_id = ione.entry_id(); |
| bool async = ione.async(); |
| |
| if (open_iterators_.empty()) |
| continue; |
| |
| if (open_cache_entries_.find(entry_id) != open_cache_entries_.end()) |
| continue; // Don't overwrite a currently |
| // open cache entry. |
| |
| auto iterator_it = GetNextValue(&open_iterators_, it_id); |
| |
| auto entry_result = base::MakeRefCounted< |
| base::RefCountedData<disk_cache::EntryWithOpened>>(); |
| disk_cache::EntryWithOpened* entry_result_ptr = &entry_result->data; |
| EntryInfo* entry_info = &open_cache_entries_[entry_id]; |
| |
| entry_info->tcb = std::make_unique<net::TestCompletionCallback>(); |
| net::CompletionOnceCallback cb = base::BindOnce( |
| &DiskCacheLPMFuzzer::OpenCacheEntryCallback, base::Unretained(this), |
| entry_result, entry_id, async, false); |
| |
| MAYBE_PRINT << "Iterator(" << ione.it_id() |
| << ").OpenNextEntry() = " << std::flush; |
| int rv = iterator_it->second->OpenNextEntry(&entry_result_ptr->entry, |
| std::move(cb)); |
| if (!async || rv != net::ERR_IO_PENDING) { |
| rv = WaitOnEntry(entry_info, rv); |
| entry_info->entry_ptr = entry_result_ptr->entry; |
| // Print return value, and key if applicable. |
| if (!entry_info->entry_ptr) { |
| MAYBE_PRINT << rv << std::endl; |
| } else { |
| MAYBE_PRINT << rv << ", key = " << entry_info->entry_ptr->GetKey() |
| << std::endl; |
| } |
| } else { |
| MAYBE_PRINT << "net::ERR_IO_PENDING (async)" << std::endl; |
| } |
| break; |
| } |
| case disk_cache_fuzzer::FuzzCommand::kFastForwardBy: { |
| base::TimeDelta to_wait = base::TimeDelta::FromMilliseconds( |
| command.fast_forward_by().capped_num_millis() % |
| kMaxNumMillisToWait); |
| MAYBE_PRINT << "FastForwardBy(" << to_wait << ")" << std::endl; |
| init_globals->scoped_task_environment_->FastForwardBy(to_wait); |
| |
| base::Time curr_time = base::Time::Now(); |
| saved_times_[command.fast_forward_by().time_id()] = curr_time; |
| // MAYBE_PRINT << "Saved time " << curr_time << std::endl; |
| break; |
| } |
| case disk_cache_fuzzer::FuzzCommand::kDoomEntriesSince: { |
| if (!cache_) |
| continue; |
| // App cache does not keep track of LRU timestamps so this method cannot |
| // be used. |
| if (type == net::APP_CACHE) |
| continue; |
| if (saved_times_.empty()) |
| continue; |
| |
| const disk_cache_fuzzer::DoomEntriesSince& des = |
| command.doom_entries_since(); |
| auto time_it = GetNextValue(&saved_times_, des.time_id()); |
| bool async = des.async(); |
| |
| net::TestCompletionCallback tcb; |
| net::CompletionOnceCallback cb = |
| !async ? tcb.callback() : GetIOCallback(IOType::DoomEntriesSince); |
| |
| MAYBE_PRINT << "DoomEntriesSince(" << time_it->second << ")" |
| << std::flush; |
| int rv = cache_->DoomEntriesSince(time_it->second, std::move(cb)); |
| if (!async) |
| rv = tcb.GetResult(rv); |
| MAYBE_PRINT << " = " << rv << std::endl; |
| break; |
| } |
| case disk_cache_fuzzer::FuzzCommand::kDoomEntriesBetween: { |
| if (!cache_) |
| continue; |
| // App cache does not keep track of LRU timestamps so this method cannot |
| // be used. |
| if (type == net::APP_CACHE) |
| continue; |
| if (saved_times_.empty()) |
| continue; |
| |
| const disk_cache_fuzzer::DoomEntriesBetween& deb = |
| command.doom_entries_between(); |
| auto time_it1 = GetNextValue(&saved_times_, deb.time_id1()); |
| auto time_it2 = GetNextValue(&saved_times_, deb.time_id2()); |
| base::Time time1 = time_it1->second; |
| base::Time time2 = time_it2->second; |
| if (time1 > time2) |
| std::swap(time1, time2); |
| bool async = deb.async(); |
| |
| net::TestCompletionCallback tcb; |
| net::CompletionOnceCallback cb = |
| !async ? tcb.callback() : GetIOCallback(IOType::DoomEntriesBetween); |
| |
| MAYBE_PRINT << "DoomEntriesBetween(" << time1 << ", " << time2 << ")" |
| << std::flush; |
| int rv = cache_->DoomEntriesBetween(time1, time2, std::move(cb)); |
| if (!async) |
| rv = tcb.GetResult(rv); |
| MAYBE_PRINT << " = " << rv << std::endl; |
| break; |
| } |
| case disk_cache_fuzzer::FuzzCommand::kOnExternalCacheHit: { |
| if (!cache_) |
| continue; |
| if (created_cache_entries_.empty()) |
| continue; |
| |
| uint64_t key_id = command.on_external_cache_hit().key_id(); |
| |
| auto key_it = GetNextValue(&created_cache_entries_, key_id); |
| MAYBE_PRINT << "OnExternalCacheHit(\"" << key_it->second << "\")" |
| << std::endl; |
| cache_->OnExternalCacheHit(key_it->second); |
| break; |
| } |
| case disk_cache_fuzzer::FuzzCommand::kTrimForTest: { |
| // Blockfile-cache specific method. |
| if (!block_impl_ || type != net::DISK_CACHE) |
| return; |
| |
| MAYBE_PRINT << "TrimForTest()" << std::endl; |
| |
| RunTaskForTest(base::BindOnce(&disk_cache::BackendImpl::TrimForTest, |
| base::Unretained(block_impl_), |
| command.trim_for_test().empty())); |
| break; |
| } |
| case disk_cache_fuzzer::FuzzCommand::kTrimDeletedListForTest: { |
| // Blockfile-cache specific method. |
| if (!block_impl_ || type != net::DISK_CACHE) |
| return; |
| |
| MAYBE_PRINT << "TrimDeletedListForTest()" << std::endl; |
| |
| RunTaskForTest( |
| base::BindOnce(&disk_cache::BackendImpl::TrimDeletedListForTest, |
| base::Unretained(block_impl_), |
| command.trim_deleted_list_for_test().empty())); |
| break; |
| } |
| case disk_cache_fuzzer::FuzzCommand::kGetAvailableRange: { |
| if (open_cache_entries_.empty()) |
| continue; |
| |
| const disk_cache_fuzzer::GetAvailableRange& gar = |
| command.get_available_range(); |
| auto entry_it = GetNextValue(&open_cache_entries_, gar.entry_id()); |
| if (!IsValidEntry(&entry_it->second) || |
| !sparse_entry_tracker_[entry_it->second.entry_ptr]) |
| continue; |
| |
| disk_cache::Entry* entry = entry_it->second.entry_ptr; |
| uint32_t offset = gar.offset() % kMaxEntrySize; |
| uint32_t len = gar.len() % kMaxEntrySize; |
| bool async = gar.async(); |
| auto start = base::MakeRefCounted<base::RefCountedData<int64_t>>(); |
| // Raw pointer will stay alive until the end of this command for sure, |
| // as we hold a reference to the object. |
| int64_t* start_tmp = &start->data; |
| |
| auto result_checker = base::BindRepeating( |
| [](net::CompletionOnceCallback callback, |
| scoped_refptr<base::RefCountedData<int64_t>> start, |
| uint32_t offset, uint32_t len, int rv) { |
| std::move(callback).Run(rv); |
| |
| int64_t* start_tmp = &start->data; |
| CHECK_LE(offset, *start_tmp); |
| CHECK_LE(*start_tmp, offset + len); |
| CHECK_LE(*start_tmp + rv, offset + len); |
| // Offsets are capped by kMaxEntrySize |
| CHECK_LE(*start_tmp, kMaxEntrySize); |
| // And size are also capped by kMaxEntrySize |
| CHECK_LE(*start_tmp + rv, kMaxEntrySize * 2); |
| }, |
| GetIOCallback(IOType::GetAvailableRange), start, offset, len); |
| |
| net::TestCompletionCallback tcb; |
| net::CompletionOnceCallback cb = |
| !async ? tcb.callback() : result_checker; |
| |
| MAYBE_PRINT << "GetAvailableRange(\"" << entry->GetKey() << "\", " |
| << offset << ", " << len << ")" << std::flush; |
| int rv = |
| entry->GetAvailableRange(offset, len, start_tmp, std::move(cb)); |
| |
| if (rv != net::ERR_IO_PENDING) { |
| // Run the checker callback ourselves. |
| result_checker.Run(rv); |
| } else if (!async) { |
| // In this case the callback will be run by the backend, so we don't |
| // need to do it manually. |
| rv = tcb.GetResult(rv); |
| } |
| |
| // Finally, take care of printing. |
| if (async && rv == net::ERR_IO_PENDING) { |
| MAYBE_PRINT << " = net::ERR_IO_PENDING (async)" << std::endl; |
| } else { |
| MAYBE_PRINT << " = " << rv << ", *start = " << *start_tmp; |
| if (rv < 0) { |
| MAYBE_PRINT << ", error to string: " << net::ErrorToShortString(rv) |
| << std::endl; |
| } else { |
| MAYBE_PRINT << std::endl; |
| } |
| } |
| break; |
| } |
| case disk_cache_fuzzer::FuzzCommand::kCancelSparseIo: { |
| if (open_cache_entries_.empty()) |
| continue; |
| |
| const disk_cache_fuzzer::CancelSparseIO& csio = |
| command.cancel_sparse_io(); |
| auto entry_it = GetNextValue(&open_cache_entries_, csio.entry_id()); |
| if (!IsValidEntry(&entry_it->second)) |
| continue; |
| |
| MAYBE_PRINT << "CancelSparseIO(\"" |
| << entry_it->second.entry_ptr->GetKey() << "\")" |
| << std::endl; |
| entry_it->second.entry_ptr->CancelSparseIO(); |
| break; |
| } |
| case disk_cache_fuzzer::FuzzCommand::kDoomKey: { |
| if (!cache_) |
| continue; |
| if (created_cache_entries_.empty()) |
| continue; |
| |
| const disk_cache_fuzzer::DoomKey& dk = command.doom_key(); |
| uint64_t key_id = dk.key_id(); |
| net::RequestPriority pri = GetRequestPriority(dk.pri()); |
| bool async = dk.async(); |
| |
| auto key_it = GetNextValue(&created_cache_entries_, key_id); |
| |
| net::TestCompletionCallback tcb; |
| net::CompletionOnceCallback cb = |
| !async ? tcb.callback() : GetIOCallback(IOType::DoomKey); |
| |
| MAYBE_PRINT << "DoomKey(\"" << key_it->second << "\")" << std::flush; |
| int rv = cache_->DoomEntry(key_it->second, pri, std::move(cb)); |
| if (!async) |
| rv = tcb.GetResult(rv); |
| MAYBE_PRINT << " = " << rv << std::endl; |
| |
| break; |
| } |
| case disk_cache_fuzzer::FuzzCommand::kDestructBackend: { |
| // Block_impl_ will leak if we destruct the backend without closing |
| // previous entries. |
| // TODO(mpdenton) consider creating a separate fuzz target that allows |
| // closing the |block_impl_| and ignore leaks. |
| if (block_impl_ || !cache_) |
| continue; |
| |
| const disk_cache_fuzzer::DestructBackend& db = |
| command.destruct_backend(); |
| // Only sometimes actually destruct the backend. |
| if (!db.actually_destruct1() || !db.actually_destruct2()) |
| continue; |
| |
| MAYBE_PRINT << "~Backend(). Backend destruction." << std::endl; |
| cache_.reset(); |
| break; |
| } |
| case disk_cache_fuzzer::FuzzCommand::kAddRealDelay: { |
| if (!command.add_real_delay().actually_delay()) |
| continue; |
| |
| MAYBE_PRINT << "AddRealDelay(1ms)" << std::endl; |
| base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(1)); |
| break; |
| } |
| case disk_cache_fuzzer::FuzzCommand::FUZZ_COMMAND_ONEOF_NOT_SET: { |
| continue; |
| break; |
| } |
| } |
| } |
| } |
| |
| void DiskCacheLPMFuzzer::HandleSetMaxSize( |
| const disk_cache_fuzzer::SetMaxSize& sms) { |
| if (!cache_) |
| return; |
| |
| max_size_ = sms.size(); |
| max_size_ %= kMaxSizeKB; |
| max_size_ *= 1024; |
| MAYBE_PRINT << "SetMaxSize(" << max_size_ << ")" << std::endl; |
| if (simple_cache_impl_) |
| CHECK_EQ(true, simple_cache_impl_->SetMaxSize(max_size_)); |
| |
| if (block_impl_) |
| CHECK_EQ(true, block_impl_->SetMaxSize(max_size_)); |
| |
| if (mem_cache_) |
| CHECK_EQ(true, mem_cache_->SetMaxSize(max_size_)); |
| } |
| |
| void DiskCacheLPMFuzzer::CreateBackend( |
| disk_cache_fuzzer::FuzzCommands::CacheBackend cache_backend, |
| uint32_t mask, |
| net::CacheType type, |
| bool simple_cache_wait_for_index) { |
| if (cache_backend == disk_cache_fuzzer::FuzzCommands::IN_MEMORY) { |
| MAYBE_PRINT << "Using in-memory cache." << std::endl; |
| mem_cache_ = new disk_cache::MemBackendImpl(nullptr); |
| cache_.reset(mem_cache_); |
| CHECK(cache_); |
| } else if (cache_backend == disk_cache_fuzzer::FuzzCommands::SIMPLE) { |
| MAYBE_PRINT << "Using simple cache." << std::endl; |
| net::TestCompletionCallback cb; |
| // We limit ourselves to 64 fds since OS X by default gives us 256. |
| // (Chrome raises the number on startup, but the fuzzer doesn't). |
| if (!simple_file_tracker_) |
| simple_file_tracker_ = |
| std::make_unique<disk_cache::SimpleFileTracker>(kMaxFdsSimpleCache); |
| std::unique_ptr<disk_cache::SimpleBackendImpl> simple_backend = |
| std::make_unique<disk_cache::SimpleBackendImpl>( |
| cache_path_, /* cleanup_tracker = */ nullptr, |
| simple_file_tracker_.get(), max_size_, type, |
| /*net_log = */ nullptr); |
| int rv = simple_backend->Init(cb.callback()); |
| CHECK_EQ(cb.GetResult(rv), net::OK); |
| simple_cache_impl_ = simple_backend.get(); |
| cache_ = std::move(simple_backend); |
| |
| if (simple_cache_wait_for_index) { |
| MAYBE_PRINT << "Waiting for simple cache index to be ready..." |
| << std::endl; |
| net::TestCompletionCallback wait_for_index_cb; |
| rv = simple_cache_impl_->index()->ExecuteWhenReady( |
| wait_for_index_cb.callback()); |
| CHECK_EQ(wait_for_index_cb.GetResult(rv), net::OK); |
| } |
| } else { |
| MAYBE_PRINT << "Using blockfile cache"; |
| |
| if (mask) { |
| MAYBE_PRINT << ", mask = " << mask << std::endl; |
| block_impl_ = new disk_cache::BackendImpl(cache_path_, mask, |
| /* runner = */ nullptr, type, |
| /* net_log = */ nullptr); |
| } else { |
| MAYBE_PRINT << "." << std::endl; |
| block_impl_ = new disk_cache::BackendImpl(cache_path_, |
| /* cleanup_tracker = */ nullptr, |
| /* runner = */ nullptr, type, |
| /* net_log = */ nullptr); |
| } |
| cache_.reset(block_impl_); |
| CHECK(cache_); |
| // TODO(mpdenton) kNoRandom or not? It does a lot of waiting for IO. May be |
| // good for avoiding leaks but tests a less realistic cache. |
| // block_impl_->SetFlags(disk_cache::kNoRandom); |
| |
| // TODO(mpdenton) should I always wait here? |
| net::TestCompletionCallback cb; |
| int rv = block_impl_->Init(cb.callback()); |
| CHECK_EQ(cb.GetResult(rv), net::OK); |
| } |
| } |
| |
| void DiskCacheLPMFuzzer::CloseAllRemainingEntries() { |
| for (auto& entry_info : open_cache_entries_) { |
| disk_cache::Entry** entry_ptr = &entry_info.second.entry_ptr; |
| if (!*entry_ptr) |
| continue; |
| MAYBE_PRINT << "Destructor CloseEntry(\"" << (*entry_ptr)->GetKey() << "\")" |
| << std::endl; |
| (*entry_ptr)->Close(); |
| *entry_ptr = nullptr; |
| } |
| } |
| |
| DiskCacheLPMFuzzer::~DiskCacheLPMFuzzer() { |
| // |block_impl_| leaks a lot more if we don't close entries before destructing |
| // the backend. |
| if (block_impl_) { |
| // TODO(mpdenton) Consider creating a fuzz target that does not wait for |
| // blockfile, and also does not detect leaks. |
| |
| // Because the blockfile backend will leak any entries closed after its |
| // destruction, we need to wait for any remaining backend callbacks to |
| // finish. Otherwise, there will always be a race between handling callbacks |
| // with RunUntilIdle() and actually closing all of the remaining entries. |
| // And, closing entries after destructing the backend will not work and |
| // cause leaks. |
| for (auto& entry_it : open_cache_entries_) { |
| if (entry_it.second.tcb) { |
| WaitOnEntry(&entry_it.second); |
| } |
| } |
| |
| // Destroy any open iterators before destructing the backend so we don't |
| // cause leaks. TODO(mpdenton) should maybe be documented? |
| // Also *must* happen after waiting for all OpenNextEntry callbacks to |
| // finish, because destructing the iterators may cause those callbacks to be |
| // cancelled, which will cause WaitOnEntry() to spin forever waiting. |
| // TODO(mpdenton) should also be documented? |
| open_iterators_.clear(); |
| // Just in case, finish any callbacks. |
| init_globals->scoped_task_environment_->RunUntilIdle(); |
| // Close all entries that haven't been closed yet. |
| CloseAllRemainingEntries(); |
| // Destroy the backend. |
| cache_.reset(); |
| } else { |
| // Here we won't bother with waiting for our OpenEntry* callbacks. |
| cache_.reset(); |
| // Finish any callbacks that came in before backend destruction. |
| init_globals->scoped_task_environment_->RunUntilIdle(); |
| // Close all entries that haven't been closed yet. |
| CloseAllRemainingEntries(); |
| } |
| |
| // Make sure any tasks triggered by the CloseEntry's have run. |
| init_globals->scoped_task_environment_->RunUntilIdle(); |
| if (simple_cache_impl_) |
| CHECK(simple_file_tracker_->IsEmptyForTesting()); |
| base::RunLoop().RunUntilIdle(); |
| |
| DeleteCache(cache_path_); |
| } |
| |
| DEFINE_BINARY_PROTO_FUZZER(const disk_cache_fuzzer::FuzzCommands& commands) { |
| { |
| DiskCacheLPMFuzzer disk_cache_fuzzer_instance; |
| disk_cache_fuzzer_instance.RunCommands(commands); |
| } |
| MAYBE_PRINT << "-----------------------" << std::endl; |
| } |
| // |