#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/fuzzer-common.h"
#include "test/common/wasm/wasm-module-runner.h"
#include "test/fuzzer/fuzzer-support.h"
namespace v8::internal::wasm::fuzzing {
namespace {
using ExecutionResult = std::variant<int, std::string >;
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 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);
FlagScope<bool> no_liftoff_code_flushing(&v8_flags.flush_liftoff_code, false);
ErrorThrower thrower(isolate, "WasmFuzzerSyncCompileReference");
int32_t max_steps = kDefaultMaxFuzzerExecutedInstructions;
EnterDebuggingScope debugging_scope(isolate);
DirectHandle<WasmModuleObject> module_object;
if (!CompileReferenceModule(isolate, wire_bytes.module_bytes(), &max_steps)
.ToHandle(&module_object)) {
return {};
}
thrower.Reset();
CHECK(!isolate->has_exception());
DirectHandle<WasmInstanceObject> instance;
if (!GetWasmEngine()
->SyncInstantiate(isolate, &thrower, module_object, {}, {})
.ToHandle(&instance)) {
CHECK(thrower.error());
CHECK(strstr(thrower.error_msg(), "Out of memory"));
return {};
}
NearHeapLimitCallbackScope near_heap_limit(isolate);
for (uint32_t i = 0; i < callees.size(); ++i) {
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 (WasmEngine::clear_nondeterminism()) break;
if (max_steps < 0) break;
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) {
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) {
v8_flags.wasm_native_module_cache = false;
v8_flags.wasm_sync_tier_up = true;
v8_flags.wasm_deopt = true;
v8_flags.wasm_inlining_call_indirect = true;
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;
v8_flags.wasm_allow_mixed_eh_for_testing = true;
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();
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);
ResetTypeCanonicalizer(isolate);
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());
auto enabled_features = WasmEnabledFeatures::FromIsolate(i_isolate);
bool valid = GetWasmEngine()->SyncValidate(
i_isolate, enabled_features, CompileTimeImportsForFuzzing(), buffer);
const bool optimize_main_function =
inlinees.empty() || data.empty() || !(data.last() & 1);
const bool assert_types = !data.empty() && (data.last() & 2);
FlagScope<bool> assert_types_scope(&v8_flags.wasm_assert_types, assert_types);
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;
bool emit_call_main = false;
GenerateTestCase(os, i_isolate, wire_bytes, valid, emit_call_main,
flags.str());
os << "let maybeThrows = (fct, ...args) => {\n"
" try {\n"
" print(fct(...args));\n"
" } catch (e) {\n"
" print(`caught ${e}`);\n"
" }\n"
"};\n\n";
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()) {
return -1;
}
DirectHandle<WasmModuleObject> module_object = compiled.ToHandleChecked();
DirectHandle<WasmInstanceObject> instance;
if (!GetWasmEngine()
->SyncInstantiate(i_isolate, &thrower, module_object, {}, {})
.ToHandle(&instance)) {
DCHECK(thrower.error());
if (strstr(thrower.error_msg(), "Out of memory")) {
return -1;
}
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());
int32_t result_value = testing::CallWasmFunctionForTesting(
i_isolate, instance, "main", base::VectorOf(arguments), &exception);
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) {
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;
}
}
V8_SYMBOL_USED extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv) {
i::v8_flags.shared_heap = true;
i::v8_flags.shared_strings = true;
i::v8_flags.experimental_wasm_shared = true;
v8_fuzzer::FuzzerSupport::InitializeFuzzerSupport(argc, argv);
return 0;
}
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
return FuzzIt({data, size});
}
}