blob: 7567f2599ca7445dc556e58e376a737bca40a63e [file]
// Copyright 2022 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.
#ifndef FUZZTEST_FUZZTEST_INTERNAL_DOMAINS_VALUE_MUTATION_HELPERS_H_
#define FUZZTEST_FUZZTEST_INTERNAL_DOMAINS_VALUE_MUTATION_HELPERS_H_
#include <cstddef>
#include <limits>
#include <optional>
#include "absl/numeric/bits.h"
#include "absl/numeric/int128.h"
#include "absl/random/bit_gen_ref.h"
#include "absl/random/random.h"
#include "./fuzztest/internal/domains/mutation_metadata.h"
#include "./fuzztest/internal/meta.h"
#include "./fuzztest/internal/table_of_recent_compares.h"
namespace fuzztest::internal {
// Random bit flip: minimal mutation to a field, it will converge
// the hamming distance of a value to its target in constant steps.
template <typename T>
void RandomBitFlip(absl::BitGenRef prng, T& val, size_t range) {
using U = MakeUnsignedT<T>;
U u = static_cast<U>(val);
u ^= U{1} << absl::Uniform(prng, 0u, range);
val = static_cast<T>(u);
}
// BitWidth of the value of val, specially handle uint12 and int128, because
// they don't have overloads in absl::bit_width.
template <typename T>
size_t BitWidth(T val) {
if constexpr (std::is_same_v<T, absl::int128> ||
std::is_same_v<T, absl::uint128>) {
auto val_unsigned = MakeUnsignedT<T>(val);
size_t res = 0;
while (val_unsigned >>= 1) ++res;
return res;
} else {
return absl::bit_width(static_cast<MakeUnsignedT<T>>(val));
}
}
// Given a parameter pack of functions `f`, run exactly one of the functions.
template <typename... F>
void RunOne(absl::BitGenRef prng, F... f) {
ApplyIndex<sizeof...(F)>([&](auto... I) {
int i = absl::Uniform<int>(prng, 0, sizeof...(F));
((i == I ? (void)f() : (void)0), ...);
});
}
template <typename T, size_t N, typename F>
T ChooseOneOr(const T (&values)[N], absl::BitGenRef prng, F f) {
const auto i = absl::Uniform<size_t>(absl::IntervalClosedClosed, prng, 0, N);
return i == N ? f() : values[i];
}
template <typename C, typename F>
auto ChooseOneOr(const C& values, absl::BitGenRef prng, F f) {
const auto i =
absl::Uniform<size_t>(absl::IntervalClosedClosed, prng, 0, values.size());
return i < values.size() ? values[i] : f();
}
template <typename T>
T SampleFromUniformRange(absl::BitGenRef prng, T min, T max) {
return absl::Uniform(absl::IntervalClosedClosed, prng, min, max);
}
// Randomly apply Randomwalk or Uniform distribution or dictionary to mutate
// the val:
// RandomWalk: converge the absolute distance of a value to its target
// more efficiently.
// Uniform Distribution: go across some non-linear boundary that cannot
// be solved by bit flipping or randomwalk.
// Dictionary: if applicable, choose randomly from the dictionary. if
// dictionary fails to mutate, fall back to uniform.
template <unsigned char RANGE, typename T, typename IntegerDictionaryT>
void RandomWalkOrUniformOrDict(absl::BitGenRef prng, T& val, T min, T max,
domain_implementor::ConstCmpTablesPtr cmp_tables,
const IntegerDictionaryT& temporary_dict,
const IntegerDictionaryT& permanent_dict,
std::optional<T>& permanent_dict_candidate) {
constexpr bool is_memory_dictionary_compatible_integer =
std::numeric_limits<T>::is_integer &&
(sizeof(T) == 1 || sizeof(T) == 2 || sizeof(T) == 4 || sizeof(T) == 8);
const bool can_use_memory_dictionary =
is_memory_dictionary_compatible_integer && cmp_tables != nullptr;
const int action_count = 2 + can_use_memory_dictionary;
int action = absl::Uniform(prng, 0, action_count);
// Random walk.
if (action-- == 0) {
if (max / 2 - min / 2 <= RANGE) {
val = SampleFromUniformRange(prng, min, max);
} else {
T lo = min;
T hi = max;
if (val > lo + RANGE) lo = val - RANGE;
if (val < hi - RANGE) hi = val + RANGE;
val = SampleFromUniformRange(prng, lo, hi);
}
return;
}
// Random choose.
if (action-- == 0) {
val = SampleFromUniformRange(prng, min, max);
return;
}
// Dictionary
if constexpr (is_memory_dictionary_compatible_integer) {
if (can_use_memory_dictionary) {
if (action-- == 0) {
RunOne(
prng,
[&] {
if (temporary_dict.IsEmpty()) {
val = SampleFromUniformRange(prng, min, max);
} else {
val = temporary_dict.GetRandomSavedEntry(prng);
permanent_dict_candidate = val;
}
},
[&] {
if (permanent_dict.IsEmpty()) {
val = SampleFromUniformRange(prng, min, max);
} else {
val = permanent_dict.GetRandomSavedEntry(prng);
}
},
[&] {
auto entry = IntegerDictionary<T>::GetRandomTORCEntry(
val, prng, *cmp_tables, min, max);
if (entry.has_value()) {
val = *entry;
} else {
val = SampleFromUniformRange(prng, min, max);
}
});
}
}
}
}
// REQUIRES: |target| < |val|
template <typename T>
T ShrinkTowards(absl::BitGenRef prng, T val, T target) {
if (val < target) {
return absl::Uniform(absl::IntervalOpenClosed, prng, val, target);
} else {
return absl::Uniform(absl::IntervalClosedOpen, prng, target, val);
}
}
} // namespace fuzztest::internal
#endif // FUZZTEST_FUZZTEST_INTERNAL_DOMAINS_VALUE_MUTATION_HELPERS_H_