blob: c6c17de754cd343d9a893e3dcef2ba557dff9a04 [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_CONTAINER_MUTATION_HELPERS_H_
#define FUZZTEST_FUZZTEST_INTERNAL_DOMAINS_CONTAINER_MUTATION_HELPERS_H_
#include <algorithm>
#include <cstddef>
#include <iterator>
#include <optional>
#include "absl/random/bit_gen_ref.h"
#include "absl/random/distributions.h"
#include "./fuzztest/internal/coverage.h"
#include "./fuzztest/internal/table_of_recent_compares.h"
namespace fuzztest::internal {
// Trying to copy a segment from `from` to `to`, with given offsets.
// Invalid offset that cause boundary check failures will make this function
// return false. `is_self` tells the function whether `from` and `to` points
// to the same object. Returns `true` iff copying results in `to` being mutated.
template <bool is_self, typename ContainerT>
bool CopyPart(const ContainerT& from, ContainerT& to,
size_t from_segment_start_offset, size_t from_segment_size,
size_t to_segment_start_offset, size_t max_size) {
bool mutated = false;
if (from_segment_size == 0) return mutated;
size_t from_segment_end_offset =
from_segment_start_offset + from_segment_size;
size_t to_segment_end_offset = to_segment_start_offset + from_segment_size;
if (from_segment_start_offset >= from.size() ||
to_segment_start_offset > to.size() ||
from_segment_end_offset > from.size() || to_segment_end_offset > max_size)
return mutated;
if (to_segment_end_offset > to.size()) {
mutated = true;
to.resize(to_segment_end_offset);
} else {
if (!std::equal(std::next(to.begin(), to_segment_start_offset),
std::next(to.begin(), to_segment_end_offset),
std::next(from.begin(), from_segment_start_offset),
std::next(from.begin(), from_segment_end_offset)))
mutated = true;
}
if (!mutated) return mutated;
if constexpr (!is_self) {
std::copy(std::next(from.begin(), from_segment_start_offset),
std::next(from.begin(), from_segment_end_offset),
std::next(to.begin(), to_segment_start_offset));
} else {
ContainerT tmp(std::next(from.begin(), from_segment_start_offset),
std::next(from.begin(), from_segment_end_offset));
std::copy(tmp.begin(), tmp.end(),
std::next(to.begin(), to_segment_start_offset));
}
return mutated;
}
// Trying to insert a segment from `from` to `to`, with given offsets.
// Invalid offset that cause boundary check failures will make this function
// return false. `is_self` tells the function whether `from` and `to` points
// to the same object. Returns `true` iff insertion results in `to` being
// mutated.
template <bool is_self, typename ContainerT>
bool InsertPart(const ContainerT& from, ContainerT& to,
size_t from_segment_start_offset, size_t from_segment_size,
size_t to_segment_start_offset, size_t max_size) {
bool mutated = false;
if (from_segment_size == 0) return mutated;
size_t from_segment_end_offset =
from_segment_start_offset + from_segment_size;
if (from_segment_start_offset >= from.size() ||
to_segment_start_offset > to.size() ||
from_segment_end_offset > from.size() ||
from_segment_size > max_size - to.size())
return mutated;
mutated = true;
if constexpr (!is_self) {
to.insert(std::next(to.begin(), to_segment_start_offset),
std::next(from.begin(), from_segment_start_offset),
std::next(from.begin(), from_segment_end_offset));
} else {
ContainerT tmp(std::next(from.begin(), from_segment_start_offset),
std::next(from.begin(), from_segment_end_offset));
to.insert(std::next(to.begin(), to_segment_start_offset), tmp.begin(),
tmp.end());
}
return mutated;
}
inline size_t GetOrGuessPositionHint(std::optional<size_t> position_hint,
size_t max, absl::BitGenRef prng) {
if (position_hint.has_value()) {
return *position_hint;
} else {
return ChooseOffset(max + 1, prng);
}
}
// Try to copy `dict_entry.value` to `val`:
// If `dict_entry` has a position hint, copy to that offset; otherwise,
// guess a position hint. Return the copied-to position if mutation succeed,
// otherwise std::nullopt. Return true iff `val` is successfully mutated.
template <bool is_self, typename ContainerT>
bool CopyFromDictionaryEntry(const DictionaryEntry<ContainerT>& dict_entry,
absl::BitGenRef prng, ContainerT& val,
size_t max_size) {
if (dict_entry.value.size() > max_size) return false;
size_t position_hint = GetOrGuessPositionHint(
dict_entry.position_hint,
std::min(val.size(), max_size - dict_entry.value.size()), prng);
return CopyPart<is_self>(dict_entry.value, val, 0, dict_entry.value.size(),
position_hint, max_size);
}
// The same as above, but set `permanent_dict_candidate` iff successfully
// mutated.
template <bool is_self, typename ContainerT>
bool CopyFromDictionaryEntry(
const DictionaryEntry<ContainerT>& dict_entry, absl::BitGenRef prng,
ContainerT& val, size_t max_size,
std::optional<DictionaryEntry<ContainerT>>& permanent_dict_candidate) {
if (dict_entry.value.size() > max_size) return false;
size_t position_hint = GetOrGuessPositionHint(
dict_entry.position_hint,
std::min(val.size(), max_size - dict_entry.value.size()), prng);
bool mutated =
CopyPart<is_self>(dict_entry.value, val, 0, dict_entry.value.size(),
position_hint, max_size);
if (mutated) {
permanent_dict_candidate = {position_hint, val};
}
return mutated;
}
// Try to insert `dict_entry.value` to `val`:
// If `dict_entry` has a position hint, copy to that offset; otherwise,
// guess a position hint. Return the inserted-to position if mutation succeed,
// otherwise std::nullopt. Return true iff successfully mutated.
template <bool is_self, typename ContainerT>
bool InsertFromDictionaryEntry(const DictionaryEntry<ContainerT>& dict_entry,
absl::BitGenRef prng, ContainerT& val,
size_t max_size) {
if (val.size() + dict_entry.value.size() > max_size) return false;
size_t position_hint =
GetOrGuessPositionHint(dict_entry.position_hint, val.size(), prng);
return InsertPart<is_self>(dict_entry.value, val, 0, dict_entry.value.size(),
position_hint, max_size);
}
// The same as above, but set `permanent_dict_candidate` iff successfully
// mutated.
template <bool is_self, typename ContainerT>
bool InsertFromDictionaryEntry(
const DictionaryEntry<ContainerT>& dict_entry, absl::BitGenRef prng,
ContainerT& val, size_t max_size,
std::optional<DictionaryEntry<ContainerT>>& permanent_dict_candidate) {
if (val.size() + dict_entry.value.size() > max_size) return false;
size_t position_hint =
GetOrGuessPositionHint(dict_entry.position_hint, val.size(), prng);
bool mutated =
InsertPart<is_self>(dict_entry.value, val, 0, dict_entry.value.size(),
position_hint, max_size);
if (mutated) {
permanent_dict_candidate = {position_hint, val};
}
return mutated;
}
template <typename ContainerT>
bool ApplyDictionaryMutationAndSavePermanentCandidate(
ContainerT& val, const DictionaryEntry<ContainerT>& entry,
absl::BitGenRef prng,
std::optional<DictionaryEntry<ContainerT>>& permanent_dict_candidate,
size_t max_size) {
bool mutated = false;
RunOne(
prng,
// Temporary dictionary replace contents from position hint.
[&] {
mutated = CopyFromDictionaryEntry<false>(entry, prng, val, max_size,
permanent_dict_candidate);
},
// Temporary dictionary insert into position hint.
[&] {
mutated = InsertFromDictionaryEntry<false>(entry, prng, val, max_size,
permanent_dict_candidate);
});
return mutated;
}
// Replace or insert the dictionary contents to position hints.
template <typename ContainerT>
bool MemoryDictionaryMutation(
ContainerT& val, absl::BitGenRef prng,
ContainerDictionary<ContainerT>& temporary_dict,
ContainerDictionary<ContainerT>& manual_dict,
ContainerDictionary<ContainerT>& permanent_dict,
std::optional<DictionaryEntry<ContainerT>>& permanent_dict_candidate,
size_t max_size) {
bool mutated = false;
const bool can_use_manual_dictionary = !manual_dict.IsEmpty();
const bool can_use_temporary_dictionary = !temporary_dict.IsEmpty();
const bool can_use_permanent_dictionary = !permanent_dict.IsEmpty();
const int dictionary_action_count = 1 + can_use_manual_dictionary +
can_use_temporary_dictionary +
can_use_permanent_dictionary;
int dictionary_action = absl::Uniform(prng, 0, dictionary_action_count);
if (can_use_temporary_dictionary && dictionary_action-- == 0) {
mutated = ApplyDictionaryMutationAndSavePermanentCandidate(
val, temporary_dict.GetRandomSavedEntry(prng), prng,
permanent_dict_candidate, max_size);
}
if (can_use_manual_dictionary && dictionary_action-- == 0) {
mutated = ApplyDictionaryMutationAndSavePermanentCandidate(
val, manual_dict.GetRandomSavedEntry(prng), prng,
permanent_dict_candidate, max_size);
}
if (can_use_permanent_dictionary && dictionary_action-- == 0) {
RunOne(
prng,
// Permanent dictionary replace contents from position hint.
[&] {
mutated = CopyFromDictionaryEntry<false>(
permanent_dict.GetRandomSavedEntry(prng), prng, val, max_size);
},
// Permanent dictionary insert into position hint.
[&] {
mutated = InsertFromDictionaryEntry<false>(
permanent_dict.GetRandomSavedEntry(prng), prng, val, max_size);
});
}
// Pick entries from tables_of_recent_compares(TORC) directly.
if (dictionary_action-- == 0) {
auto dictionary_entry = ContainerDictionary<ContainerT>::GetRandomTORCEntry(
val, prng, GetExecutionCoverage()->GetTablesOfRecentCompares());
if (dictionary_entry.has_value()) {
mutated = ApplyDictionaryMutationAndSavePermanentCandidate(
val, *dictionary_entry, prng, permanent_dict_candidate, max_size);
}
}
return mutated;
}
// Randomly erases a contiguous chunk of at least 1 and at most half the
// elements in `val`. The chunk's size is sampled from a distribution that makes
// smaller chunks more likely. The final size of `val` will be at least
// `min_size`.
template <typename ContainerT>
void EraseRandomChunk(ContainerT& val, absl::BitGenRef prng, size_t min_size) {
if (val.size() <= min_size) return;
const size_t min_final_size = std::max(min_size, val.size() / 2);
const size_t chunk_size =
min_final_size + 1 == val.size()
? 1
: 1 + absl::Zipf(prng, val.size() - min_final_size - 1);
const size_t chunk_offset = ChooseOffset(val.size() - chunk_size + 1, prng);
auto it_start = std::next(val.begin(), chunk_offset);
auto it_end = std::next(it_start, chunk_size);
val.erase(it_start, it_end);
}
// Inserts a chunk consisting of `new_element_val` at a random position in
// `val`. The chunk's size is sampled from a distribution so that the
// final size of `val` is between `val.size() + 1` and `max_size`, with smaller
// chunks being more likely.
template <typename ContainerT, typename T>
void InsertRandomChunk(ContainerT& val, absl::BitGenRef prng, size_t max_size,
T new_element_val) {
if (val.size() >= max_size) return;
size_t chunk_size = val.size() + 1 == max_size
? 1
: 1 + absl::Zipf(prng, max_size - val.size() - 1);
const size_t chunk_offset = ChooseOffset(val.size() + 1, prng);
while (chunk_size--) {
val.insert(std::next(val.begin(), chunk_offset), new_element_val);
}
}
} // namespace fuzztest::internal
#endif // FUZZTEST_FUZZTEST_INTERNAL_DOMAINS_CONTAINER_MUTATION_HELPERS_H_