// 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;
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(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 PerformReferenceRun(
const std::vector& callees, ModuleWireBytes wire_bytes,
WasmEnabledFeatures enabled_features, bool valid, Isolate* isolate) {
std::vector results;
FlagScope 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 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 module_object =
CompileReferenceModule(isolate, wire_bytes.module_bytes(), &max_steps);
thrower.Reset();
CHECK(!isolate->has_exception());
DirectHandle 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