blob: e5865e639c3028a7d9693821e8163c6225c79d38 [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/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/snapshot/snapshot.h"
namespace v8 {
namespace internal {
static inline bool IsDebugContext(Isolate* isolate, Context* context) {
return context->native_context() == *isolate->debug()->debug_context();
}
MaybeHandle<Object> DebugEvaluate::Global(Isolate* isolate,
Handle<String> source) {
// Handle the processing of break.
DisableBreak disable_break_scope(isolate->debug());
// Enter the top context from before the debugger was invoked.
SaveContext save(isolate);
SaveContext* top = &save;
while (top != nullptr && IsDebugContext(isolate, *top->context())) {
top = top->prev();
}
if (top != nullptr) isolate->set_context(*top->context());
// Get the native context now set to the top context from before the
// debugger was invoked.
Handle<Context> context = isolate->native_context();
Handle<JSObject> receiver(context->global_proxy());
Handle<SharedFunctionInfo> outer_info(context->closure()->shared(), isolate);
return Evaluate(isolate, outer_info, context, receiver, source, false);
}
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());
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;
}
// 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;
{
NoSideEffectScope no_side_effect(isolate, throw_on_side_effect);
ASSIGN_RETURN_ON_EXCEPTION(
isolate, result,
Execution::Call(isolate, eval_fun, receiver, 0, nullptr), 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;
}
DebugEvaluate::ContextBuilder::ContextBuilder(Isolate* isolate,
JavaScriptFrame* frame,
int inlined_jsframe_index)
: isolate_(isolate),
frame_(frame),
inlined_jsframe_index_(inlined_jsframe_index) {
FrameInspector frame_inspector(frame, inlined_jsframe_index, isolate);
Handle<JSFunction> local_function = frame_inspector.GetFunction();
Handle<Context> outer_context(local_function->context());
evaluation_context_ = outer_context;
outer_info_ = handle(local_function->shared());
Factory* factory = isolate->factory();
// 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.
const ScopeIterator::Option option = ScopeIterator::COLLECT_NON_LOCALS;
for (ScopeIterator it(isolate, &frame_inspector, option); !it.Done();
it.Next()) {
ScopeIterator::ScopeType scope_type = it.Type();
if (scope_type == ScopeIterator::ScopeTypeLocal) {
DCHECK_EQ(FUNCTION_SCOPE, it.CurrentScopeInfo()->scope_type());
Handle<JSObject> materialized = factory->NewJSObjectWithNullProto();
Handle<Context> local_context =
it.HasContext() ? it.CurrentContext() : outer_context;
Handle<StringSet> non_locals = it.GetNonLocals();
MaterializeReceiver(materialized, local_context, local_function,
non_locals);
MaterializeStackLocals(materialized, local_function, &frame_inspector);
ContextChainElement context_chain_element;
context_chain_element.scope_info = it.CurrentScopeInfo();
context_chain_element.materialized_object = materialized;
// Non-locals that are already being referenced by the current function
// are guaranteed to be correctly resolved.
context_chain_element.whitelist = non_locals;
if (it.HasContext()) {
context_chain_element.wrapped_context = it.CurrentContext();
}
context_chain_.push_back(context_chain_element);
evaluation_context_ = outer_context;
break;
} else if (scope_type == ScopeIterator::ScopeTypeCatch ||
scope_type == ScopeIterator::ScopeTypeWith ||
scope_type == ScopeIterator::ScopeTypeModule) {
ContextChainElement context_chain_element;
Handle<Context> current_context = it.CurrentContext();
if (!current_context->IsDebugEvaluateContext()) {
context_chain_element.wrapped_context = current_context;
}
context_chain_.push_back(context_chain_element);
} else if (scope_type == ScopeIterator::ScopeTypeBlock ||
scope_type == ScopeIterator::ScopeTypeEval) {
Handle<JSObject> materialized = factory->NewJSObjectWithNullProto();
frame_inspector.MaterializeStackLocals(materialized,
it.CurrentScopeInfo());
ContextChainElement context_chain_element;
context_chain_element.scope_info = it.CurrentScopeInfo();
context_chain_element.materialized_object = materialized;
if (it.HasContext()) {
context_chain_element.wrapped_context = it.CurrentContext();
}
context_chain_.push_back(context_chain_element);
} else {
break;
}
}
for (auto rit = context_chain_.rbegin(); rit != context_chain_.rend();
rit++) {
ContextChainElement element = *rit;
Handle<ScopeInfo> scope_info(ScopeInfo::CreateForWithScope(
isolate, evaluation_context_->IsNativeContext()
? Handle<ScopeInfo>::null()
: Handle<ScopeInfo>(evaluation_context_->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() {
for (ContextChainElement& element : context_chain_) {
if (!element.materialized_object.is_null()) {
// Write back potential changes to materialized stack locals to the stack.
FrameInspector(frame_, inlined_jsframe_index_, isolate_)
.UpdateStackLocalsFromMaterializedObject(element.materialized_object,
element.scope_info);
}
}
}
void DebugEvaluate::ContextBuilder::MaterializeReceiver(
Handle<JSObject> target, Handle<Context> local_context,
Handle<JSFunction> local_function, Handle<StringSet> non_locals) {
Handle<Object> recv = isolate_->factory()->undefined_value();
Handle<String> name = isolate_->factory()->this_string();
if (non_locals->Has(name)) {
// 'this' is allocated in an outer context and is is already being
// referenced by the current function, so it can be correctly resolved.
return;
} else if (local_function->shared()->scope_info()->HasReceiver() &&
!frame_->receiver()->IsTheHole(isolate_)) {
recv = handle(frame_->receiver(), isolate_);
}
JSObject::SetOwnPropertyIgnoreAttributes(target, name, recv, NONE).Check();
}
void DebugEvaluate::ContextBuilder::MaterializeStackLocals(
Handle<JSObject> target, Handle<JSFunction> function,
FrameInspector* frame_inspector) {
bool materialize_arguments_object = true;
// Do not materialize the arguments object for eval or top-level code.
if (function->shared()->is_toplevel()) materialize_arguments_object = false;
// First materialize stack locals (modulo arguments object).
Handle<SharedFunctionInfo> shared(function->shared());
Handle<ScopeInfo> scope_info(shared->scope_info());
frame_inspector->MaterializeStackLocals(target, scope_info,
materialize_arguments_object);
// Then materialize the arguments object.
if (materialize_arguments_object) {
// Skip if "arguments" is already taken and wasn't optimized out (which
// causes {MaterializeStackLocals} above to skip the local variable).
Handle<String> arguments_str = isolate_->factory()->arguments_string();
Maybe<bool> maybe = JSReceiver::HasOwnProperty(target, arguments_str);
DCHECK(maybe.IsJust());
if (maybe.FromJust()) return;
// FunctionGetArguments can't throw an exception.
Handle<JSObject> arguments =
Accessors::FunctionGetArguments(frame_, inlined_jsframe_index_);
JSObject::SetOwnPropertyIgnoreAttributes(target, arguments_str, arguments,
NONE)
.Check();
}
}
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(ToInteger) \
V(ToObject) \
V(ToString) \
V(ToLength) \
V(ToNumber) \
V(NumberToStringSkipCache) \
/* Type checks */ \
V(IsJSReceiver) \
V(IsSmi) \
V(IsArray) \
V(IsFunction) \
V(IsDate) \
V(IsJSProxy) \
V(IsJSMap) \
V(IsJSSet) \
V(IsJSWeakMap) \
V(IsJSWeakSet) \
V(IsRegExp) \
V(IsTypedArray) \
V(ClassOf) \
/* Loads */ \
V(LoadLookupSlotForCall) \
/* Arrays */ \
V(ArraySpeciesConstructor) \
V(NormalizeElements) \
V(GetArrayKeys) \
V(TrySliceSimpleNonFastElements) \
V(HasComplexElements) \
V(EstimateNumberOfElements) \
/* Errors */ \
V(ReThrow) \
V(ThrowReferenceError) \
V(ThrowSymbolIteratorInvalid) \
V(ThrowIteratorResultNotAnObject) \
V(NewTypeError) \
V(ThrowInvalidStringLength) \
/* Strings */ \
V(StringIndexOf) \
V(StringIncludes) \
V(StringReplaceOneCharWithString) \
V(StringToNumber) \
V(StringTrim) \
V(SubString) \
V(RegExpInternalReplace) \
/* BigInts */ \
V(BigIntEqualToBigInt) \
V(BigIntToBoolean) \
V(BigIntToNumber) \
/* Literals */ \
V(CreateArrayLiteral) \
V(CreateObjectLiteral) \
V(CreateRegExpLiteral) \
/* Collections */ \
V(GenericHash) \
/* Called from builtins */ \
V(StringAdd) \
V(StringParseFloat) \
V(StringParseInt) \
V(StringCharCodeAt) \
V(StringIndexOfUnchecked) \
V(StringEqual) \
V(RegExpInitializeAndCompile) \
V(SymbolDescriptiveString) \
V(GenerateRandomNumbers) \
V(GlobalPrint) \
V(AllocateInNewSpace) \
V(AllocateInTargetSpace) \
V(AllocateSeqOneByteString) \
V(AllocateSeqTwoByteString) \
V(ObjectCreate) \
V(ObjectHasOwnProperty) \
V(ArrayIndexOf) \
V(ArrayIncludes_Slow) \
V(ArrayIsArray) \
V(ThrowTypeError) \
V(ThrowCalledOnNullOrUndefined) \
V(ThrowIncompatibleMethodReceiver) \
V(ThrowInvalidHint) \
V(ThrowNotDateError) \
V(ThrowRangeError) \
V(ToName) \
V(GetOwnPropertyDescriptor) \
V(StackGuard) \
/* Misc. */ \
V(Call) \
V(MaxSmi) \
V(NewObject) \
V(CompleteInobjectSlackTrackingForMap) \
V(HasInPrototypeChain) \
V(StringMaxLength)
#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
}
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:
// 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::kTestEqualStrictNoFeedback:
case Bytecode::kTestUndetectable:
case Bytecode::kTestTypeOf:
case Bytecode::kTestUndefined:
case Bytecode::kTestNull:
// Conversions.
case Bytecode::kToObject:
case Bytecode::kToNumber:
case Bytecode::kToName:
// 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:
if (FLAG_trace_side_effect_free_debug_evaluate) {
PrintF("[debug-evaluate] bytecode %s may cause side effect.\n",
Bytecodes::ToString(bytecode));
}
return false;
}
}
bool BuiltinHasNoSideEffect(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::kArrayConstructor:
case Builtins::kArrayIndexOf:
case Builtins::kArrayPrototypeValues:
case Builtins::kArrayIncludes:
case Builtins::kArrayPrototypeEntries:
case Builtins::kArrayPrototypeFind:
case Builtins::kArrayPrototypeFindIndex:
case Builtins::kArrayPrototypeKeys:
case Builtins::kArrayForEach:
case Builtins::kArrayEvery:
case Builtins::kArraySome:
case Builtins::kArrayReduce:
case Builtins::kArrayReduceRight:
// 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::kMapPrototypeGet:
case Builtins::kMapPrototypeEntries:
case Builtins::kMapPrototypeGetSize:
case Builtins::kMapPrototypeKeys:
case Builtins::kMapPrototypeValues:
// 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:
// Set builtins.
case Builtins::kSetConstructor:
case Builtins::kSetPrototypeEntries:
case Builtins::kSetPrototypeGetSize:
case Builtins::kSetPrototypeValues:
// 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::kStringPrototypeTrimLeft:
case Builtins::kStringPrototypeTrimRight:
case Builtins::kStringPrototypeValueOf:
case Builtins::kStringToNumber:
case Builtins::kSubString:
// 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:
// Error builtins.
case Builtins::kMakeError:
case Builtins::kMakeTypeError:
case Builtins::kMakeSyntaxError:
case Builtins::kMakeRangeError:
case Builtins::kMakeURIError:
return true;
default:
if (FLAG_trace_side_effect_free_debug_evaluate) {
PrintF("[debug-evaluate] built-in %s may cause side effect.\n",
Builtins::name(id));
}
return false;
}
}
static const Address accessors_with_no_side_effect[] = {
// Whitelist for accessors.
FUNCTION_ADDR(Accessors::StringLengthGetter),
FUNCTION_ADDR(Accessors::ArrayLengthGetter),
FUNCTION_ADDR(Accessors::FunctionLengthGetter),
FUNCTION_ADDR(Accessors::FunctionNameGetter),
FUNCTION_ADDR(Accessors::BoundFunctionLengthGetter),
FUNCTION_ADDR(Accessors::BoundFunctionNameGetter),
};
} // anonymous namespace
// static
bool DebugEvaluate::FunctionHasNoSideEffect(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->bytecode_array());
if (FLAG_trace_side_effect_free_debug_evaluate) bytecode_array->Print();
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 false;
}
if (BytecodeHasNoSideEffect(bytecode)) continue;
// Did not match whitelist.
return false;
}
return true;
} else {
// Check built-ins against whitelist.
int builtin_index = info->HasLazyDeserializationBuiltinId()
? info->lazy_deserialization_builtin_id()
: info->code()->builtin_index();
DCHECK_NE(Builtins::kDeserializeLazy, builtin_index);
if (Builtins::IsBuiltinId(builtin_index) &&
BuiltinHasNoSideEffect(static_cast<Builtins::Name>(builtin_index))) {
#ifdef DEBUG
Isolate* isolate = info->GetIsolate();
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));
if (FLAG_trace_lazy_deserialization) {
PrintF("Lazy-deserializing builtin %s\n",
Builtins::name(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 (!IntrinsicHasNoSideEffect(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 true;
}
}
return false;
}
// static
bool DebugEvaluate::CallbackHasNoSideEffect(Address function_addr) {
for (size_t i = 0; i < arraysize(accessors_with_no_side_effect); i++) {
if (function_addr == accessors_with_no_side_effect[i]) return true;
}
if (FLAG_trace_side_effect_free_debug_evaluate) {
PrintF("[debug-evaluate] API Callback at %p may cause side effect.\n",
reinterpret_cast<void*>(function_addr));
}
return false;
}
} // namespace internal
} // namespace v8