blob: d300e0195a217d74da54d7d90253909c7431c531 [file] [log] [blame]
// 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<intptr_t>(4)));
return Smi::FromInt(id >> 2);
}
static StackFrame::Id UnwrapFrameId(int wrapped) {
return static_cast<StackFrame::Id>(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<Object> 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<Object> accessors = it->GetAccessors();
if (!accessors->IsAccessorInfo()) {
return it->isolate()->factory()->undefined_value();
}
MaybeHandle<Object> maybe_result = JSObject::GetPropertyWithAccessor(
it->GetReceiver(), it->name(), it->GetHolder<JSObject>(),
accessors);
Handle<Object> 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<FixedArray> details = isolate->factory()->NewFixedArray(2);
Handle<Object> 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, DATA, 0).AsSmi());
return *isolate->factory()->NewJSArrayWithElements(details);
}
LookupIterator it(obj, name, LookupIterator::HIDDEN);
bool has_caught = false;
Handle<Object> value = DebugGetProperty(&it, &has_caught);
if (!it.IsFound()) return isolate->heap()->undefined_value();
Handle<Object> 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<FixedArray> 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, DATA, 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<int>(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<int>(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): Works only for dictionary mode holders.
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<Object> 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<Object> 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<FrameSummary> 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<FrameSummary> 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<Object> 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<JSFunction> function(JSFunction::cast(frame_inspector.GetFunction()));
Handle<SharedFunctionInfo> shared(function->shared());
Handle<ScopeInfo> 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<FixedArray> 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(
Context::cast(frame_inspector.GetContext())->declaration_context());
for (; i < scope_info->LocalCount(); ++i) {
if (scope_info->LocalIsSynthetic(i)) continue;
Handle<String> 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<Object> 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<Object>(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<FixedArray> 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<Object> 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<Context> 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<ScopeInfo> info,
Handle<String> 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<JSObject> MaterializeStackLocalsWithFrameInspector(
Isolate* isolate, Handle<JSObject> target, Handle<JSFunction> function,
FrameInspector* frame_inspector) {
Handle<SharedFunctionInfo> shared(function->shared());
Handle<ScopeInfo> 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<String> name(scope_info->ParameterName(i));
if (ParameterIsShadowedByContextLocal(scope_info, name)) continue;
HandleScope scope(isolate);
Handle<Object> 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<String> name(scope_info->StackLocalName(i));
Handle<Object> 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<JSObject> target,
Handle<JSFunction> 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<SharedFunctionInfo> shared(function->shared());
Handle<ScopeInfo> scope_info(shared->scope_info());
// Parameters.
for (int i = 0; i < scope_info->ParameterCount(); ++i) {
// Shadowed parameters were not materialized.
Handle<String> name(scope_info->ParameterName(i));
if (ParameterIsShadowedByContextLocal(scope_info, name)) continue;
DCHECK(!frame->GetParameter(i)->IsTheHole());
HandleScope scope(isolate);
Handle<Object> 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<Object> value = Object::GetPropertyOrElement(
target, handle(scope_info->StackLocalName(i),
isolate)).ToHandleChecked();
frame->SetExpression(i, *value);
}
}
MUST_USE_RESULT static MaybeHandle<JSObject> MaterializeLocalContext(
Isolate* isolate, Handle<JSObject> target, Handle<JSFunction> function,
JavaScriptFrame* frame) {
HandleScope scope(isolate);
Handle<SharedFunctionInfo> shared(function->shared());
Handle<ScopeInfo> scope_info(shared->scope_info());
if (!scope_info->HasContext()) return target;
// Third fill all context locals.
Handle<Context> frame_context(Context::cast(frame->context()));
Handle<Context> function_context(frame_context->declaration_context());
if (!ScopeInfo::CopyContextLocalsToScopeObject(scope_info, function_context,
target)) {
return MaybeHandle<JSObject>();
}
// 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<JSObject> ext(JSObject::cast(function_context->extension()));
Handle<FixedArray> 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<String> key(String::cast(keys->get(i)));
Handle<Object> 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<JSObject> MaterializeScriptScope(
Handle<GlobalObject> global) {
Isolate* isolate = global->GetIsolate();
Handle<ScriptContextTable> script_contexts(
global->native_context()->script_context_table());
Handle<JSObject> script_scope =
isolate->factory()->NewJSObject(isolate->object_function());
for (int context_index = 0; context_index < script_contexts->used();
context_index++) {
Handle<Context> context =
ScriptContextTable::GetContext(script_contexts, context_index);
Handle<ScopeInfo> scope_info(ScopeInfo::cast(context->extension()));
if (!ScopeInfo::CopyContextLocalsToScopeObject(scope_info, context,
script_scope)) {
return MaybeHandle<JSObject>();
}
}
return script_scope;
}
MUST_USE_RESULT static MaybeHandle<JSObject> MaterializeLocalScope(
Isolate* isolate, JavaScriptFrame* frame, int inlined_jsframe_index) {
FrameInspector frame_inspector(frame, inlined_jsframe_index, isolate);
Handle<JSFunction> function(JSFunction::cast(frame_inspector.GetFunction()));
Handle<JSObject> 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<ScopeInfo> scope_info,
Handle<Context> context,
Handle<String> variable_name,
Handle<Object> new_value) {
for (int i = 0; i < scope_info->ContextLocalCount(); i++) {
Handle<String> 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<String> variable_name,
Handle<Object> new_value) {
if (inlined_jsframe_index != 0 || frame->is_optimized()) {
// Optimized frames are not supported.
return false;
}
Handle<JSFunction> function(frame->function());
Handle<SharedFunctionInfo> shared(function->shared());
Handle<ScopeInfo> 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<Context> frame_context(Context::cast(frame->context()));
Handle<Context> 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<JSObject> ext(JSObject::cast(function_context->extension()));
Maybe<bool> 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<JSObject> MaterializeClosure(
Isolate* isolate, Handle<Context> context) {
DCHECK(context->IsFunctionContext());
Handle<SharedFunctionInfo> shared(context->closure()->shared());
Handle<ScopeInfo> scope_info(shared->scope_info());
// Allocate and initialize a JSObject with all the content of this function
// closure.
Handle<JSObject> 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<JSObject>();
}
// Finally copy any properties from the function context extension. This will
// be variables introduced by eval.
if (context->has_extension()) {
Handle<JSObject> ext(JSObject::cast(context->extension()));
Handle<FixedArray> 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<String> key(String::cast(keys->get(i)));
Handle<Object> 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> context,
Handle<String> variable_name,
Handle<Object> new_value) {
DCHECK(context->IsFunctionContext());
Handle<SharedFunctionInfo> shared(context->closure()->shared());
Handle<ScopeInfo> 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<JSObject> ext(JSObject::cast(context->extension()));
Maybe<bool> 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;
}
static bool SetBlockContextVariableValue(Handle<Context> block_context,
Handle<String> variable_name,
Handle<Object> new_value) {
DCHECK(block_context->IsBlockContext());
Handle<ScopeInfo> scope_info(ScopeInfo::cast(block_context->extension()));
return SetContextLocalValue(block_context->GetIsolate(), scope_info,
block_context, variable_name, new_value);
}
static bool SetScriptVariableValue(Handle<Context> context,
Handle<String> variable_name,
Handle<Object> new_value) {
Handle<ScriptContextTable> script_contexts(
context->global_object()->native_context()->script_context_table());
ScriptContextTable::LookupResult lookup_result;
if (ScriptContextTable::Lookup(script_contexts, variable_name,
&lookup_result)) {
Handle<Context> script_context = ScriptContextTable::GetContext(
script_contexts, lookup_result.context_index);
script_context->set(lookup_result.slot_index, *new_value);
return true;
}
return false;
}
// Create a plain JSObject which materializes the scope for the specified
// catch context.
MUST_USE_RESULT static MaybeHandle<JSObject> MaterializeCatchScope(
Isolate* isolate, Handle<Context> context) {
DCHECK(context->IsCatchContext());
Handle<String> name(String::cast(context->extension()));
Handle<Object> thrown_object(context->get(Context::THROWN_OBJECT_INDEX),
isolate);
Handle<JSObject> 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> context,
Handle<String> variable_name,
Handle<Object> new_value) {
DCHECK(context->IsCatchContext());
Handle<String> 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<JSObject> MaterializeBlockScope(
Isolate* isolate, Handle<Context> context) {
DCHECK(context->IsBlockContext());
Handle<ScopeInfo> 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<JSObject> block_scope =
isolate->factory()->NewJSObject(isolate->object_function());
// Fill all context locals.
if (!ScopeInfo::CopyContextLocalsToScopeObject(scope_info, context,
block_scope)) {
return MaybeHandle<JSObject>();
}
return block_scope;
}
// Create a plain JSObject which materializes the module scope for the specified
// module context.
MUST_USE_RESULT static MaybeHandle<JSObject> MaterializeModuleScope(
Isolate* isolate, Handle<Context> context) {
DCHECK(context->IsModuleContext());
Handle<ScopeInfo> scope_info(ScopeInfo::cast(context->extension()));
// Allocate and initialize a JSObject with all the members of the debugged
// module.
Handle<JSObject> module_scope =
isolate->factory()->NewJSObject(isolate->object_function());
// Fill all context locals.
if (!ScopeInfo::CopyContextLocalsToScopeObject(scope_info, context,
module_scope)) {
return MaybeHandle<JSObject>();
}
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,
ScopeTypeScript,
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),
seen_script_scope_(false),
failed_(false) {
// Catch the case when the debugger stops in an internal function.
Handle<SharedFunctionInfo> shared_info(function_->shared());
Handle<ScopeInfo> scope_info(shared_info->scope_info());
if (shared_info->script() == isolate->heap()->undefined_value()) {
while (context_->closure() == *function_) {
context_ = Handle<Context>(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<DebugInfo> 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>(context_->declaration_context(), isolate_);
} else {
while (context_->closure() == *function_) {
context_ = Handle<Context>(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<Script> script(Script::cast(shared_info->script()));
Scope* scope = NULL;
// Check whether we are in global, eval or function code.
Handle<ScopeInfo> scope_info(shared_info->scope_info());
if (scope_info->scope_type() != FUNCTION_SCOPE &&
scope_info->scope_type() != ARROW_SCOPE) {
// Global or eval code.
CompilationInfoWithZone info(script);
if (scope_info->scope_type() == SCRIPT_SCOPE) {
info.MarkAsGlobal();
} else {
DCHECK(scope_info->scope_type() == EVAL_SCOPE);
info.MarkAsEval();
info.SetContext(Handle<Context>(function_->context()));
}
if (Parser::Parse(&info) && Scope::Analyze(&info)) {
scope = info.function()->scope();
}
RetrieveScopeChain(scope, shared_info);
} else {
// Function code
CompilationInfoWithZone info(shared_info);
if (Parser::Parse(&info) && Scope::Analyze(&info)) {
scope = info.function()->scope();
}
RetrieveScopeChain(scope, shared_info);
}
}
}
ScopeIterator(Isolate* isolate, Handle<JSFunction> function)
: isolate_(isolate),
frame_(NULL),
inlined_jsframe_index_(0),
function_(function),
context_(function->context()),
seen_script_scope_(false),
failed_(false) {
if (function->IsBuiltin()) {
context_ = Handle<Context>();
}
}
// More scopes?
bool Done() {
DCHECK(!failed_);
return context_.is_null();
}
bool Failed() { return failed_; }
// Move to the next scope.
void Next() {
DCHECK(!failed_);
ScopeType scope_type = Type();
if (scope_type == ScopeTypeGlobal) {
// The global scope is always the last in the chain.
DCHECK(context_->IsNativeContext());
context_ = Handle<Context>();
return;
}
if (scope_type == ScopeTypeScript) seen_script_scope_ = true;
if (nested_scope_chain_.is_empty()) {
if (scope_type == ScopeTypeScript) {
if (context_->IsScriptContext()) {
context_ = Handle<Context>(context_->previous(), isolate_);
}
CHECK(context_->IsNativeContext());
} else {
context_ = Handle<Context>(context_->previous(), isolate_);
}
} else {
if (nested_scope_chain_.last()->HasContext()) {
DCHECK(context_->previous() != NULL);
context_ = Handle<Context>(context_->previous(), isolate_);
}
nested_scope_chain_.RemoveLast();
}
}
// Return the type of the current scope.
ScopeType Type() {
DCHECK(!failed_);
if (!nested_scope_chain_.is_empty()) {
Handle<ScopeInfo> scope_info = nested_scope_chain_.last();
switch (scope_info->scope_type()) {
case FUNCTION_SCOPE:
case ARROW_SCOPE:
DCHECK(context_->IsFunctionContext() || !scope_info->HasContext());
return ScopeTypeLocal;
case MODULE_SCOPE:
DCHECK(context_->IsModuleContext());
return ScopeTypeModule;
case SCRIPT_SCOPE:
DCHECK(context_->IsScriptContext() || context_->IsNativeContext());
return ScopeTypeScript;
case WITH_SCOPE:
DCHECK(context_->IsWithContext());
return ScopeTypeWith;
case CATCH_SCOPE:
DCHECK(context_->IsCatchContext());
return ScopeTypeCatch;
case BLOCK_SCOPE:
DCHECK(!scope_info->HasContext() || context_->IsBlockContext());
return ScopeTypeBlock;
case EVAL_SCOPE:
UNREACHABLE();
}
}
if (context_->IsNativeContext()) {
DCHECK(context_->global_object()->IsGlobalObject());
// If we are at the native context and have not yet seen script scope,
// fake it.
return seen_script_scope_ ? ScopeTypeGlobal : ScopeTypeScript;
}
if (context_->IsFunctionContext()) {
return ScopeTypeClosure;
}
if (context_->IsCatchContext()) {
return ScopeTypeCatch;
}
if (context_->IsBlockContext()) {
return ScopeTypeBlock;
}
if (context_->IsModuleContext()) {
return ScopeTypeModule;
}
if (context_->IsScriptContext()) {
return ScopeTypeScript;
}
DCHECK(context_->IsWithContext());
return ScopeTypeWith;
}
// Return the JavaScript object with the content of the current scope.
MaybeHandle<JSObject> ScopeObject() {
DCHECK(!failed_);
switch (Type()) {
case ScopeIterator::ScopeTypeGlobal:
return Handle<JSObject>(CurrentContext()->global_object());
case ScopeIterator::ScopeTypeScript:
return MaterializeScriptScope(
Handle<GlobalObject>(CurrentContext()->global_object()));
case ScopeIterator::ScopeTypeLocal:
// Materialize the content of the local scope into a JSObject.
DCHECK(nested_scope_chain_.length() == 1);
return MaterializeLocalScope(isolate_, frame_, inlined_jsframe_index_);
case ScopeIterator::ScopeTypeWith:
// Return the with object.
return Handle<JSObject>(JSObject::cast(CurrentContext()->extension()));
case ScopeIterator::ScopeTypeCatch:
return MaterializeCatchScope(isolate_, CurrentContext());
case ScopeIterator::ScopeTypeClosure:
// Materialize the content of the closure scope into a JSObject.
return MaterializeClosure(isolate_, CurrentContext());
case ScopeIterator::ScopeTypeBlock:
return MaterializeBlockScope(isolate_, CurrentContext());
case ScopeIterator::ScopeTypeModule:
return MaterializeModuleScope(isolate_, CurrentContext());
}
UNREACHABLE();
return Handle<JSObject>();
}
bool SetVariableValue(Handle<String> variable_name,
Handle<Object> new_value) {
DCHECK(!failed_);
switch (Type()) {
case ScopeIterator::ScopeTypeGlobal:
break;
case ScopeIterator::ScopeTypeLocal:
return SetLocalVariableValue(isolate_, frame_, inlined_jsframe_index_,
variable_name, new_value);
case ScopeIterator::ScopeTypeWith:
break;
case ScopeIterator::ScopeTypeCatch:
return SetCatchVariableValue(isolate_, CurrentContext(), variable_name,
new_value);
case ScopeIterator::ScopeTypeClosure:
return SetClosureVariableValue(isolate_, CurrentContext(),
variable_name, new_value);
case ScopeIterator::ScopeTypeScript:
return SetScriptVariableValue(CurrentContext(), variable_name,
new_value);
case ScopeIterator::ScopeTypeBlock:
return SetBlockContextVariableValue(CurrentContext(), variable_name,
new_value);
case ScopeIterator::ScopeTypeModule:
// TODO(2399): should we implement it?
break;
}
return false;
}
Handle<ScopeInfo> CurrentScopeInfo() {
DCHECK(!failed_);
if (!nested_scope_chain_.is_empty()) {
return nested_scope_chain_.last();
} else if (context_->IsBlockContext()) {
return Handle<ScopeInfo>(ScopeInfo::cast(context_->extension()));
} else if (context_->IsFunctionContext()) {
return Handle<ScopeInfo>(context_->closure()->shared()->scope_info());
}
return Handle<ScopeInfo>::null();
}
// Return the context for this scope. For the local context there might not
// be an actual context.
Handle<Context> CurrentContext() {
DCHECK(!failed_);
if (Type() == ScopeTypeGlobal || Type() == ScopeTypeScript ||
nested_scope_chain_.is_empty()) {
return context_;
} else if (nested_scope_chain_.last()->HasContext()) {
return context_;
} else {
return Handle<Context>();
}
}
#ifdef DEBUG
// Debug print of the content of the current scope.
void DebugPrint() {
OFStream os(stdout);
DCHECK(!failed_);
switch (Type()) {
case ScopeIterator::ScopeTypeGlobal:
os << "Global:\n";
CurrentContext()->Print(os);
break;
case ScopeIterator::ScopeTypeLocal: {
os << "Local:\n";
function_->shared()->scope_info()->Print();
if (!CurrentContext().is_null()) {
CurrentContext()->Print(os);
if (CurrentContext()->has_extension()) {
Handle<Object> extension(CurrentContext()->extension(), isolate_);
if (extension->IsJSContextExtensionObject()) {
extension->Print(os);
}
}
}
break;
}
case ScopeIterator::ScopeTypeWith:
os << "With:\n";
CurrentContext()->extension()->Print(os);
break;
case ScopeIterator::ScopeTypeCatch:
os << "Catch:\n";
CurrentContext()->extension()->Print(os);
CurrentContext()->get(Context::THROWN_OBJECT_INDEX)->Print(os);
break;
case ScopeIterator::ScopeTypeClosure:
os << "Closure:\n";
CurrentContext()->Print(os);
if (CurrentContext()->has_extension()) {
Handle<Object> extension(CurrentContext()->extension(), isolate_);
if (extension->IsJSContextExtensionObject()) {
extension->Print(os);
}
}
break;
case ScopeIterator::ScopeTypeScript:
os << "Script:\n";
CurrentContext()
->global_object()
->native_context()
->script_context_table()
->Print(os);
break;
default:
UNREACHABLE();
}
PrintF("\n");
}
#endif
private:
Isolate* isolate_;
JavaScriptFrame* frame_;
int inlined_jsframe_index_;
Handle<JSFunction> function_;
Handle<Context> context_;
List<Handle<ScopeInfo> > nested_scope_chain_;
bool seen_script_scope_;
bool failed_;
void RetrieveScopeChain(Scope* scope,
Handle<SharedFunctionInfo> shared_info) {
if (scope != NULL) {
int source_position = shared_info->code()->SourcePosition(frame_->pc());
scope->GetNestedScopeChain(&nested_scope_chain_, source_position);
} else {
// A failed reparse indicates that the preparser has diverged from the
// parser or that the preparse data given to the initial parse has been
// faulty. We fail in debug mode but in release mode we only provide the
// information we get from the context chain but nothing about
// completely stack allocated scopes or stack allocated locals.
// Or it could be due to stack overflow.
DCHECK(isolate_->has_pending_exception());
failed_ = true;
}
}
DISALLOW_IMPLICIT_CONSTRUCTORS(ScopeIterator);
};
RUNTIME_FUNCTION(Runtime_GetScopeCount) {
HandleScope scope(isolate);
DCHECK(args.length() == 2);
CONVERT_NUMBER_CHECKED(int, break_id, Int32, args[0]);
RUNTIME_ASSERT(isolate->debug()->CheckExecutionState(break_id));
CONVERT_SMI_ARG_CHECKED(wrapped_id, 1);
// Get the frame where the debugging is performed.
StackFrame::Id id = UnwrapFrameId(wrapped_id);
JavaScriptFrameIterator it(isolate, id);
JavaScriptFrame* frame = it.frame();
// Count the visible scopes.
int n = 0;
for (ScopeIterator it(isolate, frame, 0); !it.Done(); it.Next()) {
n++;
}
return Smi::FromInt(n);
}
// Returns the list of step-in positions (text offset) in a function of the
// stack frame in a range from the current debug break position to the end
// of the corresponding statement.
RUNTIME_FUNCTION(Runtime_GetStepInPositions) {
HandleScope scope(isolate);
DCHECK(args.length() == 2);
CONVERT_NUMBER_CHECKED(int, break_id, Int32, args[0]);
RUNTIME_ASSERT(isolate->debug()->CheckExecutionState(break_id));
CONVERT_SMI_ARG_CHECKED(wrapped_id, 1);
// Get the frame where the debugging is performed.
StackFrame::Id id = UnwrapFrameId(wrapped_id);
JavaScriptFrameIterator frame_it(isolate, id);
RUNTIME_ASSERT(!frame_it.done());
JavaScriptFrame* frame = frame_it.frame();
Handle<JSFunction> fun = Handle<JSFunction>(frame->function());
Handle<SharedFunctionInfo> shared = Handle<SharedFunctionInfo>(fun->shared());
if (!isolate->debug()->EnsureDebugInfo(shared, fun)) {
return isolate->heap()->undefined_value();
}
Handle<DebugInfo> debug_info = Debug::GetDebugInfo(shared);
int len = 0;
Handle<JSArray> array(isolate->factory()->NewJSArray(10));
// Find the break point where execution has stopped.
BreakLocationIterator break_location_iterator(debug_info,
ALL_BREAK_LOCATIONS);
break_location_iterator.FindBreakLocationFromAddress(frame->pc() - 1);
int current_statement_pos = break_location_iterator.statement_position();
while (!break_location_iterator.Done()) {
bool accept;
if (break_location_iterator.pc() > frame->pc()) {
accept = true;
} else {
StackFrame::Id break_frame_id = isolate->debug()->break_frame_id();
// The break point is near our pc. Could be a step-in possibility,
// that is currently taken by active debugger call.
if (break_frame_id == StackFrame::NO_ID) {
// We are not stepping.
accept = false;
} else {
JavaScriptFrameIterator additional_frame_it(isolate, break_frame_id);
// If our frame is a top frame and we are stepping, we can do step-in
// at this place.
accept = additional_frame_it.frame()->id() == id;
}
}
if (accept) {
if (break_location_iterator.IsStepInLocation(isolate)) {
Smi* position_value = Smi::FromInt(break_location_iterator.position());
RETURN_FAILURE_ON_EXCEPTION(
isolate, JSObject::SetElement(
array, len, Handle<Object>(position_value, isolate),
NONE, SLOPPY));
len++;
}
}
// Advance iterator.
break_location_iterator.Next();
if (current_statement_pos != break_location_iterator.statement_position()) {
break;
}
}
return *array;
}
static const int kScopeDetailsTypeIndex = 0;
static const int kScopeDetailsObjectIndex = 1;
static const int kScopeDetailsSize = 2;
MUST_USE_RESULT static MaybeHandle<JSObject> MaterializeScopeDetails(
Isolate* isolate, ScopeIterator* it) {
// Calculate the size of the result.
int details_size = kScopeDetailsSize;
Handle<FixedArray> details = isolate->factory()->NewFixedArray(details_size);
// Fill in scope details.
details->set(kScopeDetailsTypeIndex, Smi::FromInt(it->Type()));
Handle<JSObject> scope_object;
ASSIGN_RETURN_ON_EXCEPTION(isolate, scope_object, it->ScopeObject(),
JSObject);
details->set(kScopeDetailsObjectIndex, *scope_object);
return isolate->factory()->NewJSArrayWithElements(details);
}
// Return an array with scope details
// args[0]: number: break id
// args[1]: number: frame index
// args[2]: number: inlined frame index
// args[3]: number: scope index
//
// The array returned contains the following information:
// 0: Scope type
// 1: Scope object
RUNTIME_FUNCTION(Runtime_GetScopeDetails) {
HandleScope scope(isolate);
DCHECK(args.length() == 4);
CONVERT_NUMBER_CHECKED(int, break_id, Int32, args[0]);
RUNTIME_ASSERT(isolate->debug()->CheckExecutionState(break_id));
CONVERT_SMI_ARG_CHECKED(wrapped_id, 1);
CONVERT_NUMBER_CHECKED(int, inlined_jsframe_index, Int32, args[2]);
CONVERT_NUMBER_CHECKED(int, index, Int32, args[3]);
// Get the frame where the debugging is performed.
StackFrame::Id id = UnwrapFrameId(wrapped_id);
JavaScriptFrameIterator frame_it(isolate, id);
JavaScriptFrame* frame = frame_it.frame();
// Find the requested scope.
int n = 0;
ScopeIterator it(isolate, frame, inlined_jsframe_index);
for (; !it.Done() && n < index; it.Next()) {
n++;
}
if (it.Done()) {
return isolate->heap()->undefined_value();
}
Handle<JSObject> details;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, details,
MaterializeScopeDetails(isolate, &it));
return *details;
}
// Return an array of scope details
// args[0]: number: break id
// args[1]: number: frame index
// args[2]: number: inlined frame index
// args[3]: boolean: ignore nested scopes
//
// The array returned contains arrays with the following information:
// 0: Scope type
// 1: Scope object
RUNTIME_FUNCTION(Runtime_GetAllScopesDetails) {
HandleScope scope(isolate);
DCHECK(args.length() == 3 || args.length() == 4);
CONVERT_NUMBER_CHECKED(int, break_id, Int32, args[0]);
RUNTIME_ASSERT(isolate->debug()->CheckExecutionState(break_id));
CONVERT_SMI_ARG_CHECKED(wrapped_id, 1);
CONVERT_NUMBER_CHECKED(int, inlined_jsframe_index, Int32, args[2]);
bool ignore_nested_scopes = false;
if (args.length() == 4) {
CONVERT_BOOLEAN_ARG_CHECKED(flag, 3);
ignore_nested_scopes = flag;
}
// Get the frame where the debugging is performed.
StackFrame::Id id = UnwrapFrameId(wrapped_id);
JavaScriptFrameIterator frame_it(isolate, id);
JavaScriptFrame* frame = frame_it.frame();
List<Handle<JSObject> > result(4);
ScopeIterator it(isolate, frame, inlined_jsframe_index, ignore_nested_scopes);
for (; !it.Done(); it.Next()) {
Handle<JSObject> details;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, details,
MaterializeScopeDetails(isolate, &it));
result.Add(details);
}
Handle<FixedArray> array = isolate->factory()->NewFixedArray(result.length());
for (int i = 0; i < result.length(); ++i) {
array->set(i, *result[i]);
}
return *isolate->factory()->NewJSArrayWithElements(array);
}
RUNTIME_FUNCTION(Runtime_GetFunctionScopeCount) {
HandleScope scope(isolate);
DCHECK(args.length() == 1);
// Check arguments.
CONVERT_ARG_HANDLE_CHECKED(JSFunction, fun, 0);
// Count the visible scopes.
int n = 0;
for (ScopeIterator it(isolate, fun); !it.Done(); it.Next()) {
n++;
}
return Smi::FromInt(n);
}
RUNTIME_FUNCTION(Runtime_GetFunctionScopeDetails) {
HandleScope scope(isolate);
DCHECK(args.length() == 2);
// Check arguments.
CONVERT_ARG_HANDLE_CHECKED(JSFunction, fun, 0);
CONVERT_NUMBER_CHECKED(int, index, Int32, args[1]);
// Find the requested scope.
int n = 0;
ScopeIterator it(isolate, fun);
for (; !it.Done() && n < index; it.Next()) {
n++;
}
if (it.Done()) {
return isolate->heap()->undefined_value();
}
Handle<JSObject> details;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, details,
MaterializeScopeDetails(isolate, &it));
return *details;
}
static bool SetScopeVariableValue(ScopeIterator* it, int index,
Handle<String> variable_name,
Handle<Object> new_value) {
for (int n = 0; !it->Done() && n < index; it->Next()) {
n++;
}
if (it->Done()) {
return false;
}
return it->SetVariableValue(variable_name, new_value);
}
// Change variable value in closure or local scope
// args[0]: number or JsFunction: break id or function
// args[1]: number: frame index (when arg[0] is break id)
// args[2]: number: inlined frame index (when arg[0] is break id)
// args[3]: number: scope index
// args[4]: string: variable name
// args[5]: object: new value
//
// Return true if success and false otherwise
RUNTIME_FUNCTION(Runtime_SetScopeVariableValue) {
HandleScope scope(isolate);
DCHECK(args.length() == 6);
// Check arguments.
CONVERT_NUMBER_CHECKED(int, index, Int32, args[3]);
CONVERT_ARG_HANDLE_CHECKED(String, variable_name, 4);
CONVERT_ARG_HANDLE_CHECKED(Object, new_value, 5);
bool res;
if (args[0]->IsNumber()) {
CONVERT_NUMBER_CHECKED(int, break_id, Int32, args[0]);
RUNTIME_ASSERT(isolate->debug()->CheckExecutionState(break_id));
CONVERT_SMI_ARG_CHECKED(wrapped_id, 1);
CONVERT_NUMBER_CHECKED(int, inlined_jsframe_index, Int32, args[2]);
// Get the frame where the debugging is performed.
StackFrame::Id id = UnwrapFrameId(wrapped_id);
JavaScriptFrameIterator frame_it(isolate, id);
JavaScriptFrame* frame = frame_it.frame();
ScopeIterator it(isolate, frame, inlined_jsframe_index);
res = SetScopeVariableValue(&it, index, variable_name, new_value);
} else {
CONVERT_ARG_HANDLE_CHECKED(JSFunction, fun, 0);
ScopeIterator it(isolate, fun);
res = SetScopeVariableValue(&it, index, variable_name, new_value);
}
return isolate->heap()->ToBoolean(res);
}
RUNTIME_FUNCTION(Runtime_DebugPrintScopes) {
HandleScope scope(isolate);
DCHECK(args.length() == 0);
#ifdef DEBUG
// Print the scopes for the top frame.
StackFrameLocator locator(isolate);
JavaScriptFrame* frame = locator.FindJavaScriptFrame(0);
for (ScopeIterator it(isolate, frame, 0); !it.Done(); it.Next()) {
it.DebugPrint();
}
#endif
return isolate->heap()->undefined_value();
}
RUNTIME_FUNCTION(Runtime_GetThreadCount) {
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 archived V8 threads.
int n = 0;
for (ThreadState* thread = isolate->thread_manager()->FirstThreadStateInUse();
thread != NULL; thread = thread->Next()) {
n++;
}
// Total number of threads is current thread and archived threads.
return Smi::FromInt(n + 1);
}
static const int kThreadDetailsCurrentThreadIndex = 0;
static const int kThreadDetailsThreadIdIndex = 1;
static const int kThreadDetailsSize = 2;
// Return an array with thread details
// args[0]: number: break id
// args[1]: number: thread index
//
// The array returned contains the following information:
// 0: Is current thread?
// 1: Thread id
RUNTIME_FUNCTION(Runtime_GetThreadDetails) {
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]);
// Allocate array for result.
Handle<FixedArray> details =
isolate->factory()->NewFixedArray(kThreadDetailsSize);
// Thread index 0 is current thread.
if (index == 0) {
// Fill the details.
details->set(kThreadDetailsCurrentThreadIndex,
isolate->heap()->true_value());
details->set(kThreadDetailsThreadIdIndex,
Smi::FromInt(ThreadId::Current().ToInteger()));
} else {
// Find the thread with the requested index.
int n = 1;
ThreadState* thread = isolate->thread_manager()->FirstThreadStateInUse();
while (index != n && thread != NULL) {
thread = thread->Next();
n++;
}
if (thread == NULL) {
return isolate->heap()->undefined_value();
}
// Fill the details.
details->set(kThreadDetailsCurrentThreadIndex,
isolate->heap()->false_value());
details->set(kThreadDetailsThreadIdIndex,
Smi::FromInt(thread->id().ToInteger()));
}
// Convert to JS array and return.
return *isolate->factory()->NewJSArrayWithElements(details);
}
// Sets the disable break state
// args[0]: disable break state
RUNTIME_FUNCTION(Runtime_SetDisableBreak) {
HandleScope scope(isolate);
DCHECK(args.length() == 1);
CONVERT_BOOLEAN_ARG_CHECKED(disable_break, 0);
isolate->debug()->set_disable_break(disable_break);
return isolate->heap()->undefined_value();
}
static bool IsPositionAlignmentCodeCorrect(int alignment) {
return alignment == STATEMENT_ALIGNED || alignment == BREAK_POSITION_ALIGNED;
}
RUNTIME_FUNCTION(Runtime_GetBreakLocations) {
HandleScope scope(isolate);
DCHECK(args.length() == 2);
CONVERT_ARG_HANDLE_CHECKED(JSFunction, fun, 0);
CONVERT_NUMBER_CHECKED(int32_t, statement_aligned_code, Int32, args[1]);
if (!IsPositionAlignmentCodeCorrect(statement_aligned_code)) {
return isolate->ThrowIllegalOperation();
}
BreakPositionAlignment alignment =
static_cast<BreakPositionAlignment>(statement_aligned_code);
Handle<SharedFunctionInfo> shared(fun->shared());
// Find the number of break points
Handle<Object> break_locations =
Debug::GetSourceBreakLocations(shared, alignment);
if (break_locations->IsUndefined()) return isolate->heap()->undefined_value();
// Return array as JS array
return *isolate->factory()->NewJSArrayWithElements(
Handle<FixedArray>::cast(break_locations));
}
// Set a break point in a function.
// args[0]: function
// args[1]: number: break source position (within the function source)
// args[2]: number: break point object
RUNTIME_FUNCTION(Runtime_SetFunctionBreakPoint) {
HandleScope scope(isolate);
DCHECK(args.length() == 3);
CONVERT_ARG_HANDLE_CHECKED(JSFunction, function, 0);
CONVERT_NUMBER_CHECKED(int32_t, source_position, Int32, args[1]);
RUNTIME_ASSERT(source_position >= function->shared()->start_position() &&
source_position <= function->shared()->end_position());
CONVERT_ARG_HANDLE_CHECKED(Object, break_point_object_arg, 2);
// Set break point.
RUNTIME_ASSERT(isolate->debug()->SetBreakPoint(
function, break_point_object_arg, &source_position));
return Smi::FromInt(source_position);
}
// Changes the state of a break point in a script and returns source position
// where break point was set. NOTE: Regarding performance see the NOTE for
// GetScriptFromScriptData.
// args[0]: script to set break point in
// args[1]: number: break source position (within the script source)
// args[2]: number, breakpoint position alignment
// args[3]: number: break point object
RUNTIME_FUNCTION(Runtime_SetScriptBreakPoint) {
HandleScope scope(isolate);
DCHECK(args.length() == 4);
CONVERT_ARG_HANDLE_CHECKED(JSValue, wrapper, 0);
CONVERT_NUMBER_CHECKED(int32_t, source_position, Int32, args[1]);
RUNTIME_ASSERT(source_position >= 0);
CONVERT_NUMBER_CHECKED(int32_t, statement_aligned_code, Int32, args[2]);
CONVERT_ARG_HANDLE_CHECKED(Object, break_point_object_arg, 3);
if (!IsPositionAlignmentCodeCorrect(statement_aligned_code)) {
return isolate->ThrowIllegalOperation();
}
BreakPositionAlignment alignment =
static_cast<BreakPositionAlignment>(statement_aligned_code);
// Get the script from the script wrapper.
RUNTIME_ASSERT(wrapper->value()->IsScript());
Handle<Script> script(Script::cast(wrapper->value()));
// Set break point.
if (!isolate->debug()->SetBreakPointForScript(script, break_point_object_arg,
&source_position, alignment)) {
return isolate->heap()->undefined_value();
}
return Smi::FromInt(source_position);
}
// Clear a break point
// args[0]: number: break point object
RUNTIME_FUNCTION(Runtime_ClearBreakPoint) {
HandleScope scope(isolate);
DCHECK(args.length() == 1);
CONVERT_ARG_HANDLE_CHECKED(Object, break_point_object_arg, 0);
// Clear break point.
isolate->debug()->ClearBreakPoint(break_point_object_arg);
return isolate->heap()->undefined_value();
}
// Change the state of break on exceptions.
// args[0]: Enum value indicating whether to affect caught/uncaught exceptions.
// args[1]: Boolean indicating on/off.
RUNTIME_FUNCTION(Runtime_ChangeBreakOnException) {
HandleScope scope(isolate);
DCHECK(args.length() == 2);
CONVERT_NUMBER_CHECKED(uint32_t, type_arg, Uint32, args[0]);
CONVERT_BOOLEAN_ARG_CHECKED(enable, 1);
// If the number doesn't match an enum value, the ChangeBreakOnException
// function will default to affecting caught exceptions.
ExceptionBreakType type = static_cast<ExceptionBreakType>(type_arg);
// Update break point state.
isolate->debug()->ChangeBreakOnException(type, enable);
return isolate->heap()->undefined_value();
}
// Returns the state of break on exceptions
// args[0]: boolean indicating uncaught exceptions
RUNTIME_FUNCTION(Runtime_IsBreakOnException) {
HandleScope scope(isolate);
DCHECK(args.length() == 1);
CONVERT_NUMBER_CHECKED(uint32_t, type_arg, Uint32, args[0]);
ExceptionBreakType type = static_cast<ExceptionBreakType>(type_arg);
bool result = isolate->debug()->IsBreakOnException(type);
return Smi::FromInt(result);
}
// Prepare for stepping
// args[0]: break id for checking execution state
// args[1]: step action from the enumeration StepAction
// args[2]: number of times to perform the step, for step out it is the number
// of frames to step down.
RUNTIME_FUNCTION(Runtime_PrepareStep) {
HandleScope scope(isolate);
DCHECK(args.length() == 4);
CONVERT_NUMBER_CHECKED(int, break_id, Int32, args[0]);
RUNTIME_ASSERT(isolate->debug()->CheckExecutionState(break_id));
if (!args[1]->IsNumber() || !args[2]->IsNumber()) {
return isolate->Throw(isolate->heap()->illegal_argument_string());
}
CONVERT_NUMBER_CHECKED(int, wrapped_frame_id, Int32, args[3]);
StackFrame::Id frame_id;
if (wrapped_frame_id == 0) {
frame_id = StackFrame::NO_ID;
} else {
frame_id = UnwrapFrameId(wrapped_frame_id);
}
// Get the step action and check validity.
StepAction step_action = static_cast<StepAction>(NumberToInt32(args[1]));
if (step_action != StepIn && step_action != StepNext &&
step_action != StepOut && step_action != StepInMin &&
step_action != StepMin && step_action != StepFrame) {
return isolate->Throw(isolate->heap()->illegal_argument_string());
}
if (frame_id != StackFrame::NO_ID && step_action != StepNext &&
step_action != StepMin && step_action != StepOut) {
return isolate->ThrowIllegalOperation();
}
// Get the number of steps.
int step_count = NumberToInt32(args[2]);
if (step_count < 1) {
return isolate->Throw(isolate->heap()->illegal_argument_string());
}
// Clear all current stepping setup.
isolate->debug()->ClearStepping();
// Prepare step.
isolate->debug()->PrepareStep(static_cast<StepAction>(step_action),
step_count, frame_id);
return isolate->heap()->undefined_value();
}
// Clear all stepping set by PrepareStep.
RUNTIME_FUNCTION(Runtime_ClearStepping) {
HandleScope scope(isolate);
DCHECK(args.length() == 0);
isolate->debug()->ClearStepping();
return isolate->heap()->undefined_value();
}
// Helper function to find or create the arguments object for
// Runtime_DebugEvaluate.
MUST_USE_RESULT static MaybeHandle<JSObject> MaterializeArgumentsObject(
Isolate* isolate, Handle<JSObject> target, Handle<JSFunction> function) {
// Do not materialize the arguments object for eval or top-level code.
// Skip if "arguments" is already taken.
if (!function->shared()->is_function()) return target;
Maybe<bool> maybe = JSReceiver::HasOwnProperty(
target, isolate->factory()->arguments_string());
if (!maybe.has_value) return MaybeHandle<JSObject>();
if (maybe.value) return target;
// FunctionGetArguments can't throw an exception.
Handle<JSObject> arguments =
Handle<JSObject>::cast(Accessors::FunctionGetArguments(function));
Handle<String> arguments_str = isolate->factory()->arguments_string();
RETURN_ON_EXCEPTION(isolate, Runtime::DefineObjectProperty(
target, arguments_str, arguments, NONE),
JSObject);
return target;
}
// Compile and evaluate source for the given context.
static MaybeHandle<Object> DebugEvaluate(Isolate* isolate,
Handle<SharedFunctionInfo> outer_info,
Handle<Context> context,
Handle<Object> context_extension,
Handle<Object> receiver,
Handle<String> source) {
if (context_extension->IsJSObject()) {
Handle<JSObject> extension = Handle<JSObject>::cast(context_extension);
Handle<JSFunction> closure(context->closure(), isolate);
context = isolate->factory()->NewWithContext(closure, context, extension);
}
Handle<JSFunction> eval_fun;
ASSIGN_RETURN_ON_EXCEPTION(isolate, eval_fun,
Compiler::GetFunctionFromEval(
source, outer_info, context, SLOPPY,
NO_PARSE_RESTRICTION, RelocInfo::kNoPosition),
Object);
Handle<Object> result;
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, result);
// TODO(verwaest): This will crash when the global proxy is detached.
result = Handle<JSObject>::cast(PrototypeIterator::GetCurrent(iter));
}
// Clear the oneshot breakpoints so that the debugger does not step further.
isolate->debug()->ClearStepping();
return result;
}
static Handle<JSObject> NewJSObjectWithNullProto(Isolate* isolate) {
Handle<JSObject> result =
isolate->factory()->NewJSObject(isolate->object_function());
Handle<Map> new_map =
Map::Copy(Handle<Map>(result->map()), "ObjectWithNullProto");
new_map->SetPrototype(isolate->factory()->null_value());
JSObject::MigrateToMap(result, new_map);
return result;
}
// Evaluate a piece of JavaScript in the context of a stack frame for
// debugging. Things that need special attention are:
// - Parameters and stack-allocated locals need to be materialized. Altered
// values need to be written back to the stack afterwards.
// - The arguments object needs to materialized.
RUNTIME_FUNCTION(Runtime_DebugEvaluate) {
HandleScope scope(isolate);
// Check the execution state and decode arguments frame and source to be
// evaluated.
DCHECK(args.length() == 6);
CONVERT_NUMBER_CHECKED(int, break_id, Int32, args[0]);
RUNTIME_ASSERT(isolate->debug()->CheckExecutionState(break_id));
CONVERT_SMI_ARG_CHECKED(wrapped_id, 1);
CONVERT_NUMBER_CHECKED(int, inlined_jsframe_index, Int32, args[2]);
CONVERT_ARG_HANDLE_CHECKED(String, source, 3);
CONVERT_BOOLEAN_ARG_CHECKED(disable_break, 4);
CONVERT_ARG_HANDLE_CHECKED(Object, context_extension, 5);
// Handle the processing of break.
DisableBreak disable_break_scope(isolate->debug(), disable_break);
// Get the frame where the debugging is performed.
StackFrame::Id id = UnwrapFrameId(wrapped_id);
JavaScriptFrameIterator it(isolate, id);
JavaScriptFrame* frame = it.frame();
FrameInspector frame_inspector(frame, inlined_jsframe_index, isolate);
Handle<JSFunction> function(JSFunction::cast(frame_inspector.GetFunction()));
Handle<SharedFunctionInfo> outer_info(function->shared());
// Traverse the saved contexts chain to find the active context for the
// selected frame.
SaveContext* save = FindSavedContextForFrame(isolate, frame);
SaveContext savex(isolate);
isolate->set_context(*(save->context()));
// Materialize stack locals and the arguments object.
Handle<JSObject> materialized = NewJSObjectWithNullProto(isolate);
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, materialized,
MaterializeStackLocalsWithFrameInspector(isolate, materialized, function,
&frame_inspector));
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, materialized,
MaterializeArgumentsObject(isolate, materialized, function));
// At this point, the lookup chain may look like this:
// [inner context] -> [function stack]+[function context] -> [outer context]
// The function stack is not an actual context, it complements the function
// context. In order to have the same lookup chain when debug-evaluating,
// we materialize the stack and insert it into the context chain as a
// with-context before the function context.
// [inner context] -> [with context] -> [function context] -> [outer context]
// Ordering the with-context before the function context forces a dynamic
// lookup instead of a static lookup that could fail as the scope info is
// outdated and may expect variables to still be stack-allocated.
// Afterwards, we write changes to the with-context back to the stack
// and remove it from the context chain.
// This could cause lookup failures if debug-evaluate creates a closure that
// uses this temporary context chain.
Handle<Context> eval_context(Context::cast(frame_inspector.GetContext()));
DCHECK(!eval_context.is_null());
Handle<Context> function_context = eval_context;
Handle<Context> outer_context(function->context(), isolate);
Handle<Context> inner_context;
// We iterate to find the function's context. If the function has no
// context-allocated variables, we iterate until we hit the outer context.
while (!function_context->IsFunctionContext() &&
!function_context->IsScriptContext() &&
!function_context.is_identical_to(outer_context)) {
inner_context = function_context;
function_context = Handle<Context>(function_context->previous(), isolate);
}
Handle<Context> materialized_context = isolate->factory()->NewWithContext(
function, function_context, materialized);
if (inner_context.is_null()) {
// No inner context. The with-context is now inner-most.
eval_context = materialized_context;
} else {
inner_context->set_previous(*materialized_context);
}
Handle<Object> receiver(frame->receiver(), isolate);
MaybeHandle<Object> maybe_result = DebugEvaluate(
isolate, outer_info, eval_context, context_extension, receiver, source);
// Remove with-context if it was inserted in between.
if (!inner_context.is_null()) inner_context->set_previous(*function_context);
Handle<Object> result;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, result, maybe_result);
// Write back potential changes to materialized stack locals to the stack.
UpdateStackLocalsFromMaterializedObject(isolate, materialized, function,
frame, inlined_jsframe_index);
return *result;
}
RUNTIME_FUNCTION(Runtime_DebugEvaluateGlobal) {
HandleScope scope(isolate);
// Check the execution state and decode arguments frame and source to be
// evaluated.
DCHECK(args.length() == 4);
CONVERT_NUMBER_CHECKED(int, break_id, Int32, args[0]);
RUNTIME_ASSERT(isolate->debug()->CheckExecutionState(break_id));
CONVERT_ARG_HANDLE_CHECKED(String, source, 1);
CONVERT_BOOLEAN_ARG_CHECKED(disable_break, 2);
CONVERT_ARG_HANDLE_CHECKED(Object, context_extension, 3);
// Handle the processing of break.
DisableBreak disable_break_scope(isolate->debug(), disable_break);
// Enter the top context from before the debugger was invoked.
SaveContext save(isolate);
SaveContext* top = &save;
while (top != NULL && *top->context() == *isolate->debug()->debug_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> context = isolate->native_context();
Handle<JSObject> receiver(context->global_proxy());
Handle<SharedFunctionInfo> outer_info(context->closure()->shared(), isolate);
Handle<Object> result;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, result, DebugEvaluate(isolate, outer_info, context,
context_extension, receiver, source));
return *result;
}
RUNTIME_FUNCTION(Runtime_DebugGetLoadedScripts) {
HandleScope scope(isolate);
DCHECK(args.length() == 0);
// Fill the script objects.
Handle<FixedArray> instances = isolate->debug()->GetLoadedScripts();
// Convert the script objects to proper JS objects.
for (int i = 0; i < instances->length(); i++) {
Handle<Script> script = Handle<Script>(Script::cast(instances->get(i)));
// Get the script wrapper in a local handle before calling GetScriptWrapper,
// because using
// instances->set(i, *GetScriptWrapper(script))
// is unsafe as GetScriptWrapper might call GC and the C++ compiler might
// already have dereferenced the instances handle.
Handle<JSObject> wrapper = Script::GetWrapper(script);
instances->set(i, *wrapper);
}
// Return result as a JS array.
Handle<JSObject> result =
isolate->factory()->NewJSObject(isolate->array_function());
JSArray::SetContent(Handle<JSArray>::cast(result), instances);
return *result;
}
// Helper function used by Runtime_DebugReferencedBy below.
static int DebugReferencedBy(HeapIterator* iterator, JSObject* target,
Object* instance_filter, int max_references,
FixedArray* instances, int instances_size,
JSFunction* arguments_function) {
Isolate* isolate = target->GetIsolate();
SealHandleScope shs(isolate);
DisallowHeapAllocation no_allocation;
// Iterate the heap.
int count = 0;
JSObject* last = NULL;
HeapObject* heap_obj = NULL;
while (((heap_obj = iterator->next()) != NULL) &&
(max_references == 0 || count < max_references)) {
// Only look at all JSObjects.
if (heap_obj->IsJSObject()) {
// Skip context extension objects and argument arrays as these are
// checked in the context of functions using them.
JSObject* obj = JSObject::cast(heap_obj);
if (obj->IsJSContextExtensionObject() ||
obj->map()->constructor() == arguments_function) {
continue;
}
// Check if the JS object has a reference to the object looked for.
if (obj->ReferencesObject(target)) {
// Check instance filter if supplied. This is normally used to avoid
// references from mirror objects (see Runtime_IsInPrototypeChain).
if (!instance_filter->IsUndefined()) {
for (PrototypeIterator iter(isolate, obj); !iter.IsAtEnd();
iter.Advance()) {
if (iter.GetCurrent() == instance_filter) {
obj = NULL; // Don't add this object.
break;
}
}
}
if (obj != NULL) {
// Valid reference found add to instance array if supplied an update
// count.
if (instances != NULL && count < instances_size) {
instances->set(count, obj);
}
last = obj;
count++;
}
}
}
}
// Check for circular reference only. This can happen when the object is only
// referenced from mirrors and has a circular reference in which case the
// object is not really alive and would have been garbage collected if not
// referenced from the mirror.
if (count == 1 && last == target) {
count = 0;
}
// Return the number of referencing objects found.
return count;
}
// Scan the heap for objects with direct references to an object
// args[0]: the object to find references to
// args[1]: constructor function for instances to exclude (Mirror)
// args[2]: the the maximum number of objects to return
RUNTIME_FUNCTION(Runtime_DebugReferencedBy) {
HandleScope scope(isolate);
DCHECK(args.length() == 3);
// Check parameters.
CONVERT_ARG_HANDLE_CHECKED(JSObject, target, 0);
CONVERT_ARG_HANDLE_CHECKED(Object, instance_filter, 1);
RUNTIME_ASSERT(instance_filter->IsUndefined() ||
instance_filter->IsJSObject());
CONVERT_NUMBER_CHECKED(int32_t, max_references, Int32, args[2]);
RUNTIME_ASSERT(max_references >= 0);
// Get the constructor function for context extension and arguments array.
Handle<JSFunction> arguments_function(
JSFunction::cast(isolate->sloppy_arguments_map()->constructor()));
// Get the number of referencing objects.
int count;
// First perform a full GC in order to avoid dead objects and to make the heap
// iterable.
Heap* heap = isolate->heap();
heap->CollectAllGarbage(Heap::kMakeHeapIterableMask, "%DebugConstructedBy");
{
HeapIterator heap_iterator(heap);
count = DebugReferencedBy(&heap_iterator, *target, *instance_filter,
max_references, NULL, 0, *arguments_function);
}
// Allocate an array to hold the result.
Handle<FixedArray> instances = isolate->factory()->NewFixedArray(count);
// Fill the referencing objects.
{
HeapIterator heap_iterator(heap);
count = DebugReferencedBy(&heap_iterator, *target, *instance_filter,
max_references, *instances, count,
*arguments_function);
}
// Return result as JS array.
Handle<JSFunction> constructor = isolate->array_function();
Handle<JSObject> result = isolate->factory()->NewJSObject(constructor);
JSArray::SetContent(Handle<JSArray>::cast(result), instances);
return *result;
}
// Helper function used by Runtime_DebugConstructedBy below.
static int DebugConstructedBy(HeapIterator* iterator, JSFunction* constructor,
int max_references, FixedArray* instances,
int instances_size) {
DisallowHeapAllocation no_allocation;
// Iterate the heap.
int count = 0;
HeapObject* heap_obj = NULL;
while (((heap_obj = iterator->next()) != NULL) &&
(max_references == 0 || count < max_references)) {
// Only look at all JSObjects.
if (heap_obj->IsJSObject()) {
JSObject* obj = JSObject::cast(heap_obj);
if (obj->map()->constructor() == constructor) {
// Valid reference found add to instance array if supplied an update
// count.
if (instances != NULL && count < instances_size) {
instances->set(count, obj);
}
count++;
}
}
}
// Return the number of referencing objects found.
return count;
}
// Scan the heap for objects constructed by a specific function.
// args[0]: the constructor to find instances of
// args[1]: the the maximum number of objects to return
RUNTIME_FUNCTION(Runtime_DebugConstructedBy) {
HandleScope scope(isolate);
DCHECK(args.length() == 2);
// Check parameters.
CONVERT_ARG_HANDLE_CHECKED(JSFunction, constructor, 0);
CONVERT_NUMBER_CHECKED(int32_t, max_references, Int32, args[1]);
RUNTIME_ASSERT(max_references >= 0);
// Get the number of referencing objects.
int count;
// First perform a full GC in order to avoid dead objects and to make the heap
// iterable.
Heap* heap = isolate->heap();
heap->CollectAllGarbage(Heap::kMakeHeapIterableMask, "%DebugConstructedBy");
{
HeapIterator heap_iterator(heap);
count = DebugConstructedBy(&heap_iterator, *constructor, max_references,
NULL, 0);
}
// Allocate an array to hold the result.
Handle<FixedArray> instances = isolate->factory()->NewFixedArray(count);
// Fill the referencing objects.
{
HeapIterator heap_iterator2(heap);
count = DebugConstructedBy(&heap_iterator2, *constructor, max_references,
*instances, count);
}
// Return result as JS array.
Handle<JSFunction> array_function = isolate->array_function();
Handle<JSObject> result = isolate->factory()->NewJSObject(array_function);
JSArray::SetContent(Handle<JSArray>::cast(result), instances);
return *result;
}
// Find the effective prototype object as returned by __proto__.
// args[0]: the object to find the prototype for.
RUNTIME_FUNCTION(Runtime_DebugGetPrototype) {
HandleScope shs(isolate);
DCHECK(args.length() == 1);
CONVERT_ARG_HANDLE_CHECKED(JSObject, obj, 0);
return *Object::GetPrototypeSkipHiddenPrototypes(isolate, obj);
}
// Patches script source (should be called upon BeforeCompile event).
RUNTIME_FUNCTION(Runtime_DebugSetScriptSource) {
HandleScope scope(isolate);
DCHECK(args.length() == 2);
CONVERT_ARG_HANDLE_CHECKED(JSValue, script_wrapper, 0);
CONVERT_ARG_HANDLE_CHECKED(String, source, 1);
RUNTIME_ASSERT(script_wrapper->value()->IsScript());
Handle<Script> script(Script::cast(script_wrapper->value()));
int compilation_state = script->compilation_state();
RUNTIME_ASSERT(compilation_state == Script::COMPILATION_STATE_INITIAL);
script->set_source(*source);
return isolate->heap()->undefined_value();
}
RUNTIME_FUNCTION(Runtime_DebugDisassembleFunction) {
HandleScope scope(isolate);
#ifdef DEBUG
DCHECK(args.length() == 1);
// Get the function and make sure it is compiled.
CONVERT_ARG_HANDLE_CHECKED(JSFunction, func, 0);
if (!Compiler::EnsureCompiled(func, KEEP_EXCEPTION)) {
return isolate->heap()->exception();
}
OFStream os(stdout);
func->code()->Print(os);
os << std::endl;
#endif // DEBUG
return isolate->heap()->undefined_value();
}
RUNTIME_FUNCTION(Runtime_DebugDisassembleConstructor) {
HandleScope scope(isolate);
#ifdef DEBUG
DCHECK(args.length() == 1);
// Get the function and make sure it is compiled.
CONVERT_ARG_HANDLE_CHECKED(JSFunction, func, 0);
if (!Compiler::EnsureCompiled(func, KEEP_EXCEPTION)) {
return isolate->heap()->exception();
}
OFStream os(stdout);
func->shared()->construct_stub()->Print(os);
os << std::endl;
#endif // DEBUG
return isolate->heap()->undefined_value();
}
RUNTIME_FUNCTION(Runtime_FunctionGetInferredName) {
SealHandleScope shs(isolate);
DCHECK(args.length() == 1);
CONVERT_ARG_CHECKED(JSFunction, f, 0);
return f->shared()->inferred_name();
}
// A testing entry. Returns statement position which is the closest to
// source_position.
RUNTIME_FUNCTION(Runtime_GetFunctionCodePositionFromSource) {
HandleScope scope(isolate);
CHECK(isolate->debug()->live_edit_enabled());
DCHECK(args.length() == 2);
CONVERT_ARG_HANDLE_CHECKED(JSFunction, function, 0);
CONVERT_NUMBER_CHECKED(int32_t, source_position, Int32, args[1]);
Handle<Code> code(function->code(), isolate);
if (code->kind() != Code::FUNCTION &&
code->kind() != Code::OPTIMIZED_FUNCTION) {
return isolate->heap()->undefined_value();
}
RelocIterator it(*code, RelocInfo::ModeMask(RelocInfo::STATEMENT_POSITION));
int closest_pc = 0;
int distance = kMaxInt;
while (!it.done()) {
int statement_position = static_cast<int>(it.rinfo()->data());
// Check if this break point is closer that what was previously found.
if (source_position <= statement_position &&
statement_position - source_position < distance) {
closest_pc =
static_cast<int>(it.rinfo()->pc() - code->instruction_start());
distance = statement_position - source_position;
// Check whether we can't get any closer.
if (distance == 0) break;
}
it.next();
}
return Smi::FromInt(closest_pc);
}
// Calls specified function with or without entering the debugger.
// This is used in unit tests to run code as if debugger is entered or simply
// to have a stack with C++ frame in the middle.
RUNTIME_FUNCTION(Runtime_ExecuteInDebugContext) {
HandleScope scope(isolate);
DCHECK(args.length() == 2);
CONVERT_ARG_HANDLE_CHECKED(JSFunction, function, 0);
CONVERT_BOOLEAN_ARG_CHECKED(without_debugger, 1);
MaybeHandle<Object> maybe_result;
if (without_debugger) {
maybe_result = Execution::Call(isolate, function,
handle(function->global_proxy()), 0, NULL);
} else {
DebugScope debug_scope(isolate->debug());
maybe_result = Execution::Call(isolate, function,
handle(function->global_proxy()), 0, NULL);
}
Handle<Object> result;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, result, maybe_result);
return *result;
}
// Performs a GC.
// Presently, it only does a full GC.
RUNTIME_FUNCTION(Runtime_CollectGarbage) {
SealHandleScope shs(isolate);
DCHECK(args.length() == 1);
isolate->heap()->CollectAllGarbage(Heap::kNoGCFlags, "%CollectGarbage");
return isolate->heap()->undefined_value();
}
// Gets the current heap usage.
RUNTIME_FUNCTION(Runtime_GetHeapUsage) {
SealHandleScope shs(isolate);
DCHECK(args.length() == 0);
int usage = static_cast<int>(isolate->heap()->SizeOfObjects());
if (!Smi::IsValid(usage)) {
return *isolate->factory()->NewNumberFromInt(usage);
}
return Smi::FromInt(usage);
}
// Finds the script object from the script data. NOTE: This operation uses
// heap traversal to find the function generated for the source position
// for the requested break point. For lazily compiled functions several heap
// traversals might be required rendering this operation as a rather slow
// operation. However for setting break points which is normally done through
// some kind of user interaction the performance is not crucial.
RUNTIME_FUNCTION(Runtime_GetScript) {
HandleScope scope(isolate);
DCHECK(args.length() == 1);
CONVERT_ARG_HANDLE_CHECKED(String, script_name, 0);
Handle<Script> found;
Heap* heap = isolate->heap();
{
HeapIterator iterator(heap);
HeapObject* obj = NULL;
while ((obj = iterator.next()) != NULL) {
if (!obj->IsScript()) continue;
Script* script = Script::cast(obj);
if (!script->name()->IsString()) continue;
String* name = String::cast(script->name());
if (name->Equals(*script_name)) {
found = Handle<Script>(script, isolate);
break;
}
}
}
if (found.is_null()) return heap->undefined_value();
return *Script::GetWrapper(found);
}
// Check whether debugger and is about to step into the callback that is passed
// to a built-in function such as Array.forEach.
RUNTIME_FUNCTION(Runtime_DebugCallbackSupportsStepping) {
DCHECK(args.length() == 1);
Debug* debug = isolate->debug();
if (!debug->is_active() || !debug->IsStepping() ||
debug->last_step_action() != StepIn) {
return isolate->heap()->false_value();
}
CONVERT_ARG_CHECKED(Object, callback, 0);
// We do not step into the callback if it's a builtin or not even a function.
return isolate->heap()->ToBoolean(callback->IsJSFunction() &&
!JSFunction::cast(callback)->IsBuiltin());
}
// Set one shot breakpoints for the callback function that is passed to a
// built-in function such as Array.forEach to enable stepping into the callback.
RUNTIME_FUNCTION(Runtime_DebugPrepareStepInIfStepping) {
DCHECK(args.length() == 1);
Debug* debug = isolate->debug();
if (!debug->IsStepping()) return isolate->heap()->undefined_value();
HandleScope scope(isolate);
CONVERT_ARG_HANDLE_CHECKED(Object, object, 0);
RUNTIME_ASSERT(object->IsJSFunction() || object->IsJSGeneratorObject());
Handle<JSFunction> fun;
if (object->IsJSFunction()) {
fun = Handle<JSFunction>::cast(object);
} else {
fun = Handle<JSFunction>(
Handle<JSGeneratorObject>::cast(object)->function(), isolate);
}
// When leaving the function, step out has been activated, but not performed
// if we do not leave the builtin. To be able to step into the function
// again, we need to clear the step out at this point.
debug->ClearStepOut();
debug->FloodWithOneShot(fun);
return isolate->heap()->undefined_value();
}
RUNTIME_FUNCTION(Runtime_DebugPushPromise) {
DCHECK(args.length() == 1);
HandleScope scope(isolate);
CONVERT_ARG_HANDLE_CHECKED(JSObject, promise, 0);
isolate->PushPromise(promise);
return isolate->heap()->undefined_value();
}
RUNTIME_FUNCTION(Runtime_DebugPopPromise) {
DCHECK(args.length() == 0);
SealHandleScope shs(isolate);
isolate->PopPromise();
return isolate->heap()->undefined_value();
}
RUNTIME_FUNCTION(Runtime_DebugPromiseEvent) {
DCHECK(args.length() == 1);
HandleScope scope(isolate);
CONVERT_ARG_HANDLE_CHECKED(JSObject, data, 0);
isolate->debug()->OnPromiseEvent(data);
return isolate->heap()->undefined_value();
}
RUNTIME_FUNCTION(Runtime_DebugAsyncTaskEvent) {
DCHECK(args.length() == 1);
HandleScope scope(isolate);
CONVERT_ARG_HANDLE_CHECKED(JSObject, data, 0);
isolate->debug()->OnAsyncTaskEvent(data);
return isolate->heap()->undefined_value();
}
RUNTIME_FUNCTION(RuntimeReference_DebugIsActive) {
SealHandleScope shs(isolate);
return Smi::FromInt(isolate->debug()->is_active());
}
RUNTIME_FUNCTION(RuntimeReference_DebugBreakInOptimizedCode) {
UNIMPLEMENTED();
return NULL;
}
}
} // namespace v8::internal