// Copyright 2014 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "src/v8.h" #include "src/accessors.h" #include "src/arguments.h" #include "src/debug.h" #include "src/deoptimizer.h" #include "src/isolate-inl.h" #include "src/parser.h" #include "src/runtime/runtime.h" #include "src/runtime/runtime-utils.h" namespace v8 { namespace internal { RUNTIME_FUNCTION(Runtime_DebugBreak) { SealHandleScope shs(isolate); DCHECK(args.length() == 0); isolate->debug()->HandleDebugBreak(); return isolate->heap()->undefined_value(); } // Helper functions for wrapping and unwrapping stack frame ids. static Smi* WrapFrameId(StackFrame::Id id) { DCHECK(IsAligned(OffsetFrom(id), static_cast(4))); return Smi::FromInt(id >> 2); } static StackFrame::Id UnwrapFrameId(int wrapped) { return static_cast(wrapped << 2); } // Adds a JavaScript function as a debug event listener. // args[0]: debug event listener function to set or null or undefined for // clearing the event listener function // args[1]: object supplied during callback RUNTIME_FUNCTION(Runtime_SetDebugEventListener) { SealHandleScope shs(isolate); DCHECK(args.length() == 2); RUNTIME_ASSERT(args[0]->IsJSFunction() || args[0]->IsUndefined() || args[0]->IsNull()); CONVERT_ARG_HANDLE_CHECKED(Object, callback, 0); CONVERT_ARG_HANDLE_CHECKED(Object, data, 1); isolate->debug()->SetEventListener(callback, data); return isolate->heap()->undefined_value(); } RUNTIME_FUNCTION(Runtime_Break) { SealHandleScope shs(isolate); DCHECK(args.length() == 0); isolate->stack_guard()->RequestDebugBreak(); return isolate->heap()->undefined_value(); } static Handle DebugGetProperty(LookupIterator* it, bool* has_caught = NULL) { for (; it->IsFound(); it->Next()) { switch (it->state()) { case LookupIterator::NOT_FOUND: case LookupIterator::TRANSITION: UNREACHABLE(); case LookupIterator::ACCESS_CHECK: // Ignore access checks. break; case LookupIterator::INTERCEPTOR: case LookupIterator::JSPROXY: return it->isolate()->factory()->undefined_value(); case LookupIterator::ACCESSOR: { Handle accessors = it->GetAccessors(); if (!accessors->IsAccessorInfo()) { return it->isolate()->factory()->undefined_value(); } MaybeHandle maybe_result = JSObject::GetPropertyWithAccessor( it->GetReceiver(), it->name(), it->GetHolder(), accessors); Handle result; if (!maybe_result.ToHandle(&result)) { result = handle(it->isolate()->pending_exception(), it->isolate()); it->isolate()->clear_pending_exception(); if (has_caught != NULL) *has_caught = true; } return result; } case LookupIterator::DATA: return it->GetDataValue(); } } return it->isolate()->factory()->undefined_value(); } // Get debugger related details for an object property, in the following format: // 0: Property value // 1: Property details // 2: Property value is exception // 3: Getter function if defined // 4: Setter function if defined // Items 2-4 are only filled if the property has either a getter or a setter. RUNTIME_FUNCTION(Runtime_DebugGetPropertyDetails) { HandleScope scope(isolate); DCHECK(args.length() == 2); CONVERT_ARG_HANDLE_CHECKED(JSObject, obj, 0); CONVERT_ARG_HANDLE_CHECKED(Name, name, 1); // Make sure to set the current context to the context before the debugger was // entered (if the debugger is entered). The reason for switching context here // is that for some property lookups (accessors and interceptors) callbacks // into the embedding application can occour, and the embedding application // could have the assumption that its own native context is the current // context and not some internal debugger context. SaveContext save(isolate); if (isolate->debug()->in_debug_scope()) { isolate->set_context(*isolate->debug()->debugger_entry()->GetContext()); } // Check if the name is trivially convertible to an index and get the element // if so. uint32_t index; if (name->AsArrayIndex(&index)) { Handle details = isolate->factory()->NewFixedArray(2); Handle element_or_char; ASSIGN_RETURN_FAILURE_ON_EXCEPTION( isolate, element_or_char, Runtime::GetElementOrCharAt(isolate, obj, index)); details->set(0, *element_or_char); details->set(1, PropertyDetails(NONE, NORMAL, Representation::None()).AsSmi()); return *isolate->factory()->NewJSArrayWithElements(details); } LookupIterator it(obj, name, LookupIterator::HIDDEN); bool has_caught = false; Handle value = DebugGetProperty(&it, &has_caught); if (!it.IsFound()) return isolate->heap()->undefined_value(); Handle maybe_pair; if (it.state() == LookupIterator::ACCESSOR) { maybe_pair = it.GetAccessors(); } // If the callback object is a fixed array then it contains JavaScript // getter and/or setter. bool has_js_accessors = !maybe_pair.is_null() && maybe_pair->IsAccessorPair(); Handle details = isolate->factory()->NewFixedArray(has_js_accessors ? 6 : 3); details->set(0, *value); // TODO(verwaest): Get rid of this random way of handling interceptors. PropertyDetails d = it.state() == LookupIterator::INTERCEPTOR ? PropertyDetails(NONE, NORMAL, 0) : it.property_details(); details->set(1, d.AsSmi()); details->set( 2, isolate->heap()->ToBoolean(it.state() == LookupIterator::INTERCEPTOR)); if (has_js_accessors) { AccessorPair* accessors = AccessorPair::cast(*maybe_pair); details->set(3, isolate->heap()->ToBoolean(has_caught)); details->set(4, accessors->GetComponent(ACCESSOR_GETTER)); details->set(5, accessors->GetComponent(ACCESSOR_SETTER)); } return *isolate->factory()->NewJSArrayWithElements(details); } RUNTIME_FUNCTION(Runtime_DebugGetProperty) { HandleScope scope(isolate); DCHECK(args.length() == 2); CONVERT_ARG_HANDLE_CHECKED(JSObject, obj, 0); CONVERT_ARG_HANDLE_CHECKED(Name, name, 1); LookupIterator it(obj, name); return *DebugGetProperty(&it); } // Return the property type calculated from the property details. // args[0]: smi with property details. RUNTIME_FUNCTION(Runtime_DebugPropertyTypeFromDetails) { SealHandleScope shs(isolate); DCHECK(args.length() == 1); CONVERT_PROPERTY_DETAILS_CHECKED(details, 0); return Smi::FromInt(static_cast(details.type())); } // Return the property attribute calculated from the property details. // args[0]: smi with property details. RUNTIME_FUNCTION(Runtime_DebugPropertyAttributesFromDetails) { SealHandleScope shs(isolate); DCHECK(args.length() == 1); CONVERT_PROPERTY_DETAILS_CHECKED(details, 0); return Smi::FromInt(static_cast(details.attributes())); } // Return the property insertion index calculated from the property details. // args[0]: smi with property details. RUNTIME_FUNCTION(Runtime_DebugPropertyIndexFromDetails) { SealHandleScope shs(isolate); DCHECK(args.length() == 1); CONVERT_PROPERTY_DETAILS_CHECKED(details, 0); // TODO(verwaest): Depends on the type of details. return Smi::FromInt(details.dictionary_index()); } // Return property value from named interceptor. // args[0]: object // args[1]: property name RUNTIME_FUNCTION(Runtime_DebugNamedInterceptorPropertyValue) { HandleScope scope(isolate); DCHECK(args.length() == 2); CONVERT_ARG_HANDLE_CHECKED(JSObject, obj, 0); RUNTIME_ASSERT(obj->HasNamedInterceptor()); CONVERT_ARG_HANDLE_CHECKED(Name, name, 1); Handle result; ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, result, JSObject::GetProperty(obj, name)); return *result; } // Return element value from indexed interceptor. // args[0]: object // args[1]: index RUNTIME_FUNCTION(Runtime_DebugIndexedInterceptorElementValue) { HandleScope scope(isolate); DCHECK(args.length() == 2); CONVERT_ARG_HANDLE_CHECKED(JSObject, obj, 0); RUNTIME_ASSERT(obj->HasIndexedInterceptor()); CONVERT_NUMBER_CHECKED(uint32_t, index, Uint32, args[1]); Handle result; ASSIGN_RETURN_FAILURE_ON_EXCEPTION( isolate, result, JSObject::GetElementWithInterceptor(obj, obj, index)); return *result; } RUNTIME_FUNCTION(Runtime_CheckExecutionState) { SealHandleScope shs(isolate); DCHECK(args.length() == 1); CONVERT_NUMBER_CHECKED(int, break_id, Int32, args[0]); RUNTIME_ASSERT(isolate->debug()->CheckExecutionState(break_id)); return isolate->heap()->true_value(); } RUNTIME_FUNCTION(Runtime_GetFrameCount) { HandleScope scope(isolate); DCHECK(args.length() == 1); CONVERT_NUMBER_CHECKED(int, break_id, Int32, args[0]); RUNTIME_ASSERT(isolate->debug()->CheckExecutionState(break_id)); // Count all frames which are relevant to debugging stack trace. int n = 0; StackFrame::Id id = isolate->debug()->break_frame_id(); if (id == StackFrame::NO_ID) { // If there is no JavaScript stack frame count is 0. return Smi::FromInt(0); } for (JavaScriptFrameIterator it(isolate, id); !it.done(); it.Advance()) { List frames(FLAG_max_inlining_levels + 1); it.frame()->Summarize(&frames); for (int i = frames.length() - 1; i >= 0; i--) { // Omit functions from native scripts. if (!frames[i].function()->IsFromNativeScript()) n++; } } return Smi::FromInt(n); } class FrameInspector { public: FrameInspector(JavaScriptFrame* frame, int inlined_jsframe_index, Isolate* isolate) : frame_(frame), deoptimized_frame_(NULL), isolate_(isolate) { // Calculate the deoptimized frame. if (frame->is_optimized()) { deoptimized_frame_ = Deoptimizer::DebuggerInspectableFrame( frame, inlined_jsframe_index, isolate); } has_adapted_arguments_ = frame_->has_adapted_arguments(); is_bottommost_ = inlined_jsframe_index == 0; is_optimized_ = frame_->is_optimized(); } ~FrameInspector() { // Get rid of the calculated deoptimized frame if any. if (deoptimized_frame_ != NULL) { Deoptimizer::DeleteDebuggerInspectableFrame(deoptimized_frame_, isolate_); } } int GetParametersCount() { return is_optimized_ ? deoptimized_frame_->parameters_count() : frame_->ComputeParametersCount(); } int expression_count() { return deoptimized_frame_->expression_count(); } Object* GetFunction() { return is_optimized_ ? deoptimized_frame_->GetFunction() : frame_->function(); } Object* GetParameter(int index) { return is_optimized_ ? deoptimized_frame_->GetParameter(index) : frame_->GetParameter(index); } Object* GetExpression(int index) { return is_optimized_ ? deoptimized_frame_->GetExpression(index) : frame_->GetExpression(index); } int GetSourcePosition() { return is_optimized_ ? deoptimized_frame_->GetSourcePosition() : frame_->LookupCode()->SourcePosition(frame_->pc()); } bool IsConstructor() { return is_optimized_ && !is_bottommost_ ? deoptimized_frame_->HasConstructStub() : frame_->IsConstructor(); } Object* GetContext() { return is_optimized_ ? deoptimized_frame_->GetContext() : frame_->context(); } // To inspect all the provided arguments the frame might need to be // replaced with the arguments frame. void SetArgumentsFrame(JavaScriptFrame* frame) { DCHECK(has_adapted_arguments_); frame_ = frame; is_optimized_ = frame_->is_optimized(); DCHECK(!is_optimized_); } private: JavaScriptFrame* frame_; DeoptimizedFrameInfo* deoptimized_frame_; Isolate* isolate_; bool is_optimized_; bool is_bottommost_; bool has_adapted_arguments_; DISALLOW_COPY_AND_ASSIGN(FrameInspector); }; static const int kFrameDetailsFrameIdIndex = 0; static const int kFrameDetailsReceiverIndex = 1; static const int kFrameDetailsFunctionIndex = 2; static const int kFrameDetailsArgumentCountIndex = 3; static const int kFrameDetailsLocalCountIndex = 4; static const int kFrameDetailsSourcePositionIndex = 5; static const int kFrameDetailsConstructCallIndex = 6; static const int kFrameDetailsAtReturnIndex = 7; static const int kFrameDetailsFlagsIndex = 8; static const int kFrameDetailsFirstDynamicIndex = 9; static SaveContext* FindSavedContextForFrame(Isolate* isolate, JavaScriptFrame* frame) { SaveContext* save = isolate->save_context(); while (save != NULL && !save->IsBelowFrame(frame)) { save = save->prev(); } DCHECK(save != NULL); return save; } // Advances the iterator to the frame that matches the index and returns the // inlined frame index, or -1 if not found. Skips native JS functions. int Runtime::FindIndexedNonNativeFrame(JavaScriptFrameIterator* it, int index) { int count = -1; for (; !it->done(); it->Advance()) { List frames(FLAG_max_inlining_levels + 1); it->frame()->Summarize(&frames); for (int i = frames.length() - 1; i >= 0; i--) { // Omit functions from native scripts. if (frames[i].function()->IsFromNativeScript()) continue; if (++count == index) return i; } } return -1; } // Return an array with frame details // args[0]: number: break id // args[1]: number: frame index // // The array returned contains the following information: // 0: Frame id // 1: Receiver // 2: Function // 3: Argument count // 4: Local count // 5: Source position // 6: Constructor call // 7: Is at return // 8: Flags // Arguments name, value // Locals name, value // Return value if any RUNTIME_FUNCTION(Runtime_GetFrameDetails) { HandleScope scope(isolate); DCHECK(args.length() == 2); CONVERT_NUMBER_CHECKED(int, break_id, Int32, args[0]); RUNTIME_ASSERT(isolate->debug()->CheckExecutionState(break_id)); CONVERT_NUMBER_CHECKED(int, index, Int32, args[1]); Heap* heap = isolate->heap(); // Find the relevant frame with the requested index. StackFrame::Id id = isolate->debug()->break_frame_id(); if (id == StackFrame::NO_ID) { // If there are no JavaScript stack frames return undefined. return heap->undefined_value(); } JavaScriptFrameIterator it(isolate, id); // Inlined frame index in optimized frame, starting from outer function. int inlined_jsframe_index = Runtime::FindIndexedNonNativeFrame(&it, index); if (inlined_jsframe_index == -1) return heap->undefined_value(); FrameInspector frame_inspector(it.frame(), inlined_jsframe_index, isolate); bool is_optimized = it.frame()->is_optimized(); // Traverse the saved contexts chain to find the active context for the // selected frame. SaveContext* save = FindSavedContextForFrame(isolate, it.frame()); // Get the frame id. Handle frame_id(WrapFrameId(it.frame()->id()), isolate); // Find source position in unoptimized code. int position = frame_inspector.GetSourcePosition(); // Check for constructor frame. bool constructor = frame_inspector.IsConstructor(); // Get scope info and read from it for local variable information. Handle function(JSFunction::cast(frame_inspector.GetFunction())); Handle shared(function->shared()); Handle scope_info(shared->scope_info()); DCHECK(*scope_info != ScopeInfo::Empty(isolate)); // Get the locals names and values into a temporary array. int local_count = scope_info->LocalCount(); for (int slot = 0; slot < scope_info->LocalCount(); ++slot) { // Hide compiler-introduced temporary variables, whether on the stack or on // the context. if (scope_info->LocalIsSynthetic(slot)) local_count--; } Handle locals = isolate->factory()->NewFixedArray(local_count * 2); // Fill in the values of the locals. int local = 0; int i = 0; for (; i < scope_info->StackLocalCount(); ++i) { // Use the value from the stack. if (scope_info->LocalIsSynthetic(i)) continue; locals->set(local * 2, scope_info->LocalName(i)); locals->set(local * 2 + 1, frame_inspector.GetExpression(i)); local++; } if (local < local_count) { // Get the context containing declarations. Handle context( Context::cast(frame_inspector.GetContext())->declaration_context()); for (; i < scope_info->LocalCount(); ++i) { if (scope_info->LocalIsSynthetic(i)) continue; Handle name(scope_info->LocalName(i)); VariableMode mode; InitializationFlag init_flag; MaybeAssignedFlag maybe_assigned_flag; locals->set(local * 2, *name); int context_slot_index = ScopeInfo::ContextSlotIndex( scope_info, name, &mode, &init_flag, &maybe_assigned_flag); Object* value = context->get(context_slot_index); locals->set(local * 2 + 1, value); local++; } } // Check whether this frame is positioned at return. If not top // frame or if the frame is optimized it cannot be at a return. bool at_return = false; if (!is_optimized && index == 0) { at_return = isolate->debug()->IsBreakAtReturn(it.frame()); } // If positioned just before return find the value to be returned and add it // to the frame information. Handle return_value = isolate->factory()->undefined_value(); if (at_return) { StackFrameIterator it2(isolate); Address internal_frame_sp = NULL; while (!it2.done()) { if (it2.frame()->is_internal()) { internal_frame_sp = it2.frame()->sp(); } else { if (it2.frame()->is_java_script()) { if (it2.frame()->id() == it.frame()->id()) { // The internal frame just before the JavaScript frame contains the // value to return on top. A debug break at return will create an // internal frame to store the return value (eax/rax/r0) before // entering the debug break exit frame. if (internal_frame_sp != NULL) { return_value = Handle(Memory::Object_at(internal_frame_sp), isolate); break; } } } // Indicate that the previous frame was not an internal frame. internal_frame_sp = NULL; } it2.Advance(); } } // Now advance to the arguments adapter frame (if any). It contains all // the provided parameters whereas the function frame always have the number // of arguments matching the functions parameters. The rest of the // information (except for what is collected above) is the same. if ((inlined_jsframe_index == 0) && it.frame()->has_adapted_arguments()) { it.AdvanceToArgumentsFrame(); frame_inspector.SetArgumentsFrame(it.frame()); } // Find the number of arguments to fill. At least fill the number of // parameters for the function and fill more if more parameters are provided. int argument_count = scope_info->ParameterCount(); if (argument_count < frame_inspector.GetParametersCount()) { argument_count = frame_inspector.GetParametersCount(); } // Calculate the size of the result. int details_size = kFrameDetailsFirstDynamicIndex + 2 * (argument_count + local_count) + (at_return ? 1 : 0); Handle details = isolate->factory()->NewFixedArray(details_size); // Add the frame id. details->set(kFrameDetailsFrameIdIndex, *frame_id); // Add the function (same as in function frame). details->set(kFrameDetailsFunctionIndex, frame_inspector.GetFunction()); // Add the arguments count. details->set(kFrameDetailsArgumentCountIndex, Smi::FromInt(argument_count)); // Add the locals count details->set(kFrameDetailsLocalCountIndex, Smi::FromInt(local_count)); // Add the source position. if (position != RelocInfo::kNoPosition) { details->set(kFrameDetailsSourcePositionIndex, Smi::FromInt(position)); } else { details->set(kFrameDetailsSourcePositionIndex, heap->undefined_value()); } // Add the constructor information. details->set(kFrameDetailsConstructCallIndex, heap->ToBoolean(constructor)); // Add the at return information. details->set(kFrameDetailsAtReturnIndex, heap->ToBoolean(at_return)); // Add flags to indicate information on whether this frame is // bit 0: invoked in the debugger context. // bit 1: optimized frame. // bit 2: inlined in optimized frame int flags = 0; if (*save->context() == *isolate->debug()->debug_context()) { flags |= 1 << 0; } if (is_optimized) { flags |= 1 << 1; flags |= inlined_jsframe_index << 2; } details->set(kFrameDetailsFlagsIndex, Smi::FromInt(flags)); // Fill the dynamic part. int details_index = kFrameDetailsFirstDynamicIndex; // Add arguments name and value. for (int i = 0; i < argument_count; i++) { // Name of the argument. if (i < scope_info->ParameterCount()) { details->set(details_index++, scope_info->ParameterName(i)); } else { details->set(details_index++, heap->undefined_value()); } // Parameter value. if (i < frame_inspector.GetParametersCount()) { // Get the value from the stack. details->set(details_index++, frame_inspector.GetParameter(i)); } else { details->set(details_index++, heap->undefined_value()); } } // Add locals name and value from the temporary copy from the function frame. for (int i = 0; i < local_count * 2; i++) { details->set(details_index++, locals->get(i)); } // Add the value being returned. if (at_return) { details->set(details_index++, *return_value); } // Add the receiver (same as in function frame). // THIS MUST BE DONE LAST SINCE WE MIGHT ADVANCE // THE FRAME ITERATOR TO WRAP THE RECEIVER. Handle receiver(it.frame()->receiver(), isolate); if (!receiver->IsJSObject() && shared->strict_mode() == SLOPPY && !function->IsBuiltin()) { // If the receiver is not a JSObject and the function is not a // builtin or strict-mode we have hit an optimization where a // value object is not converted into a wrapped JS objects. To // hide this optimization from the debugger, we wrap the receiver // by creating correct wrapper object based on the calling frame's // native context. it.Advance(); if (receiver->IsUndefined()) { receiver = handle(function->global_proxy()); } else { Context* context = Context::cast(it.frame()->context()); Handle native_context(Context::cast(context->native_context())); if (!Object::ToObject(isolate, receiver, native_context) .ToHandle(&receiver)) { // This only happens if the receiver is forcibly set in %_CallFunction. return heap->undefined_value(); } } } details->set(kFrameDetailsReceiverIndex, *receiver); DCHECK_EQ(details_size, details_index); return *isolate->factory()->NewJSArrayWithElements(details); } static bool ParameterIsShadowedByContextLocal(Handle info, Handle parameter_name) { VariableMode mode; InitializationFlag init_flag; MaybeAssignedFlag maybe_assigned_flag; return ScopeInfo::ContextSlotIndex(info, parameter_name, &mode, &init_flag, &maybe_assigned_flag) != -1; } // Create a plain JSObject which materializes the local scope for the specified // frame. MUST_USE_RESULT static MaybeHandle MaterializeStackLocalsWithFrameInspector( Isolate* isolate, Handle target, Handle function, FrameInspector* frame_inspector) { Handle shared(function->shared()); Handle scope_info(shared->scope_info()); // First fill all parameters. for (int i = 0; i < scope_info->ParameterCount(); ++i) { // Do not materialize the parameter if it is shadowed by a context local. Handle name(scope_info->ParameterName(i)); if (ParameterIsShadowedByContextLocal(scope_info, name)) continue; HandleScope scope(isolate); Handle value(i < frame_inspector->GetParametersCount() ? frame_inspector->GetParameter(i) : isolate->heap()->undefined_value(), isolate); DCHECK(!value->IsTheHole()); RETURN_ON_EXCEPTION(isolate, Runtime::SetObjectProperty( isolate, target, name, value, SLOPPY), JSObject); } // Second fill all stack locals. for (int i = 0; i < scope_info->StackLocalCount(); ++i) { if (scope_info->LocalIsSynthetic(i)) continue; Handle name(scope_info->StackLocalName(i)); Handle value(frame_inspector->GetExpression(i), isolate); if (value->IsTheHole()) continue; RETURN_ON_EXCEPTION(isolate, Runtime::SetObjectProperty( isolate, target, name, value, SLOPPY), JSObject); } return target; } static void UpdateStackLocalsFromMaterializedObject(Isolate* isolate, Handle target, Handle function, JavaScriptFrame* frame, int inlined_jsframe_index) { if (inlined_jsframe_index != 0 || frame->is_optimized()) { // Optimized frames are not supported. // TODO(yangguo): make sure all code deoptimized when debugger is active // and assert that this cannot happen. return; } Handle shared(function->shared()); Handle scope_info(shared->scope_info()); // Parameters. for (int i = 0; i < scope_info->ParameterCount(); ++i) { // Shadowed parameters were not materialized. Handle name(scope_info->ParameterName(i)); if (ParameterIsShadowedByContextLocal(scope_info, name)) continue; DCHECK(!frame->GetParameter(i)->IsTheHole()); HandleScope scope(isolate); Handle value = Object::GetPropertyOrElement(target, name).ToHandleChecked(); frame->SetParameterValue(i, *value); } // Stack locals. for (int i = 0; i < scope_info->StackLocalCount(); ++i) { if (scope_info->LocalIsSynthetic(i)) continue; if (frame->GetExpression(i)->IsTheHole()) continue; HandleScope scope(isolate); Handle value = Object::GetPropertyOrElement( target, handle(scope_info->StackLocalName(i), isolate)).ToHandleChecked(); frame->SetExpression(i, *value); } } MUST_USE_RESULT static MaybeHandle MaterializeLocalContext( Isolate* isolate, Handle target, Handle function, JavaScriptFrame* frame) { HandleScope scope(isolate); Handle shared(function->shared()); Handle scope_info(shared->scope_info()); if (!scope_info->HasContext()) return target; // Third fill all context locals. Handle frame_context(Context::cast(frame->context())); Handle function_context(frame_context->declaration_context()); if (!ScopeInfo::CopyContextLocalsToScopeObject(scope_info, function_context, target)) { return MaybeHandle(); } // Finally copy any properties from the function context extension. // These will be variables introduced by eval. if (function_context->closure() == *function) { if (function_context->has_extension() && !function_context->IsNativeContext()) { Handle ext(JSObject::cast(function_context->extension())); Handle keys; ASSIGN_RETURN_ON_EXCEPTION( isolate, keys, JSReceiver::GetKeys(ext, JSReceiver::INCLUDE_PROTOS), JSObject); for (int i = 0; i < keys->length(); i++) { // Names of variables introduced by eval are strings. DCHECK(keys->get(i)->IsString()); Handle key(String::cast(keys->get(i))); Handle value; ASSIGN_RETURN_ON_EXCEPTION( isolate, value, Object::GetPropertyOrElement(ext, key), JSObject); RETURN_ON_EXCEPTION(isolate, Runtime::SetObjectProperty( isolate, target, key, value, SLOPPY), JSObject); } } } return target; } MUST_USE_RESULT static MaybeHandle MaterializeLocalScope( Isolate* isolate, JavaScriptFrame* frame, int inlined_jsframe_index) { FrameInspector frame_inspector(frame, inlined_jsframe_index, isolate); Handle function(JSFunction::cast(frame_inspector.GetFunction())); Handle local_scope = isolate->factory()->NewJSObject(isolate->object_function()); ASSIGN_RETURN_ON_EXCEPTION( isolate, local_scope, MaterializeStackLocalsWithFrameInspector(isolate, local_scope, function, &frame_inspector), JSObject); return MaterializeLocalContext(isolate, local_scope, function, frame); } // Set the context local variable value. static bool SetContextLocalValue(Isolate* isolate, Handle scope_info, Handle context, Handle variable_name, Handle new_value) { for (int i = 0; i < scope_info->ContextLocalCount(); i++) { Handle next_name(scope_info->ContextLocalName(i)); if (String::Equals(variable_name, next_name)) { VariableMode mode; InitializationFlag init_flag; MaybeAssignedFlag maybe_assigned_flag; int context_index = ScopeInfo::ContextSlotIndex( scope_info, next_name, &mode, &init_flag, &maybe_assigned_flag); context->set(context_index, *new_value); return true; } } return false; } static bool SetLocalVariableValue(Isolate* isolate, JavaScriptFrame* frame, int inlined_jsframe_index, Handle variable_name, Handle new_value) { if (inlined_jsframe_index != 0 || frame->is_optimized()) { // Optimized frames are not supported. return false; } Handle function(frame->function()); Handle shared(function->shared()); Handle scope_info(shared->scope_info()); bool default_result = false; // Parameters. for (int i = 0; i < scope_info->ParameterCount(); ++i) { HandleScope scope(isolate); if (String::Equals(handle(scope_info->ParameterName(i)), variable_name)) { frame->SetParameterValue(i, *new_value); // Argument might be shadowed in heap context, don't stop here. default_result = true; } } // Stack locals. for (int i = 0; i < scope_info->StackLocalCount(); ++i) { HandleScope scope(isolate); if (String::Equals(handle(scope_info->StackLocalName(i)), variable_name)) { frame->SetExpression(i, *new_value); return true; } } if (scope_info->HasContext()) { // Context locals. Handle frame_context(Context::cast(frame->context())); Handle function_context(frame_context->declaration_context()); if (SetContextLocalValue(isolate, scope_info, function_context, variable_name, new_value)) { return true; } // Function context extension. These are variables introduced by eval. if (function_context->closure() == *function) { if (function_context->has_extension() && !function_context->IsNativeContext()) { Handle ext(JSObject::cast(function_context->extension())); Maybe maybe = JSReceiver::HasProperty(ext, variable_name); DCHECK(maybe.has_value); if (maybe.value) { // We don't expect this to do anything except replacing // property value. Runtime::SetObjectProperty(isolate, ext, variable_name, new_value, SLOPPY).Assert(); return true; } } } } return default_result; } // Create a plain JSObject which materializes the closure content for the // context. MUST_USE_RESULT static MaybeHandle MaterializeClosure( Isolate* isolate, Handle context) { DCHECK(context->IsFunctionContext()); Handle shared(context->closure()->shared()); Handle scope_info(shared->scope_info()); // Allocate and initialize a JSObject with all the content of this function // closure. Handle closure_scope = isolate->factory()->NewJSObject(isolate->object_function()); // Fill all context locals to the context extension. if (!ScopeInfo::CopyContextLocalsToScopeObject(scope_info, context, closure_scope)) { return MaybeHandle(); } // Finally copy any properties from the function context extension. This will // be variables introduced by eval. if (context->has_extension()) { Handle ext(JSObject::cast(context->extension())); Handle keys; ASSIGN_RETURN_ON_EXCEPTION( isolate, keys, JSReceiver::GetKeys(ext, JSReceiver::INCLUDE_PROTOS), JSObject); for (int i = 0; i < keys->length(); i++) { HandleScope scope(isolate); // Names of variables introduced by eval are strings. DCHECK(keys->get(i)->IsString()); Handle key(String::cast(keys->get(i))); Handle value; ASSIGN_RETURN_ON_EXCEPTION( isolate, value, Object::GetPropertyOrElement(ext, key), JSObject); RETURN_ON_EXCEPTION(isolate, Runtime::DefineObjectProperty( closure_scope, key, value, NONE), JSObject); } } return closure_scope; } // This method copies structure of MaterializeClosure method above. static bool SetClosureVariableValue(Isolate* isolate, Handle context, Handle variable_name, Handle new_value) { DCHECK(context->IsFunctionContext()); Handle shared(context->closure()->shared()); Handle scope_info(shared->scope_info()); // Context locals to the context extension. if (SetContextLocalValue(isolate, scope_info, context, variable_name, new_value)) { return true; } // Properties from the function context extension. This will // be variables introduced by eval. if (context->has_extension()) { Handle ext(JSObject::cast(context->extension())); Maybe maybe = JSReceiver::HasProperty(ext, variable_name); DCHECK(maybe.has_value); if (maybe.value) { // We don't expect this to do anything except replacing property value. Runtime::DefineObjectProperty(ext, variable_name, new_value, NONE) .Assert(); return true; } } return false; } // Create a plain JSObject which materializes the scope for the specified // catch context. MUST_USE_RESULT static MaybeHandle MaterializeCatchScope( Isolate* isolate, Handle context) { DCHECK(context->IsCatchContext()); Handle name(String::cast(context->extension())); Handle thrown_object(context->get(Context::THROWN_OBJECT_INDEX), isolate); Handle catch_scope = isolate->factory()->NewJSObject(isolate->object_function()); RETURN_ON_EXCEPTION(isolate, Runtime::DefineObjectProperty( catch_scope, name, thrown_object, NONE), JSObject); return catch_scope; } static bool SetCatchVariableValue(Isolate* isolate, Handle context, Handle variable_name, Handle new_value) { DCHECK(context->IsCatchContext()); Handle name(String::cast(context->extension())); if (!String::Equals(name, variable_name)) { return false; } context->set(Context::THROWN_OBJECT_INDEX, *new_value); return true; } // Create a plain JSObject which materializes the block scope for the specified // block context. MUST_USE_RESULT static MaybeHandle MaterializeBlockScope( Isolate* isolate, Handle context) { DCHECK(context->IsBlockContext()); Handle scope_info(ScopeInfo::cast(context->extension())); // Allocate and initialize a JSObject with all the arguments, stack locals // heap locals and extension properties of the debugged function. Handle block_scope = isolate->factory()->NewJSObject(isolate->object_function()); // Fill all context locals. if (!ScopeInfo::CopyContextLocalsToScopeObject(scope_info, context, block_scope)) { return MaybeHandle(); } return block_scope; } // Create a plain JSObject which materializes the module scope for the specified // module context. MUST_USE_RESULT static MaybeHandle MaterializeModuleScope( Isolate* isolate, Handle context) { DCHECK(context->IsModuleContext()); Handle scope_info(ScopeInfo::cast(context->extension())); // Allocate and initialize a JSObject with all the members of the debugged // module. Handle module_scope = isolate->factory()->NewJSObject(isolate->object_function()); // Fill all context locals. if (!ScopeInfo::CopyContextLocalsToScopeObject(scope_info, context, module_scope)) { return MaybeHandle(); } return module_scope; } // Iterate over the actual scopes visible from a stack frame or from a closure. // The iteration proceeds from the innermost visible nested scope outwards. // All scopes are backed by an actual context except the local scope, // which is inserted "artificially" in the context chain. class ScopeIterator { public: enum ScopeType { ScopeTypeGlobal = 0, ScopeTypeLocal, ScopeTypeWith, ScopeTypeClosure, ScopeTypeCatch, ScopeTypeBlock, ScopeTypeModule }; ScopeIterator(Isolate* isolate, JavaScriptFrame* frame, int inlined_jsframe_index, bool ignore_nested_scopes = false) : isolate_(isolate), frame_(frame), inlined_jsframe_index_(inlined_jsframe_index), function_(frame->function()), context_(Context::cast(frame->context())), nested_scope_chain_(4), failed_(false) { // Catch the case when the debugger stops in an internal function. Handle shared_info(function_->shared()); Handle scope_info(shared_info->scope_info()); if (shared_info->script() == isolate->heap()->undefined_value()) { while (context_->closure() == *function_) { context_ = Handle(context_->previous(), isolate_); } return; } // Get the debug info (create it if it does not exist). if (!isolate->debug()->EnsureDebugInfo(shared_info, function_)) { // Return if ensuring debug info failed. return; } // Currently it takes too much time to find nested scopes due to script // parsing. Sometimes we want to run the ScopeIterator as fast as possible // (for example, while collecting async call stacks on every // addEventListener call), even if we drop some nested scopes. // Later we may optimize getting the nested scopes (cache the result?) // and include nested scopes into the "fast" iteration case as well. if (!ignore_nested_scopes) { Handle debug_info = Debug::GetDebugInfo(shared_info); // Find the break point where execution has stopped. BreakLocationIterator break_location_iterator(debug_info, ALL_BREAK_LOCATIONS); // pc points to the instruction after the current one, possibly a break // location as well. So the "- 1" to exclude it from the search. break_location_iterator.FindBreakLocationFromAddress(frame->pc() - 1); // Within the return sequence at the moment it is not possible to // get a source position which is consistent with the current scope chain. // Thus all nested with, catch and block contexts are skipped and we only // provide the function scope. ignore_nested_scopes = break_location_iterator.IsExit(); } if (ignore_nested_scopes) { if (scope_info->HasContext()) { context_ = Handle(context_->declaration_context(), isolate_); } else { while (context_->closure() == *function_) { context_ = Handle(context_->previous(), isolate_); } } if (scope_info->scope_type() == FUNCTION_SCOPE || scope_info->scope_type() == ARROW_SCOPE) { nested_scope_chain_.Add(scope_info); } } else { // Reparse the code and analyze the scopes. Handle