diff options
Diffstat (limited to 'deps/v8/test/fuzzer/wasm-compile.cc')
-rw-r--r-- | deps/v8/test/fuzzer/wasm-compile.cc | 335 |
1 files changed, 269 insertions, 66 deletions
diff --git a/deps/v8/test/fuzzer/wasm-compile.cc b/deps/v8/test/fuzzer/wasm-compile.cc index ded3a101f2..4192a938e8 100644 --- a/deps/v8/test/fuzzer/wasm-compile.cc +++ b/deps/v8/test/fuzzer/wasm-compile.cc @@ -30,6 +30,8 @@ namespace fuzzer { namespace { +constexpr int kMaxFunctions = 4; + class DataRange { const uint8_t* data_; size_t size_; @@ -37,46 +39,67 @@ class DataRange { public: DataRange(const uint8_t* data, size_t size) : data_(data), size_(size) {} - size_t size() const { return size_; } - - std::pair<DataRange, DataRange> split(uint32_t index) const { - return std::make_pair(DataRange(data_, index), - DataRange(data_ + index, size() - index)); + // Don't accidentally pass DataRange by value. This will reuse bytes and might + // lead to OOM because the end might not be reached. + // Define move constructor and move assignment, disallow copy constructor and + // copy assignment (below). + DataRange(DataRange&& other) : DataRange(other.data_, other.size_) { + other.data_ = nullptr; + other.size_ = 0; + } + DataRange& operator=(DataRange&& other) { + data_ = other.data_; + size_ = other.size_; + other.data_ = nullptr; + other.size_ = 0; + return *this; } - std::pair<DataRange, DataRange> split() { - uint16_t index = get<uint16_t>(); - if (size() > 0) { - index = index % size(); - } else { - index = 0; - } - return split(index); + size_t size() const { return size_; } + + DataRange split() { + uint16_t num_bytes = get<uint16_t>() % std::max(size_t{1}, size_); + DataRange split(data_, num_bytes); + data_ += num_bytes; + size_ -= num_bytes; + return split; } template <typename T> T get() { - if (size() == 0) { - return T(); - } else { - // We want to support the case where we have less than sizeof(T) bytes - // remaining in the slice. For example, if we emit an i32 constant, it's - // okay if we don't have a full four bytes available, we'll just use what - // we have. We aren't concerned about endianness because we are generating - // arbitrary expressions. - const size_t num_bytes = std::min(sizeof(T), size()); - T result = T(); - memcpy(&result, data_, num_bytes); - data_ += num_bytes; - size_ -= num_bytes; - return result; - } + // We want to support the case where we have less than sizeof(T) bytes + // remaining in the slice. For example, if we emit an i32 constant, it's + // okay if we don't have a full four bytes available, we'll just use what + // we have. We aren't concerned about endianness because we are generating + // arbitrary expressions. + const size_t num_bytes = std::min(sizeof(T), size_); + T result = T(); + memcpy(&result, data_, num_bytes); + data_ += num_bytes; + size_ -= num_bytes; + return result; } + + DISALLOW_COPY_AND_ASSIGN(DataRange); }; +ValueType GetValueType(DataRange& data) { + switch (data.get<uint8_t>() % 4) { + case 0: + return kWasmI32; + case 1: + return kWasmI64; + case 2: + return kWasmF32; + case 3: + return kWasmF64; + } + UNREACHABLE(); +} + class WasmGenerator { template <WasmOpcode Op, ValueType... Args> - void op(DataRange data) { + void op(DataRange& data) { Generate<Args...>(data); builder_->Emit(Op); } @@ -101,20 +124,20 @@ class WasmGenerator { }; template <ValueType T> - void block(DataRange data) { + void block(DataRange& data) { BlockScope block_scope(this, kExprBlock, T, T); Generate<T>(data); } template <ValueType T> - void loop(DataRange data) { + void loop(DataRange& data) { // When breaking to a loop header, don't provide any input value (hence // kWasmStmt). BlockScope block_scope(this, kExprLoop, T, kWasmStmt); Generate<T>(data); } - void br(DataRange data) { + void br(DataRange& data) { // There is always at least the block representing the function body. DCHECK(!blocks_.empty()); const uint32_t target_block = data.get<uint32_t>() % blocks_.size(); @@ -161,7 +184,7 @@ class WasmGenerator { } template <WasmOpcode memory_op, ValueType... arg_types> - void memop(DataRange data) { + void memop(DataRange& data) { const uint8_t align = data.get<uint8_t>() % (max_alignment(memory_op) + 1); const uint32_t offset = data.get<uint32_t>(); @@ -173,21 +196,131 @@ class WasmGenerator { builder_->EmitU32V(offset); } + void drop(DataRange& data) { + Generate(GetValueType(data), data); + builder_->Emit(kExprDrop); + } + + template <ValueType wanted_type> + void call(DataRange& data) { + call(data, wanted_type); + } + + void Convert(ValueType src, ValueType dst) { + auto idx = [](ValueType t) -> int { + switch (t) { + case kWasmI32: + return 0; + case kWasmI64: + return 1; + case kWasmF32: + return 2; + case kWasmF64: + return 3; + default: + UNREACHABLE(); + } + }; + static constexpr WasmOpcode kConvertOpcodes[] = { + // {i32, i64, f32, f64} -> i32 + kExprNop, kExprI32ConvertI64, kExprI32SConvertF32, kExprI32SConvertF64, + // {i32, i64, f32, f64} -> i64 + kExprI64SConvertI32, kExprNop, kExprI64SConvertF32, kExprI64SConvertF64, + // {i32, i64, f32, f64} -> f32 + kExprF32SConvertI32, kExprF32SConvertI64, kExprNop, kExprF32ConvertF64, + // {i32, i64, f32, f64} -> f64 + kExprF64SConvertI32, kExprF64SConvertI64, kExprF64ConvertF32, kExprNop}; + int arr_idx = idx(dst) << 2 | idx(src); + builder_->Emit(kConvertOpcodes[arr_idx]); + } + + void call(DataRange& data, ValueType wanted_type) { + int func_index = data.get<uint8_t>() % functions_.size(); + FunctionSig* sig = functions_[func_index]; + // Generate arguments. + for (size_t i = 0; i < sig->parameter_count(); ++i) { + Generate(sig->GetParam(i), data); + } + // Emit call. + builder_->EmitWithU32V(kExprCallFunction, func_index); + // Convert the return value to the wanted type. + ValueType return_type = + sig->return_count() == 0 ? kWasmStmt : sig->GetReturn(0); + if (return_type == kWasmStmt && wanted_type != kWasmStmt) { + // The call did not generate a value. Thus just generate it here. + Generate(wanted_type, data); + } else if (return_type != kWasmStmt && wanted_type == kWasmStmt) { + // The call did generate a value, but we did not want one. + builder_->Emit(kExprDrop); + } else if (return_type != wanted_type) { + // If the returned type does not match the wanted type, convert it. + Convert(return_type, wanted_type); + } + } + + struct Local { + uint32_t index; + ValueType type = kWasmStmt; + Local() = default; + Local(uint32_t index, ValueType type) : index(index), type(type) {} + bool is_valid() const { return type != kWasmStmt; } + }; + + Local GetRandomLocal(DataRange& data) { + uint32_t num_params = + static_cast<uint32_t>(builder_->signature()->parameter_count()); + uint32_t num_locals = static_cast<uint32_t>(locals_.size()); + if (num_params + num_locals == 0) return {}; + uint32_t index = data.get<uint8_t>() % (num_params + num_locals); + ValueType type = index < num_params ? builder_->signature()->GetParam(index) + : locals_[index - num_params]; + return {index, type}; + } + + template <ValueType wanted_type> + void local_op(DataRange& data, WasmOpcode opcode) { + Local local = GetRandomLocal(data); + // If there are no locals and no parameters, just generate any value (if a + // value is needed), or do nothing. + if (!local.is_valid()) { + if (wanted_type == kWasmStmt) return; + return Generate<wanted_type>(data); + } + + if (opcode != kExprGetLocal) Generate(local.type, data); + builder_->EmitWithU32V(opcode, local.index); + if (wanted_type != kWasmStmt && local.type != wanted_type) { + Convert(local.type, wanted_type); + } + } + + template <ValueType wanted_type> + void get_local(DataRange& data) { + local_op<wanted_type>(data, kExprGetLocal); + } + + void set_local(DataRange& data) { local_op<kWasmStmt>(data, kExprSetLocal); } + + template <ValueType wanted_type> + void tee_local(DataRange& data) { + local_op<wanted_type>(data, kExprTeeLocal); + } + template <ValueType T1, ValueType T2> - void sequence(DataRange data) { + void sequence(DataRange& data) { Generate<T1, T2>(data); } - void current_memory(DataRange data) { + void current_memory(DataRange& data) { builder_->EmitWithU8(kExprMemorySize, 0); } - void grow_memory(DataRange data); + void grow_memory(DataRange& data); - using generate_fn = void (WasmGenerator::*const)(DataRange); + using generate_fn = void (WasmGenerator::*const)(DataRange&); template <size_t N> - void GenerateOneOf(generate_fn (&alternates)[N], DataRange data) { + void GenerateOneOf(generate_fn (&alternates)[N], DataRange& data) { static_assert(N < std::numeric_limits<uint8_t>::max(), "Too many alternates. Replace with a bigger type if needed."); const auto which = data.get<uint8_t>(); @@ -209,26 +342,39 @@ class WasmGenerator { }; public: - explicit WasmGenerator(WasmFunctionBuilder* fn) : builder_(fn) { - DCHECK_EQ(1, fn->signature()->return_count()); - blocks_.push_back(fn->signature()->GetReturn(0)); + WasmGenerator(WasmFunctionBuilder* fn, + const std::vector<FunctionSig*>& functions, DataRange& data) + : builder_(fn), functions_(functions) { + FunctionSig* sig = fn->signature(); + DCHECK_GE(1, sig->return_count()); + blocks_.push_back(sig->return_count() == 0 ? kWasmStmt : sig->GetReturn(0)); + + constexpr uint32_t kMaxLocals = 32; + locals_.resize(data.get<uint8_t>() % kMaxLocals); + for (ValueType& local : locals_) { + local = GetValueType(data); + fn->AddLocal(local); + } } - void Generate(ValueType type, DataRange data); + void Generate(ValueType type, DataRange& data); template <ValueType T> - void Generate(DataRange data); + void Generate(DataRange& data); template <ValueType T1, ValueType T2, ValueType... Ts> - void Generate(DataRange data) { - const auto parts = data.split(); - Generate<T1>(parts.first); - Generate<T2, Ts...>(parts.second); + void Generate(DataRange& data) { + // TODO(clemensh): Implement a more even split. + auto first_data = data.split(); + Generate<T1>(first_data); + Generate<T2, Ts...>(data); } private: WasmFunctionBuilder* builder_; std::vector<ValueType> blocks_; + const std::vector<FunctionSig*>& functions_; + std::vector<ValueType> locals_; uint32_t recursion_depth = 0; static constexpr uint32_t kMaxRecursionDepth = 64; @@ -239,7 +385,7 @@ class WasmGenerator { }; template <> -void WasmGenerator::Generate<kWasmStmt>(DataRange data) { +void WasmGenerator::Generate<kWasmStmt>(DataRange& data) { GeneratorRecursionScope rec_scope(this); if (recursion_limit_reached() || data.size() == 0) return; @@ -257,13 +403,18 @@ void WasmGenerator::Generate<kWasmStmt>(DataRange data) { &WasmGenerator::memop<kExprI64StoreMem32, kWasmI64>, &WasmGenerator::memop<kExprF32StoreMem, kWasmF32>, &WasmGenerator::memop<kExprF64StoreMem, kWasmF64>, - }; + + &WasmGenerator::drop, + + &WasmGenerator::call<kWasmStmt>, + + &WasmGenerator::set_local}; GenerateOneOf(alternates, data); } template <> -void WasmGenerator::Generate<kWasmI32>(DataRange data) { +void WasmGenerator::Generate<kWasmI32>(DataRange& data) { GeneratorRecursionScope rec_scope(this); if (recursion_limit_reached() || data.size() <= sizeof(uint32_t)) { builder_->EmitI32Const(data.get<uint32_t>()); @@ -338,13 +489,18 @@ void WasmGenerator::Generate<kWasmI32>(DataRange data) { &WasmGenerator::memop<kExprI32LoadMem16U>, &WasmGenerator::current_memory, - &WasmGenerator::grow_memory}; + &WasmGenerator::grow_memory, + + &WasmGenerator::get_local<kWasmI32>, + &WasmGenerator::tee_local<kWasmI32>, + + &WasmGenerator::call<kWasmI32>}; GenerateOneOf(alternates, data); } template <> -void WasmGenerator::Generate<kWasmI64>(DataRange data) { +void WasmGenerator::Generate<kWasmI64>(DataRange& data) { GeneratorRecursionScope rec_scope(this); if (recursion_limit_reached() || data.size() <= sizeof(uint64_t)) { builder_->EmitI64Const(data.get<int64_t>()); @@ -385,13 +541,18 @@ void WasmGenerator::Generate<kWasmI64>(DataRange data) { &WasmGenerator::memop<kExprI64LoadMem16S>, &WasmGenerator::memop<kExprI64LoadMem16U>, &WasmGenerator::memop<kExprI64LoadMem32S>, - &WasmGenerator::memop<kExprI64LoadMem32U>}; + &WasmGenerator::memop<kExprI64LoadMem32U>, + + &WasmGenerator::get_local<kWasmI64>, + &WasmGenerator::tee_local<kWasmI64>, + + &WasmGenerator::call<kWasmI64>}; GenerateOneOf(alternates, data); } template <> -void WasmGenerator::Generate<kWasmF32>(DataRange data) { +void WasmGenerator::Generate<kWasmF32>(DataRange& data) { GeneratorRecursionScope rec_scope(this); if (recursion_limit_reached() || data.size() <= sizeof(float)) { builder_->EmitF32Const(data.get<float>()); @@ -408,13 +569,18 @@ void WasmGenerator::Generate<kWasmF32>(DataRange data) { &WasmGenerator::block<kWasmF32>, &WasmGenerator::loop<kWasmF32>, - &WasmGenerator::memop<kExprF32LoadMem>}; + &WasmGenerator::memop<kExprF32LoadMem>, + + &WasmGenerator::get_local<kWasmF32>, + &WasmGenerator::tee_local<kWasmF32>, + + &WasmGenerator::call<kWasmF32>}; GenerateOneOf(alternates, data); } template <> -void WasmGenerator::Generate<kWasmF64>(DataRange data) { +void WasmGenerator::Generate<kWasmF64>(DataRange& data) { GeneratorRecursionScope rec_scope(this); if (recursion_limit_reached() || data.size() <= sizeof(double)) { builder_->EmitF64Const(data.get<double>()); @@ -431,17 +597,22 @@ void WasmGenerator::Generate<kWasmF64>(DataRange data) { &WasmGenerator::block<kWasmF64>, &WasmGenerator::loop<kWasmF64>, - &WasmGenerator::memop<kExprF64LoadMem>}; + &WasmGenerator::memop<kExprF64LoadMem>, + + &WasmGenerator::get_local<kWasmF64>, + &WasmGenerator::tee_local<kWasmF64>, + + &WasmGenerator::call<kWasmF64>}; GenerateOneOf(alternates, data); } -void WasmGenerator::grow_memory(DataRange data) { +void WasmGenerator::grow_memory(DataRange& data) { Generate<kWasmI32>(data); builder_->EmitWithU8(kExprGrowMemory, 0); } -void WasmGenerator::Generate(ValueType type, DataRange data) { +void WasmGenerator::Generate(ValueType type, DataRange& data) { switch (type) { case kWasmStmt: return Generate<kWasmStmt>(data); @@ -457,6 +628,19 @@ void WasmGenerator::Generate(ValueType type, DataRange data) { UNREACHABLE(); } } + +FunctionSig* GenerateSig(Zone* zone, DataRange& data) { + // Generate enough parameters to spill some to the stack. + constexpr int kMaxParameters = 15; + int num_params = int{data.get<uint8_t>()} % (kMaxParameters + 1); + bool has_return = data.get<bool>(); + + FunctionSig::Builder builder(zone, has_return ? 1 : 0, num_params); + if (has_return) builder.AddReturn(GetValueType(data)); + for (int i = 0; i < num_params; ++i) builder.AddParam(GetValueType(data)); + return builder.Build(); +} + } // namespace class WasmCompileFuzzer : public WasmExecutionFuzzer { @@ -469,13 +653,32 @@ class WasmCompileFuzzer : public WasmExecutionFuzzer { WasmModuleBuilder builder(zone); - WasmFunctionBuilder* f = builder.AddFunction(sigs.i_iii()); + DataRange range(data, static_cast<uint32_t>(size)); + std::vector<FunctionSig*> function_signatures; + function_signatures.push_back(sigs.i_iii()); + + static_assert(kMaxFunctions >= 1, "need min. 1 function"); + int num_functions = 1 + (range.get<uint8_t>() % kMaxFunctions); + + for (int i = 1; i < num_functions; ++i) { + function_signatures.push_back(GenerateSig(zone, range)); + } - WasmGenerator gen(f); - gen.Generate<kWasmI32>(DataRange(data, static_cast<uint32_t>(size))); + for (int i = 0; i < num_functions; ++i) { + DataRange function_range = + i == num_functions - 1 ? std::move(range) : range.split(); - f->Emit(kExprEnd); - builder.AddExport(CStrVector("main"), f); + FunctionSig* sig = function_signatures[i]; + WasmFunctionBuilder* f = builder.AddFunction(sig); + + WasmGenerator gen(f, function_signatures, function_range); + ValueType return_type = + sig->return_count() == 0 ? kWasmStmt : sig->GetReturn(0); + gen.Generate(return_type, function_range); + + f->Emit(kExprEnd); + if (i == 0) builder.AddExport(CStrVector("main"), f); + } builder.SetMaxMemorySize(32); builder.WriteTo(buffer); @@ -485,8 +688,8 @@ class WasmCompileFuzzer : public WasmExecutionFuzzer { new WasmValue[3]{WasmValue(1), WasmValue(2), WasmValue(3)}); compiler_args.reset(new Handle<Object>[3]{ - handle(Smi::FromInt(1), isolate), handle(Smi::FromInt(1), isolate), - handle(Smi::FromInt(1), isolate)}); + handle(Smi::FromInt(1), isolate), handle(Smi::FromInt(2), isolate), + handle(Smi::FromInt(3), isolate)}); return true; } }; |