| // Copyright 2014 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/runtime/runtime.h" |
| |
| #include <stdio.h> |
| |
| #include <iomanip> |
| #include <memory> |
| |
| #include "include/v8-function.h" |
| #include "include/v8-profiler.h" |
| #include "src/api/api-inl.h" |
| #include "src/base/iterator.h" |
| #include "src/base/macros.h" |
| #include "src/base/numbers/double.h" |
| #include "src/base/strong-alias.h" |
| #include "src/codegen/compiler.h" |
| #include "src/codegen/pending-optimization-table.h" |
| #include "src/common/globals.h" |
| #include "src/common/synchronization-point-support.h" |
| #include "src/compiler-dispatcher/lazy-compile-dispatcher.h" |
| #include "src/compiler-dispatcher/optimizing-compile-dispatcher.h" |
| #include "src/debug/debug-evaluate.h" |
| #include "src/deoptimizer/deoptimizer.h" |
| #include "src/execution/arguments-inl.h" |
| #include "src/execution/frames-inl.h" |
| #include "src/execution/isolate-inl.h" |
| #include "src/execution/protectors-inl.h" |
| #include "src/execution/tiering-manager.h" |
| #include "src/flags/flags.h" |
| #include "src/heap/heap-layout-inl.h" |
| #include "src/heap/heap-write-barrier-inl.h" |
| #include "src/heap/pretenuring-handler-inl.h" |
| #include "src/ic/stub-cache.h" |
| #include "src/objects/abstract-code-inl.h" |
| #include "src/objects/bytecode-array.h" |
| #include "src/objects/js-collection-inl.h" |
| #include "src/objects/object-conversions-inl.h" |
| #include "src/profiler/heap-profiler.h" |
| #include "src/sandbox/bytecode-verifier.h" |
| #include "src/utils/utils.h" |
| #ifdef V8_ENABLE_MAGLEV |
| #include "src/maglev/maglev-concurrent-dispatcher.h" |
| #endif // V8_ENABLE_MAGLEV |
| #include "src/objects/js-array-buffer-inl.h" |
| #include "src/objects/js-atomics-synchronization-inl.h" |
| #include "src/objects/js-function-inl.h" |
| #include "src/objects/js-regexp-inl.h" |
| #include "src/objects/smi.h" |
| #include "src/profiler/heap-snapshot-generator.h" |
| #include "src/regexp/regexp.h" |
| #include "src/snapshot/snapshot.h" |
| |
| #ifdef V8_ENABLE_MAGLEV |
| #include "src/maglev/maglev.h" |
| #endif // V8_ENABLE_MAGLEV |
| |
| #if V8_ENABLE_WEBASSEMBLY |
| #include "src/wasm/wasm-engine.h" |
| #endif // V8_ENABLE_WEBASSEMBLY |
| |
| namespace v8 { |
| namespace internal { |
| |
| namespace { |
| |
| // This macro is mostly used instead of (D)CHECKs for functions exposed to |
| // fuzzers. TODO(353685107): consider being more permissive in functions using |
| // this. For example, for fuzzing we could probably allow excess arguments, |
| #define CHECK_UNLESS_FUZZING(condition) \ |
| do { \ |
| if (V8_UNLIKELY(!(condition))) { \ |
| if (v8_flags.fuzzing) { \ |
| return GetReadOnlyRoots().undefined_value(); \ |
| } else { \ |
| CHECK_FAILED_HANDLER(#condition); \ |
| } \ |
| } \ |
| } while (false) |
| |
| #define CHECK_UNLESS_FUZZING_RETURN_FALSE(condition) \ |
| do { \ |
| if (V8_UNLIKELY(!(condition))) { \ |
| if (v8_flags.fuzzing) { \ |
| return false; \ |
| } else { \ |
| CHECK_FAILED_HANDLER(#condition); \ |
| } \ |
| } \ |
| } while (false) |
| |
| V8_WARN_UNUSED_RESULT bool CheckMarkedForManualOptimization( |
| Isolate* isolate, Tagged<JSFunction> function) { |
| if (!ManualOptimizationTable::IsMarkedForManualOptimization(isolate, |
| function)) { |
| PrintF("Error: Function "); |
| ShortPrint(function); |
| PrintF( |
| " should be prepared for optimization with " |
| "%%PrepareFunctionForOptimization before " |
| "%%OptimizeFunctionOnNextCall / %%OptimizeMaglevOnNextCall"); |
| return false; |
| } |
| return true; |
| } |
| |
| // Returns |value| unless correctness-fuzzer-supressions is enabled, |
| // otherwise returns undefined_value. |
| V8_WARN_UNUSED_RESULT Tagged<Object> ReturnFuzzSafe(Tagged<Object> value, |
| Isolate* isolate) { |
| return v8_flags.correctness_fuzzer_suppressions |
| ? ReadOnlyRoots(isolate).undefined_value() |
| : value; |
| } |
| |
| // Assert that the given argument is a number within the Int32 range |
| // and convert it to int32_t. If the argument is not an Int32 we crash if not |
| // in fuzzing mode. |
| #define CONVERT_INT32_ARG_FUZZ_SAFE(name, index) \ |
| CHECK_UNLESS_FUZZING(IsNumber(args[index])); \ |
| int32_t name = 0; \ |
| CHECK_UNLESS_FUZZING(Object::ToInt32(args[index], &name)); |
| |
| // Cast the given object to a boolean and store it in a variable with |
| // the given name. If the object is not a boolean we crash if not in |
| // fuzzing mode. |
| #define CONVERT_BOOLEAN_ARG_FUZZ_SAFE(name, index) \ |
| CHECK_UNLESS_FUZZING(IsBoolean(args[index])); \ |
| bool name = IsTrue(args[index]); |
| |
| } // namespace |
| |
| RUNTIME_FUNCTION(Runtime_ClearMegamorphicStubCache) { |
| HandleScope scope(isolate); |
| isolate->load_stub_cache()->Clear(); |
| isolate->store_stub_cache()->Clear(); |
| isolate->define_own_stub_cache()->Clear(); |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_ConstructDouble) { |
| HandleScope scope(isolate); |
| // This isn't exposed to fuzzers so doesn't need to handle invalid arguments. |
| DCHECK_EQ(args.length(), 2); |
| uint32_t hi = NumberToUint32(args[0]); |
| uint32_t lo = NumberToUint32(args[1]); |
| uint64_t result = (static_cast<uint64_t>(hi) << 32) | lo; |
| return *isolate->factory()->NewNumber(base::uint64_to_double(result)); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_StringIsFlat) { |
| HandleScope scope(isolate); |
| DCHECK_EQ(args.length(), 1); |
| DirectHandle<String> s = args.at<String>(0); |
| return isolate->heap()->ToBoolean(s->IsFlat()); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_ConstructConsString) { |
| HandleScope scope(isolate); |
| CHECK_UNLESS_FUZZING(args.length() == 2); |
| CHECK_UNLESS_FUZZING(IsString(args[0])); |
| CHECK_UNLESS_FUZZING(IsString(args[1])); |
| DirectHandle<String> left = args.at<String>(0); |
| DirectHandle<String> right = args.at<String>(1); |
| CHECK_UNLESS_FUZZING(left->length() + right->length() >= |
| ConsString::kMinLength); |
| CHECK_UNLESS_FUZZING(left->length() + right->length() <= String::kMaxLength); |
| return *isolate->factory()->NewConsString(left, right).ToHandleChecked(); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_ConstructSlicedString) { |
| HandleScope scope(isolate); |
| CHECK_UNLESS_FUZZING(args.length() == 2); |
| CHECK_UNLESS_FUZZING(IsString(args[0])); |
| CHECK_UNLESS_FUZZING(IsSmi(args[1])); |
| Handle<String> string = args.at<String>(0); |
| uint32_t index = args.smi_value_at(1); |
| |
| CHECK_UNLESS_FUZZING(index < string->length()); |
| |
| DirectHandle<String> sliced_string = |
| isolate->factory()->NewSubString(string, index, string->length()); |
| CHECK_UNLESS_FUZZING(IsSlicedString(*sliced_string)); |
| return *sliced_string; |
| } |
| |
| RUNTIME_FUNCTION(Runtime_ConstructInternalizedString) { |
| HandleScope scope(isolate); |
| CHECK_UNLESS_FUZZING(args.length() == 1); |
| CHECK_UNLESS_FUZZING(IsString(args[0])); |
| Handle<String> string = args.at<String>(0); |
| DirectHandle<String> internalized = |
| isolate->factory()->InternalizeString(string); |
| // The argument was either already an internalized string or it is now a thin |
| // string to an internalized string. |
| // For shared strings, one of the following happens: |
| // 1) With `--shared-string-table` the string is inserted into the |
| // `StringForwardingTable` to reduce the overhead of repeated |
| // internalization. |
| // 2) Without `--shared-string-table` the original string gets copied on each |
| // internalization inte the unshared heap and from there it needs to be |
| // internalized each time. |
| // In either case, the input shared string does not change its shape on |
| // internalization. |
| CHECK(IsInternalizedString(*string) || |
| (IsThinString(*string) && |
| IsInternalizedString(Cast<ThinString>(*string)->actual())) || |
| HeapLayout::InAnySharedSpace(*string)); |
| CHECK(IsInternalizedString(*internalized)); |
| return *internalized; |
| } |
| |
| RUNTIME_FUNCTION(Runtime_ConstructThinString) { |
| HandleScope scope(isolate); |
| CHECK_UNLESS_FUZZING(args.length() == 1); |
| CHECK_UNLESS_FUZZING(IsString(args[0])); |
| Handle<String> string = args.at<String>(0); |
| if (IsThinString(*string)) { |
| return *string; |
| } |
| if (!IsConsString(*string)) { |
| CHECK_UNLESS_FUZZING(string->length() >= ConsString::kMinLength); |
| string = isolate->factory()->NewConsString( |
| isolate->factory()->empty_string(), string, string->length(), |
| string->IsOneByteRepresentation(), |
| // Pretenure to ensure it stays thin. |
| AllocationType::kOld); |
| } |
| CHECK(IsConsString(*string)); |
| DirectHandle<String> internalized = |
| isolate->factory()->InternalizeString(string); |
| CHECK_NE(*internalized, *string); |
| CHECK(IsThinString(*string)); |
| return *string; |
| } |
| |
| RUNTIME_FUNCTION(Runtime_DeoptimizeFunction) { |
| HandleScope scope(isolate); |
| CHECK_UNLESS_FUZZING(args.length() == 1); |
| |
| Handle<Object> function_object = args.at(0); |
| CHECK_UNLESS_FUZZING(IsJSFunction(*function_object)); |
| auto function = Cast<JSFunction>(function_object); |
| |
| if (function->IsTieringRequestedOrInProgress(isolate)) { |
| if (function->tiering_in_progress()) { |
| // Abort optimization so that calling DeoptimizeFunction on a function |
| // currently being optimized ends up with a non-optimized function. |
| isolate->AbortConcurrentOptimization(BlockingBehavior::kBlock); |
| } |
| function->ResetTieringRequests(isolate); |
| } |
| |
| if (function->HasAttachedOptimizedCode(isolate)) { |
| Deoptimizer::DeoptimizeFunction(*function, LazyDeoptimizeReason::kTesting, |
| function->code(isolate)); |
| } |
| |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_DeoptimizeNow) { |
| HandleScope scope(isolate); |
| |
| DirectHandle<JSFunction> function; |
| |
| // Find the JavaScript function on the top of the stack. |
| JavaScriptStackFrameIterator it(isolate); |
| if (!it.done()) function = direct_handle(it.frame()->function(), isolate); |
| CHECK_UNLESS_FUZZING(!function.is_null()); |
| |
| if (function->HasAttachedOptimizedCode(isolate)) { |
| Deoptimizer::DeoptimizeFunction(*function, LazyDeoptimizeReason::kTesting, |
| function->code(isolate)); |
| } |
| |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_LeakHole) { |
| HandleScope scope(isolate); |
| |
| // TODO(chromium:1445008): once we have multiple different hole values, we |
| // could make this function take a number as argument and return the nth hole |
| // value, or a random hole if the argument is undefined. |
| return ReadOnlyRoots(isolate).the_hole_value(); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_RunningInSimulator) { |
| SealHandleScope shs(isolate); |
| #if defined(USE_SIMULATOR) |
| return ReadOnlyRoots(isolate).true_value(); |
| #else |
| return ReadOnlyRoots(isolate).false_value(); |
| #endif |
| } |
| |
| RUNTIME_FUNCTION(Runtime_RuntimeEvaluateREPL) { |
| HandleScope scope(isolate); |
| CHECK_UNLESS_FUZZING(args.length() == 1); |
| CHECK_UNLESS_FUZZING(IsString(args[0])); |
| Handle<String> source = args.at<String>(0); |
| DirectHandle<Object> result; |
| ASSIGN_RETURN_FAILURE_ON_EXCEPTION( |
| isolate, result, |
| DebugEvaluate::Global(isolate, source, |
| debug::EvaluateGlobalMode::kDefault, |
| REPLMode::kYes)); |
| |
| return *result; |
| } |
| |
| RUNTIME_FUNCTION(Runtime_ICsAreEnabled) { |
| SealHandleScope shs(isolate); |
| return isolate->heap()->ToBoolean(v8_flags.use_ic); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_IsConcurrentRecompilationSupported) { |
| SealHandleScope shs(isolate); |
| return isolate->heap()->ToBoolean( |
| isolate->concurrent_recompilation_enabled()); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_IsAtomicsWaitAllowed) { |
| SealHandleScope shs(isolate); |
| return isolate->heap()->ToBoolean(isolate->allow_atomics_wait()); |
| } |
| |
| namespace { |
| |
| bool CanOptimizeFunction(CodeKind target_kind, |
| DirectHandle<JSFunction> function, Isolate* isolate, |
| IsCompiledScope* is_compiled_scope) { |
| // The following conditions were lifted (in part) from the DCHECK inside |
| // JSFunction::MarkForOptimization(). |
| |
| // If function isn't compiled, compile it now. |
| CHECK_UNLESS_FUZZING_RETURN_FALSE(is_compiled_scope->is_compiled() || |
| Compiler::Compile(isolate, function, |
| Compiler::CLEAR_EXCEPTION, |
| is_compiled_scope)); |
| |
| if (target_kind == CodeKind::TURBOFAN_JS && !v8_flags.turbofan) return false; |
| if (target_kind == CodeKind::MAGLEV && !maglev::IsMaglevEnabled()) { |
| return false; |
| } |
| |
| if (function->shared()->optimization_disabled(target_kind)) { |
| return false; |
| } |
| |
| // If we're fuzzing, allow having not marked the function for manual |
| // optimization (if the steps below succeed). |
| if (!v8_flags.fuzzing) { |
| CHECK(CheckMarkedForManualOptimization(isolate, *function)); |
| } |
| |
| CHECK_UNLESS_FUZZING_RETURN_FALSE( |
| !function->is_compiled(isolate) || |
| function->HasAvailableCodeKind(isolate, CodeKind::INTERPRETED_FUNCTION)); |
| |
| if (function->HasAvailableCodeKind(isolate, target_kind) || |
| function->HasAvailableHigherTierCodeThan(isolate, target_kind)) { |
| DCHECK(function->HasAttachedOptimizedCode(isolate) || |
| function->ChecksTieringState(isolate)); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| Tagged<Object> OptimizeFunctionOnNextCall(RuntimeArguments& args, |
| Isolate* isolate, |
| CodeKind target_kind) { |
| CHECK_UNLESS_FUZZING(args.length() == 1 || args.length() == 2); |
| |
| DirectHandle<Object> function_object = args.at(0); |
| CHECK_UNLESS_FUZZING(IsJSFunction(*function_object)); |
| DirectHandle<JSFunction> function = Cast<JSFunction>(function_object); |
| |
| IsCompiledScope is_compiled_scope( |
| function->shared()->is_compiled_scope(isolate)); |
| if (!CanOptimizeFunction(target_kind, function, isolate, |
| &is_compiled_scope)) { |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| ConcurrencyMode concurrency_mode = ConcurrencyMode::kSynchronous; |
| if (args.length() == 2) { |
| DirectHandle<Object> type = args.at(1); |
| CHECK_UNLESS_FUZZING(IsString(*type)); |
| if (Cast<String>(type)->IsOneByteEqualTo( |
| base::StaticCharVector("concurrent")) && |
| isolate->concurrent_recompilation_enabled()) { |
| concurrency_mode = ConcurrencyMode::kConcurrent; |
| } |
| } |
| |
| // This function may not have been lazily compiled yet, even though its shared |
| // function has. |
| if (!function->is_compiled(isolate)) { |
| DCHECK(function->shared()->HasBytecodeArray()); |
| Tagged<Code> code = *BUILTIN_CODE(isolate, InterpreterEntryTrampoline); |
| if (function->shared()->HasBaselineCode()) { |
| code = function->shared()->baseline_code(kAcquireLoad); |
| } |
| function->UpdateCode(isolate, code); |
| } |
| |
| TraceManualRecompile(*function, target_kind, concurrency_mode); |
| JSFunction::EnsureFeedbackVector(isolate, function, &is_compiled_scope); |
| if (function->GetActiveTier(isolate) != target_kind) { |
| function->RequestOptimization(isolate, target_kind, concurrency_mode); |
| } |
| |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| bool EnsureCompiledAndFeedbackVector(Isolate* isolate, |
| DirectHandle<JSFunction> function, |
| IsCompiledScope* is_compiled_scope) { |
| *is_compiled_scope = function->shared()->is_compiled_scope(isolate); |
| |
| // If function isn't compiled, compile it now. |
| if (!is_compiled_scope->is_compiled()) { |
| // Check function allows lazy compilation. |
| DCHECK(function->shared()->allows_lazy_compilation()); |
| if (!Compiler::Compile(isolate, function, Compiler::CLEAR_EXCEPTION, |
| is_compiled_scope)) { |
| return false; |
| } |
| } |
| |
| // Ensure function has a feedback vector to hold type feedback for |
| // optimization. |
| if (!function->shared()->HasFeedbackMetadata()) { |
| return false; |
| } |
| JSFunction::EnsureFeedbackVector(isolate, function, is_compiled_scope); |
| return true; |
| } |
| |
| } // namespace |
| |
| RUNTIME_FUNCTION(Runtime_CompileBaseline) { |
| HandleScope scope(isolate); |
| CHECK_UNLESS_FUZZING(args.length() == 1); |
| DirectHandle<Object> function_object = args.at(0); |
| CHECK_UNLESS_FUZZING(IsJSFunction(*function_object)); |
| DirectHandle<JSFunction> function = Cast<JSFunction>(function_object); |
| |
| IsCompiledScope is_compiled_scope = |
| function->shared()->is_compiled_scope(isolate); |
| |
| CHECK_UNLESS_FUZZING(function->shared()->IsUserJavaScript()); |
| |
| // First compile the bytecode, if we have to. |
| CHECK_UNLESS_FUZZING(is_compiled_scope.is_compiled() || |
| Compiler::Compile(isolate, function, |
| Compiler::CLEAR_EXCEPTION, |
| &is_compiled_scope)); |
| |
| CHECK_UNLESS_FUZZING(Compiler::CompileBaseline( |
| isolate, function, Compiler::CLEAR_EXCEPTION, &is_compiled_scope)); |
| |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| // TODO(v8:7700): Remove this function once we no longer need it to measure |
| // maglev compile times. For normal tierup, OptimizeMaglevOnNextCall should be |
| // used instead. |
| #ifdef V8_ENABLE_MAGLEV |
| RUNTIME_FUNCTION(Runtime_BenchMaglev) { |
| HandleScope scope(isolate); |
| DCHECK_EQ(args.length(), 2); |
| Handle<JSFunction> function = args.at<JSFunction>(0); |
| int count = args.smi_value_at(1); |
| |
| DirectHandle<Code> code; |
| base::ElapsedTimer timer; |
| timer.Start(); |
| code = Maglev::Compile(isolate, function, BytecodeOffset::None()) |
| .ToHandleChecked(); |
| for (int i = 1; i < count; ++i) { |
| HandleScope handle_scope(isolate); |
| Maglev::Compile(isolate, function, BytecodeOffset::None()); |
| } |
| PrintF("Maglev compile time: %g ms!\n", |
| timer.Elapsed().InMillisecondsF() / count); |
| |
| function->UpdateOptimizedCode(isolate, *code); |
| |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| #else |
| RUNTIME_FUNCTION(Runtime_BenchMaglev) { |
| PrintF("Maglev is not enabled.\n"); |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| #endif // V8_ENABLE_MAGLEV |
| |
| RUNTIME_FUNCTION(Runtime_BenchTurbofan) { |
| HandleScope scope(isolate); |
| DCHECK_EQ(args.length(), 2); |
| DirectHandle<JSFunction> function = args.at<JSFunction>(0); |
| int count = args.smi_value_at(1); |
| |
| base::ElapsedTimer timer; |
| timer.Start(); |
| Compiler::CompileOptimized(isolate, function, ConcurrencyMode::kSynchronous, |
| CodeKind::TURBOFAN_JS); |
| for (int i = 1; i < count; ++i) { |
| Compiler::CompileOptimized(isolate, function, ConcurrencyMode::kSynchronous, |
| CodeKind::TURBOFAN_JS); |
| } |
| |
| double compile_time = timer.Elapsed().InMillisecondsF() / count; |
| |
| return *isolate->factory()->NewNumber(compile_time); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_ActiveTierIsIgnition) { |
| HandleScope scope(isolate); |
| CHECK_UNLESS_FUZZING(args.length() == 1); |
| CHECK_UNLESS_FUZZING(IsJSFunction(args[0])); |
| DirectHandle<JSFunction> function = args.at<JSFunction>(0); |
| return isolate->heap()->ToBoolean(function->ActiveTierIsIgnition(isolate)); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_ActiveTierIsSparkplug) { |
| HandleScope scope(isolate); |
| CHECK_UNLESS_FUZZING(args.length() == 1); |
| CHECK_UNLESS_FUZZING(IsJSFunction(args[0])); |
| DirectHandle<JSFunction> function = args.at<JSFunction>(0); |
| return isolate->heap()->ToBoolean(function->ActiveTierIsBaseline(isolate)); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_ActiveTierIsMaglev) { |
| HandleScope scope(isolate); |
| CHECK_UNLESS_FUZZING(args.length() == 1); |
| CHECK_UNLESS_FUZZING(IsJSFunction(args[0])); |
| DirectHandle<JSFunction> function = args.at<JSFunction>(0); |
| return isolate->heap()->ToBoolean(function->ActiveTierIsMaglev(isolate)); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_ActiveTierIsTurbofan) { |
| HandleScope scope(isolate); |
| CHECK_UNLESS_FUZZING(args.length() == 1); |
| CHECK_UNLESS_FUZZING(IsJSFunction(args[0])); |
| DirectHandle<JSFunction> function = args.at<JSFunction>(0); |
| return isolate->heap()->ToBoolean(function->ActiveTierIsTurbofan(isolate)); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_IsSparkplugEnabled) { |
| return isolate->heap()->ToBoolean(v8_flags.sparkplug); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_IsMaglevEnabled) { |
| return isolate->heap()->ToBoolean(maglev::IsMaglevEnabled()); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_IsTurbofanEnabled) { |
| return isolate->heap()->ToBoolean(v8_flags.turbofan); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_CurrentFrameIsTurbofan) { |
| HandleScope scope(isolate); |
| JavaScriptStackFrameIterator it(isolate); |
| return isolate->heap()->ToBoolean(it.frame()->is_turbofan()); |
| } |
| |
| #ifdef V8_ENABLE_MAGLEV |
| RUNTIME_FUNCTION(Runtime_OptimizeMaglevOnNextCall) { |
| HandleScope scope(isolate); |
| return OptimizeFunctionOnNextCall( |
| args, isolate, |
| v8_flags.optimize_maglev_optimizes_to_turbofan ? CodeKind::TURBOFAN_JS |
| : CodeKind::MAGLEV); |
| } |
| #else |
| RUNTIME_FUNCTION(Runtime_OptimizeMaglevOnNextCall) { |
| if (!v8_flags.fuzzing) PrintF("Maglev is not enabled.\n"); |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| #endif // V8_ENABLE_MAGLEV |
| |
| // TODO(jgruber): Rename to OptimizeTurbofanOnNextCall. |
| RUNTIME_FUNCTION(Runtime_OptimizeFunctionOnNextCall) { |
| HandleScope scope(isolate); |
| return OptimizeFunctionOnNextCall( |
| args, isolate, |
| v8_flags.optimize_on_next_call_optimizes_to_maglev |
| ? CodeKind::MAGLEV |
| : CodeKind::TURBOFAN_JS); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_ExhaustInterruptBudget) { |
| HandleScope scope(isolate); |
| CHECK_UNLESS_FUZZING(args.length() == 1); |
| CHECK_UNLESS_FUZZING(IsJSFunction(args[0])); |
| DirectHandle<JSFunction> function = args.at<JSFunction>(0); |
| |
| if (!function->has_feedback_vector()) { |
| IsCompiledScope is_compiled_scope; |
| CHECK_UNLESS_FUZZING( |
| EnsureCompiledAndFeedbackVector(isolate, function, &is_compiled_scope)); |
| } |
| |
| CHECK_UNLESS_FUZZING(function->has_feedback_vector()); |
| CHECK_UNLESS_FUZZING(function->shared()->HasBytecodeArray()); |
| |
| function->SetInterruptBudget(isolate, BudgetModification::kReset); |
| function->raw_feedback_cell()->set_interrupt_budget(0); |
| |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_EnsureFeedbackVectorForFunction) { |
| HandleScope scope(isolate); |
| CHECK_UNLESS_FUZZING(args.length() == 1); |
| CHECK_UNLESS_FUZZING(IsJSFunction(args[0])); |
| DirectHandle<JSFunction> function = args.at<JSFunction>(0); |
| if (function->has_feedback_vector()) { |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| IsCompiledScope is_compiled_scope; |
| EnsureCompiledAndFeedbackVector(isolate, function, &is_compiled_scope); |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_PrepareFunctionForOptimization) { |
| HandleScope scope(isolate); |
| CHECK_UNLESS_FUZZING(args.length() == 1 || args.length() == 2); |
| CHECK_UNLESS_FUZZING(IsJSFunction(args[0])); |
| DirectHandle<JSFunction> function = args.at<JSFunction>(0); |
| |
| IsCompiledScope is_compiled_scope; |
| CHECK_UNLESS_FUZZING( |
| EnsureCompiledAndFeedbackVector(isolate, function, &is_compiled_scope)); |
| |
| // If optimization is disabled for the function, return without marking it for |
| // manual optimization |
| if (function->shared()->all_optimization_disabled()) { |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| // Hold onto the bytecode array between marking and optimization to ensure |
| // it's not flushed. |
| ManualOptimizationTable::MarkFunctionForManualOptimization( |
| isolate, function, &is_compiled_scope); |
| |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| namespace { |
| |
| void FinalizeOptimization(Isolate* isolate) { |
| DCHECK(isolate->concurrent_recompilation_enabled()); |
| isolate->WaitForConcurrentOptimizationJobs(); |
| isolate->optimizing_compile_dispatcher()->InstallOptimizedFunctions(); |
| isolate->optimizing_compile_dispatcher()->set_finalize(true); |
| |
| #if V8_ENABLE_MAGLEV |
| if (isolate->maglev_concurrent_dispatcher()->is_enabled()) { |
| isolate->maglev_concurrent_dispatcher()->FinalizeFinishedJobs(); |
| } |
| #endif // V8_ENABLE_MAGLEV |
| } |
| |
| BytecodeOffset OffsetOfNextJumpLoop(Isolate* isolate, |
| Handle<BytecodeArray> bytecode_array, |
| int current_offset) { |
| interpreter::BytecodeArrayIterator it(bytecode_array, current_offset); |
| |
| // First, look for a loop that contains the current bytecode offset. |
| for (; !it.done(); it.Advance()) { |
| if (it.current_bytecode() != interpreter::Bytecode::kJumpLoop) { |
| continue; |
| } |
| if (!base::IsInRange(current_offset, it.GetJumpTargetOffset(), |
| it.current_offset())) { |
| continue; |
| } |
| |
| return BytecodeOffset(it.current_offset()); |
| } |
| |
| // Fall back to any loop after the current offset. |
| it.SetOffset(current_offset); |
| for (; !it.done(); it.Advance()) { |
| if (it.current_bytecode() == interpreter::Bytecode::kJumpLoop) { |
| return BytecodeOffset(it.current_offset()); |
| } |
| } |
| |
| return BytecodeOffset::None(); |
| } |
| |
| } // namespace |
| |
| RUNTIME_FUNCTION(Runtime_OptimizeOsr) { |
| HandleScope handle_scope(isolate); |
| |
| DirectHandle<JSFunction> function; |
| |
| // The optional parameter determines the frame being targeted. |
| int stack_depth = 0; |
| if (args.length() == 1) { |
| CHECK_UNLESS_FUZZING(IsSmi(args[0])); |
| stack_depth = args.smi_value_at(0); |
| } |
| |
| // Find the JavaScript function on the top of the stack. |
| JavaScriptStackFrameIterator it(isolate); |
| while (!it.done() && stack_depth--) it.Advance(); |
| if (!it.done()) { |
| if (it.frame()->is_turbofan()) { |
| if (v8_flags.trace_osr) { |
| CodeTracer::Scope scope(isolate->GetCodeTracer()); |
| PrintF(scope.file(), |
| "[OSR - %%OptimizeOsr failed because the current function could " |
| "not be found.]\n"); |
| } |
| // This can happen if %OptimizeOsr is in inlined function. |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } else if (it.frame()->is_maglev()) { |
| function = MaglevFrame::cast(it.frame())->GetInnermostFunction(); |
| } else { |
| function = direct_handle(it.frame()->function(), isolate); |
| } |
| } |
| CHECK_UNLESS_FUZZING(!function.is_null()); |
| |
| if (V8_UNLIKELY((!v8_flags.turbofan && !maglev::IsMaglevEnabled()) || |
| (!v8_flags.use_osr && !maglev::IsMaglevOsrEnabled()))) { |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| CHECK_UNLESS_FUZZING(function->shared()->allows_lazy_compilation()); |
| |
| CHECK_UNLESS_FUZZING(!function->shared()->all_optimization_disabled()); |
| |
| if (!v8_flags.fuzzing && |
| !ManualOptimizationTable::IsMarkedForManualOptimization(isolate, |
| *function)) { |
| PrintF("Warning: Function "); |
| ShortPrint(*function); |
| PrintF( |
| " might have to be prepared for optimization with " |
| "%%PrepareFunctionForOptimization before " |
| "%%OptimizeOsr"); |
| } |
| |
| if (function->HasAvailableOptimizedCode(isolate) && |
| (!function->code(isolate)->is_maglevved() || !v8_flags.osr_from_maglev)) { |
| DCHECK(function->HasAttachedOptimizedCode(isolate) || |
| function->ChecksTieringState(isolate)); |
| // If function is already optimized, return. |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| if (!it.frame()->is_unoptimized() && |
| (!it.frame()->is_maglev() || !v8_flags.osr_from_maglev)) { |
| // Nothing to be done. |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| IsCompiledScope is_compiled_scope( |
| function->shared()->is_compiled_scope(isolate)); |
| JSFunction::EnsureFeedbackVector(isolate, function, &is_compiled_scope); |
| isolate->tiering_manager()->RequestOsrAtNextOpportunity(*function); |
| |
| // If concurrent OSR is enabled, the testing workflow is a bit tricky. We |
| // must guarantee that the next JumpLoop installs the finished OSR'd code |
| // object, but we still want to exercise concurrent code paths. To do so, |
| // we attempt to find the next JumpLoop, start an OSR job for it now, and |
| // immediately force finalization. |
| // If this succeeds and we correctly match up the next JumpLoop, once we |
| // reach the JumpLoop we'll hit the OSR cache and install the generated code. |
| // If not (e.g. because we enter a nested loop first), the next JumpLoop will |
| // see the cached OSR code with a mismatched offset, and trigger |
| // non-concurrent OSR compilation and installation. |
| // To tier up from Maglev to TF we always do this, because the non-concurrent |
| // recompilation in `CompileOptimizedOSRFromMaglev` is broken. See the comment |
| // in `runtime-compiler.cc`. |
| bool concurrent_osr = |
| isolate->concurrent_recompilation_enabled() && v8_flags.concurrent_osr; |
| bool is_maglev = false; |
| if (it.frame()->is_maglev() || concurrent_osr) { |
| BytecodeOffset osr_offset = BytecodeOffset::None(); |
| if (it.frame()->is_unoptimized()) { |
| UnoptimizedJSFrame* frame = UnoptimizedJSFrame::cast(it.frame()); |
| Handle<BytecodeArray> bytecode_array(frame->GetBytecodeArray(), isolate); |
| const int current_offset = frame->GetBytecodeOffset(); |
| osr_offset = |
| OffsetOfNextJumpLoop(isolate, bytecode_array, current_offset); |
| } else { |
| MaglevFrame* frame = MaglevFrame::cast(it.frame()); |
| Handle<BytecodeArray> bytecode_array( |
| function->shared()->GetBytecodeArray(isolate), isolate); |
| const BytecodeOffset current_offset = frame->GetBytecodeOffsetForOSR(); |
| osr_offset = OffsetOfNextJumpLoop( |
| isolate, bytecode_array, |
| current_offset.IsNone() ? 0 : current_offset.ToInt()); |
| is_maglev = true; |
| } |
| |
| if (osr_offset.IsNone()) { |
| // The loop may have been elided by bytecode generation (e.g. for |
| // patterns such as `do { ... } while (false);` or we are in an inlined |
| // constructor stub. |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| // Finalize first to ensure all pending tasks are done (since we can't |
| // queue more than one OSR job for each function). |
| if (concurrent_osr) { |
| FinalizeOptimization(isolate); |
| } |
| |
| // Queue the job. |
| auto unused_result = Compiler::CompileOptimizedOSR( |
| isolate, function, osr_offset, |
| concurrent_osr ? ConcurrencyMode::kConcurrent |
| : ConcurrencyMode::kSynchronous, |
| (maglev::IsMaglevOsrEnabled() && !it.frame()->is_maglev()) |
| ? CodeKind::MAGLEV |
| : CodeKind::TURBOFAN_JS); |
| USE(unused_result); |
| |
| // Finalize again to finish the queued job. The next call into |
| // Runtime::kCompileOptimizedOSR will pick up the cached InstructionStream |
| // object. |
| if (concurrent_osr) { |
| FinalizeOptimization(isolate); |
| } |
| |
| if (is_maglev) { |
| // Maglev ignores the maybe_has_optimized_osr_code flag, thus we also need |
| // to set a maximum urgency. |
| function->feedback_vector()->set_osr_urgency( |
| FeedbackVector::kMaxOsrUrgency); |
| } |
| } |
| |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_BaselineOsr) { |
| HandleScope scope(isolate); |
| |
| // Find the JavaScript function on the top of the stack. |
| JavaScriptStackFrameIterator it(isolate); |
| DirectHandle<JSFunction> function(it.frame()->function(), isolate); |
| CHECK_UNLESS_FUZZING(!function.is_null()); |
| if (!v8_flags.sparkplug || !v8_flags.use_osr) { |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| if (!it.frame()->is_unoptimized()) { |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| IsCompiledScope is_compiled_scope( |
| function->shared()->is_compiled_scope(isolate)); |
| Compiler::CompileBaseline(isolate, function, Compiler::CLEAR_EXCEPTION, |
| &is_compiled_scope); |
| |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_NeverOptimizeFunction) { |
| HandleScope scope(isolate); |
| CHECK_UNLESS_FUZZING(args.length() == 1); |
| Handle<Object> function_object = args.at(0); |
| CHECK_UNLESS_FUZZING(IsJSFunction(*function_object)); |
| auto function = Cast<JSFunction>(function_object); |
| DirectHandle<SharedFunctionInfo> sfi(function->shared(), isolate); |
| CodeKind code_kind = sfi->abstract_code(isolate)->kind(); |
| switch (code_kind) { |
| case CodeKind::INTERPRETED_FUNCTION: |
| break; |
| case CodeKind::BUILTIN: |
| // SFIs for builtin functions are in RO space and thus we cannot set |
| // the never-optimize bit. But such SFIs cannot be optimized anyways. |
| CHECK_UNLESS_FUZZING(!HeapLayout::InReadOnlySpace(*sfi)); |
| break; |
| default: |
| CHECK_UNLESS_FUZZING(false); |
| break; |
| } |
| |
| // Make sure to finish compilation if there is a parallel lazy compilation in |
| // progress, to make sure that the compilation finalization doesn't clobber |
| // the SharedFunctionInfo's disable_optimization field. |
| if (isolate->lazy_compile_dispatcher() && |
| isolate->lazy_compile_dispatcher()->IsEnqueued(sfi)) { |
| isolate->lazy_compile_dispatcher()->FinishNow(sfi); |
| } |
| |
| sfi->DisableOptimization(isolate, BailoutReason::kNeverOptimize); |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_GetOptimizationStatus) { |
| HandleScope scope(isolate); |
| DCHECK_EQ(args.length(), 1); |
| |
| int status = 0; |
| if (v8_flags.lite_mode || v8_flags.jitless || !V8_ENABLE_TURBOFAN_BOOL) { |
| // These modes cannot optimize. Unit tests should handle these the same |
| // way. |
| status |= static_cast<int>(OptimizationStatus::kLiteMode); |
| } |
| if (!isolate->use_optimizer()) { |
| status |= static_cast<int>(OptimizationStatus::kNeverOptimize); |
| } |
| if (v8_flags.deopt_every_n_times || v8_flags.stress_flush_code || |
| v8_flags.gc_interval > 0 || v8_flags.random_gc_interval > 0) { |
| status |= static_cast<int>(OptimizationStatus::kMaybeDeopted); |
| } |
| if (v8_flags.optimize_on_next_call_optimizes_to_maglev) { |
| status |= static_cast<int>( |
| OptimizationStatus::kOptimizeOnNextCallOptimizesToMaglev); |
| } |
| if (v8_flags.optimize_maglev_optimizes_to_turbofan) { |
| status |= static_cast<int>( |
| OptimizationStatus::kOptimizeMaglevOptimizesToTurbofan); |
| } |
| |
| Handle<Object> function_object = args.at(0); |
| if (IsUndefined(*function_object)) return Smi::FromInt(status); |
| CHECK_UNLESS_FUZZING(IsJSFunction(*function_object)); |
| |
| auto function = Cast<JSFunction>(function_object); |
| status |= static_cast<int>(OptimizationStatus::kIsFunction); |
| |
| if (function->has_feedback_vector()) { |
| if (function->tiering_in_progress()) { |
| status |= static_cast<int>(OptimizationStatus::kOptimizingConcurrently); |
| } else if (function->GetRequestedOptimizationIfAny( |
| isolate, ConcurrencyMode::kConcurrent) == CodeKind::MAGLEV) { |
| status |= static_cast<int>( |
| OptimizationStatus::kMarkedForConcurrentMaglevOptimization); |
| } else if (function->GetRequestedOptimizationIfAny( |
| isolate, ConcurrencyMode::kSynchronous) == |
| CodeKind::MAGLEV) { |
| status |= |
| static_cast<int>(OptimizationStatus::kMarkedForMaglevOptimization); |
| } else if (function->GetRequestedOptimizationIfAny( |
| isolate, ConcurrencyMode::kConcurrent) == |
| CodeKind::TURBOFAN_JS) { |
| status |= static_cast<int>( |
| OptimizationStatus::kMarkedForConcurrentOptimization); |
| } else if (function->GetRequestedOptimizationIfAny( |
| isolate, ConcurrencyMode::kSynchronous) == |
| CodeKind::TURBOFAN_JS) { |
| status |= static_cast<int>(OptimizationStatus::kMarkedForOptimization); |
| } |
| } |
| |
| if (function->HasAttachedOptimizedCode(isolate)) { |
| Tagged<Code> code = function->code(isolate); |
| if (code->marked_for_deoptimization()) { |
| status |= static_cast<int>(OptimizationStatus::kMarkedForDeoptimization); |
| } else { |
| status |= static_cast<int>(OptimizationStatus::kOptimized); |
| } |
| if (code->is_maglevved()) { |
| status |= static_cast<int>(OptimizationStatus::kMaglevved); |
| } else if (code->is_turbofanned()) { |
| status |= static_cast<int>(OptimizationStatus::kTurboFanned); |
| } |
| } |
| if (function->HasAttachedCodeKind(isolate, CodeKind::BASELINE)) { |
| status |= static_cast<int>(OptimizationStatus::kBaseline); |
| } |
| if (function->ActiveTierIsIgnition(isolate)) { |
| status |= static_cast<int>(OptimizationStatus::kInterpreted); |
| } |
| if (!function->is_compiled(isolate)) { |
| status |= static_cast<int>(OptimizationStatus::kIsLazy); |
| } |
| |
| // Additionally, detect activations of this frame on the stack, and report the |
| // status of the topmost frame. |
| JavaScriptFrame* frame = nullptr; |
| JavaScriptStackFrameIterator it(isolate); |
| while (!it.done()) { |
| if (it.frame()->function() == *function) { |
| frame = it.frame(); |
| break; |
| } |
| it.Advance(); |
| } |
| if (frame != nullptr) { |
| status |= static_cast<int>(OptimizationStatus::kIsExecuting); |
| if (frame->is_turbofan()) { |
| status |= |
| static_cast<int>(OptimizationStatus::kTopmostFrameIsTurboFanned); |
| } else if (frame->is_interpreted()) { |
| status |= |
| static_cast<int>(OptimizationStatus::kTopmostFrameIsInterpreted); |
| } else if (frame->is_baseline()) { |
| status |= static_cast<int>(OptimizationStatus::kTopmostFrameIsBaseline); |
| } else if (frame->is_maglev()) { |
| status |= static_cast<int>(OptimizationStatus::kTopmostFrameIsMaglev); |
| } |
| } |
| |
| return Smi::FromInt(status); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_GetFunctionForCurrentFrame) { |
| HandleScope scope(isolate); |
| // This isn't exposed to fuzzers so doesn't need to handle invalid arguments. |
| DCHECK_EQ(args.length(), 0); |
| |
| JavaScriptStackFrameIterator it(isolate); |
| DCHECK(!it.done()); |
| return it.frame()->function(); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_DisableOptimizationFinalization) { |
| if (isolate->concurrent_recompilation_enabled()) { |
| isolate->optimizing_compile_dispatcher()->WaitUntilCompilationJobsDone(); |
| isolate->optimizing_compile_dispatcher()->InstallOptimizedFunctions(); |
| isolate->stack_guard()->ClearInstallCode(); |
| isolate->optimizing_compile_dispatcher()->set_finalize(false); |
| } |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_WaitForBackgroundOptimization) { |
| isolate->WaitForConcurrentOptimizationJobs(); |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_FinalizeOptimization) { |
| if (isolate->concurrent_recompilation_enabled()) { |
| FinalizeOptimization(isolate); |
| } |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_ForceFlush) { |
| HandleScope scope(isolate); |
| CHECK_UNLESS_FUZZING(args.length() == 1); |
| |
| Handle<Object> function_object = args.at(0); |
| CHECK_UNLESS_FUZZING(IsJSFunction(*function_object)); |
| auto function = Cast<JSFunction>(function_object); |
| Tagged<SharedFunctionInfo> sfi = function->shared(); |
| |
| // Don't try to flush functions that cannot be flushed. |
| CHECK_UNLESS_FUZZING(sfi->CanDiscardCompiled()); |
| |
| // Don't flush functions that are active on the stack. |
| for (JavaScriptStackFrameIterator frame_it(isolate); !frame_it.done(); |
| frame_it.Advance()) { |
| std::vector<Tagged<SharedFunctionInfo>> infos; |
| frame_it.frame()->GetFunctions(&infos); |
| for (Tagged<SharedFunctionInfo> info : base::Reversed(infos)) { |
| CHECK_UNLESS_FUZZING(info != sfi); |
| } |
| } |
| |
| SharedFunctionInfo::DiscardCompiled(isolate, direct_handle(sfi, isolate)); |
| function->ResetIfCodeFlushed(isolate); |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| static void ReturnNull(const v8::FunctionCallbackInfo<v8::Value>& info) { |
| DCHECK(ValidateCallbackInfo(info)); |
| info.GetReturnValue().SetNull(); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_GetUndetectable) { |
| HandleScope scope(isolate); |
| v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate); |
| Local<v8::ObjectTemplate> desc = v8::ObjectTemplate::New(v8_isolate); |
| desc->MarkAsUndetectable(); |
| desc->SetCallAsFunctionHandler(ReturnNull); |
| Local<v8::Object> obj = |
| desc->NewInstance(v8_isolate->GetCurrentContext()).ToLocalChecked(); |
| return *Utils::OpenDirectHandle(*obj); |
| } |
| |
| namespace { |
| // Does globalThis[target_function_name](...args). |
| void call_as_function(const v8::FunctionCallbackInfo<v8::Value>& info) { |
| DCHECK(ValidateCallbackInfo(info)); |
| v8::Isolate* isolate = info.GetIsolate(); |
| auto context = isolate->GetCurrentContext(); |
| auto global = context->Global(); |
| auto target_function_name = info.DataV2().As<v8::Value>().As<v8::String>(); |
| v8::Local<v8::Function> target; |
| { |
| Local<Value> result; |
| if (!global->Get(context, target_function_name).ToLocal(&result)) { |
| return; |
| } |
| if (!result->IsFunction()) { |
| isolate->ThrowError("Target function is not callable"); |
| return; |
| } |
| target = result.As<Function>(); |
| } |
| int argc = info.Length(); |
| v8::LocalVector<v8::Value> args(isolate, argc); |
| for (int i = 0; i < argc; i++) { |
| args[i] = info[i]; |
| } |
| Local<Value> result; |
| if (!target->Call(context, info.This(), argc, args.data()).ToLocal(&result)) { |
| return; |
| } |
| info.GetReturnValue().Set(result); |
| } |
| } // namespace |
| |
| RUNTIME_FUNCTION(Runtime_GetAbstractModuleSource) { |
| // This isn't exposed to fuzzers. Crash if the native context is been |
| // modified. |
| HandleScope scope(isolate); |
| DisallowGarbageCollection no_gc; |
| Tagged<JSFunction> abstract_module_source_function = |
| isolate->native_context()->abstract_module_source_function(); |
| CHECK(IsJSFunction(abstract_module_source_function)); |
| return abstract_module_source_function; |
| } |
| |
| // Returns a callable object which redirects [[Call]] requests to |
| // globalThis[target_function_name] function. |
| RUNTIME_FUNCTION(Runtime_GetCallable) { |
| HandleScope scope(isolate); |
| CHECK_UNLESS_FUZZING(args.length() == 1); |
| DirectHandle<String> target_function_name = args.at<String>(0); |
| v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate); |
| Local<v8::FunctionTemplate> t = v8::FunctionTemplate::New(v8_isolate); |
| Local<v8::ObjectTemplate> instance_template = t->InstanceTemplate(); |
| instance_template->SetCallAsFunctionHandler( |
| call_as_function, v8::Utils::ToLocal(target_function_name)); |
| v8_isolate->GetCurrentContext(); |
| Local<v8::Object> instance = |
| t->GetFunction(v8_isolate->GetCurrentContext()) |
| .ToLocalChecked() |
| ->NewInstance(v8_isolate->GetCurrentContext()) |
| .ToLocalChecked(); |
| return *Utils::OpenDirectHandle(*instance); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_ClearFunctionFeedback) { |
| HandleScope scope(isolate); |
| // This isn't exposed to fuzzers so doesn't need to handle invalid arguments. |
| DCHECK_EQ(args.length(), 1); |
| DirectHandle<JSFunction> function = args.at<JSFunction>(0); |
| function->ClearAllTypeFeedbackInfoForTesting(isolate); |
| // Typically tests use this function to start from scratch. Thus, we should |
| // also clear tiering requests. |
| function->ResetTieringRequests(isolate); |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_NotifyContextDisposed) { |
| HandleScope scope(isolate); |
| isolate->heap()->NotifyContextDisposed(true); |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_SetAllocationTimeout) { |
| SealHandleScope shs(isolate); |
| CHECK_UNLESS_FUZZING(args.length() == 2 || args.length() == 3); |
| #ifdef V8_ENABLE_ALLOCATION_TIMEOUT |
| CONVERT_INT32_ARG_FUZZ_SAFE(interval, 0); |
| HeapAllocator::SetAllocationGcInterval(interval); |
| CONVERT_INT32_ARG_FUZZ_SAFE(timeout, 1); |
| isolate->heap()->set_allocation_timeout(timeout); |
| #else // !V8_ENABLE_ALLOCATION_TIMEOUT |
| static std::atomic_flag printed_warning = ATOMIC_FLAG_INIT; |
| if (!printed_warning.test_and_set()) { |
| base::OS::PrintError( |
| "Warning: %%SetAllocationTimeout has no effect in this build. Set the " |
| "`v8_enable_test_features` GN arg to enable it.\n"); |
| } |
| #endif // !V8_ENABLE_ALLOCATION_TIMEOUT |
| #ifdef DEBUG |
| if (args.length() == 3) { |
| // Enable/disable inline allocation if requested. |
| CONVERT_BOOLEAN_ARG_FUZZ_SAFE(inline_allocation, 2); |
| if (inline_allocation) { |
| isolate->heap()->EnableInlineAllocation(); |
| } else { |
| isolate->heap()->DisableInlineAllocation(); |
| } |
| } |
| #endif |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| namespace { |
| |
| int FixedArrayLenFromSize(int size) { |
| return std::min({(size - OFFSET_OF_DATA_START(FixedArray)) / kTaggedSize, |
| FixedArray::kMaxRegularLength}); |
| } |
| |
| void FillUpOneNewSpacePage(Isolate* isolate, Heap* heap, |
| SemiSpaceNewSpace* space) { |
| DCHECK(!v8_flags.single_generation); |
| heap->FreeMainThreadLinearAllocationAreas(); |
| PauseAllocationObserversScope pause_observers(heap); |
| while (space->GetSpaceRemainingOnCurrentPageForTesting() > 0) { |
| int space_remaining = space->GetSpaceRemainingOnCurrentPageForTesting(); |
| int length = FixedArrayLenFromSize(space_remaining); |
| if (length > 0) { |
| DirectHandle<FixedArray> padding = |
| isolate->factory()->NewFixedArray(length, AllocationType::kYoung); |
| DCHECK(heap->new_space()->Contains(*padding)); |
| space_remaining -= padding->Size(); |
| } else { |
| // Not enough room to create another fixed array. Create a filler instead. |
| space->FillCurrentPageForTesting(); |
| } |
| heap->FreeMainThreadLinearAllocationAreas(); |
| } |
| } |
| |
| } // namespace |
| |
| RUNTIME_FUNCTION(Runtime_SimulateNewspaceFull) { |
| HandleScope scope(isolate); |
| Heap* heap = isolate->heap(); |
| heap->FreeMainThreadLinearAllocationAreas(); |
| AlwaysAllocateScopeForTesting always_allocate(heap); |
| if (v8_flags.minor_ms) { |
| if (heap->minor_sweeping_in_progress()) { |
| heap->EnsureYoungSweepingCompleted(); |
| } |
| auto* space = heap->paged_new_space()->paged_space(); |
| space->AllocatePageUpToCapacityForTesting(); |
| space->ResetFreeList(); |
| } else { |
| SemiSpaceNewSpace* space = heap->semi_space_new_space(); |
| do { |
| FillUpOneNewSpacePage(isolate, heap, space); |
| } while (space->AddFreshPage()); |
| } |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_ScheduleGCInStackCheck) { |
| SealHandleScope shs(isolate); |
| isolate->RequestInterrupt( |
| [](v8::Isolate* isolate, void*) { |
| isolate->RequestGarbageCollectionForTesting( |
| v8::Isolate::kFullGarbageCollection); |
| }, |
| nullptr); |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_TakeHeapSnapshot) { |
| if (v8_flags.fuzzing) { |
| // We don't want to create snapshots in fuzzers. |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| std::string filename = "heap.heapsnapshot"; |
| |
| if (args.length() >= 1) { |
| HandleScope hs(isolate); |
| DirectHandle<String> filename_as_js_string = args.at<String>(0); |
| std::unique_ptr<char[]> buffer = filename_as_js_string->ToCString(); |
| filename = std::string(buffer.get()); |
| } |
| |
| HeapProfiler* heap_profiler = isolate->heap()->heap_profiler(); |
| heap_profiler->TakeSnapshotToFile( |
| HeapProfiler::GetDefaultHeapSnapshotOptionsForTestingUsage(), filename); |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| static void DebugPrintImpl(Tagged<MaybeObject> maybe_object, std::ostream& os) { |
| if (maybe_object.IsCleared()) { |
| os << "[weak cleared]"; |
| } else { |
| Tagged<Object> object = maybe_object.GetHeapObjectOrSmi(); |
| bool weak = maybe_object.IsWeak(); |
| |
| #ifdef OBJECT_PRINT |
| os << "DebugPrint: "; |
| if (weak) os << "[weak] "; |
| Print(object, os); |
| if (IsHeapObject(object) && !IsInaccessible(Cast<HeapObject>(object))) { |
| Print(Cast<HeapObject>(object)->map(), os); |
| } |
| #else |
| if (weak) os << "[weak] "; |
| // ShortPrint is available in release mode. Print is not. |
| os << Brief(object); |
| #endif |
| } |
| os << std::endl; |
| } |
| |
| RUNTIME_FUNCTION(Runtime_DebugPrint) { |
| SealHandleScope shs(isolate); |
| |
| if (args.length() == 0) { |
| // This runtime method has variable number of arguments, but if there is no |
| // argument, undefined behavior may happen. |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| // This is exposed to tests / fuzzers; handle variable arguments gracefully. |
| std::unique_ptr<std::ostream> output_stream(new StdoutStream()); |
| if (args.length() >= 2) { |
| // Args: object, stream. |
| if (IsSmi(args[1])) { |
| int output_int = Cast<Smi>(args[1]).value(); |
| if (output_int == fileno(stderr)) { |
| output_stream.reset(new StderrStream()); |
| } |
| } |
| } |
| |
| Tagged<MaybeObject> maybe_object(*args.address_of_arg_at(0)); |
| DebugPrintImpl(maybe_object, *output_stream); |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_DebugPrintGeneric) { |
| static constexpr int kNum16BitChunks = 4; |
| SealHandleScope shs(isolate); |
| |
| // Args are: prefix, value_format, |
| // smi:0, smi:0, smi:0, tagged - if value_format is kTagged |
| // <bits 63-48>, <bits 47-32>, <bits 31-16>, <bits 15-0>, - otherwise |
| // stream. |
| |
| CHECK_UNLESS_FUZZING(args.length() == 2 + kNum16BitChunks + 1); |
| |
| CHECK_UNLESS_FUZZING(IsSmi(args[1])); |
| DebugPrintValueType value_type = |
| static_cast<DebugPrintValueType>(Cast<Smi>(args[1]).value()); |
| CHECK_UNLESS_FUZZING(IsSmi(args[6])); |
| int stream_int = Cast<Smi>(args[6]).value(); |
| FILE* output_stream = stream_int == fileno(stderr) ? stderr : stdout; |
| |
| if (IsString(args[0])) { |
| Tagged<String> prefix = Cast<String>(args[0]); |
| StringCharacterStream stream(prefix); |
| while (stream.HasMore()) { |
| uint16_t character = stream.GetNext(); |
| PrintF(output_stream, "%c", character); |
| } |
| } |
| |
| uint64_t value = 0; |
| if (value_type != DebugPrintValueType::kTagged) { |
| for (int i = 0; i < kNum16BitChunks; ++i) { |
| value <<= 16; |
| CHECK_UNLESS_FUZZING(IsSmi(args[2 + i])); |
| uint32_t chunk = Cast<Smi>(args[2 + i]).value(); |
| // We encode 16 bit per chunk only! |
| CHECK_UNLESS_FUZZING((chunk & 0xFFFF0000) == 0); |
| value |= chunk; |
| } |
| } |
| |
| switch (value_type) { |
| case DebugPrintValueType::kWord32: { |
| PrintF(output_stream, "0x%" PRIx32 "\n", static_cast<uint32_t>(value)); |
| break; |
| } |
| case DebugPrintValueType::kWord64: { |
| PrintF(output_stream, "0x%" PRIx64 "\n", value); |
| break; |
| } |
| case DebugPrintValueType::kFloat32: { |
| const float f = base::bit_cast<float>(static_cast<uint32_t>(value)); |
| if (std::isnan(f)) { |
| PrintF(output_stream, "%g (0x%" PRIx32 ")\n", f, |
| static_cast<uint32_t>(value)); |
| } else { |
| PrintF(output_stream, "%.20g\n", f); |
| } |
| break; |
| } |
| case DebugPrintValueType::kFloat64: { |
| const double d = base::bit_cast<double>(value); |
| if (std::isnan(d)) { |
| PrintF(output_stream, "%g (0x%" PRIx64 ")\n", d, value); |
| } else { |
| PrintF(output_stream, "%.20g\n", d); |
| } |
| break; |
| } |
| case DebugPrintValueType::kTagged: { |
| Tagged<Object> tagged = args[5]; |
| CHECK_UNLESS_FUZZING(IsHeapObject(tagged)); |
| if (IsString(tagged) && !IsString(args[0])) { |
| // We don't have a prefix and just print a string. In this case we don't |
| // print the full JS object but just the text. |
| Tagged<String> text = Cast<String>(tagged); |
| StringCharacterStream stream(text); |
| while (stream.HasMore()) { |
| uint16_t character = stream.GetNext(); |
| PrintF(output_stream, "%c", character); |
| } |
| PrintF(output_stream, "\n"); |
| fflush(output_stream); |
| } else { |
| Tagged<MaybeObject> maybe_object(tagged); |
| OFStream fstream(output_stream); |
| DebugPrintImpl(maybe_object, fstream); |
| } |
| break; |
| } |
| } |
| fflush(output_stream); |
| |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_DebugPrintPtr) { |
| SealHandleScope shs(isolate); |
| // This isn't exposed to fuzzers so doesn't need to handle invalid arguments. |
| DCHECK_EQ(args.length(), 1); |
| |
| StdoutStream os; |
| Tagged<MaybeObject> maybe_object(*args.address_of_arg_at(0)); |
| if (!maybe_object.IsCleared()) { |
| Tagged<Object> object = maybe_object.GetHeapObjectOrSmi(); |
| size_t pointer; |
| if (Object::ToIntegerIndex(object, &pointer)) { |
| Tagged<MaybeObject> from_pointer(static_cast<Address>(pointer)); |
| DebugPrintImpl(from_pointer, os); |
| } |
| } |
| // We don't allow the converted pointer to leak out to JavaScript. |
| return args[0]; |
| } |
| |
| RUNTIME_FUNCTION(Runtime_DebugPrintWord) { |
| static constexpr int kNum16BitChunks = 4; |
| SealHandleScope shs(isolate); |
| |
| // Args are: <bits 63-48>, <bits 47-32>, <bits 31-16>, <bits 15-0>, stream. |
| CHECK_UNLESS_FUZZING(args.length() == kNum16BitChunks + 1); |
| |
| uint64_t value = 0; |
| for (int i = 0; i < kNum16BitChunks; ++i) { |
| value <<= 16; |
| CHECK_UNLESS_FUZZING(IsSmi(args[i])); |
| uint32_t chunk = Cast<Smi>(args[i]).value(); |
| // We encode 16 bit per chunk only! |
| CHECK_UNLESS_FUZZING((chunk & 0xFFFF0000) == 0); |
| value |= chunk; |
| } |
| |
| if (!IsSmi(args[4]) || (Cast<Smi>(args[4]).value() == fileno(stderr))) { |
| StderrStream os; |
| os << "0x" << std::hex << value << std::dec << std::endl; |
| } else { |
| StdoutStream os; |
| os << "0x" << std::hex << value << std::dec << std::endl; |
| } |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_DebugPrintFloat) { |
| static constexpr int kNum16BitChunks = 4; |
| SealHandleScope shs(isolate); |
| |
| // Args are: <bits 63-48>, <bits 47-32>, <bits 31-16>, <bits 15-0>, stream. |
| CHECK_UNLESS_FUZZING(args.length() == kNum16BitChunks + 1); |
| |
| uint64_t value = 0; |
| for (int i = 0; i < kNum16BitChunks; ++i) { |
| value <<= 16; |
| CHECK_UNLESS_FUZZING(IsSmi(args[i])); |
| uint32_t chunk = Cast<Smi>(args[i]).value(); |
| // We encode 16 bit per chunk only! |
| CHECK_UNLESS_FUZZING((chunk & 0xFFFF0000) == 0); |
| value |= chunk; |
| } |
| |
| if (!IsSmi(args[4]) || (Cast<Smi>(args[4]).value() == fileno(stderr))) { |
| StderrStream os; |
| std::streamsize precision = os.precision(); |
| const double d = base::bit_cast<double>(value); |
| os << std::setprecision(20) << d; |
| if (std::isnan(d)) { |
| os << " (0x" << std::hex << value << std::dec << ")"; |
| } |
| os << std::endl; |
| os.precision(precision); |
| } else { |
| StdoutStream os; |
| std::streamsize precision = os.precision(); |
| const double d = base::bit_cast<double>(value); |
| os << std::setprecision(20) << d; |
| if (std::isnan(d)) { |
| os << " (0x" << std::hex << value << std::dec << ")"; |
| } |
| os << std::endl; |
| os.precision(precision); |
| } |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_PrintWithNameForAssert) { |
| SealHandleScope shs(isolate); |
| CHECK_UNLESS_FUZZING(args.length() == 2); |
| |
| CHECK_UNLESS_FUZZING(IsString(args[0])); |
| auto name = Cast<String>(args[0]); |
| |
| PrintF(" * "); |
| StringCharacterStream stream(name); |
| while (stream.HasMore()) { |
| uint16_t character = stream.GetNext(); |
| PrintF("%c", character); |
| } |
| PrintF(": "); |
| ShortPrint(args[1]); |
| PrintF("\n"); |
| |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_DebugTrace) { |
| SealHandleScope shs(isolate); |
| isolate->PrintStack(stdout); |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_DebugTraceMinimal) { |
| SealHandleScope shs(isolate); |
| isolate->PrintMinimalStack(stdout); |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| // This will not allocate (flatten the string), but it may run |
| // very slowly for very deeply nested ConsStrings. For debugging use only. |
| RUNTIME_FUNCTION(Runtime_GlobalPrint) { |
| SealHandleScope shs(isolate); |
| |
| // This is exposed to tests / fuzzers; handle variable arguments gracefully. |
| FILE* output_stream = stdout; |
| if (args.length() >= 2) { |
| // Args: object, stream. |
| if (IsSmi(args[1])) { |
| int output_int = Cast<Smi>(args[1]).value(); |
| if (output_int == fileno(stderr)) { |
| output_stream = stderr; |
| } |
| } |
| } |
| |
| CHECK_UNLESS_FUZZING(args.length() >= 1); |
| if (!IsString(args[0])) { |
| return args[0]; |
| } |
| |
| auto string = Cast<String>(args[0]); |
| StringCharacterStream stream(string); |
| while (stream.HasMore()) { |
| uint16_t character = stream.GetNext(); |
| PrintF(output_stream, "%c", character); |
| } |
| fflush(output_stream); |
| return string; |
| } |
| |
| RUNTIME_FUNCTION(Runtime_SystemBreak) { |
| // The code below doesn't create handles, but when breaking here in GDB |
| // having a handle scope might be useful. |
| HandleScope scope(isolate); |
| base::OS::DebugBreak(); |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_SetForceSlowPath) { |
| SealHandleScope shs(isolate); |
| CHECK_UNLESS_FUZZING(args.length() == 1); |
| Tagged<Object> arg = args[0]; |
| if (IsTrue(arg)) { |
| isolate->set_force_slow_path(true); |
| } else { |
| // This function is fuzzer exposed and as such we might not always have an |
| // input that IsTrue or IsFalse. In these cases we assume that if !IsTrue |
| // then it IsFalse when fuzzing. |
| DCHECK(IsFalse(arg) || v8_flags.fuzzing); |
| isolate->set_force_slow_path(false); |
| } |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_Abort) { |
| SealHandleScope shs(isolate); |
| CHECK_UNLESS_FUZZING(args.length() == 1); |
| int message_id = args.smi_value_at(0); |
| const char* message = GetAbortReason(static_cast<AbortReason>(message_id)); |
| base::OS::PrintError("abort: %s\n", message); |
| isolate->PrintStack(stderr); |
| base::OS::Abort(); |
| UNREACHABLE(); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_AbortJS) { |
| HandleScope scope(isolate); |
| CHECK_UNLESS_FUZZING(args.length() == 1); |
| DirectHandle<String> message = args.at<String>(0); |
| if (v8_flags.disable_abortjs) { |
| base::OS::PrintError("[disabled] abort: %s\n", message->ToCString().get()); |
| return Tagged<Object>(); |
| } |
| base::OS::PrintError("abort: %s\n", message->ToCString().get()); |
| isolate->PrintStack(stderr); |
| base::OS::Abort(); |
| UNREACHABLE(); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_AbortCSADcheck) { |
| HandleScope scope(isolate); |
| CHECK_UNLESS_FUZZING(args.length() == 1); |
| DirectHandle<String> message = args.at<String>(0); |
| if (base::ControlledCrashesAreHarmless()) { |
| base::OS::PrintError( |
| "Safely terminating process due to CSA check failure\n"); |
| // Also prefix the error message (printed below). This has two purposes: |
| // (1) it makes it clear that this error is deemed "safe" (2) it causes |
| // fuzzers that pattern-match on stderr output to ignore these failures. |
| base::OS::PrintError("The following harmless failure was encountered: %s\n", |
| message->ToCString().get()); |
| } else { |
| std::unique_ptr<char[]> message_str = message->ToCString(); |
| base::OS::PrintError("abort: CSA_DCHECK failed: %s\n\n", message_str.get()); |
| |
| isolate->PushStackTraceAndDie(message_str.get(), |
| reinterpret_cast<void*>(message->ptr())); |
| } |
| base::OS::Abort(); |
| UNREACHABLE(); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_DisassembleFunction) { |
| HandleScope scope(isolate); |
| #ifdef DEBUG |
| CHECK_UNLESS_FUZZING(args.length() == 1); |
| // Get the function and make sure it is compiled. |
| DirectHandle<JSFunction> func = args.at<JSFunction>(0); |
| IsCompiledScope is_compiled_scope; |
| CHECK(func->shared()->is_compiled() || |
| Compiler::Compile(isolate, func, Compiler::KEEP_EXCEPTION, |
| &is_compiled_scope)); |
| StdoutStream os; |
| Print(func->code(isolate), os); |
| os << std::endl; |
| #endif // DEBUG |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_VerifyGetJSBuiltinState) { |
| HandleScope scope(isolate); |
| #ifdef DEBUG |
| CHECK_UNLESS_FUZZING(args.length() == 1); |
| // Checking the state of builtins doesn't make sense with --fuzzing |
| // because the JS code could have modified the heap state in a way |
| // not expected by VerifyGetJSBuiltinState() machinery. |
| if (!v8_flags.fuzzing) { |
| bool allow_non_initial_state = Object::BooleanValue(args[0], isolate); |
| isolate->builtins()->VerifyGetJSBuiltinState(allow_non_initial_state); |
| } |
| #endif |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| namespace { |
| |
| int StackSize(Isolate* isolate) { |
| int n = 0; |
| for (JavaScriptStackFrameIterator it(isolate); !it.done(); it.Advance()) n++; |
| return n; |
| } |
| |
| } // anonymous namespace |
| |
| void PrintIndentation(int stack_size) { |
| const int max_display = 80; |
| if (stack_size <= max_display) { |
| PrintF("%4d:%*s", stack_size, stack_size, ""); |
| } else { |
| PrintF("%4d:%*s", stack_size, max_display, "..."); |
| } |
| } |
| |
| RUNTIME_FUNCTION(Runtime_TraceEnter) { |
| SealHandleScope shs(isolate); |
| PrintIndentation(StackSize(isolate)); |
| JavaScriptFrame::PrintTop(isolate, stdout, true, false); |
| PrintF(" {\n"); |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_TraceExit) { |
| SealHandleScope shs(isolate); |
| CHECK_UNLESS_FUZZING(args.length() == 1); |
| Tagged<Object> obj = args[0]; |
| PrintIndentation(StackSize(isolate)); |
| PrintF("} -> "); |
| ShortPrint(obj); |
| PrintF("\n"); |
| return obj; // return TOS |
| } |
| |
| RUNTIME_FUNCTION(Runtime_HaveSameMap) { |
| SealHandleScope shs(isolate); |
| CHECK_UNLESS_FUZZING(args.length() == 2); |
| CHECK_UNLESS_FUZZING(!IsSmi(args[0])); |
| CHECK_UNLESS_FUZZING(!IsSmi(args[1])); |
| auto obj1 = Cast<HeapObject>(args[0]); |
| auto obj2 = Cast<HeapObject>(args[1]); |
| return isolate->heap()->ToBoolean(obj1->map() == obj2->map()); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_InLargeObjectSpace) { |
| SealHandleScope shs(isolate); |
| CHECK_UNLESS_FUZZING(args.length() == 1); |
| CHECK_UNLESS_FUZZING(IsHeapObject(args[0])); |
| auto obj = Cast<HeapObject>(args[0]); |
| return isolate->heap()->ToBoolean( |
| (isolate->heap()->new_lo_space() && |
| isolate->heap()->new_lo_space()->Contains(obj)) || |
| isolate->heap()->code_lo_space()->Contains(obj) || |
| isolate->heap()->lo_space()->Contains(obj)); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_HasElementsInALargeObjectSpace) { |
| SealHandleScope shs(isolate); |
| CHECK_UNLESS_FUZZING(args.length() == 1); |
| CHECK_UNLESS_FUZZING(IsJSArray(args[0])); |
| auto array = Cast<JSArray>(args[0]); |
| Tagged<FixedArrayBase> elements = array->elements(); |
| return isolate->heap()->ToBoolean( |
| (isolate->heap()->new_lo_space() && |
| isolate->heap()->new_lo_space()->Contains(elements)) || |
| isolate->heap()->lo_space()->Contains(elements)); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_HasCowElements) { |
| SealHandleScope shs(isolate); |
| CHECK_UNLESS_FUZZING(args.length() == 1); |
| CHECK_UNLESS_FUZZING(IsJSArray(args[0])); |
| auto array = Cast<JSArray>(args[0]); |
| Tagged<FixedArrayBase> elements = array->elements(); |
| return isolate->heap()->ToBoolean(elements->IsCowArray()); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_InYoungGeneration) { |
| SealHandleScope shs(isolate); |
| CHECK_UNLESS_FUZZING(args.length() == 1); |
| Tagged<Object> obj = args[0]; |
| return isolate->heap()->ToBoolean(HeapLayout::InYoungGeneration(obj)); |
| } |
| |
| // Force pretenuring for the allocation site the passed object belongs to. |
| RUNTIME_FUNCTION(Runtime_PretenureAllocationSite) { |
| DisallowGarbageCollection no_gc; |
| |
| CHECK_UNLESS_FUZZING(args.length() == 1); |
| Tagged<Object> arg = args[0]; |
| CHECK_UNLESS_FUZZING(IsJSObject(arg)); |
| Tagged<JSObject> object = Cast<JSObject>(arg); |
| |
| if (!v8_flags.sticky_mark_bits && !HeapLayout::InYoungGeneration(object)) { |
| // Object is not in new space, thus there is no memento and nothing to do. |
| return ReturnFuzzSafe(ReadOnlyRoots(isolate).false_value(), isolate); |
| } |
| |
| Heap* heap = isolate->heap(); |
| PretenuringHandler* pretenuring_handler = heap->pretenuring_handler(); |
| Tagged<AllocationMemento> memento = PretenuringHandler::FindAllocationMemento< |
| PretenuringHandler::kForRuntime>(heap, object->map(), object); |
| if (memento.is_null()) { |
| return ReturnFuzzSafe(ReadOnlyRoots(isolate).false_value(), isolate); |
| } |
| Tagged<AllocationSite> site = memento->GetAllocationSite(); |
| pretenuring_handler->PretenureAllocationSiteOnNextCollection(site); |
| return ReturnFuzzSafe(ReadOnlyRoots(isolate).true_value(), isolate); |
| } |
| |
| namespace { |
| |
| v8::ModifyCodeGenerationFromStringsResult DisallowCodegenFromStringsCallback( |
| v8::Local<v8::Context> context, v8::Local<v8::Value> source, |
| bool is_code_kind) { |
| return {false, {}}; |
| } |
| |
| } // namespace |
| |
| RUNTIME_FUNCTION(Runtime_DisallowCodegenFromStrings) { |
| SealHandleScope shs(isolate); |
| CHECK_UNLESS_FUZZING(args.length() == 1); |
| CHECK_UNLESS_FUZZING(IsBoolean(args[0])); |
| bool flag = Cast<Boolean>(args[0])->ToBool(isolate); |
| v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate); |
| v8_isolate->SetModifyCodeGenerationFromStringsCallback( |
| flag ? DisallowCodegenFromStringsCallback : nullptr); |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_RegexpHasBytecode) { |
| SealHandleScope shs(isolate); |
| CHECK_UNLESS_FUZZING(args.length() == 2); |
| CHECK_UNLESS_FUZZING(IsJSRegExp(args[0])); |
| CHECK_UNLESS_FUZZING(IsBoolean(args[1])); |
| auto regexp = args.at<JSRegExp>(0); |
| bool is_latin1 = args.at<Boolean>(1)->ToBool(isolate); |
| bool result = false; |
| if (regexp->has_data()) { |
| Tagged<RegExpData> data = regexp->data(isolate); |
| if (data->type_tag() == RegExpData::Type::IRREGEXP) { |
| result = TrustedCast<IrRegExpData>(data)->has_bytecode(is_latin1); |
| } |
| } |
| return isolate->heap()->ToBoolean(result); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_RegexpHasNativeCode) { |
| SealHandleScope shs(isolate); |
| CHECK_UNLESS_FUZZING(args.length() == 2); |
| CHECK_UNLESS_FUZZING(IsJSRegExp(args[0])); |
| CHECK_UNLESS_FUZZING(IsBoolean(args[1])); |
| auto regexp = args.at<JSRegExp>(0); |
| bool is_latin1 = args.at<Boolean>(1)->ToBool(isolate); |
| bool result = false; |
| if (regexp->has_data()) { |
| Tagged<RegExpData> data = regexp->data(isolate); |
| if (data->type_tag() == RegExpData::Type::IRREGEXP) { |
| result = TrustedCast<IrRegExpData>(data)->has_code(is_latin1); |
| } |
| } |
| return isolate->heap()->ToBoolean(result); |
| } |
| |
| // Returns true iff the regexp cannot match a string starting with |c|, |
| // according to the quick-check filters. Lets tests assert that a filter was |
| // actually built, which exec results alone cannot show. |
| RUNTIME_FUNCTION(Runtime_RegexpQuickCheckRejects) { |
| SealHandleScope shs(isolate); |
| CHECK_UNLESS_FUZZING(args.length() == 2); |
| CHECK_UNLESS_FUZZING(IsJSRegExp(args[0])); |
| CHECK_UNLESS_FUZZING(IsString(args[1])); |
| auto regexp = args.at<JSRegExp>(0); |
| auto string = args.at<String>(1); |
| CHECK_UNLESS_FUZZING(string->length() == 1); |
| if (!regexp->has_data()) return ReadOnlyRoots(isolate).false_value(); |
| DisallowGarbageCollection no_gc; |
| String::FlatContent content = string->GetFlatContent(no_gc); |
| if (!content.IsOneByte()) return ReadOnlyRoots(isolate).false_value(); |
| return isolate->heap()->ToBoolean( |
| regexp->data(isolate)->QuickCheckRejects(content.ToOneByteVector(), 0)); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_RegexpTypeTag) { |
| HandleScope shs(isolate); |
| CHECK_UNLESS_FUZZING(args.length() == 1); |
| CHECK_UNLESS_FUZZING(IsJSRegExp(args[0])); |
| auto regexp = Cast<JSRegExp>(args[0]); |
| const char* type_str; |
| if (regexp->has_data()) { |
| switch (regexp->data(isolate)->type_tag()) { |
| case RegExpData::Type::ATOM: |
| type_str = "ATOM"; |
| break; |
| case RegExpData::Type::IRREGEXP: |
| type_str = "IRREGEXP"; |
| break; |
| case RegExpData::Type::EXPERIMENTAL: |
| type_str = "EXPERIMENTAL"; |
| break; |
| default: |
| UNREACHABLE(); |
| } |
| } else { |
| type_str = "NOT_COMPILED"; |
| } |
| return *isolate->factory()->NewStringFromAsciiChecked(type_str); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_RegexpIsUnmodified) { |
| HandleScope shs(isolate); |
| CHECK_UNLESS_FUZZING(args.length() == 1); |
| CHECK_UNLESS_FUZZING(IsJSRegExp(args[0])); |
| DirectHandle<JSRegExp> regexp = args.at<JSRegExp>(0); |
| return isolate->heap()->ToBoolean( |
| RegExp::IsUnmodifiedRegExp(isolate, regexp)); |
| } |
| |
| #define ELEMENTS_KIND_CHECK_RUNTIME_FUNCTION(Name) \ |
| RUNTIME_FUNCTION(Runtime_##Name) { \ |
| CHECK_UNLESS_FUZZING(args.length() == 1); \ |
| CHECK_UNLESS_FUZZING(IsJSObject(args[0])); \ |
| auto obj = args.at<JSObject>(0); \ |
| return isolate->heap()->ToBoolean(obj->Name()); \ |
| } |
| |
| ELEMENTS_KIND_CHECK_RUNTIME_FUNCTION(HasFastElements) |
| ELEMENTS_KIND_CHECK_RUNTIME_FUNCTION(HasSmiElements) |
| ELEMENTS_KIND_CHECK_RUNTIME_FUNCTION(HasObjectElements) |
| ELEMENTS_KIND_CHECK_RUNTIME_FUNCTION(HasSmiOrObjectElements) |
| ELEMENTS_KIND_CHECK_RUNTIME_FUNCTION(HasDoubleElements) |
| ELEMENTS_KIND_CHECK_RUNTIME_FUNCTION(HasHoleyElements) |
| ELEMENTS_KIND_CHECK_RUNTIME_FUNCTION(HasDictionaryElements) |
| ELEMENTS_KIND_CHECK_RUNTIME_FUNCTION(HasPackedElements) |
| ELEMENTS_KIND_CHECK_RUNTIME_FUNCTION(HasSloppyArgumentsElements) |
| // Properties test sitting with elements tests - not fooling anyone. |
| ELEMENTS_KIND_CHECK_RUNTIME_FUNCTION(HasFastProperties) |
| |
| #undef ELEMENTS_KIND_CHECK_RUNTIME_FUNCTION |
| |
| #define FIXED_TYPED_ARRAYS_CHECK_RUNTIME_FUNCTION(Type, type, TYPE, ctype) \ |
| RUNTIME_FUNCTION(Runtime_HasFixed##Type##Elements) { \ |
| CHECK_UNLESS_FUZZING(args.length() == 1); \ |
| CHECK_UNLESS_FUZZING(IsJSObject(args[0])); \ |
| auto obj = Cast<JSObject>(args[0]); \ |
| return isolate->heap()->ToBoolean(obj->HasFixed##Type##Elements()); \ |
| } |
| |
| TYPED_ARRAYS(FIXED_TYPED_ARRAYS_CHECK_RUNTIME_FUNCTION) |
| |
| #undef FIXED_TYPED_ARRAYS_CHECK_RUNTIME_FUNCTION |
| |
| RUNTIME_FUNCTION(Runtime_IsConcatSpreadableProtector) { |
| SealHandleScope shs(isolate); |
| return isolate->heap()->ToBoolean( |
| Protectors::IsIsConcatSpreadableLookupChainIntact(isolate)); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_TypedArraySpeciesProtector) { |
| SealHandleScope shs(isolate); |
| return isolate->heap()->ToBoolean( |
| Protectors::IsTypedArraySpeciesLookupChainIntact(isolate)); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_RegExpSpeciesProtector) { |
| SealHandleScope shs(isolate); |
| return isolate->heap()->ToBoolean( |
| Protectors::IsRegExpSpeciesLookupChainIntact(isolate)); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_PromiseSpeciesProtector) { |
| SealHandleScope shs(isolate); |
| return isolate->heap()->ToBoolean( |
| Protectors::IsPromiseSpeciesLookupChainIntact(isolate)); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_ArraySpeciesProtector) { |
| SealHandleScope shs(isolate); |
| return isolate->heap()->ToBoolean( |
| Protectors::IsArraySpeciesLookupChainIntact(isolate)); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_MapIteratorProtector) { |
| SealHandleScope shs(isolate); |
| return isolate->heap()->ToBoolean( |
| Protectors::IsMapIteratorLookupChainIntact(isolate)); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_SetIteratorProtector) { |
| SealHandleScope shs(isolate); |
| return isolate->heap()->ToBoolean( |
| Protectors::IsSetIteratorLookupChainIntact(isolate)); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_StringIteratorProtector) { |
| SealHandleScope shs(isolate); |
| return isolate->heap()->ToBoolean( |
| Protectors::IsStringIteratorLookupChainIntact(isolate)); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_ArrayIteratorProtector) { |
| SealHandleScope shs(isolate); |
| return isolate->heap()->ToBoolean( |
| Protectors::IsArrayIteratorLookupChainIntact(isolate)); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_NoElementsProtector) { |
| SealHandleScope shs(isolate); |
| return isolate->heap()->ToBoolean(Protectors::IsNoElementsIntact(isolate)); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_StringWrapperToPrimitiveProtector) { |
| SealHandleScope shs(isolate); |
| return isolate->heap()->ToBoolean( |
| Protectors::IsStringWrapperToPrimitiveIntact(isolate)); |
| } |
| |
| // For use by tests and fuzzers. It |
| // |
| // 1. serializes a snapshot of the current isolate, |
| // 2. deserializes the snapshot, |
| // 3. and runs VerifyHeap on the resulting isolate. |
| // |
| // The current isolate should not be modified by this call and can keep running |
| // once it completes. |
| RUNTIME_FUNCTION(Runtime_SerializeDeserializeNow) { |
| // TODO(353971258): This function is not currently exposed to fuzzers. |
| // Investigate if it should be. |
| HandleScope scope(isolate); |
| Snapshot::SerializeDeserializeAndVerifyForTesting(isolate, |
| isolate->native_context()); |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_HeapObjectVerify) { |
| HandleScope shs(isolate); |
| CHECK_UNLESS_FUZZING(args.length() == 1); |
| DirectHandle<Object> object = args.at(0); |
| #ifdef VERIFY_HEAP |
| Object::ObjectVerify(*object, isolate); |
| if (IsHeapObject(*object)) { |
| Cast<HeapObject>(*object)->map()->MapVerify(isolate); |
| } |
| #else |
| CHECK(IsObject(*object)); |
| if (IsHeapObject(*object)) { |
| CHECK(IsMap(Cast<HeapObject>(*object)->map())); |
| } else { |
| CHECK(IsSmi(*object)); |
| } |
| #endif |
| return isolate->heap()->ToBoolean(true); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_CompleteInobjectSlackTracking) { |
| // TODO(353928347): This function is not currently exposed to fuzzers. |
| // Investigate if it should be. |
| HandleScope scope(isolate); |
| CHECK_UNLESS_FUZZING(args.length() == 1); |
| CHECK_UNLESS_FUZZING(IsJSObject(args[0])); |
| |
| DirectHandle<JSObject> object = args.at<JSObject>(0); |
| MapUpdater::CompleteInobjectSlackTracking(isolate, object->map()); |
| |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| // Called from the %MajorGCForCompilerTesting intrinsic, this function triggers |
| // a full (major) GC. Maglev and Turbofan/Turboshaft will recognize that it |
| // doesn't have any side effects beyond triggering a GC, and it thus shouldn't |
| // interfere too much with most optimizations (except a few like allocation |
| // folding). If you need finer control over the GC, use the `gc()` function in |
| // combination for --expose-gc, but optimizing compilers will treat that as a |
| // generic runtime call with arbitrary side effects, which may impact various |
| // optimizations. |
| RUNTIME_FUNCTION(Runtime_MajorGCForCompilerTesting) { |
| HandleScope scope(isolate); |
| CHECK_UNLESS_FUZZING(v8_flags.allow_natives_syntax || |
| v8_flags.allow_natives_for_differential_fuzzing); |
| CHECK_UNLESS_FUZZING(args.length() == 0); |
| |
| isolate->heap()->CollectGarbage(OLD_SPACE, GarbageCollectionReason::kTesting); |
| |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_TurbofanStaticAssert) { |
| SealHandleScope shs(isolate); |
| // Always lowered to StaticAssert node in Turbofan, so we never get here in |
| // compiled code. |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_AssertPeeled) { |
| SealHandleScope shs(isolate); |
| // In Turbolev this is lowered to an AssertPeeled node that the loop peeler |
| // removes when it peels the surrounding loop, so we never reach this in |
| // compiled code once peeling fires. |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_AssertNotPeeled) { |
| SealHandleScope shs(isolate); |
| // In Turbolev this is lowered to an AssertPeeled node that fails compilation |
| // if the loop peeler peels the surrounding loop, so we never reach this in |
| // compiled code unless the loop was (correctly) left unpeeled. |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_AssertEscapeAnalysisElided) { |
| SealHandleScope shs(isolate); |
| // Always removed during escape analysis in Turbolev, so we never get here in |
| // compiled code (if it was elided). If it wasn't elided, we crash during |
| // compile. In interpreter, we just return undefined. |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_IsBeingInterpreted) { |
| SealHandleScope shs(isolate); |
| // Always lowered to false in Turbofan, so we never get here in compiled code. |
| return ReadOnlyRoots(isolate).true_value(); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_EnableCodeLoggingForTesting) { |
| // The {NoopListener} currently does nothing on any callback, but reports |
| // {true} on {is_listening_to_code_events()}. Feel free to add assertions to |
| // any method to further test the code logging callbacks. |
| class NoopListener final : public LogEventListener { |
| void CodeCreateEvent(CodeTag tag, DirectHandle<AbstractCode> code, |
| const char* name) final {} |
| void CodeCreateEvent(CodeTag tag, DirectHandle<AbstractCode> code, |
| DirectHandle<Name> name) final {} |
| void CodeCreateEvent(CodeTag tag, DirectHandle<AbstractCode> code, |
| DirectHandle<SharedFunctionInfo> shared, |
| DirectHandle<Name> script_name) final {} |
| void CodeCreateEvent(CodeTag tag, DirectHandle<AbstractCode> code, |
| DirectHandle<SharedFunctionInfo> shared, |
| DirectHandle<Name> script_name, int line, |
| int column) final {} |
| #if V8_ENABLE_WEBASSEMBLY |
| void CodeCreateEvent(CodeTag tag, const wasm::WasmCode* code, |
| wasm::WasmName name, std::string_view source_url, |
| int code_offset, int script_id) final {} |
| #endif // V8_ENABLE_WEBASSEMBLY |
| |
| void CallbackEvent(DirectHandle<Name> name, Address entry_point) final {} |
| void GetterCallbackEvent(DirectHandle<Name> name, |
| Address entry_point) final {} |
| void SetterCallbackEvent(DirectHandle<Name> name, |
| Address entry_point) final {} |
| void RegExpCodeCreateEvent(DirectHandle<AbstractCode> code, |
| DirectHandle<String> source, |
| regexp::Flags flags) final {} |
| void CodeMoveEvent(Tagged<InstructionStream> from, |
| Tagged<InstructionStream> to) final {} |
| void BytecodeMoveEvent(Tagged<BytecodeArray> from, |
| Tagged<BytecodeArray> to) final {} |
| void SharedFunctionInfoMoveEvent(Address from, Address to) final {} |
| void NativeContextMoveEvent(Address from, Address to) final {} |
| void CodeMovingGCEvent() final {} |
| void CodeDisableOptEvent(DirectHandle<AbstractCode> code, |
| DirectHandle<SharedFunctionInfo> shared) final {} |
| void CodeDeoptEvent(DirectHandle<Code> code, DeoptimizeKind kind, |
| Address pc, int fp_to_sp_delta) final {} |
| void CodeDependencyChangeEvent(DirectHandle<Code> code, |
| DirectHandle<SharedFunctionInfo> shared, |
| const char* reason) final {} |
| void WeakCodeClearEvent() final {} |
| |
| bool is_listening_to_code_events() final { return true; } |
| }; |
| static base::LeakyObject<NoopListener> noop_listener; |
| #if V8_ENABLE_WEBASSEMBLY |
| wasm::GetWasmEngine()->EnableCodeLogging(isolate); |
| #endif // V8_ENABLE_WEBASSEMBLY |
| isolate->logger()->AddListener(noop_listener.get()); |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_NewRegExpWithBacktrackLimit) { |
| HandleScope scope(isolate); |
| CHECK_UNLESS_FUZZING(args.length() == 3); |
| CHECK_UNLESS_FUZZING(IsString(args[0])); |
| CHECK_UNLESS_FUZZING(IsString(args[1])); |
| CHECK_UNLESS_FUZZING(IsSmi(args[2])); |
| |
| DirectHandle<String> pattern = args.at<String>(0); |
| DirectHandle<String> flags_string = args.at<String>(1); |
| int backtrack_limit = args.smi_value_at(2); |
| CHECK_UNLESS_FUZZING(backtrack_limit >= 0); |
| |
| auto maybe_flags = JSRegExp::FlagsFromString(isolate, flags_string); |
| CHECK_UNLESS_FUZZING(maybe_flags.has_value()); |
| JSRegExp::Flags flags = maybe_flags.value(); |
| |
| RETURN_RESULT_OR_FAILURE( |
| isolate, JSRegExp::New(isolate, pattern, flags, backtrack_limit)); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_Is64Bit) { |
| SealHandleScope shs(isolate); |
| return isolate->heap()->ToBoolean(kSystemPointerSize == 8); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_BigIntMaxBits) { |
| HandleScope scope(isolate); |
| return *isolate->factory()->NewNumber(BigInt::kMaxBits); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_IsSameHeapObject) { |
| HandleScope scope(isolate); |
| CHECK_UNLESS_FUZZING(args.length() == 2); |
| CHECK_UNLESS_FUZZING(IsHeapObject(args[0])); |
| CHECK_UNLESS_FUZZING(IsHeapObject(args[1])); |
| DirectHandle<HeapObject> obj1 = args.at<HeapObject>(0); |
| DirectHandle<HeapObject> obj2 = args.at<HeapObject>(1); |
| return isolate->heap()->ToBoolean(obj1->address() == obj2->address()); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_IsSharedString) { |
| HandleScope scope(isolate); |
| CHECK_UNLESS_FUZZING(args.length() == 1); |
| CHECK_UNLESS_FUZZING(IsHeapObject(args[0])); |
| DirectHandle<HeapObject> obj = args.at<HeapObject>(0); |
| return isolate->heap()->ToBoolean(IsString(*obj) && |
| Cast<String>(obj)->IsShared()); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_IsInWritableSharedSpace) { |
| HandleScope scope(isolate); |
| CHECK_UNLESS_FUZZING(args.length() == 1); |
| if (!IsHeapObject(args[0])) return ReadOnlyRoots(isolate).false_value(); |
| DirectHandle<HeapObject> obj = args.at<HeapObject>(0); |
| return isolate->heap()->ToBoolean(HeapLayout::InWritableSharedSpace(*obj)); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_ShareObject) { |
| HandleScope scope(isolate); |
| CHECK_UNLESS_FUZZING(args.length() == 1); |
| CHECK_UNLESS_FUZZING(v8_flags.shared_heap); |
| // String sharing needs to be enabled explicitly. For simplicity, this runtime |
| // function only shares any object if shared strings are enabled. |
| CHECK_UNLESS_FUZZING(v8_flags.shared_strings); |
| if (IsSmi(args[0])) return args[0]; |
| CHECK_UNLESS_FUZZING(IsHeapObject(args[0])); |
| Handle<HeapObject> obj = args.at<HeapObject>(0); |
| ShouldThrow should_throw = v8_flags.fuzzing ? kDontThrow : kThrowOnError; |
| MaybeDirectHandle<Object> maybe_shared = |
| Object::Share(isolate, obj, should_throw); |
| DirectHandle<Object> shared; |
| CHECK_UNLESS_FUZZING(maybe_shared.ToHandle(&shared)); |
| return *shared; |
| } |
| |
| RUNTIME_FUNCTION(Runtime_IsInPlaceInternalizableString) { |
| HandleScope scope(isolate); |
| CHECK_UNLESS_FUZZING(args.length() == 1); |
| CHECK_UNLESS_FUZZING(IsHeapObject(args[0])); |
| DirectHandle<HeapObject> obj = args.at<HeapObject>(0); |
| return isolate->heap()->ToBoolean( |
| IsString(*obj) && String::IsInPlaceInternalizable(Cast<String>(*obj))); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_IsInternalizedString) { |
| HandleScope scope(isolate); |
| CHECK_UNLESS_FUZZING(args.length() == 1); |
| CHECK_UNLESS_FUZZING(IsHeapObject(args[0])); |
| DirectHandle<HeapObject> obj = args.at<HeapObject>(0); |
| return isolate->heap()->ToBoolean(IsInternalizedString(*obj)); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_StringToCString) { |
| HandleScope scope(isolate); |
| CHECK_UNLESS_FUZZING(args.length() == 1); |
| CHECK_UNLESS_FUZZING(IsString(args[0])); |
| DirectHandle<String> string = args.at<String>(0); |
| |
| size_t output_length; |
| auto bytes = string->ToCString(&output_length); |
| |
| DirectHandle<JSArrayBuffer> result = |
| isolate->factory() |
| ->NewJSArrayBufferAndBackingStore(output_length, |
| InitializedFlag{false}) |
| .ToHandleChecked(); |
| memcpy(result->backing_store(), bytes.get(), output_length); |
| return *result; |
| } |
| |
| RUNTIME_FUNCTION(Runtime_StringUtf8Value) { |
| HandleScope scope(isolate); |
| CHECK_UNLESS_FUZZING(args.length() == 1); |
| CHECK_UNLESS_FUZZING(IsString(args[0])); |
| DirectHandle<String> string = args.at<String>(0); |
| |
| v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate); |
| v8::String::Utf8Value value(v8_isolate, v8::Utils::ToLocal(string)); |
| |
| DirectHandle<JSArrayBuffer> result = |
| isolate->factory() |
| ->NewJSArrayBufferAndBackingStore(value.length(), |
| InitializedFlag{false}) |
| .ToHandleChecked(); |
| memcpy(result->backing_store(), *value, value.length()); |
| return *result; |
| } |
| |
| RUNTIME_FUNCTION(Runtime_SharedGC) { |
| SealHandleScope scope(isolate); |
| CHECK_UNLESS_FUZZING(isolate->has_shared_space()); |
| isolate->heap()->CollectGarbageShared(isolate->main_thread_local_heap(), |
| GarbageCollectionReason::kTesting); |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_AtomicsSynchronizationPrimitiveNumWaitersForTesting) { |
| HandleScope scope(isolate); |
| CHECK_UNLESS_FUZZING(args.length() == 1); |
| CHECK_UNLESS_FUZZING(IsJSSynchronizationPrimitive(*args.at(0))); |
| DirectHandle<JSSynchronizationPrimitive> primitive = |
| args.at<JSSynchronizationPrimitive>(0); |
| return primitive->NumWaitersForTesting(isolate); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_GetWeakCollectionSize) { |
| HandleScope scope(isolate); |
| CHECK_UNLESS_FUZZING(args.length() == 1); |
| CHECK_UNLESS_FUZZING(IsJSWeakCollection(args[0])); |
| DirectHandle<JSWeakCollection> collection = args.at<JSWeakCollection>(0); |
| |
| return Smi::FromInt( |
| Cast<EphemeronHashTable>(collection->table())->NumberOfElements()); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_SetPriorityBestEffort) { |
| isolate->SetPriority(v8::Isolate::Priority::kBestEffort); |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_SetPriorityUserVisible) { |
| isolate->SetPriority(v8::Isolate::Priority::kUserVisible); |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_SetPriorityUserBlocking) { |
| isolate->SetPriority(v8::Isolate::Priority::kUserBlocking); |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_IsEfficiencyModeEnabled) { |
| if (isolate->EfficiencyModeEnabled()) { |
| return ReadOnlyRoots(isolate).true_value(); |
| } |
| return ReadOnlyRoots(isolate).false_value(); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_IsUndefinedDoubleEnabled) { |
| #ifdef V8_ENABLE_UNDEFINED_DOUBLE |
| return ReadOnlyRoots(isolate).true_value(); |
| #else |
| return ReadOnlyRoots(isolate).false_value(); |
| #endif // V8_ENABLE_UNDEFINED_DOUBLE |
| } |
| |
| RUNTIME_FUNCTION(Runtime_SetBatterySaverMode) { |
| HandleScope scope(isolate); |
| CHECK_UNLESS_FUZZING(args.length() == 1); |
| if (*args.at<Object>(0) == ReadOnlyRoots(isolate).true_value()) { |
| isolate->set_battery_saver_mode_enabled(true); |
| } else { |
| isolate->set_battery_saver_mode_enabled(false); |
| } |
| // If the override flag is set changing the mode has no effect. |
| if (v8_flags.battery_saver_mode.value().has_value()) { |
| return ReadOnlyRoots(isolate).false_value(); |
| } |
| return ReadOnlyRoots(isolate).true_value(); |
| } |
| |
| // Returns true if the tiering state (liftoff, turbofan) of wasm functions can |
| // be asserted in a predictable way. |
| RUNTIME_FUNCTION(Runtime_IsWasmTieringPredictable) { |
| DCHECK_EQ(args.length(), 0); |
| const bool single_isolate = g_num_isolates_for_testing == 1; |
| const bool stress_deopt = v8_flags.deopt_every_n_times > 0; |
| return ReadOnlyRoots(isolate).boolean_value(single_isolate && !stress_deopt); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_GetFeedback) { |
| HandleScope scope(isolate); |
| CHECK_UNLESS_FUZZING(args.length() == 1); |
| DirectHandle<Object> function_object = args.at(0); |
| CHECK_UNLESS_FUZZING(IsJSFunction(*function_object)); |
| DirectHandle<JSFunction> function = Cast<JSFunction>(function_object); |
| |
| bool has_feedback_vector = function->has_feedback_vector(); |
| bool has_bytecode_array = function->shared()->HasBytecodeArray(); |
| CHECK_UNLESS_FUZZING(has_feedback_vector || has_bytecode_array); |
| |
| #ifdef V8_JITLESS |
| // No feedback is collected in jitless mode, so tests calling %GetFeedback |
| // don't make sense. |
| return ReadOnlyRoots(isolate).undefined_value(); |
| #else |
| #ifdef OBJECT_PRINT |
| |
| struct FeedbackValue { |
| std::string slot_kind_; |
| std::string details_; |
| }; |
| |
| std::vector<FeedbackValue> extracted_feedbacks; |
| |
| // 1. collect feedbacks from FeedbackVector |
| if (has_feedback_vector) { |
| DirectHandle<FeedbackVector> feedback_vector = |
| direct_handle(function->feedback_vector(), isolate); |
| |
| CHECK_UNLESS_FUZZING(feedback_vector->has_metadata()); |
| // Make sure the function stays compiled across the following allocations. |
| IsCompiledScope is_compiled_scope( |
| function->shared()->is_compiled_scope(isolate)); |
| USE(is_compiled_scope); |
| |
| FeedbackMetadataIterator iter(handle(feedback_vector->metadata(), isolate)); |
| while (iter.HasNext()) { |
| FeedbackSlot slot = iter.Next(); |
| FeedbackSlotKind kind = iter.kind(); |
| |
| FeedbackValue feedback_value; |
| { |
| std::ostringstream out; |
| out << kind; |
| feedback_value.slot_kind_ = out.str(); |
| } |
| |
| FeedbackNexus nexus(isolate, *feedback_vector, slot); |
| { |
| std::ostringstream out; |
| nexus.Print(out); |
| feedback_value.details_ = out.str(); |
| } |
| |
| extracted_feedbacks.push_back(feedback_value); |
| } |
| } |
| |
| // 2. collect embedded feedback in the BytecodeArray |
| if (has_bytecode_array) { |
| Handle<BytecodeArray> bytecode_array = |
| handle(function->shared()->GetBytecodeArray(isolate), isolate); |
| |
| interpreter::BytecodeArrayIterator it(bytecode_array); |
| for (; !it.done(); it.Advance()) { |
| auto bytecode = it.current_bytecode(); |
| if (!interpreter::Bytecodes::IsEmbeddedFeedbackBytecode(bytecode)) { |
| continue; |
| } |
| |
| FeedbackValue feedback_value; |
| std::ostringstream out; |
| if (interpreter::Bytecodes::IsCompareWithEmbeddedFeedback(bytecode)) { |
| out << "CompareOp"; |
| feedback_value.slot_kind_ = out.str(); |
| out << ":" << it.GetEmbeddedOperationHint<CompareOperationFeedback>(); |
| feedback_value.details_ = out.str(); |
| } else if (interpreter::Bytecodes::IsBinaryOpWithEmbeddedFeedback( |
| bytecode)) { |
| out << "BinaryOp"; |
| feedback_value.slot_kind_ = out.str(); |
| out << ":" << it.GetEmbeddedOperationHint<BinaryOperationFeedback>(); |
| feedback_value.details_ = out.str(); |
| } else if (interpreter::Bytecodes::IsUnaryOpWithEmbeddedFeedback( |
| bytecode)) { |
| out << "UnaryOp"; |
| feedback_value.slot_kind_ = out.str(); |
| out << ":" << it.GetEmbeddedOperationHint<BinaryOperationFeedback>(); |
| feedback_value.details_ = out.str(); |
| } else { |
| UNREACHABLE(); |
| } |
| |
| extracted_feedbacks.push_back(feedback_value); |
| } |
| } |
| |
| // 3. construct output JSArray |
| int result_size = static_cast<int>(extracted_feedbacks.size()); |
| DirectHandle<FixedArray> result = isolate->factory()->NewFixedArray( |
| static_cast<int>(extracted_feedbacks.size())); |
| for (int idx = 0; idx < result_size; idx++) { |
| const auto& feedback_value = extracted_feedbacks[idx]; |
| |
| DirectHandle<FixedArray> sub_result = isolate->factory()->NewFixedArray(2); |
| DirectHandle<String> kind_string = |
| isolate->factory()->NewStringFromAsciiChecked( |
| feedback_value.slot_kind_); |
| sub_result->set(0, *kind_string); |
| DirectHandle<String> details_string = |
| isolate->factory()->NewStringFromAsciiChecked(feedback_value.details_); |
| sub_result->set(1, *details_string); |
| |
| DirectHandle<JSArray> sub_result_array = |
| isolate->factory()->NewJSArrayWithElements(sub_result); |
| result->set(idx, *sub_result_array); |
| } |
| |
| return *isolate->factory()->NewJSArrayWithElements(result); |
| #else |
| return ReadOnlyRoots(isolate).undefined_value(); |
| #endif // OBJECT_PRINT |
| #endif // not V8_JITLESS |
| } |
| |
| RUNTIME_FUNCTION(Runtime_ArrayBufferDetachForceWasm) { |
| HandleScope scope(isolate); |
| // This isn't exposed to fuzzers so doesn't need to handle invalid arguments. |
| DCHECK_LE(args.length(), 2); |
| DCHECK(IsJSArrayBuffer(*args.at(0))); |
| auto array_buffer = Cast<JSArrayBuffer>(args.at(0)); |
| DCHECK(array_buffer->GetBackingStore()->is_wasm_memory()); |
| DCHECK(!array_buffer->is_shared()); |
| constexpr bool kForceForWasmMemory = true; |
| MAYBE_RETURN(JSArrayBuffer::Detach(array_buffer, kForceForWasmMemory, |
| args.atOrUndefined(isolate, 1)), |
| ReadOnlyRoots(isolate).exception()); |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| namespace { |
| |
| // Parses one or two arguments from Smis into ArgType. |
| // Returns false if the arguments were not Smis and true otherwise. |
| template <typename ArgType> |
| bool ParseArgumentsForTablePrinter(const RuntimeArguments& args, |
| Isolate* isolate, ArgType* arg1, |
| ArgType* arg2) { |
| if (args.length() > 2) { |
| return false; |
| } |
| if (args.length() == 1) { |
| Tagged<Smi> smi; |
| if (!TryCast(args[0], &smi)) { |
| return false; |
| } |
| *arg1 = static_cast<ArgType>(Smi::ToInt(smi)); |
| *arg2 = static_cast<ArgType>(Smi::ToInt(smi) + 1); |
| } else if (args.length() == 2) { |
| Tagged<Smi> smi; |
| if (!TryCast(args[0], &smi)) { |
| return false; |
| } |
| *arg1 = static_cast<ArgType>(Smi::ToInt(smi)); |
| if (!TryCast(args[1], &smi)) { |
| return false; |
| } |
| *arg2 = static_cast<ArgType>(Smi::ToInt(smi)); |
| } |
| return true; |
| } |
| |
| template <typename EntryFilter> |
| void PrintCppHeapPointerTableImpl(Isolate* isolate, |
| CppHeapPointerHandle min_handle, |
| CppHeapPointerHandle max_handle, |
| EntryFilter entry_filter) { |
| PrintF("CppHeapPointerTable:\n"); |
| #ifdef OBJECT_PRINT |
| #ifdef V8_COMPRESS_POINTERS |
| const auto& table = Isolate::Current()->cpp_heap_pointer_table(); |
| table.Print(Isolate::Current()->heap()->cpp_heap_pointer_space(), "Old space", |
| min_handle, max_handle, entry_filter); |
| #else // !V8_COMPRESS_POINTERS |
| PrintF("Table not used in this configuration.\n"); |
| #endif // !V8_COMPRESS_POINTERS |
| #else // !OBJECT_PRINT |
| PrintF("Object printing not enabled.\n"); |
| #endif // !OBJECT_PRINT |
| } |
| |
| template <typename EntryFilter> |
| void PrintExternalPointerTableImpl(Isolate* isolate, |
| ExternalPointerHandle min_handle, |
| ExternalPointerHandle max_handle, |
| EntryFilter entry_filter) { |
| PrintF("ExternalPointerTable:\n"); |
| #ifdef OBJECT_PRINT |
| #ifdef V8_COMPRESS_POINTERS |
| const auto& table = Isolate::Current()->external_pointer_table(); |
| table.Print(Isolate::Current()->heap()->read_only_external_pointer_space(), |
| "Read-only space", min_handle, max_handle, entry_filter); |
| table.Print(Isolate::Current()->heap()->young_external_pointer_space(), |
| "Young space", min_handle, max_handle, entry_filter); |
| table.Print(Isolate::Current()->heap()->old_external_pointer_space(), |
| "Old space", min_handle, max_handle, entry_filter); |
| #else // !V8_COMPRESS_POINTERS |
| PrintF("Table not used in this configuration.\n"); |
| #endif // !V8_COMPRESS_POINTERS |
| #else // !OBJECT_PRINT |
| PrintF("Object printing not enabled.\n"); |
| #endif // !OBJECT_PRINT |
| } |
| |
| } // namespace |
| |
| RUNTIME_FUNCTION(Runtime_DebugPrintCppHeapPointerTable) { |
| using HandleType = CppHeapPointerHandle; |
| HandleType min_handle = std::numeric_limits<HandleType>::min(); |
| HandleType max_handle = std::numeric_limits<HandleType>::max(); |
| if (!ParseArgumentsForTablePrinter(args, isolate, &min_handle, &max_handle)) { |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| PrintCppHeapPointerTableImpl(isolate, min_handle, max_handle, |
| [](CppHeapPointerTag) { return true; }); |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_DebugPrintCppHeapPointerTableFilterTag) { |
| using HandleType = CppHeapPointerHandle; |
| const HandleType min_handle = std::numeric_limits<HandleType>::min(); |
| const HandleType max_handle = std::numeric_limits<HandleType>::max(); |
| CppHeapPointerTag min_tag = CppHeapPointerTag::kFirstTag; |
| CppHeapPointerTag max_tag = CppHeapPointerTag::kLastTag; |
| if (!ParseArgumentsForTablePrinter(args, isolate, &min_tag, &max_tag)) { |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| PrintCppHeapPointerTableImpl(isolate, min_handle, max_handle, |
| [min_tag, max_tag](CppHeapPointerTag tag) { |
| return tag >= min_tag && tag < max_tag; |
| }); |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_DebugPrintExternalPointerTable) { |
| using HandleType = ExternalPointerHandle; |
| HandleType min_handle = std::numeric_limits<HandleType>::min(); |
| HandleType max_handle = std::numeric_limits<HandleType>::max(); |
| if (!ParseArgumentsForTablePrinter(args, isolate, &min_handle, &max_handle)) { |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| PrintExternalPointerTableImpl(isolate, min_handle, max_handle, |
| [](ExternalPointerTag) { return true; }); |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_DebugPrintExternalPointerTableFilterTag) { |
| using HandleType = ExternalPointerHandle; |
| const HandleType min_handle = std::numeric_limits<HandleType>::min(); |
| const HandleType max_handle = std::numeric_limits<HandleType>::max(); |
| ExternalPointerTag min_tag = ExternalPointerTag::kFirstExternalPointerTag; |
| ExternalPointerTag max_tag = ExternalPointerTag::kLastExternalPointerTag; |
| if (!ParseArgumentsForTablePrinter(args, isolate, &min_tag, &max_tag)) { |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| PrintExternalPointerTableImpl(isolate, min_handle, max_handle, |
| [min_tag, max_tag](ExternalPointerTag tag) { |
| return tag >= min_tag && tag < max_tag; |
| }); |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_GetHoleNaNUpper) { |
| HandleScope scope(isolate); |
| CHECK_UNLESS_FUZZING(args.length() == 0); |
| return *isolate->factory()->NewNumberFromUint(kHoleNanUpper32); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_GetHoleNaNLower) { |
| HandleScope scope(isolate); |
| CHECK_UNLESS_FUZZING(args.length() == 0); |
| return *isolate->factory()->NewNumberFromUint(kHoleNanLower32); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_GetHoleNaN) { |
| HandleScope scope(isolate); |
| CHECK_UNLESS_FUZZING(args.length() == 0); |
| return *isolate->factory()->NewHeapNumberFromBits(kHoleNanInt64); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_GetUndefinedNaN) { |
| HandleScope scope(isolate); |
| CHECK_UNLESS_FUZZING(args.length() == 0); |
| #if V8_ENABLE_UNDEFINED_DOUBLE |
| return *isolate->factory()->NewHeapNumberFromBits(kUndefinedNanInt64); |
| #else |
| CHECK_UNLESS_FUZZING(false && "undefined NaNs are disabled via build flag"); |
| return ReadOnlyRoots(isolate).undefined_value(); |
| #endif |
| } |
| |
| RUNTIME_FUNCTION(Runtime_GetBytecode) { |
| HandleScope scope(isolate); |
| CHECK_UNLESS_FUZZING(args.length() == 1); |
| CHECK_UNLESS_FUZZING(IsJSFunction(args[0])); |
| DirectHandle<JSFunction> function = args.at<JSFunction>(0); |
| |
| DirectHandle<SharedFunctionInfo> shared(function->shared(), isolate); |
| if (!shared->HasBytecodeArray()) { |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| DirectHandle<BytecodeArray> bytecode_array(shared->GetBytecodeArray(isolate), |
| isolate); |
| |
| if (v8_flags.print_bytecode) { |
| PrintF("%%GetBytecode:\n"); |
| StdoutStream os; |
| bytecode_array->Disassemble(os); |
| os << std::flush; |
| } |
| |
| int length = bytecode_array->length(); |
| Handle<JSArrayBuffer> bytecode_buffer = |
| isolate->factory() |
| ->NewJSArrayBufferAndBackingStore(length, InitializedFlag{false}) |
| .ToHandleChecked(); |
| memcpy( |
| bytecode_buffer->backing_store(), |
| reinterpret_cast<const void*>(bytecode_array->GetFirstBytecodeAddress()), |
| length); |
| |
| DirectHandle<TrustedFixedArray> constant_pool(bytecode_array->constant_pool(), |
| isolate); |
| uint32_t cp_length = constant_pool->ulength().value(); |
| Handle<JSArray> constant_pool_array = |
| isolate->factory()->NewJSArray(cp_length); |
| for (uint32_t i = 0; i < cp_length; ++i) { |
| Handle<Object> value(constant_pool->get(i), isolate); |
| RETURN_FAILURE_ON_EXCEPTION( |
| isolate, Object::SetElement(isolate, constant_pool_array, i, value, |
| ShouldThrow::kThrowOnError)); |
| } |
| |
| DirectHandle<TrustedByteArray> handler_table(bytecode_array->handler_table(), |
| isolate); |
| size_t ht_length = static_cast<size_t>(handler_table->ulength().value()); |
| Handle<JSArrayBuffer> handler_table_buffer = |
| isolate->factory() |
| ->NewJSArrayBufferAndBackingStore(ht_length, InitializedFlag{false}) |
| .ToHandleChecked(); |
| memcpy(handler_table_buffer->backing_store(), handler_table->begin(), |
| ht_length); |
| |
| Handle<JSObject> result = |
| isolate->factory()->NewJSObject(isolate->object_function()); |
| |
| JSObject::AddProperty(isolate, result, "bytecode", bytecode_buffer, |
| PropertyAttributes::NONE); |
| JSObject::AddProperty(isolate, result, "constant_pool", constant_pool_array, |
| PropertyAttributes::NONE); |
| JSObject::AddProperty(isolate, result, "handler_table", handler_table_buffer, |
| PropertyAttributes::NONE); |
| |
| JSObject::AddProperty( |
| isolate, result, "frame_size", |
| isolate->factory()->NewNumberFromInt(bytecode_array->frame_size()), |
| PropertyAttributes::NONE); |
| JSObject::AddProperty( |
| isolate, result, "parameter_count", |
| isolate->factory()->NewNumberFromInt(bytecode_array->parameter_count()), |
| PropertyAttributes::NONE); |
| JSObject::AddProperty( |
| isolate, result, "max_arguments", |
| isolate->factory()->NewNumberFromInt(bytecode_array->max_arguments()), |
| PropertyAttributes::NONE); |
| JSObject::AddProperty( |
| isolate, result, "incoming_new_target_or_generator_register", |
| isolate->factory()->NewNumberFromInt( |
| bytecode_array->incoming_new_target_or_generator_register().index()), |
| PropertyAttributes::NONE); |
| |
| return *result; |
| } |
| |
| RUNTIME_FUNCTION(Runtime_InstallBytecode) { |
| HandleScope scope(isolate); |
| CHECK_UNLESS_FUZZING(args.length() == 2); |
| CHECK_UNLESS_FUZZING(IsJSFunction(args[0])); |
| CHECK_UNLESS_FUZZING(IsJSObject(args[1])); |
| |
| DirectHandle<JSFunction> function = args.at<JSFunction>(0); |
| Handle<JSObject> input = args.at<JSObject>(1); |
| |
| auto GetIntProperty = [&](const char* name) -> Maybe<int32_t> { |
| Handle<Object> val; |
| ASSIGN_RETURN_ON_EXCEPTION_VALUE( |
| isolate, val, JSReceiver::GetProperty(isolate, input, name), |
| Nothing<int32_t>()); |
| Handle<Object> result_obj; |
| ASSIGN_RETURN_ON_EXCEPTION_VALUE( |
| isolate, result_obj, Object::ToInt32(isolate, val), Nothing<int32_t>()); |
| int32_t result; |
| CHECK(Object::ToInt32(*result_obj, &result)); |
| return Just(result); |
| }; |
| |
| Handle<Object> bytecode_obj; |
| ASSIGN_RETURN_FAILURE_ON_EXCEPTION( |
| isolate, bytecode_obj, |
| JSReceiver::GetProperty(isolate, input, "bytecode")); |
| CHECK_UNLESS_FUZZING(IsJSArrayBuffer(*bytecode_obj)); |
| auto bytecode_buffer = Cast<JSArrayBuffer>(bytecode_obj); |
| |
| Handle<Object> cp_obj; |
| ASSIGN_RETURN_FAILURE_ON_EXCEPTION( |
| isolate, cp_obj, |
| JSReceiver::GetProperty(isolate, input, "constant_pool")); |
| CHECK_UNLESS_FUZZING(IsJSArray(*cp_obj)); |
| auto cp_array = Cast<JSArray>(cp_obj); |
| uint32_t cp_length = Smi::ToUInt(cp_array->length()); |
| DirectHandle<TrustedFixedArray> constant_pool = |
| isolate->factory()->NewTrustedFixedArray(cp_length); |
| for (uint32_t i = 0; i < cp_length; ++i) { |
| Handle<Object> val; |
| ASSIGN_RETURN_FAILURE_ON_EXCEPTION( |
| isolate, val, JSReceiver::GetElement(isolate, cp_array, i)); |
| constant_pool->set(i, *val); |
| } |
| |
| Handle<Object> ht_obj; |
| ASSIGN_RETURN_FAILURE_ON_EXCEPTION( |
| isolate, ht_obj, |
| JSReceiver::GetProperty(isolate, input, "handler_table")); |
| CHECK_UNLESS_FUZZING(IsJSArrayBuffer(*ht_obj)); |
| auto ht_buffer = Cast<JSArrayBuffer>(ht_obj); |
| uint32_t ht_length = static_cast<uint32_t>(ht_buffer->byte_length()); |
| uint8_t* ht_data = static_cast<uint8_t*>(ht_buffer->backing_store()); |
| DirectHandle<TrustedByteArray> handler_table = |
| isolate->factory()->NewTrustedByteArray(ht_length); |
| memcpy(handler_table->begin(), ht_data, ht_length); |
| |
| int frame_size; |
| ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, frame_size, |
| GetIntProperty("frame_size")); |
| |
| int parameter_count; |
| ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, parameter_count, |
| GetIntProperty("parameter_count")); |
| |
| int max_arguments; |
| ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, max_arguments, |
| GetIntProperty("max_arguments")); |
| |
| int incoming_reg_index; |
| ASSIGN_RETURN_FAILURE_ON_EXCEPTION( |
| isolate, incoming_reg_index, |
| GetIntProperty("incoming_new_target_or_generator_register")); |
| |
| int length = static_cast<int>(bytecode_buffer->byte_length()); |
| const uint8_t* raw_bytecodes = |
| static_cast<uint8_t*>(bytecode_buffer->backing_store()); |
| Handle<BytecodeArray> new_bytecode = isolate->factory()->NewBytecodeArray( |
| length, raw_bytecodes, frame_size, parameter_count, max_arguments, |
| constant_pool, handler_table); |
| |
| new_bytecode->set_incoming_new_target_or_generator_register( |
| interpreter::Register(incoming_reg_index)); |
| |
| Zone zone(isolate->allocator(), "Runtime_InstallBytecode"); |
| BytecodeVerifier::Verify(isolate, new_bytecode, &zone); |
| |
| if (v8_flags.print_bytecode) { |
| PrintF("%%InstallBytecode:\n"); |
| StdoutStream os; |
| new_bytecode->Disassemble(os); |
| os << std::flush; |
| } |
| |
| DirectHandle<SharedFunctionInfo> shared(function->shared(), isolate); |
| if (shared->is_compiled()) { |
| SharedFunctionInfo::DiscardCompiled(isolate, shared); |
| } |
| shared->set_bytecode_array(*new_bytecode); |
| |
| if (function->HasAttachedOptimizedCode(isolate)) { |
| Deoptimizer::DeoptimizeFunction(*function, LazyDeoptimizeReason::kTesting, |
| function->code(isolate)); |
| } |
| function->UpdateCode(isolate, |
| *BUILTIN_CODE(isolate, InterpreterEntryTrampoline)); |
| |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_AllocateHeapNumberWithValue) { |
| HandleScope scope(isolate); |
| CHECK_UNLESS_FUZZING(args.length() == 1); |
| MaybeHandle<Number> result = |
| Object::ToNumber(isolate, handle(args[0], isolate)); |
| DirectHandle<Number> result_handle; |
| if (!result.ToHandle(&result_handle)) { |
| return ReadOnlyRoots(isolate).exception(); |
| } |
| // Force HeapNumber, even if the value is representable as a Smi. |
| if (IsSmi(*result_handle)) { |
| return *isolate->factory()->NewHeapNumber(i::Smi::ToInt(*result_handle)); |
| } |
| return *result_handle; |
| } |
| |
| // Blocks the background compiler (or other background tasks) at the specified |
| // synchronization point. The task will remain blocked until %Resume is called. |
| // This is primarily used for deterministically testing race conditions. |
| RUNTIME_FUNCTION(Runtime_BlockAt) { |
| HandleScope scope(isolate); |
| CHECK_UNLESS_FUZZING(v8_flags.allow_natives_syntax || |
| v8_flags.allow_natives_for_differential_fuzzing); |
| DCHECK_EQ(2, args.length()); |
| CHECK_UNLESS_FUZZING(IsString(args[0])); |
| CHECK_UNLESS_FUZZING(IsSmi(args[1])); |
| DirectHandle<String> phase_name = args.at<String>(0); |
| base::TimeDelta timeout = |
| base::TimeDelta::FromMilliseconds(args.smi_value_at(1)); |
| SynchronizationPointSupport::Get()->RequestBlockAt(phase_name->ToStdString(), |
| timeout); |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| // Resumes a background compiler (or other background task) that was previously |
| // blocked by %BlockAt at the specified synchronization point. |
| RUNTIME_FUNCTION(Runtime_Resume) { |
| HandleScope scope(isolate); |
| CHECK_UNLESS_FUZZING(v8_flags.allow_natives_syntax || |
| v8_flags.allow_natives_for_differential_fuzzing); |
| DCHECK_EQ(1, args.length()); |
| CHECK_UNLESS_FUZZING(IsString(args[0])); |
| DirectHandle<String> phase_name = args.at<String>(0); |
| bool success = |
| SynchronizationPointSupport::Get()->Resume(phase_name->ToStdString()); |
| return isolate->heap()->ToBoolean(success); |
| } |
| |
| // Waits until the given synchronization point is reached. Throws an exception |
| // if the point is not armed or if a timeout is reached. The timeout is |
| // provided in milliseconds. |
| RUNTIME_FUNCTION(Runtime_WaitUntilBlocked) { |
| HandleScope scope(isolate); |
| CHECK_UNLESS_FUZZING(v8_flags.allow_natives_syntax || |
| v8_flags.allow_natives_for_differential_fuzzing); |
| DCHECK_EQ(2, args.length()); |
| CHECK_UNLESS_FUZZING(IsString(args[0])); |
| CHECK_UNLESS_FUZZING(IsSmi(args[1])); |
| DirectHandle<String> phase_name = args.at<String>(0); |
| base::TimeDelta timeout = |
| base::TimeDelta::FromMilliseconds(args.smi_value_at(1)); |
| |
| bool success = SynchronizationPointSupport::Get()->WaitUntilBlocked( |
| phase_name->ToStdString(), timeout); |
| return isolate->heap()->ToBoolean(success); |
| } |
| |
| } // namespace internal |
| } // namespace v8 |