• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2021 The Tint Authors.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include "fuzzers/tint_ast_fuzzer/mutator.h"
16 
17 #include <cassert>
18 #include <memory>
19 #include <unordered_map>
20 #include <utility>
21 #include <vector>
22 
23 #include "fuzzers/tint_ast_fuzzer/mutation_finders/replace_identifiers.h"
24 #include "fuzzers/tint_ast_fuzzer/node_id_map.h"
25 
26 #include "src/program_builder.h"
27 
28 namespace tint {
29 namespace fuzzers {
30 namespace ast_fuzzer {
31 namespace {
32 
33 template <typename T, typename... Args>
MaybeAddFinder(bool enable_all_mutations,ProbabilityContext * probability_context,MutationFinderList * finders,Args &&...args)34 void MaybeAddFinder(bool enable_all_mutations,
35                     ProbabilityContext* probability_context,
36                     MutationFinderList* finders,
37                     Args&&... args) {
38   if (enable_all_mutations || probability_context->RandomBool()) {
39     finders->push_back(std::make_unique<T>(std::forward<Args>(args)...));
40   }
41 }
42 
CreateMutationFinders(ProbabilityContext * probability_context,bool enable_all_mutations)43 MutationFinderList CreateMutationFinders(
44     ProbabilityContext* probability_context,
45     bool enable_all_mutations) {
46   MutationFinderList result;
47   do {
48     MaybeAddFinder<MutationFinderReplaceIdentifiers>(
49         enable_all_mutations, probability_context, &result);
50   } while (result.empty());
51   return result;
52 }
53 
54 }  // namespace
55 
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)56 bool MaybeApplyMutation(const tint::Program& program,
57                         const Mutation& mutation,
58                         const NodeIdMap& node_id_map,
59                         tint::Program* out_program,
60                         NodeIdMap* out_node_id_map,
61                         protobufs::MutationSequence* mutation_sequence) {
62   assert(out_program && "`out_program` may not be a nullptr");
63   assert(out_node_id_map && "`out_node_id_map` may not be a nullptr");
64 
65   if (!mutation.IsApplicable(program, node_id_map)) {
66     return false;
67   }
68 
69   // The mutated `program` will be copied into the `mutated` program builder.
70   tint::ProgramBuilder mutated;
71   tint::CloneContext clone_context(&mutated, &program);
72   NodeIdMap new_node_id_map;
73   clone_context.ReplaceAll(
74       [&node_id_map, &new_node_id_map, &clone_context](const ast::Node* node) {
75         // Make sure all `tint::ast::` nodes' ids are preserved.
76         auto* cloned = tint::As<ast::Node>(node->Clone(&clone_context));
77         new_node_id_map.Add(cloned, node_id_map.GetId(node));
78         return cloned;
79       });
80 
81   mutation.Apply(node_id_map, &clone_context, &new_node_id_map);
82   if (mutation_sequence) {
83     *mutation_sequence->add_mutation() = mutation.ToMessage();
84   }
85 
86   clone_context.Clone();
87   *out_program = tint::Program(std::move(mutated));
88   *out_node_id_map = std::move(new_node_id_map);
89   return true;
90 }
91 
Replay(tint::Program program,const protobufs::MutationSequence & mutation_sequence)92 tint::Program Replay(tint::Program program,
93                      const protobufs::MutationSequence& mutation_sequence) {
94   assert(program.IsValid() && "Initial program is invalid");
95 
96   NodeIdMap node_id_map(program);
97   for (const auto& mutation_message : mutation_sequence.mutation()) {
98     auto mutation = Mutation::FromMessage(mutation_message);
99     auto status = MaybeApplyMutation(program, *mutation, node_id_map, &program,
100                                      &node_id_map, nullptr);
101     (void)status;  // `status` will be unused in release mode.
102     assert(status && "`mutation` is inapplicable - it's most likely a bug");
103     if (!program.IsValid()) {
104       // `mutation` has a bug.
105       break;
106     }
107   }
108 
109   return program;
110 }
111 
Mutate(tint::Program program,ProbabilityContext * probability_context,bool enable_all_mutations,uint32_t max_applied_mutations,protobufs::MutationSequence * mutation_sequence)112 tint::Program Mutate(tint::Program program,
113                      ProbabilityContext* probability_context,
114                      bool enable_all_mutations,
115                      uint32_t max_applied_mutations,
116                      protobufs::MutationSequence* mutation_sequence) {
117   assert(max_applied_mutations != 0 &&
118          "Maximum number of mutations is invalid");
119   assert(program.IsValid() && "Initial program is invalid");
120 
121   // The number of allowed failed attempts to apply mutations. If this number is
122   // exceeded, the mutator is considered stuck and the mutation session is
123   // stopped.
124   const uint32_t kMaxFailureToApply = 10;
125 
126   auto finders =
127       CreateMutationFinders(probability_context, enable_all_mutations);
128   NodeIdMap node_id_map(program);
129 
130   // Total number of applied mutations during this call to `Mutate`.
131   uint32_t applied_mutations = 0;
132 
133   // The number of consecutively failed attempts to apply mutations.
134   uint32_t failure_to_apply = 0;
135 
136   // Apply mutations as long as the `program` is valid, the limit on the number
137   // of mutations is not reached and the mutator is not stuck (i.e. unable to
138   // apply any mutations for some time).
139   while (program.IsValid() && applied_mutations < max_applied_mutations &&
140          failure_to_apply < kMaxFailureToApply) {
141     // Get all applicable mutations from some mutation finder.
142     const auto& mutation_finder =
143         finders[probability_context->GetRandomIndex(finders)];
144     auto mutations = mutation_finder->FindMutations(program, &node_id_map,
145                                                     probability_context);
146 
147     const auto old_applied_mutations = applied_mutations;
148     for (const auto& mutation : mutations) {
149       if (!probability_context->ChoosePercentage(
150               mutation_finder->GetChanceOfApplyingMutation(
151                   probability_context))) {
152         // Skip this `mutation` probabilistically.
153         continue;
154       }
155 
156       if (!MaybeApplyMutation(program, *mutation, node_id_map, &program,
157                               &node_id_map, mutation_sequence)) {
158         // This `mutation` is inapplicable. This may happen if some of the
159         // earlier mutations cancelled this one.
160         continue;
161       }
162 
163       applied_mutations++;
164       if (!program.IsValid()) {
165         // This `mutation` has a bug.
166         return program;
167       }
168     }
169 
170     if (old_applied_mutations == applied_mutations) {
171       // No mutation was applied. Increase the counter to prevent an infinite
172       // loop.
173       failure_to_apply++;
174     } else {
175       failure_to_apply = 0;
176     }
177   }
178 
179   return program;
180 }
181 
182 }  // namespace ast_fuzzer
183 }  // namespace fuzzers
184 }  // namespace tint
185