Details | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
14 | pmbaty | 1 | //===-- IRMutator.h - Mutation engine for fuzzing IR ------------*- C++ -*-===// |
2 | // |
||
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
||
4 | // See https://llvm.org/LICENSE.txt for license information. |
||
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
||
6 | // |
||
7 | //===----------------------------------------------------------------------===// |
||
8 | // |
||
9 | // Provides the IRMutator class, which drives mutations on IR based on a |
||
10 | // configurable set of strategies. Some common strategies are also included |
||
11 | // here. |
||
12 | // |
||
13 | // Fuzzer-friendly (de)serialization functions are also provided, as these |
||
14 | // are usually needed when mutating IR. |
||
15 | // |
||
16 | //===----------------------------------------------------------------------===// |
||
17 | |||
18 | #ifndef LLVM_FUZZMUTATE_IRMUTATOR_H |
||
19 | #define LLVM_FUZZMUTATE_IRMUTATOR_H |
||
20 | |||
21 | #include "llvm/FuzzMutate/OpDescriptor.h" |
||
22 | #include "llvm/Support/ErrorHandling.h" |
||
23 | #include <optional> |
||
24 | |||
25 | namespace llvm { |
||
26 | class BasicBlock; |
||
27 | class Function; |
||
28 | class Instruction; |
||
29 | class Module; |
||
30 | |||
31 | struct RandomIRBuilder; |
||
32 | |||
33 | /// Base class for describing how to mutate a module. mutation functions for |
||
34 | /// each IR unit forward to the contained unit. |
||
35 | class IRMutationStrategy { |
||
36 | public: |
||
37 | virtual ~IRMutationStrategy() = default; |
||
38 | |||
39 | /// Provide a weight to bias towards choosing this strategy for a mutation. |
||
40 | /// |
||
41 | /// The value of the weight is arbitrary, but a good default is "the number of |
||
42 | /// distinct ways in which this strategy can mutate a unit". This can also be |
||
43 | /// used to prefer strategies that shrink the overall size of the result when |
||
44 | /// we start getting close to \c MaxSize. |
||
45 | virtual uint64_t getWeight(size_t CurrentSize, size_t MaxSize, |
||
46 | uint64_t CurrentWeight) = 0; |
||
47 | |||
48 | /// @{ |
||
49 | /// Mutators for each IR unit. By default these forward to a contained |
||
50 | /// instance of the next smaller unit. |
||
51 | virtual void mutate(Module &M, RandomIRBuilder &IB); |
||
52 | virtual void mutate(Function &F, RandomIRBuilder &IB); |
||
53 | virtual void mutate(BasicBlock &BB, RandomIRBuilder &IB); |
||
54 | virtual void mutate(Instruction &I, RandomIRBuilder &IB) { |
||
55 | llvm_unreachable("Strategy does not implement any mutators"); |
||
56 | } |
||
57 | /// @} |
||
58 | }; |
||
59 | |||
60 | using TypeGetter = std::function<Type *(LLVMContext &)>; |
||
61 | |||
62 | /// Entry point for configuring and running IR mutations. |
||
63 | class IRMutator { |
||
64 | std::vector<TypeGetter> AllowedTypes; |
||
65 | std::vector<std::unique_ptr<IRMutationStrategy>> Strategies; |
||
66 | |||
67 | public: |
||
68 | IRMutator(std::vector<TypeGetter> &&AllowedTypes, |
||
69 | std::vector<std::unique_ptr<IRMutationStrategy>> &&Strategies) |
||
70 | : AllowedTypes(std::move(AllowedTypes)), |
||
71 | Strategies(std::move(Strategies)) {} |
||
72 | |||
73 | void mutateModule(Module &M, int Seed, size_t CurSize, size_t MaxSize); |
||
74 | }; |
||
75 | |||
76 | /// Strategy that injects operations into the function. |
||
77 | class InjectorIRStrategy : public IRMutationStrategy { |
||
78 | std::vector<fuzzerop::OpDescriptor> Operations; |
||
79 | |||
80 | std::optional<fuzzerop::OpDescriptor> chooseOperation(Value *Src, |
||
81 | RandomIRBuilder &IB); |
||
82 | |||
83 | public: |
||
84 | InjectorIRStrategy(std::vector<fuzzerop::OpDescriptor> &&Operations) |
||
85 | : Operations(std::move(Operations)) {} |
||
86 | static std::vector<fuzzerop::OpDescriptor> getDefaultOps(); |
||
87 | |||
88 | uint64_t getWeight(size_t CurrentSize, size_t MaxSize, |
||
89 | uint64_t CurrentWeight) override { |
||
90 | return Operations.size(); |
||
91 | } |
||
92 | |||
93 | using IRMutationStrategy::mutate; |
||
94 | void mutate(Function &F, RandomIRBuilder &IB) override; |
||
95 | void mutate(BasicBlock &BB, RandomIRBuilder &IB) override; |
||
96 | }; |
||
97 | |||
98 | /// Strategy that deletes instructions when the Module is too large. |
||
99 | class InstDeleterIRStrategy : public IRMutationStrategy { |
||
100 | public: |
||
101 | uint64_t getWeight(size_t CurrentSize, size_t MaxSize, |
||
102 | uint64_t CurrentWeight) override; |
||
103 | |||
104 | using IRMutationStrategy::mutate; |
||
105 | void mutate(Function &F, RandomIRBuilder &IB) override; |
||
106 | void mutate(Instruction &Inst, RandomIRBuilder &IB) override; |
||
107 | }; |
||
108 | |||
109 | /// Strategy that modifies instruction attributes and operands. |
||
110 | class InstModificationIRStrategy : public IRMutationStrategy { |
||
111 | public: |
||
112 | uint64_t getWeight(size_t CurrentSize, size_t MaxSize, |
||
113 | uint64_t CurrentWeight) override { |
||
114 | return 4; |
||
115 | } |
||
116 | |||
117 | using IRMutationStrategy::mutate; |
||
118 | void mutate(Instruction &Inst, RandomIRBuilder &IB) override; |
||
119 | }; |
||
120 | |||
121 | /// Strategy to split a random block and insert a random CFG in between. |
||
122 | class InsertCFGStrategy : public IRMutationStrategy { |
||
123 | private: |
||
124 | uint64_t MaxNumCases; |
||
125 | enum CFGToSink { Return, DirectSink, SinkOrSelfLoop, EndOfCFGToLink }; |
||
126 | |||
127 | public: |
||
128 | InsertCFGStrategy(uint64_t MNC = 8) : MaxNumCases(MNC){}; |
||
129 | uint64_t getWeight(size_t CurrentSize, size_t MaxSize, |
||
130 | uint64_t CurrentWeight) override { |
||
131 | return 5; |
||
132 | } |
||
133 | |||
134 | void mutate(BasicBlock &BB, RandomIRBuilder &IB) override; |
||
135 | |||
136 | private: |
||
137 | void connectBlocksToSink(ArrayRef<BasicBlock *> Blocks, BasicBlock *Sink, |
||
138 | RandomIRBuilder &IB); |
||
139 | }; |
||
140 | |||
141 | /// Strategy to insert PHI Nodes at the head of each basic block. |
||
142 | class InsertPHIStrategy : public IRMutationStrategy { |
||
143 | public: |
||
144 | uint64_t getWeight(size_t CurrentSize, size_t MaxSize, |
||
145 | uint64_t CurrentWeight) override { |
||
146 | return 2; |
||
147 | } |
||
148 | |||
149 | void mutate(BasicBlock &BB, RandomIRBuilder &IB) override; |
||
150 | }; |
||
151 | |||
152 | /// Strategy to select a random instruction and add a new sink (user) to it to |
||
153 | /// increate data dependency. |
||
154 | class SinkInstructionStrategy : public IRMutationStrategy { |
||
155 | public: |
||
156 | uint64_t getWeight(size_t CurrentSize, size_t MaxSize, |
||
157 | uint64_t CurrentWeight) override { |
||
158 | return 2; |
||
159 | } |
||
160 | |||
161 | void mutate(Function &F, RandomIRBuilder &IB) override; |
||
162 | void mutate(BasicBlock &BB, RandomIRBuilder &IB) override; |
||
163 | }; |
||
164 | |||
165 | /// Strategy to randomly select a block and shuffle the operations without |
||
166 | /// affecting data dependency. |
||
167 | class ShuffleBlockStrategy : public IRMutationStrategy { |
||
168 | public: |
||
169 | uint64_t getWeight(size_t CurrentSize, size_t MaxSize, |
||
170 | uint64_t CurrentWeight) override { |
||
171 | return 2; |
||
172 | } |
||
173 | |||
174 | void mutate(BasicBlock &BB, RandomIRBuilder &IB) override; |
||
175 | }; |
||
176 | |||
177 | /// Fuzzer friendly interface for the llvm bitcode parser. |
||
178 | /// |
||
179 | /// \param Data Bitcode we are going to parse |
||
180 | /// \param Size Size of the 'Data' in bytes |
||
181 | /// \return New module or nullptr in case of error |
||
182 | std::unique_ptr<Module> parseModule(const uint8_t *Data, size_t Size, |
||
183 | LLVMContext &Context); |
||
184 | |||
185 | /// Fuzzer friendly interface for the llvm bitcode printer. |
||
186 | /// |
||
187 | /// \param M Module to print |
||
188 | /// \param Dest Location to store serialized module |
||
189 | /// \param MaxSize Size of the destination buffer |
||
190 | /// \return Number of bytes that were written. When module size exceeds MaxSize |
||
191 | /// returns 0 and leaves Dest unchanged. |
||
192 | size_t writeModule(const Module &M, uint8_t *Dest, size_t MaxSize); |
||
193 | |||
194 | /// Try to parse module and verify it. May output verification errors to the |
||
195 | /// errs(). |
||
196 | /// \return New module or nullptr in case of error. |
||
197 | std::unique_ptr<Module> parseAndVerify(const uint8_t *Data, size_t Size, |
||
198 | LLVMContext &Context); |
||
199 | |||
200 | } // namespace llvm |
||
201 | |||
202 | #endif // LLVM_FUZZMUTATE_IRMUTATOR_H |