blob: 0be2fb924b4410a7ab0e7741c8ff9a8a970eea93 [file] [log] [blame]
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "./fuzztest/fuzzing_bit_gen.h"
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <limits>
#include <utility>
#include "absl/base/fast_type_id.h"
#include "absl/base/no_destructor.h"
#include "absl/container/flat_hash_map.h"
#include "absl/numeric/bits.h"
#include "absl/numeric/int128.h"
#include "absl/types/span.h"
#include "./fuzztest/internal/fuzzing_mock_stream.h"
#include "./fuzztest/internal/register_absl_fuzzing_mocks.h"
namespace fuzztest {
namespace {
using FuzzingMockStream = ::fuzztest::internal::FuzzingMockStream;
using Instruction = FuzzingMockStream::Instruction;
// Minimal implementation of a PCG64 engine equivalent to xsl_rr_128_64.
inline constexpr absl::uint128 multiplier() {
return absl::MakeUint128(0x2360ed051fc65da4, 0x4385df649fccf645);
}
inline constexpr absl::uint128 increment() {
return absl::MakeUint128(0x5851f42d4c957f2d, 0x14057b7ef767814f);
}
inline absl::uint128 lcg(absl::uint128 s) {
return s * multiplier() + increment();
}
inline uint64_t mix(absl::uint128 state) {
uint64_t h = absl::Uint128High64(state);
uint64_t rotate = h >> 58u;
uint64_t s = absl::Uint128Low64(state) ^ h;
return absl::rotr(s, rotate);
}
inline uint64_t GetSeedFromDataStream(absl::Span<const uint8_t>& data_stream) {
// Seed the internal URBG with the first 8 bytes of the data stream.
uint64_t stream_seed = 0x6C7FD535EDC7A62D;
if (!data_stream.empty()) {
size_t num_bytes = std::min(sizeof(stream_seed), data_stream.size());
std::memcpy(&stream_seed, data_stream.data(), num_bytes);
data_stream.remove_prefix(num_bytes);
}
return stream_seed;
}
} // namespace
FuzzingBitGen::FuzzingBitGen(absl::Span<const uint8_t> data_stream)
: control_stream_({}), data_stream_(data_stream) {
seed(GetSeedFromDataStream(data_stream_));
}
FuzzingBitGen::FuzzingBitGen(absl::Span<const uint8_t> data_stream,
absl::Span<const uint8_t> control_stream,
uint64_t seed_value)
: control_stream_(control_stream), data_stream_(data_stream) {
seed(seed_value);
}
void FuzzingBitGen::DataStreamFn(bool use_lcg, void* result,
size_t result_size) {
if (!use_lcg && !data_stream_.empty()) {
// Consume up to result_size bytes from the data stream and copy to result.
// leaving the remaining bytes unchanged.
size_t n = std::min(result_size, data_stream_.size());
memcpy(result, data_stream_.data(), n);
data_stream_.remove_prefix(n);
return;
}
// The stream is expired. Generate up to 16 bytes from the LCG, and copy to
// result, leaving the remaining bytes unchanged.
//
// NOTE: This will satisfy uniform values up to uint128, however it
// will not fill longer string values.
urbg_state_ = lcg(urbg_state_);
uint64_t x = mix(urbg_state_);
memcpy(result, &x, std::min(result_size, sizeof(x)));
if (result_size > sizeof(x)) {
urbg_state_ = lcg(urbg_state_);
x = mix(urbg_state_);
memcpy(static_cast<uint8_t*>(result) + sizeof(x), &x,
std::min(result_size - sizeof(x), sizeof(x)));
}
}
uint64_t FuzzingBitGen::operator()() {
// Use the control stream to determine the return value.
Instruction instruction =
FuzzingMockStream::GetNextInstruction(control_stream_);
switch (instruction) {
case Instruction::kMin:
return 0;
case Instruction::kMax:
return (std::numeric_limits<uint64_t>::max)();
case Instruction::kMean:
return (std::numeric_limits<uint64_t>::max)() / 2;
default:
break;
}
uint64_t x = 0;
DataStreamFn(instruction == Instruction::kLCGVariate, &x, sizeof(x));
return x;
}
void FuzzingBitGen::seed(result_type seed_value) {
absl::uint128 tmp = seed_value;
urbg_state_ = lcg(tmp + increment());
}
bool FuzzingBitGen::InvokeMock(absl::FastTypeIdType key_id, void* args_tuple,
void* result) {
using FuzzMapT = absl::flat_hash_map<absl::FastTypeIdType,
internal::TypeErasedFuzzFunctionT>;
static const absl::NoDestructor<FuzzMapT> fuzzing_map([]() {
FuzzMapT map;
auto register_fn = [&map](absl::FastTypeIdType key, auto fn) {
map[key] = fn;
};
internal::RegisterAbslRandomFuzzingMocks(register_fn);
return map;
}());
auto it = fuzzing_map->find(key_id);
if (it == fuzzing_map->end()) {
return false;
}
Instruction instruction =
FuzzingMockStream::GetNextInstruction(control_stream_);
bool use_lcg = instruction == Instruction::kLCGVariate;
it->second(FuzzingMockStream(
[this, use_lcg](void* result, size_t n) {
this->DataStreamFn(use_lcg, result, n);
},
instruction),
args_tuple, result);
return true;
}
} // namespace fuzztest