1// Copyright 2015 the V8 project authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "src/debug/debug-evaluate.h"
6
7#include "src/accessors.h"
8#include "src/compiler.h"
9#include "src/contexts.h"
10#include "src/debug/debug-frames.h"
11#include "src/debug/debug-scopes.h"
12#include "src/debug/debug.h"
13#include "src/frames-inl.h"
14#include "src/globals.h"
15#include "src/isolate-inl.h"
16
17namespace v8 {
18namespace internal {
19
20static inline bool IsDebugContext(Isolate* isolate, Context* context) {
21  return context->native_context() == *isolate->debug()->debug_context();
22}
23
24
25MaybeHandle<Object> DebugEvaluate::Global(
26    Isolate* isolate, Handle<String> source, bool disable_break,
27    Handle<HeapObject> context_extension) {
28  // Handle the processing of break.
29  DisableBreak disable_break_scope(isolate->debug(), disable_break);
30
31  // Enter the top context from before the debugger was invoked.
32  SaveContext save(isolate);
33  SaveContext* top = &save;
34  while (top != NULL && IsDebugContext(isolate, *top->context())) {
35    top = top->prev();
36  }
37  if (top != NULL) isolate->set_context(*top->context());
38
39  // Get the native context now set to the top context from before the
40  // debugger was invoked.
41  Handle<Context> context = isolate->native_context();
42  Handle<JSObject> receiver(context->global_proxy());
43  Handle<SharedFunctionInfo> outer_info(context->closure()->shared(), isolate);
44  return Evaluate(isolate, outer_info, context, context_extension, receiver,
45                  source);
46}
47
48
49MaybeHandle<Object> DebugEvaluate::Local(Isolate* isolate,
50                                         StackFrame::Id frame_id,
51                                         int inlined_jsframe_index,
52                                         Handle<String> source,
53                                         bool disable_break,
54                                         Handle<HeapObject> context_extension) {
55  // Handle the processing of break.
56  DisableBreak disable_break_scope(isolate->debug(), disable_break);
57
58  // Get the frame where the debugging is performed.
59  StackTraceFrameIterator it(isolate, frame_id);
60  if (!it.is_javascript()) return isolate->factory()->undefined_value();
61  JavaScriptFrame* frame = it.javascript_frame();
62
63  // Traverse the saved contexts chain to find the active context for the
64  // selected frame.
65  SaveContext* save =
66      DebugFrameHelper::FindSavedContextForFrame(isolate, frame);
67  SaveContext savex(isolate);
68  isolate->set_context(*(save->context()));
69
70  // This is not a lot different than DebugEvaluate::Global, except that
71  // variables accessible by the function we are evaluating from are
72  // materialized and included on top of the native context. Changes to
73  // the materialized object are written back afterwards.
74  // Note that the native context is taken from the original context chain,
75  // which may not be the current native context of the isolate.
76  ContextBuilder context_builder(isolate, frame, inlined_jsframe_index);
77  if (isolate->has_pending_exception()) return MaybeHandle<Object>();
78
79  Handle<Context> context = context_builder.evaluation_context();
80  Handle<JSObject> receiver(context->global_proxy());
81  MaybeHandle<Object> maybe_result =
82      Evaluate(isolate, context_builder.outer_info(), context,
83               context_extension, receiver, source);
84  if (!maybe_result.is_null()) context_builder.UpdateValues();
85  return maybe_result;
86}
87
88
89// Compile and evaluate source for the given context.
90MaybeHandle<Object> DebugEvaluate::Evaluate(
91    Isolate* isolate, Handle<SharedFunctionInfo> outer_info,
92    Handle<Context> context, Handle<HeapObject> context_extension,
93    Handle<Object> receiver, Handle<String> source) {
94  if (context_extension->IsJSObject()) {
95    Handle<JSObject> extension = Handle<JSObject>::cast(context_extension);
96    Handle<JSFunction> closure(context->closure(), isolate);
97    context = isolate->factory()->NewWithContext(
98        closure, context,
99        ScopeInfo::CreateForWithScope(
100            isolate, context->IsNativeContext()
101                         ? Handle<ScopeInfo>::null()
102                         : Handle<ScopeInfo>(context->scope_info())),
103        extension);
104  }
105
106  Handle<JSFunction> eval_fun;
107  ASSIGN_RETURN_ON_EXCEPTION(
108      isolate, eval_fun,
109      Compiler::GetFunctionFromEval(source, outer_info, context, SLOPPY,
110                                    NO_PARSE_RESTRICTION, kNoSourcePosition,
111                                    kNoSourcePosition),
112      Object);
113
114  Handle<Object> result;
115  ASSIGN_RETURN_ON_EXCEPTION(
116      isolate, result, Execution::Call(isolate, eval_fun, receiver, 0, NULL),
117      Object);
118
119  // Skip the global proxy as it has no properties and always delegates to the
120  // real global object.
121  if (result->IsJSGlobalProxy()) {
122    PrototypeIterator iter(isolate, Handle<JSGlobalProxy>::cast(result));
123    // TODO(verwaest): This will crash when the global proxy is detached.
124    result = PrototypeIterator::GetCurrent<JSObject>(iter);
125  }
126
127  return result;
128}
129
130
131DebugEvaluate::ContextBuilder::ContextBuilder(Isolate* isolate,
132                                              JavaScriptFrame* frame,
133                                              int inlined_jsframe_index)
134    : isolate_(isolate),
135      frame_(frame),
136      inlined_jsframe_index_(inlined_jsframe_index) {
137  FrameInspector frame_inspector(frame, inlined_jsframe_index, isolate);
138  Handle<JSFunction> local_function = frame_inspector.GetFunction();
139  Handle<Context> outer_context(local_function->context());
140  evaluation_context_ = outer_context;
141  outer_info_ = handle(local_function->shared());
142  Factory* factory = isolate->factory();
143
144  // To evaluate as if we were running eval at the point of the debug break,
145  // we reconstruct the context chain as follows:
146  //  - To make stack-allocated variables visible, we materialize them and
147  //    use a debug-evaluate context to wrap both the materialized object and
148  //    the original context.
149  //  - We use the original context chain from the function context to the
150  //    native context.
151  //  - Between the function scope and the native context, we only resolve
152  //    variable names that the current function already uses. Only for these
153  //    names we can be sure that they will be correctly resolved. For the
154  //    rest, we only resolve to with, script, and native contexts. We use a
155  //    whitelist to implement that.
156  // Context::Lookup has special handling for debug-evaluate contexts:
157  //  - Look up in the materialized stack variables.
158  //  - Look up in the original context.
159  //  - Check the whitelist to find out whether to skip contexts during lookup.
160  const ScopeIterator::Option option = ScopeIterator::COLLECT_NON_LOCALS;
161  for (ScopeIterator it(isolate, &frame_inspector, option);
162       !it.Failed() && !it.Done(); it.Next()) {
163    ScopeIterator::ScopeType scope_type = it.Type();
164    if (scope_type == ScopeIterator::ScopeTypeLocal) {
165      DCHECK_EQ(FUNCTION_SCOPE, it.CurrentScopeInfo()->scope_type());
166      Handle<JSObject> materialized = factory->NewJSObjectWithNullProto();
167      Handle<Context> local_context =
168          it.HasContext() ? it.CurrentContext() : outer_context;
169      Handle<StringSet> non_locals = it.GetNonLocals();
170      MaterializeReceiver(materialized, local_context, local_function,
171                          non_locals);
172      frame_inspector.MaterializeStackLocals(materialized, local_function);
173      MaterializeArgumentsObject(materialized, local_function);
174      ContextChainElement context_chain_element;
175      context_chain_element.scope_info = it.CurrentScopeInfo();
176      context_chain_element.materialized_object = materialized;
177      // Non-locals that are already being referenced by the current function
178      // are guaranteed to be correctly resolved.
179      context_chain_element.whitelist = non_locals;
180      if (it.HasContext()) {
181        context_chain_element.wrapped_context = it.CurrentContext();
182      }
183      context_chain_.Add(context_chain_element);
184      evaluation_context_ = outer_context;
185      break;
186    } else if (scope_type == ScopeIterator::ScopeTypeCatch ||
187               scope_type == ScopeIterator::ScopeTypeWith) {
188      ContextChainElement context_chain_element;
189      Handle<Context> current_context = it.CurrentContext();
190      if (!current_context->IsDebugEvaluateContext()) {
191        context_chain_element.wrapped_context = current_context;
192      }
193      context_chain_.Add(context_chain_element);
194    } else if (scope_type == ScopeIterator::ScopeTypeBlock ||
195               scope_type == ScopeIterator::ScopeTypeEval) {
196      Handle<JSObject> materialized = factory->NewJSObjectWithNullProto();
197      frame_inspector.MaterializeStackLocals(materialized,
198                                             it.CurrentScopeInfo());
199      ContextChainElement context_chain_element;
200      context_chain_element.scope_info = it.CurrentScopeInfo();
201      context_chain_element.materialized_object = materialized;
202      if (it.HasContext()) {
203        context_chain_element.wrapped_context = it.CurrentContext();
204      }
205      context_chain_.Add(context_chain_element);
206    } else {
207      break;
208    }
209  }
210
211  for (int i = context_chain_.length() - 1; i >= 0; i--) {
212    Handle<ScopeInfo> scope_info(ScopeInfo::CreateForWithScope(
213        isolate, evaluation_context_->IsNativeContext()
214                     ? Handle<ScopeInfo>::null()
215                     : Handle<ScopeInfo>(evaluation_context_->scope_info())));
216    scope_info->SetIsDebugEvaluateScope();
217    evaluation_context_ = factory->NewDebugEvaluateContext(
218        evaluation_context_, scope_info, context_chain_[i].materialized_object,
219        context_chain_[i].wrapped_context, context_chain_[i].whitelist);
220  }
221}
222
223
224void DebugEvaluate::ContextBuilder::UpdateValues() {
225  // TODO(yangguo): remove updating values.
226  for (int i = 0; i < context_chain_.length(); i++) {
227    ContextChainElement element = context_chain_[i];
228    if (!element.materialized_object.is_null()) {
229      // Write back potential changes to materialized stack locals to the stack.
230      FrameInspector(frame_, inlined_jsframe_index_, isolate_)
231          .UpdateStackLocalsFromMaterializedObject(element.materialized_object,
232                                                   element.scope_info);
233    }
234  }
235}
236
237
238void DebugEvaluate::ContextBuilder::MaterializeArgumentsObject(
239    Handle<JSObject> target, Handle<JSFunction> function) {
240  // Do not materialize the arguments object for eval or top-level code.
241  // Skip if "arguments" is already taken.
242  if (!function->shared()->is_function()) return;
243  Maybe<bool> maybe = JSReceiver::HasOwnProperty(
244      target, isolate_->factory()->arguments_string());
245  DCHECK(maybe.IsJust());
246  if (maybe.FromJust()) return;
247
248  // FunctionGetArguments can't throw an exception.
249  Handle<JSObject> arguments = Accessors::FunctionGetArguments(function);
250  Handle<String> arguments_str = isolate_->factory()->arguments_string();
251  JSObject::SetOwnPropertyIgnoreAttributes(target, arguments_str, arguments,
252                                           NONE)
253      .Check();
254}
255
256void DebugEvaluate::ContextBuilder::MaterializeReceiver(
257    Handle<JSObject> target, Handle<Context> local_context,
258    Handle<JSFunction> local_function, Handle<StringSet> non_locals) {
259  Handle<Object> recv = isolate_->factory()->undefined_value();
260  Handle<String> name = isolate_->factory()->this_string();
261  if (non_locals->Has(name)) {
262    // 'this' is allocated in an outer context and is is already being
263    // referenced by the current function, so it can be correctly resolved.
264    return;
265  } else if (local_function->shared()->scope_info()->HasReceiver() &&
266             !frame_->receiver()->IsTheHole(isolate_)) {
267    recv = handle(frame_->receiver(), isolate_);
268  }
269  JSObject::SetOwnPropertyIgnoreAttributes(target, name, recv, NONE).Check();
270}
271
272}  // namespace internal
273}  // namespace v8
274