b83c5a06c5
Make WasmFeatures a proper class which uses an EnumSet under the hood. This way, it inherits all behaviour of EnumSet like comparison, merge, etc. Accesses change from being simple field access into the struct to actually bit tests in the EnumSet. R=mstarzinger@chromium.org Bug: v8:10019 Change-Id: I768f92b90ac0294156f4482defba5ce00bc70165 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1934334 Commit-Queue: Clemens Backes <clemensb@chromium.org> Reviewed-by: Michael Starzinger <mstarzinger@chromium.org> Cr-Commit-Position: refs/heads/master@{#65184}
392 lines
13 KiB
C++
392 lines
13 KiB
C++
// Copyright 2016 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 "test/fuzzer/wasm-fuzzer-common.h"
|
|
|
|
#include <ctime>
|
|
|
|
#include "include/v8.h"
|
|
#include "src/execution/isolate.h"
|
|
#include "src/objects/objects-inl.h"
|
|
#include "src/utils/ostreams.h"
|
|
#include "src/wasm/wasm-engine.h"
|
|
#include "src/wasm/wasm-feature-flags.h"
|
|
#include "src/wasm/wasm-module-builder.h"
|
|
#include "src/wasm/wasm-module.h"
|
|
#include "src/wasm/wasm-objects-inl.h"
|
|
#include "src/zone/accounting-allocator.h"
|
|
#include "src/zone/zone.h"
|
|
#include "test/common/wasm/flag-utils.h"
|
|
#include "test/common/wasm/wasm-module-runner.h"
|
|
#include "test/fuzzer/fuzzer-support.h"
|
|
|
|
namespace v8 {
|
|
namespace internal {
|
|
namespace wasm {
|
|
namespace fuzzer {
|
|
|
|
void InterpretAndExecuteModule(i::Isolate* isolate,
|
|
Handle<WasmModuleObject> module_object) {
|
|
// We do not instantiate the module if there is a start function, because a
|
|
// start function can contain an infinite loop which we cannot handle.
|
|
if (module_object->module()->start_function_index >= 0) return;
|
|
|
|
ErrorThrower thrower(isolate, "WebAssembly Instantiation");
|
|
MaybeHandle<WasmInstanceObject> maybe_instance;
|
|
Handle<WasmInstanceObject> instance;
|
|
|
|
// Try to instantiate and interpret the module_object.
|
|
maybe_instance = isolate->wasm_engine()->SyncInstantiate(
|
|
isolate, &thrower, module_object,
|
|
Handle<JSReceiver>::null(), // imports
|
|
MaybeHandle<JSArrayBuffer>()); // memory
|
|
if (!maybe_instance.ToHandle(&instance)) {
|
|
isolate->clear_pending_exception();
|
|
thrower.Reset(); // Ignore errors.
|
|
return;
|
|
}
|
|
if (!testing::InterpretWasmModuleForTesting(isolate, instance, "main", 0,
|
|
nullptr)) {
|
|
isolate->clear_pending_exception();
|
|
return;
|
|
}
|
|
|
|
// Try to instantiate and execute the module_object.
|
|
maybe_instance = isolate->wasm_engine()->SyncInstantiate(
|
|
isolate, &thrower, module_object,
|
|
Handle<JSReceiver>::null(), // imports
|
|
MaybeHandle<JSArrayBuffer>()); // memory
|
|
if (!maybe_instance.ToHandle(&instance)) {
|
|
isolate->clear_pending_exception();
|
|
thrower.Reset(); // Ignore errors.
|
|
return;
|
|
}
|
|
if (testing::RunWasmModuleForTesting(isolate, instance, 0, nullptr) < 0) {
|
|
isolate->clear_pending_exception();
|
|
return;
|
|
}
|
|
}
|
|
|
|
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";
|
|
case kWasmAnyRef:
|
|
return "kWasmAnyRef";
|
|
case kWasmFuncRef:
|
|
return "kWasmFuncRef";
|
|
case kWasmExnRef:
|
|
return "kWasmExnRef";
|
|
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 << "]";
|
|
}
|
|
|
|
struct PrintName {
|
|
WasmName name;
|
|
PrintName(ModuleWireBytes wire_bytes, WireBytesRef ref)
|
|
: name(wire_bytes.GetNameOrNull(ref)) {}
|
|
};
|
|
std::ostream& operator<<(std::ostream& os, const PrintName& name) {
|
|
return os.write(name.name.begin(), name.name.size());
|
|
}
|
|
} // namespace
|
|
|
|
void GenerateTestCase(Isolate* isolate, ModuleWireBytes wire_bytes,
|
|
bool compiles) {
|
|
constexpr bool kVerifyFunctions = false;
|
|
auto enabled_features = i::wasm::WasmFeatures::FromIsolate(isolate);
|
|
ModuleResult module_res = DecodeWasmModule(
|
|
enabled_features, wire_bytes.start(), wire_bytes.end(), kVerifyFunctions,
|
|
ModuleOrigin::kWasmOrigin, isolate->counters(),
|
|
isolate->wasm_engine()->allocator());
|
|
CHECK(module_res.ok());
|
|
WasmModule* module = module_res.value().get();
|
|
CHECK_NOT_NULL(module);
|
|
|
|
StdoutStream os;
|
|
|
|
tzset();
|
|
time_t current_time = time(nullptr);
|
|
struct tm current_localtime;
|
|
#ifdef V8_OS_WIN
|
|
localtime_s(¤t_localtime, ¤t_time);
|
|
#else
|
|
localtime_r(¤t_time, ¤t_localtime);
|
|
#endif
|
|
int year = 1900 + current_localtime.tm_year;
|
|
|
|
os << "// Copyright " << year
|
|
<< " 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"
|
|
"// Flags: --wasm-staging\n"
|
|
"\n"
|
|
"load('test/mjsunit/wasm/wasm-module-builder.js');\n"
|
|
"\n"
|
|
"(function() {\n"
|
|
" const builder = new WasmModuleBuilder();\n";
|
|
|
|
if (module->has_memory) {
|
|
os << " builder.addMemory(" << module->initial_pages;
|
|
if (module->has_maximum_pages) {
|
|
os << ", " << module->maximum_pages;
|
|
} else {
|
|
os << ", undefined";
|
|
}
|
|
os << ", " << (module->mem_export ? "true" : "false");
|
|
if (module->has_shared_memory) {
|
|
os << ", true";
|
|
}
|
|
os << ");\n";
|
|
}
|
|
|
|
for (WasmGlobal& glob : module->globals) {
|
|
os << " builder.addGlobal(" << ValueTypeToConstantName(glob.type) << ", "
|
|
<< glob.mutability << ");\n";
|
|
}
|
|
|
|
for (const FunctionSig* sig : module->signatures) {
|
|
os << " builder.addType(makeSig(" << PrintParameters(sig) << ", "
|
|
<< PrintReturns(sig) << "));\n";
|
|
}
|
|
|
|
Zone tmp_zone(isolate->allocator(), ZONE_NAME);
|
|
|
|
// There currently cannot be more than one table.
|
|
DCHECK_GE(1, module->tables.size());
|
|
for (const WasmTable& table : module->tables) {
|
|
os << " builder.setTableBounds(" << table.initial_size << ", ";
|
|
if (table.has_maximum_size) {
|
|
os << table.maximum_size << ");\n";
|
|
} else {
|
|
os << "undefined);\n";
|
|
}
|
|
}
|
|
for (const WasmElemSegment& elem_segment : module->elem_segments) {
|
|
os << " builder.addElementSegment(";
|
|
switch (elem_segment.offset.kind) {
|
|
case WasmInitExpr::kGlobalIndex:
|
|
os << elem_segment.offset.val.global_index << ", true";
|
|
break;
|
|
case WasmInitExpr::kI32Const:
|
|
os << elem_segment.offset.val.i32_const << ", false";
|
|
break;
|
|
default:
|
|
UNREACHABLE();
|
|
}
|
|
os << ", " << PrintCollection(elem_segment.entries) << ");\n";
|
|
}
|
|
|
|
for (const WasmFunction& func : module->functions) {
|
|
Vector<const uint8_t> func_code = wire_bytes.GetFunctionBytes(&func);
|
|
os << " // Generate function " << (func.func_index + 1) << " (out of "
|
|
<< module->functions.size() << ").\n";
|
|
|
|
// Add function.
|
|
os << " builder.addFunction(undefined, " << func.sig_index
|
|
<< " /* sig */)\n";
|
|
|
|
// Add locals.
|
|
BodyLocalDecls decls(&tmp_zone);
|
|
DecodeLocalDecls(enabled_features, &decls, func_code.begin(),
|
|
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({" << ValueTypes::TypeName(type)
|
|
<< "_count: " << count << "})";
|
|
}
|
|
os << "\n";
|
|
}
|
|
|
|
// Add body.
|
|
os << " .addBodyWithEnd([\n";
|
|
|
|
FunctionBody func_body(func.sig, func.code.offset(), func_code.begin(),
|
|
func_code.end());
|
|
PrintRawWasmCode(isolate->allocator(), func_body, module, kOmitLocals);
|
|
os << " ]);\n";
|
|
}
|
|
|
|
for (WasmExport& exp : module->export_table) {
|
|
if (exp.kind != kExternalFunction) continue;
|
|
os << " builder.addExport('" << PrintName(wire_bytes, exp.name) << "', "
|
|
<< exp.index << ");\n";
|
|
}
|
|
|
|
if (compiles) {
|
|
os << " const instance = builder.instantiate();\n"
|
|
" print(instance.exports.main(1, 2, 3));\n";
|
|
} else {
|
|
os << " assertThrows(function() { builder.instantiate(); }, "
|
|
"WebAssembly.CompileError);\n";
|
|
}
|
|
os << "})();\n";
|
|
}
|
|
|
|
void WasmExecutionFuzzer::FuzzWasmModule(Vector<const uint8_t> data,
|
|
bool require_valid) {
|
|
// We explicitly enable staged WebAssembly features here to increase fuzzer
|
|
// coverage. For libfuzzer fuzzers it is not possible that the fuzzer enables
|
|
// the flag by itself.
|
|
#define ENABLE_STAGED_FEATURES(feat, desc, val) \
|
|
FlagScope<bool> enable_##feat(&FLAG_experimental_wasm_##feat, true);
|
|
FOREACH_WASM_STAGING_FEATURE_FLAG(ENABLE_STAGED_FEATURES)
|
|
#undef ENABLE_STAGED_FEATURES
|
|
|
|
// Strictly enforce the input size limit. Note that setting "max_len" on the
|
|
// fuzzer target is not enough, since different fuzzers are used and not all
|
|
// respect that limit.
|
|
if (data.size() > max_input_size()) return;
|
|
|
|
v8_fuzzer::FuzzerSupport* support = v8_fuzzer::FuzzerSupport::Get();
|
|
v8::Isolate* isolate = support->GetIsolate();
|
|
i::Isolate* i_isolate = reinterpret_cast<Isolate*>(isolate);
|
|
|
|
// Clear any pending exceptions from a prior run.
|
|
i_isolate->clear_pending_exception();
|
|
|
|
v8::Isolate::Scope isolate_scope(isolate);
|
|
v8::HandleScope handle_scope(isolate);
|
|
v8::Context::Scope context_scope(support->GetContext());
|
|
v8::TryCatch try_catch(isolate);
|
|
HandleScope scope(i_isolate);
|
|
|
|
AccountingAllocator allocator;
|
|
Zone zone(&allocator, ZONE_NAME);
|
|
|
|
ZoneBuffer buffer(&zone);
|
|
int32_t num_args = 0;
|
|
std::unique_ptr<WasmValue[]> interpreter_args;
|
|
std::unique_ptr<Handle<Object>[]> compiler_args;
|
|
// The first byte builds the bitmask to control which function will be
|
|
// compiled with Turbofan and which one with Liftoff.
|
|
uint8_t tier_mask = data.empty() ? 0 : data[0];
|
|
if (!data.empty()) data += 1;
|
|
if (!GenerateModule(i_isolate, &zone, data, &buffer, &num_args,
|
|
&interpreter_args, &compiler_args)) {
|
|
return;
|
|
}
|
|
|
|
testing::SetupIsolateForWasmModule(i_isolate);
|
|
|
|
ErrorThrower interpreter_thrower(i_isolate, "Interpreter");
|
|
ModuleWireBytes wire_bytes(buffer.begin(), buffer.end());
|
|
|
|
// Compile with Turbofan here. Liftoff will be tested later.
|
|
auto enabled_features = i::wasm::WasmFeatures::FromIsolate(i_isolate);
|
|
MaybeHandle<WasmModuleObject> compiled_module;
|
|
{
|
|
// Explicitly enable Liftoff, disable tiering and set the tier_mask. This
|
|
// way, we deterministically test a combination of Liftoff and Turbofan.
|
|
FlagScope<bool> liftoff(&FLAG_liftoff, true);
|
|
FlagScope<bool> no_tier_up(&FLAG_wasm_tier_up, false);
|
|
FlagScope<int> tier_mask_scope(&FLAG_wasm_tier_mask_for_testing, tier_mask);
|
|
compiled_module = i_isolate->wasm_engine()->SyncCompile(
|
|
i_isolate, enabled_features, &interpreter_thrower, wire_bytes);
|
|
}
|
|
bool compiles = !compiled_module.is_null();
|
|
|
|
if (FLAG_wasm_fuzzer_gen_test) {
|
|
GenerateTestCase(i_isolate, wire_bytes, compiles);
|
|
}
|
|
|
|
bool validates = i_isolate->wasm_engine()->SyncValidate(
|
|
i_isolate, enabled_features, wire_bytes);
|
|
|
|
CHECK_EQ(compiles, validates);
|
|
CHECK_IMPLIES(require_valid, validates);
|
|
|
|
if (!compiles) return;
|
|
|
|
MaybeHandle<WasmInstanceObject> interpreter_instance =
|
|
i_isolate->wasm_engine()->SyncInstantiate(
|
|
i_isolate, &interpreter_thrower, compiled_module.ToHandleChecked(),
|
|
MaybeHandle<JSReceiver>(), MaybeHandle<JSArrayBuffer>());
|
|
|
|
// Ignore instantiation failure.
|
|
if (interpreter_thrower.error()) return;
|
|
|
|
testing::WasmInterpretationResult interpreter_result =
|
|
testing::InterpretWasmModule(i_isolate,
|
|
interpreter_instance.ToHandleChecked(), 0,
|
|
interpreter_args.get());
|
|
|
|
// Do not execute the generated code if the interpreter did not finished after
|
|
// a bounded number of steps.
|
|
if (interpreter_result.stopped()) return;
|
|
|
|
// The WebAssembly spec allows the sign bit of NaN to be non-deterministic.
|
|
// This sign bit can make the difference between an infinite loop and
|
|
// terminating code. With possible non-determinism we cannot guarantee that
|
|
// the generated code will not go into an infinite loop and cause a timeout in
|
|
// Clusterfuzz. Therefore we do not execute the generated code if the result
|
|
// may be non-deterministic.
|
|
if (interpreter_result.possible_nondeterminism()) return;
|
|
|
|
int32_t result_compiled;
|
|
{
|
|
ErrorThrower compiler_thrower(i_isolate, "Compile");
|
|
MaybeHandle<WasmInstanceObject> compiled_instance =
|
|
i_isolate->wasm_engine()->SyncInstantiate(
|
|
i_isolate, &compiler_thrower, compiled_module.ToHandleChecked(),
|
|
MaybeHandle<JSReceiver>(), MaybeHandle<JSArrayBuffer>());
|
|
|
|
DCHECK(!compiler_thrower.error());
|
|
result_compiled = testing::CallWasmFunctionForTesting(
|
|
i_isolate, compiled_instance.ToHandleChecked(), &compiler_thrower,
|
|
"main", num_args, compiler_args.get());
|
|
}
|
|
|
|
if (interpreter_result.trapped() != i_isolate->has_pending_exception()) {
|
|
const char* exception_text[] = {"no exception", "exception"};
|
|
FATAL("interpreter: %s; compiled: %s",
|
|
exception_text[interpreter_result.trapped()],
|
|
exception_text[i_isolate->has_pending_exception()]);
|
|
}
|
|
|
|
if (!interpreter_result.trapped()) {
|
|
CHECK_EQ(interpreter_result.result(), result_compiled);
|
|
}
|
|
|
|
// Cleanup any pending exception.
|
|
i_isolate->clear_pending_exception();
|
|
}
|
|
|
|
} // namespace fuzzer
|
|
} // namespace wasm
|
|
} // namespace internal
|
|
} // namespace v8
|