Index: src/runtime/runtime-debug.cc |
diff --git a/src/runtime/runtime-debug.cc b/src/runtime/runtime-debug.cc |
index 94ac074de3ec0b44d0a3efc4cd76710d7d718702..d23d0e2322e983aa019cfa3364937df4cd52d324 100644 |
--- a/src/runtime/runtime-debug.cc |
+++ b/src/runtime/runtime-debug.cc |
@@ -686,17 +686,16 @@ static bool ParameterIsShadowedByContextLocal(Handle<ScopeInfo> info, |
// frame. |
MUST_USE_RESULT |
static MaybeHandle<JSObject> MaterializeStackLocalsWithFrameInspector( |
- Isolate* isolate, Handle<JSObject> target, Handle<JSFunction> function, |
+ Isolate* isolate, Handle<JSObject> target, Handle<ScopeInfo> scope_info, |
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; |
+ DCHECK_NOT_NULL(frame_inspector); |
+ |
HandleScope scope(isolate); |
Handle<Object> value(i < frame_inspector->GetParametersCount() |
? frame_inspector->GetParameter(i) |
@@ -713,8 +712,12 @@ static MaybeHandle<JSObject> MaterializeStackLocalsWithFrameInspector( |
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; |
+ Handle<Object> value( |
+ frame_inspector->GetExpression(scope_info->StackLocalIndex(i)), |
+ isolate); |
+ if (value->IsTheHole()) { |
+ value = isolate->factory()->undefined_value(); |
+ } |
RETURN_ON_EXCEPTION(isolate, Runtime::SetObjectProperty( |
isolate, target, name, value, SLOPPY), |
@@ -724,12 +727,21 @@ static MaybeHandle<JSObject> MaterializeStackLocalsWithFrameInspector( |
return target; |
} |
+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()); |
+ |
+ return MaterializeStackLocalsWithFrameInspector(isolate, target, scope_info, |
+ frame_inspector); |
+} |
+ |
-static void UpdateStackLocalsFromMaterializedObject(Isolate* isolate, |
- Handle<JSObject> target, |
- Handle<JSFunction> function, |
- JavaScriptFrame* frame, |
- int inlined_jsframe_index) { |
+static void UpdateStackLocalsFromMaterializedObject( |
+ Isolate* isolate, Handle<JSObject> target, Handle<ScopeInfo> scope_info, |
+ 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 |
@@ -737,9 +749,6 @@ static void UpdateStackLocalsFromMaterializedObject(Isolate* isolate, |
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. |
@@ -756,12 +765,13 @@ static void UpdateStackLocalsFromMaterializedObject(Isolate* isolate, |
// Stack locals. |
for (int i = 0; i < scope_info->StackLocalCount(); ++i) { |
if (scope_info->LocalIsSynthetic(i)) continue; |
- if (frame->GetExpression(i)->IsTheHole()) continue; |
+ int index = scope_info->StackLocalIndex(i); |
+ if (frame->GetExpression(index)->IsTheHole()) continue; |
HandleScope scope(isolate); |
Handle<Object> value = Object::GetPropertyOrElement( |
target, handle(scope_info->StackLocalName(i), |
isolate)).ToHandleChecked(); |
- frame->SetExpression(i, *value); |
+ frame->SetExpression(index, *value); |
} |
} |
@@ -903,7 +913,7 @@ static bool SetLocalVariableValue(Isolate* isolate, JavaScriptFrame* frame, |
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); |
+ frame->SetExpression(scope_info->StackLocalIndex(i), *new_value); |
return true; |
} |
} |
@@ -940,6 +950,30 @@ static bool SetLocalVariableValue(Isolate* isolate, JavaScriptFrame* frame, |
} |
+static bool SetBlockVariableValue(Isolate* isolate, |
+ Handle<Context> block_context, |
+ Handle<ScopeInfo> scope_info, |
+ JavaScriptFrame* frame, |
+ Handle<String> variable_name, |
+ Handle<Object> new_value) { |
+ if (frame != nullptr) { |
+ for (int i = 0; i < scope_info->StackLocalCount(); ++i) { |
+ HandleScope scope(isolate); |
+ if (String::Equals(handle(scope_info->StackLocalName(i)), |
+ variable_name)) { |
+ frame->SetExpression(scope_info->StackLocalIndex(i), *new_value); |
+ return true; |
+ } |
+ } |
+ } |
+ if (!block_context.is_null()) { |
+ return SetContextLocalValue(block_context->GetIsolate(), scope_info, |
+ block_context, variable_name, new_value); |
+ } |
+ return false; |
+} |
+ |
+ |
// Create a plain JSObject which materializes the closure content for the |
// context. |
MUST_USE_RESULT static MaybeHandle<JSObject> MaterializeClosure( |
@@ -1020,17 +1054,6 @@ static bool SetClosureVariableValue(Isolate* isolate, Handle<Context> context, |
} |
-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) { |
@@ -1082,19 +1105,27 @@ static bool SetCatchVariableValue(Isolate* isolate, Handle<Context> context, |
// 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. |
+ Isolate* isolate, Handle<ScopeInfo> scope_info, Handle<Context> context, |
+ JavaScriptFrame* frame, int inlined_jsframe_index) { |
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>(); |
+ if (frame != nullptr) { |
+ FrameInspector frame_inspector(frame, inlined_jsframe_index, isolate); |
+ RETURN_ON_EXCEPTION(isolate, |
+ MaterializeStackLocalsWithFrameInspector( |
+ isolate, block_scope, scope_info, &frame_inspector), |
+ JSObject); |
+ } |
+ |
+ if (!context.is_null()) { |
+ Handle<ScopeInfo> scope_info_from_context( |
+ ScopeInfo::cast(context->extension())); |
+ // Fill all context locals. |
+ if (!ScopeInfo::CopyContextLocalsToScopeObject(scope_info_from_context, |
+ context, block_scope)) { |
+ return MaybeHandle<JSObject>(); |
+ } |
} |
return block_scope; |
@@ -1361,8 +1392,20 @@ class ScopeIterator { |
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::ScopeTypeBlock: { |
+ if (!nested_scope_chain_.is_empty()) { |
+ // this is a block scope on the stack. |
+ Handle<ScopeInfo> scope_info = nested_scope_chain_.last(); |
+ Handle<Context> context = scope_info->HasContext() |
+ ? CurrentContext() |
+ : Handle<Context>::null(); |
+ return MaterializeBlockScope(isolate_, scope_info, context, frame_, |
+ inlined_jsframe_index_); |
+ } else { |
+ return MaterializeBlockScope(isolate_, Handle<ScopeInfo>::null(), |
+ CurrentContext(), nullptr, 0); |
+ } |
+ } |
case ScopeIterator::ScopeTypeModule: |
return MaterializeModuleScope(isolate_, CurrentContext()); |
} |
@@ -1370,6 +1413,16 @@ class ScopeIterator { |
return Handle<JSObject>(); |
} |
+ bool HasContext() { |
+ ScopeType type = Type(); |
+ if (type == ScopeTypeBlock || type == ScopeTypeLocal) { |
+ if (!nested_scope_chain_.is_empty()) { |
+ return nested_scope_chain_.last()->HasContext(); |
+ } |
+ } |
+ return true; |
+ } |
+ |
bool SetVariableValue(Handle<String> variable_name, |
Handle<Object> new_value) { |
DCHECK(!failed_); |
@@ -1391,8 +1444,9 @@ class ScopeIterator { |
return SetScriptVariableValue(CurrentContext(), variable_name, |
new_value); |
case ScopeIterator::ScopeTypeBlock: |
- return SetBlockContextVariableValue(CurrentContext(), variable_name, |
- new_value); |
+ return SetBlockVariableValue( |
+ isolate_, HasContext() ? CurrentContext() : Handle<Context>::null(), |
+ CurrentScopeInfo(), frame_, variable_name, new_value); |
case ScopeIterator::ScopeTypeModule: |
// TODO(2399): should we implement it? |
break; |
@@ -2188,6 +2242,179 @@ static Handle<JSObject> NewJSObjectWithNullProto(Isolate* isolate) { |
} |
+namespace { |
+ |
+// This class builds a context chain for evaluation of expressions |
+// in debugger. |
+// The scope chain leading up to a breakpoint where evaluation occurs |
+// looks like: |
+// - [a mix of with, catch and block scopes] |
+// - [function stack + context] |
+// - [outer context] |
+// The builder materializes all stack variables into properties of objects; |
+// the expression is then evaluated as if it is inside a series of 'with' |
+// statements using those objects. To this end, the builder builds a new |
+// context chain, based on a scope chain: |
+// - every With and Catch scope begets a cloned context |
+// - Block scope begets one or two contexts: |
+// - if a block has context-allocated varaibles, its context is cloned |
+// - stack locals are materizalized as a With context |
+// - Local scope begets a With context for materizalized locals, chained to |
+// original function context. Original function context is the end of |
+// the chain. |
+class EvaluationContextBuilder { |
+ public: |
+ EvaluationContextBuilder(Isolate* isolate, JavaScriptFrame* frame, |
+ int inlined_jsframe_index) |
+ : isolate_(isolate), |
+ frame_(frame), |
+ inlined_jsframe_index_(inlined_jsframe_index) { |
+ FrameInspector frame_inspector(frame, inlined_jsframe_index, isolate); |
+ Handle<JSFunction> function = |
+ handle(JSFunction::cast(frame_inspector.GetFunction())); |
+ Handle<Context> outer_context = handle(function->context(), isolate); |
+ outer_info_ = handle(function->shared()); |
+ Handle<Context> inner_context; |
+ |
+ bool stop = false; |
+ for (ScopeIterator it(isolate, frame, inlined_jsframe_index); |
+ !it.Failed() && !it.Done() && !stop; it.Next()) { |
+ ScopeIterator::ScopeType scope_type = it.Type(); |
+ |
+ if (scope_type == ScopeIterator::ScopeTypeLocal) { |
+ Handle<JSObject> materialized_function = |
+ NewJSObjectWithNullProto(isolate); |
+ |
+ if (!MaterializeStackLocalsWithFrameInspector( |
+ isolate, materialized_function, function, &frame_inspector) |
+ .ToHandle(&materialized_function)) |
+ return; |
+ |
+ if (!MaterializeArgumentsObject(isolate, materialized_function, |
+ function) |
+ .ToHandle(&materialized_function)) |
+ return; |
+ |
+ Handle<Context> parent_context = |
+ it.HasContext() ? it.CurrentContext() : outer_context; |
+ Handle<Context> with_context = isolate->factory()->NewWithContext( |
+ function, parent_context, materialized_function); |
+ |
+ ContextChainElement context_chain_element; |
+ context_chain_element.original_context = it.CurrentContext(); |
+ context_chain_element.materialized_object = materialized_function; |
+ context_chain_element.scope_info = it.CurrentScopeInfo(); |
+ context_chain_.Add(context_chain_element); |
+ |
+ stop = true; |
+ RecordContextsInChain(&inner_context, with_context, with_context); |
+ } else if (scope_type == ScopeIterator::ScopeTypeCatch || |
+ scope_type == ScopeIterator::ScopeTypeWith) { |
+ Handle<Context> cloned_context = |
+ Handle<Context>::cast(FixedArray::CopySize( |
+ it.CurrentContext(), it.CurrentContext()->length())); |
+ |
+ ContextChainElement context_chain_element; |
+ context_chain_element.original_context = it.CurrentContext(); |
+ context_chain_element.cloned_context = cloned_context; |
+ context_chain_.Add(context_chain_element); |
+ |
+ RecordContextsInChain(&inner_context, cloned_context, cloned_context); |
+ } else if (scope_type == ScopeIterator::ScopeTypeBlock) { |
+ Handle<JSObject> materialized_object = |
+ NewJSObjectWithNullProto(isolate); |
+ if (!MaterializeStackLocalsWithFrameInspector( |
+ isolate, materialized_object, it.CurrentScopeInfo(), |
+ &frame_inspector).ToHandle(&materialized_object)) |
+ return; |
+ if (it.HasContext()) { |
+ Handle<Context> cloned_context = |
+ Handle<Context>::cast(FixedArray::CopySize( |
+ it.CurrentContext(), it.CurrentContext()->length())); |
+ Handle<Context> with_context = isolate->factory()->NewWithContext( |
+ function, cloned_context, materialized_object); |
+ |
+ ContextChainElement context_chain_element; |
+ context_chain_element.original_context = it.CurrentContext(); |
+ context_chain_element.cloned_context = cloned_context; |
+ context_chain_element.materialized_object = materialized_object; |
+ context_chain_element.scope_info = it.CurrentScopeInfo(); |
+ context_chain_.Add(context_chain_element); |
+ |
+ RecordContextsInChain(&inner_context, cloned_context, with_context); |
+ } else { |
+ Handle<Context> with_context = isolate->factory()->NewWithContext( |
+ function, outer_context, materialized_object); |
+ |
+ ContextChainElement context_chain_element; |
+ context_chain_element.materialized_object = materialized_object; |
+ context_chain_element.scope_info = it.CurrentScopeInfo(); |
+ context_chain_.Add(context_chain_element); |
+ |
+ RecordContextsInChain(&inner_context, with_context, with_context); |
+ } |
+ } else { |
+ stop = true; |
+ } |
+ } |
+ if (innermost_context_.is_null()) { |
+ innermost_context_ = outer_context; |
+ } |
+ DCHECK(!innermost_context_.is_null()); |
+ } |
+ |
+ void UpdateVariables() { |
+ for (int i = 0; i < context_chain_.length(); i++) { |
+ ContextChainElement element = context_chain_[i]; |
+ if (!element.original_context.is_null() && |
+ !element.cloned_context.is_null()) { |
+ Handle<Context> cloned_context = element.cloned_context; |
+ cloned_context->CopyTo( |
+ Context::MIN_CONTEXT_SLOTS, *element.original_context, |
+ Context::MIN_CONTEXT_SLOTS, |
+ cloned_context->length() - Context::MIN_CONTEXT_SLOTS); |
+ } |
+ if (!element.materialized_object.is_null()) { |
+ // Write back potential changes to materialized stack locals to the |
+ // stack. |
+ UpdateStackLocalsFromMaterializedObject( |
+ isolate_, element.materialized_object, element.scope_info, frame_, |
+ inlined_jsframe_index_); |
+ } |
+ } |
+ } |
+ |
+ Handle<Context> innermost_context() const { return innermost_context_; } |
+ Handle<SharedFunctionInfo> outer_info() const { return outer_info_; } |
+ |
+ private: |
+ struct ContextChainElement { |
+ Handle<Context> original_context; |
+ Handle<Context> cloned_context; |
+ Handle<JSObject> materialized_object; |
+ Handle<ScopeInfo> scope_info; |
+ }; |
+ |
+ void RecordContextsInChain(Handle<Context>* inner_context, |
+ Handle<Context> first, Handle<Context> last) { |
+ if (!inner_context->is_null()) { |
+ (*inner_context)->set_previous(*last); |
+ } else { |
+ innermost_context_ = last; |
+ } |
+ *inner_context = first; |
+ } |
+ |
+ Handle<SharedFunctionInfo> outer_info_; |
+ Handle<Context> innermost_context_; |
+ List<ContextChainElement> context_chain_; |
+ Isolate* isolate_; |
+ JavaScriptFrame* frame_; |
+ int inlined_jsframe_index_; |
+}; |
+} |
+ |
+ |
// 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 |
@@ -2224,100 +2451,22 @@ RUNTIME_FUNCTION(Runtime_DebugEvaluate) { |
isolate->set_context(*(save->context())); |
// Materialize stack locals and the arguments object. |
- Handle<JSObject> materialized; |
- Handle<JSFunction> function; |
- Handle<SharedFunctionInfo> outer_info; |
- Handle<Context> eval_context; |
- |
- // We need to limit the lifetime of the FrameInspector because evaluation can |
- // call arbitrary code and only one FrameInspector can be active at a time. |
- { |
- FrameInspector frame_inspector(frame, inlined_jsframe_index, isolate); |
- materialized = NewJSObjectWithNullProto(isolate); |
- function = handle(JSFunction::cast(frame_inspector.GetFunction())); |
- outer_info = handle(function->shared()); |
- eval_context = handle(Context::cast(frame_inspector.GetContext())); |
- 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: |
- // - clone inner context |
- // - materialize the stack and insert it into the context chain as a |
- // with-context before the function context. |
- // [inner context clone] -> [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 |
- // write changes in cloned contexts back to original contexts. |
- |
- DCHECK(!eval_context.is_null()); |
- Handle<Context> function_context = eval_context; |
- Handle<Context> outer_context(function->context(), isolate); |
- Handle<Context> inner_context; |
- Handle<Context> innermost_context; |
- |
- // We iterate to find the function's context, cloning until we hit it. |
- // 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)) { |
- Handle<Context> clone = Handle<Context>::cast( |
- FixedArray::CopySize(function_context, function_context->length())); |
- if (!inner_context.is_null()) { |
- inner_context->set_previous(*clone); |
- } else { |
- innermost_context = clone; |
- } |
- inner_context = clone; |
- function_context = Handle<Context>(function_context->previous(), isolate); |
+ EvaluationContextBuilder context_builder(isolate, frame, |
+ inlined_jsframe_index); |
+ if (isolate->has_pending_exception()) { |
+ return isolate->heap()->exception(); |
} |
- 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. |
- innermost_context = materialized_context; |
- } else { |
- inner_context->set_previous(*materialized_context); |
- } |
Handle<Object> receiver(frame->receiver(), isolate); |
- MaybeHandle<Object> maybe_result = |
- DebugEvaluate(isolate, outer_info, innermost_context, context_extension, |
- receiver, source); |
+ MaybeHandle<Object> maybe_result = DebugEvaluate( |
+ isolate, context_builder.outer_info(), |
+ context_builder.innermost_context(), context_extension, receiver, source); |
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); |
- |
- while (!innermost_context.is_identical_to(materialized_context)) { |
- DCHECK(eval_context->map() == innermost_context->map()); |
- innermost_context->CopyTo( |
- Context::MIN_CONTEXT_SLOTS, *eval_context, Context::MIN_CONTEXT_SLOTS, |
- innermost_context->length() - Context::MIN_CONTEXT_SLOTS); |
- innermost_context = handle(innermost_context->previous(), isolate); |
- eval_context = handle(eval_context->previous(), isolate); |
- } |
- |
+ context_builder.UpdateVariables(); |
return *result; |
} |