| // Copyright 2021 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 <cinttypes> |
| #include <cstring> |
| |
| #include "include/v8-wasm.h" |
| #include "src/api/api.h" |
| #include "src/base/memory.h" |
| #include "src/base/platform/mutex.h" |
| #include "src/builtins/builtins-inl.h" |
| #include "src/execution/arguments-inl.h" |
| #include "src/execution/frames-inl.h" |
| #include "src/handles/handles.h" |
| #include "src/heap/heap-inl.h" |
| #include "src/objects/js-array-buffer-inl.h" |
| #include "src/objects/property-descriptor.h" |
| #include "src/objects/smi.h" |
| #include "src/trap-handler/trap-handler.h" |
| #include "src/wasm/compilation-hints-generation.h" |
| #include "src/wasm/function-body-decoder.h" |
| #include "src/wasm/fuzzing/random-module-generation.h" |
| #include "src/wasm/module-compiler.h" |
| #include "src/wasm/wasm-code-manager.h" |
| #include "src/wasm/wasm-code-pointer-table-inl.h" |
| #include "src/wasm/wasm-engine.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-result.h" |
| #include "src/wasm/wasm-serialization.h" |
| #include "src/wasm/wasm-tracing.h" |
| |
| namespace v8::internal { |
| |
| namespace { |
| V8_WARN_UNUSED_RESULT Tagged<Object> CrashUnlessFuzzing(Isolate* isolate) { |
| CHECK(v8_flags.fuzzing); |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| struct WasmCompileControls { |
| uint32_t MaxWasmBufferSize = std::numeric_limits<uint32_t>::max(); |
| bool AllowAnySizeForAsync = true; |
| }; |
| using WasmCompileControlsMap = std::map<v8::Isolate*, WasmCompileControls>; |
| |
| // We need per-isolate controls, because we sometimes run tests in multiple |
| // isolates concurrently. Methods need to hold the accompanying mutex on access. |
| // To avoid upsetting the static initializer count, we lazy initialize this. |
| DEFINE_LAZY_LEAKY_OBJECT_GETTER(WasmCompileControlsMap, |
| GetPerIsolateWasmControls) |
| base::LazyMutex g_PerIsolateWasmControlsMutex = LAZY_MUTEX_INITIALIZER; |
| |
| bool IsWasmCompileAllowed(v8::Isolate* isolate, v8::Local<v8::Value> value, |
| bool is_async) { |
| base::MutexGuard guard(g_PerIsolateWasmControlsMutex.Pointer()); |
| DCHECK_GT(GetPerIsolateWasmControls()->count(isolate), 0); |
| const WasmCompileControls& ctrls = GetPerIsolateWasmControls()->at(isolate); |
| return (is_async && ctrls.AllowAnySizeForAsync) || |
| (value->IsArrayBuffer() && value.As<v8::ArrayBuffer>()->ByteLength() <= |
| ctrls.MaxWasmBufferSize) || |
| (value->IsArrayBufferView() && |
| value.As<v8::ArrayBufferView>()->ByteLength() <= |
| ctrls.MaxWasmBufferSize); |
| } |
| |
| // Use the compile controls for instantiation, too |
| bool IsWasmInstantiateAllowed(v8::Isolate* isolate, |
| v8::Local<v8::Value> module_or_bytes, |
| bool is_async) { |
| base::MutexGuard guard(g_PerIsolateWasmControlsMutex.Pointer()); |
| DCHECK_GT(GetPerIsolateWasmControls()->count(isolate), 0); |
| const WasmCompileControls& ctrls = GetPerIsolateWasmControls()->at(isolate); |
| if (is_async && ctrls.AllowAnySizeForAsync) return true; |
| if (!module_or_bytes->IsWasmModuleObject()) { |
| return IsWasmCompileAllowed(isolate, module_or_bytes, is_async); |
| } |
| v8::Local<v8::WasmModuleObject> module = |
| v8::Local<v8::WasmModuleObject>::Cast(module_or_bytes); |
| return static_cast<uint32_t>( |
| module->GetCompiledModule().GetWireBytesRef().size()) <= |
| ctrls.MaxWasmBufferSize; |
| } |
| |
| v8::Local<v8::Value> NewRangeException(v8::Isolate* isolate, |
| const char* message) { |
| return v8::Exception::RangeError( |
| v8::String::NewFromOneByte(isolate, |
| reinterpret_cast<const uint8_t*>(message)) |
| .ToLocalChecked()); |
| } |
| |
| void ThrowRangeException(v8::Isolate* isolate, const char* message) { |
| isolate->ThrowException(NewRangeException(isolate, message)); |
| } |
| |
| bool WasmModuleOverride(const v8::FunctionCallbackInfo<v8::Value>& info) { |
| DCHECK(ValidateCallbackInfo(info)); |
| if (IsWasmCompileAllowed(info.GetIsolate(), info[0], false)) return false; |
| ThrowRangeException(info.GetIsolate(), "Sync compile not allowed"); |
| return true; |
| } |
| |
| bool WasmInstanceOverride(const v8::FunctionCallbackInfo<v8::Value>& info) { |
| DCHECK(ValidateCallbackInfo(info)); |
| if (IsWasmInstantiateAllowed(info.GetIsolate(), info[0], false)) return false; |
| ThrowRangeException(info.GetIsolate(), "Sync instantiate not allowed"); |
| return true; |
| } |
| |
| } // namespace |
| |
| // Returns a callable object. The object returns the difference of its two |
| // parameters when it is called. |
| RUNTIME_FUNCTION(Runtime_SetWasmCompileControls) { |
| SealHandleScope shs(isolate); |
| DisallowGarbageCollection no_gc; |
| if (args.length() != 2 || !IsSmi(args[0]) || !IsBoolean(args[1])) { |
| return CrashUnlessFuzzing(isolate); |
| } |
| v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate); |
| int block_size = args.smi_value_at(0); |
| bool allow_async = Cast<Boolean>(args[1])->ToBool(isolate); |
| base::MutexGuard guard(g_PerIsolateWasmControlsMutex.Pointer()); |
| WasmCompileControls& ctrl = (*GetPerIsolateWasmControls())[v8_isolate]; |
| ctrl.AllowAnySizeForAsync = allow_async; |
| ctrl.MaxWasmBufferSize = static_cast<uint32_t>(block_size); |
| v8_isolate->SetWasmModuleCallback(WasmModuleOverride); |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_SetWasmInstantiateControls) { |
| SealHandleScope shs(isolate); |
| DisallowGarbageCollection no_gc; |
| v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate); |
| v8_isolate->SetWasmInstanceCallback(WasmInstanceOverride); |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| namespace { |
| |
| int WasmStackSize(Isolate* isolate) { |
| // TODO(wasm): Fix this for mixed JS/Wasm stacks with both --trace and |
| // --trace-wasm. |
| int n = 0; |
| for (DebuggableStackFrameIterator it(isolate); !it.done(); it.Advance()) { |
| if (it.is_wasm()) n++; |
| } |
| return n; |
| } |
| |
| } // anonymous namespace |
| |
| // TODO(jkummerow): I think this should just iterate the WasmCodePointerTable |
| // directly, not individual dispatch tables. |
| RUNTIME_FUNCTION(Runtime_CountUnoptimizedWasmToJSWrapper) { |
| SealHandleScope shs(isolate); |
| DisallowGarbageCollection no_gc; |
| if (args.length() != 1 || !IsWasmInstanceObject(args[0])) { |
| return CrashUnlessFuzzing(isolate); |
| } |
| Tagged<WasmInstanceObject> instance_object = |
| Cast<WasmInstanceObject>(args[0]); |
| Tagged<WasmTrustedInstanceData> trusted_data = |
| instance_object->trusted_data(isolate); |
| Address wrapper_entry = |
| Builtins::EmbeddedEntryOf(Builtin::kWasmToJsWrapperAsm); |
| |
| int result = 0; |
| Tagged<WasmDispatchTableForImports> dispatch_table = |
| trusted_data->dispatch_table_for_imports(); |
| int import_count = dispatch_table->length(); |
| wasm::WasmCodePointerTable* cpt = wasm::GetProcessWideWasmCodePointerTable(); |
| for (int i = 0; i < import_count; ++i) { |
| if (cpt->EntrypointEqualTo(dispatch_table->target(i), wrapper_entry)) { |
| ++result; |
| } |
| } |
| Tagged<ProtectedFixedArray> dispatch_tables = trusted_data->dispatch_tables(); |
| uint32_t table_count = dispatch_tables->ulength().value(); |
| for (uint32_t table_index = 0; table_index < table_count; ++table_index) { |
| if (dispatch_tables->get(table_index) == Smi::zero()) continue; |
| Tagged<WasmDispatchTable> table = |
| TrustedCast<WasmDispatchTable>(dispatch_tables->get(table_index)); |
| int table_size = table->length(); |
| for (int entry_index = 0; entry_index < table_size; ++entry_index) { |
| WasmCodePointer target = table->target(entry_index); |
| if (target != wasm::kInvalidWasmCodePointer && |
| cpt->EntrypointEqualTo(target, wrapper_entry)) { |
| ++result; |
| } |
| } |
| } |
| return Smi::FromInt(result); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_HasUnoptimizedWasmToJSWrapper) { |
| SealHandleScope shs(isolate); |
| DisallowGarbageCollection no_gc; |
| if (args.length() != 1 || !IsJSFunction(args[0])) { |
| return CrashUnlessFuzzing(isolate); |
| } |
| Tagged<JSFunction> function = Cast<JSFunction>(args[0]); |
| Tagged<SharedFunctionInfo> sfi = function->shared(); |
| if (!sfi->HasWasmFunctionData(isolate)) { |
| return isolate->heap()->ToBoolean(false); |
| } |
| Tagged<WasmFunctionData> func_data = sfi->wasm_function_data(); |
| WasmCodePointer call_target = func_data->internal()->call_target(); |
| |
| Address wrapper_entry = |
| Builtins::EmbeddedEntryOf(Builtin::kWasmToJsWrapperAsm); |
| return isolate->heap()->ToBoolean( |
| wasm::GetProcessWideWasmCodePointerTable()->EntrypointEqualTo( |
| call_target, wrapper_entry)); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_WasmTraceEnter) { |
| SealHandleScope shs(isolate); |
| DisallowGarbageCollection no_gc; |
| // This isn't exposed to fuzzers so doesn't need to handle invalid arguments. |
| DCHECK_EQ(0, args.length()); |
| PrintIndentation(WasmStackSize(isolate)); |
| |
| // Find the caller wasm frame. |
| wasm::WasmCodeRefScope wasm_code_ref_scope; |
| DebuggableStackFrameIterator it(isolate); |
| DCHECK(!it.done()); |
| DCHECK(it.is_wasm()); |
| #if V8_ENABLE_DRUMBRAKE |
| DCHECK(!it.is_wasm_interpreter_entry()); |
| #endif // V8_ENABLE_DRUMBRAKE |
| WasmFrame* frame = WasmFrame::cast(it.frame()); |
| |
| // Find the function name. |
| int func_index = frame->GetInnermostFunctionIndex(); |
| const wasm::WasmModule* module = frame->trusted_instance_data()->module(); |
| wasm::ModuleWireBytes wire_bytes = |
| wasm::ModuleWireBytes(frame->native_module()->wire_bytes()); |
| wasm::WireBytesRef name_ref = |
| module->lazily_generated_names.LookupFunctionName(wire_bytes, func_index); |
| wasm::WasmName name = wire_bytes.GetNameOrNull(name_ref); |
| |
| wasm::WasmCode* code = frame->wasm_code(); |
| PrintF(code->is_liftoff() ? "~" : "*"); |
| |
| if (name.empty()) { |
| PrintF("wasm-function[%d] {\n", func_index); |
| } else { |
| PrintF("wasm-function[%d] \"%.*s\" {\n", func_index, name.length(), |
| name.begin()); |
| } |
| |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_WasmTraceExit) { |
| SealHandleScope shs(isolate); |
| DisallowGarbageCollection no_gc; |
| // This isn't exposed to fuzzers so doesn't need to handle invalid arguments. |
| DCHECK_EQ(1, args.length()); |
| Tagged<Smi> return_addr_smi = Cast<Smi>(args[0]); |
| |
| PrintIndentation(WasmStackSize(isolate)); |
| PrintF("}"); |
| |
| // Find the caller wasm frame. |
| wasm::WasmCodeRefScope wasm_code_ref_scope; |
| DebuggableStackFrameIterator it(isolate); |
| DCHECK(!it.done()); |
| DCHECK(it.is_wasm()); |
| #if V8_ENABLE_DRUMBRAKE |
| DCHECK(!it.is_wasm_interpreter_entry()); |
| #endif // V8_ENABLE_DRUMBRAKE |
| WasmFrame* frame = WasmFrame::cast(it.frame()); |
| int func_index = frame->GetInnermostFunctionIndex(); |
| const wasm::WasmModule* module = frame->trusted_instance_data()->module(); |
| const wasm::FunctionSig* sig = module->functions[func_index].sig; |
| |
| size_t num_returns = sig->return_count(); |
| // If we have no returns, we should have passed {Smi::zero()}. |
| DCHECK_IMPLIES(num_returns == 0, IsZero(return_addr_smi)); |
| if (num_returns == 1) { |
| wasm::ValueType return_type = sig->GetReturn(0); |
| switch (return_type.kind()) { |
| case wasm::kI32: { |
| int32_t value = |
| base::ReadUnalignedValue<int32_t>(return_addr_smi.ptr()); |
| PrintF(" -> %d\n", value); |
| break; |
| } |
| case wasm::kI64: { |
| int64_t value = |
| base::ReadUnalignedValue<int64_t>(return_addr_smi.ptr()); |
| PrintF(" -> %" PRId64 "\n", value); |
| break; |
| } |
| case wasm::kF32: { |
| float value = base::ReadUnalignedValue<float>(return_addr_smi.ptr()); |
| PrintF(" -> %f\n", value); |
| break; |
| } |
| case wasm::kF64: { |
| double value = base::ReadUnalignedValue<double>(return_addr_smi.ptr()); |
| PrintF(" -> %f\n", value); |
| break; |
| } |
| default: |
| PrintF(" -> Unsupported type\n"); |
| break; |
| } |
| } else { |
| // TODO(wasm) Handle multiple return values. |
| PrintF("\n"); |
| } |
| |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| namespace { |
| |
| bool DisallowWasmCodegenFromStringsCallback(v8::Local<v8::Context> context, |
| v8::Local<v8::String> source) { |
| return false; |
| } |
| |
| } // namespace |
| |
| RUNTIME_FUNCTION(Runtime_DisallowWasmCodegen) { |
| SealHandleScope shs(isolate); |
| DisallowGarbageCollection no_gc; |
| if (args.length() != 1 || !IsBoolean(args[0])) { |
| return CrashUnlessFuzzing(isolate); |
| } |
| bool flag = Cast<Boolean>(args[0])->ToBool(isolate); |
| v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate); |
| v8_isolate->SetAllowWasmCodeGenerationCallback( |
| flag ? DisallowWasmCodegenFromStringsCallback : nullptr); |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_IsWasmCode) { |
| SealHandleScope shs(isolate); |
| DisallowGarbageCollection no_gc; |
| if (args.length() != 1 || !IsJSFunction(args[0])) { |
| return CrashUnlessFuzzing(isolate); |
| } |
| auto function = Cast<JSFunction>(args[0]); |
| Tagged<Code> code = function->code(isolate); |
| bool is_js_to_wasm = code->kind() == CodeKind::JS_TO_WASM_FUNCTION || |
| (code->builtin_id() == Builtin::kJSToWasmWrapper); |
| #if V8_ENABLE_DRUMBRAKE |
| // TODO(paolosev@microsoft.com) - Implement an empty |
| // kJSToWasmInterpreterWrapper also when V8_ENABLE_DRUMBRAKE is not |
| // defined to get rid of these #ifdefs. |
| is_js_to_wasm = is_js_to_wasm || |
| (code->builtin_id() == Builtin::kJSToWasmInterpreterWrapper); |
| #endif // V8_ENABLE_DRUMBRAKE |
| return isolate->heap()->ToBoolean(is_js_to_wasm); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_IsWasmTrapHandlerEnabled) { |
| SealHandleScope shs(isolate); |
| DisallowGarbageCollection no_gc; |
| #if defined(V8_ENABLE_DRUMBRAKE) && defined(V8_DRUMBRAKE_BOUNDS_CHECKS) |
| if (v8_flags.wasm_jitless) { |
| return ReadOnlyRoots(isolate).false_value(); |
| } |
| #endif // defined(V8_ENABLE_DRUMBRAKE) && defined(V8_DRUMBRAKE_BOUNDS_CHECKS) |
| return isolate->heap()->ToBoolean(trap_handler::IsTrapHandlerEnabled()); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_IsWasmPartialOOBWriteNoop) { |
| SealHandleScope shs(isolate); |
| DisallowGarbageCollection no_gc; |
| return isolate->heap()->ToBoolean(v8_flags.wasm_partial_oob_writes_are_noops); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_GenerateWasmCompilationHints) { |
| SealHandleScope shs(isolate); |
| DisallowGarbageCollection no_gc; |
| if (!v8_flags.wasm_generate_compilation_hints && |
| !v8_flags.trace_wasm_generate_compilation_hints) { |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| if (args.length() != 1 || !IsWasmInstanceObject(args[0])) { |
| PrintF("Pass a Wasm instance as the first and only argument!\n"); |
| return CrashUnlessFuzzing(isolate); |
| } |
| |
| Tagged<WasmInstanceObject> instance = Cast<WasmInstanceObject>(args[0]); |
| |
| wasm::NativeModule* native_module = |
| instance->trusted_data(isolate)->native_module(); |
| const wasm::WasmModule* module = native_module->module(); |
| |
| wasm::TransitiveTypeFeedbackProcessor::ProcessAll( |
| isolate, instance->trusted_data(isolate)); |
| |
| if (v8_flags.trace_wasm_generate_compilation_hints) { |
| base::MutexGuard compilation_hints_mutex_guard( |
| &module->compilation_hints_mutex); |
| base::MutexGuard mutex(&module->type_feedback.mutex); |
| |
| int num_imported_functions = module->num_imported_functions; |
| int num_total_functions = static_cast<int>(module->functions.size()); |
| |
| for (int i = num_imported_functions; i < num_total_functions; i++) { |
| wasm::WasmCodeRefScope code_ref_scope; |
| wasm::WasmCode* code = native_module->GetCode(i); |
| if (code) { |
| if (!code->is_liftoff()) { |
| PrintF( |
| "You're holding it wrong! Don't call " |
| "%%GenerateWasmCompilationHints after triggering tier-up!\n"); |
| return CrashUnlessFuzzing(isolate); |
| } |
| if (module->marked_for_tierup.contains(i)) { |
| PrintF("%d: optimized\n", i); |
| } else { |
| PrintF("%d: compiled\n", i); |
| } |
| } else { |
| PrintF("%d: uncompiled\n", i); |
| } |
| } |
| |
| std::unordered_map<uint32_t, wasm::FunctionTypeFeedback>& feedback = |
| module->type_feedback.feedback_for_function; |
| |
| for (int func_index = num_imported_functions; |
| func_index < num_total_functions; func_index++) { |
| PrintF("%d", func_index); |
| auto it = feedback.find(func_index); |
| if (it == feedback.end()) { |
| PrintF(" no feedback\n"); |
| continue; |
| } |
| PrintF("\n"); |
| wasm::FunctionTypeFeedback& feedback_for_function = it->second; |
| |
| for (size_t num_slot = 0; |
| num_slot < feedback_for_function.feedback_vector.size(); |
| num_slot++) { |
| wasm::CallSiteFeedback& slot = |
| feedback_for_function.feedback_vector[num_slot]; |
| int total_count_at_slot = 0; |
| for (int call = 0; call < slot.num_cases(); call++) { |
| total_count_at_slot += slot.call_count(call); |
| } |
| |
| PrintF( |
| " slot %d, offset %d: total relative call count %lf\n", |
| static_cast<int>(num_slot), |
| module->feedback_slots_to_wire_byte_offsets[func_index][num_slot], |
| static_cast<double>(total_count_at_slot) / |
| feedback_for_function.num_invocations); |
| if (feedback_for_function.call_targets[num_slot] != |
| wasm::FunctionTypeFeedback::kCallIndirect && |
| feedback_for_function.call_targets[num_slot] != |
| wasm::FunctionTypeFeedback::kCallRef) { |
| PrintF(" direct call to %d\n", slot.function_index(0)); |
| } else { |
| for (int call = 0; call < slot.num_cases(); call++) { |
| // We floor the percentage so we do not end up with a sum of over |
| // 100. |
| PrintF(" call to %d, percentage %d\n", slot.function_index(call), |
| static_cast<int>( |
| std::floor(static_cast<double>(slot.call_count(call)) * |
| 100 / total_count_at_slot))); |
| } |
| } |
| } |
| } |
| } |
| |
| if (v8_flags.wasm_generate_compilation_hints) { |
| Zone zone{isolate->allocator(), "wasm::EmitCompilationHintsToBuffer"}; |
| wasm::ZoneBuffer buffer{&zone}; |
| wasm::EmitCompilationHintsToBuffer(buffer, native_module); |
| wasm::WriteCompilationHintsToFile(buffer, native_module); |
| } |
| |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_GetWasmRecoveredTrapCount) { |
| HandleScope scope(isolate); |
| size_t trap_count = trap_handler::GetRecoveredTrapCount(); |
| return *isolate->factory()->NewNumberFromSize(trap_count); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_GetWasmExceptionTagId) { |
| HandleScope scope(isolate); |
| if (args.length() != 2 || !IsWasmExceptionPackage(args[0]) || |
| !IsWasmInstanceObject(args[1]) || |
| !args.at<WasmInstanceObject>(1) |
| ->trusted_data(isolate) |
| ->has_tags_table()) { |
| return CrashUnlessFuzzing(isolate); |
| } |
| DirectHandle<WasmExceptionPackage> exception = |
| args.at<WasmExceptionPackage>(0); |
| DirectHandle<WasmInstanceObject> instance_object = |
| args.at<WasmInstanceObject>(1); |
| DirectHandle<WasmTrustedInstanceData> trusted_data( |
| instance_object->trusted_data(isolate), isolate); |
| DirectHandle<Object> tag = |
| WasmExceptionPackage::GetExceptionTag(isolate, exception); |
| CHECK(IsWasmExceptionTag(*tag)); |
| DirectHandle<TrustedFixedArray> tags_table(trusted_data->tags_table(), |
| isolate); |
| uint32_t tags_table_len = tags_table->length().value(); |
| for (uint32_t index = 0; index < tags_table_len; ++index) { |
| if (tags_table->get(index) == *tag) return Smi::FromUInt(index); |
| } |
| return CrashUnlessFuzzing(isolate); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_GetWasmExceptionValues) { |
| HandleScope scope(isolate); |
| if (args.length() != 1 || !IsWasmExceptionPackage(args[0])) { |
| return CrashUnlessFuzzing(isolate); |
| } |
| DirectHandle<WasmExceptionPackage> exception = |
| args.at<WasmExceptionPackage>(0); |
| DirectHandle<Object> values_obj = |
| WasmExceptionPackage::GetExceptionValues(isolate, exception); |
| if (!IsFixedArray(*values_obj)) { |
| // Only called with correct input (unless fuzzing). |
| return CrashUnlessFuzzing(isolate); |
| } |
| auto values = Cast<FixedArray>(values_obj); |
| const uint32_t values_len = values->length().value(); |
| DirectHandle<FixedArray> externalized_values = |
| isolate->factory()->NewFixedArray(values_len); |
| for (uint32_t i = 0; i < values_len; i++) { |
| DirectHandle<Object> value(values->get(i), isolate); |
| if (IsWasmNull(*value)) { |
| value = isolate->factory()->null_value(); |
| } else if (!IsSmi(*value)) { |
| // When fuzzers use this function, don't leak anything that the JS side |
| // can't handle. |
| if (IsByteArray(*value) || // Probably a stringview_wtf8. |
| IsWasmContinuationObject(*value) || IsWasmExceptionPackage(*value) || |
| IsWasmStringViewIter(*value) || IsForeign(*value)) { |
| return CrashUnlessFuzzing(isolate); |
| } |
| value = wasm::WasmToJSObject(isolate, value); |
| DCHECK(IsPrimitiveHeapObject(*value) || IsJSReceiver(*value)); |
| } |
| externalized_values->set(i, *value); |
| } |
| return *isolate->factory()->NewJSArrayWithElements(externalized_values); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_WasmGetNumberOfInstances) { |
| SealHandleScope shs(isolate); |
| DisallowGarbageCollection no_gc; |
| if (args.length() != 1 || !IsWasmModuleObject(args[0])) { |
| return CrashUnlessFuzzing(isolate); |
| } |
| Tagged<WasmModuleObject> module_obj = Cast<WasmModuleObject>(args[0]); |
| uint32_t instance_count = 0; |
| Tagged<WeakArrayList> weak_instance_list = |
| module_obj->script()->wasm_weak_instance_list(); |
| const uint32_t weak_instance_len = weak_instance_list->length().value(); |
| for (uint32_t i = 0; i < weak_instance_len; ++i) { |
| if (weak_instance_list->Get(i).IsWeak()) instance_count++; |
| } |
| return Smi::FromUInt(instance_count); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_WasmNumCodeSpaces) { |
| HandleScope scope(isolate); |
| if (args.length() != 1 || !IsJSObject(args[0])) { |
| return CrashUnlessFuzzing(isolate); |
| } |
| DirectHandle<JSObject> argument = args.at<JSObject>(0); |
| size_t num_spaces; |
| if (IsWasmInstanceObject(*argument)) { |
| num_spaces = Cast<WasmInstanceObject>(*argument) |
| ->trusted_data(isolate) |
| ->native_module() |
| ->GetNumberOfCodeSpacesForTesting(); |
| } else if (IsWasmModuleObject(*argument)) { |
| num_spaces = Cast<WasmModuleObject>(*argument) |
| ->native_module() |
| ->GetNumberOfCodeSpacesForTesting(); |
| } else { |
| return CrashUnlessFuzzing(isolate); |
| } |
| return *isolate->factory()->NewNumberFromSize(num_spaces); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_WasmTraceGlobal) { |
| CHECK(v8_flags.trace_wasm_globals); |
| HandleScope handle_scope(isolate); |
| if (args.length() != 1 || !IsSmi(args[0])) { |
| return CrashUnlessFuzzing(isolate); |
| } |
| DisallowGarbageCollection no_gc; |
| auto info_addr = Cast<Smi>(args[0]); |
| |
| wasm::GlobalTracingInfo* info = |
| reinterpret_cast<wasm::GlobalTracingInfo*>(info_addr.ptr()); |
| |
| wasm::WasmCodeRefScope wasm_code_ref_scope; |
| DebuggableStackFrameIterator it(isolate); |
| DCHECK(!it.done()); |
| DCHECK(it.is_wasm()); |
| WasmFrame* frame = WasmFrame::cast(it.frame()); |
| |
| const wasm::WasmModule* module = frame->trusted_instance_data()->module(); |
| const wasm::WasmGlobal& global = module->globals[info->global_index]; |
| |
| wasm::ExecutionTier tier = frame->wasm_code()->tier(); |
| |
| wasm::WasmValue value = |
| frame->trusted_instance_data()->GetGlobalValue(isolate, global); |
| |
| wasm::GlobalTraceEntry trace_entry = { |
| .function_index = frame->GetInnermostFunctionIndex(), |
| .global_index = info->global_index, |
| .frame_position = frame->position(), |
| .tier = tier, |
| .kind = global.type.kind(), |
| .is_store = static_cast<bool>(info->is_store), |
| .value_bytes = {}}; |
| CHECK_GE(sizeof(trace_entry.value_bytes), value.type().value_kind_size()); |
| |
| if (value.type().is_numeric()) { |
| value.CopyTo(trace_entry.value_bytes); |
| } else { |
| DirectHandle<Object> ref_handle = value.to_ref(); |
| base::WriteUnalignedValue<Address>( |
| reinterpret_cast<Address>(trace_entry.value_bytes), |
| (*ref_handle).ptr()); |
| } |
| |
| wasm::WasmTracesForTesting& traces = wasm::GetWasmTracesForTesting(); |
| if (traces.should_store_trace) { |
| traces.global_trace.push_back(trace_entry); |
| } else { |
| std::ostringstream ss; |
| PrintGlobalTraceString(trace_entry, frame->native_module(), ss); |
| ss << "\n"; |
| PrintF("%s", ss.str().c_str()); |
| } |
| |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_WasmTraceMemory) { |
| SealHandleScope shs(isolate); |
| DisallowGarbageCollection no_gc; |
| if (args.length() != 1 || !IsSmi(args[0])) { |
| return CrashUnlessFuzzing(isolate); |
| } |
| auto info_addr = Cast<Smi>(args[0]); |
| |
| wasm::MemoryTracingInfo* info = |
| reinterpret_cast<wasm::MemoryTracingInfo*>(info_addr.ptr()); |
| |
| // Find the caller wasm frame. |
| wasm::WasmCodeRefScope wasm_code_ref_scope; |
| DebuggableStackFrameIterator it(isolate); |
| CHECK(!it.done()); |
| CHECK(it.is_wasm()); |
| #if V8_ENABLE_DRUMBRAKE |
| DCHECK(!it.is_wasm_interpreter_entry()); |
| #endif // V8_ENABLE_DRUMBRAKE |
| WasmFrame* frame = WasmFrame::cast(it.frame()); |
| |
| wasm::ExecutionTier tier = frame->wasm_code()->tier(); |
| MachineRepresentation mem_rep = |
| static_cast<MachineRepresentation>(info->mem_rep); |
| |
| const Address address = |
| reinterpret_cast<Address>(frame->trusted_instance_data() |
| ->memory_object(info->mem_index) |
| ->backing_store() |
| ->buffer_start()) + |
| info->offset; |
| |
| wasm::MemoryTraceEntry trace_entry = { |
| .offset = info->offset, |
| .function_index = frame->GetInnermostFunctionIndex(), |
| .mem_index = info->mem_index, |
| .frame_position = frame->position(), |
| .tier = tier, |
| .representation = mem_rep, |
| .is_store = static_cast<bool>(info->is_store), |
| .value_bytes = {}}; |
| int mem_rep_size = ElementSizeInBytes(mem_rep); |
| CHECK_GE(sizeof(trace_entry.value_bytes), mem_rep_size); |
| memcpy(trace_entry.value_bytes, reinterpret_cast<void*>(address), |
| mem_rep_size); |
| |
| wasm::WasmTracesForTesting& traces = wasm::GetWasmTracesForTesting(); |
| if (traces.should_store_trace) { |
| traces.memory_trace.push_back(trace_entry); |
| } else { |
| std::ostringstream ss; |
| PrintMemoryTraceString(trace_entry, frame->native_module(), ss); |
| ss << "\n"; |
| PrintF("%s", ss.str().c_str()); |
| } |
| |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_WasmTierUpFunction) { |
| SealHandleScope shs(isolate); |
| DisallowGarbageCollection no_gc; |
| DCHECK(!v8_flags.wasm_jitless); |
| |
| if (V8_UNLIKELY(v8_flags.wasm_generate_compilation_hints || |
| v8_flags.trace_wasm_generate_compilation_hints)) { |
| // These flags expect functions to remain compiled with Liftoff. |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| if (args.length() != 1 || |
| !WasmExportedFunction::IsWasmExportedFunction(args[0])) { |
| return CrashUnlessFuzzing(isolate); |
| } |
| Tagged<WasmExportedFunction> exp_fun = Cast<WasmExportedFunction>(args[0]); |
| auto func_data = exp_fun->shared()->wasm_exported_function_data(); |
| Tagged<WasmTrustedInstanceData> trusted_data = func_data->instance_data(); |
| int func_index = func_data->function_index(); |
| wasm::NativeModule* native_module = trusted_data->native_module(); |
| const wasm::WasmModule* module = native_module->module(); |
| if (static_cast<uint32_t>(func_index) < module->num_imported_functions || |
| static_cast<uint32_t>(func_index) >= module->functions.size()) { |
| return CrashUnlessFuzzing(isolate); |
| } |
| wasm::TierUpNowForTesting(isolate, trusted_data, func_index); |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_WasmTriggerTierUpForTesting) { |
| SealHandleScope shs(isolate); |
| DisallowGarbageCollection no_gc; |
| DCHECK(!v8_flags.wasm_jitless); |
| |
| if (V8_UNLIKELY(v8_flags.wasm_generate_compilation_hints || |
| v8_flags.trace_wasm_generate_compilation_hints)) { |
| // These flags expect functions to remain compiled with Liftoff. |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| if (args.length() != 1 || |
| !WasmExportedFunction::IsWasmExportedFunction(args[0])) { |
| return CrashUnlessFuzzing(isolate); |
| } |
| Tagged<WasmExportedFunction> exp_fun = Cast<WasmExportedFunction>(args[0]); |
| auto func_data = exp_fun->shared()->wasm_exported_function_data(); |
| Tagged<WasmTrustedInstanceData> trusted_data = func_data->instance_data(); |
| int func_index = func_data->function_index(); |
| wasm::NativeModule* native_module = trusted_data->native_module(); |
| const wasm::WasmModule* module = native_module->module(); |
| if (static_cast<uint32_t>(func_index) < module->num_imported_functions || |
| static_cast<uint32_t>(func_index) >= module->functions.size()) { |
| return CrashUnlessFuzzing(isolate); |
| } |
| wasm::TriggerTierUp(isolate, trusted_data, func_index); |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| static Tagged<Object> CreateWasmObject(Isolate* isolate, |
| base::Vector<const uint8_t> module_bytes, |
| bool is_struct) { |
| if (module_bytes.size() > v8_flags.wasm_max_module_size) { |
| return CrashUnlessFuzzing(isolate); |
| } |
| // Create and compile the wasm module. |
| wasm::ErrorThrower thrower(isolate, "CreateWasmObject"); |
| base::OwnedVector<const uint8_t> bytes = base::OwnedCopyOf(module_bytes); |
| wasm::WasmEngine* engine = wasm::GetWasmEngine(); |
| MaybeDirectHandle<WasmModuleObject> maybe_module_object = engine->SyncCompile( |
| isolate, wasm::WasmEnabledFeatures(), wasm::CompileTimeImports(), |
| &thrower, std::move(bytes)); |
| CHECK(!thrower.error()); |
| DirectHandle<WasmModuleObject> module_object; |
| if (!maybe_module_object.ToHandle(&module_object)) { |
| DCHECK(isolate->has_exception()); |
| return ReadOnlyRoots(isolate).exception(); |
| } |
| // Instantiate the module. |
| MaybeDirectHandle<WasmInstanceObject> maybe_instance = |
| engine->SyncInstantiate(isolate, &thrower, module_object, |
| Handle<JSReceiver>::null()); |
| CHECK(!thrower.error()); |
| DirectHandle<WasmInstanceObject> instance; |
| if (!maybe_instance.ToHandle(&instance)) { |
| DCHECK(isolate->has_exception()); |
| return ReadOnlyRoots(isolate).exception(); |
| } |
| CppGCManaged<wasm::NativeModule>::Ptr native_module = |
| module_object->native_module(); |
| const wasm::WasmModule* module = native_module->module(); |
| wasm::WasmValue value(int64_t{0x7AADF00DBAADF00D}); |
| wasm::ModuleTypeIndex type_index{0}; |
| Tagged<Map> map = |
| Cast<Map>(instance->trusted_data(isolate)->managed_object_maps()->get( |
| type_index.index)); |
| if (is_struct) { |
| const wasm::StructType* struct_type = module->struct_type(type_index); |
| SBXCHECK_EQ(struct_type->field_count(), 1); |
| SBXCHECK_EQ(struct_type->field(0), wasm::kWasmI64); |
| return *isolate->factory()->NewWasmStruct(struct_type, &value, |
| direct_handle(map, isolate)); |
| } else { |
| SBXCHECK_EQ(module->array_type(type_index)->element_type(), wasm::kWasmI64); |
| return *isolate->factory()->NewWasmArray( |
| wasm::kWasmI64, 1, value, direct_handle(map, isolate), |
| AllocationType::kYoung, SKIP_WRITE_BARRIER); |
| } |
| } |
| |
| // In jitless mode we don't support creation of real wasm objects (they require |
| // a NativeModule and instantiating that is not supported in jitless), so this |
| // function creates a frozen JS object that should behave the same as a wasm |
| // object within JS. |
| static Tagged<Object> CreateDummyWasmLookAlikeForFuzzing(Isolate* isolate) { |
| DirectHandle<JSObject> obj = isolate->factory()->NewJSObjectWithNullProto(); |
| CHECK(IsJSReceiver(*obj)); |
| MAYBE_RETURN(JSReceiver::SetIntegrityLevel(isolate, Cast<JSReceiver>(obj), |
| FROZEN, kThrowOnError), |
| ReadOnlyRoots(isolate).exception()); |
| return *obj; |
| } |
| |
| // Creates a new wasm struct with one i64 (value 0x7AADF00DBAADF00D). |
| RUNTIME_FUNCTION(Runtime_WasmStruct) { |
| HandleScope scope(isolate); |
| if (v8_flags.jitless && !v8_flags.wasm_jitless) { |
| return CreateDummyWasmLookAlikeForFuzzing(isolate); |
| } |
| /* Recreate with: |
| d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js'); |
| let builder = new WasmModuleBuilder(); |
| let struct = builder.addStruct([makeField(kWasmI64, false)]); |
| builder.instantiate(); |
| */ |
| static constexpr uint8_t wasm_module_bytes[] = { |
| 0x00, 0x61, 0x73, 0x6d, // wasm magic |
| 0x01, 0x00, 0x00, 0x00, // wasm version |
| 0x01, // section kind: Type |
| 0x07, // section length 7 |
| 0x01, 0x50, 0x00, // types count 1: subtype extensible, |
| // supertype count 0 |
| 0x5f, 0x01, 0x7e, 0x00, // kind: struct, field count 1: i64 immutable |
| }; |
| return CreateWasmObject(isolate, base::VectorOf(wasm_module_bytes), true); |
| } |
| |
| // Creates a new wasm array of type i64 with one element (0x7AADF00DBAADF00D). |
| RUNTIME_FUNCTION(Runtime_WasmArray) { |
| HandleScope scope(isolate); |
| if (v8_flags.jitless && !v8_flags.wasm_jitless) { |
| return CreateDummyWasmLookAlikeForFuzzing(isolate); |
| } |
| /* Recreate with: |
| d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js'); |
| let builder = new WasmModuleBuilder(); |
| let array = builder.addArray(kWasmI64, false); |
| builder.instantiate(); |
| */ |
| static constexpr uint8_t wasm_module_bytes[] = { |
| 0x00, 0x61, 0x73, 0x6d, // wasm magic |
| 0x01, 0x00, 0x00, 0x00, // wasm version |
| 0x01, // section kind: Type |
| 0x06, // section length 6 |
| 0x01, 0x50, 0x00, // types count 1: subtype extensible, |
| // supertype count 0 |
| 0x5e, 0x7e, 0x00, // kind: array i64 immutable |
| }; |
| return CreateWasmObject(isolate, base::VectorOf(wasm_module_bytes), false); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_WasmEnterDebugging) { |
| SealHandleScope shs(isolate); |
| DisallowGarbageCollection no_gc; |
| wasm::GetWasmEngine()->EnterDebuggingForIsolate(isolate); |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_WasmLeaveDebugging) { |
| SealHandleScope shs(isolate); |
| DisallowGarbageCollection no_gc; |
| wasm::GetWasmEngine()->LeaveDebuggingForIsolate(isolate); |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_IsWasmDebugFunction) { |
| SealHandleScope shs(isolate); |
| DisallowGarbageCollection no_gc; |
| if (args.length() != 1 || |
| !WasmExportedFunction::IsWasmExportedFunction(args[0])) { |
| return CrashUnlessFuzzing(isolate); |
| } |
| Tagged<WasmExportedFunction> exp_fun = Cast<WasmExportedFunction>(args[0]); |
| auto data = exp_fun->shared()->wasm_exported_function_data(); |
| wasm::NativeModule* native_module = data->instance_data()->native_module(); |
| const wasm::WasmModule* module = native_module->module(); |
| uint32_t func_index = data->function_index(); |
| if (func_index < module->num_imported_functions || |
| func_index >= module->functions.size()) { |
| return CrashUnlessFuzzing(isolate); |
| } |
| wasm::WasmCodeRefScope code_ref_scope; |
| wasm::WasmCode* code = native_module->GetCode(func_index); |
| return isolate->heap()->ToBoolean(code && code->is_liftoff() && |
| code->for_debugging()); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_IsLiftoffFunction) { |
| SealHandleScope shs(isolate); |
| DisallowGarbageCollection no_gc; |
| if (args.length() != 1 || |
| !WasmExportedFunction::IsWasmExportedFunction(args[0])) { |
| return CrashUnlessFuzzing(isolate); |
| } |
| Tagged<WasmExportedFunction> exp_fun = Cast<WasmExportedFunction>(args[0]); |
| auto data = exp_fun->shared()->wasm_exported_function_data(); |
| wasm::NativeModule* native_module = data->instance_data()->native_module(); |
| const wasm::WasmModule* module = native_module->module(); |
| uint32_t func_index = data->function_index(); |
| if (func_index < module->num_imported_functions || |
| func_index >= module->functions.size()) { |
| return CrashUnlessFuzzing(isolate); |
| } |
| wasm::WasmCodeRefScope code_ref_scope; |
| wasm::WasmCode* code = native_module->GetCode(func_index); |
| return isolate->heap()->ToBoolean(code && code->is_liftoff()); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_IsTurboFanFunction) { |
| SealHandleScope shs(isolate); |
| DisallowGarbageCollection no_gc; |
| if (args.length() != 1 || |
| !WasmExportedFunction::IsWasmExportedFunction(args[0])) { |
| return CrashUnlessFuzzing(isolate); |
| } |
| Tagged<WasmExportedFunction> exp_fun = Cast<WasmExportedFunction>(args[0]); |
| auto data = exp_fun->shared()->wasm_exported_function_data(); |
| wasm::NativeModule* native_module = data->instance_data()->native_module(); |
| const wasm::WasmModule* module = native_module->module(); |
| uint32_t func_index = data->function_index(); |
| if (func_index < module->num_imported_functions || |
| func_index >= module->functions.size()) { |
| return CrashUnlessFuzzing(isolate); |
| } |
| wasm::WasmCodeRefScope code_ref_scope; |
| wasm::WasmCode* code = native_module->GetCode(func_index); |
| return isolate->heap()->ToBoolean(code && code->is_turbofan()); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_IsUncompiledWasmFunction) { |
| SealHandleScope shs(isolate); |
| DisallowGarbageCollection no_gc; |
| if (args.length() != 1 || |
| !WasmExportedFunction::IsWasmExportedFunction(args[0])) { |
| return CrashUnlessFuzzing(isolate); |
| } |
| Tagged<WasmExportedFunction> exp_fun = Cast<WasmExportedFunction>(args[0]); |
| auto data = exp_fun->shared()->wasm_exported_function_data(); |
| wasm::NativeModule* native_module = data->instance_data()->native_module(); |
| const wasm::WasmModule* module = native_module->module(); |
| uint32_t func_index = data->function_index(); |
| if (func_index < module->num_imported_functions || |
| func_index >= module->functions.size()) { |
| return CrashUnlessFuzzing(isolate); |
| } |
| return isolate->heap()->ToBoolean(!native_module->HasCode(func_index)); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_FreezeWasmLazyCompilation) { |
| SealHandleScope shs(isolate); |
| DisallowGarbageCollection no_gc; |
| // This isn't exposed to fuzzers so doesn't need to handle invalid arguments. |
| DCHECK_EQ(args.length(), 1); |
| DCHECK(IsWasmInstanceObject(args[0])); |
| auto instance_object = Cast<WasmInstanceObject>(args[0]); |
| |
| instance_object->module_object()->native_module()->set_lazy_compile_frozen( |
| true); |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_FlushLiftoffCode) { |
| SealHandleScope shs(isolate); |
| DisallowGarbageCollection no_gc; |
| if (!v8_flags.flush_liftoff_code) return CrashUnlessFuzzing(isolate); |
| wasm::GetWasmEngine()->FlushLiftoffCode(); |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_WasmTriggerCodeGC) { |
| SealHandleScope shs(isolate); |
| DisallowGarbageCollection no_gc; |
| wasm::GetWasmEngine()->TriggerCodeGCForTesting(); |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_EstimateCurrentMemoryConsumption) { |
| HandleScope shs(isolate); |
| size_t result = wasm::GetWasmEngine()->EstimateCurrentMemoryConsumption(); |
| return *isolate->factory()->NewNumberFromSize(result); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_WasmCompiledExportWrappersCount) { |
| SealHandleScope shs(isolate); |
| DisallowGarbageCollection no_gc; |
| int count = isolate->counters() |
| ->wasm_compiled_export_wrapper() |
| ->GetInternalPointer() |
| ->load(); |
| return Smi::FromInt(count); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_WasmDeoptsExecutedCount) { |
| SealHandleScope shs(isolate); |
| DisallowGarbageCollection no_gc; |
| int count = wasm::GetWasmEngine()->GetDeoptsExecutedCount(); |
| return Smi::FromInt(count); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_WasmDeoptsExecutedForFunction) { |
| SealHandleScope shs(isolate); |
| DisallowGarbageCollection no_gc; |
| if (args.length() != 1 || |
| !WasmExportedFunction::IsWasmExportedFunction(args[0])) { |
| return CrashUnlessFuzzing(isolate); |
| } |
| Tagged<WasmExportedFunction> exp_fun = Cast<WasmExportedFunction>(args[0]); |
| auto func_data = exp_fun->shared()->wasm_exported_function_data(); |
| const wasm::WasmModule* module = |
| func_data->instance_data()->native_module()->module(); |
| uint32_t func_index = func_data->function_index(); |
| if (func_index < module->num_imported_functions || |
| func_index >= module->functions.size()) { |
| return CrashUnlessFuzzing(isolate); |
| } |
| const wasm::TypeFeedbackStorage& feedback = module->type_feedback; |
| base::MutexGuard mutex_guard(&feedback.mutex); |
| auto entry = feedback.deopt_count_for_function.find(func_index); |
| if (entry == feedback.deopt_count_for_function.end()) { |
| return Smi::FromInt(0); |
| } |
| return Smi::FromInt(entry->second); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_WasmSwitchToTheCentralStackCount) { |
| SealHandleScope shs(isolate); |
| DisallowGarbageCollection no_gc; |
| int count = isolate->wasm_switch_to_the_central_stack_counter(); |
| return Smi::FromInt(count); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_CheckIsOnCentralStack) { |
| SealHandleScope shs(isolate); |
| DisallowGarbageCollection no_gc; |
| // This function verifies that itself, and therefore the JS function that |
| // called it, is running on the central stack. This is used to check that wasm |
| // switches to the central stack to run JS imports. |
| CHECK(isolate->IsOnCentralStack()); |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| // Takes a type index, creates a ValueType for (ref $index) and returns its |
| // raw bit field. Useful for sandbox tests. |
| RUNTIME_FUNCTION(Runtime_BuildRefTypeBitfield) { |
| SealHandleScope shs(isolate); |
| DisallowGarbageCollection no_gc; |
| if (args.length() != 2 || !IsSmi(args[0]) || !IsWasmInstanceObject(args[1])) { |
| return CrashUnlessFuzzing(isolate); |
| } |
| // Allow fuzzers to generate invalid types, but avoid running into the |
| // DCHECK in base::BitField::encode(). |
| static constexpr uint32_t kMask = (1u << wasm::ValueType::kNumIndexBits) - 1; |
| wasm::ModuleTypeIndex type_index{ |
| static_cast<uint32_t>(Cast<Smi>(args[0]).value()) & kMask}; |
| const wasm::WasmModule* module = |
| Cast<WasmInstanceObject>(args[1])->trusted_data(isolate)->module(); |
| // If we get an invalid type index, make up the additional data; the result |
| // may still be useful for fuzzers for causing interesting confusion. |
| wasm::ValueType t = module->has_type(type_index) |
| ? wasm::ValueType::Ref(module->heap_type(type_index)) |
| : wasm::ValueType::Ref(type_index, SharedFlag{false}, |
| wasm::RefTypeKind::kStruct); |
| return Smi::FromInt(t.raw_bit_field()); |
| } |
| |
| // The GenerateRandomWasmModule function is only implemented in non-official |
| // builds (to save binary size). Hence also skip the runtime function in |
| // official builds. |
| #ifdef V8_WASM_RANDOM_FUZZERS |
| RUNTIME_FUNCTION(Runtime_WasmGenerateRandomModule) { |
| if (v8_flags.jitless) { |
| return CrashUnlessFuzzing(isolate); |
| } |
| HandleScope scope{isolate}; |
| Zone temporary_zone{isolate->allocator(), "WasmGenerateRandomModule"}; |
| constexpr size_t kMaxInputBytes = 512; |
| ZoneVector<uint8_t> input_bytes{&temporary_zone}; |
| auto add_input_bytes = [&input_bytes](void* bytes, size_t max_bytes) { |
| size_t num_bytes = std::min(kMaxInputBytes - input_bytes.size(), max_bytes); |
| input_bytes.resize(input_bytes.size() + num_bytes); |
| memcpy(input_bytes.end() - num_bytes, bytes, num_bytes); |
| }; |
| if (args.length() == 0) { |
| // If we are called without any arguments, use the RNG from the isolate to |
| // generate between 1 and kMaxInputBytes random bytes. |
| int num_bytes = |
| 1 + isolate->random_number_generator()->NextInt(kMaxInputBytes); |
| input_bytes.resize(num_bytes); |
| isolate->random_number_generator()->NextBytes(input_bytes.data(), |
| num_bytes); |
| } else { |
| for (int i = 0; i < args.length(); ++i) { |
| if (IsJSTypedArray(args[i])) { |
| Tagged<JSTypedArray> typed_array = Cast<JSTypedArray>(args[i]); |
| add_input_bytes(typed_array->DataPtr(), typed_array->GetByteLength()); |
| } else if (IsJSArrayBuffer(args[i])) { |
| Tagged<JSArrayBuffer> array_buffer = Cast<JSArrayBuffer>(args[i]); |
| add_input_bytes(array_buffer->backing_store(), |
| array_buffer->GetByteLength()); |
| } else if (IsSmi(args[i])) { |
| int smi_value = Cast<Smi>(args[i]).value(); |
| add_input_bytes(&smi_value, kIntSize); |
| } else if (IsHeapNumber(args[i])) { |
| double value = Cast<HeapNumber>(args[i])->value(); |
| add_input_bytes(&value, kDoubleSize); |
| } else { |
| // TODO(14637): Extract bytes from more types. |
| } |
| } |
| } |
| |
| // Avoid generating SIMD if the CPU does not support it, or it's disabled via |
| // flags. Otherwise, do not limit the generated expressions and types. |
| constexpr auto kAllOptions = |
| wasm::fuzzing::WasmModuleGenerationOptions::All(); |
| constexpr auto kNoSimdOptions = wasm::fuzzing::WasmModuleGenerationOptions{ |
| {wasm::fuzzing::WasmModuleGenerationOption::kGenerateWasmGC}}; |
| static_assert( |
| (kNoSimdOptions | |
| wasm::fuzzing::WasmModuleGenerationOptions{ |
| {wasm::fuzzing::WasmModuleGenerationOption::kGenerateSIMD}}) == |
| kAllOptions); |
| auto options = |
| wasm::CheckHardwareSupportsSimd() ? kAllOptions : kNoSimdOptions; |
| |
| base::Vector<const uint8_t> module_bytes = |
| wasm::fuzzing::GenerateRandomWasmModule(&temporary_zone, options, |
| base::VectorOf(input_bytes)); |
| |
| // Fuzzers can set `--wasm-max-module-size` to small values and then call |
| // %WasmGenerateRandomModule() (see https://crbug.com/382816108). |
| if (module_bytes.size() > v8_flags.wasm_max_module_size) { |
| return CrashUnlessFuzzing(isolate); |
| } |
| |
| wasm::ErrorThrower thrower{isolate, "WasmGenerateRandomModule"}; |
| MaybeDirectHandle<WasmModuleObject> maybe_module_object = |
| wasm::GetWasmEngine()->SyncCompile(isolate, |
| wasm::WasmEnabledFeatures::FromFlags(), |
| wasm::CompileTimeImports{}, &thrower, |
| base::OwnedCopyOf(module_bytes)); |
| if (thrower.error()) { |
| FATAL( |
| "wasm::GenerateRandomWasmModule produced a module which did not " |
| "compile: %s", |
| thrower.error_msg()); |
| } |
| return *maybe_module_object.ToHandleChecked(); |
| } |
| #endif // V8_WASM_RANDOM_FUZZERS |
| |
| } // namespace v8::internal |