// Copyright 2017 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include <stddef.h> #include <stdint.h> #include <stdlib.h> #include <algorithm> #include "include/v8.h" #include "src/isolate.h" #include "src/objects-inl.h" #include "src/objects.h" #include "src/ostreams.h" #include "src/wasm/wasm-interpreter.h" #include "src/wasm/wasm-module-builder.h" #include "src/wasm/wasm-module.h" #include "test/common/wasm/test-signatures.h" #include "test/common/wasm/wasm-module-runner.h" #include "test/fuzzer/fuzzer-support.h" #include "test/fuzzer/wasm-fuzzer-common.h" typedef uint8_t byte; namespace v8 { namespace internal { namespace wasm { namespace fuzzer { namespace { constexpr int kMaxFunctions = 4; constexpr int kMaxGlobals = 64; class DataRange { Vector<const uint8_t> data_; public: explicit DataRange(Vector<const uint8_t> data) : data_(data) {} // 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) V8_NOEXCEPT : DataRange(other.data_) { other.data_ = {}; } DataRange& operator=(DataRange&& other) V8_NOEXCEPT { data_ = other.data_; other.data_ = {}; return *this; } size_t size() const { return data_.size(); } DataRange split() { uint16_t num_bytes = get<uint16_t>() % std::max(size_t{1}, data_.size()); DataRange split(data_.SubVector(0, num_bytes)); data_ += num_bytes; return split; } template <typename T, size_t max_bytes = sizeof(T)> T get() { STATIC_ASSERT(max_bytes <= sizeof(T)); // 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(max_bytes, data_.size()); T result = T(); memcpy(&result, data_.start(), num_bytes); data_ += 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) { Generate<Args...>(data); builder_->Emit(Op); } class BlockScope { public: BlockScope(WasmGenerator* gen, WasmOpcode block_type, ValueType result_type, ValueType br_type) : gen_(gen) { gen->blocks_.push_back(br_type); gen->builder_->EmitWithU8(block_type, ValueTypes::ValueTypeCodeFor(result_type)); } ~BlockScope() { gen_->builder_->Emit(kExprEnd); gen_->blocks_.pop_back(); } private: WasmGenerator* const gen_; }; template <ValueType T> void block(DataRange& data) { BlockScope block_scope(this, kExprBlock, T, T); Generate<T>(data); } template <ValueType T> 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); } enum IfType { kIf, kIfElse }; template <ValueType T, IfType type> void if_(DataRange& data) { static_assert(T == kWasmStmt || type == kIfElse, "if without else cannot produce a value"); Generate<kWasmI32>(data); BlockScope block_scope(this, kExprIf, T, T); Generate<T>(data); if (type == kIfElse) { builder_->Emit(kExprElse); Generate<T>(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(); const ValueType break_type = blocks_[target_block]; Generate(break_type, data); builder_->EmitWithI32V( kExprBr, static_cast<uint32_t>(blocks_.size()) - 1 - target_block); } template <ValueType wanted_type> void br_if(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(); const ValueType break_type = blocks_[target_block]; Generate(break_type, data); Generate(kWasmI32, data); builder_->EmitWithI32V( kExprBrIf, static_cast<uint32_t>(blocks_.size()) - 1 - target_block); ConvertOrGenerate(break_type, wanted_type, data); } // TODO(eholk): make this function constexpr once gcc supports it static uint8_t max_alignment(WasmOpcode memop) { switch (memop) { case kExprI64LoadMem: case kExprF64LoadMem: case kExprI64StoreMem: case kExprF64StoreMem: return 3; case kExprI32LoadMem: case kExprI64LoadMem32S: case kExprI64LoadMem32U: case kExprF32LoadMem: case kExprI32StoreMem: case kExprI64StoreMem32: case kExprF32StoreMem: return 2; case kExprI32LoadMem16S: case kExprI32LoadMem16U: case kExprI64LoadMem16S: case kExprI64LoadMem16U: case kExprI32StoreMem16: case kExprI64StoreMem16: return 1; case kExprI32LoadMem8S: case kExprI32LoadMem8U: case kExprI64LoadMem8S: case kExprI64LoadMem8U: case kExprI32StoreMem8: case kExprI64StoreMem8: return 0; default: return 0; } } template <WasmOpcode memory_op, ValueType... arg_types> 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>(); // Generate the index and the arguments, if any. Generate<kWasmI32, arg_types...>(data); builder_->Emit(memory_op); builder_->EmitU32V(align); 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 ConvertOrGenerate(ValueType src, ValueType dst, DataRange& data) { if (src == dst) return; if (src == kWasmStmt && dst != kWasmStmt) { Generate(dst, data); } else if (dst == kWasmStmt && src != kWasmStmt) { builder_->Emit(kExprDrop); } else { Convert(src, dst); } } 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 Var { uint32_t index; ValueType type = kWasmStmt; Var() = default; Var(uint32_t index, ValueType type) : index(index), type(type) {} bool is_valid() const { return type != kWasmStmt; } }; Var 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) { Var 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) { static_assert(wanted_type != kWasmStmt, "illegal type"); 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 <size_t num_bytes> void i32_const(DataRange& data) { builder_->EmitI32Const(data.get<int32_t, num_bytes>()); } template <size_t num_bytes> void i64_const(DataRange& data) { builder_->EmitI64Const(data.get<int64_t, num_bytes>()); } Var GetRandomGlobal(DataRange& data, bool ensure_mutable) { uint32_t index; if (ensure_mutable) { if (mutable_globals_.empty()) return {}; index = mutable_globals_[data.get<uint8_t>() % mutable_globals_.size()]; } else { if (globals_.empty()) return {}; index = data.get<uint8_t>() % globals_.size(); } ValueType type = globals_[index]; return {index, type}; } template <ValueType wanted_type> void global_op(DataRange& data) { constexpr bool is_set = wanted_type == kWasmStmt; Var global = GetRandomGlobal(data, is_set); // If there are no globals, just generate any value (if a value is needed), // or do nothing. if (!global.is_valid()) { if (wanted_type == kWasmStmt) return; return Generate<wanted_type>(data); } if (is_set) Generate(global.type, data); builder_->EmitWithU32V(is_set ? kExprSetGlobal : kExprGetGlobal, global.index); if (!is_set && global.type != wanted_type) { Convert(global.type, wanted_type); } } template <ValueType wanted_type> void get_global(DataRange& data) { static_assert(wanted_type != kWasmStmt, "illegal type"); global_op<wanted_type>(data); } void set_global(DataRange& data) { global_op<kWasmStmt>(data); } template <ValueType... Types> void sequence(DataRange& data) { Generate<Types...>(data); } void current_memory(DataRange& data) { builder_->EmitWithU8(kExprMemorySize, 0); } void grow_memory(DataRange& data); using generate_fn = void (WasmGenerator::*const)(DataRange&); template <size_t N> 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>(); generate_fn alternate = alternates[which % N]; (this->*alternate)(data); } struct GeneratorRecursionScope { explicit GeneratorRecursionScope(WasmGenerator* gen) : gen(gen) { ++gen->recursion_depth; DCHECK_LE(gen->recursion_depth, kMaxRecursionDepth); } ~GeneratorRecursionScope() { DCHECK_GT(gen->recursion_depth, 0); --gen->recursion_depth; } WasmGenerator* gen; }; public: WasmGenerator(WasmFunctionBuilder* fn, const std::vector<FunctionSig*>& functions, const std::vector<ValueType>& globals, const std::vector<uint8_t>& mutable_globals, DataRange& data) : builder_(fn), functions_(functions), globals_(globals), mutable_globals_(mutable_globals) { 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); template <ValueType T> void Generate(DataRange& data); template <ValueType T1, ValueType T2, ValueType... Ts> 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_; std::vector<ValueType> globals_; std::vector<uint8_t> mutable_globals_; // indexes into {globals_}. uint32_t recursion_depth = 0; static constexpr uint32_t kMaxRecursionDepth = 64; bool recursion_limit_reached() { return recursion_depth >= kMaxRecursionDepth; } }; template <> void WasmGenerator::Generate<kWasmStmt>(DataRange& data) { GeneratorRecursionScope rec_scope(this); if (recursion_limit_reached() || data.size() == 0) return; constexpr generate_fn alternates[] = { &WasmGenerator::sequence<kWasmStmt, kWasmStmt>, &WasmGenerator::sequence<kWasmStmt, kWasmStmt, kWasmStmt, kWasmStmt>, &WasmGenerator::sequence<kWasmStmt, kWasmStmt, kWasmStmt, kWasmStmt, kWasmStmt, kWasmStmt, kWasmStmt, kWasmStmt>, &WasmGenerator::block<kWasmStmt>, &WasmGenerator::loop<kWasmStmt>, &WasmGenerator::if_<kWasmStmt, kIf>, &WasmGenerator::if_<kWasmStmt, kIfElse>, &WasmGenerator::br, &WasmGenerator::br_if<kWasmStmt>, &WasmGenerator::memop<kExprI32StoreMem, kWasmI32>, &WasmGenerator::memop<kExprI32StoreMem8, kWasmI32>, &WasmGenerator::memop<kExprI32StoreMem16, kWasmI32>, &WasmGenerator::memop<kExprI64StoreMem, kWasmI64>, &WasmGenerator::memop<kExprI64StoreMem8, kWasmI64>, &WasmGenerator::memop<kExprI64StoreMem16, kWasmI64>, &WasmGenerator::memop<kExprI64StoreMem32, kWasmI64>, &WasmGenerator::memop<kExprF32StoreMem, kWasmF32>, &WasmGenerator::memop<kExprF64StoreMem, kWasmF64>, &WasmGenerator::drop, &WasmGenerator::call<kWasmStmt>, &WasmGenerator::set_local, &WasmGenerator::set_global}; GenerateOneOf(alternates, data); } template <> void WasmGenerator::Generate<kWasmI32>(DataRange& data) { GeneratorRecursionScope rec_scope(this); if (recursion_limit_reached() || data.size() <= 1) { builder_->EmitI32Const(data.get<uint32_t>()); return; } constexpr generate_fn alternates[] = { &WasmGenerator::i32_const<1>, &WasmGenerator::i32_const<2>, &WasmGenerator::i32_const<3>, &WasmGenerator::i32_const<4>, &WasmGenerator::sequence<kWasmI32, kWasmStmt>, &WasmGenerator::sequence<kWasmStmt, kWasmI32>, &WasmGenerator::sequence<kWasmStmt, kWasmI32, kWasmStmt>, &WasmGenerator::op<kExprI32Eqz, kWasmI32>, &WasmGenerator::op<kExprI32Eq, kWasmI32, kWasmI32>, &WasmGenerator::op<kExprI32Ne, kWasmI32, kWasmI32>, &WasmGenerator::op<kExprI32LtS, kWasmI32, kWasmI32>, &WasmGenerator::op<kExprI32LtU, kWasmI32, kWasmI32>, &WasmGenerator::op<kExprI32GeS, kWasmI32, kWasmI32>, &WasmGenerator::op<kExprI32GeU, kWasmI32, kWasmI32>, &WasmGenerator::op<kExprI64Eqz, kWasmI64>, &WasmGenerator::op<kExprI64Eq, kWasmI64, kWasmI64>, &WasmGenerator::op<kExprI64Ne, kWasmI64, kWasmI64>, &WasmGenerator::op<kExprI64LtS, kWasmI64, kWasmI64>, &WasmGenerator::op<kExprI64LtU, kWasmI64, kWasmI64>, &WasmGenerator::op<kExprI64GeS, kWasmI64, kWasmI64>, &WasmGenerator::op<kExprI64GeU, kWasmI64, kWasmI64>, &WasmGenerator::op<kExprF32Eq, kWasmF32, kWasmF32>, &WasmGenerator::op<kExprF32Ne, kWasmF32, kWasmF32>, &WasmGenerator::op<kExprF32Lt, kWasmF32, kWasmF32>, &WasmGenerator::op<kExprF32Ge, kWasmF32, kWasmF32>, &WasmGenerator::op<kExprF64Eq, kWasmF64, kWasmF64>, &WasmGenerator::op<kExprF64Ne, kWasmF64, kWasmF64>, &WasmGenerator::op<kExprF64Lt, kWasmF64, kWasmF64>, &WasmGenerator::op<kExprF64Ge, kWasmF64, kWasmF64>, &WasmGenerator::op<kExprI32Add, kWasmI32, kWasmI32>, &WasmGenerator::op<kExprI32Sub, kWasmI32, kWasmI32>, &WasmGenerator::op<kExprI32Mul, kWasmI32, kWasmI32>, &WasmGenerator::op<kExprI32DivS, kWasmI32, kWasmI32>, &WasmGenerator::op<kExprI32DivU, kWasmI32, kWasmI32>, &WasmGenerator::op<kExprI32RemS, kWasmI32, kWasmI32>, &WasmGenerator::op<kExprI32RemU, kWasmI32, kWasmI32>, &WasmGenerator::op<kExprI32And, kWasmI32, kWasmI32>, &WasmGenerator::op<kExprI32Ior, kWasmI32, kWasmI32>, &WasmGenerator::op<kExprI32Xor, kWasmI32, kWasmI32>, &WasmGenerator::op<kExprI32Shl, kWasmI32, kWasmI32>, &WasmGenerator::op<kExprI32ShrU, kWasmI32, kWasmI32>, &WasmGenerator::op<kExprI32ShrS, kWasmI32, kWasmI32>, &WasmGenerator::op<kExprI32Ror, kWasmI32, kWasmI32>, &WasmGenerator::op<kExprI32Rol, kWasmI32, kWasmI32>, &WasmGenerator::op<kExprI32Clz, kWasmI32>, &WasmGenerator::op<kExprI32Ctz, kWasmI32>, &WasmGenerator::op<kExprI32Popcnt, kWasmI32>, &WasmGenerator::op<kExprI32ConvertI64, kWasmI64>, &WasmGenerator::op<kExprI32SConvertF32, kWasmF32>, &WasmGenerator::op<kExprI32UConvertF32, kWasmF32>, &WasmGenerator::op<kExprI32SConvertF64, kWasmF64>, &WasmGenerator::op<kExprI32UConvertF64, kWasmF64>, &WasmGenerator::op<kExprI32ReinterpretF32, kWasmF32>, &WasmGenerator::block<kWasmI32>, &WasmGenerator::loop<kWasmI32>, &WasmGenerator::if_<kWasmI32, kIfElse>, &WasmGenerator::br_if<kWasmI32>, &WasmGenerator::memop<kExprI32LoadMem>, &WasmGenerator::memop<kExprI32LoadMem8S>, &WasmGenerator::memop<kExprI32LoadMem8U>, &WasmGenerator::memop<kExprI32LoadMem16S>, &WasmGenerator::memop<kExprI32LoadMem16U>, &WasmGenerator::current_memory, &WasmGenerator::grow_memory, &WasmGenerator::get_local<kWasmI32>, &WasmGenerator::tee_local<kWasmI32>, &WasmGenerator::get_global<kWasmI32>, &WasmGenerator::call<kWasmI32>}; GenerateOneOf(alternates, data); } template <> void WasmGenerator::Generate<kWasmI64>(DataRange& data) { GeneratorRecursionScope rec_scope(this); if (recursion_limit_reached() || data.size() <= 1) { builder_->EmitI64Const(data.get<int64_t>()); return; } constexpr generate_fn alternates[] = { &WasmGenerator::i64_const<1>, &WasmGenerator::i64_const<2>, &WasmGenerator::i64_const<3>, &WasmGenerator::i64_const<4>, &WasmGenerator::i64_const<5>, &WasmGenerator::i64_const<6>, &WasmGenerator::i64_const<7>, &WasmGenerator::i64_const<8>, &WasmGenerator::sequence<kWasmI64, kWasmStmt>, &WasmGenerator::sequence<kWasmStmt, kWasmI64>, &WasmGenerator::sequence<kWasmStmt, kWasmI64, kWasmStmt>, &WasmGenerator::op<kExprI64Add, kWasmI64, kWasmI64>, &WasmGenerator::op<kExprI64Sub, kWasmI64, kWasmI64>, &WasmGenerator::op<kExprI64Mul, kWasmI64, kWasmI64>, &WasmGenerator::op<kExprI64DivS, kWasmI64, kWasmI64>, &WasmGenerator::op<kExprI64DivU, kWasmI64, kWasmI64>, &WasmGenerator::op<kExprI64RemS, kWasmI64, kWasmI64>, &WasmGenerator::op<kExprI64RemU, kWasmI64, kWasmI64>, &WasmGenerator::op<kExprI64And, kWasmI64, kWasmI64>, &WasmGenerator::op<kExprI64Ior, kWasmI64, kWasmI64>, &WasmGenerator::op<kExprI64Xor, kWasmI64, kWasmI64>, &WasmGenerator::op<kExprI64Shl, kWasmI64, kWasmI64>, &WasmGenerator::op<kExprI64ShrU, kWasmI64, kWasmI64>, &WasmGenerator::op<kExprI64ShrS, kWasmI64, kWasmI64>, &WasmGenerator::op<kExprI64Ror, kWasmI64, kWasmI64>, &WasmGenerator::op<kExprI64Rol, kWasmI64, kWasmI64>, &WasmGenerator::op<kExprI64Clz, kWasmI64>, &WasmGenerator::op<kExprI64Ctz, kWasmI64>, &WasmGenerator::op<kExprI64Popcnt, kWasmI64>, &WasmGenerator::block<kWasmI64>, &WasmGenerator::loop<kWasmI64>, &WasmGenerator::if_<kWasmI64, kIfElse>, &WasmGenerator::br_if<kWasmI64>, &WasmGenerator::memop<kExprI64LoadMem>, &WasmGenerator::memop<kExprI64LoadMem8S>, &WasmGenerator::memop<kExprI64LoadMem8U>, &WasmGenerator::memop<kExprI64LoadMem16S>, &WasmGenerator::memop<kExprI64LoadMem16U>, &WasmGenerator::memop<kExprI64LoadMem32S>, &WasmGenerator::memop<kExprI64LoadMem32U>, &WasmGenerator::get_local<kWasmI64>, &WasmGenerator::tee_local<kWasmI64>, &WasmGenerator::get_global<kWasmI64>, &WasmGenerator::call<kWasmI64>}; GenerateOneOf(alternates, data); } template <> void WasmGenerator::Generate<kWasmF32>(DataRange& data) { GeneratorRecursionScope rec_scope(this); if (recursion_limit_reached() || data.size() <= sizeof(float)) { builder_->EmitF32Const(data.get<float>()); return; } constexpr generate_fn alternates[] = { &WasmGenerator::sequence<kWasmF32, kWasmStmt>, &WasmGenerator::sequence<kWasmStmt, kWasmF32>, &WasmGenerator::sequence<kWasmStmt, kWasmF32, kWasmStmt>, &WasmGenerator::op<kExprF32Add, kWasmF32, kWasmF32>, &WasmGenerator::op<kExprF32Sub, kWasmF32, kWasmF32>, &WasmGenerator::op<kExprF32Mul, kWasmF32, kWasmF32>, &WasmGenerator::block<kWasmF32>, &WasmGenerator::loop<kWasmF32>, &WasmGenerator::if_<kWasmF32, kIfElse>, &WasmGenerator::br_if<kWasmF32>, &WasmGenerator::memop<kExprF32LoadMem>, &WasmGenerator::get_local<kWasmF32>, &WasmGenerator::tee_local<kWasmF32>, &WasmGenerator::get_global<kWasmF32>, &WasmGenerator::call<kWasmF32>}; GenerateOneOf(alternates, data); } template <> void WasmGenerator::Generate<kWasmF64>(DataRange& data) { GeneratorRecursionScope rec_scope(this); if (recursion_limit_reached() || data.size() <= sizeof(double)) { builder_->EmitF64Const(data.get<double>()); return; } constexpr generate_fn alternates[] = { &WasmGenerator::sequence<kWasmF64, kWasmStmt>, &WasmGenerator::sequence<kWasmStmt, kWasmF64>, &WasmGenerator::sequence<kWasmStmt, kWasmF64, kWasmStmt>, &WasmGenerator::op<kExprF64Add, kWasmF64, kWasmF64>, &WasmGenerator::op<kExprF64Sub, kWasmF64, kWasmF64>, &WasmGenerator::op<kExprF64Mul, kWasmF64, kWasmF64>, &WasmGenerator::block<kWasmF64>, &WasmGenerator::loop<kWasmF64>, &WasmGenerator::if_<kWasmF64, kIfElse>, &WasmGenerator::br_if<kWasmF64>, &WasmGenerator::memop<kExprF64LoadMem>, &WasmGenerator::get_local<kWasmF64>, &WasmGenerator::tee_local<kWasmF64>, &WasmGenerator::get_global<kWasmF64>, &WasmGenerator::call<kWasmF64>}; GenerateOneOf(alternates, data); } void WasmGenerator::grow_memory(DataRange& data) { Generate<kWasmI32>(data); builder_->EmitWithU8(kExprMemoryGrow, 0); } void WasmGenerator::Generate(ValueType type, DataRange& data) { switch (type) { case kWasmStmt: return Generate<kWasmStmt>(data); case kWasmI32: return Generate<kWasmI32>(data); case kWasmI64: return Generate<kWasmI64>(data); case kWasmF32: return Generate<kWasmF32>(data); case kWasmF64: return Generate<kWasmF64>(data); default: 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 { bool GenerateModule( Isolate* isolate, Zone* zone, Vector<const uint8_t> data, ZoneBuffer& buffer, int32_t& num_args, std::unique_ptr<WasmValue[]>& interpreter_args, std::unique_ptr<Handle<Object>[]>& compiler_args) override { TestSignatures sigs; WasmModuleBuilder builder(zone); DataRange range(data); 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)); } int num_globals = range.get<uint8_t>() % (kMaxGlobals + 1); std::vector<ValueType> globals; std::vector<uint8_t> mutable_globals; globals.reserve(num_globals); mutable_globals.reserve(num_globals); for (int i = 0; i < num_globals; ++i) { ValueType type = GetValueType(range); const bool exported = range.get<bool>(); // 1/8 of globals are immutable. const bool mutability = (range.get<uint8_t>() % 8) != 0; builder.AddGlobal(type, exported, mutability, WasmInitExpr()); globals.push_back(type); if (mutability) mutable_globals.push_back(static_cast<uint8_t>(i)); } for (int i = 0; i < num_functions; ++i) { DataRange function_range = i == num_functions - 1 ? std::move(range) : range.split(); FunctionSig* sig = function_signatures[i]; WasmFunctionBuilder* f = builder.AddFunction(sig); WasmGenerator gen(f, function_signatures, globals, mutable_globals, 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); num_args = 3; interpreter_args.reset( new WasmValue[3]{WasmValue(1), WasmValue(2), WasmValue(3)}); compiler_args.reset(new Handle<Object>[3]{ handle(Smi::FromInt(1), isolate), handle(Smi::FromInt(2), isolate), handle(Smi::FromInt(3), isolate)}); return true; } }; extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { constexpr bool require_valid = true; WasmCompileFuzzer().FuzzWasmModule({data, size}, require_valid); return 0; } } // namespace fuzzer } // namespace wasm } // namespace internal } // namespace v8