summaryrefslogtreecommitdiff
path: root/deps/v8/src/debug/debug-evaluate.cc
blob: e19b93eebea0174e85ae44adb4e2c011589b6405 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
// 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, 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 =
      Handle<JSObject>::cast(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