Line data Source code
1 : // Copyright 2018 the V8 project authors. All rights reserved.
2 : // Use of this source code is governed by a BSD-style license that can be
3 : // found in the LICENSE file.
4 :
5 : #include <cstddef>
6 : #include <cstdint>
7 :
8 : #include "src/compiler/backend/instruction-selector.h"
9 : #include "src/compiler/graph.h"
10 : #include "src/compiler/linkage.h"
11 : #include "src/compiler/node.h"
12 : #include "src/compiler/operator.h"
13 : #include "src/compiler/pipeline.h"
14 : #include "src/compiler/raw-machine-assembler.h"
15 : #include "src/compiler/wasm-compiler.h"
16 : #include "src/machine-type.h"
17 : #include "src/objects-inl.h"
18 : #include "src/objects.h"
19 : #include "src/optimized-compilation-info.h"
20 : #include "src/simulator.h"
21 : #include "src/wasm/wasm-engine.h"
22 : #include "src/wasm/wasm-features.h"
23 : #include "src/wasm/wasm-limits.h"
24 : #include "src/wasm/wasm-objects-inl.h"
25 : #include "src/wasm/wasm-objects.h"
26 : #include "src/wasm/wasm-opcodes.h"
27 : #include "src/zone/accounting-allocator.h"
28 : #include "src/zone/zone.h"
29 : #include "test/fuzzer/fuzzer-support.h"
30 :
31 : namespace v8 {
32 : namespace internal {
33 : namespace compiler {
34 : namespace fuzzer {
35 :
36 : constexpr MachineType kTypes[] = {
37 : // The first entry is just a placeholder, because '0' is a separator.
38 : MachineType(),
39 : #if !V8_TARGET_ARCH_32_BIT
40 : MachineType::Int64(),
41 : #endif
42 : MachineType::Int32(), MachineType::Float32(), MachineType::Float64()};
43 :
44 : static constexpr int kNumTypes = arraysize(kTypes);
45 :
46 : class InputProvider {
47 : public:
48 : InputProvider(const uint8_t* data, size_t size)
49 1 : : current_(data), end_(data + size) {}
50 :
51 : size_t NumNonZeroBytes(size_t offset, int limit) {
52 : DCHECK_LE(limit, std::numeric_limits<uint8_t>::max());
53 : DCHECK_GE(current_ + offset, current_);
54 : const uint8_t* p;
55 5 : for (p = current_ + offset; p < end_; ++p) {
56 6 : if (*p % limit == 0) break;
57 : }
58 2 : return p - current_ - offset;
59 : }
60 :
61 : int NextInt8(int limit) {
62 : DCHECK_LE(limit, std::numeric_limits<uint8_t>::max());
63 9 : if (current_ == end_) return 0;
64 8 : uint8_t result = *current_;
65 9 : current_++;
66 8 : return static_cast<int>(result) % limit;
67 : }
68 :
69 : int NextInt32(int limit) {
70 0 : if (current_ + sizeof(uint32_t) > end_) return 0;
71 : int result =
72 : ReadLittleEndianValue<int>(reinterpret_cast<Address>(current_));
73 0 : current_ += sizeof(uint32_t);
74 0 : return result % limit;
75 : }
76 :
77 : private:
78 : const uint8_t* current_;
79 : const uint8_t* end_;
80 : };
81 :
82 0 : MachineType RandomType(InputProvider* input) {
83 4 : return kTypes[input->NextInt8(kNumTypes)];
84 : }
85 :
86 4 : int index(MachineType type) { return static_cast<int>(type.representation()); }
87 :
88 5 : Node* Constant(RawMachineAssembler& m, MachineType type, int value) {
89 5 : switch (type.representation()) {
90 : case MachineRepresentation::kWord32:
91 3 : return m.Int32Constant(static_cast<int32_t>(value));
92 : case MachineRepresentation::kWord64:
93 0 : return m.Int64Constant(static_cast<int64_t>(value));
94 : case MachineRepresentation::kFloat32:
95 2 : return m.Float32Constant(static_cast<float>(value));
96 : case MachineRepresentation::kFloat64:
97 0 : return m.Float64Constant(static_cast<double>(value));
98 : default:
99 0 : UNREACHABLE();
100 : }
101 : }
102 :
103 2 : Node* ToInt32(RawMachineAssembler& m, MachineType type, Node* a) {
104 2 : switch (type.representation()) {
105 : case MachineRepresentation::kWord32:
106 : return a;
107 : case MachineRepresentation::kWord64:
108 0 : return m.TruncateInt64ToInt32(a);
109 : case MachineRepresentation::kFloat32:
110 0 : return m.TruncateFloat32ToInt32(a);
111 : case MachineRepresentation::kFloat64:
112 0 : return m.RoundFloat64ToInt32(a);
113 : default:
114 0 : UNREACHABLE();
115 : }
116 : }
117 :
118 1 : CallDescriptor* CreateRandomCallDescriptor(Zone* zone, size_t return_count,
119 : size_t param_count,
120 : InputProvider* input) {
121 : wasm::FunctionSig::Builder builder(zone, return_count, param_count);
122 1 : for (size_t i = 0; i < param_count; i++) {
123 : MachineType type = RandomType(input);
124 0 : builder.AddParam(wasm::ValueTypes::ValueTypeFor(type));
125 : }
126 : // Read the end byte of the parameters.
127 : input->NextInt8(1);
128 :
129 4 : for (size_t i = 0; i < return_count; i++) {
130 : MachineType type = RandomType(input);
131 4 : builder.AddReturn(wasm::ValueTypes::ValueTypeFor(type));
132 : }
133 :
134 1 : return compiler::GetWasmCallDescriptor(zone, builder.Build());
135 : }
136 :
137 1 : std::unique_ptr<wasm::NativeModule> AllocateNativeModule(i::Isolate* isolate,
138 : size_t code_size) {
139 2 : std::shared_ptr<wasm::WasmModule> module(new wasm::WasmModule);
140 1 : module->num_declared_functions = 1;
141 :
142 : // We have to add the code object to a NativeModule, because the
143 : // WasmCallDescriptor assumes that code is on the native heap and not
144 : // within a code object.
145 : return isolate->wasm_engine()->NewNativeModule(
146 3 : isolate, i::wasm::kAllWasmFeatures, code_size, false, std::move(module));
147 : }
148 :
149 1 : extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
150 1 : v8_fuzzer::FuzzerSupport* support = v8_fuzzer::FuzzerSupport::Get();
151 : v8::Isolate* isolate = support->GetIsolate();
152 : i::Isolate* i_isolate = reinterpret_cast<Isolate*>(isolate);
153 : v8::Isolate::Scope isolate_scope(isolate);
154 2 : v8::HandleScope handle_scope(isolate);
155 1 : v8::Context::Scope context_scope(support->GetContext());
156 2 : v8::TryCatch try_catch(isolate);
157 2 : v8::internal::AccountingAllocator allocator;
158 2 : Zone zone(&allocator, ZONE_NAME);
159 :
160 : InputProvider input(data, size);
161 : // Create randomized descriptor.
162 : size_t param_count = input.NumNonZeroBytes(0, kNumTypes);
163 1 : if (param_count > Code::kMaxArguments) return 0;
164 :
165 1 : size_t return_count = input.NumNonZeroBytes(param_count + 1, kNumTypes);
166 1 : if (return_count > wasm::kV8MaxWasmFunctionMultiReturns) return 0;
167 :
168 15 : CallDescriptor* desc =
169 1 : CreateRandomCallDescriptor(&zone, return_count, param_count, &input);
170 :
171 1 : if (FLAG_wasm_fuzzer_gen_test) {
172 : // Print some debugging output which describes the produced signature.
173 : printf("[");
174 0 : for (size_t j = 0; j < param_count; ++j) {
175 : // Parameter 0 is the WasmContext.
176 : printf(" %s", MachineReprToString(
177 0 : desc->GetParameterType(j + 1).representation()));
178 : }
179 : printf(" ] -> [");
180 0 : for (size_t j = 0; j < desc->ReturnCount(); ++j) {
181 : printf(" %s",
182 0 : MachineReprToString(desc->GetReturnType(j).representation()));
183 : }
184 : printf(" ]\n\n");
185 : }
186 :
187 : // Count parameters of each type.
188 : constexpr size_t kNumMachineRepresentations =
189 : static_cast<size_t>(MachineRepresentation::kLastRepresentation) + 1;
190 :
191 : // Trivial hash table for the number of occurrences of parameter types. The
192 : // MachineRepresentation of the parameter types is used as hash code.
193 1 : int counts[kNumMachineRepresentations] = {0};
194 2 : for (size_t i = 0; i < param_count; ++i) {
195 : // Parameter 0 is the WasmContext.
196 0 : ++counts[index(desc->GetParameterType(i + 1))];
197 : }
198 :
199 : // Generate random inputs.
200 1 : std::unique_ptr<int[]> inputs(new int[param_count]);
201 1 : std::unique_ptr<int[]> outputs(new int[desc->ReturnCount()]);
202 1 : for (size_t i = 0; i < param_count; ++i) {
203 0 : inputs[i] = input.NextInt32(10000);
204 : }
205 :
206 : RawMachineAssembler callee(
207 1 : i_isolate, new (&zone) Graph(&zone), desc,
208 : MachineType::PointerRepresentation(),
209 2 : InstructionSelector::SupportedMachineOperatorFlags());
210 :
211 : // Generate callee, returning random picks of its parameters.
212 1 : std::unique_ptr<Node* []> params(new Node*[desc->ParameterCount() + 2]);
213 : // The first input of a return is the number of stack slots that should be
214 : // popped before returning.
215 1 : std::unique_ptr<Node* []> returns(new Node*[desc->ReturnCount() + 1]);
216 2 : for (size_t i = 0; i < param_count; ++i) {
217 : // Parameter(0) is the WasmContext.
218 0 : params[i] = callee.Parameter(i + 1);
219 : }
220 :
221 9 : for (size_t i = 0; i < desc->ReturnCount(); ++i) {
222 : MachineType type = desc->GetReturnType(i);
223 : // Find a random same-type parameter to return. Use a constant if none.
224 4 : if (counts[index(type)] == 0) {
225 4 : returns[i] = Constant(callee, type, 42);
226 4 : outputs[i] = 42;
227 : } else {
228 : int n = input.NextInt32(counts[index(type)]);
229 : int k = 0;
230 0 : while (desc->GetParameterType(k + 1) != desc->GetReturnType(i) ||
231 : --n > 0) {
232 : ++k;
233 : }
234 0 : returns[i] = params[k];
235 0 : outputs[i] = inputs[k];
236 : }
237 : }
238 1 : callee.Return(static_cast<int>(desc->ReturnCount()), returns.get());
239 :
240 2 : OptimizedCompilationInfo info(ArrayVector("testing"), &zone, Code::STUB);
241 : Handle<Code> code = Pipeline::GenerateCodeForTesting(
242 : &info, i_isolate, desc, callee.graph(),
243 2 : AssemblerOptions::Default(i_isolate), callee.Export())
244 2 : .ToHandleChecked();
245 :
246 : std::unique_ptr<wasm::NativeModule> module =
247 1 : AllocateNativeModule(i_isolate, code->raw_instruction_size());
248 1 : byte* code_start = module->AddCodeForTesting(code)->instructions().start();
249 : // Generate wrapper.
250 : int expect = 0;
251 :
252 : MachineSignature::Builder sig_builder(&zone, 1, 0);
253 : sig_builder.AddReturn(MachineType::Int32());
254 :
255 : CallDescriptor* wrapper_desc =
256 1 : Linkage::GetSimplifiedCDescriptor(&zone, sig_builder.Build());
257 : RawMachineAssembler caller(
258 1 : i_isolate, new (&zone) Graph(&zone), wrapper_desc,
259 : MachineType::PointerRepresentation(),
260 2 : InstructionSelector::SupportedMachineOperatorFlags());
261 :
262 1 : params[0] = caller.PointerConstant(code_start);
263 : // WasmContext dummy.
264 1 : params[1] = caller.PointerConstant(nullptr);
265 2 : for (size_t i = 0; i < param_count; ++i) {
266 0 : params[i + 2] = Constant(caller, desc->GetParameterType(i + 1), inputs[i]);
267 : }
268 : Node* call = caller.AddNode(caller.common()->Call(desc),
269 1 : static_cast<int>(param_count + 2), params.get());
270 1 : Node* ret = Constant(caller, MachineType::Int32(), 0);
271 10 : for (size_t i = 0; i < desc->ReturnCount(); ++i) {
272 : // Skip roughly one third of the outputs.
273 4 : if (input.NextInt8(3) == 0) continue;
274 : Node* ret_i = (desc->ReturnCount() == 1)
275 : ? call
276 2 : : caller.AddNode(caller.common()->Projection(i), call);
277 2 : ret = caller.Int32Add(ret, ToInt32(caller, desc->GetReturnType(i), ret_i));
278 2 : expect += outputs[i];
279 : }
280 1 : caller.Return(ret);
281 :
282 : // Call the wrapper.
283 : OptimizedCompilationInfo wrapper_info(ArrayVector("wrapper"), &zone,
284 2 : Code::STUB);
285 : Handle<Code> wrapper_code =
286 : Pipeline::GenerateCodeForTesting(
287 : &wrapper_info, i_isolate, wrapper_desc, caller.graph(),
288 2 : AssemblerOptions::Default(i_isolate), caller.Export())
289 2 : .ToHandleChecked();
290 :
291 : auto fn = GeneratedCode<int32_t>::FromCode(*wrapper_code);
292 : int result = fn.Call();
293 :
294 1 : CHECK_EQ(expect, result);
295 : return 0;
296 : }
297 :
298 : } // namespace fuzzer
299 : } // namespace compiler
300 : } // namespace internal
301 3 : } // namespace v8
|