| // 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_ |