blob: e3fb0f8dffc68f236b26de267e5da56d71a98349 [file] [log] [blame]
// 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_GRAMMAR_H_
#define FUZZTEST_FUZZTEST_INTERNAL_GRAMMAR_H_
#include <cstddef>
#include <optional>
#include <string>
#include <tuple>
#include <utility>
#include <variant>
#include <vector>
#include "absl/container/flat_hash_map.h"
#include "absl/random/distributions.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "absl/types/span.h"
#include "./fuzztest/internal/domain.h"
#include "./fuzztest/internal/logging.h"
#include "./fuzztest/internal/meta.h"
#include "./fuzztest/internal/serialization.h"
#include "./fuzztest/internal/type_support.h"
namespace fuzztest::internal::grammar {
// We use a generic and simple data structure to represent the AST tree. The key
// member in the data structure is the type id, from which we can tell the
// structure information about the ASTNode. We then provide three generic Domain
// types for Vector (a list of AST nodes of the same types), Variant (a variant
// of several AST nodes), and Tuple (a tuple of AST nodes). In the code
// generation, we generate an InGrammar Domain by aggregating the specific
// version of the these generic Domain types.
using ASTTypeId = int;
struct ASTNode {
ASTTypeId type_id;
std::variant<std::monostate, // If the node is a string terminal.
internal::InRegexpImpl::DFAPath, // If the node is a regex
// terminal.
std::vector<ASTNode>> // If the node is a non-terminal.
children;
size_t NodeCount() const;
};
template <typename T>
bool CheckASTNodeTypeIdAndChildType(const ASTNode& astnode, ASTTypeId type_id) {
return astnode.type_id == type_id &&
std::holds_alternative<T>(astnode.children);
}
template <typename T>
bool CheckASTCorpusStructure(const IRObject& obj) {
auto subs = obj.Subs();
// An valid sub should contain a type id, an index for the variant, and an
// IRObject for the children. So the size should be 3.
if (!subs || subs->size() != 3) {
return false;
}
if (!(*subs)[0].ToCorpus<ASTTypeId>().has_value()) {
return false;
}
auto children_type = (*subs)[1].ToCorpus<size_t>();
if (!children_type.has_value()) {
return false;
}
constexpr size_t kNumChildASTType =
std::variant_size_v<decltype(ASTNode::children)>;
return Switch<kNumChildASTType>(*children_type, [](auto I) {
return std::is_same_v<
T, std::variant_alternative_t<I, decltype(ASTNode::children)>>;
});
}
IRObject WrapASTIntoIRObject(const ASTNode& astnode, IRObject parsed_child);
template <ASTTypeId id, const absl::string_view& value>
class StringLiteralDomain {
public:
template <typename PRNG>
static ASTNode Init(PRNG& /*prng*/) {
return ASTNode{id, std::monostate()};
}
template <typename PRNG>
static ASTNode InitWithBudget(PRNG& /*prng*/, int /*generation_budget*/) {
return ASTNode{id, std::monostate()};
}
template <typename PRNG>
static void Mutate(ASTNode& val, PRNG& prng, bool only_shrink) {}
static ASTTypeId TypeId() { return id; }
static void ToString(std::string& output, const ASTNode& /*val*/) {
absl::StrAppend(&output, value);
}
static bool IsMutable(const ASTNode& /*val*/) { return false; }
static IRObject SerializeCorpus(const ASTNode& astnode) {
FUZZTEST_INTERNAL_CHECK(
CheckASTNodeTypeIdAndChildType<std::monostate>(astnode, id),
"Invalid node!");
return WrapASTIntoIRObject(astnode, {});
}
static std::optional<ASTNode> ParseCorpus(const IRObject& obj) {
if (!CheckASTCorpusStructure<std::monostate>(obj)) {
return std::nullopt;
}
auto subs = obj.Subs();
auto type_id = (*subs)[0].ToCorpus<ASTTypeId>();
ASTNode result;
result.type_id = *type_id;
result.children.emplace<std::monostate>();
return result;
}
};
template <ASTTypeId id, const absl::string_view& value>
class RegexLiteralDomain {
public:
template <typename PRNG>
static ASTNode Init(PRNG& prng) {
return ASTNode{id, GetInnerRegexpDomain().Init(prng)};
}
template <typename PRNG>
static ASTNode InitWithBudget(PRNG& prng, int /*generation_budget*/) {
return ASTNode{id, GetInnerRegexpDomain().Init(prng)};
}
template <typename PRNG>
static void Mutate(ASTNode& val, PRNG& prng, bool only_shrink) {
GetInnerRegexpDomain().Mutate(std::get<1>(val.children), prng, only_shrink);
}
static ASTTypeId TypeId() { return id; }
static void ToString(std::string& output, const ASTNode& val) {
FUZZTEST_INTERNAL_CHECK(val.children.index() == 1, "Not a regex literal!");
absl::StrAppend(&output,
GetInnerRegexpDomain().GetValue(std::get<1>(val.children)));
}
static bool IsMutable(const ASTNode& /*val*/) { return true; }
static IRObject SerializeCorpus(const ASTNode& astnode) {
FUZZTEST_INTERNAL_CHECK(
CheckASTNodeTypeIdAndChildType<internal::InRegexpImpl::DFAPath>(astnode,
id),
"Invalid node!");
return WrapASTIntoIRObject(
astnode,
GetInnerRegexpDomain().SerializeCorpus(
std::get<internal::InRegexpImpl::DFAPath>(astnode.children)));
}
static std::optional<ASTNode> ParseCorpus(const IRObject& obj) {
if (!CheckASTCorpusStructure<internal::InRegexpImpl::DFAPath>(obj)) {
return std::nullopt;
}
auto subs = obj.Subs();
auto type_id = (*subs)[0].ToCorpus<ASTTypeId>();
ASTNode result;
result.type_id = *type_id;
auto path = GetInnerRegexpDomain().ParseCorpus((*subs)[2]);
if (!path) {
return std::nullopt;
}
result.children.emplace<internal::InRegexpImpl::DFAPath>(*path);
return result;
}
private:
static internal::InRegexpImpl& GetInnerRegexpDomain() {
static internal::InRegexpImpl* inner_domain =
new internal::InRegexpImpl(value.data());
return *inner_domain;
}
};
// Use for random AST generation. To avoid inifite recursive generation (i.e.,
// for grammar rules like `expr: expr '+' expr | literal`), we limit the
// generation with budget. We first assign the budget to be `kMaxGenerationNum`.
// Every time we generate a AST node, we decrement the budget by one. When we
// are running out of the budget (the budget is less or equal than 0), the
// generation will be in FallBack mode. The FallBack Mode ensures the generation
// ends. Specifically, every grammar rule that has more than 1 production rules
// has a fallback index. When every grammar rule chooses the fallback index
// during generation (aka, the FallBack Mode is on), generation will guarantee
// to end. The fallback index is precalculated by the code generator. The idea
// is simple: We define a symbol (terminal or non-terminal) as safe if it
// generates a finite string in the fallback mode. First we mark all terminals
// as safe. If a non-terminal has a production rule that consists of only safe
// symbols, we use the index of production rule as the fallback index and mark
// the non-terminal is safe. We repeat the process until every grammar rule has
// a fallback index.
inline constexpr int kMaxGenerationNum = 200;
template <ASTTypeId id, typename ElementT, int min = 0, int max = 10000>
class VectorDomain {
public:
template <typename PRNG>
static ASTNode Init(PRNG& prng) {
return InitWithBudget(prng, kMaxGenerationNum);
}
template <typename PRNG>
static ASTNode InitWithBudget(PRNG& prng, int generation_budget) {
std::vector<ASTNode> children;
int element_size =
generation_budget <= 0 ? min : min + absl::Uniform<int>(prng, 0, 2);
children.reserve(element_size);
for (int i = 0; i < element_size; ++i) {
// Distribute the budget evenly between the subtrees.
children.emplace_back(
ElementT::InitWithBudget(prng, generation_budget / element_size));
}
return ASTNode{id, children};
}
static ASTTypeId TypeId() { return id; }
template <typename PRNG>
static void Mutate(ASTNode& val, PRNG& prng, bool only_shrink) {
FUZZTEST_INTERNAL_CHECK(val.children.index() == 2, "Not a vector!");
std::vector<ASTNode>& elements = std::get<2>(val.children);
if (only_shrink) {
ShrinkElements(elements, prng);
return;
}
bool can_mutate_element =
!elements.empty() && ElementT::IsMutable(elements.back());
constexpr bool can_change_element_num = max > min;
if (!can_mutate_element && !can_change_element_num) {
FUZZTEST_INTERNAL_CHECK(false, "We shouldn't pick an unmutable node.");
return;
}
if (!can_mutate_element) {
ChangeElementNum(elements, prng);
} else if (!can_change_element_num) {
ElementT::Mutate(
elements[absl::Uniform<size_t>(prng, 0, elements.size())], prng,
only_shrink);
} else {
if (absl::Bernoulli(prng, 0.5)) {
ChangeElementNum(elements, prng);
} else {
ElementT::Mutate(
elements[absl::Uniform<size_t>(prng, 0, elements.size())], prng,
only_shrink);
}
}
}
static void ToString(std::string& output, const ASTNode& val) {
for (const auto& child : std::get<2>(val.children)) {
ElementT::ToString(output, child);
}
}
static bool IsMutable(const ASTNode& /*val*/) { return true; }
static IRObject SerializeCorpus(const ASTNode& astnode) {
FUZZTEST_INTERNAL_CHECK(
CheckASTNodeTypeIdAndChildType<std::vector<ASTNode>>(astnode, id),
"Invalid node!");
IRObject expansion_obj;
auto& inner_subs = expansion_obj.MutableSubs();
for (auto& node : std::get<std::vector<ASTNode>>(astnode.children)) {
inner_subs.push_back(ElementT::SerializeCorpus(node));
}
return WrapASTIntoIRObject(astnode, expansion_obj);
}
static std::optional<ASTNode> ParseCorpus(const IRObject& obj) {
if (!CheckASTCorpusStructure<std::vector<ASTNode>>(obj)) {
return std::nullopt;
}
auto subs = obj.Subs();
auto type_id = (*subs)[0].ToCorpus<ASTTypeId>();
auto children = (*subs)[2].Subs();
if (!children || children->size() < min || children->size() > max) {
return std::nullopt;
}
ASTNode result;
result.type_id = *type_id;
std::vector<ASTNode>& child_nodes =
result.children.emplace<std::vector<ASTNode>>();
for (const auto& child : *children) {
auto child_node = ElementT::ParseCorpus(child);
if (!child_node) {
return std::nullopt;
}
child_nodes.push_back(*child_node);
}
return result;
}
private:
template <typename PRNG>
static void ShrinkElements(std::vector<ASTNode>& elements, PRNG& prng) {
if (elements.empty()) return;
bool can_remove_element = elements.size() > min;
bool can_shrink_element = ElementT::IsMutable(elements.back());
if (!can_remove_element && !can_shrink_element) return;
if (!can_remove_element) {
// Cannot remove elements, let's shrink them.
ElementT::Mutate(*ChoosePosition(elements, IncludeEnd::kNo, prng), prng,
true);
} else if (!can_shrink_element) {
// Cannot shrink elements, let's remove one.
elements.erase(ChoosePosition(elements, IncludeEnd::kNo, prng));
} else {
// We can do both. So let's toss a coin to decide.
if (absl::Bernoulli(prng, 0.5)) {
elements.erase(ChoosePosition(elements, IncludeEnd::kNo, prng));
} else {
ElementT::Mutate(*ChoosePosition(elements, IncludeEnd::kNo, prng), prng,
true);
}
}
}
template <typename PRNG>
static void ChangeElementNum(std::vector<ASTNode>& elements, PRNG& prng) {
if (elements.size() == min) {
elements.emplace_back(ElementT::InitWithBudget(prng, kMaxGenerationNum));
} else if (elements.size() == max) {
elements.erase(ChoosePosition(elements, IncludeEnd::kNo, prng));
} else {
if (absl::Bernoulli(prng, 0.5)) {
elements.emplace_back(
ElementT::InitWithBudget(prng, kMaxGenerationNum));
} else {
elements.erase(ChoosePosition(elements, IncludeEnd::kNo, prng));
}
}
}
};
// Maximum number of elements allowed in a vector.
inline constexpr int kMaxElementNum = 1000;
template <ASTTypeId id, typename ElementT>
using Vector = VectorDomain<id, ElementT, 0, kMaxElementNum>;
template <ASTTypeId id, typename ElementT>
using Optional = VectorDomain<id, ElementT, 0, 1>;
template <ASTTypeId id, typename ElementT>
using NonEmptyVector = VectorDomain<id, ElementT, 1, kMaxElementNum>;
template <ASTTypeId id, typename... ElementT>
class TupleDomain {
public:
static ASTTypeId TypeId() { return id; }
template <typename PRNG>
static ASTNode Init(PRNG& prng) {
return InitWithBudget(prng, kMaxGenerationNum);
}
template <typename PRNG>
static ASTNode InitWithBudget(PRNG& prng, int generation_budget) {
return ASTNode{
id, std::vector<ASTNode>{ElementT::InitWithBudget(
prng,
generation_budget / static_cast<int>(sizeof...(ElementT)))...}};
}
template <typename PRNG>
static void Mutate(ASTNode& val, PRNG& prng, bool only_shrink) {
FUZZTEST_INTERNAL_CHECK(
val.children.index() == 2 &&
std::get<2>(val.children).size() == sizeof...(ElementT),
"Tuple elements number doesn't match!");
std::vector<int> mutables;
ApplyIndex<sizeof...(ElementT)>([&](auto... I) {
((ElementT::IsMutable(std::get<2>(val.children)[I])
? mutables.push_back(I)
: (void)0),
...);
});
FUZZTEST_INTERNAL_CHECK(
!mutables.empty(),
"If the tuple is immutable it shouldn't be picked for mutation.");
int choice = mutables[absl::Uniform<int>(prng, 0, mutables.size())];
ApplyIndex<sizeof...(ElementT)>([&](auto... I) {
((choice == I ? (ElementT::Mutate(std::get<2>(val.children)[I], prng,
only_shrink))
: (void)0),
...);
});
}
static void ToString(std::string& output, const ASTNode& val) {
ApplyIndex<sizeof...(ElementT)>([&](auto... I) {
(ElementT::ToString(output, std::get<2>(val.children)[I]), ...);
});
}
static bool IsMutable(const ASTNode& val) {
bool result = false;
ApplyIndex<sizeof...(ElementT)>([&](auto... I) {
result = (ElementT::IsMutable(std::get<2>(val.children)[I]) || ...);
});
return result;
}
static IRObject SerializeCorpus(const ASTNode& astnode) {
FUZZTEST_INTERNAL_CHECK(
CheckASTNodeTypeIdAndChildType<std::vector<ASTNode>>(astnode, id),
"Invalid node!");
IRObject expansion_obj;
auto& inner_subs = expansion_obj.MutableSubs();
ApplyIndex<sizeof...(ElementT)>([&](auto... I) {
(inner_subs.push_back(
ElementT::SerializeCorpus(std::get<2>(astnode.children)[I])),
...);
});
return WrapASTIntoIRObject(astnode, expansion_obj);
}
static std::optional<ASTNode> ParseCorpus(const IRObject& obj) {
if (!CheckASTCorpusStructure<std::vector<ASTNode>>(obj)) {
return std::nullopt;
}
auto subs = obj.Subs();
auto type_id = (*subs)[0].ToCorpus<ASTTypeId>();
auto children = (*subs)[2].Subs();
if (!children || children->size() != sizeof...(ElementT)) {
return std::nullopt;
}
ASTNode result;
result.type_id = *type_id;
std::vector<std::optional<ASTNode>> parsed_child_nodes;
ApplyIndex<sizeof...(ElementT)>([&](auto... I) {
(parsed_child_nodes.push_back(ElementT::ParseCorpus((*children)[I])),
...);
});
std::vector<ASTNode>& child_nodes =
result.children.emplace<std::vector<ASTNode>>();
for (auto& child : parsed_child_nodes) {
if (!child.has_value()) {
return std::nullopt;
}
child_nodes.push_back(*child);
}
return result;
}
};
template <ASTTypeId id, int fallback_index, typename... ElementT>
class VariantDomain {
public:
static ASTTypeId TypeId() { return id; }
template <typename PRNG>
static ASTNode Init(PRNG& prng) {
return InitWithBudget(prng, kMaxGenerationNum);
}
template <typename PRNG>
static ASTNode InitWithBudget(PRNG& prng, int generation_budget) {
int choice = generation_budget <= 0
? fallback_index
: absl::Uniform<int>(prng, 0, (sizeof...(ElementT)));
ASTNode result{id, std::vector<ASTNode>()};
Switch<sizeof...(ElementT)>(choice, [&](auto I) {
std::get<2>(result.children)
.push_back(std::tuple_element<I, std::tuple<ElementT...>>::type::
InitWithBudget(prng, generation_budget));
});
return result;
}
template <typename PRNG>
static void Mutate(ASTNode& val, PRNG& prng, bool only_shrink) {
constexpr bool has_alternative = sizeof...(ElementT) > 1;
ASTNode current_value = std::get<2>(val.children).front();
ASTTypeId current_value_id = current_value.type_id;
bool is_current_value_mutable;
((ElementT::TypeId() == current_value_id
? (is_current_value_mutable = ElementT::IsMutable(current_value),
(void)0)
: (void)0),
...);
FUZZTEST_INTERNAL_CHECK(is_current_value_mutable || has_alternative,
"Impossible at" + std::to_string(id));
if (only_shrink) {
if (is_current_value_mutable) {
MutateCurrentValue(val, prng, only_shrink);
}
return;
}
if (!is_current_value_mutable) {
SwitchToAlternative(val, prng);
} else if (!has_alternative) {
MutateCurrentValue(val, prng, only_shrink);
} else {
if (absl::Bernoulli(prng, 0.5)) {
MutateCurrentValue(val, prng, only_shrink);
} else {
SwitchToAlternative(val, prng);
}
}
}
static void ToString(std::string& output, const ASTNode& val) {
FUZZTEST_INTERNAL_CHECK(std::get<2>(val.children).size() == 1,
"This is not a variant ast node.");
ASTTypeId child_id = std::get<2>(val.children).front().type_id;
((ElementT::TypeId() == child_id
? (ElementT::ToString(output, std::get<2>(val.children).front()))
: (void)0),
...);
}
static bool IsMutable(const ASTNode& val) {
// If the variant has at least two choices, it is always mutable.
if (sizeof...(ElementT) > 1) return true;
// Otherwise, we check whether the only choice is mutable.
bool result = false;
ASTTypeId child_id = std::get<2>(val.children).front().type_id;
((ElementT::TypeId() == child_id
? (result = ElementT::IsMutable(std::get<2>(val.children).front()),
(void)0)
: (void)0),
...);
return result;
}
static IRObject SerializeCorpus(const ASTNode& astnode) {
FUZZTEST_INTERNAL_CHECK(
CheckASTNodeTypeIdAndChildType<std::vector<ASTNode>>(astnode, id),
"Invalid node!");
ASTTypeId child_id =
std::get<std::vector<ASTNode>>(astnode.children).front().type_id;
IRObject expansion_obj;
auto& inner_subs = expansion_obj.MutableSubs();
((ElementT::TypeId() == child_id
? inner_subs.push_back(ElementT::SerializeCorpus(
std::get<std::vector<ASTNode>>(astnode.children).front()))
: (void)0),
...);
return WrapASTIntoIRObject(astnode, expansion_obj);
}
static std::optional<ASTNode> ParseCorpus(const IRObject& obj) {
if (!CheckASTCorpusStructure<std::vector<ASTNode>>(obj)) {
return std::nullopt;
}
auto subs = obj.Subs();
auto type_id = (*subs)[0].ToCorpus<ASTTypeId>();
auto children = (*subs)[2].Subs();
if (!children || children->size() != 1) {
return std::nullopt;
}
ASTNode result;
result.type_id = *type_id;
std::optional<ASTNode> child_node;
((child_node.has_value()
? (void)0
: (child_node = ElementT::ParseCorpus(children->front()), (void)0)),
...);
if (!child_node.has_value()) {
return std::nullopt;
}
result.children.emplace<std::vector<ASTNode>>({std::move(*child_node)});
return result;
}
private:
template <typename PRNG>
static void MutateCurrentValue(ASTNode& val, PRNG& prng, bool only_shrink) {
ASTNode& current_value = std::get<2>(val.children).front();
((ElementT::TypeId() == current_value.type_id
? (ElementT::Mutate(current_value, prng, only_shrink))
: (void)0),
...);
}
template <typename PRNG>
static void SwitchToAlternative(ASTNode& val, PRNG& prng) {
constexpr int n_alternative = sizeof...(ElementT);
FUZZTEST_INTERNAL_CHECK(n_alternative > 1, "No alternative to switch!");
int child_type_id = std::get<2>(val.children).front().type_id;
int current_choice = 0;
ApplyIndex<n_alternative>([&](auto... I) {
((ElementT::TypeId() == child_type_id ? (current_choice = I, (void)0)
: (void)0),
...);
});
int choice = current_choice;
while (choice == current_choice) {
choice = absl::Uniform<int>(prng, 0, n_alternative);
}
// Switch to an alternative value.
ApplyIndex<n_alternative>([&](auto... I) {
((choice == I ? (std::get<2>(val.children).front() =
ElementT::InitWithBudget(prng, kMaxGenerationNum),
(void)0)
: (void)0),
...);
});
}
};
void GroupElementByASTType(
ASTNode& astnode,
absl::flat_hash_map<ASTTypeId, std::vector<ASTNode*>>& groups);
template <typename TopDomain>
class InGrammarImpl : public DomainBase<InGrammarImpl<TopDomain>> {
public:
using value_type = std::string;
using corpus_type = ASTNode;
static constexpr bool has_custom_corpus_type = true;
template <typename PRNG>
ASTNode Init(PRNG& prng) {
return TopDomain::Init(prng);
}
template <typename PRNG>
void Mutate(ASTNode& val, PRNG& prng, bool only_shrink) {
if (only_shrink && absl::Bernoulli(prng, 0.5) &&
ShrinkByReplaceWithSubElementOfSameType(val, prng)) {
return;
}
TopDomain::Mutate(val, prng, only_shrink);
}
auto GetPrinter() const { return StringPrinter{}; }
value_type GetValue(const corpus_type& v) const {
std::string result;
TopDomain::ToString(result, v);
return result;
}
std::optional<corpus_type> FromValue(const value_type& /*v*/) const {
FUZZTEST_INTERNAL_CHECK(false, "Parsing is not implemented yet!");
return std::nullopt;
}
IRObject SerializeCorpus(const corpus_type& astnode) const {
return TopDomain::SerializeCorpus(astnode);
}
std::optional<corpus_type> ParseCorpus(const IRObject& obj) const {
return TopDomain::ParseCorpus(obj);
}
private:
template <typename PRNG>
bool ShrinkByReplaceWithSubElementOfSameType(ASTNode& astnode, PRNG& prng) {
absl::flat_hash_map<ASTTypeId, std::vector<ASTNode*>> groups;
GroupElementByASTType(astnode, groups);
std::vector<ASTTypeId> candidate_types;
for (const auto& iter : groups) {
if (iter.second.size() > 1) {
candidate_types.push_back(iter.first);
}
}
if (candidate_types.empty()) {
return false;
}
std::vector<ASTNode*>& candidates =
groups[*ChoosePosition(candidate_types, IncludeEnd::kNo, prng)];
size_t dst_index = absl::Uniform<size_t>(prng, 0, candidates.size() - 1);
size_t src_index =
absl::Uniform<size_t>(prng, dst_index + 1, candidates.size());
FUZZTEST_INTERNAL_CHECK(src_index < candidates.size(), "Out of bound!");
FUZZTEST_INTERNAL_CHECK(dst_index < candidates.size(), "Out of bound!");
if (candidates[dst_index]->NodeCount() <
candidates[src_index]->NodeCount()) {
std::swap(dst_index, src_index);
}
// This copy is necessary to avoid `self assignment`, since the src ast node
// might be a sub node within the dst ast node. If we write it as
// `*candidates[dst_index] = *candidates[src_index]` and the src is a sub
// node in the dst, according to
// (https://en.cppreference.com/w/cpp/utility/variant/operator%3D), the
// variant in the dst will be destroyed first. This also invalidates the src
// before its value is used, resulting a crash when the value is used later.
auto val = *candidates[src_index];
*candidates[dst_index] = val;
return true;
}
};
} // namespace fuzztest::internal::grammar
#endif // FUZZTEST_FUZZTEST_INTERNAL_GRAMMAR_H_