Upload Kmake

This commit is contained in:
Gorochu
2026-05-26 23:36:42 -07:00
parent ba051b2f74
commit 555ec72358
41615 changed files with 13344630 additions and 1 deletions

14
deps/v8/test/fuzzer/wasm/DIR_METADATA vendored Normal file
View File

@ -0,0 +1,14 @@
# Metadata information for this directory.
#
# For more information on DIR_METADATA files, see:
# https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/README.md
#
# For the schema of this file, see Metadata message:
# https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/proto/dir_metadata.proto
monorail {
component: "Blink>JavaScript>WebAssembly"
}
buganizer_public: {
component_id: 1456332
}

100
deps/v8/test/fuzzer/wasm/async.cc vendored Normal file
View File

@ -0,0 +1,100 @@
// 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 <limits.h>
#include <stddef.h>
#include <stdint.h>
#include "include/v8-context.h"
#include "include/v8-exception.h"
#include "include/v8-isolate.h"
#include "include/v8-local-handle.h"
#include "src/execution/isolate-inl.h"
#include "src/wasm/wasm-engine.h"
#include "src/wasm/wasm-module.h"
#include "test/common/wasm/wasm-module-runner.h"
#include "test/fuzzer/fuzzer-support.h"
#include "test/fuzzer/wasm/fuzzer-common.h"
namespace v8::internal {
class WasmModuleObject;
}
namespace v8::internal::wasm::fuzzing {
class AsyncFuzzerResolver : public CompilationResultResolver {
public:
AsyncFuzzerResolver(Isolate* isolate, bool* done)
: isolate_(isolate), done_(done) {}
void OnCompilationSucceeded(DirectHandle<WasmModuleObject> module) override {
*done_ = true;
fuzzing_return_value_ = ExecuteAgainstReference(
isolate_, module, kDefaultMaxFuzzerExecutedInstructions);
}
void OnCompilationFailed(DirectHandle<JSAny> error_reason) override {
*done_ = true;
// Keep {fuzzing_return_value_} at -1; while failed compilation is also
// somewhat interesting, we get this often enough via mutation, so no need
// to add this to the corpus.
}
int fuzzing_return_value() const { return fuzzing_return_value_; }
private:
Isolate* isolate_;
bool* done_;
int fuzzing_return_value_ = -1;
};
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
v8_fuzzer::FuzzerSupport* support = v8_fuzzer::FuzzerSupport::Get();
v8::Isolate* isolate = support->GetIsolate();
// Set some more flags.
v8_flags.wasm_async_compilation = true;
v8_flags.wasm_max_mem_pages = 32;
v8_flags.wasm_max_table_size = 100;
Isolate* i_isolate = reinterpret_cast<Isolate*>(isolate);
v8::Isolate::Scope isolate_scope(isolate);
// Clear any exceptions from a prior run.
if (i_isolate->has_exception()) {
i_isolate->clear_exception();
}
v8::HandleScope handle_scope(isolate);
v8::Context::Scope context_scope(support->GetContext());
// We explicitly enable staged/experimental WebAssembly features here to
// increase fuzzer coverage. For libfuzzer fuzzers it is not possible that the
// fuzzer enables the flag by itself.
EnableExperimentalWasmFeatures(isolate);
TryCatch try_catch(isolate);
testing::SetupIsolateForWasmModule(i_isolate);
bool done = false;
auto enabled_features = WasmEnabledFeatures::FromIsolate(i_isolate);
constexpr const char* kAPIMethodName = "WasmAsyncFuzzer.compile";
base::OwnedVector<const uint8_t> bytes = base::OwnedCopyOf(data, size);
std::shared_ptr<AsyncFuzzerResolver> resolver =
std::make_shared<AsyncFuzzerResolver>(i_isolate, &done);
GetWasmEngine()->AsyncCompile(i_isolate, enabled_features,
CompileTimeImportsForFuzzing(), resolver,
std::move(bytes), kAPIMethodName);
// Wait for the promise to resolve.
while (!done) {
support->PumpMessageLoop(platform::MessageLoopBehavior::kWaitForWork);
isolate->PerformMicrotaskCheckpoint();
}
return resolver->fuzzing_return_value();
}
} // namespace v8::internal::wasm::fuzzing

View File

@ -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.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

36
deps/v8/test/fuzzer/wasm/code.cc vendored Normal file
View File

@ -0,0 +1,36 @@
// 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 <stddef.h>
#include <stdint.h>
#include "src/execution/isolate.h"
#include "src/wasm/wasm-module-builder.h"
#include "test/common/wasm/test-signatures.h"
#include "test/fuzzer/wasm/fuzzer-common.h"
namespace v8::internal::wasm::fuzzing {
class WasmCodeFuzzer : public WasmExecutionFuzzer {
bool GenerateModule(Isolate* isolate, Zone* zone,
base::Vector<const uint8_t> data,
ZoneBuffer* buffer) override {
TestSignatures sigs;
WasmModuleBuilder builder(zone);
WasmFunctionBuilder* f = builder.AddFunction(sigs.i_iii());
f->EmitCode(data.begin(), static_cast<uint32_t>(data.size()));
f->Emit(kExprEnd);
builder.AddExport(base::CStrVector("main"), f);
builder.AddMemory(0, 32);
builder.WriteTo(buffer);
return true;
}
};
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
return WasmCodeFuzzer().FuzzWasmModule({data, size});
}
} // namespace v8::internal::wasm::fuzzing

0
deps/v8/test/fuzzer/wasm/code/foo vendored Normal file
View File

31
deps/v8/test/fuzzer/wasm/compile-all.cc vendored Normal file
View File

@ -0,0 +1,31 @@
// Copyright 2024 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 "src/base/vector.h"
#include "src/wasm/fuzzing/random-module-generation.h"
#include "src/wasm/wasm-module-builder.h"
#include "src/zone/zone.h"
#include "test/fuzzer/wasm/fuzzer-common.h"
namespace v8::internal::wasm::fuzzing {
// Fuzzer that may generate WasmGC and SIMD expressions.
class WasmCompileAllFuzzer : public WasmExecutionFuzzer {
bool GenerateModule(Isolate* isolate, Zone* zone,
base::Vector<const uint8_t> data,
ZoneBuffer* buffer) override {
base::Vector<const uint8_t> wire_bytes = GenerateRandomWasmModule(
zone, WasmModuleGenerationOptions::All(), data);
if (wire_bytes.empty()) return false;
buffer->write(wire_bytes.data(), wire_bytes.size());
return true;
}
};
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
constexpr bool kRequireValid = true;
return WasmCompileAllFuzzer().FuzzWasmModule({data, size}, kRequireValid);
}
} // namespace v8::internal::wasm::fuzzing

View File

@ -0,0 +1,31 @@
// Copyright 2024 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 "src/base/vector.h"
#include "src/wasm/fuzzing/random-module-generation.h"
#include "src/wasm/wasm-module-builder.h"
#include "src/zone/zone.h"
#include "test/fuzzer/wasm/fuzzer-common.h"
namespace v8::internal::wasm::fuzzing {
// Fuzzer that may generate SIMD expressions.
class WasmCompileSIMDFuzzer : public WasmExecutionFuzzer {
bool GenerateModule(Isolate* isolate, Zone* zone,
base::Vector<const uint8_t> data,
ZoneBuffer* buffer) override {
base::Vector<const uint8_t> wire_bytes = GenerateRandomWasmModule(
zone, {{WasmModuleGenerationOption::kGenerateSIMD}}, data);
if (wire_bytes.empty()) return false;
buffer->write(wire_bytes.data(), wire_bytes.size());
return true;
}
};
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
constexpr bool kRequireValid = true;
return WasmCompileSIMDFuzzer().FuzzWasmModule({data, size}, kRequireValid);
}
} // namespace v8::internal::wasm::fuzzing

View File

@ -0,0 +1,31 @@
// Copyright 2024 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 "src/base/vector.h"
#include "src/wasm/fuzzing/random-module-generation.h"
#include "src/wasm/wasm-module-builder.h"
#include "src/zone/zone.h"
#include "test/fuzzer/wasm/fuzzer-common.h"
namespace v8::internal::wasm::fuzzing {
// Fuzzer that may generate WasmGC expressions.
class WasmCompileWasmGCFuzzer : public WasmExecutionFuzzer {
bool GenerateModule(Isolate* isolate, Zone* zone,
base::Vector<const uint8_t> data,
ZoneBuffer* buffer) override {
base::Vector<const uint8_t> wire_bytes = GenerateRandomWasmModule(
zone, {{WasmModuleGenerationOption::kGenerateWasmGC}}, data);
if (wire_bytes.empty()) return false;
buffer->write(wire_bytes.data(), wire_bytes.size());
return true;
}
};
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
constexpr bool kRequireValid = true;
return WasmCompileWasmGCFuzzer().FuzzWasmModule({data, size}, kRequireValid);
}
} // namespace v8::internal::wasm::fuzzing

30
deps/v8/test/fuzzer/wasm/compile.cc vendored Normal file
View File

@ -0,0 +1,30 @@
// 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 "src/base/vector.h"
#include "src/wasm/fuzzing/random-module-generation.h"
#include "src/wasm/wasm-module-builder.h"
#include "src/zone/zone.h"
#include "test/fuzzer/wasm/fuzzer-common.h"
namespace v8::internal::wasm::fuzzing {
class WasmCompileMVPFuzzer : public WasmExecutionFuzzer {
bool GenerateModule(Isolate* isolate, Zone* zone,
base::Vector<const uint8_t> data,
ZoneBuffer* buffer) override {
base::Vector<const uint8_t> wire_bytes = GenerateRandomWasmModule(
zone, WasmModuleGenerationOptions::MVP(), data);
buffer->write(wire_bytes.data(), wire_bytes.size());
// Without SIMD expressions we are always able to produce a valid module.
return true;
}
};
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
constexpr bool kRequireValid = true;
return WasmCompileMVPFuzzer().FuzzWasmModule({data, size}, kRequireValid);
}
} // namespace v8::internal::wasm::fuzzing

0
deps/v8/test/fuzzer/wasm/compile/foo vendored Normal file
View File

405
deps/v8/test/fuzzer/wasm/deopt.cc vendored Normal file
View File

@ -0,0 +1,405 @@
// Copyright 2024 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 "include/v8-context.h"
#include "include/v8-exception.h"
#include "include/v8-isolate.h"
#include "include/v8-local-handle.h"
#include "src/base/vector.h"
#include "src/execution/isolate.h"
#include "src/objects/property-descriptor.h"
#include "src/wasm/compilation-environment-inl.h"
#include "src/wasm/fuzzing/random-module-generation.h"
#include "src/wasm/module-compiler.h"
#include "src/wasm/wasm-engine.h"
#include "src/wasm/wasm-feature-flags.h"
#include "src/wasm/wasm-module.h"
#include "src/wasm/wasm-objects-inl.h"
#include "src/wasm/wasm-subtyping.h"
#include "src/zone/accounting-allocator.h"
#include "src/zone/zone.h"
#include "test/common/flag-utils.h"
#include "test/common/wasm/wasm-module-runner.h"
#include "test/fuzzer/fuzzer-support.h"
#include "test/fuzzer/wasm/fuzzer-common.h"
// This fuzzer fuzzes deopts.
// It generates a main function accepting a call target. The call target is then
// used in a call_ref or call_indirect. The fuzzer runs the program in a
// reference run to collect expected results.
// Then it performs the same run on a new module optimizing the module after
// every target, causing emission of deopt nodes and potentially triggering
// deopts. Note that if the code containing the speculative call is unreachable
// or not inlined, the fuzzer won't generate a deopt node and won't perform a
// deopt.
// Pseudo code of a minimal wasm module that the fuzzer could generate:
//
// int global0 = 0;
// Table table = [callee0, callee1];
//
// int callee0(int a, int b) {
// return a + b;
// }
//
// int callee1(int a, int b) {
// return a * b;
// }
//
// int inlinee(int a, int b) {
// auto callee = table.get(global0);
// return call_ref(auto_callee)(a, b);
// }
//
// int main(int callee_index) {
// global0 = callee_index;
// return inlinee(1, 2);
// }
// The fuzzer then performs the following test:
// assertEquals(expected_val0, main(0)); // Collects feedback.
// %WasmTierUpFunction(main);
// assertEquals(expected_val1, main(1)); // Potentially triggers deopt.
namespace v8::internal::wasm::fuzzing {
namespace {
using ExecutionResult = std::variant<int, std::string /*exception*/>;
std::ostream& operator<<(std::ostream& out, const ExecutionResult& result) {
std::visit([&out](auto&& val) { out << val; }, result);
return out;
}
class NearHeapLimitCallbackScope {
public:
explicit NearHeapLimitCallbackScope(Isolate* isolate) : isolate_(isolate) {
isolate_->heap()->AddNearHeapLimitCallback(Callback, this);
}
~NearHeapLimitCallbackScope() {
isolate_->heap()->RemoveNearHeapLimitCallback(Callback, initial_limit_);
}
bool heap_limit_reached() const { return heap_limit_reached_; }
private:
static size_t Callback(void* raw_data, size_t current_limit,
size_t initial_limit) {
NearHeapLimitCallbackScope* data =
reinterpret_cast<NearHeapLimitCallbackScope*>(raw_data);
data->heap_limit_reached_ = true;
data->isolate_->TerminateExecution();
data->initial_limit_ = initial_limit;
// Return a slightly raised limit, just to make it to the next
// interrupt check point, where execution will terminate.
return initial_limit * 1.25;
}
Isolate* isolate_;
bool heap_limit_reached_ = false;
size_t initial_limit_ = 0;
};
class EnterDebuggingScope {
public:
explicit EnterDebuggingScope(Isolate* isolate) : isolate_(isolate) {
GetWasmEngine()->EnterDebuggingForIsolate(isolate_);
}
~EnterDebuggingScope() {
GetWasmEngine()->LeaveDebuggingForIsolate(isolate_);
}
private:
Isolate* isolate_;
};
std::vector<ExecutionResult> PerformReferenceRun(
const std::vector<std::string>& callees, ModuleWireBytes wire_bytes,
WasmEnabledFeatures enabled_features, bool valid, Isolate* isolate) {
std::vector<ExecutionResult> results;
FlagScope<bool> eager_compile(&v8_flags.wasm_lazy_compilation, false);
// Reference runs use extra compile settings (like non-determinism detection),
// which would be removed and replaced with a new liftoff function without
// these options.
FlagScope<bool> no_liftoff_code_flushing(&v8_flags.flush_liftoff_code, false);
ErrorThrower thrower(isolate, "WasmFuzzerSyncCompileReference");
int32_t max_steps = kDefaultMaxFuzzerExecutedInstructions;
// We aren't really debugging but this will prevent tier-up and other
// "dynamic" behavior that we do not want to trigger during reference
// execution. This also aligns well with the reference compilation compiling
// with the kForDebugging liftoff option.
EnterDebuggingScope debugging_scope(isolate);
DirectHandle<WasmModuleObject> module_object =
CompileReferenceModule(isolate, wire_bytes.module_bytes(), &max_steps);
thrower.Reset();
CHECK(!isolate->has_exception());
DirectHandle<WasmInstanceObject> instance;
if (!GetWasmEngine()
->SyncInstantiate(isolate, &thrower, module_object, {}, {})
.ToHandle(&instance)) {
CHECK(thrower.error());
// The only reason to fail the second instantiation should be OOM. This can
// happen e.g. for memories with a very big initial size especially on 32
// bit platforms.
CHECK(strstr(thrower.error_msg(), "Out of memory"));
return {};
}
NearHeapLimitCallbackScope near_heap_limit(isolate);
for (uint32_t i = 0; i < callees.size(); ++i) {
// Before execution, there should be no dangling nondeterminism registered
// on the engine.
DCHECK(!WasmEngine::had_nondeterminism());
DirectHandle<Object> arguments[] = {
direct_handle(Smi::FromInt(i), isolate)};
std::unique_ptr<const char[]> exception;
int32_t result = testing::CallWasmFunctionForTesting(
isolate, instance, "main", base::VectorOf(arguments), &exception);
// If there is nondeterminism, we cannot guarantee the behavior of the test
// module, and in particular it may not terminate.
if (WasmEngine::clear_nondeterminism()) break;
// Reached max steps, do not try to execute the test module as it might
// never terminate.
if (max_steps < 0) break;
// Similar to max steps reached, also discard modules that need too much
// memory.
if (near_heap_limit.heap_limit_reached()) {
isolate->CancelTerminateExecution();
break;
}
if (exception) {
isolate->CancelTerminateExecution();
if (strcmp(exception.get(),
"RangeError: Maximum call stack size exceeded") == 0) {
// There was a stack overflow, which may happen nondeterministically. We
// cannot guarantee the behavior of the test module, and in particular
// it may not terminate.
break;
}
results.emplace_back(exception.get());
} else {
results.emplace_back(result);
}
}
thrower.Reset();
isolate->clear_exception();
return results;
}
void ConfigureFlags(v8::Isolate* isolate) {
struct FlagConfiguration {
explicit FlagConfiguration(v8::Isolate* isolate) {
// Disable the NativeModule cache. Different fuzzer iterations should not
// interact with each other. Rerunning a fuzzer input (e.g. with
// libfuzzer's "-runs=x" argument) should repeatedly test deoptimizations.
// When caching the optimized code, only the first run will execute any
// deopts.
v8_flags.wasm_native_module_cache = false;
// We switch it to synchronous mode to avoid the nondeterminism of
// background jobs finishing at random times.
v8_flags.wasm_sync_tier_up = true;
// Enable the experimental features we want to fuzz. (Note that
// EnableExperimentalWasmFeatures only enables staged features.)
v8_flags.wasm_deopt = true;
v8_flags.wasm_inlining_call_indirect = true;
// Make inlining more aggressive.
v8_flags.wasm_inlining_ignore_call_counts = true;
v8_flags.wasm_inlining_budget = v8_flags.wasm_inlining_budget * 5;
v8_flags.wasm_inlining_max_size = v8_flags.wasm_inlining_max_size * 5;
v8_flags.wasm_inlining_factor = v8_flags.wasm_inlining_factor * 5;
// Enable other staged or experimental features and enforce flag
// implications.
EnableExperimentalWasmFeatures(isolate);
}
};
static FlagConfiguration config(isolate);
}
int FuzzIt(base::Vector<const uint8_t> data) {
v8_fuzzer::FuzzerSupport* support = v8_fuzzer::FuzzerSupport::Get();
v8::Isolate* isolate = support->GetIsolate();
// Strictly enforce the input size limit as in fuzzer-common.h.
if (data.size() > kMaxFuzzerInputSize) return 0;
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());
ConfigureFlags(isolate);
v8::TryCatch try_catch(isolate);
AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME);
// Clear recursive groups: The fuzzer creates random types in every run. These
// are saved as recursive groups as part of the type canonicalizer, but types
// from previous runs just waste memory.
ResetTypeCanonicalizer(isolate, &zone);
std::vector<std::string> callees;
std::vector<std::string> inlinees;
base::Vector<const uint8_t> buffer =
GenerateWasmModuleForDeopt(&zone, data, callees, inlinees);
ModuleWireBytes wire_bytes(buffer.begin(), buffer.end());
testing::SetupIsolateForWasmModule(i_isolate);
auto enabled_features = WasmEnabledFeatures::FromIsolate(i_isolate);
bool valid = GetWasmEngine()->SyncValidate(
i_isolate, enabled_features, CompileTimeImportsForFuzzing(), buffer);
// Choose whether to optimize the main function or the outermost callee.
// As the main function has a fixed signature, it doesn't provide great
// coverage to always optimize and deopt the main function. Instead by
// optimizing an inner wasm function, there can be a large amount of
// parameters and returns with all kinds of types.
const bool optimize_main_function =
inlinees.empty() || data.empty() || !(data.last() & 1);
if (v8_flags.wasm_fuzzer_gen_test) {
StdoutStream os;
std::stringstream flags;
flags << " --allow-natives-syntax --wasm-deopt "
"--wasm-inlining-ignore-call-counts --wasm-sync-tier-up "
"--wasm-inlining-budget="
<< v8_flags.wasm_inlining_budget
<< " --wasm-inlining-max-size=" << v8_flags.wasm_inlining_max_size
<< " --wasm-inlining-factor=" << v8_flags.wasm_inlining_factor;
// The deopt fuzzer generates a different call sequence.
bool emit_call_main = false;
GenerateTestCase(os, i_isolate, wire_bytes, valid, emit_call_main,
flags.str());
// Emit helper.
os << "let maybeThrows = (fct, ...args) => {\n"
" try {\n"
" print(fct(...args));\n"
" } catch (e) {\n"
" print(`caught ${e}`);\n"
" }\n"
"};\n\n";
// Emit calls for the different call target indices each followed by an
// explicit tier-up.
std::string fct("instance.exports.");
fct.append(optimize_main_function ? "main" : inlinees.back());
for (size_t i = 0; i < callees.size(); ++i) {
os << "maybeThrows(instance.exports.main, " << i
<< ");\n"
"%WasmTierUpFunction("
<< fct << ");\n";
}
os.flush();
}
ErrorThrower thrower(i_isolate, "WasmFuzzerSyncCompile");
MaybeDirectHandle<WasmModuleObject> compiled = GetWasmEngine()->SyncCompile(
i_isolate, enabled_features, CompileTimeImportsForFuzzing(), &thrower,
base::OwnedCopyOf(buffer));
if (!valid) {
FATAL("Generated module should validate, but got: %s\n",
thrower.error_msg());
}
std::vector<ExecutionResult> reference_results = PerformReferenceRun(
callees, wire_bytes, enabled_features, valid, i_isolate);
if (reference_results.empty()) {
// If the first run already included non-determinism, there isn't any value
// in even compiling it (as this fuzzer focuses on executing deopts).
// Return -1 to not add this case to the corpus.
return -1;
}
DirectHandle<WasmModuleObject> module_object = compiled.ToHandleChecked();
DirectHandle<WasmInstanceObject> instance;
if (!GetWasmEngine()
->SyncInstantiate(i_isolate, &thrower, module_object, {}, {})
.ToHandle(&instance)) {
DCHECK(thrower.error());
// The only reason to fail the second instantiation should be OOM. This can
// happen e.g. for memories with a very big initial size especially on 32
// bit platforms.
if (strstr(thrower.error_msg(), "Out of memory")) {
return -1; // Return -1 to not add this case to the corpus.
}
FATAL("Second instantiation failed unexpectedly: %s", thrower.error_msg());
}
DCHECK(!thrower.error());
DirectHandle<WasmExportedFunction> main_function =
testing::GetExportedFunction(i_isolate, instance, "main")
.ToHandleChecked();
int function_to_optimize =
main_function->shared()->wasm_exported_function_data()->function_index();
if (!optimize_main_function) {
function_to_optimize--;
}
int deopt_count_begin = GetWasmEngine()->GetDeoptsExecutedCount();
int deopt_count_previous_iteration = deopt_count_begin;
size_t num_callees = reference_results.size();
for (uint32_t i = 0; i < num_callees; ++i) {
DirectHandle<Object> arguments[] = {
direct_handle(Smi::FromInt(i), i_isolate)};
std::unique_ptr<const char[]> exception;
DCHECK(!WasmEngine::had_nondeterminism()); // No dangling nondeterminism.
int32_t result_value = testing::CallWasmFunctionForTesting(
i_isolate, instance, "main", base::VectorOf(arguments), &exception);
// Also the second run can hit nondeterminism which was not hit before (when
// growing memory). In that case, do not compare results.
// TODO(384781857): Due to nondeterminism, the second run could even not
// terminate. If this happens often enough we should do something about
// this.
if (WasmEngine::clear_nondeterminism()) return -1;
ExecutionResult actual_result;
if (exception) {
actual_result = exception.get();
} else {
actual_result = result_value;
}
if (actual_result != reference_results[i]) {
std::cerr << "Different results vs. reference run for callee "
<< callees[i] << ": \nReference: " << reference_results[i]
<< "\nActual: " << actual_result << std::endl;
CHECK_EQ(actual_result, reference_results[i]);
UNREACHABLE();
}
int deopt_count = GetWasmEngine()->GetDeoptsExecutedCount();
if (i != 0 && deopt_count == deopt_count_previous_iteration) {
// No deopt triggered. Skip the rest of the run as it won't provide
// meaningful coverage for the deoptimizer.
// Return -1 to prevent adding this case to the corpus if not a single
// deopt was executed.
return deopt_count == deopt_count_begin ? -1 : 0;
}
deopt_count_previous_iteration = deopt_count;
TierUpNowForTesting(i_isolate, instance->trusted_data(i_isolate),
function_to_optimize);
}
return 0;
}
} // anonymous namespace
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
return FuzzIt({data, size});
}
} // namespace v8::internal::wasm::fuzzing

View File

@ -0,0 +1,560 @@
// 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 "include/v8-context.h"
#include "include/v8-exception.h"
#include "include/v8-isolate.h"
#include "include/v8-local-handle.h"
#include "include/v8-metrics.h"
#include "src/execution/isolate.h"
#include "src/utils/ostreams.h"
#include "src/wasm/baseline/liftoff-compiler.h"
#include "src/wasm/compilation-environment-inl.h"
#include "src/wasm/function-body-decoder-impl.h"
#include "src/wasm/module-compiler.h"
#include "src/wasm/module-decoder-impl.h"
#include "src/wasm/module-instantiate.h"
#include "src/wasm/string-builder-multiline.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/wasm/wasm-opcodes-inl.h"
#include "src/zone/accounting-allocator.h"
#include "src/zone/zone.h"
#include "test/common/flag-utils.h"
#include "test/common/wasm/wasm-module-runner.h"
#include "test/fuzzer/fuzzer-support.h"
#include "tools/wasm/mjsunit-module-disassembler-impl.h"
#if V8_ENABLE_DRUMBRAKE
#include "src/wasm/interpreter/wasm-interpreter.h"
#endif // V8_ENABLE_DRUMBRAKE
namespace v8::internal::wasm::fuzzing {
namespace {
void CompileAllFunctionsForReferenceExecution(NativeModule* native_module,
int32_t* max_steps) {
const WasmModule* module = native_module->module();
WasmCodeRefScope code_ref_scope;
CompilationEnv env = CompilationEnv::ForModule(native_module);
ModuleWireBytes wire_bytes_accessor{native_module->wire_bytes()};
for (size_t i = module->num_imported_functions; i < module->functions.size();
++i) {
auto& func = module->functions[i];
base::Vector<const uint8_t> func_code =
wire_bytes_accessor.GetFunctionBytes(&func);
constexpr bool kIsShared = false;
FunctionBody func_body(func.sig, func.code.offset(), func_code.begin(),
func_code.end(), kIsShared);
auto result =
ExecuteLiftoffCompilation(&env, func_body,
LiftoffOptions{}
.set_func_index(func.func_index)
.set_for_debugging(kForDebugging)
.set_max_steps(max_steps)
.set_detect_nondeterminism(true));
if (!result.succeeded()) {
FATAL(
"Liftoff compilation failed on a valid module. Run with "
"--trace-wasm-decoder (in a debug build) to see why.");
}
native_module->PublishCode(native_module->AddCompiledCode(result));
}
}
} // namespace
CompileTimeImports CompileTimeImportsForFuzzing() {
CompileTimeImports result;
result.Add(CompileTimeImport::kJsString);
result.Add(CompileTimeImport::kTextDecoder);
result.Add(CompileTimeImport::kTextEncoder);
return result;
}
// Compile a baseline module. We pass a pointer to a max step counter and a
// nondeterminsm flag that are updated during execution by Liftoff.
DirectHandle<WasmModuleObject> CompileReferenceModule(
Isolate* isolate, base::Vector<const uint8_t> wire_bytes,
int32_t* max_steps) {
// Create the native module.
std::shared_ptr<NativeModule> native_module;
constexpr bool kNoVerifyFunctions = false;
auto enabled_features = WasmEnabledFeatures::FromIsolate(isolate);
WasmDetectedFeatures detected_features;
ModuleResult module_res =
DecodeWasmModule(enabled_features, wire_bytes, kNoVerifyFunctions,
ModuleOrigin::kWasmOrigin, &detected_features);
CHECK(module_res.ok());
std::shared_ptr<WasmModule> module = std::move(module_res).value();
CHECK_NOT_NULL(module);
CompileTimeImports compile_imports = CompileTimeImportsForFuzzing();
WasmError imports_error = ValidateAndSetBuiltinImports(
module.get(), wire_bytes, compile_imports, &detected_features);
CHECK(!imports_error.has_error()); // The module was compiled before.
const size_t code_size_estimate =
WasmCodeManager::EstimateNativeModuleCodeSize(module.get());
native_module = GetWasmEngine()->NewNativeModule(
isolate, enabled_features, detected_features,
CompileTimeImportsForFuzzing(), module, code_size_estimate);
native_module->SetWireBytes(base::OwnedCopyOf(wire_bytes));
// The module is known to be valid as this point (it was compiled by the
// caller before).
module->set_all_functions_validated();
// The value is -3 so that it is different than the compilation ID of actual
// compilations, different than the sentinel value of the CompilationState
// (-1) and the value used by native module deserialization (-2).
const int dummy_fuzzing_compilation_id = -3;
native_module->compilation_state()->set_compilation_id(
dummy_fuzzing_compilation_id);
InitializeCompilationForTesting(native_module.get());
// Compile all functions with Liftoff.
CompileAllFunctionsForReferenceExecution(native_module.get(), max_steps);
// Create the module object.
constexpr base::Vector<const char> kNoSourceUrl;
DirectHandle<Script> script =
GetWasmEngine()->GetOrCreateScript(isolate, native_module, kNoSourceUrl);
TypeCanonicalizer::PrepareForCanonicalTypeId(isolate,
module->MaxCanonicalTypeIndex());
return WasmModuleObject::New(isolate, std::move(native_module), script);
}
#if V8_ENABLE_DRUMBRAKE
void ClearJsToWasmWrappersForTesting(Isolate* isolate) {
for (int i = 0; i < isolate->heap()->js_to_wasm_wrappers()->length(); i++) {
isolate->heap()->js_to_wasm_wrappers()->set(i, ClearedValue(isolate));
}
}
int ExecuteAgainstReference(Isolate* isolate,
DirectHandle<WasmModuleObject> module_object,
int32_t max_executed_instructions,
bool is_wasm_jitless) {
#else // V8_ENABLE_DRUMBRAKE
int ExecuteAgainstReference(Isolate* isolate,
DirectHandle<WasmModuleObject> module_object,
int32_t max_executed_instructions) {
#endif // V8_ENABLE_DRUMBRAKE
// 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 -1;
int32_t max_steps = max_executed_instructions;
HandleScope handle_scope(isolate); // Avoid leaking handles.
Zone reference_module_zone(isolate->allocator(), "wasm reference module");
DirectHandle<WasmModuleObject> module_ref = CompileReferenceModule(
isolate, module_object->native_module()->wire_bytes(), &max_steps);
DirectHandle<WasmInstanceObject> instance_ref;
// Before execution, there should be no dangling nondeterminism registered on
// the engine.
DCHECK(!WasmEngine::had_nondeterminism());
// Try to instantiate the reference instance, return if it fails.
{
ErrorThrower thrower(isolate, "ExecuteAgainstReference");
if (!GetWasmEngine()
->SyncInstantiate(isolate, &thrower, module_ref, {},
{}) // no imports & memory
.ToHandle(&instance_ref)) {
isolate->clear_exception();
thrower.Reset(); // Ignore errors.
return -1;
}
}
// Get the "main" exported function. Do nothing if it does not exist.
DirectHandle<WasmExportedFunction> main_function;
if (!testing::GetExportedFunction(isolate, instance_ref, "main")
.ToHandle(&main_function)) {
return -1;
}
struct OomCallbackData {
Isolate* isolate;
bool heap_limit_reached{false};
size_t initial_limit{0};
};
OomCallbackData oom_callback_data{isolate};
auto heap_limit_callback = [](void* raw_data, size_t current_limit,
size_t initial_limit) -> size_t {
OomCallbackData* data = reinterpret_cast<OomCallbackData*>(raw_data);
data->heap_limit_reached = true;
data->isolate->TerminateExecution();
data->initial_limit = initial_limit;
// Return a slightly raised limit, just to make it to the next
// interrupt check point, where execution will terminate.
return initial_limit * 1.25;
};
isolate->heap()->AddNearHeapLimitCallback(heap_limit_callback,
&oom_callback_data);
Tagged<WasmExportedFunctionData> func_data =
main_function->shared()->wasm_exported_function_data();
const FunctionSig* sig = func_data->instance_data()
->module()
->functions[func_data->function_index()]
.sig;
auto compiled_args = testing::MakeDefaultArguments(isolate, sig);
std::unique_ptr<const char[]> exception_ref;
int32_t result_ref = testing::CallWasmFunctionForTesting(
isolate, instance_ref, "main", base::VectorOf(compiled_args),
&exception_ref);
bool execute = true;
// Reached max steps, do not try to execute the test module as it might
// never terminate.
if (max_steps < 0) execute = false;
// If there is nondeterminism, we cannot guarantee the behavior of the test
// module, and in particular it may not terminate.
if (WasmEngine::clear_nondeterminism()) execute = false;
// Similar to max steps reached, also discard modules that need too much
// memory.
isolate->heap()->RemoveNearHeapLimitCallback(heap_limit_callback,
oom_callback_data.initial_limit);
if (oom_callback_data.heap_limit_reached) {
execute = false;
isolate->CancelTerminateExecution();
}
#if V8_ENABLE_DRUMBRAKE
if (is_wasm_jitless) {
v8::internal::v8_flags.jitless = true;
v8::internal::v8_flags.wasm_jitless = true;
FlagList::EnforceFlagImplications();
v8::internal::wasm::WasmInterpreterThread::Initialize();
ClearJsToWasmWrappersForTesting(isolate);
// Compiled WasmCode objects should be cleared before running drumbrake.
module_ref = Handle<WasmModuleObject>::null();
isolate->heap()->CollectAllGarbage(GCFlag::kNoFlags,
i::GarbageCollectionReason::kTesting);
// The module should be validated when compiled for jitless mode.
// But, we already compiled the module without jitless for the reference
// instance. So, we run the validation here before running drumbrake.
auto enabled_features = WasmEnabledFeatures::FromIsolate(isolate);
WasmDetectedFeatures unused_detected_features;
ModuleDecoderImpl decoder(
enabled_features, module_object->native_module()->wire_bytes(),
ModuleOrigin::kWasmOrigin, &unused_detected_features);
if (decoder.DecodeModule(/*validate_functions=*/true).failed()) return -1;
}
#endif // V8_ENABLE_DRUMBRAKE
if (exception_ref) {
if (strcmp(exception_ref.get(),
"RangeError: Maximum call stack size exceeded") == 0) {
// There was a stack overflow, which may happen nondeterministically. We
// cannot guarantee the behavior of the test module, and in particular it
// may not terminate.
execute = false;
}
}
if (!execute) {
// Before discarding the module, see if Turbofan runs into any DCHECKs.
TierUpAllForTesting(isolate, instance_ref->trusted_data(isolate));
return -1;
}
// Instantiate a fresh instance for the actual (non-ref) execution.
DirectHandle<WasmInstanceObject> instance;
{
ErrorThrower thrower(isolate, "ExecuteAgainstReference (second)");
// We instantiated before, so the second instantiation must also succeed.
if (!GetWasmEngine()
->SyncInstantiate(isolate, &thrower, module_object, {},
{}) // no imports & memory
.ToHandle(&instance)) {
DCHECK(thrower.error());
// The only reason to fail the second instantiation should be OOM.
if (strstr(thrower.error_msg(), "Out of memory")) {
// The initial memory size might be too large for instantiation
// (especially on 32 bit systems), therefore do not treat it as a fuzzer
// failure.
return -1;
}
FATAL("Second instantiation failed unexpectedly: %s",
thrower.error_msg());
}
DCHECK(!thrower.error());
}
std::unique_ptr<const char[]> exception;
int32_t result = testing::CallWasmFunctionForTesting(
isolate, instance, "main", base::VectorOf(compiled_args), &exception);
// Also the second run can hit nondeterminism which was not hit before (when
// growing memory). In that case, do not compare results.
// TODO(384781857): Due to nondeterminism, the second run could even not
// terminate. If this happens often enough we should do something about this.
if (WasmEngine::clear_nondeterminism()) return -1;
if ((exception_ref != nullptr) != (exception != nullptr)) {
FATAL("Exception mismatch! Expected: <%s>; got: <%s>",
exception_ref ? exception_ref.get() : "<no exception>",
exception ? exception.get() : "<no exception>");
}
if (!exception) {
CHECK_EQ(result_ref, result);
}
return 0;
}
void GenerateTestCase(Isolate* isolate, ModuleWireBytes wire_bytes,
bool compiles) {
StdoutStream os;
GenerateTestCase(os, isolate, wire_bytes, compiles, false, "");
os.flush();
}
void GenerateTestCase(StdoutStream& os, Isolate* isolate,
ModuleWireBytes wire_bytes, bool compiles,
bool emit_call_main, std::string_view extra_flags) {
// Libfuzzer sometimes runs a test twice (for detecting memory leaks), and in
// this case we do not want multiple outputs by this function.
// Similarly if we explicitly execute the same test multiple times (via
// `-runs=N`).
static std::atomic<bool> did_output_before{false};
if (did_output_before.exchange(true)) return;
constexpr bool kVerifyFunctions = false;
auto enabled_features = WasmEnabledFeatures::FromIsolate(isolate);
WasmDetectedFeatures unused_detected_features;
ModuleResult module_res = DecodeWasmModule(
enabled_features, wire_bytes.module_bytes(), kVerifyFunctions,
ModuleOrigin::kWasmOrigin, &unused_detected_features);
CHECK_WITH_MSG(module_res.ok(), module_res.error().message().c_str());
WasmModule* module = module_res.value().get();
CHECK_NOT_NULL(module);
AccountingAllocator allocator;
Zone zone(&allocator, "constant expression zone");
MultiLineStringBuilder out;
NamesProvider names(module, wire_bytes.module_bytes());
MjsunitModuleDis disassembler(out, module, &names, wire_bytes, &allocator,
!compiles);
disassembler.PrintModule(extra_flags, emit_call_main);
const bool offsets = false; // Not supported by MjsunitModuleDis.
out.WriteTo(os, offsets);
}
namespace {
std::vector<uint8_t> CreateDummyModuleWireBytes(Zone* zone) {
// Build a simple module with a few types to pre-populate the type
// canonicalizer.
WasmModuleBuilder builder(zone);
const bool is_final = true;
builder.AddRecursiveTypeGroup(0, 2);
builder.AddArrayType(zone->New<ArrayType>(kWasmF32, true), is_final);
StructType::Builder struct_builder(zone, 2, false);
struct_builder.AddField(kWasmI64, false);
struct_builder.AddField(kWasmExternRef, false);
builder.AddStructType(struct_builder.Build(), !is_final);
FunctionSig::Builder sig_builder(zone, 1, 0);
sig_builder.AddReturn(kWasmI32);
builder.AddSignature(sig_builder.Get(), is_final);
ZoneBuffer buffer{zone};
builder.WriteTo(&buffer);
return std::vector<uint8_t>(buffer.begin(), buffer.end());
}
} // namespace
void AddDummyTypesToTypeCanonicalizer(Isolate* isolate, Zone* zone) {
const size_t type_count = GetTypeCanonicalizer()->GetCurrentNumberOfTypes();
testing::SetupIsolateForWasmModule(isolate);
// Cache (and leak) the wire bytes, so they don't need to be rebuilt on each
// run.
static const std::vector<uint8_t> wire_bytes =
CreateDummyModuleWireBytes(zone);
const bool is_valid = GetWasmEngine()->SyncValidate(
isolate, WasmEnabledFeatures(), CompileTimeImportsForFuzzing(),
base::VectorOf(wire_bytes));
CHECK(is_valid);
// As the types are reset on each run by the fuzzer, the validation should
// have added new types to the TypeCanonicalizer.
CHECK_GT(GetTypeCanonicalizer()->GetCurrentNumberOfTypes(), type_count);
}
void EnableExperimentalWasmFeatures(v8::Isolate* isolate) {
struct EnableExperimentalWasmFeatures {
explicit EnableExperimentalWasmFeatures(v8::Isolate* isolate) {
// Enable all staged features.
#define ENABLE_STAGED_FEATURES(feat, ...) \
v8_flags.experimental_wasm_##feat = true;
FOREACH_WASM_STAGING_FEATURE_FLAG(ENABLE_STAGED_FEATURES)
#undef ENABLE_STAGED_FEATURES
// Enable non-staged experimental features or other experimental flags
// that we also want to fuzz, e.g., new optimizations.
// Note: If you add a Wasm feature here, you will also have to add the
// respective flag(s) to the mjsunit/wasm/generate-random-module.js test,
// otherwise that fails on an unsupported feature.
// You may also want to add the flag(s) to the JS file header in
// `PrintModule()` of `mjsunit-module-disassembler-impl.h`, to make bugs
// easier to reproduce with generated mjsunit test cases.
// See https://crbug.com/335082212.
v8_flags.wasm_inlining_call_indirect = true;
// Enforce implications from enabling features.
FlagList::EnforceFlagImplications();
// Last, install any conditional features. Implications are handled
// implicitly.
isolate->InstallConditionalFeatures(isolate->GetCurrentContext());
}
};
// The compiler will properly synchronize the constructor call.
static EnableExperimentalWasmFeatures one_time_enable_experimental_features(
isolate);
}
void ResetTypeCanonicalizer(v8::Isolate* isolate, Zone* zone) {
v8::internal::Isolate* i_isolate =
reinterpret_cast<v8::internal::Isolate*>(isolate);
// Make sure that there are no NativeModules left referencing the canonical
// types. Collecting NativeModules can require two rounds of GC.
for (int i = 0; i < 2 && GetWasmEngine()->NativeModuleCount() != 0; i++) {
// We need to invoke GC without stack, otherwise the native module may
// survive.
DisableConservativeStackScanningScopeForTesting no_stack_scanning(
i_isolate->heap());
isolate->RequestGarbageCollectionForTesting(
v8::Isolate::kFullGarbageCollection);
}
GetTypeCanonicalizer()->EmptyStorageForTesting();
TypeCanonicalizer::ClearWasmCanonicalTypesForTesting(i_isolate);
AddDummyTypesToTypeCanonicalizer(i_isolate, zone);
}
int WasmExecutionFuzzer::FuzzWasmModule(base::Vector<const uint8_t> data,
bool require_valid) {
v8_fuzzer::FuzzerSupport* support = v8_fuzzer::FuzzerSupport::Get();
v8::Isolate* isolate = support->GetIsolate();
// 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 -1;
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());
// 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.
EnableExperimentalWasmFeatures(isolate);
AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME);
// Clear recursive groups: The fuzzer creates random types in every run. These
// are saved as recursive groups as part of the type canonicalizer, but types
// from previous runs just waste memory.
ResetTypeCanonicalizer(isolate, &zone);
// Clear any exceptions from a prior run.
if (i_isolate->has_exception()) {
i_isolate->clear_exception();
}
v8::TryCatch try_catch(isolate);
HandleScope scope(i_isolate);
ZoneBuffer buffer(&zone);
// The first byte specifies some internal configuration, like which function
// is compiled with which compiler, and other flags.
uint8_t configuration_byte = data.empty() ? 0 : data[0];
if (!data.empty()) data += 1;
// Derive the compiler configuration for the first four functions from the
// configuration byte, to choose for each function between:
// 0: TurboFan
// 1: Liftoff
// 2: Liftoff for debugging
uint8_t tier_mask = 0;
uint8_t debug_mask = 0;
for (int i = 0; i < 4; ++i, configuration_byte /= 3) {
int compiler_config = configuration_byte % 3;
tier_mask |= (compiler_config == 0) << i;
debug_mask |= (compiler_config == 2) << i;
}
if (!GenerateModule(i_isolate, &zone, data, &buffer)) {
return -1;
}
testing::SetupIsolateForWasmModule(i_isolate);
ModuleWireBytes wire_bytes(buffer.begin(), buffer.end());
auto enabled_features = WasmEnabledFeatures::FromIsolate(i_isolate);
bool valid = GetWasmEngine()->SyncValidate(i_isolate, enabled_features,
CompileTimeImportsForFuzzing(),
wire_bytes.module_bytes());
if (v8_flags.wasm_fuzzer_gen_test) {
GenerateTestCase(i_isolate, wire_bytes, valid);
}
FlagScope<bool> eager_compile(&v8_flags.wasm_lazy_compilation, false);
// We want to keep dynamic tiering enabled because that changes the code
// Liftoff generates as well as optimizing compilers' behavior (especially
// around inlining). We switch it to synchronous mode to avoid the
// nondeterminism of background jobs finishing at random times.
FlagScope<bool> sync_tier_up(&v8_flags.wasm_sync_tier_up, true);
// The purpose of setting the tier mask (which affects the initial
// compilation of each function) is to deterministically test a combination
// of Liftoff and Turbofan.
FlagScope<int> tier_mask_scope(&v8_flags.wasm_tier_mask_for_testing,
tier_mask);
FlagScope<int> debug_mask_scope(&v8_flags.wasm_debug_mask_for_testing,
debug_mask);
// Reference runs use extra compile settings (like non-determinism detection),
// which would be removed and replaced with a new liftoff function without
// these options.
FlagScope<bool> no_liftoff_code_flushing(&v8_flags.flush_liftoff_code, false);
ErrorThrower thrower(i_isolate, "WasmFuzzerSyncCompile");
MaybeDirectHandle<WasmModuleObject> compiled_module =
GetWasmEngine()->SyncCompile(i_isolate, enabled_features,
CompileTimeImportsForFuzzing(), &thrower,
base::OwnedCopyOf(buffer));
CHECK_EQ(valid, !compiled_module.is_null());
CHECK_EQ(!valid, thrower.error());
if (require_valid && !valid) {
FATAL("Generated module should validate, but got: %s", thrower.error_msg());
}
thrower.Reset();
// Do not execute invalid modules, and return `-1` to avoid adding them to the
// corpus. Even though invalid modules are also somewhat interesting to fuzz,
// we will get them often enough via mutations, so we do not add them to the
// corpus.
if (!valid) return -1;
return ExecuteAgainstReference(i_isolate, compiled_module.ToHandleChecked(),
kDefaultMaxFuzzerExecutedInstructions);
}
} // namespace v8::internal::wasm::fuzzing

107
deps/v8/test/fuzzer/wasm/fuzzer-common.h vendored Normal file
View File

@ -0,0 +1,107 @@
// 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.
#ifndef WASM_FUZZER_COMMON_H_
#define WASM_FUZZER_COMMON_H_
#include <stddef.h>
#include <stdint.h>
#include <memory>
#include "src/wasm/wasm-features.h"
#include "src/wasm/wasm-module.h"
namespace v8::internal {
class StdoutStream;
class WasmInstanceObject;
namespace wasm {
class ZoneBuffer;
}
} // namespace v8::internal
namespace v8::internal::wasm::fuzzing {
// A default value for {max_executed_instructions} in {ExecuteAgainstReference}.
#ifdef USE_SIMULATOR
constexpr int kDefaultMaxFuzzerExecutedInstructions = 16'000;
#else
constexpr int kDefaultMaxFuzzerExecutedInstructions = 1'000'000;
#endif
CompileTimeImports CompileTimeImportsForFuzzing();
// First creates a reference module fully compiled with Liftoff, with
// instrumentation to stop after a given number of steps and to record any
// nondeterminism while executing. If execution finishes within {max_steps},
// {module_object} is instantiated, its "main" function is executed, and the
// result is compared against the reference execution. If non-determinism was
// detected during the reference execution, the result is allowed to differ.
#ifndef V8_ENABLE_DRUMBRAKE
// Returns 0 or -1, see {WasmExecutionFuzzer::FuzzWasmModule}.
V8_WARN_UNUSED_RESULT int ExecuteAgainstReference(
Isolate* isolate, DirectHandle<WasmModuleObject> module_object,
int32_t max_executed_instructions);
#else // V8_ENABLE_DRUMBRAKE
// It turns `--wasm-jitless` flag on for the second execution when
// `is_wasm_jitless` is set.
int ExecuteAgainstReference(Isolate* isolate,
DirectHandle<WasmModuleObject> module_object,
int32_t max_executed_instructions,
bool is_wasm_jitless = false);
void ClearJsToWasmWrappersForTesting(Isolate* isolate);
#endif // V8_ENABLE_DRUMBRAKE
DirectHandle<WasmModuleObject> CompileReferenceModule(
Isolate* isolate, base::Vector<const uint8_t> wire_bytes,
int32_t* max_steps);
void GenerateTestCase(Isolate* isolate, ModuleWireBytes wire_bytes,
bool compiles);
void GenerateTestCase(StdoutStream& os, Isolate* isolate,
ModuleWireBytes wire_bytes, bool compiles,
bool emit_call_main, std::string_view extra_args);
// Validate a module containing a few wasm-gc types. This can be done to
// prepulate the TypeCanonicalizer with a few canonical types, so that a
// module-specific type index is more likely to be different from its canonical
// type index.
void AddDummyTypesToTypeCanonicalizer(Isolate* isolate, Zone* zone);
// On the first call, enables all staged wasm features and experimental features
// that are ready for fuzzing. All subsequent calls are no-ops. This avoids race
// conditions with threads reading the flags. Fuzzers are executed in their own
// process anyway, so this should not interfere with anything.
void EnableExperimentalWasmFeatures(v8::Isolate* isolate);
// Clear the type canonicalizer storage and re-add the dummy types.
void ResetTypeCanonicalizer(v8::Isolate* isolate, Zone* zone);
constexpr int kMaxFuzzerInputSize = 512;
class WasmExecutionFuzzer {
public:
virtual ~WasmExecutionFuzzer() = default;
// The main entry point: Generate a module from the data, using
// `GenerateModule` defined below. Returns `-1` if the test case is not
// interesting for fuzzing; otherwise returns `0`.
// This should be returned to libfuzzer to indicate that the test case should
// not be added to the corpus (see
// https://llvm.org/docs/LibFuzzer.html#rejecting-unwanted-inputs).
V8_WARN_UNUSED_RESULT int FuzzWasmModule(base::Vector<const uint8_t> data,
bool require_valid = false);
virtual size_t max_input_size() const { return kMaxFuzzerInputSize; }
protected:
virtual bool GenerateModule(Isolate* isolate, Zone* zone,
base::Vector<const uint8_t> data,
ZoneBuffer* buffer) = 0;
};
} // namespace v8::internal::wasm::fuzzing
#endif // WASM_FUZZER_COMMON_H_

379
deps/v8/test/fuzzer/wasm/init-expr.cc vendored Normal file
View File

@ -0,0 +1,379 @@
// Copyright 2024 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 "include/v8-context.h"
#include "include/v8-exception.h"
#include "include/v8-isolate.h"
#include "include/v8-local-handle.h"
#include "src/base/vector.h"
#include "src/execution/isolate.h"
#include "src/objects/property-descriptor.h"
#include "src/wasm/compilation-environment-inl.h"
#include "src/wasm/fuzzing/random-module-generation.h"
#include "src/wasm/wasm-engine.h"
#include "src/wasm/wasm-feature-flags.h"
#include "src/wasm/wasm-module.h"
#include "src/wasm/wasm-objects-inl.h"
#include "src/wasm/wasm-subtyping.h"
#include "src/zone/accounting-allocator.h"
#include "src/zone/zone.h"
#include "test/common/flag-utils.h"
#include "test/common/wasm/wasm-module-runner.h"
#include "test/fuzzer/fuzzer-support.h"
#include "test/fuzzer/wasm/fuzzer-common.h"
// This fuzzer fuzzes initializer expressions used e.g. in globals.
// The fuzzer creates a set of globals with initializer expressions and a set of
// functions containing the same body as these initializer expressions.
// The global value should be equal to the result of running the corresponding
// function.
namespace v8::internal::wasm::fuzzing {
#define CHECK_FLOAT_EQ(expected, actual) \
if (std::isnan(expected)) { \
CHECK(std::isnan(actual)); \
} else { \
CHECK_EQ(expected, actual); \
CHECK_EQ(std::signbit(expected), std::signbit(actual)); \
}
namespace {
bool IsNullOrWasmNull(Tagged<Object> obj) {
return IsNull(obj) || IsWasmNull(obj);
}
DirectHandle<Object> GetExport(Isolate* isolate,
DirectHandle<WasmInstanceObject> instance,
const char* name) {
DirectHandle<JSObject> exports_object;
DirectHandle<Name> exports =
isolate->factory()->InternalizeUtf8String("exports");
exports_object = Cast<JSObject>(
JSObject::GetProperty(isolate, instance, exports).ToHandleChecked());
DirectHandle<Name> main_name =
isolate->factory()->NewStringFromAsciiChecked(name);
PropertyDescriptor desc;
Maybe<bool> property_found = JSReceiver::GetOwnPropertyDescriptor(
isolate, exports_object, main_name, &desc);
CHECK(property_found.FromMaybe(false));
return desc.value();
}
void CheckEquivalent(const WasmValue& init_lhs, const WasmValue& init_rhs,
const WasmModule& module) {
DisallowGarbageCollection no_gc;
// Stack of elements to be checked.
std::vector<std::pair<WasmValue, WasmValue>> cmp = {{init_lhs, init_rhs}};
using TaggedT = decltype(Tagged<Object>().ptr());
// Map of lhs objects we have already seen to their rhs object on the first
// visit. This is needed to ensure a reasonable runtime for the check.
// Example:
// (array.new $myArray 10 (array.new_default $myArray 10))
// This creates a nested array where each outer array element is the same
// inner array. Without memorizing the inner array, we'd end up performing
// 100+ comparisons.
std::unordered_map<TaggedT, TaggedT> lhs_map;
auto SeenAlready = [&lhs_map](Tagged<Object> lhs, Tagged<Object> rhs) {
auto [iter, inserted] = lhs_map.insert({lhs.ptr(), rhs.ptr()});
if (inserted) return false;
CHECK_EQ(iter->second, rhs.ptr());
return true;
};
auto CheckArray = [&cmp, &SeenAlready](Tagged<Object> lhs,
Tagged<Object> rhs) {
if (SeenAlready(lhs, rhs)) return;
CHECK(IsWasmArray(lhs));
CHECK(IsWasmArray(rhs));
Tagged<WasmArray> lhs_array = Cast<WasmArray>(lhs);
Tagged<WasmArray> rhs_array = Cast<WasmArray>(rhs);
CHECK_EQ(lhs_array->map(), rhs_array->map());
CHECK_EQ(lhs_array->length(), rhs_array->length());
cmp.reserve(cmp.size() + lhs_array->length());
for (uint32_t i = 0; i < lhs_array->length(); ++i) {
cmp.emplace_back(lhs_array->GetElement(i), rhs_array->GetElement(i));
}
};
auto CheckStruct = [&cmp, &SeenAlready](Tagged<Object> lhs,
Tagged<Object> rhs) {
if (SeenAlready(lhs, rhs)) return;
CHECK(IsWasmStruct(lhs));
CHECK(IsWasmStruct(rhs));
Tagged<WasmStruct> lhs_struct = Cast<WasmStruct>(lhs);
Tagged<WasmStruct> rhs_struct = Cast<WasmStruct>(rhs);
CHECK_EQ(lhs_struct->map(), rhs_struct->map());
const CanonicalStructType* type = GetTypeCanonicalizer()->LookupStruct(
lhs_struct->map()->wasm_type_info()->type_index());
uint32_t field_count = type->field_count();
for (uint32_t i = 0; i < field_count; ++i) {
cmp.emplace_back(lhs_struct->GetFieldValue(i),
rhs_struct->GetFieldValue(i));
}
};
// Compare the function result with the global value.
while (!cmp.empty()) {
const auto [lhs, rhs] = cmp.back();
cmp.pop_back();
CHECK_EQ(lhs.type(), rhs.type());
switch (lhs.type().kind()) {
case ValueKind::kF32:
CHECK_FLOAT_EQ(lhs.to_f32(), rhs.to_f32());
break;
case ValueKind::kF64:
CHECK_FLOAT_EQ(lhs.to_f64(), rhs.to_f64());
break;
case ValueKind::kI8:
CHECK_EQ(lhs.to_i8(), rhs.to_i8());
break;
case ValueKind::kI16:
CHECK_EQ(lhs.to_i16(), rhs.to_i16());
break;
case ValueKind::kI32:
CHECK_EQ(lhs.to_i32(), rhs.to_i32());
break;
case ValueKind::kI64:
CHECK_EQ(lhs.to_i64(), rhs.to_i64());
break;
case ValueKind::kS128:
CHECK_EQ(lhs.to_s128(), lhs.to_s128());
break;
case ValueKind::kRef:
case ValueKind::kRefNull: {
Tagged<Object> lhs_ref = *lhs.to_ref();
Tagged<Object> rhs_ref = *rhs.to_ref();
CHECK_EQ(IsNull(lhs_ref), IsNull(rhs_ref));
CHECK_EQ(IsWasmNull(lhs_ref), IsWasmNull(rhs_ref));
switch (lhs.type().heap_representation_non_shared()) {
case HeapType::kFunc:
case HeapType::kI31:
CHECK_EQ(lhs_ref, rhs_ref);
break;
case HeapType::kNoFunc:
case HeapType::kNone:
case HeapType::kNoExn:
CHECK(IsWasmNull(lhs_ref));
CHECK(IsWasmNull(rhs_ref));
break;
case HeapType::kNoExtern:
CHECK(IsNull(lhs_ref));
CHECK(IsNull(rhs_ref));
break;
case HeapType::kExtern:
case HeapType::kAny:
case HeapType::kEq:
case HeapType::kArray:
case HeapType::kStruct:
if (IsNullOrWasmNull(lhs_ref)) break;
if (IsWasmStruct(lhs_ref)) {
CheckStruct(lhs_ref, rhs_ref);
} else if (IsWasmArray(lhs_ref)) {
CheckArray(lhs_ref, rhs_ref);
} else if (IsSmi(lhs_ref)) {
CHECK_EQ(lhs_ref, rhs_ref);
}
break;
default:
CHECK(lhs.type().has_index());
if (IsWasmNull(lhs_ref)) break;
CanonicalTypeIndex type_index = lhs.type().ref_index();
TypeCanonicalizer* types = GetTypeCanonicalizer();
if (types->IsFunctionSignature(type_index)) {
CHECK_EQ(lhs_ref, rhs_ref);
} else if (types->IsStruct(type_index)) {
CheckStruct(lhs_ref, rhs_ref);
} else if (types->IsArray(type_index)) {
CheckArray(lhs_ref, rhs_ref);
} else {
UNIMPLEMENTED();
}
}
break;
}
default:
UNIMPLEMENTED();
}
}
}
void FuzzIt(base::Vector<const uint8_t> data) {
v8_fuzzer::FuzzerSupport* support = v8_fuzzer::FuzzerSupport::Get();
v8::Isolate* isolate = support->GetIsolate();
Isolate* i_isolate = reinterpret_cast<Isolate*>(isolate);
v8::Isolate::Scope isolate_scope(isolate);
AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME);
v8::HandleScope handle_scope(isolate);
v8::Context::Scope context_scope(support->GetContext());
// Disable the optimizing compiler. The init expressions can be huge and might
// produce long compilation times. The function is only used as a reference
// and only run once, so use liftoff only as it allows much faster fuzzing.
v8_flags.liftoff_only = true;
// 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.
EnableExperimentalWasmFeatures(isolate);
v8::TryCatch try_catch(isolate);
HandleScope scope(i_isolate);
// Clear recursive groups: The fuzzer creates random types in every run. These
// are saved as recursive groups as part of the type canonicalizer, but types
// from previous runs just waste memory.
ResetTypeCanonicalizer(isolate, &zone);
size_t expression_count = 0;
base::Vector<const uint8_t> bytes =
GenerateWasmModuleForInitExpressions(&zone, data, &expression_count);
testing::SetupIsolateForWasmModule(i_isolate);
ModuleWireBytes wire_bytes(bytes.begin(), bytes.end());
auto enabled_features = WasmEnabledFeatures::FromIsolate(i_isolate);
bool valid = GetWasmEngine()->SyncValidate(i_isolate, enabled_features,
CompileTimeImportsForFuzzing(),
wire_bytes.module_bytes());
if (v8_flags.wasm_fuzzer_gen_test) {
GenerateTestCase(i_isolate, wire_bytes, valid);
}
CHECK(valid);
FlagScope<bool> eager_compile(&v8_flags.wasm_lazy_compilation, false);
ErrorThrower thrower(i_isolate, "WasmFuzzerSyncCompile");
MaybeDirectHandle<WasmModuleObject> compiled_module =
GetWasmEngine()->SyncCompile(i_isolate, enabled_features,
CompileTimeImportsForFuzzing(), &thrower,
base::OwnedCopyOf(bytes));
CHECK(!compiled_module.is_null());
CHECK(!thrower.error());
thrower.Reset();
CHECK(!i_isolate->has_exception());
DirectHandle<WasmModuleObject> module_object =
compiled_module.ToHandleChecked();
DirectHandle<WasmInstanceObject> instance =
GetWasmEngine()
->SyncInstantiate(i_isolate, &thrower, module_object, {}, {})
.ToHandleChecked();
CHECK_EQ(expression_count,
module_object->native_module()->module()->num_declared_functions);
for (size_t i = 0; i < expression_count; ++i) {
char buffer[22];
snprintf(buffer, sizeof buffer, "f%zu", i);
// Execute corresponding function.
auto function =
Cast<WasmExportedFunction>(GetExport(i_isolate, instance, buffer));
DirectHandle<Object> undefined = i_isolate->factory()->undefined_value();
DirectHandle<Object> function_result =
Execution::Call(i_isolate, function, undefined, {}).ToHandleChecked();
// Get global value.
snprintf(buffer, sizeof buffer, "g%zu", i);
auto global =
Cast<WasmGlobalObject>(GetExport(i_isolate, instance, buffer));
switch (global->type().kind()) {
case ValueKind::kF32: {
float global_val = global->GetF32();
float func_val;
if (IsSmi(*function_result)) {
func_val = Smi::ToInt(*function_result);
} else {
CHECK(IsHeapNumber(*function_result));
func_val = Cast<HeapNumber>(*function_result)->value();
}
CHECK_FLOAT_EQ(func_val, global_val);
break;
}
case ValueKind::kF64: {
double global_val = global->GetF64();
double func_val;
if (IsSmi(*function_result)) {
func_val = Smi::ToInt(*function_result);
} else {
CHECK(IsHeapNumber(*function_result));
func_val = Cast<HeapNumber>(*function_result)->value();
}
CHECK_FLOAT_EQ(func_val, global_val);
break;
}
case ValueKind::kI32: {
int32_t global_val = global->GetI32();
int32_t func_val;
if (IsSmi(*function_result)) {
func_val = Smi::ToInt(*function_result);
} else {
CHECK(IsHeapNumber(*function_result));
func_val = Cast<HeapNumber>(*function_result)->value();
}
CHECK_EQ(func_val, global_val);
break;
}
case ValueKind::kI64: {
int64_t global_val = global->GetI64();
int64_t func_val;
if (IsSmi(*function_result)) {
func_val = Smi::ToInt(*function_result);
} else {
CHECK(IsBigInt(*function_result));
bool lossless;
func_val = Cast<BigInt>(*function_result)->AsInt64(&lossless);
CHECK(lossless);
}
CHECK_EQ(func_val, global_val);
break;
}
case ValueKind::kRef:
case ValueKind::kRefNull: {
// For reference types the expectations are more limited.
// Any struct.new would create a new object, so reference equality
// comparisons will not work.
DirectHandle<Object> global_val = global->GetRef();
CHECK_EQ(IsUndefined(*global_val), IsUndefined(*function_result));
CHECK_EQ(IsNullOrWasmNull(*global_val),
IsNullOrWasmNull(*function_result));
if (!IsNullOrWasmNull(*global_val)) {
if (IsSubtypeOf(global->type(), kWasmFuncRef,
module_object->module())) {
// For any function the global should be an internal function
// whose external function equals the call result. (The call goes
// through JS conversions while the global is accessed directly.)
CHECK(IsWasmFuncRef(*global_val));
CHECK(
WasmExportedFunction::IsWasmExportedFunction(*function_result));
CHECK(*WasmInternalFunction::GetOrCreateExternal(direct_handle(
Cast<WasmFuncRef>(*global_val)->internal(i_isolate),
i_isolate)) == *function_result);
} else {
// On arrays and structs, perform a deep comparison.
DisallowGarbageCollection no_gc;
WasmValue global_value =
instance->trusted_data(i_isolate)->GetGlobalValue(
i_isolate, instance->module()->globals[i]);
WasmValue func_value(function_result, global_value.type());
CheckEquivalent(global_value, func_value, *module_object->module());
}
}
break;
}
default:
UNIMPLEMENTED();
}
}
}
} // anonymous namespace
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
FuzzIt({data, size});
return 0;
}
} // namespace v8::internal::wasm::fuzzing

View File

@ -0,0 +1,3 @@
choongwoo.han@microsoft.com
paolosev@microsoft.com
sethb@microsoft.com

View File

@ -0,0 +1,71 @@
// Copyright 2024 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 "include/v8-context.h"
#include "include/v8-isolate.h"
#include "src/execution/isolate-inl.h"
#include "src/wasm/fuzzing/random-module-generation.h"
#include "src/wasm/wasm-engine.h"
#include "src/wasm/wasm-module-builder.h"
#include "src/zone/zone.h"
#include "test/common/wasm/wasm-module-runner.h"
#include "test/fuzzer/fuzzer-support.h"
#include "test/fuzzer/wasm/fuzzer-common.h"
#include "test/fuzzer/wasm/interpreter/interpreter-fuzzer-common.h"
namespace v8::internal::wasm::fuzzing {
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
auto rnd_gen = MersenneTwister(reinterpret_cast<const char*>(data), size);
InitializeDrumbrakeForFuzzing();
v8_fuzzer::FuzzerSupport* support = v8_fuzzer::FuzzerSupport::Get();
v8::Isolate* isolate = support->GetIsolate();
v8::internal::Isolate* i_isolate = reinterpret_cast<Isolate*>(isolate);
// Clear any pending exceptions from a prior run.
if (i_isolate->has_exception()) {
i_isolate->clear_exception();
}
v8::Isolate::Scope isolate_scope(isolate);
v8::HandleScope handle_scope(isolate);
v8::Context::Scope context_scope(support->GetContext());
// Drumbrake may not support some experimental features yet.
// EnableExperimentalWasmFeatures(isolate);
v8::TryCatch try_catch(isolate);
testing::SetupIsolateForWasmModule(i_isolate);
ModuleWireBytes wire_bytes(data, data + size);
HandleScope scope(i_isolate);
ErrorThrower thrower(i_isolate, "wasm fuzzer");
DirectHandle<i::WasmModuleObject> module_object;
auto enabled_features = WasmEnabledFeatures::FromIsolate(i_isolate);
bool compiles =
GetWasmEngine()
->SyncCompile(i_isolate, enabled_features, CompileTimeImports{},
&thrower,
v8::base::OwnedCopyOf(wire_bytes.module_bytes()))
.ToHandle(&module_object);
// TODO(338326645): Add similar GenerateTestCase code for wasm fast
// interpreter.
// if (v8_flags.wasm_fuzzer_gen_test) {
// GenerateTestCase(i_isolate, wire_bytes, compiles);
// }
if (compiles) {
FastInterpretAndExecuteModule(i_isolate, module_object, rnd_gen);
}
thrower.Reset();
// Pump the message loop and run micro tasks, e.g. GC finalization tasks.
support->PumpMessageLoop(v8::platform::MessageLoopBehavior::kDoNotWait);
isolate->PerformMicrotaskCheckpoint();
return 0;
}
} // namespace v8::internal::wasm::fuzzing

View File

@ -0,0 +1,28 @@
// Copyright 2024 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 "src/wasm/fuzzing/random-module-generation.h"
#include "src/wasm/wasm-module-builder.h"
#include "src/zone/zone.h"
#include "test/fuzzer/wasm/interpreter/interpreter-fuzzer-common.h"
namespace v8::internal::wasm::fuzzing {
// Fuzzer that may generate WasmGC+SIMD expressions.
bool GenerateModule(i::Isolate* isolate, i::Zone* zone,
v8::base::Vector<const uint8_t> data,
i::wasm::ZoneBuffer* buffer) {
v8::base::Vector<const uint8_t> wire_bytes =
fuzzing::GenerateRandomWasmModule(
zone, fuzzing::WasmModuleGenerationOptions::All(), data);
if (wire_bytes.empty()) return false;
buffer->write(wire_bytes.data(), wire_bytes.size());
return true;
}
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
return LLVMFuzzerTestOneInputCommon(data, size, GenerateModule);
}
} // namespace v8::internal::wasm::fuzzing

View File

@ -0,0 +1,27 @@
// Copyright 2024 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 "src/wasm/fuzzing/random-module-generation.h"
#include "src/wasm/wasm-module-builder.h"
#include "src/zone/zone.h"
#include "test/fuzzer/wasm/interpreter/interpreter-fuzzer-common.h"
namespace v8::internal::wasm::fuzzing {
bool GenerateModule(i::Isolate* isolate, i::Zone* zone,
v8::base::Vector<const uint8_t> data,
i::wasm::ZoneBuffer* buffer) {
v8::base::Vector<const uint8_t> wire_bytes =
fuzzing::GenerateRandomWasmModule(
zone, fuzzing::WasmModuleGenerationOptions::MVP(), data);
if (wire_bytes.empty()) return false;
buffer->write(wire_bytes.data(), wire_bytes.size());
return true;
}
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
return LLVMFuzzerTestOneInputCommon(data, size, GenerateModule);
}
} // namespace v8::internal::wasm::fuzzing

View File

@ -0,0 +1,31 @@
// Copyright 2024 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 "src/wasm/fuzzing/random-module-generation.h"
#include "src/wasm/wasm-module-builder.h"
#include "src/zone/zone.h"
#include "test/fuzzer/wasm/interpreter/interpreter-fuzzer-common.h"
namespace v8::internal::wasm::fuzzing {
bool GenerateModule(Isolate* isolate, Zone* zone,
base::Vector<const uint8_t> data, ZoneBuffer* buffer) {
ValueType kIntTypes4[4];
FunctionSig sig_i_iii{1, 3, kIntTypes4};
WasmModuleBuilder builder(zone);
WasmFunctionBuilder* f = builder.AddFunction(&sig_i_iii);
f->EmitCode(data.begin(), static_cast<uint32_t>(data.size()));
f->Emit(kExprEnd);
builder.AddExport(base::CStrVector("main"), f);
builder.AddMemory(0, 32);
builder.WriteTo(buffer);
return true;
}
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
return LLVMFuzzerTestOneInputCommon(data, size, GenerateModule);
}
} // namespace v8::internal::wasm::fuzzing

View File

@ -0,0 +1,89 @@
// Copyright 2024 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 <limits.h>
#include <stddef.h>
#include <stdint.h>
#include "include/libplatform/libplatform.h"
#include "include/v8-context.h"
#include "include/v8-exception.h"
#include "include/v8-isolate.h"
#include "include/v8-local-handle.h"
#include "src/execution/isolate-inl.h"
#include "src/wasm/wasm-engine.h"
#include "src/wasm/wasm-feature-flags.h"
#include "src/wasm/wasm-module.h"
#include "test/common/wasm/wasm-module-runner.h"
#include "test/fuzzer/fuzzer-support.h"
#include "test/fuzzer/wasm/fuzzer-common.h"
#include "test/fuzzer/wasm/interpreter/interpreter-fuzzer-common.h"
namespace v8::internal::wasm::fuzzing {
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
v8_fuzzer::FuzzerSupport* support = v8_fuzzer::FuzzerSupport::Get();
v8::Isolate* isolate = support->GetIsolate();
// We reduce the maximum memory size and table size of WebAssembly instances
// to avoid OOMs in the fuzzer.
v8_flags.wasm_max_mem_pages = 32;
v8_flags.wasm_max_table_size = 100;
// We need to reset jitless flags set by a previous run.
v8::internal::v8_flags.jitless = false;
v8::internal::v8_flags.wasm_jitless = false;
FlagList::EnforceFlagImplications();
Isolate* i_isolate = reinterpret_cast<Isolate*>(isolate);
// We also need to clear JsToWasm wrappers from a previous run.
ClearJsToWasmWrappersForTesting(i_isolate);
// Clear any pending exceptions from a prior run.
if (i_isolate->has_exception()) {
i_isolate->clear_exception();
}
v8::Isolate::Scope isolate_scope(isolate);
v8::HandleScope handle_scope(isolate);
v8::Context::Scope context_scope(support->GetContext());
// Drumbrake may not support some experimental features yet.
// EnableExperimentalWasmFeatures(isolate);
v8::TryCatch try_catch(isolate);
testing::SetupIsolateForWasmModule(i_isolate);
ModuleWireBytes wire_bytes(data, data + size);
HandleScope scope(i_isolate);
ErrorThrower thrower(i_isolate, "wasm interpreter diff fuzzer");
DirectHandle<WasmModuleObject> module_object;
auto enabled_features = WasmEnabledFeatures::FromIsolate(i_isolate);
bool compiles =
GetWasmEngine()
->SyncCompile(i_isolate, enabled_features,
fuzzing::CompileTimeImportsForFuzzing(), &thrower,
v8::base::OwnedCopyOf(wire_bytes.module_bytes()))
.ToHandle(&module_object);
if (v8_flags.wasm_fuzzer_gen_test) {
fuzzing::GenerateTestCase(i_isolate, wire_bytes, compiles);
}
int module_result = -1;
if (compiles) {
module_result = fuzzing::ExecuteAgainstReference(
i_isolate, module_object,
fuzzing::kDefaultMaxFuzzerExecutedInstructions,
/* edge_is_wasm_jitless */ true);
}
// Pump the message loop and run micro tasks, e.g. GC finalization tasks.
support->PumpMessageLoop(v8::platform::MessageLoopBehavior::kDoNotWait);
isolate->PerformMicrotaskCheckpoint();
return module_result;
}
} // namespace v8::internal::wasm::fuzzing

View File

@ -0,0 +1,519 @@
// Copyright 2024 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/interpreter/interpreter-fuzzer-common.h"
#include <ctime>
#include "include/v8-context.h"
#include "include/v8-exception.h"
#include "include/v8-function.h"
#include "include/v8-isolate.h"
#include "include/v8-local-handle.h"
#include "include/v8-metrics.h"
#include "src/api/api-inl.h"
#include "src/execution/isolate.h"
#include "src/objects/objects-inl.h"
#include "src/objects/property-descriptor.h"
#include "src/utils/ostreams.h"
#include "src/wasm/baseline/liftoff-compiler.h"
#include "src/wasm/function-body-decoder-impl.h"
#include "src/wasm/interpreter/wasm-interpreter-runtime.h"
#include "src/wasm/interpreter/wasm-interpreter.h"
#include "src/wasm/module-instantiate.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/wasm/wasm-opcodes-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"
#include "test/fuzzer/wasm/fuzzer-common.h"
namespace v8::internal::wasm::fuzzing {
std::mt19937_64 MersenneTwister(const char* data, size_t size) {
std::mt19937_64 rng;
std::string str = std::string(data, size);
std::size_t hash = std::hash<std::string>()(str);
rng.seed(hash);
return rng;
}
class WasmInterpretationResult {
public:
static WasmInterpretationResult Failed() { return {kFailed, 0, false}; }
static WasmInterpretationResult Trapped(bool possible_nondeterminism) {
return {kTrapped, 0, possible_nondeterminism};
}
static WasmInterpretationResult Finished(int32_t result,
bool possible_nondeterminism) {
return {kFinished, result, possible_nondeterminism};
}
// {failed()} captures different reasons: The module was invalid, no function
// to call was found in the module, the function did not terminate within a
// limited number of steps, or a stack overflow happened.
bool failed() const { return status_ == kFailed; }
bool trapped() const { return status_ == kTrapped; }
bool finished() const { return status_ == kFinished; }
int32_t result() const {
DCHECK_EQ(status_, kFinished);
return result_;
}
bool possible_nondeterminism() const { return possible_nondeterminism_; }
private:
enum Status { kFinished, kTrapped, kFailed };
const Status status_;
const int32_t result_;
const bool possible_nondeterminism_;
WasmInterpretationResult(Status status, int32_t result,
bool possible_nondeterminism)
: status_(status),
result_(result),
possible_nondeterminism_(possible_nondeterminism) {}
};
// Interprets the given module, starting at the function specified by
// {function_index}. The return type of the function has to be int32. The module
// should not have any imports or exports.
WasmInterpretationResult FastInterpretWasmModule(
Isolate* isolate, DirectHandle<WasmInstanceObject> instance,
int32_t function_index, const std::vector<WasmValue>& args,
std::vector<WasmValue>& rets) {
Zone zone(isolate->allocator(), ZONE_NAME);
v8::internal::HandleScope scope(isolate);
const WasmFunction* func = &instance->module()->functions[function_index];
CHECK(func->exported);
// This would normally be handled by export wrappers.
if (!IsJSCompatibleSignature(
reinterpret_cast<const wasm::CanonicalSig*>(func->sig))) {
return WasmInterpretationResult::Trapped(false);
}
CHECK(v8_flags.wasm_jitless);
isolate->clear_exception();
wasm::WasmInterpreterThread* thread =
wasm::WasmInterpreterThread::GetCurrentInterpreterThread(isolate);
DirectHandle<Tuple2> interpreter_object =
v8::internal::WasmTrustedInstanceData::GetOrCreateInterpreterObject(
instance);
int* thread_in_wasm_code = trap_handler::GetThreadInWasmThreadLocalAddress();
*thread_in_wasm_code = true;
// Assume an instance can run in only one thread.
wasm::InterpreterHandle* handle =
wasm::GetOrCreateInterpreterHandle(isolate, interpreter_object);
for (const WasmValue& arg : args) {
if (arg.type().is_reference()) {
// We should pass WasmNull as null argument, not a JS null value.
CHECK(!IsNull(*arg.to_ref(), isolate));
}
}
bool success = handle->wasm::InterpreterHandle::Execute(
thread, 0, static_cast<uint32_t>(function_index), args, rets);
*thread_in_wasm_code = false;
// Returned values should not be the hole value.
if (success) {
for (auto& wasm_value : rets) {
if (wasm_value.type().is_reference())
CHECK(!IsTheHole(*wasm_value.to_ref()));
}
}
return success ? WasmInterpretationResult::Finished(0, false)
: WasmInterpretationResult::Failed();
}
Handle<JSObject> GetOrCreateModuleNamespaceObject(Isolate* isolate,
Handle<JSObject> ffi_object,
Handle<String> module_name) {
PropertyDescriptor desc;
Maybe<bool> property_found = JSReceiver::GetOwnPropertyDescriptor(
isolate, ffi_object, module_name, &desc);
if (property_found.FromMaybe(false)) {
return Cast<JSObject>(desc.value());
}
Handle<JSObject> module_namespace =
isolate->factory()->NewJSObject(isolate->object_function());
JSObject::DefinePropertyOrElementIgnoreAttributes(ffi_object, module_name,
module_namespace)
.Assert();
return module_namespace;
}
Handle<JSFunction> GenerateJSFunction(Isolate* isolate) {
v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate);
v8::Local<v8::String> fn_body =
v8::String::NewFromUtf8(v8_isolate, "return arguments[1] + 0x414141;")
.ToLocalChecked();
v8::ScriptCompiler::Source script_source(fn_body);
v8::Local<v8::Function> function =
v8::ScriptCompiler::CompileFunction(v8_isolate->GetCurrentContext(),
&script_source)
.ToLocalChecked();
return Cast<JSFunction>(
v8::Utils::OpenHandle(*v8::Local<v8::Function>::Cast(function)));
}
MaybeDirectHandle<WasmTableObject> GenerateWasmTable(
Isolate* isolate, DirectHandle<WasmModuleObject> module_object,
uint32_t table_index) {
const WasmTable& table = module_object->module()->tables[table_index];
uint32_t table_initial = 10;
uint32_t table_maximum = 30;
bool has_maximum = true;
if (table.type == wasm::kWasmAnyRef) {
DirectHandle<WasmTableObject> table_obj = WasmTableObject::New(
isolate, Handle<WasmTrustedInstanceData>(), wasm::kWasmAnyRef,
wasm::CanonicalValueType{wasm::kWasmAnyRef}, table_initial, has_maximum,
table_maximum, isolate->factory()->undefined_value(),
wasm::AddressType::kI32);
// Anyref (externref) Table
Handle<FixedArray> externref_backing_store{table_obj->entries(), isolate};
externref_backing_store->set(0, Smi::FromInt(0x414141));
externref_backing_store->set(
1, *isolate->factory()->NewJSObject(isolate->object_function()));
return table_obj;
} else if (table.type == wasm::kWasmFuncRef) {
return WasmTableObject::New(
isolate, Handle<WasmTrustedInstanceData>(), wasm::kWasmFuncRef,
wasm::CanonicalValueType{wasm::kWasmFuncRef}, table_initial,
has_maximum, table_maximum, isolate->factory()->null_value(),
wasm::AddressType::kI32);
}
// Not supported type
return {};
}
Handle<String> ExtractUtf8StringFromModuleBytes(
Isolate* isolate, base::Vector<const uint8_t> wire_bytes,
wasm::WireBytesRef ref) {
base::Vector<const uint8_t> name_vec =
wire_bytes.SubVector(ref.offset(), ref.end_offset());
return isolate->factory()->InternalizeUtf8String(
base::Vector<const char>::cast(name_vec));
}
DirectHandle<JSObject> CreateImportObject(
Isolate* isolate, DirectHandle<WasmModuleObject> module_object) {
Handle<JSObject> ffi_object =
isolate->factory()->NewJSObject(isolate->object_function());
base::Vector<const uint8_t> wire_bytes =
module_object->native_module()->wire_bytes();
for (size_t index = 0; index < module_object->module()->import_table.size();
++index) {
const WasmImport& import = module_object->module()->import_table[index];
Handle<String> module_name = ExtractUtf8StringFromModuleBytes(
isolate, wire_bytes, import.module_name);
Handle<String> field_name = ExtractUtf8StringFromModuleBytes(
isolate, wire_bytes, import.field_name);
Handle<JSObject> module_namespace =
GetOrCreateModuleNamespaceObject(isolate, ffi_object, module_name);
switch (import.kind) {
case kExternalFunction: {
// TODO: Support other types of external functions such as wasm?
// It currently supports only external JS function.
Handle<JSFunction> fn_obj = GenerateJSFunction(isolate);
JSObject::DefinePropertyOrElementIgnoreAttributes(module_namespace,
field_name, fn_obj)
.Assert();
break;
}
case kExternalTable: {
uint32_t table_index = import.index;
DirectHandle<WasmTableObject> table_obj;
if (GenerateWasmTable(isolate, module_object, table_index)
.ToHandle(&table_obj)) {
JSObject::DefinePropertyOrElementIgnoreAttributes(
module_namespace, field_name, table_obj)
.Assert();
}
break;
}
case kExternalMemory: {
// Memory
SharedFlag shared = SharedFlag::kNotShared;
int memory_initial = 1;
int memory_maximum = 32;
DirectHandle<WasmMemoryObject> memory_obj;
if (WasmMemoryObject::New(isolate, memory_initial, memory_maximum,
shared, wasm::AddressType::kI32)
.ToHandle(&memory_obj)) {
JSObject::DefinePropertyOrElementIgnoreAttributes(
module_namespace, field_name, memory_obj)
.Assert();
}
break;
}
case kExternalGlobal: {
// Global
const uint32_t offset = 0;
const WasmGlobal& global =
module_object->module()->globals[import.index];
DirectHandle<WasmTrustedInstanceData> trusted_data =
WasmTrustedInstanceData::New(isolate, module_object, false);
MaybeDirectHandle<WasmGlobalObject> maybe_global_obj =
WasmGlobalObject::New(isolate, trusted_data,
MaybeHandle<JSArrayBuffer>(),
MaybeHandle<FixedArray>(), global.type,
offset, global.mutability);
DirectHandle<WasmGlobalObject> global_obj;
if (maybe_global_obj.ToHandle(&global_obj)) {
JSObject::DefinePropertyOrElementIgnoreAttributes(
module_namespace, field_name, global_obj)
.Assert();
}
break;
}
case kExternalTag: {
// Not supported yet.
break;
}
default:
UNREACHABLE();
}
}
return ffi_object;
}
bool InstantiateModule(Isolate* isolate,
DirectHandle<WasmModuleObject> module_object,
DirectHandle<internal::JSObject> imports_obj,
DirectHandle<WasmInstanceObject>* instance) {
ErrorThrower thrower(isolate, "WebAssembly Instantiation");
if (!GetWasmEngine()
->SyncInstantiate(isolate, &thrower, module_object, imports_obj, {})
.ToHandle(instance)) {
isolate->clear_exception();
thrower.Reset(); // Ignore errors.
return false;
}
return true;
}
// Generate an array of default arguments for the given signature, to be used in
// the interpreter.
std::vector<WasmValue> FastMakeDefaultInterpreterArguments(
Isolate* isolate, const WasmModule* module, const FunctionSig* sig,
std::mt19937_64 mt_generator_64) {
size_t param_count = sig->parameter_count();
std::vector<WasmValue> arguments(param_count);
int64_t rand_num = 0;
for (size_t i = 0; i < param_count; ++i) {
rand_num = mt_generator_64();
switch (sig->GetParam(i).kind()) {
case kI32:
arguments[i] = WasmValue(static_cast<int32_t>(rand_num));
break;
case kI64:
arguments[i] = WasmValue(rand_num);
break;
case kF32:
arguments[i] = WasmValue(static_cast<float>(rand_num));
break;
case kF64:
arguments[i] = WasmValue(static_cast<double>(rand_num));
break;
case kRefNull: {
ValueType type = sig->GetParam(i);
wasm::CanonicalValueType canonical_type = wasm::kWasmBottom;
if (type.has_index()) {
canonical_type =
type.Canonicalize(module->canonical_type_id(type.ref_index()));
} else {
canonical_type = CanonicalValueType{type};
}
if (type.heap_representation() == HeapType::kExtern) {
arguments[i] = WasmValue(
Cast<Object>(isolate->factory()->NewHeapNumber(rand_num + 0.125)),
canonical_type);
} else {
arguments[i] =
WasmValue(isolate->factory()->wasm_null(), canonical_type);
}
break;
}
case kS128: {
int64_t rand_num2 = mt_generator_64();
const int64_t simd_value[2] = {rand_num, rand_num2};
arguments[i] = WasmValue(reinterpret_cast<const uint8_t*>(simd_value),
(CanonicalValueType)sig->GetParam(i));
break;
}
case kRef:
case kI8:
case kI16:
case kF16:
case kVoid:
case kTop:
case kBottom:
UNREACHABLE();
}
}
return arguments;
}
void RunInstance(Isolate* isolate, std::mt19937_64 rand_generator,
DirectHandle<WasmModuleObject> module,
DirectHandle<WasmInstanceObject> instance) {
size_t num_exports = instance->module()->export_table.size();
for (size_t i = 0; i < num_exports; ++i) {
WasmExport exp = instance->module()->export_table[i];
if (exp.kind != kExternalFunction) continue;
WasmFunction wfn = instance->module()->functions[exp.index];
std::vector<WasmValue> arguments = FastMakeDefaultInterpreterArguments(
isolate, instance->module(), wfn.sig, rand_generator);
std::vector<WasmValue> retvals(wfn.sig->return_count());
FastInterpretWasmModule(isolate, instance, wfn.func_index, arguments,
retvals);
isolate->clear_exception();
} // end handle loop
}
int FastInterpretAndExecuteModule(Isolate* isolate,
DirectHandle<WasmModuleObject> module_object,
std::mt19937_64 rand_generator) {
// 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 -1;
HandleScope handle_scope(isolate); // Avoid leaking handles.
DirectHandle<JSObject> imports_obj =
CreateImportObject(isolate, module_object);
DirectHandle<WasmInstanceObject> other_instance;
if (!InstantiateModule(isolate, module_object, imports_obj, &other_instance))
return -1;
DirectHandle<WasmInstanceObject> instance;
if (!InstantiateModule(isolate, module_object, imports_obj, &instance))
return -1;
RunInstance(isolate, rand_generator, module_object, other_instance);
RunInstance(isolate, rand_generator, module_object, instance);
return 0;
}
void InitializeDrumbrakeForFuzzing() {
// We should always pass jitless for wasm-jitless.
v8_flags.jitless = true;
v8_flags.wasm_jitless = true;
// This configuration will make fuzzing slower, but helps to reproduce GC bugs
// more reliably.
v8_flags.stress_compaction = true;
v8_flags.gc_interval = 500;
// Avoid restarting the fuzzer process due to timeout when there is an
// infinite loop.
v8_flags.drumbrake_fuzzer_timeout = true;
// We reduce the maximum memory size and table size of WebAssembly instances
// to avoid OOMs in the fuzzer.
v8_flags.wasm_max_mem_pages = 32;
v8_flags.wasm_max_table_size = 100;
FlagList::EnforceFlagImplications();
WasmInterpreterThread::Initialize();
}
int LLVMFuzzerTestOneInputCommon(const uint8_t* data, size_t size,
GenerateModuleFunc generate_module) {
auto rnd_gen = MersenneTwister(reinterpret_cast<const char*>(data), size);
wasm::fuzzing::InitializeDrumbrakeForFuzzing();
v8_fuzzer::FuzzerSupport* support = v8_fuzzer::FuzzerSupport::Get();
v8::Isolate* isolate = support->GetIsolate();
Isolate* i_isolate = reinterpret_cast<Isolate*>(isolate);
// Clear any pending exceptions from a prior run.
if (i_isolate->has_exception()) {
i_isolate->clear_exception();
}
v8::Isolate::Scope isolate_scope(isolate);
v8::HandleScope handle_scope(isolate);
v8::Context::Scope context_scope(support->GetContext());
// Drumbrake may not support some experimental features yet.
// wasm::fuzzing::EnableExperimentalWasmFeatures(isolate);
v8::TryCatch try_catch(isolate);
testing::SetupIsolateForWasmModule(i_isolate);
AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME);
wasm::ZoneBuffer buffer(&zone);
if (!generate_module(i_isolate, &zone, {data, size}, &buffer)) {
return 0;
}
wasm::ModuleWireBytes wire_bytes(buffer.begin(), buffer.end());
HandleScope scope(i_isolate);
wasm::ErrorThrower thrower(i_isolate, "wasm fuzzer");
DirectHandle<WasmModuleObject> module_object;
auto enabled_features = wasm::WasmEnabledFeatures::FromIsolate(i_isolate);
bool compiles = wasm::GetWasmEngine()
->SyncCompile(i_isolate, enabled_features,
fuzzing::CompileTimeImportsForFuzzing(),
&thrower, v8::base::OwnedCopyOf(buffer))
.ToHandle(&module_object);
// TODO(338326645): add similar GenerateTestCase code for wasm fast
// interpreter.
// if (v8_flags.wasm_fuzzer_gen_test) {
// wasm::fuzzing::GenerateTestCase(i_isolate, wire_bytes, compiles);
// }
int module_result = -1;
if (compiles) {
module_result =
FastInterpretAndExecuteModule(i_isolate, module_object, rnd_gen);
}
thrower.Reset();
// Pump the message loop and run micro tasks, e.g. GC finalization tasks.
support->PumpMessageLoop(v8::platform::MessageLoopBehavior::kDoNotWait);
isolate->PerformMicrotaskCheckpoint();
return module_result;
}
} // namespace v8::internal::wasm::fuzzing

View File

@ -0,0 +1,38 @@
// Copyright 2024 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.
#ifndef WASM_FAST_INTERPRETER_FUZZER_COMMON_H_
#define WASM_FAST_INTERPRETER_FUZZER_COMMON_H_
#include <stddef.h>
#include <stdint.h>
#include <memory>
#include <random>
#include "src/wasm/module-decoder.h"
#include "src/wasm/wasm-module-builder.h"
namespace v8::internal::wasm::fuzzing {
std::mt19937_64 MersenneTwister(const char* data, size_t size);
// Instantiates and interprets exported functions within module_object if
// possible
int FastInterpretAndExecuteModule(Isolate* isolate,
DirectHandle<WasmModuleObject> module_object,
std::mt19937_64 g);
void InitializeDrumbrakeForFuzzing();
typedef std::function<bool(Isolate*, Zone*, v8::base::Vector<const uint8_t>,
ZoneBuffer*)>
GenerateModuleFunc;
int LLVMFuzzerTestOneInputCommon(const uint8_t* data, size_t size,
GenerateModuleFunc generate_module);
} // namespace v8::internal::wasm::fuzzing
#endif // WASM_FAST_INTERPRETER_FUZZER_COMMON_H_

View File

@ -0,0 +1,382 @@
// Copyright 2024 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 "include/v8-context.h"
#include "include/v8-exception.h"
#include "include/v8-isolate.h"
#include "include/v8-local-handle.h"
#include "src/base/vector.h"
#include "src/execution/isolate.h"
#include "src/objects/property-descriptor.h"
#include "src/wasm/compilation-environment-inl.h"
#include "src/wasm/fuzzing/random-module-generation.h"
#include "src/wasm/wasm-engine.h"
#include "src/wasm/wasm-feature-flags.h"
#include "src/wasm/wasm-module.h"
#include "src/wasm/wasm-objects-inl.h"
#include "src/wasm/wasm-subtyping.h"
#include "src/zone/accounting-allocator.h"
#include "src/zone/zone.h"
#include "test/common/flag-utils.h"
#include "test/common/wasm/wasm-module-runner.h"
#include "test/fuzzer/fuzzer-support.h"
#include "test/fuzzer/wasm/fuzzer-common.h"
#include "test/fuzzer/wasm/interpreter/interpreter-fuzzer-common.h"
namespace fuzzing = v8::internal::wasm::fuzzing;
// This fuzzer fuzzes initializer expressions used e.g. in globals.
// The fuzzer creates a set of globals with initializer expressions and a set of
// functions containing the same body as these initializer expressions.
// The global value should be equal to the result of running the corresponding
// function.
namespace v8::internal::wasm::fuzzing {
#define CHECK_FLOAT_EQ(expected, actual) \
if (std::isnan(expected)) { \
CHECK(std::isnan(actual)); \
} else { \
CHECK_EQ(expected, actual); \
CHECK_EQ(std::signbit(expected), std::signbit(actual)); \
}
namespace {
bool IsNullOrWasmNull(Tagged<Object> obj) {
return IsNull(obj) || IsWasmNull(obj);
}
DirectHandle<Object> GetExport(Isolate* isolate,
DirectHandle<WasmInstanceObject> instance,
const char* name) {
DirectHandle<JSObject> exports_object;
DirectHandle<Name> exports =
isolate->factory()->InternalizeUtf8String("exports");
exports_object = Cast<JSObject>(
JSObject::GetProperty(isolate, instance, exports).ToHandleChecked());
DirectHandle<Name> main_name =
isolate->factory()->NewStringFromAsciiChecked(name);
PropertyDescriptor desc;
Maybe<bool> property_found = JSReceiver::GetOwnPropertyDescriptor(
isolate, exports_object, main_name, &desc);
CHECK(property_found.FromMaybe(false));
return desc.value();
}
void CheckEquivalent(const WasmValue& init_lhs, const WasmValue& init_rhs,
const WasmModule& module) {
DisallowGarbageCollection no_gc;
// Stack of elements to be checked.
std::vector<std::pair<WasmValue, WasmValue>> cmp = {{init_lhs, init_rhs}};
using TaggedT = decltype(Tagged<Object>().ptr());
// Map of lhs objects we have already seen to their rhs object on the first
// visit. This is needed to ensure a reasonable runtime for the check.
// Example:
// (array.new $myArray 10 (array.new_default $myArray 10))
// This creates a nested array where each outer array element is the same
// inner array. Without memorizing the inner array, we'd end up performing
// 100+ comparisons.
std::unordered_map<TaggedT, TaggedT> lhs_map;
auto SeenAlready = [&lhs_map](Tagged<Object> lhs, Tagged<Object> rhs) {
auto [iter, inserted] = lhs_map.insert({lhs.ptr(), rhs.ptr()});
if (inserted) return false;
CHECK_EQ(iter->second, rhs.ptr());
return true;
};
auto CheckArray = [&cmp, &SeenAlready](Tagged<Object> lhs,
Tagged<Object> rhs) {
if (SeenAlready(lhs, rhs)) return;
CHECK(IsWasmArray(lhs));
CHECK(IsWasmArray(rhs));
Tagged<WasmArray> lhs_array = Cast<WasmArray>(lhs);
Tagged<WasmArray> rhs_array = Cast<WasmArray>(rhs);
CHECK_EQ(lhs_array->map(), rhs_array->map());
CHECK_EQ(lhs_array->length(), rhs_array->length());
cmp.reserve(cmp.size() + lhs_array->length());
for (uint32_t i = 0; i < lhs_array->length(); ++i) {
cmp.emplace_back(lhs_array->GetElement(i), rhs_array->GetElement(i));
}
};
auto CheckStruct = [&cmp, &SeenAlready](Tagged<Object> lhs,
Tagged<Object> rhs) {
if (SeenAlready(lhs, rhs)) return;
CHECK(IsWasmStruct(lhs));
CHECK(IsWasmStruct(rhs));
Tagged<WasmStruct> lhs_struct = Cast<WasmStruct>(lhs);
Tagged<WasmStruct> rhs_struct = Cast<WasmStruct>(rhs);
CHECK_EQ(lhs_struct->map(), rhs_struct->map());
const CanonicalStructType* type = GetTypeCanonicalizer()->LookupStruct(
lhs_struct->map()->wasm_type_info()->type_index());
uint32_t field_count = type->field_count();
for (uint32_t i = 0; i < field_count; ++i) {
cmp.emplace_back(lhs_struct->GetFieldValue(i),
rhs_struct->GetFieldValue(i));
}
};
// Compare the function result with the global value.
while (!cmp.empty()) {
const auto [lhs, rhs] = cmp.back();
cmp.pop_back();
CHECK_EQ(lhs.type(), rhs.type());
switch (lhs.type().kind()) {
case ValueKind::kF32:
CHECK_FLOAT_EQ(lhs.to_f32(), rhs.to_f32());
break;
case ValueKind::kF64:
CHECK_FLOAT_EQ(lhs.to_f64(), rhs.to_f64());
break;
case ValueKind::kI8:
CHECK_EQ(lhs.to_i8(), rhs.to_i8());
break;
case ValueKind::kI16:
CHECK_EQ(lhs.to_i16(), rhs.to_i16());
break;
case ValueKind::kI32:
CHECK_EQ(lhs.to_i32(), rhs.to_i32());
break;
case ValueKind::kI64:
CHECK_EQ(lhs.to_i64(), rhs.to_i64());
break;
case ValueKind::kS128:
CHECK_EQ(lhs.to_s128(), lhs.to_s128());
break;
case ValueKind::kRef:
case ValueKind::kRefNull: {
Tagged<Object> lhs_ref = *lhs.to_ref();
Tagged<Object> rhs_ref = *rhs.to_ref();
CHECK_EQ(IsNull(lhs_ref), IsNull(rhs_ref));
CHECK_EQ(IsWasmNull(lhs_ref), IsWasmNull(rhs_ref));
switch (lhs.type().heap_representation_non_shared()) {
case HeapType::kFunc:
case HeapType::kI31:
CHECK_EQ(lhs_ref, rhs_ref);
break;
case HeapType::kNoFunc:
case HeapType::kNone:
case HeapType::kNoExn:
CHECK(IsWasmNull(lhs_ref));
CHECK(IsWasmNull(rhs_ref));
break;
case HeapType::kNoExtern:
CHECK(IsNull(lhs_ref));
CHECK(IsNull(rhs_ref));
break;
case HeapType::kExtern:
case HeapType::kAny:
case HeapType::kEq:
case HeapType::kArray:
case HeapType::kStruct:
if (IsNullOrWasmNull(lhs_ref)) break;
if (IsWasmStruct(lhs_ref)) {
CheckStruct(lhs_ref, rhs_ref);
} else if (IsWasmArray(lhs_ref)) {
CheckArray(lhs_ref, rhs_ref);
} else if (IsSmi(lhs_ref)) {
CHECK_EQ(lhs_ref, rhs_ref);
}
break;
default:
CHECK(lhs.type().has_index());
if (IsWasmNull(lhs_ref)) break;
CanonicalTypeIndex type_index = lhs.type().ref_index();
TypeCanonicalizer* types = GetTypeCanonicalizer();
if (types->IsFunctionSignature(type_index)) {
CHECK_EQ(lhs_ref, rhs_ref);
} else if (types->IsStruct(type_index)) {
CheckStruct(lhs_ref, rhs_ref);
} else if (types->IsArray(type_index)) {
CheckArray(lhs_ref, rhs_ref);
} else {
UNIMPLEMENTED();
}
}
break;
}
default:
UNIMPLEMENTED();
}
}
}
// Forked from test\fuzzer\wasm\init-expr.cc to work with the Wasm interpreter.
// TODO(choongwoo.han@microsoft.com): Deduplicate code.
void FuzzIt(base::Vector<const uint8_t> data) {
i::wasm::fuzzing::InitializeDrumbrakeForFuzzing();
v8_fuzzer::FuzzerSupport* support = v8_fuzzer::FuzzerSupport::Get();
v8::Isolate* isolate = support->GetIsolate();
Isolate* i_isolate = reinterpret_cast<Isolate*>(isolate);
v8::Isolate::Scope isolate_scope(isolate);
AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME);
v8::HandleScope handle_scope(isolate);
v8::Context::Scope context_scope(support->GetContext());
// 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.
EnableExperimentalWasmFeatures(isolate);
v8::TryCatch try_catch(isolate);
HandleScope scope(i_isolate);
// Clear recursive groups: The fuzzer creates random types in every run. These
// are saved as recursive groups as part of the type canonicalizer, but types
// from previous runs just waste memory.
ResetTypeCanonicalizer(isolate, &zone);
size_t expression_count = 0;
base::Vector<const uint8_t> bytes =
GenerateWasmModuleForInitExpressions(&zone, data, &expression_count);
testing::SetupIsolateForWasmModule(i_isolate);
ModuleWireBytes wire_bytes(bytes.begin(), bytes.end());
auto enabled_features = WasmEnabledFeatures::FromIsolate(i_isolate);
bool valid = GetWasmEngine()->SyncValidate(i_isolate, enabled_features,
CompileTimeImportsForFuzzing(),
wire_bytes.module_bytes());
if (v8_flags.wasm_fuzzer_gen_test) {
GenerateTestCase(i_isolate, wire_bytes, valid);
}
CHECK(valid);
FlagScope<bool> eager_compile(&v8_flags.wasm_lazy_compilation, false);
ErrorThrower thrower(i_isolate, "WasmFuzzerSyncCompile");
MaybeDirectHandle<WasmModuleObject> compiled_module =
GetWasmEngine()->SyncCompile(i_isolate, enabled_features,
CompileTimeImportsForFuzzing(), &thrower,
base::OwnedCopyOf(bytes));
CHECK(!compiled_module.is_null());
CHECK(!thrower.error());
thrower.Reset();
CHECK(!i_isolate->has_exception());
DirectHandle<WasmModuleObject> module_object =
compiled_module.ToHandleChecked();
DirectHandle<WasmInstanceObject> instance =
GetWasmEngine()
->SyncInstantiate(i_isolate, &thrower, module_object, {}, {})
.ToHandleChecked();
CHECK_EQ(expression_count,
module_object->native_module()->module()->num_declared_functions);
for (size_t i = 0; i < expression_count; ++i) {
char buffer[22];
snprintf(buffer, sizeof buffer, "f%zu", i);
// Execute corresponding function.
auto function =
Cast<WasmExportedFunction>(GetExport(i_isolate, instance, buffer));
DirectHandle<Object> undefined = i_isolate->factory()->undefined_value();
DirectHandle<Object> function_result =
Execution::Call(i_isolate, function, undefined, {}).ToHandleChecked();
// Get global value.
snprintf(buffer, sizeof buffer, "g%zu", i);
auto global =
Cast<WasmGlobalObject>(GetExport(i_isolate, instance, buffer));
switch (global->type().kind()) {
case ValueKind::kF32: {
float global_val = global->GetF32();
float func_val;
if (IsSmi(*function_result)) {
func_val = Smi::ToInt(*function_result);
} else {
CHECK(IsHeapNumber(*function_result));
func_val = Cast<HeapNumber>(*function_result)->value();
}
CHECK_FLOAT_EQ(func_val, global_val);
break;
}
case ValueKind::kF64: {
double global_val = global->GetF64();
double func_val;
if (IsSmi(*function_result)) {
func_val = Smi::ToInt(*function_result);
} else {
CHECK(IsHeapNumber(*function_result));
func_val = Cast<HeapNumber>(*function_result)->value();
}
CHECK_FLOAT_EQ(func_val, global_val);
break;
}
case ValueKind::kI32: {
int32_t global_val = global->GetI32();
int32_t func_val;
if (IsSmi(*function_result)) {
func_val = Smi::ToInt(*function_result);
} else {
CHECK(IsHeapNumber(*function_result));
func_val = Cast<HeapNumber>(*function_result)->value();
}
CHECK_EQ(func_val, global_val);
break;
}
case ValueKind::kI64: {
int64_t global_val = global->GetI64();
int64_t func_val;
if (IsSmi(*function_result)) {
func_val = Smi::ToInt(*function_result);
} else {
CHECK(IsBigInt(*function_result));
bool lossless;
func_val = Cast<BigInt>(*function_result)->AsInt64(&lossless);
CHECK(lossless);
}
CHECK_EQ(func_val, global_val);
break;
}
case ValueKind::kRef:
case ValueKind::kRefNull: {
// For reference types the expectations are more limited.
// Any struct.new would create a new object, so reference equality
// comparisons will not work.
DirectHandle<Object> global_val = global->GetRef();
CHECK_EQ(IsUndefined(*global_val), IsUndefined(*function_result));
CHECK_EQ(IsNullOrWasmNull(*global_val),
IsNullOrWasmNull(*function_result));
if (!IsNullOrWasmNull(*global_val)) {
if (IsSubtypeOf(global->type(), kWasmFuncRef,
module_object->module())) {
// For any function the global should be an internal function
// whose external function equals the call result. (The call goes
// through JS conversions while the global is accessed directly.)
CHECK(IsWasmFuncRef(*global_val));
CHECK(
WasmExportedFunction::IsWasmExportedFunction(*function_result));
CHECK(*WasmInternalFunction::GetOrCreateExternal(direct_handle(
Cast<WasmFuncRef>(*global_val)->internal(i_isolate),
i_isolate)) == *function_result);
} else {
// On arrays and structs, perform a deep comparison.
DisallowGarbageCollection no_gc;
WasmValue global_value =
instance->trusted_data(i_isolate)->GetGlobalValue(
i_isolate, instance->module()->globals[i]);
WasmValue func_value(function_result, global_value.type());
CheckEquivalent(global_value, func_value, *module_object->module());
}
}
break;
}
default:
UNIMPLEMENTED();
}
}
}
} // anonymous namespace
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
FuzzIt({data, size});
return 0;
}
} // namespace v8::internal::wasm::fuzzing

View File

@ -0,0 +1,27 @@
// Copyright 2024 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 "src/wasm/fuzzing/random-module-generation.h"
#include "src/wasm/wasm-module-builder.h"
#include "src/zone/zone.h"
#include "test/fuzzer/wasm/interpreter/interpreter-fuzzer-common.h"
namespace v8::internal::wasm::fuzzing {
// Fuzzer that may generate SIMD expressions.
bool GenerateModule(Isolate* isolate, Zone* zone,
base::Vector<const uint8_t> data,
wasm::ZoneBuffer* buffer) {
v8::base::Vector<const uint8_t> wire_bytes = GenerateRandomWasmModule(
zone, {{WasmModuleGenerationOption::kGenerateSIMD}}, data);
if (wire_bytes.empty()) return false;
buffer->write(wire_bytes.data(), wire_bytes.size());
return true;
}
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
return LLVMFuzzerTestOneInputCommon(data, size, GenerateModule);
}
} // namespace v8::internal::wasm::fuzzing

View File

@ -0,0 +1,26 @@
// Copyright 2024 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 "src/wasm/fuzzing/random-module-generation.h"
#include "src/wasm/wasm-module-builder.h"
#include "src/zone/zone.h"
#include "test/fuzzer/wasm/interpreter/interpreter-fuzzer-common.h"
namespace v8::internal::wasm::fuzzing {
// Fuzzer that may generate WasmGC expressions.
bool GenerateModule(Isolate* isolate, Zone* zone,
base::Vector<const uint8_t> data, ZoneBuffer* buffer) {
base::Vector<const uint8_t> wire_bytes = GenerateRandomWasmModule(
zone, {{WasmModuleGenerationOption::kGenerateWasmGC}}, data);
if (wire_bytes.empty()) return false;
buffer->write(wire_bytes.data(), wire_bytes.size());
return true;
}
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
return LLVMFuzzerTestOneInputCommon(data, size, GenerateModule);
}
} // namespace v8::internal::wasm::fuzzing

91
deps/v8/test/fuzzer/wasm/module.cc vendored Normal file
View File

@ -0,0 +1,91 @@
// 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 <limits.h>
#include <stddef.h>
#include <stdint.h>
#include "include/libplatform/libplatform.h"
#include "include/v8-context.h"
#include "include/v8-exception.h"
#include "include/v8-isolate.h"
#include "include/v8-local-handle.h"
#include "src/execution/isolate-inl.h"
#include "src/wasm/wasm-engine.h"
#include "src/wasm/wasm-feature-flags.h"
#include "src/wasm/wasm-module.h"
#include "test/common/wasm/wasm-module-runner.h"
#include "test/fuzzer/fuzzer-support.h"
#include "test/fuzzer/wasm/fuzzer-common.h"
namespace v8::internal::wasm::fuzzing {
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
v8_fuzzer::FuzzerSupport* support = v8_fuzzer::FuzzerSupport::Get();
v8::Isolate* isolate = support->GetIsolate();
// We reduce the maximum memory size and table size of WebAssembly instances
// to avoid OOMs in the fuzzer.
v8_flags.wasm_max_mem_pages = 32;
v8_flags.wasm_max_table_size = 100;
// Disable lazy compilation to find compiler bugs easier.
v8_flags.wasm_lazy_compilation = false;
// Choose one of Liftoff or TurboFan, depending on the size of the input (we
// can't use a dedicated byte from the input, because we want to be able to
// pass Wasm modules unmodified to this fuzzer).
v8_flags.liftoff = size & 1;
Isolate* i_isolate = reinterpret_cast<Isolate*>(isolate);
v8::Isolate::Scope isolate_scope(isolate);
// Clear any exceptions from a prior run.
if (i_isolate->has_exception()) {
i_isolate->clear_exception();
}
v8::HandleScope handle_scope(isolate);
v8::Context::Scope context_scope(support->GetContext());
// We explicitly enable staged/experimental WebAssembly features here to
// increase fuzzer coverage. For libfuzzer fuzzers it is not possible that the
// fuzzer enables the flag by itself.
EnableExperimentalWasmFeatures(isolate);
v8::TryCatch try_catch(isolate);
testing::SetupIsolateForWasmModule(i_isolate);
ModuleWireBytes wire_bytes(data, data + size);
HandleScope scope(i_isolate);
ErrorThrower thrower(i_isolate, "wasm fuzzer");
DirectHandle<WasmModuleObject> module_object;
auto enabled_features = WasmEnabledFeatures::FromIsolate(i_isolate);
bool compiles =
GetWasmEngine()
->SyncCompile(i_isolate, enabled_features,
CompileTimeImportsForFuzzing(), &thrower,
base::OwnedCopyOf(wire_bytes.module_bytes()))
.ToHandle(&module_object);
if (v8_flags.wasm_fuzzer_gen_test) {
GenerateTestCase(i_isolate, wire_bytes, compiles);
}
// Return `-1` for invalid modules. We generate enough of them via mutation,
// no need to add them to the corpus.
int fuzzer_return_value =
compiles ? ExecuteAgainstReference(i_isolate, module_object,
kDefaultMaxFuzzerExecutedInstructions)
: -1;
// Pump the message loop and run micro tasks, e.g. GC finalization tasks.
support->PumpMessageLoop(v8::platform::MessageLoopBehavior::kDoNotWait);
isolate->PerformMicrotaskCheckpoint();
return fuzzer_return_value;
}
} // namespace v8::internal::wasm::fuzzing

0
deps/v8/test/fuzzer/wasm/module/foo vendored Normal file
View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

201
deps/v8/test/fuzzer/wasm/streaming.cc vendored Normal file
View File

@ -0,0 +1,201 @@
// Copyright 2022 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 <memory>
#include <string>
#include "include/v8-isolate.h"
#include "src/api/api-inl.h"
#include "src/flags/flags.h"
#include "src/wasm/streaming-decoder.h"
#include "src/wasm/wasm-engine.h"
#include "src/wasm/wasm-objects-inl.h"
#include "test/fuzzer/fuzzer-support.h"
#include "test/fuzzer/wasm/fuzzer-common.h"
namespace v8::internal::wasm::fuzzing {
// Some properties of the compilation result to check. Extend if needed.
struct CompilationResult {
bool failed = false;
std::string error_message;
// If successful:
uint32_t imported_functions = 0;
uint32_t declared_functions = 0;
static CompilationResult ForFailure(std::string error_message) {
DCHECK(!error_message.empty());
return {true, std::move(error_message)};
}
static CompilationResult ForSuccess(const WasmModule* module) {
return {false,
{},
module->num_imported_functions,
module->num_declared_functions};
}
};
class TestResolver : public CompilationResultResolver {
public:
explicit TestResolver(Isolate* isolate) : isolate_(isolate) {}
void OnCompilationSucceeded(DirectHandle<WasmModuleObject> module) override {
done_ = true;
native_module_ = module->shared_native_module();
}
void OnCompilationFailed(DirectHandle<JSAny> error_reason) override {
done_ = true;
failed_ = true;
DirectHandle<String> str =
Object::ToString(isolate_, error_reason).ToHandleChecked();
error_message_.assign(str->ToCString().get());
}
bool done() const { return done_; }
bool failed() const { return failed_; }
const std::shared_ptr<NativeModule>& native_module() const {
return native_module_;
}
const std::string& error_message() const { return error_message_; }
private:
Isolate* isolate_;
bool done_ = false;
bool failed_ = false;
std::string error_message_;
std::shared_ptr<NativeModule> native_module_;
};
CompilationResult CompileStreaming(v8_fuzzer::FuzzerSupport* support,
WasmEnabledFeatures enabled_features,
base::Vector<const uint8_t> data,
uint8_t config) {
v8::Isolate* isolate = support->GetIsolate();
Isolate* i_isolate = reinterpret_cast<Isolate*>(isolate);
CompilationResult result;
std::weak_ptr<NativeModule> weak_native_module;
{
HandleScope handle_scope{i_isolate};
auto resolver = std::make_shared<TestResolver>(i_isolate);
DirectHandle<Context> context =
v8::Utils::OpenDirectHandle(*support->GetContext());
std::shared_ptr<StreamingDecoder> stream =
GetWasmEngine()->StartStreamingCompilation(
i_isolate, enabled_features, CompileTimeImports{}, context,
"wasm-streaming-fuzzer", resolver);
if (data.size() > 0) {
size_t split = config % data.size();
stream->OnBytesReceived(data.SubVector(0, split));
stream->OnBytesReceived(data.SubVectorFrom(split));
}
stream->Finish();
// Wait for the promise to resolve or reject.
while (!resolver->done()) {
support->PumpMessageLoop(platform::MessageLoopBehavior::kWaitForWork);
isolate->PerformMicrotaskCheckpoint();
}
if (resolver->failed()) {
return CompilationResult::ForFailure(resolver->error_message());
}
result = CompilationResult::ForSuccess(resolver->native_module()->module());
weak_native_module = resolver->native_module();
}
// Collect garbage until the native module is collected. This ensures that we
// recompile the module for sync compilation instead of taking it from the
// cache.
// If this turns out to be too slow, we could try to explicitly clear the
// cache, but we have to be careful not to break other internal assumptions
// then (because we have several identical modules / scripts).
while (weak_native_module.lock()) {
isolate->RequestGarbageCollectionForTesting(
v8::Isolate::kFullGarbageCollection);
}
return result;
}
CompilationResult CompileSync(Isolate* isolate,
WasmEnabledFeatures enabled_features,
base::Vector<const uint8_t> data) {
ErrorThrower thrower{isolate, "wasm-streaming-fuzzer"};
DirectHandle<WasmModuleObject> module_object;
CompilationResult result;
if (!GetWasmEngine()
->SyncCompile(isolate, enabled_features, CompileTimeImports{},
&thrower, base::OwnedCopyOf(data))
.ToHandle(&module_object)) {
DirectHandle<Object> error = thrower.Reify();
DirectHandle<String> error_msg =
Object::ToString(isolate, error).ToHandleChecked();
return CompilationResult::ForFailure(error_msg->ToCString().get());
}
return CompilationResult::ForSuccess(module_object->module());
}
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
if (size < 1) return 0;
v8_fuzzer::FuzzerSupport* support = v8_fuzzer::FuzzerSupport::Get();
v8::Isolate* isolate = support->GetIsolate();
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());
// We explicitly enable staged/experimental WebAssembly features here to
// increase fuzzer coverage. For libfuzzer fuzzers it is not possible that the
// fuzzer enables the flag by itself.
EnableExperimentalWasmFeatures(isolate);
// Limit the maximum module size to avoid OOM.
v8_flags.wasm_max_module_size = 256 * KB;
WasmEnabledFeatures enabled_features =
WasmEnabledFeatures::FromIsolate(i_isolate);
base::Vector<const uint8_t> data_vec{data, size - 1};
uint8_t config = data[size - 1];
CompilationResult streaming_result =
CompileStreaming(support, enabled_features, data_vec, config);
CompilationResult sync_result =
CompileSync(i_isolate, enabled_features, data_vec);
if (streaming_result.failed != sync_result.failed) {
const char* error_msg = streaming_result.failed
? streaming_result.error_message.c_str()
: sync_result.error_message.c_str();
FATAL(
"Streaming compilation did%s fail, sync compilation did%s. "
"Error message: %s\n",
streaming_result.failed ? "" : " not", sync_result.failed ? "" : " not",
error_msg);
}
if (streaming_result.error_message != sync_result.error_message) {
FATAL("Error messages differ:\nstreaming: %s\n sync: %s",
streaming_result.error_message.c_str(),
sync_result.error_message.c_str());
}
CHECK_EQ(streaming_result.imported_functions, sync_result.imported_functions);
CHECK_EQ(streaming_result.declared_functions, sync_result.declared_functions);
// We should not leave exceptions behind.
DCHECK(!i_isolate->has_exception());
return 0;
}
} // namespace v8::internal::wasm::fuzzing

View File

@ -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/main/test/fuzzer/README.md. There should be at
least one file in this directory, e.g. this README file.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.