blob: 7fb0b3723f75070a63a341c30e97e9b2fcb9ea35 [file] [log] [blame]
// Copyright 2015 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/debug/debug-evaluate.h"
#include "src/builtins/accessors.h"
#include "src/codegen/assembler-inl.h"
#include "src/codegen/compiler.h"
#include "src/common/globals.h"
#include "src/debug/debug-frames.h"
#include "src/debug/debug-scopes.h"
#include "src/debug/debug.h"
#include "src/execution/frames-inl.h"
#include "src/execution/isolate-inl.h"
#include "src/interpreter/bytecode-array-iterator.h"
#include "src/interpreter/bytecodes.h"
#include "src/objects/contexts.h"
#include "src/snapshot/snapshot.h"
#include "src/wasm/wasm-debug.h"
#include "src/wasm/wasm-js.h"
namespace v8 {
namespace internal {
namespace {
static MaybeHandle<SharedFunctionInfo> GetFunctionInfo(Isolate* isolate,
Handle<String> source,
REPLMode repl_mode) {
Compiler::ScriptDetails script_details(isolate->factory()->empty_string());
script_details.repl_mode = repl_mode;
ScriptOriginOptions origin_options(false, true);
return Compiler::GetSharedFunctionInfoForScript(
isolate, source, script_details, origin_options, nullptr, nullptr,
ScriptCompiler::kNoCompileOptions, ScriptCompiler::kNoCacheNoReason,
NOT_NATIVES_CODE);
}
} // namespace
MaybeHandle<Object> DebugEvaluate::Global(Isolate* isolate,
Handle<String> source,
debug::EvaluateGlobalMode mode,
REPLMode repl_mode) {
// Disable breaks in side-effect free mode.
DisableBreak disable_break_scope(
isolate->debug(),
mode == debug::EvaluateGlobalMode::kDisableBreaks ||
mode ==
debug::EvaluateGlobalMode::kDisableBreaksAndThrowOnSideEffect);
Handle<SharedFunctionInfo> shared_info;
if (!GetFunctionInfo(isolate, source, repl_mode).ToHandle(&shared_info)) {
return MaybeHandle<Object>();
}
Handle<Context> context = isolate->native_context();
Handle<JSFunction> fun =
isolate->factory()->NewFunctionFromSharedFunctionInfo(shared_info,
context);
if (mode == debug::EvaluateGlobalMode::kDisableBreaksAndThrowOnSideEffect) {
isolate->debug()->StartSideEffectCheckMode();
}
MaybeHandle<Object> result = Execution::Call(
isolate, fun, Handle<JSObject>(context->global_proxy(), isolate), 0,
nullptr);
if (mode == debug::EvaluateGlobalMode::kDisableBreaksAndThrowOnSideEffect) {
isolate->debug()->StopSideEffectCheckMode();
}
return result;
}
MaybeHandle<Object> DebugEvaluate::Local(Isolate* isolate,
StackFrameId frame_id,
int inlined_jsframe_index,
Handle<String> source,
bool throw_on_side_effect) {
// Handle the processing of break.
DisableBreak disable_break_scope(isolate->debug());
// Get the frame where the debugging is performed.
StackTraceFrameIterator it(isolate, frame_id);
if (!it.is_javascript()) return isolate->factory()->undefined_value();
JavaScriptFrame* frame = it.javascript_frame();
// This is not a lot different than DebugEvaluate::Global, except that
// variables accessible by the function we are evaluating from are
// materialized and included on top of the native context. Changes to
// the materialized object are written back afterwards.
// Note that the native context is taken from the original context chain,
// which may not be the current native context of the isolate.
ContextBuilder context_builder(isolate, frame, inlined_jsframe_index);
if (isolate->has_pending_exception()) return MaybeHandle<Object>();
Handle<Context> context = context_builder.evaluation_context();
Handle<JSObject> receiver(context->global_proxy(), isolate);
MaybeHandle<Object> maybe_result =
Evaluate(isolate, context_builder.outer_info(), context, receiver, source,
throw_on_side_effect);
if (!maybe_result.is_null()) context_builder.UpdateValues();
return maybe_result;
}
V8_EXPORT MaybeHandle<Object> DebugEvaluate::WebAssembly(
Handle<WasmInstanceObject> instance, StackFrameId frame_id,
Handle<String> source, bool throw_on_side_effect) {
Isolate* isolate = instance->GetIsolate();
StackTraceFrameIterator it(isolate, frame_id);
if (!it.is_wasm()) return isolate->factory()->undefined_value();
WasmFrame* frame = WasmFrame::cast(it.frame());
Handle<JSProxy> context_extension = WasmJs::GetJSDebugProxy(frame);
DisableBreak disable_break_scope(isolate->debug(), /*disable=*/true);
Handle<SharedFunctionInfo> shared_info;
if (!GetFunctionInfo(isolate, source, REPLMode::kNo).ToHandle(&shared_info)) {
return {};
}
Handle<ScopeInfo> scope_info =
ScopeInfo::CreateForWithScope(isolate, Handle<ScopeInfo>::null());
Handle<Context> context = isolate->factory()->NewWithContext(
isolate->native_context(), scope_info, context_extension);
Handle<Object> result;
if (!DebugEvaluate::Evaluate(isolate, shared_info, context, context_extension,
source, throw_on_side_effect)
.ToHandle(&result)) {
return {};
}
return result;
}
MaybeHandle<Object> DebugEvaluate::WithTopmostArguments(Isolate* isolate,
Handle<String> source) {
// Handle the processing of break.
DisableBreak disable_break_scope(isolate->debug());
Factory* factory = isolate->factory();
JavaScriptFrameIterator it(isolate);
// Get context and receiver.
Handle<Context> native_context(
Context::cast(it.frame()->context()).native_context(), isolate);
// Materialize arguments as property on an extension object.
Handle<JSObject> materialized = factory->NewJSObjectWithNullProto();
Handle<String> arguments_str = factory->arguments_string();
JSObject::SetOwnPropertyIgnoreAttributes(
materialized, arguments_str,
Accessors::FunctionGetArguments(it.frame(), 0), NONE)
.Check();
// Materialize receiver.
Handle<Object> this_value(it.frame()->receiver(), isolate);
DCHECK_EQ(it.frame()->IsConstructor(), this_value->IsTheHole(isolate));
if (!this_value->IsTheHole(isolate)) {
Handle<String> this_str = factory->this_string();
JSObject::SetOwnPropertyIgnoreAttributes(materialized, this_str, this_value,
NONE)
.Check();
}
// Use extension object in a debug-evaluate scope.
Handle<ScopeInfo> scope_info =
ScopeInfo::CreateForWithScope(isolate, Handle<ScopeInfo>::null());
scope_info->SetIsDebugEvaluateScope();
Handle<Context> evaluation_context =
factory->NewDebugEvaluateContext(native_context, scope_info, materialized,
Handle<Context>(), Handle<StringSet>());
Handle<SharedFunctionInfo> outer_info(
native_context->empty_function().shared(), isolate);
Handle<JSObject> receiver(native_context->global_proxy(), isolate);
const bool throw_on_side_effect = false;
MaybeHandle<Object> maybe_result =
Evaluate(isolate, outer_info, evaluation_context, receiver, source,
throw_on_side_effect);
return maybe_result;
}
// Compile and evaluate source for the given context.
MaybeHandle<Object> DebugEvaluate::Evaluate(
Isolate* isolate, Handle<SharedFunctionInfo> outer_info,
Handle<Context> context, Handle<Object> receiver, Handle<String> source,
bool throw_on_side_effect) {
Handle<JSFunction> eval_fun;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, eval_fun,
Compiler::GetFunctionFromEval(source, outer_info, context,
LanguageMode::kSloppy, NO_PARSE_RESTRICTION,
kNoSourcePosition, kNoSourcePosition,
kNoSourcePosition),
Object);
Handle<Object> result;
bool success = false;
if (throw_on_side_effect) isolate->debug()->StartSideEffectCheckMode();
success = Execution::Call(isolate, eval_fun, receiver, 0, nullptr)
.ToHandle(&result);
if (throw_on_side_effect) isolate->debug()->StopSideEffectCheckMode();
if (!success) DCHECK(isolate->has_pending_exception());
return success ? result : MaybeHandle<Object>();
}
Handle<SharedFunctionInfo> DebugEvaluate::ContextBuilder::outer_info() const {
return handle(frame_inspector_.GetFunction()->shared(), isolate_);
}
DebugEvaluate::ContextBuilder::ContextBuilder(Isolate* isolate,
JavaScriptFrame* frame,
int inlined_jsframe_index)
: isolate_(isolate),
frame_inspector_(frame, inlined_jsframe_index, isolate),
scope_iterator_(isolate, &frame_inspector_,
ScopeIterator::ReparseStrategy::kScript) {
Handle<Context> outer_context(frame_inspector_.GetFunction()->context(),
isolate);
evaluation_context_ = outer_context;
Factory* factory = isolate->factory();
if (scope_iterator_.Done()) return;
// To evaluate as if we were running eval at the point of the debug break,
// we reconstruct the context chain as follows:
// - To make stack-allocated variables visible, we materialize them and
// use a debug-evaluate context to wrap both the materialized object and
// the original context.
// - We also wrap all contexts on the chain between the original context
// and the function context.
// - Between the function scope and the native context, we only resolve
// variable names that are guaranteed to not be shadowed by stack-allocated
// variables. Contexts between the function context and the original
// context have a blocklist attached to implement that.
// Context::Lookup has special handling for debug-evaluate contexts:
// - Look up in the materialized stack variables.
// - Check the blocklist to find out whether to abort further lookup.
// - Look up in the original context.
for (; !scope_iterator_.Done(); scope_iterator_.Next()) {
ScopeIterator::ScopeType scope_type = scope_iterator_.Type();
if (scope_type == ScopeIterator::ScopeTypeScript) break;
ContextChainElement context_chain_element;
if (scope_iterator_.InInnerScope() &&
(scope_type == ScopeIterator::ScopeTypeLocal ||
scope_iterator_.DeclaresLocals(ScopeIterator::Mode::STACK))) {
context_chain_element.materialized_object =
scope_iterator_.ScopeObject(ScopeIterator::Mode::STACK);
}
if (scope_iterator_.HasContext()) {
context_chain_element.wrapped_context = scope_iterator_.CurrentContext();
}
if (!scope_iterator_.InInnerScope()) {
context_chain_element.blocklist = scope_iterator_.GetLocals();
}
context_chain_.push_back(context_chain_element);
}
Handle<ScopeInfo> scope_info =
evaluation_context_->IsNativeContext()
? Handle<ScopeInfo>::null()
: handle(evaluation_context_->scope_info(), isolate);
for (auto rit = context_chain_.rbegin(); rit != context_chain_.rend();
rit++) {
ContextChainElement element = *rit;
scope_info = ScopeInfo::CreateForWithScope(isolate, scope_info);
scope_info->SetIsDebugEvaluateScope();
evaluation_context_ = factory->NewDebugEvaluateContext(
evaluation_context_, scope_info, element.materialized_object,
element.wrapped_context, element.blocklist);
}
}
void DebugEvaluate::ContextBuilder::UpdateValues() {
scope_iterator_.Restart();
for (ContextChainElement& element : context_chain_) {
if (!element.materialized_object.is_null()) {
Handle<FixedArray> keys =
KeyAccumulator::GetKeys(element.materialized_object,
KeyCollectionMode::kOwnOnly,
ENUMERABLE_STRINGS)
.ToHandleChecked();
for (int i = 0; i < keys->length(); i++) {
DCHECK(keys->get(i).IsString());
Handle<String> key(String::cast(keys->get(i)), isolate_);
Handle<Object> value =
JSReceiver::GetDataProperty(element.materialized_object, key);
scope_iterator_.SetVariableValue(key, value);
}
}
scope_iterator_.Next();
}
}
namespace {
bool IntrinsicHasNoSideEffect(Runtime::FunctionId id) {
// Use macro to include only the non-inlined version of an intrinsic.
#define INTRINSIC_ALLOWLIST(V) \
/* Conversions */ \
V(NumberToStringSlow) \
V(ToBigInt) \
V(ToLength) \
V(ToNumber) \
V(ToObject) \
V(ToString) \
/* Type checks */ \
V(IsArray) \
V(IsFunction) \
V(IsJSProxy) \
V(IsJSReceiver) \
V(IsRegExp) \
V(IsSmi) \
/* Loads */ \
V(LoadLookupSlotForCall) \
V(GetProperty) \
/* Arrays */ \
V(ArraySpeciesConstructor) \
V(HasFastPackedElements) \
V(NewArray) \
V(NormalizeElements) \
V(TypedArrayGetBuffer) \
/* Errors */ \
V(NewTypeError) \
V(ReThrow) \
V(ThrowCalledNonCallable) \
V(ThrowInvalidStringLength) \
V(ThrowIteratorError) \
V(ThrowIteratorResultNotAnObject) \
V(ThrowPatternAssignmentNonCoercible) \
V(ThrowReferenceError) \
V(ThrowSymbolIteratorInvalid) \
/* Strings */ \
V(StringIncludes) \
V(StringIndexOf) \
V(StringReplaceOneCharWithString) \
V(StringSubstring) \
V(StringToNumber) \
V(StringTrim) \
/* BigInts */ \
V(BigIntEqualToBigInt) \
V(BigIntToBoolean) \
V(BigIntToNumber) \
/* Literals */ \
V(CreateArrayLiteral) \
V(CreateArrayLiteralWithoutAllocationSite) \
V(CreateObjectLiteral) \
V(CreateObjectLiteralWithoutAllocationSite) \
V(CreateRegExpLiteral) \
/* Called from builtins */ \
V(AllocateInYoungGeneration) \
V(AllocateInOldGeneration) \
V(AllocateSeqOneByteString) \
V(AllocateSeqTwoByteString) \
V(ArrayIncludes_Slow) \
V(ArrayIndexOf) \
V(ArrayIsArray) \
V(GetFunctionName) \
V(GetOwnPropertyDescriptor) \
V(GlobalPrint) \
V(HasProperty) \
V(ObjectCreate) \
V(ObjectEntries) \
V(ObjectEntriesSkipFastPath) \
V(ObjectHasOwnProperty) \
V(ObjectKeys) \
V(ObjectValues) \
V(ObjectValuesSkipFastPath) \
V(ObjectGetOwnPropertyNames) \
V(ObjectGetOwnPropertyNamesTryFast) \
V(ObjectIsExtensible) \
V(RegExpInitializeAndCompile) \
V(StackGuard) \
V(StringAdd) \
V(StringCharCodeAt) \
V(StringEqual) \
V(StringIndexOfUnchecked) \
V(StringParseFloat) \
V(StringParseInt) \
V(SymbolDescriptiveString) \
V(ThrowRangeError) \
V(ThrowTypeError) \
V(ToName) \
V(TransitionElementsKind) \
/* Misc. */ \
V(Call) \
V(CompleteInobjectSlackTrackingForMap) \
V(HasInPrototypeChain) \
V(IncrementUseCounter) \
V(MaxSmi) \
V(NewObject) \
V(StringMaxLength) \
V(StringToArray) \
V(AsyncFunctionEnter) \
V(AsyncFunctionReject) \
V(AsyncFunctionResolve) \
/* Test */ \
V(GetOptimizationStatus) \
V(OptimizeFunctionOnNextCall) \
V(OptimizeOsr) \
V(UnblockConcurrentRecompilation)
// Intrinsics with inline versions have to be allowlisted here a second time.
#define INLINE_INTRINSIC_ALLOWLIST(V) \
V(Call) \
V(IsJSReceiver) \
V(AsyncFunctionEnter) \
V(AsyncFunctionReject) \
V(AsyncFunctionResolve)
#define CASE(Name) case Runtime::k##Name:
#define INLINE_CASE(Name) case Runtime::kInline##Name:
switch (id) {
INTRINSIC_ALLOWLIST(CASE)
INLINE_INTRINSIC_ALLOWLIST(INLINE_CASE)
return true;
default:
if (FLAG_trace_side_effect_free_debug_evaluate) {
PrintF("[debug-evaluate] intrinsic %s may cause side effect.\n",
Runtime::FunctionForId(id)->name);
}
return false;
}
#undef CASE
#undef INLINE_CASE
#undef INTRINSIC_ALLOWLIST
#undef INLINE_INTRINSIC_ALLOWLIST
}
bool BytecodeHasNoSideEffect(interpreter::Bytecode bytecode) {
using interpreter::Bytecode;
using interpreter::Bytecodes;
if (Bytecodes::IsWithoutExternalSideEffects(bytecode)) return true;
if (Bytecodes::IsCallOrConstruct(bytecode)) return true;
if (Bytecodes::IsJumpIfToBoolean(bytecode)) return true;
if (Bytecodes::IsPrefixScalingBytecode(bytecode)) return true;
switch (bytecode) {
// Allowlist for bytecodes.
// Loads.
case Bytecode::kLdaLookupSlot:
case Bytecode::kLdaGlobal:
case Bytecode::kLdaNamedProperty:
case Bytecode::kLdaNamedPropertyNoFeedback:
case Bytecode::kLdaKeyedProperty:
case Bytecode::kLdaGlobalInsideTypeof:
case Bytecode::kLdaLookupSlotInsideTypeof:
case Bytecode::kGetIterator:
// Arithmetics.
case Bytecode::kAdd:
case Bytecode::kAddSmi:
case Bytecode::kSub:
case Bytecode::kSubSmi:
case Bytecode::kMul:
case Bytecode::kMulSmi:
case Bytecode::kDiv:
case Bytecode::kDivSmi:
case Bytecode::kMod:
case Bytecode::kModSmi:
case Bytecode::kExp:
case Bytecode::kExpSmi:
case Bytecode::kNegate:
case Bytecode::kBitwiseAnd:
case Bytecode::kBitwiseAndSmi:
case Bytecode::kBitwiseNot:
case Bytecode::kBitwiseOr:
case Bytecode::kBitwiseOrSmi:
case Bytecode::kBitwiseXor:
case Bytecode::kBitwiseXorSmi:
case Bytecode::kShiftLeft:
case Bytecode::kShiftLeftSmi:
case Bytecode::kShiftRight:
case Bytecode::kShiftRightSmi:
case Bytecode::kShiftRightLogical:
case Bytecode::kShiftRightLogicalSmi:
case Bytecode::kInc:
case Bytecode::kDec:
case Bytecode::kLogicalNot:
case Bytecode::kToBooleanLogicalNot:
case Bytecode::kTypeOf:
// Contexts.
case Bytecode::kCreateBlockContext:
case Bytecode::kCreateCatchContext:
case Bytecode::kCreateFunctionContext:
case Bytecode::kCreateEvalContext:
case Bytecode::kCreateWithContext:
// Literals.
case Bytecode::kCreateArrayLiteral:
case Bytecode::kCreateEmptyArrayLiteral:
case Bytecode::kCreateArrayFromIterable:
case Bytecode::kCreateObjectLiteral:
case Bytecode::kCreateEmptyObjectLiteral:
case Bytecode::kCreateRegExpLiteral:
// Allocations.
case Bytecode::kCreateClosure:
case Bytecode::kCreateUnmappedArguments:
case Bytecode::kCreateRestParameter:
// Comparisons.
case Bytecode::kTestEqual:
case Bytecode::kTestEqualStrict:
case Bytecode::kTestLessThan:
case Bytecode::kTestLessThanOrEqual:
case Bytecode::kTestGreaterThan:
case Bytecode::kTestGreaterThanOrEqual:
case Bytecode::kTestInstanceOf:
case Bytecode::kTestIn:
case Bytecode::kTestReferenceEqual:
case Bytecode::kTestUndetectable:
case Bytecode::kTestTypeOf:
case Bytecode::kTestUndefined:
case Bytecode::kTestNull:
// Conversions.
case Bytecode::kToObject:
case Bytecode::kToName:
case Bytecode::kToNumber:
case Bytecode::kToNumeric:
case Bytecode::kToString:
// Misc.
case Bytecode::kIncBlockCounter: // Coverage counters.
case Bytecode::kForInEnumerate:
case Bytecode::kForInPrepare:
case Bytecode::kForInContinue:
case Bytecode::kForInNext:
case Bytecode::kForInStep:
case Bytecode::kJumpLoop:
case Bytecode::kThrow:
case Bytecode::kReThrow:
case Bytecode::kThrowReferenceErrorIfHole:
case Bytecode::kThrowSuperNotCalledIfHole:
case Bytecode::kThrowSuperAlreadyCalledIfNotHole:
case Bytecode::kIllegal:
case Bytecode::kCallJSRuntime:
case Bytecode::kReturn:
case Bytecode::kSetPendingMessage:
return true;
default:
return false;
}
}
DebugInfo::SideEffectState BuiltinGetSideEffectState(Builtins::Name id) {
switch (id) {
// Allowlist for builtins.
// Object builtins.
case Builtins::kObjectConstructor:
case Builtins::kObjectCreate:
case Builtins::kObjectEntries:
case Builtins::kObjectGetOwnPropertyDescriptor:
case Builtins::kObjectGetOwnPropertyDescriptors:
case Builtins::kObjectGetOwnPropertyNames:
case Builtins::kObjectGetOwnPropertySymbols:
case Builtins::kObjectGetPrototypeOf:
case Builtins::kObjectIs:
case Builtins::kObjectIsExtensible:
case Builtins::kObjectIsFrozen:
case Builtins::kObjectIsSealed:
case Builtins::kObjectKeys:
case Builtins::kObjectPrototypeValueOf:
case Builtins::kObjectValues:
case Builtins::kObjectPrototypeHasOwnProperty:
case Builtins::kObjectPrototypeIsPrototypeOf:
case Builtins::kObjectPrototypePropertyIsEnumerable:
case Builtins::kObjectPrototypeToString:
case Builtins::kObjectPrototypeToLocaleString:
// Array builtins.
case Builtins::kArrayIsArray:
case Builtins::kArrayConstructor:
case Builtins::kArrayIndexOf:
case Builtins::kArrayPrototypeValues:
case Builtins::kArrayIncludes:
case Builtins::kArrayPrototypeEntries:
case Builtins::kArrayPrototypeFill:
case Builtins::kArrayPrototypeFind:
case Builtins::kArrayPrototypeFindIndex:
case Builtins::kArrayPrototypeFlat:
case Builtins::kArrayPrototypeFlatMap:
case Builtins::kArrayPrototypeJoin:
case Builtins::kArrayPrototypeKeys:
case Builtins::kArrayPrototypeLastIndexOf:
case Builtins::kArrayPrototypeSlice:
case Builtins::kArrayPrototypeToLocaleString:
case Builtins::kArrayPrototypeToString:
case Builtins::kArrayForEach:
case Builtins::kArrayEvery:
case Builtins::kArraySome:
case Builtins::kArrayConcat:
case Builtins::kArrayFilter:
case Builtins::kArrayMap:
case Builtins::kArrayReduce:
case Builtins::kArrayReduceRight:
// Trace builtins.
case Builtins::kIsTraceCategoryEnabled:
case Builtins::kTrace:
// TypedArray builtins.
case Builtins::kTypedArrayConstructor:
case Builtins::kTypedArrayPrototypeBuffer:
case Builtins::kTypedArrayPrototypeByteLength:
case Builtins::kTypedArrayPrototypeByteOffset:
case Builtins::kTypedArrayPrototypeLength:
case Builtins::kTypedArrayPrototypeEntries:
case Builtins::kTypedArrayPrototypeKeys:
case Builtins::kTypedArrayPrototypeValues:
case Builtins::kTypedArrayPrototypeFind:
case Builtins::kTypedArrayPrototypeFindIndex:
case Builtins::kTypedArrayPrototypeIncludes:
case Builtins::kTypedArrayPrototypeJoin:
case Builtins::kTypedArrayPrototypeIndexOf:
case Builtins::kTypedArrayPrototypeLastIndexOf:
case Builtins::kTypedArrayPrototypeSlice:
case Builtins::kTypedArrayPrototypeSubArray:
case Builtins::kTypedArrayPrototypeEvery:
case Builtins::kTypedArrayPrototypeSome:
case Builtins::kTypedArrayPrototypeToLocaleString:
case Builtins::kTypedArrayPrototypeFilter:
case Builtins::kTypedArrayPrototypeMap:
case Builtins::kTypedArrayPrototypeReduce:
case Builtins::kTypedArrayPrototypeReduceRight:
case Builtins::kTypedArrayPrototypeForEach:
// ArrayBuffer builtins.
case Builtins::kArrayBufferConstructor:
case Builtins::kArrayBufferPrototypeGetByteLength:
case Builtins::kArrayBufferIsView:
case Builtins::kArrayBufferPrototypeSlice:
case Builtins::kReturnReceiver:
// DataView builtins.
case Builtins::kDataViewConstructor:
case Builtins::kDataViewPrototypeGetBuffer:
case Builtins::kDataViewPrototypeGetByteLength:
case Builtins::kDataViewPrototypeGetByteOffset:
case Builtins::kDataViewPrototypeGetInt8:
case Builtins::kDataViewPrototypeGetUint8:
case Builtins::kDataViewPrototypeGetInt16:
case Builtins::kDataViewPrototypeGetUint16:
case Builtins::kDataViewPrototypeGetInt32:
case Builtins::kDataViewPrototypeGetUint32:
case Builtins::kDataViewPrototypeGetFloat32:
case Builtins::kDataViewPrototypeGetFloat64:
case Builtins::kDataViewPrototypeGetBigInt64:
case Builtins::kDataViewPrototypeGetBigUint64:
// Boolean bulitins.
case Builtins::kBooleanConstructor:
case Builtins::kBooleanPrototypeToString:
case Builtins::kBooleanPrototypeValueOf:
// Date builtins.
case Builtins::kDateConstructor:
case Builtins::kDateNow:
case Builtins::kDateParse:
case Builtins::kDatePrototypeGetDate:
case Builtins::kDatePrototypeGetDay:
case Builtins::kDatePrototypeGetFullYear:
case Builtins::kDatePrototypeGetHours:
case Builtins::kDatePrototypeGetMilliseconds:
case Builtins::kDatePrototypeGetMinutes:
case Builtins::kDatePrototypeGetMonth:
case Builtins::kDatePrototypeGetSeconds:
case Builtins::kDatePrototypeGetTime:
case Builtins::kDatePrototypeGetTimezoneOffset:
case Builtins::kDatePrototypeGetUTCDate:
case Builtins::kDatePrototypeGetUTCDay:
case Builtins::kDatePrototypeGetUTCFullYear:
case Builtins::kDatePrototypeGetUTCHours:
case Builtins::kDatePrototypeGetUTCMilliseconds:
case Builtins::kDatePrototypeGetUTCMinutes:
case Builtins::kDatePrototypeGetUTCMonth:
case Builtins::kDatePrototypeGetUTCSeconds:
case Builtins::kDatePrototypeGetYear:
case Builtins::kDatePrototypeToDateString:
case Builtins::kDatePrototypeToISOString:
case Builtins::kDatePrototypeToUTCString:
case Builtins::kDatePrototypeToString:
#ifdef V8_INTL_SUPPORT
case Builtins::kDatePrototypeToLocaleString:
case Builtins::kDatePrototypeToLocaleDateString:
case Builtins::kDatePrototypeToLocaleTimeString:
#endif
case Builtins::kDatePrototypeToTimeString:
case Builtins::kDatePrototypeToJson:
case Builtins::kDatePrototypeToPrimitive:
case Builtins::kDatePrototypeValueOf:
// Map builtins.
case Builtins::kMapConstructor:
case Builtins::kMapPrototypeForEach:
case Builtins::kMapPrototypeGet:
case Builtins::kMapPrototypeHas:
case Builtins::kMapPrototypeEntries:
case Builtins::kMapPrototypeGetSize:
case Builtins::kMapPrototypeKeys:
case Builtins::kMapPrototypeValues:
// WeakMap builtins.
case Builtins::kWeakMapConstructor:
case Builtins::kWeakMapGet:
case Builtins::kWeakMapPrototypeHas:
// Math builtins.
case Builtins::kMathAbs:
case Builtins::kMathAcos:
case Builtins::kMathAcosh:
case Builtins::kMathAsin:
case Builtins::kMathAsinh:
case Builtins::kMathAtan:
case Builtins::kMathAtanh:
case Builtins::kMathAtan2:
case Builtins::kMathCeil:
case Builtins::kMathCbrt:
case Builtins::kMathExpm1:
case Builtins::kMathClz32:
case Builtins::kMathCos:
case Builtins::kMathCosh:
case Builtins::kMathExp:
case Builtins::kMathFloor:
case Builtins::kMathFround:
case Builtins::kMathHypot:
case Builtins::kMathImul:
case Builtins::kMathLog:
case Builtins::kMathLog1p:
case Builtins::kMathLog2:
case Builtins::kMathLog10:
case Builtins::kMathMax:
case Builtins::kMathMin:
case Builtins::kMathPow:
case Builtins::kMathRound:
case Builtins::kMathSign:
case Builtins::kMathSin:
case Builtins::kMathSinh:
case Builtins::kMathSqrt:
case Builtins::kMathTan:
case Builtins::kMathTanh:
case Builtins::kMathTrunc:
// Number builtins.
case Builtins::kNumberConstructor:
case Builtins::kNumberIsFinite:
case Builtins::kNumberIsInteger:
case Builtins::kNumberIsNaN:
case Builtins::kNumberIsSafeInteger:
case Builtins::kNumberParseFloat:
case Builtins::kNumberParseInt:
case Builtins::kNumberPrototypeToExponential:
case Builtins::kNumberPrototypeToFixed:
case Builtins::kNumberPrototypeToPrecision:
case Builtins::kNumberPrototypeToString:
case Builtins::kNumberPrototypeToLocaleString:
case Builtins::kNumberPrototypeValueOf:
// BigInt builtins.
case Builtins::kBigIntConstructor:
case Builtins::kBigIntAsIntN:
case Builtins::kBigIntAsUintN:
case Builtins::kBigIntPrototypeToString:
case Builtins::kBigIntPrototypeValueOf:
// Set builtins.
case Builtins::kSetConstructor:
case Builtins::kSetPrototypeEntries:
case Builtins::kSetPrototypeForEach:
case Builtins::kSetPrototypeGetSize:
case Builtins::kSetPrototypeHas:
case Builtins::kSetPrototypeValues:
// WeakSet builtins.
case Builtins::kWeakSetConstructor:
case Builtins::kWeakSetPrototypeHas:
// String builtins. Strings are immutable.
case Builtins::kStringFromCharCode:
case Builtins::kStringFromCodePoint:
case Builtins::kStringConstructor:
case Builtins::kStringPrototypeAnchor:
case Builtins::kStringPrototypeBig:
case Builtins::kStringPrototypeBlink:
case Builtins::kStringPrototypeBold:
case Builtins::kStringPrototypeCharAt:
case Builtins::kStringPrototypeCharCodeAt:
case Builtins::kStringPrototypeCodePointAt:
case Builtins::kStringPrototypeConcat:
case Builtins::kStringPrototypeEndsWith:
case Builtins::kStringPrototypeFixed:
case Builtins::kStringPrototypeFontcolor:
case Builtins::kStringPrototypeFontsize:
case Builtins::kStringPrototypeIncludes:
case Builtins::kStringPrototypeIndexOf:
case Builtins::kStringPrototypeItalics:
case Builtins::kStringPrototypeLastIndexOf:
case Builtins::kStringPrototypeLink:
case Builtins::kStringPrototypeMatchAll:
case Builtins::kStringPrototypePadEnd:
case Builtins::kStringPrototypePadStart:
case Builtins::kStringPrototypeRepeat:
case Builtins::kStringPrototypeSlice:
case Builtins::kStringPrototypeSmall:
case Builtins::kStringPrototypeStartsWith:
case Builtins::kStringPrototypeStrike:
case Builtins::kStringPrototypeSub:
case Builtins::kStringPrototypeSubstr:
case Builtins::kStringPrototypeSubstring:
case Builtins::kStringPrototypeSup:
case Builtins::kStringPrototypeToString:
#ifndef V8_INTL_SUPPORT
case Builtins::kStringPrototypeToLowerCase:
case Builtins::kStringPrototypeToUpperCase:
#endif
case Builtins::kStringPrototypeTrim:
case Builtins::kStringPrototypeTrimEnd:
case Builtins::kStringPrototypeTrimStart:
case Builtins::kStringPrototypeValueOf:
case Builtins::kStringToNumber:
case Builtins::kStringSubstring:
// Symbol builtins.
case Builtins::kSymbolConstructor:
case Builtins::kSymbolKeyFor:
case Builtins::kSymbolPrototypeToString:
case Builtins::kSymbolPrototypeValueOf:
case Builtins::kSymbolPrototypeToPrimitive:
// JSON builtins.
case Builtins::kJsonParse:
case Builtins::kJsonStringify:
// Global function builtins.
case Builtins::kGlobalDecodeURI:
case Builtins::kGlobalDecodeURIComponent:
case Builtins::kGlobalEncodeURI:
case Builtins::kGlobalEncodeURIComponent:
case Builtins::kGlobalEscape:
case Builtins::kGlobalUnescape:
case Builtins::kGlobalIsFinite:
case Builtins::kGlobalIsNaN:
// Function builtins.
case Builtins::kFunctionPrototypeToString:
case Builtins::kFunctionPrototypeBind:
case Builtins::kFastFunctionPrototypeBind:
case Builtins::kFunctionPrototypeCall:
case Builtins::kFunctionPrototypeApply:
// Error builtins.
case Builtins::kErrorConstructor:
// RegExp builtins.
case Builtins::kRegExpConstructor:
// Internal.
case Builtins::kStrictPoisonPillThrower:
case Builtins::kAllocateInYoungGeneration:
case Builtins::kAllocateInOldGeneration:
case Builtins::kAllocateRegularInYoungGeneration:
case Builtins::kAllocateRegularInOldGeneration:
return DebugInfo::kHasNoSideEffect;
// Set builtins.
case Builtins::kSetIteratorPrototypeNext:
case Builtins::kSetPrototypeAdd:
case Builtins::kSetPrototypeClear:
case Builtins::kSetPrototypeDelete:
// Array builtins.
case Builtins::kArrayIteratorPrototypeNext:
case Builtins::kArrayPrototypePop:
case Builtins::kArrayPrototypePush:
case Builtins::kArrayPrototypeReverse:
case Builtins::kArrayPrototypeShift:
case Builtins::kArrayPrototypeUnshift:
case Builtins::kArrayPrototypeSort:
case Builtins::kArrayPrototypeSplice:
case Builtins::kArrayUnshift:
// Map builtins.
case Builtins::kMapIteratorPrototypeNext:
case Builtins::kMapPrototypeClear:
case Builtins::kMapPrototypeDelete:
case Builtins::kMapPrototypeSet:
// RegExp builtins.
case Builtins::kRegExpPrototypeTest:
case Builtins::kRegExpPrototypeExec:
case Builtins::kRegExpPrototypeSplit:
case Builtins::kRegExpPrototypeFlagsGetter:
case Builtins::kRegExpPrototypeGlobalGetter:
case Builtins::kRegExpPrototypeIgnoreCaseGetter:
case Builtins::kRegExpPrototypeMatchAll:
case Builtins::kRegExpPrototypeMultilineGetter:
case Builtins::kRegExpPrototypeDotAllGetter:
case Builtins::kRegExpPrototypeUnicodeGetter:
case Builtins::kRegExpPrototypeStickyGetter:
return DebugInfo::kRequiresRuntimeChecks;
default:
if (FLAG_trace_side_effect_free_debug_evaluate) {
PrintF("[debug-evaluate] built-in %s may cause side effect.\n",
Builtins::name(id));
}
return DebugInfo::kHasSideEffects;
}
}
bool BytecodeRequiresRuntimeCheck(interpreter::Bytecode bytecode) {
using interpreter::Bytecode;
switch (bytecode) {
case Bytecode::kStaNamedProperty:
case Bytecode::kStaNamedPropertyNoFeedback:
case Bytecode::kStaNamedOwnProperty:
case Bytecode::kStaKeyedProperty:
case Bytecode::kStaInArrayLiteral:
case Bytecode::kStaDataPropertyInLiteral:
case Bytecode::kStaCurrentContextSlot:
return true;
default:
return false;
}
}
} // anonymous namespace
// static
DebugInfo::SideEffectState DebugEvaluate::FunctionGetSideEffectState(
Isolate* isolate, Handle<SharedFunctionInfo> info) {
if (FLAG_trace_side_effect_free_debug_evaluate) {
PrintF("[debug-evaluate] Checking function %s for side effect.\n",
info->DebugName().ToCString().get());
}
DCHECK(info->is_compiled());
DCHECK(!info->needs_script_context());
if (info->HasBytecodeArray()) {
// Check bytecodes against allowlist.
Handle<BytecodeArray> bytecode_array(info->GetBytecodeArray(), isolate);
if (FLAG_trace_side_effect_free_debug_evaluate) {
bytecode_array->Print();
}
bool requires_runtime_checks = false;
for (interpreter::BytecodeArrayIterator it(bytecode_array); !it.done();
it.Advance()) {
interpreter::Bytecode bytecode = it.current_bytecode();
if (interpreter::Bytecodes::IsCallRuntime(bytecode)) {
Runtime::FunctionId id =
(bytecode == interpreter::Bytecode::kInvokeIntrinsic)
? it.GetIntrinsicIdOperand(0)
: it.GetRuntimeIdOperand(0);
if (IntrinsicHasNoSideEffect(id)) continue;
return DebugInfo::kHasSideEffects;
}
if (BytecodeHasNoSideEffect(bytecode)) continue;
if (BytecodeRequiresRuntimeCheck(bytecode)) {
requires_runtime_checks = true;
continue;
}
if (FLAG_trace_side_effect_free_debug_evaluate) {
PrintF("[debug-evaluate] bytecode %s may cause side effect.\n",
interpreter::Bytecodes::ToString(bytecode));
}
// Did not match allowlist.
return DebugInfo::kHasSideEffects;
}
return requires_runtime_checks ? DebugInfo::kRequiresRuntimeChecks
: DebugInfo::kHasNoSideEffect;
} else if (info->IsApiFunction()) {
if (info->GetCode().is_builtin()) {
return info->GetCode().builtin_index() == Builtins::kHandleApiCall
? DebugInfo::kHasNoSideEffect
: DebugInfo::kHasSideEffects;
}
} else {
// Check built-ins against allowlist.
int builtin_index =
info->HasBuiltinId() ? info->builtin_id() : Builtins::kNoBuiltinId;
if (!Builtins::IsBuiltinId(builtin_index))
return DebugInfo::kHasSideEffects;
DebugInfo::SideEffectState state =
BuiltinGetSideEffectState(static_cast<Builtins::Name>(builtin_index));
return state;
}
return DebugInfo::kHasSideEffects;
}
#ifdef DEBUG
static bool TransitivelyCalledBuiltinHasNoSideEffect(Builtins::Name caller,
Builtins::Name callee) {
switch (callee) {
// Transitively called Builtins:
case Builtins::kAbort:
case Builtins::kAbortCSAAssert:
case Builtins::kAdaptorWithBuiltinExitFrame:
case Builtins::kArrayConstructorImpl:
case Builtins::kArrayEveryLoopContinuation:
case Builtins::kArrayFilterLoopContinuation:
case Builtins::kArrayFindIndexLoopContinuation:
case Builtins::kArrayFindLoopContinuation:
case Builtins::kArrayForEachLoopContinuation:
case Builtins::kArrayIncludesHoleyDoubles:
case Builtins::kArrayIncludesPackedDoubles:
case Builtins::kArrayIncludesSmiOrObject:
case Builtins::kArrayIndexOfHoleyDoubles:
case Builtins::kArrayIndexOfPackedDoubles:
case Builtins::kArrayIndexOfSmiOrObject:
case Builtins::kArrayMapLoopContinuation:
case Builtins::kArrayReduceLoopContinuation:
case Builtins::kArrayReduceRightLoopContinuation:
case Builtins::kArraySomeLoopContinuation:
case Builtins::kArrayTimSort:
case Builtins::kCall_ReceiverIsAny:
case Builtins::kCall_ReceiverIsNotNullOrUndefined:
case Builtins::kCall_ReceiverIsNullOrUndefined:
case Builtins::kCallWithArrayLike:
case Builtins::kCEntry_Return1_DontSaveFPRegs_ArgvOnStack_NoBuiltinExit:
case Builtins::kCEntry_Return1_DontSaveFPRegs_ArgvOnStack_BuiltinExit:
case Builtins::kCEntry_Return1_DontSaveFPRegs_ArgvInRegister_NoBuiltinExit:
case Builtins::kCEntry_Return1_SaveFPRegs_ArgvOnStack_NoBuiltinExit:
case Builtins::kCEntry_Return1_SaveFPRegs_ArgvOnStack_BuiltinExit:
case Builtins::kCEntry_Return2_DontSaveFPRegs_ArgvOnStack_NoBuiltinExit:
case Builtins::kCEntry_Return2_DontSaveFPRegs_ArgvOnStack_BuiltinExit:
case Builtins::kCEntry_Return2_DontSaveFPRegs_ArgvInRegister_NoBuiltinExit:
case Builtins::kCEntry_Return2_SaveFPRegs_ArgvOnStack_NoBuiltinExit:
case Builtins::kCEntry_Return2_SaveFPRegs_ArgvOnStack_BuiltinExit:
case Builtins::kCloneFastJSArray:
case Builtins::kConstruct:
case Builtins::kConvertToLocaleString:
case Builtins::kCreateTypedArray:
case Builtins::kDirectCEntry:
case Builtins::kDoubleToI:
case Builtins::kExtractFastJSArray:
case Builtins::kFastNewObject:
case Builtins::kFindOrderedHashMapEntry:
case Builtins::kFlatMapIntoArray:
case Builtins::kFlattenIntoArray:
case Builtins::kGetProperty:
case Builtins::kHasProperty:
case Builtins::kCreateHTML:
case Builtins::kNonNumberToNumber:
case Builtins::kNonPrimitiveToPrimitive_Number:
case Builtins::kNumberToString:
case Builtins::kObjectToString:
case Builtins::kOrderedHashTableHealIndex:
case Builtins::kOrdinaryToPrimitive_Number:
case Builtins::kOrdinaryToPrimitive_String:
case Builtins::kParseInt:
case Builtins::kProxyHasProperty:
case Builtins::kProxyIsExtensible:
case Builtins::kProxyGetPrototypeOf:
case Builtins::kRecordWrite:
case Builtins::kStringAdd_CheckNone:
case Builtins::kStringEqual:
case Builtins::kStringIndexOf:
case Builtins::kStringRepeat:
case Builtins::kToInteger:
case Builtins::kToLength:
case Builtins::kToName:
case Builtins::kToObject:
case Builtins::kToString:
case Builtins::kWeakMapLookupHashIndex:
return true;
case Builtins::kJoinStackPop:
case Builtins::kJoinStackPush:
switch (caller) {
case Builtins::kArrayPrototypeJoin:
case Builtins::kArrayPrototypeToLocaleString:
case Builtins::kTypedArrayPrototypeJoin:
case Builtins::kTypedArrayPrototypeToLocaleString:
return true;
default:
return false;
}
case Builtins::kFastCreateDataProperty:
switch (caller) {
case Builtins::kArrayPrototypeSlice:
case Builtins::kArrayFilter:
return true;
default:
return false;
}
case Builtins::kSetProperty:
switch (caller) {
case Builtins::kArrayPrototypeSlice:
case Builtins::kTypedArrayPrototypeMap:
case Builtins::kStringPrototypeMatchAll:
return true;
default:
return false;
}
default:
return false;
}
}
// static
void DebugEvaluate::VerifyTransitiveBuiltins(Isolate* isolate) {
// TODO(yangguo): also check runtime calls.
bool failed = false;
bool sanity_check = false;
for (int i = 0; i < Builtins::builtin_count; i++) {
Builtins::Name caller = static_cast<Builtins::Name>(i);
DebugInfo::SideEffectState state = BuiltinGetSideEffectState(caller);
if (state != DebugInfo::kHasNoSideEffect) continue;
Code code = isolate->builtins()->builtin(caller);
int mode = RelocInfo::ModeMask(RelocInfo::CODE_TARGET) |
RelocInfo::ModeMask(RelocInfo::RELATIVE_CODE_TARGET);
for (RelocIterator it(code, mode); !it.done(); it.next()) {
RelocInfo* rinfo = it.rinfo();
DCHECK(RelocInfo::IsCodeTargetMode(rinfo->rmode()));
Code callee_code = isolate->heap()->GcSafeFindCodeForInnerPointer(
rinfo->target_address());
if (!callee_code.is_builtin()) continue;
Builtins::Name callee =
static_cast<Builtins::Name>(callee_code.builtin_index());
if (BuiltinGetSideEffectState(callee) == DebugInfo::kHasNoSideEffect) {
continue;
}
if (TransitivelyCalledBuiltinHasNoSideEffect(caller, callee)) {
sanity_check = true;
continue;
}
PrintF("Allowlisted builtin %s calls non-allowlisted builtin %s\n",
Builtins::name(caller), Builtins::name(callee));
failed = true;
}
}
CHECK(!failed);
#if defined(V8_TARGET_ARCH_PPC) || defined(V8_TARGET_ARCH_PPC64) || \
defined(V8_TARGET_ARCH_MIPS64)
// Isolate-independent builtin calls and jumps do not emit reloc infos
// on PPC. We try to avoid using PC relative code due to performance
// issue with especially older hardwares.
// MIPS64 doesn't have PC relative code currently.
// TODO(mips): Add PC relative code to MIPS64.
USE(sanity_check);
#else
CHECK(sanity_check);
#endif
}
#endif // DEBUG
// static
void DebugEvaluate::ApplySideEffectChecks(
Handle<BytecodeArray> bytecode_array) {
for (interpreter::BytecodeArrayIterator it(bytecode_array); !it.done();
it.Advance()) {
interpreter::Bytecode bytecode = it.current_bytecode();
if (BytecodeRequiresRuntimeCheck(bytecode)) it.ApplyDebugBreak();
}
}
} // namespace internal
} // namespace v8