Migrate QpackDecoderFuzzer from FuzzedDataProvider to FuzzTest domains
Anecdotally, this roughly doubles the "runs/secs" metric from ~1200 to ~2000.
PiperOrigin-RevId: 808147546
diff --git a/quiche/quic/core/qpack/fuzzer/qpack_decoder_fuzzer.cc b/quiche/quic/core/qpack/fuzzer/qpack_decoder_fuzzer.cc
index 5ccc117..ed2171f 100644
--- a/quiche/quic/core/qpack/fuzzer/qpack_decoder_fuzzer.cc
+++ b/quiche/quic/core/qpack/fuzzer/qpack_decoder_fuzzer.cc
@@ -2,23 +2,26 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#include <fuzzer/FuzzedDataProvider.h>
-
+#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <iterator>
-#include <limits>
#include <map>
#include <memory>
#include <string>
#include <utility>
+#include <variant>
+#include <vector>
+#include "absl/functional/overload.h"
#include "absl/strings/string_view.h"
#include "quiche/quic/core/qpack/qpack_decoder.h"
+#include "quiche/quic/core/qpack/qpack_progressive_decoder.h"
#include "quiche/quic/core/quic_error_codes.h"
-#include "quiche/quic/test_tools/qpack/qpack_decoder_test_utils.h"
+#include "quiche/quic/core/quic_types.h"
#include "quiche/quic/test_tools/qpack/qpack_test_utils.h"
#include "quiche/common/platform/api/quiche_fuzztest.h"
+#include "quiche/common/platform/api/quiche_logging.h"
namespace quic {
namespace test {
@@ -79,21 +82,34 @@
bool* const error_detected_;
};
+class FuzzerAction {
+ public:
+ struct Decode {
+ std::string encoded_data;
+ };
+ struct CreateProgressiveDecoder {
+ QuicStreamId stream_id;
+ };
+ struct DecodeWithExistingDecoder {
+ size_t distance;
+ std::string encoded_data;
+ };
+ struct EndHeaderBlock {
+ size_t distance;
+ };
+
+ using ActionVariant = std::variant<Decode, CreateProgressiveDecoder,
+ DecodeWithExistingDecoder, EndHeaderBlock>;
+};
+
// This fuzzer exercises QpackDecoder. It should be able to cover all possible
// code paths. There is no point in encoding QpackDecoder's output to turn this
// into a roundtrip test, because the same header list can be encoded in many
// different ways, so the output could not be expected to match the original
// input.
-void DoesNotCrash(const std::vector<uint8_t>& data) {
- FuzzedDataProvider provider(data.data(), data.size());
-
- // Maximum 256 byte dynamic table. Such a small size helps test draining
- // entries and eviction.
- const uint64_t maximum_dynamic_table_capacity =
- provider.ConsumeIntegral<uint8_t>();
- // Maximum 256 blocked streams.
- const uint64_t maximum_blocked_streams = provider.ConsumeIntegral<uint8_t>();
-
+void DoesNotCrash(uint64_t maximum_dynamic_table_capacity,
+ uint64_t maximum_blocked_streams,
+ const std::vector<FuzzerAction::ActionVariant>& actions) {
// |error_detected| will be set to true if an error is encountered either in a
// header block or on the encoder stream.
bool error_detected = false;
@@ -112,86 +128,83 @@
// with corresponding handlers.
DecoderAndHandlerMap processing_decoders;
- // Maximum 256 data fragments to limit runtime and memory usage.
- auto fragment_count = provider.ConsumeIntegral<uint8_t>();
- while (fragment_count > 0 && !error_detected &&
- provider.remaining_bytes() > 0) {
- --fragment_count;
- switch (provider.ConsumeIntegralInRange<uint8_t>(0, 3)) {
- // Feed encoder stream data to QpackDecoder.
- case 0: {
- size_t fragment_size = provider.ConsumeIntegral<uint8_t>();
- std::string encoded_data =
- provider.ConsumeRandomLengthString(fragment_size);
- decoder.encoder_stream_receiver()->Decode(encoded_data);
-
- continue;
- }
-
- // Create new progressive decoder.
- case 1: {
- QuicStreamId stream_id = provider.ConsumeIntegral<uint8_t>();
- if (reading_decoders.find(stream_id) != reading_decoders.end() ||
- processing_decoders.find(stream_id) != processing_decoders.end()) {
- continue;
- }
-
- DecoderAndHandler decoder_and_handler;
- decoder_and_handler.handler = std::make_unique<HeadersHandler>(
- stream_id, &processing_decoders, &error_detected);
- decoder_and_handler.decoder = decoder.CreateProgressiveDecoder(
- stream_id, decoder_and_handler.handler.get());
- reading_decoders.insert({stream_id, std::move(decoder_and_handler)});
-
- continue;
- }
-
- // Feed header block data to existing decoder.
- case 2: {
- if (reading_decoders.empty()) {
- continue;
- }
-
- auto it = reading_decoders.begin();
- auto distance = provider.ConsumeIntegralInRange<uint8_t>(
- 0, reading_decoders.size() - 1);
- std::advance(it, distance);
-
- size_t fragment_size = provider.ConsumeIntegral<uint8_t>();
- std::string encoded_data =
- provider.ConsumeRandomLengthString(fragment_size);
- it->second.decoder->Decode(encoded_data);
-
- continue;
- }
-
- // End header block.
- case 3: {
- if (reading_decoders.empty()) {
- continue;
- }
-
- auto it = reading_decoders.begin();
- auto distance = provider.ConsumeIntegralInRange<uint8_t>(
- 0, reading_decoders.size() - 1);
- std::advance(it, distance);
-
- QpackProgressiveDecoder* reading_decoder = it->second.decoder.get();
-
- // Move DecoderAndHandler to |processing_decoders| first, because
- // EndHeaderBlock() might synchronously call OnDecodingCompleted().
- QuicStreamId stream_id = it->first;
- processing_decoders.insert({stream_id, std::move(it->second)});
- reading_decoders.erase(it);
-
- reading_decoder->EndHeaderBlock();
-
- continue;
- }
+ for (const FuzzerAction::ActionVariant& action : actions) {
+ if (error_detected) {
+ break;
}
+ std::visit(
+ absl::Overload{
+ [&](const FuzzerAction::Decode& params) {
+ // Feed encoder stream data to QpackDecoder.
+ decoder.encoder_stream_receiver()->Decode(params.encoded_data);
+ },
+ [&](const FuzzerAction::CreateProgressiveDecoder& params) {
+ static constexpr QuicStreamId kMaxStreamId = 255;
+
+ // Create new progressive decoder.
+ QuicStreamId stream_id = std::min(params.stream_id, kMaxStreamId);
+ if (reading_decoders.find(stream_id) != reading_decoders.end() ||
+ processing_decoders.find(stream_id) !=
+ processing_decoders.end()) {
+ return;
+ }
+
+ DecoderAndHandler decoder_and_handler;
+ decoder_and_handler.handler = std::make_unique<HeadersHandler>(
+ stream_id, &processing_decoders, &error_detected);
+ decoder_and_handler.decoder = decoder.CreateProgressiveDecoder(
+ stream_id, decoder_and_handler.handler.get());
+ reading_decoders.insert(
+ {stream_id, std::move(decoder_and_handler)});
+ },
+ [&](const FuzzerAction::DecodeWithExistingDecoder& params) {
+ // Feed header block data to existing decoder.
+ if (reading_decoders.empty()) {
+ return;
+ }
+
+ const size_t distance =
+ std::min(params.distance, reading_decoders.size() - 1);
+ auto it = reading_decoders.begin();
+ std::advance(it, distance);
+
+ it->second.decoder->Decode(params.encoded_data);
+ },
+ [&](const FuzzerAction::EndHeaderBlock& params) {
+ if (reading_decoders.empty()) {
+ return;
+ }
+
+ const size_t distance =
+ std::min(params.distance, reading_decoders.size() - 1);
+ auto it = reading_decoders.begin();
+ std::advance(it, distance);
+
+ QpackProgressiveDecoder* reading_decoder =
+ it->second.decoder.get();
+
+ // Move DecoderAndHandler to |processing_decoders| first, because
+ // EndHeaderBlock() might synchronously call
+ // OnDecodingCompleted().
+ QuicStreamId stream_id = it->first;
+ processing_decoders.insert({stream_id, std::move(it->second)});
+ reading_decoders.erase(it);
+
+ reading_decoder->EndHeaderBlock();
+ }},
+ action);
}
}
-FUZZ_TEST(QpackDecoderFuzzer, DoesNotCrash);
+FUZZ_TEST(QpackDecoderFuzzer, DoesNotCrash)
+ .WithDomains(
+ // Maximum 256 byte dynamic table. Such a small size helps test
+ // draining entries and eviction.
+ fuzztest::InRange(0u, 256u),
+ // Maximum 256 blocked streams.
+ fuzztest::InRange(0u, 256u),
+ // Maximum 256 data fragments to limit runtime and memory usage.
+ fuzztest::VectorOf(fuzztest::Arbitrary<FuzzerAction::ActionVariant>())
+ .WithMaxSize(256));
} // namespace test
} // namespace quic