1// Copyright 2021 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-wasm-objects.h"
6
7#include "src/api/api-inl.h"
8#include "src/api/api-natives.h"
9#include "src/base/strings.h"
10#include "src/debug/debug-wasm-objects-inl.h"
11#include "src/execution/frames-inl.h"
12#include "src/objects/property-descriptor.h"
13#include "src/wasm/wasm-debug.h"
14#include "src/wasm/wasm-objects-inl.h"
15#include "src/wasm/wasm-value.h"
16
17namespace v8 {
18namespace internal {
19namespace {
20
21// Helper for unpacking a maybe name that makes a default with an index if
22// the name is empty. If the name is not empty, it's prefixed with a $.
23Handle<String> GetNameOrDefault(Isolate* isolate,
24                                MaybeHandle<String> maybe_name,
25                                const char* default_name_prefix,
26                                uint32_t index) {
27  Handle<String> name;
28  if (maybe_name.ToHandle(&name)) {
29    name = isolate->factory()
30               ->NewConsString(
31                   isolate->factory()->NewStringFromAsciiChecked("$"), name)
32               .ToHandleChecked();
33    return isolate->factory()->InternalizeString(name);
34  }
35  base::EmbeddedVector<char, 64> value;
36  int len = SNPrintF(value, "%s%u", default_name_prefix, index);
37  return isolate->factory()->InternalizeString(value.SubVector(0, len));
38}
39
40MaybeHandle<String> GetNameFromImportsAndExportsOrNull(
41    Isolate* isolate, Handle<WasmInstanceObject> instance,
42    wasm::ImportExportKindCode kind, uint32_t index) {
43  auto debug_info = instance->module_object().native_module()->GetDebugInfo();
44  wasm::ModuleWireBytes wire_bytes(
45      instance->module_object().native_module()->wire_bytes());
46
47  auto import_name_ref = debug_info->GetImportName(kind, index);
48  if (!import_name_ref.first.is_empty()) {
49    base::ScopedVector<char> name(import_name_ref.first.length() + 1 +
50                                  import_name_ref.second.length());
51    auto name_begin = &name.first(), name_end = name_begin;
52    auto module_name = wire_bytes.GetNameOrNull(import_name_ref.first);
53    name_end = std::copy(module_name.begin(), module_name.end(), name_end);
54    *name_end++ = '.';
55    auto field_name = wire_bytes.GetNameOrNull(import_name_ref.second);
56    name_end = std::copy(field_name.begin(), field_name.end(), name_end);
57    return isolate->factory()->NewStringFromUtf8(
58        base::VectorOf(name_begin, name_end - name_begin));
59  }
60
61  auto export_name_ref = debug_info->GetExportName(kind, index);
62  if (!export_name_ref.is_empty()) {
63    auto name = wire_bytes.GetNameOrNull(export_name_ref);
64    return isolate->factory()->NewStringFromUtf8(name);
65  }
66
67  return {};
68}
69
70enum DebugProxyId {
71  kFunctionsProxy,
72  kGlobalsProxy,
73  kMemoriesProxy,
74  kTablesProxy,
75  kLastInstanceProxyId = kTablesProxy,
76
77  kContextProxy,
78  kLocalsProxy,
79  kStackProxy,
80  kStructProxy,
81  kArrayProxy,
82  kLastProxyId = kArrayProxy,
83
84  kNumProxies = kLastProxyId + 1,
85  kNumInstanceProxies = kLastInstanceProxyId + 1
86};
87
88constexpr int kWasmValueMapIndex = kNumProxies;
89constexpr int kNumDebugMaps = kWasmValueMapIndex + 1;
90
91Handle<FixedArray> GetOrCreateDebugMaps(Isolate* isolate) {
92  Handle<FixedArray> maps = isolate->wasm_debug_maps();
93  if (maps->length() == 0) {
94    maps = isolate->factory()->NewFixedArrayWithHoles(kNumDebugMaps);
95    isolate->native_context()->set_wasm_debug_maps(*maps);
96  }
97  return maps;
98}
99
100// Creates a Map for the given debug proxy |id| using the |create_template_fn|
101// on-demand and caches this map in the global object. The map is derived from
102// the FunctionTemplate returned by |create_template_fn| and has its prototype
103// set to |null| and is marked non-extensible (by default).
104// TODO(bmeurer): remove the extensibility opt-out and replace it with a proper
105// way to add non-intercepted named properties.
106Handle<Map> GetOrCreateDebugProxyMap(
107    Isolate* isolate, DebugProxyId id,
108    v8::Local<v8::FunctionTemplate> (*create_template_fn)(v8::Isolate*),
109    bool make_non_extensible = true) {
110  auto maps = GetOrCreateDebugMaps(isolate);
111  CHECK_LE(kNumProxies, maps->length());
112  if (!maps->is_the_hole(isolate, id)) {
113    return handle(Map::cast(maps->get(id)), isolate);
114  }
115  auto tmp = (*create_template_fn)(reinterpret_cast<v8::Isolate*>(isolate));
116  auto fun = ApiNatives::InstantiateFunction(Utils::OpenHandle(*tmp))
117                 .ToHandleChecked();
118  auto map = JSFunction::GetDerivedMap(isolate, fun, fun).ToHandleChecked();
119  Map::SetPrototype(isolate, map, isolate->factory()->null_value());
120  if (make_non_extensible) {
121    map->set_is_extensible(false);
122  }
123  maps->set(id, *map);
124  return map;
125}
126
127// Base class for debug proxies, offers indexed access. The subclasses
128// need to implement |Count| and |Get| methods appropriately.
129template <typename T, DebugProxyId id, typename Provider>
130struct IndexedDebugProxy {
131  static constexpr DebugProxyId kId = id;
132
133  static Handle<JSObject> Create(Isolate* isolate, Handle<Provider> provider,
134                                 bool make_map_non_extensible = true) {
135    auto object_map = GetOrCreateDebugProxyMap(isolate, kId, &T::CreateTemplate,
136                                               make_map_non_extensible);
137    auto object = isolate->factory()->NewJSObjectFromMap(object_map);
138    object->SetEmbedderField(kProviderField, *provider);
139    return object;
140  }
141
142  enum {
143    kProviderField,
144    kFieldCount,
145  };
146
147  static v8::Local<v8::FunctionTemplate> CreateTemplate(v8::Isolate* isolate) {
148    Local<v8::FunctionTemplate> templ = v8::FunctionTemplate::New(isolate);
149    templ->SetClassName(
150        v8::String::NewFromUtf8(isolate, T::kClassName).ToLocalChecked());
151    templ->InstanceTemplate()->SetInternalFieldCount(T::kFieldCount);
152    templ->InstanceTemplate()->SetHandler(
153        v8::IndexedPropertyHandlerConfiguration(
154            &T::IndexedGetter, {}, &T::IndexedQuery, {}, &T::IndexedEnumerator,
155            {}, &T::IndexedDescriptor, {},
156            v8::PropertyHandlerFlags::kHasNoSideEffect));
157    return templ;
158  }
159
160  template <typename V>
161  static Isolate* GetIsolate(const PropertyCallbackInfo<V>& info) {
162    return reinterpret_cast<Isolate*>(info.GetIsolate());
163  }
164
165  template <typename V>
166  static Handle<JSObject> GetHolder(const PropertyCallbackInfo<V>& info) {
167    return Handle<JSObject>::cast(Utils::OpenHandle(*info.Holder()));
168  }
169
170  static Handle<Provider> GetProvider(Handle<JSObject> holder,
171                                      Isolate* isolate) {
172    return handle(Provider::cast(holder->GetEmbedderField(kProviderField)),
173                  isolate);
174  }
175
176  template <typename V>
177  static Handle<Provider> GetProvider(const PropertyCallbackInfo<V>& info) {
178    return GetProvider(GetHolder(info), GetIsolate(info));
179  }
180
181  static void IndexedGetter(uint32_t index,
182                            const PropertyCallbackInfo<v8::Value>& info) {
183    auto isolate = GetIsolate(info);
184    auto provider = GetProvider(info);
185    if (index < T::Count(isolate, provider)) {
186      auto value = T::Get(isolate, provider, index);
187      info.GetReturnValue().Set(Utils::ToLocal(value));
188    }
189  }
190
191  static void IndexedDescriptor(uint32_t index,
192                                const PropertyCallbackInfo<v8::Value>& info) {
193    auto isolate = GetIsolate(info);
194    auto provider = GetProvider(info);
195    if (index < T::Count(isolate, provider)) {
196      PropertyDescriptor descriptor;
197      descriptor.set_configurable(false);
198      descriptor.set_enumerable(true);
199      descriptor.set_writable(false);
200      descriptor.set_value(T::Get(isolate, provider, index));
201      info.GetReturnValue().Set(Utils::ToLocal(descriptor.ToObject(isolate)));
202    }
203  }
204
205  static void IndexedQuery(uint32_t index,
206                           const PropertyCallbackInfo<v8::Integer>& info) {
207    if (index < T::Count(GetIsolate(info), GetProvider(info))) {
208      info.GetReturnValue().Set(Integer::New(
209          info.GetIsolate(),
210          PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly));
211    }
212  }
213
214  static void IndexedEnumerator(const PropertyCallbackInfo<v8::Array>& info) {
215    auto isolate = GetIsolate(info);
216    auto count = T::Count(isolate, GetProvider(info));
217    auto indices = isolate->factory()->NewFixedArray(count);
218    for (uint32_t index = 0; index < count; ++index) {
219      indices->set(index, Smi::FromInt(index));
220    }
221    info.GetReturnValue().Set(
222        Utils::ToLocal(isolate->factory()->NewJSArrayWithElements(
223            indices, PACKED_SMI_ELEMENTS)));
224  }
225};
226
227// Extends |IndexedDebugProxy| with named access, where the names are computed
228// on-demand, and all names are assumed to start with a dollar char ($). This
229// is important in order to scale to Wasm modules with hundreds of thousands
230// of functions in them.
231template <typename T, DebugProxyId id, typename Provider = WasmInstanceObject>
232struct NamedDebugProxy : IndexedDebugProxy<T, id, Provider> {
233  static v8::Local<v8::FunctionTemplate> CreateTemplate(v8::Isolate* isolate) {
234    auto templ = IndexedDebugProxy<T, id, Provider>::CreateTemplate(isolate);
235    templ->InstanceTemplate()->SetHandler(v8::NamedPropertyHandlerConfiguration(
236        &T::NamedGetter, {}, &T::NamedQuery, {}, &T::NamedEnumerator, {},
237        &T::NamedDescriptor, {}, v8::PropertyHandlerFlags::kHasNoSideEffect));
238    return templ;
239  }
240
241  static void IndexedEnumerator(const PropertyCallbackInfo<v8::Array>& info) {
242    info.GetReturnValue().Set(v8::Array::New(info.GetIsolate()));
243  }
244
245  static Handle<NameDictionary> GetNameTable(Handle<JSObject> holder,
246                                             Isolate* isolate) {
247    Handle<Symbol> symbol = isolate->factory()->wasm_debug_proxy_names_symbol();
248    Handle<Object> table_or_undefined =
249        JSObject::GetProperty(isolate, holder, symbol).ToHandleChecked();
250    if (!table_or_undefined->IsUndefined(isolate)) {
251      return Handle<NameDictionary>::cast(table_or_undefined);
252    }
253    auto provider = T::GetProvider(holder, isolate);
254    auto count = T::Count(isolate, provider);
255    auto table = NameDictionary::New(isolate, count);
256    for (uint32_t index = 0; index < count; ++index) {
257      HandleScope scope(isolate);
258      auto key = T::GetName(isolate, provider, index);
259      if (table->FindEntry(isolate, key).is_found()) continue;
260      Handle<Smi> value(Smi::FromInt(index), isolate);
261      table = NameDictionary::Add(isolate, table, key, value,
262                                  PropertyDetails::Empty());
263    }
264    Object::SetProperty(isolate, holder, symbol, table).Check();
265    return table;
266  }
267
268  template <typename V>
269  static base::Optional<uint32_t> FindName(
270      Local<v8::Name> name, const PropertyCallbackInfo<V>& info) {
271    if (!name->IsString()) return {};
272    auto name_str = Utils::OpenHandle(*name.As<v8::String>());
273    if (name_str->length() == 0 || name_str->Get(0) != '$') return {};
274    auto isolate = T::GetIsolate(info);
275    auto table = GetNameTable(T::GetHolder(info), isolate);
276    auto entry = table->FindEntry(isolate, name_str);
277    if (entry.is_found()) return Smi::ToInt(table->ValueAt(entry));
278    return {};
279  }
280
281  static void NamedGetter(Local<v8::Name> name,
282                          const PropertyCallbackInfo<v8::Value>& info) {
283    if (auto index = FindName(name, info)) T::IndexedGetter(*index, info);
284  }
285
286  static void NamedQuery(Local<v8::Name> name,
287                         const PropertyCallbackInfo<v8::Integer>& info) {
288    if (auto index = FindName(name, info)) T::IndexedQuery(*index, info);
289  }
290
291  static void NamedDescriptor(Local<v8::Name> name,
292                              const PropertyCallbackInfo<v8::Value>& info) {
293    if (auto index = FindName(name, info)) T::IndexedDescriptor(*index, info);
294  }
295
296  static void NamedEnumerator(const PropertyCallbackInfo<v8::Array>& info) {
297    auto isolate = T::GetIsolate(info);
298    auto table = GetNameTable(T::GetHolder(info), isolate);
299    auto names = NameDictionary::IterationIndices(isolate, table);
300    for (int i = 0; i < names->length(); ++i) {
301      InternalIndex entry(Smi::ToInt(names->get(i)));
302      names->set(i, table->NameAt(entry));
303    }
304    info.GetReturnValue().Set(Utils::ToLocal(
305        isolate->factory()->NewJSArrayWithElements(names, PACKED_ELEMENTS)));
306  }
307};
308
309// This class implements the "functions" proxy.
310struct FunctionsProxy : NamedDebugProxy<FunctionsProxy, kFunctionsProxy> {
311  static constexpr char const* kClassName = "Functions";
312
313  static uint32_t Count(Isolate* isolate, Handle<WasmInstanceObject> instance) {
314    return static_cast<uint32_t>(instance->module()->functions.size());
315  }
316
317  static Handle<Object> Get(Isolate* isolate,
318                            Handle<WasmInstanceObject> instance,
319                            uint32_t index) {
320    return handle(WasmInstanceObject::GetOrCreateWasmInternalFunction(
321                      isolate, instance, index)
322                      ->external(),
323                  isolate);
324  }
325
326  static Handle<String> GetName(Isolate* isolate,
327                                Handle<WasmInstanceObject> instance,
328                                uint32_t index) {
329    return GetWasmFunctionDebugName(isolate, instance, index);
330  }
331};
332
333// This class implements the "globals" proxy.
334struct GlobalsProxy : NamedDebugProxy<GlobalsProxy, kGlobalsProxy> {
335  static constexpr char const* kClassName = "Globals";
336
337  static uint32_t Count(Isolate* isolate, Handle<WasmInstanceObject> instance) {
338    return static_cast<uint32_t>(instance->module()->globals.size());
339  }
340
341  static Handle<Object> Get(Isolate* isolate,
342                            Handle<WasmInstanceObject> instance,
343                            uint32_t index) {
344    Handle<WasmModuleObject> module(instance->module_object(), isolate);
345    return WasmValueObject::New(
346        isolate,
347        WasmInstanceObject::GetGlobalValue(instance,
348                                           instance->module()->globals[index]),
349        module);
350  }
351
352  static Handle<String> GetName(Isolate* isolate,
353                                Handle<WasmInstanceObject> instance,
354                                uint32_t index) {
355    return GetNameOrDefault(
356        isolate,
357        GetNameFromImportsAndExportsOrNull(
358            isolate, instance, wasm::ImportExportKindCode::kExternalGlobal,
359            index),
360        "$global", index);
361  }
362};
363
364// This class implements the "memories" proxy.
365struct MemoriesProxy : NamedDebugProxy<MemoriesProxy, kMemoriesProxy> {
366  static constexpr char const* kClassName = "Memories";
367
368  static uint32_t Count(Isolate* isolate, Handle<WasmInstanceObject> instance) {
369    return instance->has_memory_object() ? 1 : 0;
370  }
371
372  static Handle<Object> Get(Isolate* isolate,
373                            Handle<WasmInstanceObject> instance,
374                            uint32_t index) {
375    return handle(instance->memory_object(), isolate);
376  }
377
378  static Handle<String> GetName(Isolate* isolate,
379                                Handle<WasmInstanceObject> instance,
380                                uint32_t index) {
381    return GetNameOrDefault(
382        isolate,
383        GetNameFromImportsAndExportsOrNull(
384            isolate, instance, wasm::ImportExportKindCode::kExternalMemory,
385            index),
386        "$memory", index);
387  }
388};
389
390// This class implements the "tables" proxy.
391struct TablesProxy : NamedDebugProxy<TablesProxy, kTablesProxy> {
392  static constexpr char const* kClassName = "Tables";
393
394  static uint32_t Count(Isolate* isolate, Handle<WasmInstanceObject> instance) {
395    return instance->tables().length();
396  }
397
398  static Handle<Object> Get(Isolate* isolate,
399                            Handle<WasmInstanceObject> instance,
400                            uint32_t index) {
401    return handle(instance->tables().get(index), isolate);
402  }
403
404  static Handle<String> GetName(Isolate* isolate,
405                                Handle<WasmInstanceObject> instance,
406                                uint32_t index) {
407    return GetNameOrDefault(
408        isolate,
409        GetNameFromImportsAndExportsOrNull(
410            isolate, instance, wasm::ImportExportKindCode::kExternalTable,
411            index),
412        "$table", index);
413  }
414};
415
416// This class implements the "locals" proxy.
417struct LocalsProxy : NamedDebugProxy<LocalsProxy, kLocalsProxy, FixedArray> {
418  static constexpr char const* kClassName = "Locals";
419
420  static Handle<JSObject> Create(WasmFrame* frame) {
421    auto isolate = frame->isolate();
422    auto debug_info = frame->native_module()->GetDebugInfo();
423    // TODO(bmeurer): Check if pc is inspectable.
424    int count = debug_info->GetNumLocals(frame->pc());
425    auto function = debug_info->GetFunctionAtAddress(frame->pc());
426    auto values = isolate->factory()->NewFixedArray(count + 2);
427    Handle<WasmModuleObject> module_object(
428        frame->wasm_instance().module_object(), isolate);
429    for (int i = 0; i < count; ++i) {
430      auto value = WasmValueObject::New(
431          isolate,
432          debug_info->GetLocalValue(i, frame->pc(), frame->fp(),
433                                    frame->callee_fp(), isolate),
434          module_object);
435      values->set(i, *value);
436    }
437    values->set(count + 0, frame->wasm_instance().module_object());
438    values->set(count + 1, Smi::FromInt(function.func_index));
439    return NamedDebugProxy::Create(isolate, values);
440  }
441
442  static uint32_t Count(Isolate* isolate, Handle<FixedArray> values) {
443    return values->length() - 2;
444  }
445
446  static Handle<Object> Get(Isolate* isolate, Handle<FixedArray> values,
447                            uint32_t index) {
448    return handle(values->get(index), isolate);
449  }
450
451  static Handle<String> GetName(Isolate* isolate, Handle<FixedArray> values,
452                                uint32_t index) {
453    uint32_t count = Count(isolate, values);
454    auto native_module =
455        WasmModuleObject::cast(values->get(count + 0)).native_module();
456    auto function_index = Smi::ToInt(Smi::cast(values->get(count + 1)));
457    wasm::ModuleWireBytes module_wire_bytes(native_module->wire_bytes());
458    auto name_vec = module_wire_bytes.GetNameOrNull(
459        native_module->GetDebugInfo()->GetLocalName(function_index, index));
460    return GetNameOrDefault(
461        isolate,
462        name_vec.empty() ? MaybeHandle<String>()
463                         : isolate->factory()->NewStringFromUtf8(name_vec),
464        "$var", index);
465  }
466};
467
468// This class implements the "stack" proxy (which offers only indexed access).
469struct StackProxy : IndexedDebugProxy<StackProxy, kStackProxy, FixedArray> {
470  static constexpr char const* kClassName = "Stack";
471
472  static Handle<JSObject> Create(WasmFrame* frame) {
473    auto isolate = frame->isolate();
474    auto debug_info =
475        frame->wasm_instance().module_object().native_module()->GetDebugInfo();
476    int count = debug_info->GetStackDepth(frame->pc());
477    auto values = isolate->factory()->NewFixedArray(count);
478    Handle<WasmModuleObject> module_object(
479        frame->wasm_instance().module_object(), isolate);
480    for (int i = 0; i < count; ++i) {
481      auto value = WasmValueObject::New(
482          isolate,
483          debug_info->GetStackValue(i, frame->pc(), frame->fp(),
484                                    frame->callee_fp(), isolate),
485          module_object);
486      values->set(i, *value);
487    }
488    return IndexedDebugProxy::Create(isolate, values);
489  }
490
491  static uint32_t Count(Isolate* isolate, Handle<FixedArray> values) {
492    return values->length();
493  }
494
495  static Handle<Object> Get(Isolate* isolate, Handle<FixedArray> values,
496                            uint32_t index) {
497    return handle(values->get(index), isolate);
498  }
499};
500
501// Creates FixedArray with size |kNumInstanceProxies| as cache on-demand
502// on the |instance|, stored under the |wasm_debug_proxy_cache_symbol|.
503// This is used to cache the various instance debug proxies (functions,
504// globals, tables, and memories) on the WasmInstanceObject.
505Handle<FixedArray> GetOrCreateInstanceProxyCache(
506    Isolate* isolate, Handle<WasmInstanceObject> instance) {
507  Handle<Object> cache;
508  Handle<Symbol> symbol = isolate->factory()->wasm_debug_proxy_cache_symbol();
509  if (!Object::GetProperty(isolate, instance, symbol).ToHandle(&cache) ||
510      cache->IsUndefined(isolate)) {
511    cache = isolate->factory()->NewFixedArrayWithHoles(kNumInstanceProxies);
512    Object::SetProperty(isolate, instance, symbol, cache).Check();
513  }
514  return Handle<FixedArray>::cast(cache);
515}
516
517// Creates an instance of the |Proxy| on-demand and caches that on the
518// |instance|.
519template <typename Proxy>
520Handle<JSObject> GetOrCreateInstanceProxy(Isolate* isolate,
521                                          Handle<WasmInstanceObject> instance) {
522  STATIC_ASSERT(Proxy::kId < kNumInstanceProxies);
523  Handle<FixedArray> proxies = GetOrCreateInstanceProxyCache(isolate, instance);
524  if (!proxies->is_the_hole(isolate, Proxy::kId)) {
525    return handle(JSObject::cast(proxies->get(Proxy::kId)), isolate);
526  }
527  Handle<JSObject> proxy = Proxy::Create(isolate, instance);
528  proxies->set(Proxy::kId, *proxy);
529  return proxy;
530}
531
532// This class implements the debug proxy for a given Wasm frame. The debug
533// proxy is used when evaluating JavaScript expressions on a wasm frame via
534// the inspector |Runtime.evaluateOnCallFrame()| API and enables developers
535// and extensions to inspect the WebAssembly engine state from JavaScript.
536// The proxy provides the following interface:
537//
538// type WasmValue = {
539//   type: string;
540//   value: number | bigint | object | string;
541// };
542// type WasmFunction = (... args : WasmValue[]) = > WasmValue;
543// interface WasmInterface {
544//   $globalX: WasmValue;
545//   $varX: WasmValue;
546//   $funcX(a : WasmValue /*, ...*/) : WasmValue;
547//   readonly $memoryX : WebAssembly.Memory;
548//   readonly $tableX : WebAssembly.Table;
549//
550//   readonly instance : WebAssembly.Instance;
551//   readonly module : WebAssembly.Module;
552//
553//   readonly memories : {[nameOrIndex:string | number] : WebAssembly.Memory};
554//   readonly tables : {[nameOrIndex:string | number] : WebAssembly.Table};
555//   readonly stack : WasmValue[];
556//   readonly globals : {[nameOrIndex:string | number] : WasmValue};
557//   readonly locals : {[nameOrIndex:string | number] : WasmValue};
558//   readonly functions : {[nameOrIndex:string | number] : WasmFunction};
559// }
560//
561// The wasm index spaces memories, tables, stack, globals, locals, and
562// functions are JSObjects with interceptors that lazily produce values
563// either by index or by name (except for stack).
564// Only the names are reported by APIs such as Object.keys() and
565// Object.getOwnPropertyNames(), since the indices are not meant to be
566// used interactively by developers (in Chrome DevTools), but are provided
567// for WebAssembly language extensions. Also note that these JSObjects
568// all have null prototypes, to not confuse context lookup and to make
569// their purpose as dictionaries clear.
570//
571// See http://doc/1VZOJrU2VsqOZe3IUzbwQWQQSZwgGySsm5119Ust1gUA and
572// http://bit.ly/devtools-wasm-entities for more details.
573class ContextProxyPrototype {
574 public:
575  static Handle<JSObject> Create(Isolate* isolate) {
576    auto object_map =
577        GetOrCreateDebugProxyMap(isolate, kContextProxy, &CreateTemplate);
578    return isolate->factory()->NewJSObjectFromMap(object_map);
579  }
580
581 private:
582  static v8::Local<v8::FunctionTemplate> CreateTemplate(v8::Isolate* isolate) {
583    Local<v8::FunctionTemplate> templ = v8::FunctionTemplate::New(isolate);
584    templ->InstanceTemplate()->SetHandler(v8::NamedPropertyHandlerConfiguration(
585        &NamedGetter, {}, {}, {}, {}, {}, {}, {},
586        static_cast<v8::PropertyHandlerFlags>(
587            static_cast<unsigned>(
588                v8::PropertyHandlerFlags::kOnlyInterceptStrings) |
589            static_cast<unsigned>(
590                v8::PropertyHandlerFlags::kHasNoSideEffect))));
591    return templ;
592  }
593
594  static MaybeHandle<Object> GetNamedProperty(Isolate* isolate,
595                                              Handle<JSObject> receiver,
596                                              Handle<String> name) {
597    if (name->length() != 0 && name->Get(0) == '$') {
598      const char* kDelegateNames[] = {"memories", "locals", "tables",
599                                      "functions", "globals"};
600      for (auto delegate_name : kDelegateNames) {
601        Handle<Object> delegate;
602        ASSIGN_RETURN_ON_EXCEPTION(
603            isolate, delegate,
604            JSObject::GetProperty(isolate, receiver, delegate_name), Object);
605        if (!delegate->IsUndefined(isolate)) {
606          Handle<Object> value;
607          ASSIGN_RETURN_ON_EXCEPTION(
608              isolate, value, Object::GetProperty(isolate, delegate, name),
609              Object);
610          if (!value->IsUndefined(isolate)) return value;
611        }
612      }
613    }
614    return {};
615  }
616
617  static void NamedGetter(Local<v8::Name> name,
618                          const PropertyCallbackInfo<v8::Value>& info) {
619    auto name_string = Handle<String>::cast(Utils::OpenHandle(*name));
620    auto isolate = reinterpret_cast<Isolate*>(info.GetIsolate());
621    auto receiver = Handle<JSObject>::cast(Utils::OpenHandle(*info.This()));
622    Handle<Object> value;
623    if (GetNamedProperty(isolate, receiver, name_string).ToHandle(&value)) {
624      info.GetReturnValue().Set(Utils::ToLocal(value));
625    }
626  }
627};
628
629class ContextProxy {
630 public:
631  static Handle<JSObject> Create(WasmFrame* frame) {
632    Isolate* isolate = frame->isolate();
633    auto object = isolate->factory()->NewSlowJSObjectWithNullProto();
634    Handle<WasmInstanceObject> instance(frame->wasm_instance(), isolate);
635    JSObject::AddProperty(isolate, object, "instance", instance, FROZEN);
636    Handle<WasmModuleObject> module_object(instance->module_object(), isolate);
637    JSObject::AddProperty(isolate, object, "module", module_object, FROZEN);
638    auto locals = LocalsProxy::Create(frame);
639    JSObject::AddProperty(isolate, object, "locals", locals, FROZEN);
640    auto stack = StackProxy::Create(frame);
641    JSObject::AddProperty(isolate, object, "stack", stack, FROZEN);
642    auto memories = GetOrCreateInstanceProxy<MemoriesProxy>(isolate, instance);
643    JSObject::AddProperty(isolate, object, "memories", memories, FROZEN);
644    auto tables = GetOrCreateInstanceProxy<TablesProxy>(isolate, instance);
645    JSObject::AddProperty(isolate, object, "tables", tables, FROZEN);
646    auto globals = GetOrCreateInstanceProxy<GlobalsProxy>(isolate, instance);
647    JSObject::AddProperty(isolate, object, "globals", globals, FROZEN);
648    auto functions =
649        GetOrCreateInstanceProxy<FunctionsProxy>(isolate, instance);
650    JSObject::AddProperty(isolate, object, "functions", functions, FROZEN);
651    Handle<JSObject> prototype = ContextProxyPrototype::Create(isolate);
652    JSObject::SetPrototype(isolate, object, prototype, false, kDontThrow)
653        .Check();
654    return object;
655  }
656};
657
658class DebugWasmScopeIterator final : public debug::ScopeIterator {
659 public:
660  explicit DebugWasmScopeIterator(WasmFrame* frame)
661      : frame_(frame),
662        type_(debug::ScopeIterator::ScopeTypeWasmExpressionStack) {
663    // Skip local scope and expression stack scope if the frame is not
664    // inspectable.
665    if (!frame->is_inspectable()) {
666      type_ = debug::ScopeIterator::ScopeTypeModule;
667    }
668  }
669
670  bool Done() override { return type_ == ScopeTypeWith; }
671
672  void Advance() override {
673    DCHECK(!Done());
674    switch (type_) {
675      case ScopeTypeWasmExpressionStack:
676        type_ = debug::ScopeIterator::ScopeTypeLocal;
677        break;
678      case ScopeTypeLocal:
679        type_ = debug::ScopeIterator::ScopeTypeModule;
680        break;
681      case ScopeTypeModule:
682        // We use ScopeTypeWith type as marker for done.
683        type_ = debug::ScopeIterator::ScopeTypeWith;
684        break;
685      default:
686        UNREACHABLE();
687    }
688  }
689
690  ScopeType GetType() override { return type_; }
691
692  v8::Local<v8::Object> GetObject() override {
693    Isolate* isolate = frame_->isolate();
694    switch (type_) {
695      case debug::ScopeIterator::ScopeTypeModule: {
696        Handle<WasmInstanceObject> instance(frame_->wasm_instance(), isolate);
697        Handle<JSObject> object =
698            isolate->factory()->NewSlowJSObjectWithNullProto();
699        JSObject::AddProperty(isolate, object, "instance", instance, FROZEN);
700        Handle<JSObject> module_object(instance->module_object(), isolate);
701        JSObject::AddProperty(isolate, object, "module", module_object, FROZEN);
702        if (FunctionsProxy::Count(isolate, instance) != 0) {
703          JSObject::AddProperty(
704              isolate, object, "functions",
705              GetOrCreateInstanceProxy<FunctionsProxy>(isolate, instance),
706              FROZEN);
707        }
708        if (GlobalsProxy::Count(isolate, instance) != 0) {
709          JSObject::AddProperty(
710              isolate, object, "globals",
711              GetOrCreateInstanceProxy<GlobalsProxy>(isolate, instance),
712              FROZEN);
713        }
714        if (MemoriesProxy::Count(isolate, instance) != 0) {
715          JSObject::AddProperty(
716              isolate, object, "memories",
717              GetOrCreateInstanceProxy<MemoriesProxy>(isolate, instance),
718              FROZEN);
719        }
720        if (TablesProxy::Count(isolate, instance) != 0) {
721          JSObject::AddProperty(
722              isolate, object, "tables",
723              GetOrCreateInstanceProxy<TablesProxy>(isolate, instance), FROZEN);
724        }
725        return Utils::ToLocal(object);
726      }
727      case debug::ScopeIterator::ScopeTypeLocal: {
728        return Utils::ToLocal(LocalsProxy::Create(frame_));
729      }
730      case debug::ScopeIterator::ScopeTypeWasmExpressionStack: {
731        auto object = isolate->factory()->NewSlowJSObjectWithNullProto();
732        auto stack = StackProxy::Create(frame_);
733        JSObject::AddProperty(isolate, object, "stack", stack, FROZEN);
734        return Utils::ToLocal(object);
735      }
736      default:
737        UNREACHABLE();
738    }
739  }
740  v8::Local<v8::Value> GetFunctionDebugName() override {
741    return Utils::ToLocal(frame_->isolate()->factory()->empty_string());
742  }
743
744  int GetScriptId() override { return -1; }
745
746  bool HasLocationInfo() override { return false; }
747
748  debug::Location GetStartLocation() override { return {}; }
749
750  debug::Location GetEndLocation() override { return {}; }
751
752  bool SetVariableValue(v8::Local<v8::String> name,
753                        v8::Local<v8::Value> value) override {
754    return false;
755  }
756
757 private:
758  WasmFrame* const frame_;
759  ScopeType type_;
760};
761
762Handle<String> WasmSimd128ToString(Isolate* isolate, wasm::Simd128 s128) {
763  // We use the canonical format as described in:
764  // https://github.com/WebAssembly/simd/blob/master/proposals/simd/TextSIMD.md
765  base::EmbeddedVector<char, 50> buffer;
766  auto i32x4 = s128.to_i32x4();
767  SNPrintF(buffer, "i32x4 0x%08X 0x%08X 0x%08X 0x%08X", i32x4.val[0],
768           i32x4.val[1], i32x4.val[2], i32x4.val[3]);
769  return isolate->factory()->NewStringFromAsciiChecked(buffer.data());
770}
771
772Handle<String> GetRefTypeName(Isolate* isolate, wasm::ValueType type,
773                              wasm::NativeModule* module) {
774  DCHECK(type.is_object_reference());
775  std::ostringstream name;
776  if (type.heap_type().is_generic()) {
777    name << type.name();
778  } else {
779    name << "(ref " << (type.is_nullable() ? "null " : "") << "$";
780    wasm::ModuleWireBytes module_wire_bytes(module->wire_bytes());
781    base::Vector<const char> module_name = module_wire_bytes.GetNameOrNull(
782        module->GetDebugInfo()->GetTypeName(type.ref_index()));
783    if (module_name.empty()) {
784      name << "type" << type.ref_index();
785    } else {
786      name.write(module_name.begin(), module_name.size());
787    }
788    name << ")";
789  }
790  return isolate->factory()->InternalizeString(base::VectorOf(name.str()));
791}
792
793}  // namespace
794
795// static
796Handle<WasmValueObject> WasmValueObject::New(Isolate* isolate,
797                                             Handle<String> type,
798                                             Handle<Object> value) {
799  auto maps = GetOrCreateDebugMaps(isolate);
800  if (maps->is_the_hole(isolate, kWasmValueMapIndex)) {
801    Handle<Map> map = isolate->factory()->NewMap(
802        WASM_VALUE_OBJECT_TYPE, WasmValueObject::kSize,
803        TERMINAL_FAST_ELEMENTS_KIND, 2);
804    Map::EnsureDescriptorSlack(isolate, map, 2);
805    map->SetConstructor(*isolate->object_function());
806    {  // type
807      Descriptor d = Descriptor::DataField(
808          isolate,
809          isolate->factory()->InternalizeString(base::StaticCharVector("type")),
810          WasmValueObject::kTypeIndex, FROZEN, Representation::Tagged());
811      map->AppendDescriptor(isolate, &d);
812    }
813    {  // value
814      Descriptor d = Descriptor::DataField(
815          isolate,
816          isolate->factory()->InternalizeString(
817              base::StaticCharVector("value")),
818          WasmValueObject::kValueIndex, FROZEN, Representation::Tagged());
819      map->AppendDescriptor(isolate, &d);
820    }
821    map->set_is_extensible(false);
822    maps->set(kWasmValueMapIndex, *map);
823  }
824  Handle<Map> value_map =
825      handle(Map::cast(maps->get(kWasmValueMapIndex)), isolate);
826  Handle<WasmValueObject> object = Handle<WasmValueObject>::cast(
827      isolate->factory()->NewJSObjectFromMap(value_map));
828  object->set_type(*type);
829  object->set_value(*value);
830  return object;
831}
832
833// This class implements a proxy for a single inspectable Wasm struct.
834struct StructProxy : NamedDebugProxy<StructProxy, kStructProxy, FixedArray> {
835  static constexpr char const* kClassName = "Struct";
836
837  static const int kObjectIndex = 0;
838  static const int kModuleIndex = 1;
839  static const int kTypeIndexIndex = 2;
840  static const int kLength = 3;
841
842  static Handle<JSObject> Create(Isolate* isolate, const wasm::WasmValue& value,
843                                 Handle<WasmModuleObject> module) {
844    Handle<FixedArray> data = isolate->factory()->NewFixedArray(kLength);
845    data->set(kObjectIndex, *value.to_ref());
846    data->set(kModuleIndex, *module);
847    int struct_type_index = value.type().ref_index();
848    data->set(kTypeIndexIndex, Smi::FromInt(struct_type_index));
849    return NamedDebugProxy::Create(isolate, data);
850  }
851
852  static uint32_t Count(Isolate* isolate, Handle<FixedArray> data) {
853    return WasmStruct::cast(data->get(kObjectIndex)).type()->field_count();
854  }
855
856  static Handle<Object> Get(Isolate* isolate, Handle<FixedArray> data,
857                            uint32_t index) {
858    Handle<WasmStruct> obj(WasmStruct::cast(data->get(kObjectIndex)), isolate);
859    Handle<WasmModuleObject> module(
860        WasmModuleObject::cast(data->get(kModuleIndex)), isolate);
861    return WasmValueObject::New(isolate, obj->GetFieldValue(index), module);
862  }
863
864  static Handle<String> GetName(Isolate* isolate, Handle<FixedArray> data,
865                                uint32_t index) {
866    wasm::NativeModule* native_module =
867        WasmModuleObject::cast(data->get(kModuleIndex)).native_module();
868    int struct_type_index = Smi::ToInt(Smi::cast(data->get(kTypeIndexIndex)));
869    wasm::ModuleWireBytes module_wire_bytes(native_module->wire_bytes());
870    base::Vector<const char> name_vec = module_wire_bytes.GetNameOrNull(
871        native_module->GetDebugInfo()->GetFieldName(struct_type_index, index));
872    return GetNameOrDefault(
873        isolate,
874        name_vec.empty() ? MaybeHandle<String>()
875                         : isolate->factory()->NewStringFromUtf8(name_vec),
876        "$field", index);
877  }
878};
879
880// This class implements a proxy for a single inspectable Wasm array.
881struct ArrayProxy : IndexedDebugProxy<ArrayProxy, kArrayProxy, FixedArray> {
882  static constexpr char const* kClassName = "Array";
883
884  static const int kObjectIndex = 0;
885  static const int kModuleIndex = 1;
886  static const int kLength = 2;
887
888  static Handle<JSObject> Create(Isolate* isolate, const wasm::WasmValue& value,
889                                 Handle<WasmModuleObject> module) {
890    Handle<FixedArray> data = isolate->factory()->NewFixedArray(kLength);
891    data->set(kObjectIndex, *value.to_ref());
892    data->set(kModuleIndex, *module);
893    Handle<JSObject> proxy = IndexedDebugProxy::Create(
894        isolate, data, false /* leave map extensible */);
895    uint32_t length = WasmArray::cast(*value.to_ref()).length();
896    Handle<Object> length_obj = isolate->factory()->NewNumberFromUint(length);
897    Object::SetProperty(isolate, proxy, isolate->factory()->length_string(),
898                        length_obj, StoreOrigin::kNamed,
899                        Just(ShouldThrow::kThrowOnError))
900        .Check();
901    return proxy;
902  }
903
904  static v8::Local<v8::FunctionTemplate> CreateTemplate(v8::Isolate* isolate) {
905    Local<v8::FunctionTemplate> templ =
906        IndexedDebugProxy::CreateTemplate(isolate);
907    templ->InstanceTemplate()->Set(isolate, "length",
908                                   v8::Number::New(isolate, 0));
909    return templ;
910  }
911
912  static uint32_t Count(Isolate* isolate, Handle<FixedArray> data) {
913    return WasmArray::cast(data->get(kObjectIndex)).length();
914  }
915
916  static Handle<Object> Get(Isolate* isolate, Handle<FixedArray> data,
917                            uint32_t index) {
918    Handle<WasmArray> array(WasmArray::cast(data->get(kObjectIndex)), isolate);
919    Handle<WasmModuleObject> module(
920        WasmModuleObject::cast(data->get(kModuleIndex)), isolate);
921    return WasmValueObject::New(isolate, array->GetElement(index), module);
922  }
923};
924
925// static
926Handle<WasmValueObject> WasmValueObject::New(
927    Isolate* isolate, const wasm::WasmValue& value,
928    Handle<WasmModuleObject> module_object) {
929  Handle<String> t;
930  Handle<Object> v;
931  switch (value.type().kind()) {
932    case wasm::kI8: {
933      // This can't be reached for most "top-level" things, only via nested
934      // calls for struct/array fields.
935      t = isolate->factory()->InternalizeString(base::StaticCharVector("i8"));
936      v = isolate->factory()->NewNumber(value.to_i8_unchecked());
937      break;
938    }
939    case wasm::kI16: {
940      // This can't be reached for most "top-level" things, only via nested
941      // calls for struct/array fields.
942      t = isolate->factory()->InternalizeString(base::StaticCharVector("i16"));
943      v = isolate->factory()->NewNumber(value.to_i16_unchecked());
944      break;
945    }
946    case wasm::kI32: {
947      t = isolate->factory()->InternalizeString(base::StaticCharVector("i32"));
948      v = isolate->factory()->NewNumberFromInt(value.to_i32_unchecked());
949      break;
950    }
951    case wasm::kI64: {
952      t = isolate->factory()->InternalizeString(base::StaticCharVector("i64"));
953      v = BigInt::FromInt64(isolate, value.to_i64_unchecked());
954      break;
955    }
956    case wasm::kF32: {
957      t = isolate->factory()->InternalizeString(base::StaticCharVector("f32"));
958      v = isolate->factory()->NewNumber(value.to_f32_unchecked());
959      break;
960    }
961    case wasm::kF64: {
962      t = isolate->factory()->InternalizeString(base::StaticCharVector("f64"));
963      v = isolate->factory()->NewNumber(value.to_f64_unchecked());
964      break;
965    }
966    case wasm::kS128: {
967      t = isolate->factory()->InternalizeString(base::StaticCharVector("v128"));
968      v = WasmSimd128ToString(isolate, value.to_s128_unchecked());
969      break;
970    }
971    case wasm::kOptRef:
972    case wasm::kRef: {
973      t = GetRefTypeName(isolate, value.type(), module_object->native_module());
974      Handle<Object> ref = value.to_ref();
975      if (ref->IsWasmStruct()) {
976        v = StructProxy::Create(isolate, value, module_object);
977      } else if (ref->IsWasmArray()) {
978        v = ArrayProxy::Create(isolate, value, module_object);
979      } else if (ref->IsWasmInternalFunction()) {
980        v = handle(Handle<WasmInternalFunction>::cast(ref)->external(),
981                   isolate);
982      } else if (ref->IsJSFunction() || ref->IsSmi() || ref->IsNull() ||
983                 value.type().is_reference_to(wasm::HeapType::kAny)) {
984        v = ref;
985      } else {
986        // Fail gracefully.
987        base::EmbeddedVector<char, 64> error;
988        int len = SNPrintF(error, "unimplemented object type: %d",
989                           HeapObject::cast(*ref).map().instance_type());
990        v = isolate->factory()->InternalizeString(error.SubVector(0, len));
991      }
992      break;
993    }
994    case wasm::kRtt: {
995      // TODO(7748): Expose RTTs to DevTools.
996      t = isolate->factory()->InternalizeString(base::StaticCharVector("rtt"));
997      v = isolate->factory()->InternalizeString(
998          base::StaticCharVector("(unimplemented)"));
999      break;
1000    }
1001    case wasm::kVoid:
1002    case wasm::kBottom:
1003      UNREACHABLE();
1004  }
1005  return New(isolate, t, v);
1006}
1007
1008Handle<JSObject> GetWasmDebugProxy(WasmFrame* frame) {
1009  return ContextProxy::Create(frame);
1010}
1011
1012std::unique_ptr<debug::ScopeIterator> GetWasmScopeIterator(WasmFrame* frame) {
1013  return std::make_unique<DebugWasmScopeIterator>(frame);
1014}
1015
1016Handle<String> GetWasmFunctionDebugName(Isolate* isolate,
1017                                        Handle<WasmInstanceObject> instance,
1018                                        uint32_t func_index) {
1019  Handle<WasmModuleObject> module_object(instance->module_object(), isolate);
1020  MaybeHandle<String> maybe_name = WasmModuleObject::GetFunctionNameOrNull(
1021      isolate, module_object, func_index);
1022  if (module_object->is_asm_js()) {
1023    // In case of asm.js, we use the names from the function declarations.
1024    return maybe_name.ToHandleChecked();
1025  }
1026  if (maybe_name.is_null()) {
1027    maybe_name = GetNameFromImportsAndExportsOrNull(
1028        isolate, instance, wasm::ImportExportKindCode::kExternalFunction,
1029        func_index);
1030  }
1031  return GetNameOrDefault(isolate, maybe_name, "$func", func_index);
1032}
1033
1034Handle<ArrayList> AddWasmInstanceObjectInternalProperties(
1035    Isolate* isolate, Handle<ArrayList> result,
1036    Handle<WasmInstanceObject> instance) {
1037  result = ArrayList::Add(
1038      isolate, result,
1039      isolate->factory()->NewStringFromAsciiChecked("[[Module]]"),
1040      handle(instance->module_object(), isolate));
1041
1042  if (FunctionsProxy::Count(isolate, instance) != 0) {
1043    result = ArrayList::Add(
1044        isolate, result,
1045        isolate->factory()->NewStringFromAsciiChecked("[[Functions]]"),
1046        GetOrCreateInstanceProxy<FunctionsProxy>(isolate, instance));
1047  }
1048
1049  if (GlobalsProxy::Count(isolate, instance) != 0) {
1050    result = ArrayList::Add(
1051        isolate, result,
1052        isolate->factory()->NewStringFromAsciiChecked("[[Globals]]"),
1053        GetOrCreateInstanceProxy<GlobalsProxy>(isolate, instance));
1054  }
1055
1056  if (MemoriesProxy::Count(isolate, instance) != 0) {
1057    result = ArrayList::Add(
1058        isolate, result,
1059        isolate->factory()->NewStringFromAsciiChecked("[[Memories]]"),
1060        GetOrCreateInstanceProxy<MemoriesProxy>(isolate, instance));
1061  }
1062
1063  if (TablesProxy::Count(isolate, instance) != 0) {
1064    result = ArrayList::Add(
1065        isolate, result,
1066        isolate->factory()->NewStringFromAsciiChecked("[[Tables]]"),
1067        GetOrCreateInstanceProxy<TablesProxy>(isolate, instance));
1068  }
1069
1070  return result;
1071}
1072
1073Handle<ArrayList> AddWasmModuleObjectInternalProperties(
1074    Isolate* isolate, Handle<ArrayList> result,
1075    Handle<WasmModuleObject> module_object) {
1076  result = ArrayList::Add(
1077      isolate, result,
1078      isolate->factory()->NewStringFromStaticChars("[[Exports]]"),
1079      wasm::GetExports(isolate, module_object));
1080  result = ArrayList::Add(
1081      isolate, result,
1082      isolate->factory()->NewStringFromStaticChars("[[Imports]]"),
1083      wasm::GetImports(isolate, module_object));
1084  return result;
1085}
1086
1087Handle<ArrayList> AddWasmTableObjectInternalProperties(
1088    Isolate* isolate, Handle<ArrayList> result, Handle<WasmTableObject> table) {
1089  int length = table->current_length();
1090  Handle<FixedArray> entries = isolate->factory()->NewFixedArray(length);
1091  for (int i = 0; i < length; ++i) {
1092    Handle<Object> entry = WasmTableObject::Get(isolate, table, i);
1093    if (entry->IsWasmInternalFunction()) {
1094      entry = handle(Handle<WasmInternalFunction>::cast(entry)->external(),
1095                     isolate);
1096    }
1097    entries->set(i, *entry);
1098  }
1099  Handle<JSArray> final_entries = isolate->factory()->NewJSArrayWithElements(
1100      entries, i::PACKED_ELEMENTS, length);
1101  JSObject::SetPrototype(isolate, final_entries,
1102                         isolate->factory()->null_value(), false, kDontThrow)
1103      .Check();
1104  Handle<String> entries_string =
1105      isolate->factory()->NewStringFromStaticChars("[[Entries]]");
1106  result = ArrayList::Add(isolate, result, entries_string, final_entries);
1107  return result;
1108}
1109
1110}  // namespace internal
1111}  // namespace v8
1112