// 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.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<Object> 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<Object> 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())); // Materialize stack locals and the arguments object. ContextBuilder context_builder(isolate, frame, inlined_jsframe_index); if (isolate->has_pending_exception()) return MaybeHandle<Object>(); Handle<Object> receiver(frame->receiver(), isolate); MaybeHandle<Object> maybe_result = Evaluate( isolate, context_builder.outer_info(), context_builder.innermost_context(), context_extension, receiver, source); if (!maybe_result.is_null()) 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<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)); } 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> 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_inspector); !it.Failed() && !it.Done() && !stop; it.Next()) { ScopeIterator::ScopeType scope_type = it.Type(); if (scope_type == ScopeIterator::ScopeTypeLocal) { Handle<Context> parent_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". parent_context = MaterializeReceiver(parent_context, function); Handle<JSObject> materialized_function = NewJSObjectWithNullProto(); frame_inspector.MaterializeStackLocals(materialized_function, function); MaterializeArgumentsObject(materialized_function, function); 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( 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( 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 DebugEvaluate::ContextBuilder::UpdateValues() { 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); } } } 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 = Handle<JSObject>::cast(Accessors::FunctionGetArguments(function)); Handle<String> arguments_str = isolate_->factory()->arguments_string(); JSObject::SetOwnPropertyIgnoreAttributes(target, arguments_str, arguments, NONE) .Check(); } Handle<Context> DebugEvaluate::ContextBuilder::MaterializeReceiver( Handle<Context> target, Handle<JSFunction> function) { Handle<SharedFunctionInfo> shared(function->shared()); Handle<ScopeInfo> scope_info(shared->scope_info()); Handle<Object> receiver; switch (scope_info->scope_type()) { case FUNCTION_SCOPE: { VariableMode mode; VariableLocation location; InitializationFlag init_flag; MaybeAssignedFlag maybe_assigned_flag; // Don't bother creating a fake context node if "this" is in the context // already. if (ScopeInfo::ContextSlotIndex( scope_info, isolate_->factory()->this_string(), &mode, &location, &init_flag, &maybe_assigned_flag) >= 0) { return target; } receiver = handle(frame_->receiver(), isolate_); break; } case MODULE_SCOPE: receiver = isolate_->factory()->undefined_value(); break; case SCRIPT_SCOPE: receiver = handle(function->global_proxy(), isolate_); break; default: // For eval code, arrow functions, and the like, there's no "this" binding // to materialize. return target; } return isolate_->factory()->NewCatchContext( function, target, isolate_->factory()->this_string(), receiver); } } // namespace internal } // namespace v8