// Copyright 2015 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "src/debug/debug-evaluate.h" #include "src/accessors.h" #include "src/contexts.h" #include "src/debug/debug.h" #include "src/debug/debug-frames.h" #include "src/debug/debug-scopes.h" #include "src/frames-inl.h" #include "src/isolate-inl.h" namespace v8 { namespace internal { static inline bool IsDebugContext(Isolate* isolate, Context* context) { return context->native_context() == *isolate->debug()->debug_context(); } MaybeHandle<Object> DebugEvaluate::Global( Isolate* isolate, Handle<String> source, bool disable_break, Handle<HeapObject> context_extension) { // 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 && IsDebugContext(isolate, *top->context())) { top = top->prev(); } if (top != NULL) isolate->set_context(*top->context()); // Get the native context now set to the top context from before the // debugger was invoked. Handle<Context> context = isolate->native_context(); Handle<JSObject> receiver(context->global_proxy()); Handle<SharedFunctionInfo> outer_info(context->closure()->shared(), isolate); return Evaluate(isolate, outer_info, context, context_extension, receiver, source); } MaybeHandle<Object> DebugEvaluate::Local(Isolate* isolate, StackFrame::Id frame_id, int inlined_jsframe_index, Handle<String> source, bool disable_break, Handle<HeapObject> context_extension) { // Handle the processing of break. DisableBreak disable_break_scope(isolate->debug(), disable_break); // Get the frame where the debugging is performed. JavaScriptFrameIterator it(isolate, frame_id); JavaScriptFrame* frame = it.frame(); // Traverse the saved contexts chain to find the active context for the // selected frame. SaveContext* save = DebugFrameHelper::FindSavedContextForFrame(isolate, frame); SaveContext savex(isolate); isolate->set_context(*(save->context())); // This is not a lot different than DebugEvaluate::Global, except that // variables accessible by the function we are evaluating from are // materialized and included on top of the native context. Changes to // the materialized object are written back afterwards. // Note that the native context is taken from the original context chain, // which may not be the current native context of the isolate. ContextBuilder context_builder(isolate, frame, inlined_jsframe_index); if (isolate->has_pending_exception()) return MaybeHandle<Object>(); Handle<Context> context = context_builder.native_context(); Handle<JSObject> receiver(context->global_proxy()); MaybeHandle<Object> maybe_result = Evaluate( isolate, context_builder.outer_info(), context_builder.innermost_context(), context_extension, receiver, source); if (!maybe_result.is_null() && !FLAG_debug_eval_readonly_locals) { context_builder.UpdateValues(); } return maybe_result; } // Compile and evaluate source for the given context. MaybeHandle<Object> DebugEvaluate::Evaluate( Isolate* isolate, Handle<SharedFunctionInfo> outer_info, Handle<Context> context, Handle<HeapObject> 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, Handle<JSGlobalProxy>::cast(result)); // TODO(verwaest): This will crash when the global proxy is detached. result = PrototypeIterator::GetCurrent<JSObject>(iter); } return result; } DebugEvaluate::ContextBuilder::ContextBuilder(Isolate* isolate, JavaScriptFrame* frame, int inlined_jsframe_index) : isolate_(isolate), frame_(frame), inlined_jsframe_index_(inlined_jsframe_index) { FrameInspector frame_inspector(frame, inlined_jsframe_index, isolate); Handle<JSFunction> local_function = Handle<JSFunction>::cast(frame_inspector.GetFunction()); Handle<Context> outer_context(local_function->context()); native_context_ = Handle<Context>(outer_context->native_context()); Handle<JSFunction> global_function(native_context_->closure()); outer_info_ = handle(global_function->shared()); Handle<Context> inner_context; bool stop = false; // Iterate the original context chain to create a context chain that reflects // our needs. The original context chain may look like this: // <native context> <outer contexts> <function context> <inner contexts> // In the resulting context chain, we want to materialize the receiver, // the parameters of the current function, the stack locals. We only // materialize context variables that the function already references, // because only for those variables we can be sure that they will be resolved // correctly. Variables that are not referenced by the function may be // context-allocated and thus accessible, but may be shadowed by stack- // allocated variables and the resolution would be incorrect. // The result will look like this: // <native context> <receiver context> // <materialized stack and accessible context vars> <inner contexts> // All contexts use the closure of the native context, since there is no // function context in the chain. Variables that cannot be resolved are // bound to toplevel (script contexts or global object). // Once debug-evaluate has been executed, the changes to the materialized // objects are written back to the original context chain. Any changes to // the original context chain will therefore be overwritten. const ScopeIterator::Option option = ScopeIterator::COLLECT_NON_LOCALS; for (ScopeIterator it(isolate, &frame_inspector, option); !it.Failed() && !it.Done() && !stop; it.Next()) { ScopeIterator::ScopeType scope_type = it.Type(); if (scope_type == ScopeIterator::ScopeTypeLocal) { DCHECK_EQ(FUNCTION_SCOPE, it.CurrentScopeInfo()->scope_type()); it.GetNonLocals(&non_locals_); Handle<Context> local_context = it.HasContext() ? it.CurrentContext() : outer_context; // The "this" binding, if any, can't be bound via "with". If we need // to, add another node onto the outer context to bind "this". Handle<Context> receiver_context = MaterializeReceiver(native_context_, local_context, local_function, global_function, it.ThisIsNonLocal()); Handle<JSObject> materialized_function = NewJSObjectWithNullProto(); frame_inspector.MaterializeStackLocals(materialized_function, local_function); MaterializeArgumentsObject(materialized_function, local_function); MaterializeContextChain(materialized_function, local_context); Handle<Context> with_context = isolate->factory()->NewWithContext( global_function, receiver_context, materialized_function); ContextChainElement context_chain_element; context_chain_element.original_context = local_context; 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, receiver_context, with_context); } else if (scope_type == ScopeIterator::ScopeTypeCatch || scope_type == ScopeIterator::ScopeTypeWith) { Handle<Context> cloned_context = Handle<Context>::cast( isolate->factory()->CopyFixedArray(it.CurrentContext())); 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(); frame_inspector.MaterializeStackLocals(materialized_object, it.CurrentScopeInfo()); if (it.HasContext()) { Handle<Context> cloned_context = Handle<Context>::cast( isolate->factory()->CopyFixedArray(it.CurrentContext())); Handle<Context> with_context = isolate->factory()->NewWithContext( global_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( global_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 DebugEvaluate::ContextBuilder::UpdateValues() { // TODO(yangguo): remove updating values. 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. FrameInspector(frame_, inlined_jsframe_index_, isolate_) .UpdateStackLocalsFromMaterializedObject(element.materialized_object, element.scope_info); if (element.scope_info->scope_type() == FUNCTION_SCOPE) { DCHECK_EQ(context_chain_.length() - 1, i); UpdateContextChainFromMaterializedObject(element.materialized_object, element.original_context); } } } } Handle<JSObject> DebugEvaluate::ContextBuilder::NewJSObjectWithNullProto() { Handle<JSObject> result = isolate_->factory()->NewJSObject(isolate_->object_function()); Handle<Map> new_map = Map::Copy(Handle<Map>(result->map()), "ObjectWithNullProto"); Map::SetPrototype(new_map, isolate_->factory()->null_value()); JSObject::MigrateToMap(result, new_map); return result; } void DebugEvaluate::ContextBuilder::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; } void DebugEvaluate::ContextBuilder::MaterializeArgumentsObject( 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; Maybe<bool> maybe = JSReceiver::HasOwnProperty( target, isolate_->factory()->arguments_string()); DCHECK(maybe.IsJust()); if (maybe.FromJust()) return; // FunctionGetArguments can't throw an exception. Handle<JSObject> arguments = Accessors::FunctionGetArguments(function); Handle<String> arguments_str = isolate_->factory()->arguments_string(); JSObject::SetOwnPropertyIgnoreAttributes(target, arguments_str, arguments, NONE) .Check(); } MaybeHandle<Object> DebugEvaluate::ContextBuilder::LoadFromContext( Handle<Context> context, Handle<String> name, bool* global) { static const ContextLookupFlags flags = FOLLOW_CONTEXT_CHAIN; int index; PropertyAttributes attributes; BindingFlags binding; Handle<Object> holder = context->Lookup(name, flags, &index, &attributes, &binding); if (holder.is_null()) return MaybeHandle<Object>(); Handle<Object> value; if (index != Context::kNotFound) { // Found on context. Handle<Context> context = Handle<Context>::cast(holder); // Do not shadow variables on the script context. *global = context->IsScriptContext(); return Handle<Object>(context->get(index), isolate_); } else { // Found on object. Handle<JSReceiver> object = Handle<JSReceiver>::cast(holder); // Do not shadow properties on the global object. *global = object->IsJSGlobalObject(); return JSReceiver::GetDataProperty(object, name); } } void DebugEvaluate::ContextBuilder::MaterializeContextChain( Handle<JSObject> target, Handle<Context> context) { for (const Handle<String>& name : non_locals_) { HandleScope scope(isolate_); Handle<Object> value; bool global; if (!LoadFromContext(context, name, &global).ToHandle(&value) || global) { // If resolving the variable fails, skip it. If it resolves to a global // variable, skip it as well since it's not read-only and can be resolved // within debug-evaluate. continue; } JSObject::SetOwnPropertyIgnoreAttributes(target, name, value, NONE).Check(); } } void DebugEvaluate::ContextBuilder::StoreToContext(Handle<Context> context, Handle<String> name, Handle<Object> value) { static const ContextLookupFlags flags = FOLLOW_CONTEXT_CHAIN; int index; PropertyAttributes attributes; BindingFlags binding; Handle<Object> holder = context->Lookup(name, flags, &index, &attributes, &binding); if (holder.is_null()) return; if (attributes & READ_ONLY) return; if (index != Context::kNotFound) { // Found on context. Handle<Context> context = Handle<Context>::cast(holder); context->set(index, *value); } else { // Found on object. Handle<JSReceiver> object = Handle<JSReceiver>::cast(holder); LookupIterator lookup(object, name); if (lookup.state() != LookupIterator::DATA) return; CHECK(JSReceiver::SetDataProperty(&lookup, value).FromJust()); } } void DebugEvaluate::ContextBuilder::UpdateContextChainFromMaterializedObject( Handle<JSObject> source, Handle<Context> context) { // TODO(yangguo): check whether overwriting context fields is actually safe // wrt fields we consider constant. for (const Handle<String>& name : non_locals_) { HandleScope scope(isolate_); Handle<Object> value = JSReceiver::GetDataProperty(source, name); StoreToContext(context, name, value); } } Handle<Context> DebugEvaluate::ContextBuilder::MaterializeReceiver( Handle<Context> parent_context, Handle<Context> lookup_context, Handle<JSFunction> local_function, Handle<JSFunction> global_function, bool this_is_non_local) { Handle<Object> receiver = isolate_->factory()->undefined_value(); Handle<String> this_string = isolate_->factory()->this_string(); if (this_is_non_local) { bool global; LoadFromContext(lookup_context, this_string, &global).ToHandle(&receiver); } else if (local_function->shared()->scope_info()->HasReceiver()) { receiver = handle(frame_->receiver(), isolate_); } return isolate_->factory()->NewCatchContext(global_function, parent_context, this_string, receiver); } } // namespace internal } // namespace v8