// 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/accessors.h"
|
#include "src/assembler-inl.h"
|
#include "src/compiler.h"
|
#include "src/contexts.h"
|
#include "src/debug/debug-frames.h"
|
#include "src/debug/debug-scopes.h"
|
#include "src/debug/debug.h"
|
#include "src/frames-inl.h"
|
#include "src/globals.h"
|
#include "src/interpreter/bytecode-array-iterator.h"
|
#include "src/interpreter/bytecodes.h"
|
#include "src/isolate-inl.h"
|
#include "src/objects/api-callbacks.h"
|
#include "src/snapshot/snapshot.h"
|
|
namespace v8 {
|
namespace internal {
|
|
MaybeHandle<Object> DebugEvaluate::Global(Isolate* isolate,
|
Handle<String> source,
|
bool throw_on_side_effect) {
|
// Disable breaks in side-effect free mode.
|
DisableBreak disable_break_scope(isolate->debug(), throw_on_side_effect);
|
|
Handle<Context> context = isolate->native_context();
|
ScriptOriginOptions origin_options(false, true);
|
MaybeHandle<SharedFunctionInfo> maybe_function_info =
|
Compiler::GetSharedFunctionInfoForScript(
|
isolate, source,
|
Compiler::ScriptDetails(isolate->factory()->empty_string()),
|
origin_options, nullptr, nullptr, ScriptCompiler::kNoCompileOptions,
|
ScriptCompiler::kNoCacheNoReason, NOT_NATIVES_CODE);
|
|
Handle<SharedFunctionInfo> shared_info;
|
if (!maybe_function_info.ToHandle(&shared_info)) return MaybeHandle<Object>();
|
|
Handle<JSFunction> fun =
|
isolate->factory()->NewFunctionFromSharedFunctionInfo(shared_info,
|
context);
|
if (throw_on_side_effect) isolate->debug()->StartSideEffectCheckMode();
|
MaybeHandle<Object> result = Execution::Call(
|
isolate, fun, Handle<JSObject>(context->global_proxy(), isolate), 0,
|
nullptr);
|
if (throw_on_side_effect) isolate->debug()->StopSideEffectCheckMode();
|
return result;
|
}
|
|
MaybeHandle<Object> DebugEvaluate::Local(Isolate* isolate,
|
StackFrame::Id 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;
|
}
|
|
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<String> this_str = factory->this_string();
|
JSObject::SetOwnPropertyIgnoreAttributes(
|
materialized, this_str, Handle<Object>(it.frame()->receiver(), isolate),
|
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 sucess = false;
|
if (throw_on_side_effect) isolate->debug()->StartSideEffectCheckMode();
|
sucess = Execution::Call(isolate, eval_fun, receiver, 0, nullptr)
|
.ToHandle(&result);
|
if (throw_on_side_effect) isolate->debug()->StopSideEffectCheckMode();
|
if (!sucess) {
|
DCHECK(isolate->has_pending_exception());
|
return MaybeHandle<Object>();
|
}
|
|
// Skip the global proxy as it has no properties and always delegates to the
|
// real global object.
|
if (result->IsJSGlobalProxy()) {
|
PrototypeIterator iter(isolate, Handle<JSGlobalProxy>::cast(result));
|
// TODO(verwaest): This will crash when the global proxy is detached.
|
result = PrototypeIterator::GetCurrent<JSObject>(iter);
|
}
|
|
return result;
|
}
|
|
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::COLLECT_NON_LOCALS) {
|
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 use the original context chain from the function context to the
|
// native context.
|
// - Between the function scope and the native context, we only resolve
|
// variable names that the current function already uses. Only for these
|
// names we can be sure that they will be correctly resolved. For the
|
// rest, we only resolve to with, script, and native contexts. We use a
|
// whitelist to implement that.
|
// Context::Lookup has special handling for debug-evaluate contexts:
|
// - Look up in the materialized stack variables.
|
// - Look up in the original context.
|
// - Check the whitelist to find out whether to skip contexts during lookup.
|
for (; scope_iterator_.InInnerScope(); scope_iterator_.Next()) {
|
ScopeIterator::ScopeType scope_type = scope_iterator_.Type();
|
if (scope_type == ScopeIterator::ScopeTypeScript) break;
|
ContextChainElement context_chain_element;
|
if (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_type == ScopeIterator::ScopeTypeLocal) {
|
context_chain_element.whitelist = scope_iterator_.GetNonLocals();
|
}
|
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.whitelist);
|
}
|
}
|
|
|
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 both inlined and non-inlined version of an intrinsic.
|
#define INTRINSIC_WHITELIST(V) \
|
/* Conversions */ \
|
V(NumberToString) \
|
V(ToBigInt) \
|
V(ToInteger) \
|
V(ToLength) \
|
V(ToNumber) \
|
V(ToObject) \
|
V(ToString) \
|
/* Type checks */ \
|
V(IsArray) \
|
V(IsDate) \
|
V(IsFunction) \
|
V(IsJSProxy) \
|
V(IsJSReceiver) \
|
V(IsRegExp) \
|
V(IsSmi) \
|
V(IsTypedArray) \
|
/* Loads */ \
|
V(LoadLookupSlotForCall) \
|
V(GetProperty) \
|
/* Arrays */ \
|
V(ArraySpeciesConstructor) \
|
V(EstimateNumberOfElements) \
|
V(GetArrayKeys) \
|
V(HasComplexElements) \
|
V(HasFastPackedElements) \
|
V(NewArray) \
|
V(NormalizeElements) \
|
V(PrepareElementsForSort) \
|
V(TrySliceSimpleNonFastElements) \
|
V(TypedArrayGetBuffer) \
|
/* Errors */ \
|
V(NewTypeError) \
|
V(ReThrow) \
|
V(ThrowCalledNonCallable) \
|
V(ThrowInvalidStringLength) \
|
V(ThrowIteratorResultNotAnObject) \
|
V(ThrowReferenceError) \
|
V(ThrowSymbolIteratorInvalid) \
|
/* Strings */ \
|
V(RegExpInternalReplace) \
|
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(AllocateInNewSpace) \
|
V(AllocateInTargetSpace) \
|
V(AllocateSeqOneByteString) \
|
V(AllocateSeqTwoByteString) \
|
V(ArrayIncludes_Slow) \
|
V(ArrayIndexOf) \
|
V(ArrayIsArray) \
|
V(ClassOf) \
|
V(GenerateRandomNumbers) \
|
V(GetFunctionName) \
|
V(GetOwnPropertyDescriptor) \
|
V(GlobalPrint) \
|
V(HasProperty) \
|
V(ObjectCreate) \
|
V(ObjectEntries) \
|
V(ObjectEntriesSkipFastPath) \
|
V(ObjectHasOwnProperty) \
|
V(ObjectValues) \
|
V(ObjectValuesSkipFastPath) \
|
V(ObjectGetOwnPropertyNames) \
|
V(ObjectGetOwnPropertyNamesTryFast) \
|
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(MaxSmi) \
|
V(NewObject) \
|
V(SmiLexicographicCompare) \
|
V(StringMaxLength) \
|
V(StringToArray) \
|
/* Test */ \
|
V(GetOptimizationStatus) \
|
V(OptimizeFunctionOnNextCall) \
|
V(OptimizeOsr) \
|
V(UnblockConcurrentRecompilation)
|
|
#define CASE(Name) \
|
case Runtime::k##Name: \
|
case Runtime::kInline##Name:
|
|
switch (id) {
|
INTRINSIC_WHITELIST(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 INTRINSIC_WHITELIST
|
}
|
|
#ifdef DEBUG
|
bool BuiltinToIntrinsicHasNoSideEffect(Builtins::Name builtin_id,
|
Runtime::FunctionId intrinsic_id) {
|
// First check the intrinsic whitelist.
|
if (IntrinsicHasNoSideEffect(intrinsic_id)) return true;
|
|
// Whitelist intrinsics called from specific builtins.
|
#define BUILTIN_INTRINSIC_WHITELIST(V, W) \
|
/* Arrays */ \
|
V(Builtins::kArrayFilter, W(CreateDataProperty)) \
|
V(Builtins::kArrayMap, W(CreateDataProperty)) \
|
V(Builtins::kArrayPrototypeSlice, W(CreateDataProperty) W(SetProperty)) \
|
/* TypedArrays */ \
|
V(Builtins::kTypedArrayConstructor, \
|
W(TypedArrayCopyElements) W(ThrowInvalidTypedArrayAlignment)) \
|
V(Builtins::kTypedArrayPrototypeFilter, W(TypedArrayCopyElements)) \
|
V(Builtins::kTypedArrayPrototypeMap, W(SetProperty))
|
|
#define CASE(Builtin, ...) \
|
case Builtin: \
|
return (__VA_ARGS__ false);
|
|
#define MATCH(Intrinsic) \
|
intrinsic_id == Runtime::k##Intrinsic || \
|
intrinsic_id == Runtime::kInline##Intrinsic ||
|
|
switch (builtin_id) {
|
BUILTIN_INTRINSIC_WHITELIST(CASE, MATCH)
|
default:
|
return false;
|
}
|
|
#undef MATCH
|
#undef CASE
|
#undef BUILTIN_INTRINSIC_WHITELIST
|
}
|
#endif // DEBUG
|
|
bool BytecodeHasNoSideEffect(interpreter::Bytecode bytecode) {
|
typedef interpreter::Bytecode Bytecode;
|
typedef interpreter::Bytecodes 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) {
|
// Whitelist for bytecodes.
|
// Loads.
|
case Bytecode::kLdaLookupSlot:
|
case Bytecode::kLdaGlobal:
|
case Bytecode::kLdaNamedProperty:
|
case Bytecode::kLdaKeyedProperty:
|
case Bytecode::kLdaGlobalInsideTypeof:
|
case Bytecode::kLdaLookupSlotInsideTypeof:
|
// 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::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::kForInEnumerate:
|
case Bytecode::kForInPrepare:
|
case Bytecode::kForInContinue:
|
case Bytecode::kForInNext:
|
case Bytecode::kForInStep:
|
case Bytecode::kThrow:
|
case Bytecode::kReThrow:
|
case Bytecode::kThrowReferenceErrorIfHole:
|
case Bytecode::kThrowSuperNotCalledIfHole:
|
case Bytecode::kThrowSuperAlreadyCalledIfNotHole:
|
case Bytecode::kIllegal:
|
case Bytecode::kCallJSRuntime:
|
case Bytecode::kStackCheck:
|
case Bytecode::kReturn:
|
case Bytecode::kSetPendingMessage:
|
return true;
|
default:
|
return false;
|
}
|
}
|
|
DebugInfo::SideEffectState BuiltinGetSideEffectState(Builtins::Name id) {
|
switch (id) {
|
// Whitelist 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::kObjectPrototypeValueOf:
|
case Builtins::kObjectValues:
|
case Builtins::kObjectPrototypeHasOwnProperty:
|
case Builtins::kObjectPrototypeIsPrototypeOf:
|
case Builtins::kObjectPrototypePropertyIsEnumerable:
|
case Builtins::kObjectPrototypeToString:
|
// 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::kArrayPrototypeKeys:
|
case Builtins::kArrayPrototypeSlice:
|
case Builtins::kArrayPrototypeSort:
|
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::kTypedArrayPrototypeIndexOf:
|
case Builtins::kTypedArrayPrototypeLastIndexOf:
|
case Builtins::kTypedArrayPrototypeSlice:
|
case Builtins::kTypedArrayPrototypeSubArray:
|
case Builtins::kTypedArrayPrototypeEvery:
|
case Builtins::kTypedArrayPrototypeSome:
|
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:
|
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::kWeakMapHas:
|
// 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::kMathRandom:
|
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::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::kWeakSetHas:
|
// 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::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:
|
case Builtins::kMakeError:
|
case Builtins::kMakeTypeError:
|
case Builtins::kMakeSyntaxError:
|
case Builtins::kMakeRangeError:
|
case Builtins::kMakeURIError:
|
// RegExp builtins.
|
case Builtins::kRegExpConstructor:
|
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::kArraySplice:
|
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::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) {
|
typedef interpreter::Bytecode Bytecode;
|
switch (bytecode) {
|
case Bytecode::kStaNamedProperty:
|
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());
|
if (info->HasBytecodeArray()) {
|
// Check bytecodes against whitelist.
|
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 whitelist.
|
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 whitelist.
|
int builtin_index =
|
info->HasBuiltinId() ? info->builtin_id() : Builtins::kNoBuiltinId;
|
DCHECK_NE(Builtins::kDeserializeLazy, builtin_index);
|
if (!Builtins::IsBuiltinId(builtin_index))
|
return DebugInfo::kHasSideEffects;
|
DebugInfo::SideEffectState state =
|
BuiltinGetSideEffectState(static_cast<Builtins::Name>(builtin_index));
|
#ifdef DEBUG
|
if (state == DebugInfo::kHasNoSideEffect) {
|
Code* code = isolate->builtins()->builtin(builtin_index);
|
if (code->builtin_index() == Builtins::kDeserializeLazy) {
|
// Target builtin is not yet deserialized. Deserialize it now.
|
|
DCHECK(Builtins::IsLazy(builtin_index));
|
DCHECK_EQ(Builtins::TFJ, Builtins::KindOf(builtin_index));
|
|
code = Snapshot::DeserializeBuiltin(isolate, builtin_index);
|
DCHECK_NE(Builtins::kDeserializeLazy, code->builtin_index());
|
}
|
// TODO(yangguo): Check builtin-to-builtin calls too.
|
int mode = RelocInfo::ModeMask(RelocInfo::EXTERNAL_REFERENCE);
|
bool failed = false;
|
for (RelocIterator it(code, mode); !it.done(); it.next()) {
|
RelocInfo* rinfo = it.rinfo();
|
Address address = rinfo->target_external_reference();
|
const Runtime::Function* function = Runtime::FunctionForEntry(address);
|
if (function == nullptr) continue;
|
if (!BuiltinToIntrinsicHasNoSideEffect(
|
static_cast<Builtins::Name>(builtin_index),
|
function->function_id)) {
|
PrintF("Whitelisted builtin %s calls non-whitelisted intrinsic %s\n",
|
Builtins::name(builtin_index), function->name);
|
failed = true;
|
}
|
DCHECK(!failed);
|
}
|
}
|
#endif // DEBUG
|
return state;
|
}
|
|
return DebugInfo::kHasSideEffects;
|
}
|
|
// static
|
bool DebugEvaluate::CallbackHasNoSideEffect(Object* callback_info) {
|
DisallowHeapAllocation no_gc;
|
if (callback_info->IsAccessorInfo()) {
|
// List of whitelisted internal accessors can be found in accessors.h.
|
AccessorInfo* info = AccessorInfo::cast(callback_info);
|
if (info->has_no_side_effect()) return true;
|
if (FLAG_trace_side_effect_free_debug_evaluate) {
|
PrintF("[debug-evaluate] API Callback '");
|
info->name()->ShortPrint();
|
PrintF("' may cause side effect.\n");
|
}
|
} else if (callback_info->IsInterceptorInfo()) {
|
InterceptorInfo* info = InterceptorInfo::cast(callback_info);
|
if (info->has_no_side_effect()) return true;
|
if (FLAG_trace_side_effect_free_debug_evaluate) {
|
PrintF("[debug-evaluate] API Interceptor may cause side effect.\n");
|
}
|
} else if (callback_info->IsCallHandlerInfo()) {
|
CallHandlerInfo* info = CallHandlerInfo::cast(callback_info);
|
if (info->IsSideEffectFreeCallHandlerInfo()) return true;
|
if (FLAG_trace_side_effect_free_debug_evaluate) {
|
PrintF("[debug-evaluate] API CallHandlerInfo may cause side effect.\n");
|
}
|
}
|
return false;
|
}
|
|
// 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
|