diff options
Diffstat (limited to 'deps/v8/test/fuzzer')
-rw-r--r-- | deps/v8/test/fuzzer/fuzzer-support.cc | 9 | ||||
-rw-r--r-- | deps/v8/test/fuzzer/fuzzer.gyp | 30 | ||||
-rw-r--r-- | deps/v8/test/fuzzer/fuzzer.isolate | 2 | ||||
-rw-r--r-- | deps/v8/test/fuzzer/multi-return.cc | 346 | ||||
-rw-r--r-- | deps/v8/test/fuzzer/multi_return/README.md | 4 | ||||
-rw-r--r-- | deps/v8/test/fuzzer/regexp.cc | 2 | ||||
-rw-r--r-- | deps/v8/test/fuzzer/testcfg.py | 64 | ||||
-rw-r--r-- | deps/v8/test/fuzzer/wasm-async.cc | 2 | ||||
-rw-r--r-- | deps/v8/test/fuzzer/wasm-compile.cc | 335 | ||||
-rw-r--r-- | deps/v8/test/fuzzer/wasm-fuzzer-common.cc | 159 |
10 files changed, 819 insertions, 134 deletions
diff --git a/deps/v8/test/fuzzer/fuzzer-support.cc b/deps/v8/test/fuzzer/fuzzer-support.cc index beda4899c1..d6cff118bf 100644 --- a/deps/v8/test/fuzzer/fuzzer-support.cc +++ b/deps/v8/test/fuzzer/fuzzer-support.cc @@ -89,7 +89,14 @@ bool FuzzerSupport::PumpMessageLoop( } // namespace v8_fuzzer -extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv) { +// Explicitly specify some attributes to avoid issues with the linker dead- +// stripping the following function on macOS, as it is not called directly +// by fuzz target. LibFuzzer runtime uses dlsym() to resolve that function. +#if V8_OS_MACOSX +__attribute__((used)) __attribute__((visibility("default"))) +#endif // V8_OS_MACOSX +extern "C" int +LLVMFuzzerInitialize(int* argc, char*** argv) { v8_fuzzer::FuzzerSupport::InitializeFuzzerSupport(argc, argv); return 0; } diff --git a/deps/v8/test/fuzzer/fuzzer.gyp b/deps/v8/test/fuzzer/fuzzer.gyp index 3d76018d55..0c54211290 100644 --- a/deps/v8/test/fuzzer/fuzzer.gyp +++ b/deps/v8/test/fuzzer/fuzzer.gyp @@ -90,6 +90,36 @@ ], }, { + 'target_name': 'v8_simple_multi_return_fuzzer', + 'type': 'executable', + 'dependencies': [ + 'multi_return_fuzzer_lib', + ], + 'include_dirs': [ + '../..', + ], + 'sources': [ + 'fuzzer.cc', + ], + }, + { + 'target_name': 'multi_return_fuzzer_lib', + 'type': 'static_library', + 'dependencies': [ + '../../src/v8.gyp:v8_libplatform', + 'fuzzer_support', + ], + 'include_dirs': [ + '../..', + ], + 'sources': [ ### gcmole(all) ### + '../compiler/c-signature.h', + '../compiler/call-helper.h', + '../compiler/raw-machine-assembler-tester.h', + 'multi-return.cc', + ], + }, + { 'target_name': 'v8_simple_wasm_fuzzer', 'type': 'executable', 'dependencies': [ diff --git a/deps/v8/test/fuzzer/fuzzer.isolate b/deps/v8/test/fuzzer/fuzzer.isolate index 097d55885d..9391dcc7c0 100644 --- a/deps/v8/test/fuzzer/fuzzer.isolate +++ b/deps/v8/test/fuzzer/fuzzer.isolate @@ -8,6 +8,7 @@ '<(PRODUCT_DIR)/v8_simple_json_fuzzer<(EXECUTABLE_SUFFIX)', '<(PRODUCT_DIR)/v8_simple_parser_fuzzer<(EXECUTABLE_SUFFIX)', '<(PRODUCT_DIR)/v8_simple_regexp_fuzzer<(EXECUTABLE_SUFFIX)', + '<(PRODUCT_DIR)/v8_simple_multi_return_fuzzer<(EXECUTABLE_SUFFIX)', '<(PRODUCT_DIR)/v8_simple_wasm_fuzzer<(EXECUTABLE_SUFFIX)', '<(PRODUCT_DIR)/v8_simple_wasm_async_fuzzer<(EXECUTABLE_SUFFIX)', '<(PRODUCT_DIR)/v8_simple_wasm_call_fuzzer<(EXECUTABLE_SUFFIX)', @@ -25,6 +26,7 @@ './json/', './parser/', './regexp/', + './multi_return/', './wasm/', './wasm_async/', './wasm_call/', diff --git a/deps/v8/test/fuzzer/multi-return.cc b/deps/v8/test/fuzzer/multi-return.cc new file mode 100644 index 0000000000..4766774005 --- /dev/null +++ b/deps/v8/test/fuzzer/multi-return.cc @@ -0,0 +1,346 @@ +// Copyright 2018 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 <cstddef> +#include <cstdint> + +#include "src/compilation-info.h" +#include "src/compiler/graph.h" +#include "src/compiler/instruction-selector.h" +#include "src/compiler/linkage.h" +#include "src/compiler/node.h" +#include "src/compiler/operator.h" +#include "src/compiler/pipeline.h" +#include "src/compiler/raw-machine-assembler.h" +#include "src/machine-type.h" +#include "src/objects-inl.h" +#include "src/objects.h" +#include "src/simulator.h" +#include "src/zone/accounting-allocator.h" +#include "src/zone/zone.h" +#include "test/fuzzer/fuzzer-support.h" + +namespace v8 { +namespace internal { +namespace compiler { +namespace fuzzer { + +constexpr MachineType kTypes[] = { + // The first entry is just a placeholder, because '0' is a separator. + MachineType(), +#if !V8_TARGET_ARCH_32_BIT + MachineType::Int64(), +#endif + MachineType::Int32(), MachineType::Float32(), MachineType::Float64()}; + +static constexpr int kNumTypes = arraysize(kTypes); + +class InputProvider { + public: + InputProvider(const uint8_t* data, size_t size) + : current_(data), end_(data + size) {} + + size_t NumNonZeroBytes(size_t offset, int limit) { + DCHECK_LE(limit, std::numeric_limits<uint8_t>::max()); + DCHECK_GE(current_ + offset, current_); + const uint8_t* p; + for (p = current_ + offset; p < end_; ++p) { + if (*p % limit == 0) break; + } + return p - current_ - offset; + } + + int NextInt8(int limit) { + DCHECK_LE(limit, std::numeric_limits<uint8_t>::max()); + if (current_ == end_) return 0; + uint8_t result = *current_; + current_++; + return static_cast<int>(result) % limit; + } + + int NextInt32(int limit) { + if (current_ + sizeof(uint32_t) > end_) return 0; + int result = ReadLittleEndianValue<int>(current_); + current_ += sizeof(uint32_t); + return result % limit; + } + + private: + const uint8_t* current_; + const uint8_t* end_; +}; + +MachineType RandomType(InputProvider* input) { + return kTypes[input->NextInt8(kNumTypes)]; +} + +int num_registers(MachineType type) { + const RegisterConfiguration* config = RegisterConfiguration::Default(); + switch (type.representation()) { + case MachineRepresentation::kWord32: + case MachineRepresentation::kWord64: + return config->num_allocatable_general_registers(); + case MachineRepresentation::kFloat32: + return config->num_allocatable_float_registers(); + case MachineRepresentation::kFloat64: + return config->num_allocatable_double_registers(); + default: + UNREACHABLE(); + } +} + +int size(MachineType type) { + return 1 << ElementSizeLog2Of(type.representation()); +} + +int index(MachineType type) { return static_cast<int>(type.representation()); } + +const int* codes(MachineType type) { + const RegisterConfiguration* config = RegisterConfiguration::Default(); + switch (type.representation()) { + case MachineRepresentation::kWord32: + case MachineRepresentation::kWord64: + return config->allocatable_general_codes(); + case MachineRepresentation::kFloat32: + return config->allocatable_float_codes(); + case MachineRepresentation::kFloat64: + return config->allocatable_double_codes(); + default: + UNREACHABLE(); + } +} + +LinkageLocation AllocateLocation(MachineType type, int* int_count, + int* float_count, int* stack_slots) { + int* count = IsFloatingPoint(type.representation()) ? float_count : int_count; + int reg_code = *count; +#if V8_TARGET_ARCH_ARM + // Allocate floats using a double register, but modify the code to + // reflect how ARM FP registers alias. + if (type == MachineType::Float32()) { + reg_code *= 2; + } +#endif + LinkageLocation location = LinkageLocation::ForAnyRegister(); // Dummy. + if (reg_code < num_registers(type)) { + location = LinkageLocation::ForRegister(codes(type)[reg_code], type); + } else { + location = LinkageLocation::ForCallerFrameSlot(-*stack_slots - 1, type); + *stack_slots += std::max(1, size(type) / kPointerSize); + } + ++*count; + return location; +} + +Node* Constant(RawMachineAssembler& m, MachineType type, int value) { + switch (type.representation()) { + case MachineRepresentation::kWord32: + return m.Int32Constant(static_cast<int32_t>(value)); + case MachineRepresentation::kWord64: + return m.Int64Constant(static_cast<int64_t>(value)); + case MachineRepresentation::kFloat32: + return m.Float32Constant(static_cast<float>(value)); + case MachineRepresentation::kFloat64: + return m.Float64Constant(static_cast<double>(value)); + default: + UNREACHABLE(); + } +} + +Node* ToInt32(RawMachineAssembler& m, MachineType type, Node* a) { + switch (type.representation()) { + case MachineRepresentation::kWord32: + return a; + case MachineRepresentation::kWord64: + return m.TruncateInt64ToInt32(a); + case MachineRepresentation::kFloat32: + return m.TruncateFloat32ToInt32(a); + case MachineRepresentation::kFloat64: + return m.RoundFloat64ToInt32(a); + default: + UNREACHABLE(); + } +} + +CallDescriptor* CreateRandomCallDescriptor(Zone* zone, size_t return_count, + size_t param_count, + InputProvider* input) { + LocationSignature::Builder locations(zone, return_count, param_count); + + int stack_slots = 0; + int int_params = 0; + int float_params = 0; + for (size_t i = 0; i < param_count; i++) { + MachineType type = RandomType(input); + LinkageLocation location = + AllocateLocation(type, &int_params, &float_params, &stack_slots); + locations.AddParam(location); + } + // Read the end byte of the parameters. + input->NextInt8(1); + + int stack_params = stack_slots; +#if V8_TARGET_ARCH_ARM64 + // Align the stack slots. + stack_slots = stack_slots + (stack_slots % 2); +#endif + int aligned_stack_params = stack_slots; + int int_returns = 0; + int float_returns = 0; + for (size_t i = 0; i < return_count; i++) { + MachineType type = RandomType(input); + LinkageLocation location = + AllocateLocation(type, &int_returns, &float_returns, &stack_slots); + locations.AddReturn(location); + } + int stack_returns = stack_slots - aligned_stack_params; + + MachineType target_type = MachineType::AnyTagged(); + LinkageLocation target_loc = LinkageLocation::ForAnyRegister(target_type); + return new (zone) CallDescriptor( // -- + CallDescriptor::kCallCodeObject, // kind + target_type, // target MachineType + target_loc, // target location + locations.Build(), // location_sig + stack_params, // on-stack parameter count + compiler::Operator::kNoProperties, // properties + 0, // callee-saved registers + 0, // callee-saved fp regs + CallDescriptor::kNoFlags, // flags + "c-call", // debug name + 0, // allocatable registers + stack_returns); // on-stack return count +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + v8_fuzzer::FuzzerSupport* support = v8_fuzzer::FuzzerSupport::Get(); + v8::Isolate* isolate = support->GetIsolate(); + i::Isolate* i_isolate = reinterpret_cast<Isolate*>(isolate); + v8::Isolate::Scope isolate_scope(isolate); + v8::HandleScope handle_scope(isolate); + v8::Context::Scope context_scope(support->GetContext()); + v8::TryCatch try_catch(isolate); + v8::internal::AccountingAllocator allocator; + Zone zone(&allocator, ZONE_NAME); + + InputProvider input(data, size); + // Create randomized descriptor. + size_t param_count = input.NumNonZeroBytes(0, kNumTypes); + size_t return_count = input.NumNonZeroBytes(param_count + 1, kNumTypes); + CallDescriptor* desc = + CreateRandomCallDescriptor(&zone, return_count, param_count, &input); + + if (FLAG_wasm_fuzzer_gen_test) { + // Print some debugging output which describes the produced signature. + printf("["); + for (size_t j = 0; j < desc->ParameterCount(); ++j) { + printf(" %s", + MachineReprToString(desc->GetParameterType(j).representation())); + } + printf(" ] -> ["); + for (size_t j = 0; j < desc->ReturnCount(); ++j) { + printf(" %s", + MachineReprToString(desc->GetReturnType(j).representation())); + } + printf(" ]\n\n"); + } + + // Count parameters of each type. + constexpr size_t kNumMachineRepresentations = + static_cast<size_t>(MachineRepresentation::kLastRepresentation) + 1; + + // Trivial hash table for the number of occurrences of parameter types. The + // MachineRepresentation of the parameter types is used as hash code. + int counts[kNumMachineRepresentations] = {0}; + for (size_t i = 0; i < desc->ParameterCount(); ++i) { + ++counts[index(desc->GetParameterType(i))]; + } + + // Generate random inputs. + std::unique_ptr<int[]> inputs(new int[desc->ParameterCount()]); + std::unique_ptr<int[]> outputs(new int[desc->ReturnCount()]); + for (size_t i = 0; i < desc->ParameterCount(); ++i) { + inputs[i] = input.NextInt32(10000); + } + + RawMachineAssembler callee( + i_isolate, new (&zone) Graph(&zone), desc, + MachineType::PointerRepresentation(), + InstructionSelector::SupportedMachineOperatorFlags()); + + // Generate callee, returning random picks of its parameters. + std::unique_ptr<Node* []> params(new Node*[desc->ParameterCount() + 1]); + std::unique_ptr<Node* []> returns(new Node*[desc->ReturnCount()]); + for (size_t i = 0; i < desc->ParameterCount(); ++i) { + params[i] = callee.Parameter(i); + } + for (size_t i = 0; i < desc->ReturnCount(); ++i) { + MachineType type = desc->GetReturnType(i); + // Find a random same-type parameter to return. Use a constant if none. + if (counts[index(type)] == 0) { + returns[i] = Constant(callee, type, 42); + outputs[i] = 42; + } else { + int n = input.NextInt8(counts[index(type)]); + int k = 0; + while (desc->GetParameterType(k) != desc->GetReturnType(i) || --n > 0) { + ++k; + } + returns[i] = params[k]; + outputs[i] = inputs[k]; + } + } + callee.Return(static_cast<int>(desc->ReturnCount()), returns.get()); + + CompilationInfo info(ArrayVector("testing"), &zone, Code::STUB); + Handle<Code> code = Pipeline::GenerateCodeForTesting( + &info, i_isolate, desc, callee.graph(), callee.Export()); + + // Generate wrapper. + int expect = 0; + + MachineSignature::Builder sig_builder(&zone, 1, 0); + sig_builder.AddReturn(MachineType::Int32()); + + CallDescriptor* wrapper_desc = + Linkage::GetSimplifiedCDescriptor(&zone, sig_builder.Build()); + RawMachineAssembler caller( + i_isolate, new (&zone) Graph(&zone), wrapper_desc, + MachineType::PointerRepresentation(), + InstructionSelector::SupportedMachineOperatorFlags()); + + params[0] = caller.HeapConstant(code); + for (size_t i = 0; i < desc->ParameterCount(); ++i) { + params[i + 1] = Constant(caller, desc->GetParameterType(i), inputs[i]); + } + Node* call = caller.AddNode(caller.common()->Call(desc), + static_cast<int>(desc->ParameterCount() + 1), + params.get()); + Node* ret = Constant(caller, MachineType::Int32(), 0); + for (size_t i = 0; i < desc->ReturnCount(); ++i) { + // Skip roughly one third of the outputs. + if (input.NextInt8(3) == 0) continue; + Node* ret_i = (desc->ReturnCount() == 1) + ? call + : caller.AddNode(caller.common()->Projection(i), call); + ret = caller.Int32Add(ret, ToInt32(caller, desc->GetReturnType(i), ret_i)); + expect += outputs[i]; + } + caller.Return(ret); + + // Call the wrapper. + CompilationInfo wrapper_info(ArrayVector("wrapper"), &zone, Code::STUB); + Handle<Code> wrapper_code = Pipeline::GenerateCodeForTesting( + &wrapper_info, i_isolate, wrapper_desc, caller.graph(), caller.Export()); + auto fn = GeneratedCode<int32_t>::FromCode(*wrapper_code); + int result = fn.Call(); + + CHECK_EQ(expect, result); + return 0; +} + +} // namespace fuzzer +} // namespace compiler +} // namespace internal +} // namespace v8 diff --git a/deps/v8/test/fuzzer/multi_return/README.md b/deps/v8/test/fuzzer/multi_return/README.md new file mode 100644 index 0000000000..a3764e8a7c --- /dev/null +++ b/deps/v8/test/fuzzer/multi_return/README.md @@ -0,0 +1,4 @@ +All files in this directory are used by the trybots to check that the fuzzer +executes correctly, see +https://github.com/v8/v8/blob/master/test/fuzzer/README.md. There should be at +least one file in this directory, e.g. this README file. diff --git a/deps/v8/test/fuzzer/regexp.cc b/deps/v8/test/fuzzer/regexp.cc index c73901b0e0..b652bd7e3f 100644 --- a/deps/v8/test/fuzzer/regexp.cc +++ b/deps/v8/test/fuzzer/regexp.cc @@ -49,7 +49,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { static const int kAllFlags = i::JSRegExp::kGlobal | i::JSRegExp::kIgnoreCase | i::JSRegExp::kMultiline | i::JSRegExp::kSticky | - i::JSRegExp::kUnicode; + i::JSRegExp::kUnicode | i::JSRegExp::kDotAll; const uint8_t one_byte_array[6] = {'f', 'o', 'o', 'b', 'a', 'r'}; const i::uc16 two_byte_array[6] = {'f', 0xD83D, 0xDCA9, 'b', 'a', 0x2603}; diff --git a/deps/v8/test/fuzzer/testcfg.py b/deps/v8/test/fuzzer/testcfg.py index 17cb0ef588..b3fe174d95 100644 --- a/deps/v8/test/fuzzer/testcfg.py +++ b/deps/v8/test/fuzzer/testcfg.py @@ -8,47 +8,57 @@ from testrunner.local import testsuite from testrunner.objects import testcase -class FuzzerVariantGenerator(testsuite.VariantGenerator): - # Only run the fuzzer with standard variant. - def FilterVariantsByTest(self, testcase): - return self.standard_variant +class VariantsGenerator(testsuite.VariantsGenerator): + def _get_variants(self, test): + return self._standard_variant - def GetFlagSets(self, testcase, variant): - return testsuite.FAST_VARIANT_FLAGS[variant] - -class FuzzerTestSuite(testsuite.TestSuite): - SUB_TESTS = ( 'json', 'parser', 'regexp', 'wasm', 'wasm_async', - 'wasm_call', 'wasm_code', 'wasm_compile', 'wasm_data_section', - 'wasm_function_sigs_section', 'wasm_globals_section', - 'wasm_imports_section', 'wasm_memory_section', 'wasm_names_section', - 'wasm_types_section' ) - - def __init__(self, name, root): - super(FuzzerTestSuite, self).__init__(name, root) +class TestSuite(testsuite.TestSuite): + SUB_TESTS = ( 'json', 'parser', 'regexp', 'multi_return', 'wasm', + 'wasm_async', 'wasm_call', 'wasm_code', 'wasm_compile', + 'wasm_data_section', 'wasm_function_sigs_section', + 'wasm_globals_section', 'wasm_imports_section', 'wasm_memory_section', + 'wasm_names_section', 'wasm_types_section' ) def ListTests(self, context): tests = [] - for subtest in FuzzerTestSuite.SUB_TESTS: + for subtest in TestSuite.SUB_TESTS: for fname in os.listdir(os.path.join(self.root, subtest)): if not os.path.isfile(os.path.join(self.root, subtest, fname)): continue - test = testcase.TestCase(self, '%s/%s' % (subtest, fname)) + test = self._create_test('%s/%s' % (subtest, fname)) tests.append(test) tests.sort() return tests - def GetShellForTestCase(self, testcase): - group, _ = testcase.path.split('/', 1) - return 'v8_simple_%s_fuzzer' % group + def _test_class(self): + return TestCase + + def _variants_gen_class(self): + return VariantsGenerator + + def _LegacyVariantsGeneratorFactory(self): + return testsuite.StandardLegacyVariantsGenerator - def GetParametersForTestCase(self, testcase, context): - suite, name = testcase.path.split('/') - return [os.path.join(self.root, suite, name)], [], {} - def _VariantGeneratorFactory(self): - return FuzzerVariantGenerator +class TestCase(testcase.TestCase): + def _get_files_params(self, ctx): + suite, name = self.path.split('/') + return [os.path.join(self.suite.root, suite, name)] + + def _get_variant_flags(self): + return [] + + def _get_statusfile_flags(self): + return [] + + def _get_mode_flags(self, ctx): + return [] + + def get_shell(self): + group, _ = self.path.split('/', 1) + return 'v8_simple_%s_fuzzer' % group def GetSuite(name, root): - return FuzzerTestSuite(name, root) + return TestSuite(name, root) diff --git a/deps/v8/test/fuzzer/wasm-async.cc b/deps/v8/test/fuzzer/wasm-async.cc index 13b15a9d70..4718601b0f 100644 --- a/deps/v8/test/fuzzer/wasm-async.cc +++ b/deps/v8/test/fuzzer/wasm-async.cc @@ -94,7 +94,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { Local<Promise> promise = resolver->GetPromise(); AsyncCompile(i_isolate, Utils::OpenHandle(*promise), - ModuleWireBytes(data, data + size)); + ModuleWireBytes(data, data + size), false); ASSIGN(Function, instantiate_impl, Function::New(support->GetContext(), &InstantiateCallback, 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; } }; diff --git a/deps/v8/test/fuzzer/wasm-fuzzer-common.cc b/deps/v8/test/fuzzer/wasm-fuzzer-common.cc index 4e6aed1a25..46f5133486 100644 --- a/deps/v8/test/fuzzer/wasm-fuzzer-common.cc +++ b/deps/v8/test/fuzzer/wasm-fuzzer-common.cc @@ -9,6 +9,7 @@ #include "src/objects-inl.h" #include "src/wasm/module-compiler.h" #include "src/wasm/wasm-api.h" +#include "src/wasm/wasm-engine.h" #include "src/wasm/wasm-module-builder.h" #include "src/wasm/wasm-module.h" #include "src/zone/accounting-allocator.h" @@ -89,29 +90,123 @@ void InterpretAndExecuteModule(i::Isolate* isolate, testing::RunWasmModuleForTesting(isolate, instance, 0, nullptr); } +namespace { +struct PrintSig { + const size_t num; + const std::function<ValueType(size_t)> getter; +}; +PrintSig PrintParameters(const FunctionSig* sig) { + return {sig->parameter_count(), [=](size_t i) { return sig->GetParam(i); }}; +} +PrintSig PrintReturns(const FunctionSig* sig) { + return {sig->return_count(), [=](size_t i) { return sig->GetReturn(i); }}; +} +const char* ValueTypeToConstantName(ValueType type) { + switch (type) { + case kWasmI32: + return "kWasmI32"; + case kWasmI64: + return "kWasmI64"; + case kWasmF32: + return "kWasmF32"; + case kWasmF64: + return "kWasmF64"; + default: + UNREACHABLE(); + } +} +std::ostream& operator<<(std::ostream& os, const PrintSig& print) { + os << "["; + for (size_t i = 0; i < print.num; ++i) { + os << (i == 0 ? "" : ", ") << ValueTypeToConstantName(print.getter(i)); + } + return os << "]"; +} + +void GenerateTestCase(Isolate* isolate, ModuleWireBytes wire_bytes, + bool compiles) { + constexpr bool kVerifyFunctions = false; + ModuleResult module_res = + SyncDecodeWasmModule(isolate, wire_bytes.start(), wire_bytes.end(), + kVerifyFunctions, ModuleOrigin::kWasmOrigin); + CHECK(module_res.ok()); + WasmModule* module = module_res.val.get(); + CHECK_NOT_NULL(module); + + OFStream os(stdout); + + os << "// Copyright 2018 the V8 project authors. All rights reserved.\n" + "// Use of this source code is governed by a BSD-style license that " + "can be\n" + "// found in the LICENSE file.\n" + "\n" + "load('test/mjsunit/wasm/wasm-constants.js');\n" + "load('test/mjsunit/wasm/wasm-module-builder.js');\n" + "\n" + "(function() {\n" + " var builder = new WasmModuleBuilder();\n"; + + if (module->has_memory) { + os << " builder.addMemory(" << module->initial_pages; + if (module->has_maximum_pages) { + os << ", " << module->maximum_pages << ");\n"; + } else { + os << ");\n"; + } + } + + Zone tmp_zone(isolate->allocator(), ZONE_NAME); + + for (const WasmFunction& func : module->functions) { + Vector<const uint8_t> func_code = wire_bytes.GetFunctionBytes(&func); + os << " // Generate function " << func.func_index + 1 << " of " + << module->functions.size() << ".\n"; + // Generate signature. + os << " sig" << func.func_index << " = makeSig(" + << PrintParameters(func.sig) << ", " << PrintReturns(func.sig) << ");\n"; + + // Add function. + os << " builder.addFunction(undefined, sig" << func.func_index << ")\n"; + + // Add locals. + BodyLocalDecls decls(&tmp_zone); + DecodeLocalDecls(&decls, func_code.start(), func_code.end()); + if (!decls.type_list.empty()) { + os << " "; + for (size_t pos = 0, count = 1, locals = decls.type_list.size(); + pos < locals; pos += count, count = 1) { + ValueType type = decls.type_list[pos]; + while (pos + count < locals && decls.type_list[pos + count] == type) + ++count; + os << ".addLocals({" << WasmOpcodes::TypeName(type) + << "_count: " << count << "})"; + } + os << "\n"; + } + + // Add body. + os << " .addBodyWithEnd([\n"; + + FunctionBody func_body(func.sig, func.code.offset(), func_code.start(), + func_code.end()); + PrintRawWasmCode(isolate->allocator(), func_body, module, kOmitLocals); + os << " ])"; + if (func.func_index == 0) os << "\n .exportAs('main')"; + os << ";\n "; + } + + if (compiles) { + os << " var module = builder.instantiate();\n" + " module.exports.main(1, 2, 3);\n"; + } else { + os << " assertThrows(function() { builder.instantiate(); });\n"; + } + os << "})();\n"; +} +} // namespace + int WasmExecutionFuzzer::FuzzWasmModule(const uint8_t* data, size_t size, bool require_valid) { - // Save the flag so that we can change it and restore it later. - bool generate_test = FLAG_wasm_code_fuzzer_gen_test; - if (generate_test) { - OFStream os(stdout); - - os << "// Copyright 2017 the V8 project authors. All rights reserved." - << std::endl; - os << "// Use of this source code is governed by a BSD-style license that " - "can be" - << std::endl; - os << "// found in the LICENSE file." << std::endl; - os << std::endl; - os << "load(\"test/mjsunit/wasm/wasm-constants.js\");" << std::endl; - os << "load(\"test/mjsunit/wasm/wasm-module-builder.js\");" << std::endl; - os << std::endl; - os << "(function() {" << std::endl; - os << " var builder = new WasmModuleBuilder();" << std::endl; - os << " builder.addMemory(16, 32, false);" << std::endl; - os << " builder.addFunction(\"test\", kSig_i_iii)" << std::endl; - os << " .addBodyWithEnd([" << std::endl; - } v8_fuzzer::FuzzerSupport* support = v8_fuzzer::FuzzerSupport::Get(); v8::Isolate* isolate = support->GetIsolate(); i::Isolate* i_isolate = reinterpret_cast<Isolate*>(isolate); @@ -148,26 +243,14 @@ int WasmExecutionFuzzer::FuzzWasmModule(const uint8_t* data, size_t size, FlagScope<bool> no_liftoff(&FLAG_liftoff, false); compiled_module = SyncCompile(i_isolate, &interpreter_thrower, wire_bytes); } - // Clear the flag so that the WebAssembly code is not printed twice. - FLAG_wasm_code_fuzzer_gen_test = false; bool compiles = !compiled_module.is_null(); - if (generate_test) { - OFStream os(stdout); - os << " ])" << std::endl - << " .exportFunc();" << std::endl; - if (compiles) { - os << " var module = builder.instantiate();" << std::endl - << " module.exports.test(1, 2, 3);" << std::endl; - } else { - OFStream os(stdout); - os << " assertThrows(function() { builder.instantiate(); });" - << std::endl; - } - os << "})();" << std::endl; + if (FLAG_wasm_fuzzer_gen_test) { + GenerateTestCase(i_isolate, wire_bytes, compiles); } - bool validates = SyncValidate(i_isolate, wire_bytes); + bool validates = + i_isolate->wasm_engine()->SyncValidate(i_isolate, wire_bytes); CHECK_EQ(compiles, validates); CHECK_IMPLIES(require_valid, validates); @@ -198,7 +281,7 @@ int WasmExecutionFuzzer::FuzzWasmModule(const uint8_t* data, size_t size, } bool expect_exception = - result_interpreter == static_cast<int32_t>(0xdeadbeef); + result_interpreter == static_cast<int32_t>(0xDEADBEEF); int32_t result_turbofan; { |