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