// Copyright 2021 The Tint Authors. // // 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 "fuzzers/tint_ast_fuzzer/mutator.h" #include #include #include #include #include #include "fuzzers/tint_ast_fuzzer/mutation_finders/replace_identifiers.h" #include "fuzzers/tint_ast_fuzzer/node_id_map.h" #include "src/program_builder.h" namespace tint { namespace fuzzers { namespace ast_fuzzer { namespace { template void MaybeAddFinder(bool enable_all_mutations, ProbabilityContext* probability_context, MutationFinderList* finders, Args&&... args) { if (enable_all_mutations || probability_context->RandomBool()) { finders->push_back(std::make_unique(std::forward(args)...)); } } MutationFinderList CreateMutationFinders( ProbabilityContext* probability_context, bool enable_all_mutations) { MutationFinderList result; do { MaybeAddFinder( enable_all_mutations, probability_context, &result); } while (result.empty()); return result; } } // namespace bool MaybeApplyMutation(const tint::Program& program, const Mutation& mutation, const NodeIdMap& node_id_map, tint::Program* out_program, NodeIdMap* out_node_id_map, protobufs::MutationSequence* mutation_sequence) { assert(out_program && "`out_program` may not be a nullptr"); assert(out_node_id_map && "`out_node_id_map` may not be a nullptr"); if (!mutation.IsApplicable(program, node_id_map)) { return false; } // The mutated `program` will be copied into the `mutated` program builder. tint::ProgramBuilder mutated; tint::CloneContext clone_context(&mutated, &program); NodeIdMap new_node_id_map; clone_context.ReplaceAll( [&node_id_map, &new_node_id_map, &clone_context](ast::Node* node) { // Make sure all `tint::ast::` nodes' ids are preserved. auto* cloned = tint::As(node->Clone(&clone_context)); new_node_id_map.Add(cloned, node_id_map.GetId(node)); return cloned; }); mutation.Apply(node_id_map, &clone_context, &new_node_id_map); if (mutation_sequence) { *mutation_sequence->add_mutation() = mutation.ToMessage(); } clone_context.Clone(); *out_program = tint::Program(std::move(mutated)); *out_node_id_map = std::move(new_node_id_map); return true; } tint::Program Replay(tint::Program program, const protobufs::MutationSequence& mutation_sequence) { assert(program.IsValid() && "Initial program is invalid"); NodeIdMap node_id_map(program); for (const auto& mutation_message : mutation_sequence.mutation()) { auto mutation = Mutation::FromMessage(mutation_message); auto status = MaybeApplyMutation(program, *mutation, node_id_map, &program, &node_id_map, nullptr); (void)status; // `status` will be unused in release mode. assert(status && "`mutation` is inapplicable - it's most likely a bug"); if (!program.IsValid()) { // `mutation` has a bug. break; } } return program; } tint::Program Mutate(tint::Program program, ProbabilityContext* probability_context, bool enable_all_mutations, uint32_t max_applied_mutations, protobufs::MutationSequence* mutation_sequence) { assert(max_applied_mutations != 0 && "Maximum number of mutations is invalid"); assert(program.IsValid() && "Initial program is invalid"); // The number of allowed failed attempts to apply mutations. If this number is // exceeded, the mutator is considered stuck and the mutation session is // stopped. const uint32_t kMaxFailureToApply = 10; auto finders = CreateMutationFinders(probability_context, enable_all_mutations); NodeIdMap node_id_map(program); // Total number of applied mutations during this call to `Mutate`. uint32_t applied_mutations = 0; // The number of consecutively failed attempts to apply mutations. uint32_t failure_to_apply = 0; // Apply mutations as long as the `program` is valid, the limit on the number // of mutations is not reached and the mutator is not stuck (i.e. unable to // apply any mutations for some time). while (program.IsValid() && applied_mutations < max_applied_mutations && failure_to_apply < kMaxFailureToApply) { // Get all applicable mutations from some mutation finder. const auto& mutation_finder = finders[probability_context->GetRandomIndex(finders)]; auto mutations = mutation_finder->FindMutations(program, node_id_map, probability_context); const auto old_applied_mutations = applied_mutations; for (const auto& mutation : mutations) { if (!probability_context->ChoosePercentage( mutation_finder->GetChanceOfApplyingMutation( probability_context))) { // Skip this `mutation` probabilistically. continue; } if (!MaybeApplyMutation(program, *mutation, node_id_map, &program, &node_id_map, mutation_sequence)) { // This `mutation` is inapplicable. This may happen if some of the // earlier mutations cancelled this one. continue; } applied_mutations++; if (!program.IsValid()) { // This `mutation` has a bug. return program; } } if (old_applied_mutations == applied_mutations) { // No mutation was applied. Increase the counter to prevent an infinite // loop. failure_to_apply++; } else { failure_to_apply = 0; } } return program; } } // namespace ast_fuzzer } // namespace fuzzers } // namespace tint