// 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/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" namespace v8 { namespace internal { static inline bool IsDebugContext(Isolate* isolate, Context* context) { return context->native_context() == *isolate->debug()->debug_context(); } MaybeHandle DebugEvaluate::Global(Isolate* isolate, Handle 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 != NULL && IsDebugContext(isolate, *top->context())) { top = top->prev(); } if (top != NULL) isolate->set_context(*top->context()); // Get the native context now set to the top context from before the // debugger was invoked. Handle context = isolate->native_context(); Handle receiver(context->global_proxy()); Handle outer_info(context->closure()->shared(), isolate); return Evaluate(isolate, outer_info, context, receiver, source, false); } MaybeHandle DebugEvaluate::Local(Isolate* isolate, StackFrame::Id frame_id, int inlined_jsframe_index, Handle 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(); // Traverse the saved contexts chain to find the active context for the // selected frame. SaveContext* save = DebugFrameHelper::FindSavedContextForFrame(isolate, frame); SaveContext savex(isolate); isolate->set_context(*(save->context())); // 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(); Handle context = context_builder.evaluation_context(); Handle receiver(context->global_proxy()); MaybeHandle 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 DebugEvaluate::Evaluate( Isolate* isolate, Handle outer_info, Handle context, Handle receiver, Handle source, bool throw_on_side_effect) { Handle eval_fun; ASSIGN_RETURN_ON_EXCEPTION( isolate, eval_fun, Compiler::GetFunctionFromEval(source, outer_info, context, SLOPPY, NO_PARSE_RESTRICTION, kNoSourcePosition, kNoSourcePosition, kNoSourcePosition), Object); Handle result; { NoSideEffectScope no_side_effect(isolate, throw_on_side_effect); ASSIGN_RETURN_ON_EXCEPTION( isolate, result, Execution::Call(isolate, eval_fun, receiver, 0, NULL), 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::cast(result)); // TODO(verwaest): This will crash when the global proxy is detached. result = PrototypeIterator::GetCurrent(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 local_function = frame_inspector.GetFunction(); Handle 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 materialized = factory->NewJSObjectWithNullProto(); Handle local_context = it.HasContext() ? it.CurrentContext() : outer_context; Handle non_locals = it.GetNonLocals(); MaterializeReceiver(materialized, local_context, local_function, non_locals); frame_inspector.MaterializeStackLocals(materialized, local_function); MaterializeArgumentsObject(materialized, local_function); 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_.Add(context_chain_element); evaluation_context_ = outer_context; break; } else if (scope_type == ScopeIterator::ScopeTypeCatch || scope_type == ScopeIterator::ScopeTypeWith) { ContextChainElement context_chain_element; Handle current_context = it.CurrentContext(); if (!current_context->IsDebugEvaluateContext()) { context_chain_element.wrapped_context = current_context; } context_chain_.Add(context_chain_element); } else if (scope_type == ScopeIterator::ScopeTypeBlock || scope_type == ScopeIterator::ScopeTypeEval) { Handle 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_.Add(context_chain_element); } else { break; } } for (int i = context_chain_.length() - 1; i >= 0; i--) { Handle scope_info(ScopeInfo::CreateForWithScope( isolate, evaluation_context_->IsNativeContext() ? Handle::null() : Handle(evaluation_context_->scope_info()))); scope_info->SetIsDebugEvaluateScope(); evaluation_context_ = factory->NewDebugEvaluateContext( evaluation_context_, scope_info, context_chain_[i].materialized_object, context_chain_[i].wrapped_context, context_chain_[i].whitelist); } } void DebugEvaluate::ContextBuilder::UpdateValues() { // TODO(yangguo): remove updating values. for (int i = 0; i < context_chain_.length(); i++) { ContextChainElement element = context_chain_[i]; 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::MaterializeArgumentsObject( Handle target, Handle function) { // Do not materialize the arguments object for eval or top-level code. // Skip if "arguments" is already taken. if (function->shared()->is_toplevel()) return; Maybe maybe = JSReceiver::HasOwnProperty( target, isolate_->factory()->arguments_string()); DCHECK(maybe.IsJust()); if (maybe.FromJust()) return; // FunctionGetArguments can't throw an exception. Handle arguments = Accessors::FunctionGetArguments(function); Handle arguments_str = isolate_->factory()->arguments_string(); JSObject::SetOwnPropertyIgnoreAttributes(target, arguments_str, arguments, NONE) .Check(); } void DebugEvaluate::ContextBuilder::MaterializeReceiver( Handle target, Handle local_context, Handle local_function, Handle non_locals) { Handle recv = isolate_->factory()->undefined_value(); Handle 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(); } namespace { bool IntrinsicHasNoSideEffect(Runtime::FunctionId id) { switch (id) { // Whitelist for intrinsics amd runtime functions. // Conversions. case Runtime::kToInteger: case Runtime::kInlineToInteger: case Runtime::kToObject: case Runtime::kInlineToObject: case Runtime::kToString: case Runtime::kInlineToString: case Runtime::kToLength: case Runtime::kInlineToLength: case Runtime::kToNumber: // Type checks. case Runtime::kIsJSReceiver: case Runtime::kInlineIsJSReceiver: case Runtime::kIsSmi: case Runtime::kInlineIsSmi: case Runtime::kIsArray: case Runtime::kInlineIsArray: case Runtime::kIsFunction: case Runtime::kIsDate: case Runtime::kIsJSProxy: case Runtime::kIsRegExp: case Runtime::kIsTypedArray: // Loads. case Runtime::kLoadLookupSlotForCall: // Arrays. case Runtime::kArraySpeciesConstructor: case Runtime::kNormalizeElements: case Runtime::kGetArrayKeys: case Runtime::kHasComplexElements: case Runtime::kEstimateNumberOfElements: // Errors. case Runtime::kReThrow: case Runtime::kThrowReferenceError: case Runtime::kThrowSymbolIteratorInvalid: case Runtime::kThrowIteratorResultNotAnObject: case Runtime::kNewTypeError: // Strings. case Runtime::kInlineStringCharCodeAt: case Runtime::kStringCharCodeAt: case Runtime::kStringIndexOf: case Runtime::kStringReplaceOneCharWithString: case Runtime::kSubString: case Runtime::kInlineSubString: case Runtime::kRegExpInternalReplace: // Literals. case Runtime::kCreateArrayLiteral: case Runtime::kCreateObjectLiteral: case Runtime::kCreateRegExpLiteral: // Misc. case Runtime::kForInPrepare: case Runtime::kInlineCall: case Runtime::kCall: case Runtime::kInlineMaxSmi: case Runtime::kMaxSmi: 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; } } 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::WritesBooleanToAccumulator(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::kDiv: case Bytecode::kMod: case Bytecode::kBitwiseAnd: case Bytecode::kBitwiseAndSmi: case Bytecode::kBitwiseOr: case Bytecode::kBitwiseOrSmi: case Bytecode::kBitwiseXor: case Bytecode::kShiftLeft: case Bytecode::kShiftLeftSmi: case Bytecode::kShiftRight: case Bytecode::kShiftRightSmi: case Bytecode::kShiftRightLogical: 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::kCreateObjectLiteral: case Bytecode::kCreateRegExpLiteral: // Allocations. case Bytecode::kCreateClosure: case Bytecode::kCreateUnmappedArguments: // Conversions. case Bytecode::kToObject: case Bytecode::kToNumber: // Misc. case Bytecode::kForInPrepare: case Bytecode::kForInContinue: case Bytecode::kForInNext: case Bytecode::kForInStep: case Bytecode::kThrow: case Bytecode::kReThrow: 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. // Array builtins. case Builtins::kArrayCode: case Builtins::kArrayIndexOf: case Builtins::kArrayPrototypeValues: case Builtins::kArrayIncludes: case Builtins::kArrayPrototypeEntries: case Builtins::kArrayPrototypeKeys: case Builtins::kArrayForEach: // 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: // String builtins. Strings are immutable. case Builtins::kStringFromCharCode: case Builtins::kStringFromCodePoint: case Builtins::kStringConstructor: case Builtins::kStringPrototypeCharAt: case Builtins::kStringPrototypeCharCodeAt: case Builtins::kStringPrototypeEndsWith: case Builtins::kStringPrototypeIncludes: case Builtins::kStringPrototypeIndexOf: case Builtins::kStringPrototypeLastIndexOf: case Builtins::kStringPrototypeStartsWith: case Builtins::kStringPrototypeSubstr: case Builtins::kStringPrototypeSubstring: case Builtins::kStringPrototypeToString: case Builtins::kStringPrototypeToLowerCase: case Builtins::kStringPrototypeToUpperCase: case Builtins::kStringPrototypeTrim: case Builtins::kStringPrototypeTrimLeft: case Builtins::kStringPrototypeTrimRight: case Builtins::kStringPrototypeValueOf: // JSON builtins. case Builtins::kJsonParse: case Builtins::kJsonStringify: // 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)}; } // anonymous namespace // static bool DebugEvaluate::FunctionHasNoSideEffect(Handle 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 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->code()->builtin_index(); if (builtin_index >= 0 && builtin_index < Builtins::builtin_count && BuiltinHasNoSideEffect(static_cast(builtin_index))) { 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(function_addr)); } return false; } } // namespace internal } // namespace v8