123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284 |
- // 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/memory/ref_counted.h"
- #include "base/memory/scoped_refptr.h"
- #include "base/numerics/checked_math.h"
- #include "base/strings/string_number_conversions.h"
- #include "base/test/task_environment.h"
- #include "base/test/test_timeouts.h"
- #include "base/time/time.h"
- #include "net/base/cache_type.h"
- #include "net/base/interval.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;
- // Known colliding key values taken from SimpleCacheCreateCollision unittest.
- const std::string kCollidingKey1 =
- "\xfb\x4e\x9c\x1d\x66\x71\xf7\x54\xa3\x11\xa0\x7e\x16\xa5\x68\xf6";
- const std::string kCollidingKey2 =
- "\xbc\x60\x64\x92\xbc\xa0\x5c\x15\x17\x93\x29\x2d\xe4\x21\xbd\x03";
- #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");
- // TaskEnvironment requires TestTimeouts initialization to watch for
- // problematic long-running tasks.
- TestTimeouts::Initialize();
- // Mark this thread as an IO_THREAD with MOCK_TIME, and ensure that Now()
- // is driven from the same mock clock.
- task_environment_ = std::make_unique<base::test::TaskEnvironment>(
- base::test::TaskEnvironment::MainThreadType::IO,
- base::test::TaskEnvironment::TimeSource::MOCK_TIME);
- // 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::TaskEnvironment> 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;
- EntryInfo(const EntryInfo&) = delete;
- EntryInfo& operator=(const EntryInfo&) = delete;
- disk_cache::Entry* entry_ptr = nullptr;
- std::unique_ptr<TestEntryResultCompletionCallback> tcb;
- };
- 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_error != net::ERR_IO_PENDING (and would never have
- // called the callback).
- disk_cache::EntryResult WaitOnEntry(
- EntryInfo* ei,
- disk_cache::EntryResult result =
- disk_cache::EntryResult::MakeError(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(uint64_t entry_id,
- bool async,
- bool set_is_sparse,
- disk_cache::EntryResult result);
- // 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) {
- // Use one of the two colliding key values in 1% of executions.
- if (key_num % 100 == 99)
- return kCollidingKey1;
- if (key_num % 100 == 98)
- return kCollidingKey2;
- // Otherwise, use a value based on the key id and fuzzy padding.
- std::string padding(key_num & 0xFFFF, 'A');
- return "Key" + padding + base::NumberToString(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::REMOVED_MEDIA_CACHE:
- // Media cache no longer in use; handle as HTTP_CACHE
- MAYBE_PRINT << "Cache type = REMOVED_MEDIA_CACHE." << std::endl;
- return net::CacheType::DISK_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_BYTE_CODE_CACHE:
- MAYBE_PRINT << "Cache type = GENERATED_BYTE_CODE_CACHE." << std::endl;
- return net::CacheType::GENERATED_BYTE_CODE_CACHE;
- break;
- case disk_cache_fuzzer::FuzzCommands::GENERATED_NATIVE_CODE_CACHE:
- MAYBE_PRINT << "Cache type = GENERATED_NATIVE_CODE_CACHE." << std::endl;
- return net::CacheType::GENERATED_NATIVE_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(
- uint64_t entry_id,
- bool async,
- bool set_is_sparse,
- disk_cache::EntryResult result) {
- // 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];
- if (async) {
- int rv = result.net_error();
- ei->entry_ptr = result.ReleaseEntry();
- // We are responsible for setting things up.
- if (set_is_sparse && ei->entry_ptr) {
- sparse_entry_tracker_[ei->entry_ptr] = true;
- }
- if (ei->entry_ptr) {
- MAYBE_PRINT << " [Async opening of cache entry for \""
- << ei->entry_ptr->GetKey() << "\" callback (rv = " << rv
- << ")]" << std::endl;
- }
- // Unblock any subsequent ops waiting for this --- they don't care about
- // the actual return value, but use something distinctive for debugging.
- ei->tcb->callback().Run(
- disk_cache::EntryResult::MakeError(net::ERR_FILE_VIRUS_INFECTED));
- } else {
- // The operation code will pull the result out of the completion callback,
- // so hand it to it.
- ei->tcb->callback().Run(std::move(result));
- }
- }
- disk_cache::EntryResult DiskCacheLPMFuzzer::WaitOnEntry(
- EntryInfo* ei,
- disk_cache::EntryResult result) {
- CHECK(ei->tcb);
- result = ei->tcb->GetResult(std::move(result));
- // Reset the callback so nobody accidentally waits on a callback that never
- // comes.
- ei->tcb.reset();
- return result;
- }
- 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.
- WaitOnEntry(ei);
- }
- // 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) {
- // Skip too long command sequences, they are counterproductive for fuzzing.
- // The number was chosen empirically using the existing fuzzing corpus.
- if (commands.fuzz_commands_size() > 129)
- return;
- 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->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;
- EntryInfo* entry_info = &open_cache_entries_[entry_id];
- entry_info->tcb = std::make_unique<TestEntryResultCompletionCallback>();
- disk_cache::EntryResultCallback cb =
- base::BindOnce(&DiskCacheLPMFuzzer::OpenCacheEntryCallback,
- base::Unretained(this), entry_id, async, is_sparse);
- MAYBE_PRINT << "CreateEntry(\"" << key_str
- << "\", set_is_sparse = " << is_sparse
- << ") = " << std::flush;
- disk_cache::EntryResult result =
- cache_->CreateEntry(key_str, pri, std::move(cb));
- if (!async || result.net_error() != net::ERR_IO_PENDING) {
- result = WaitOnEntry(entry_info, std::move(result));
- int rv = result.net_error();
- // Ensure we mark sparsity, save entry if the callback never ran.
- if (rv == net::OK) {
- entry_info->entry_ptr = result.ReleaseEntry();
- sparse_entry_tracker_[entry_info->entry_ptr] = is_sparse;
- }
- MAYBE_PRINT << rv << std::endl;
- } else {
- 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.
- EntryInfo* entry_info = &open_cache_entries_[entry_id];
- entry_info->tcb = std::make_unique<TestEntryResultCompletionCallback>();
- disk_cache::EntryResultCallback cb =
- base::BindOnce(&DiskCacheLPMFuzzer::OpenCacheEntryCallback,
- base::Unretained(this), entry_id, async, false);
- auto key_it = GetNextValue(&created_cache_entries_, key_id);
- MAYBE_PRINT << "OpenEntry(\"" << key_it->second
- << "\") = " << std::flush;
- disk_cache::EntryResult result =
- cache_->OpenEntry(key_it->second, pri, std::move(cb));
- if (!async || result.net_error() != net::ERR_IO_PENDING) {
- result = WaitOnEntry(entry_info, std::move(result));
- int rv = result.net_error();
- if (rv == net::OK)
- entry_info->entry_ptr = result.ReleaseEntry();
- 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.
- EntryInfo* entry_info = &open_cache_entries_[entry_id];
- entry_info->tcb = std::make_unique<TestEntryResultCompletionCallback>();
- disk_cache::EntryResultCallback cb =
- base::BindOnce(&DiskCacheLPMFuzzer::OpenCacheEntryCallback,
- base::Unretained(this), 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;
- disk_cache::EntryResult result =
- cache_->OpenOrCreateEntry(key_str, pri, std::move(cb));
- if (!async || result.net_error() != net::ERR_IO_PENDING) {
- result = WaitOnEntry(entry_info, std::move(result));
- int rv = result.net_error();
- bool opened = result.opened();
- entry_info->entry_ptr = result.ReleaseEntry();
- // Ensure we mark sparsity, even if the callback never ran.
- if (rv == net::OK && !opened)
- sparse_entry_tracker_[entry_info->entry_ptr] = is_sparse;
- MAYBE_PRINT << rv << ", opened = " << 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);
- EntryInfo* entry_info = &open_cache_entries_[entry_id];
- entry_info->tcb = std::make_unique<TestEntryResultCompletionCallback>();
- disk_cache::EntryResultCallback cb =
- base::BindOnce(&DiskCacheLPMFuzzer::OpenCacheEntryCallback,
- base::Unretained(this), entry_id, async, false);
- MAYBE_PRINT << "Iterator(" << ione.it_id()
- << ").OpenNextEntry() = " << std::flush;
- disk_cache::EntryResult result =
- iterator_it->second->OpenNextEntry(std::move(cb));
- if (!async || result.net_error() != net::ERR_IO_PENDING) {
- result = WaitOnEntry(entry_info, std::move(result));
- int rv = result.net_error();
- entry_info->entry_ptr = result.ReleaseEntry();
- // 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::Milliseconds(command.fast_forward_by().capped_num_millis() %
- kMaxNumMillisToWait);
- MAYBE_PRINT << "FastForwardBy(" << to_wait << ")" << std::endl;
- init_globals->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 result_checker = base::BindRepeating(
- [](net::CompletionOnceCallback callback, uint32_t offset,
- uint32_t len, const disk_cache::RangeResult& result) {
- std::move(callback).Run(result.net_error);
- if (result.net_error <= 0)
- return;
- // Make sure that the result is contained in what was
- // requested. It doesn't have to be the same even if there was
- // an exact corresponding write, since representation of ranges
- // may be imprecise, and here we don't know that there was.
- // No overflow thanks to % kMaxEntrySize.
- net::Interval<uint32_t> requested(offset, offset + len);
- uint32_t range_start, range_end;
- base::CheckedNumeric<uint64_t> range_start64(result.start);
- CHECK(range_start64.AssignIfValid(&range_start));
- base::CheckedNumeric<uint64_t> range_end64 =
- range_start + result.available_len;
- CHECK(range_end64.AssignIfValid(&range_end));
- net::Interval<uint32_t> gotten(range_start, range_end);
- CHECK(requested.Contains(gotten));
- },
- GetIOCallback(IOType::GetAvailableRange), offset, len);
- TestRangeResultCompletionCallback tcb;
- disk_cache::RangeResultCallback cb =
- !async ? tcb.callback() : result_checker;
- MAYBE_PRINT << "GetAvailableRange(\"" << entry->GetKey() << "\", "
- << offset << ", " << len << ")" << std::flush;
- disk_cache::RangeResult result =
- entry->GetAvailableRange(offset, len, std::move(cb));
- if (result.net_error != net::ERR_IO_PENDING) {
- // Run the checker callback ourselves.
- result_checker.Run(result);
- } else if (!async) {
- // In this case the callback will be run by the backend, so we don't
- // need to do it manually.
- result = tcb.GetResult(result);
- }
- // Finally, take care of printing.
- if (async && result.net_error == net::ERR_IO_PENDING) {
- MAYBE_PRINT << " = net::ERR_IO_PENDING (async)" << std::endl;
- } else {
- MAYBE_PRINT << " = " << result.net_error
- << ", start = " << result.start
- << ", available_len = " << result.available_len;
- if (result.net_error < 0) {
- MAYBE_PRINT << ", error to string: "
- << net::ErrorToShortString(result.net_error)
- << 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::Milliseconds(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;
- auto cache = std::make_unique<disk_cache::MemBackendImpl>(nullptr);
- mem_cache_ = cache.get();
- cache_ = std::move(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);
- auto simple_backend = std::make_unique<disk_cache::SimpleBackendImpl>(
- /*file_operations=*/nullptr, cache_path_,
- /*cleanup_tracker=*/nullptr, simple_file_tracker_.get(), max_size_,
- type, /*net_log=*/nullptr);
- simple_backend->Init(cb.callback());
- CHECK_EQ(cb.WaitForResult(), 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;
- simple_cache_impl_->index()->ExecuteWhenReady(
- wait_for_index_cb.callback());
- int rv = wait_for_index_cb.WaitForResult();
- CHECK_EQ(rv, net::OK);
- }
- } else {
- MAYBE_PRINT << "Using blockfile cache";
- std::unique_ptr<disk_cache::BackendImpl> cache;
- if (mask) {
- MAYBE_PRINT << ", mask = " << mask << std::endl;
- cache = std::make_unique<disk_cache::BackendImpl>(
- cache_path_, mask,
- /* runner = */ nullptr, type,
- /* net_log = */ nullptr);
- } else {
- MAYBE_PRINT << "." << std::endl;
- cache = std::make_unique<disk_cache::BackendImpl>(
- cache_path_,
- /* cleanup_tracker = */ nullptr,
- /* runner = */ nullptr, type,
- /* net_log = */ nullptr);
- }
- block_impl_ = cache.get();
- cache_ = std::move(cache);
- 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;
- block_impl_->Init(cb.callback());
- CHECK_EQ(cb.WaitForResult(), 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->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->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->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;
- }
- //
|