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