1 // Copyright 2020 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 "tools/v8windbg/src/object-inspection.h"
6
7 #include "src/flags/flags.h"
8 #include "tools/v8windbg/base/utilities.h"
9 #include "tools/v8windbg/src/v8-debug-helper-interop.h"
10 #include "tools/v8windbg/src/v8windbg-extension.h"
11
V8CachedObject(Location location,std::string uncompressed_type_name,WRL::ComPtr<IDebugHostContext> context,bool is_compressed)12 V8CachedObject::V8CachedObject(Location location,
13 std::string uncompressed_type_name,
14 WRL::ComPtr<IDebugHostContext> context,
15 bool is_compressed)
16 : location_(std::move(location)),
17 uncompressed_type_name_(std::move(uncompressed_type_name)),
18 context_(std::move(context)),
19 is_compressed_(is_compressed) {}
20
Create(IModelObject * p_v8_object_instance,IV8CachedObject ** result)21 HRESULT V8CachedObject::Create(IModelObject* p_v8_object_instance,
22 IV8CachedObject** result) {
23 Location location;
24 RETURN_IF_FAIL(p_v8_object_instance->GetLocation(&location));
25
26 WRL::ComPtr<IDebugHostContext> context;
27 RETURN_IF_FAIL(p_v8_object_instance->GetContext(&context));
28
29 WRL::ComPtr<IDebugHostType> sp_type;
30 _bstr_t type_name;
31 RETURN_IF_FAIL(p_v8_object_instance->GetTypeInfo(&sp_type));
32 RETURN_IF_FAIL(sp_type->GetName(type_name.GetAddress()));
33
34 // If the object is of type v8::internal::TaggedValue, and this build uses
35 // compressed pointers, then the value is compressed. Other types such as
36 // v8::internal::Object represent uncompressed tagged values.
37 bool is_compressed =
38 COMPRESS_POINTERS_BOOL &&
39 static_cast<const char*>(type_name) == std::string(kTaggedValue);
40
41 const char* uncompressed_type_name =
42 is_compressed ? kObject : static_cast<const char*>(type_name);
43
44 *result = WRL::Make<V8CachedObject>(location, uncompressed_type_name, context,
45 is_compressed)
46 .Detach();
47 return S_OK;
48 }
49
V8CachedObject(V8HeapObject heap_object)50 V8CachedObject::V8CachedObject(V8HeapObject heap_object)
51 : heap_object_(std::move(heap_object)), heap_object_initialized_(true) {}
52
53 V8CachedObject::~V8CachedObject() = default;
54
GetCachedV8HeapObject(V8HeapObject ** pp_heap_object)55 IFACEMETHODIMP V8CachedObject::GetCachedV8HeapObject(
56 V8HeapObject** pp_heap_object) noexcept {
57 if (!heap_object_initialized_) {
58 heap_object_initialized_ = true;
59 uint64_t tagged_ptr = 0;
60 uint64_t bytes_read;
61 HRESULT hr = sp_debug_host_memory->ReadBytes(
62 context_.Get(), location_, reinterpret_cast<void*>(&tagged_ptr),
63 is_compressed_ ? i::kTaggedSize : sizeof(void*), &bytes_read);
64 // S_FALSE can be returned if fewer bytes were read than were requested. We
65 // need all of the bytes, so check for S_OK.
66 if (hr != S_OK) {
67 std::stringstream message;
68 message << "Unable to read memory";
69 if (location_.IsVirtualAddress()) {
70 message << " at 0x" << std::hex << location_.GetOffset();
71 }
72 heap_object_.friendly_name = ConvertToU16String(message.str());
73 } else {
74 if (is_compressed_)
75 tagged_ptr = ExpandCompressedPointer(static_cast<uint32_t>(tagged_ptr));
76 heap_object_ =
77 ::GetHeapObject(context_, tagged_ptr, location_.GetOffset(),
78 uncompressed_type_name_.c_str(), is_compressed_);
79 }
80 }
81 *pp_heap_object = &this->heap_object_;
82 return S_OK;
83 }
84
IndexedFieldData(Property property)85 IndexedFieldData::IndexedFieldData(Property property)
86 : property_(std::move(property)) {}
87
88 IndexedFieldData::~IndexedFieldData() = default;
89
GetProperty(Property ** property)90 IFACEMETHODIMP IndexedFieldData::GetProperty(Property** property) noexcept {
91 if (!property) return E_POINTER;
92 *property = &this->property_;
93 return S_OK;
94 }
95
V8ObjectKeyEnumerator(WRL::ComPtr<IV8CachedObject> & v8_cached_object)96 V8ObjectKeyEnumerator::V8ObjectKeyEnumerator(
97 WRL::ComPtr<IV8CachedObject>& v8_cached_object)
98 : sp_v8_cached_object_{v8_cached_object} {}
99 V8ObjectKeyEnumerator::~V8ObjectKeyEnumerator() = default;
100
Reset()101 IFACEMETHODIMP V8ObjectKeyEnumerator::Reset() noexcept {
102 index_ = 0;
103 return S_OK;
104 }
105
GetNext(BSTR * key,IModelObject ** value,IKeyStore ** metadata)106 IFACEMETHODIMP V8ObjectKeyEnumerator::GetNext(BSTR* key, IModelObject** value,
107 IKeyStore** metadata) noexcept {
108 V8HeapObject* p_v8_heap_object;
109 sp_v8_cached_object_->GetCachedV8HeapObject(&p_v8_heap_object);
110
111 if (static_cast<size_t>(index_) >= p_v8_heap_object->properties.size())
112 return E_BOUNDS;
113
114 auto* name_ptr = p_v8_heap_object->properties[index_].name.c_str();
115 *key = ::SysAllocString(U16ToWChar(name_ptr));
116 ++index_;
117 return S_OK;
118 }
119
InitializeObject(IModelObject * model_object,IDebugHostTypeSignature * matching_type_signature,IDebugHostSymbolEnumerator * wildcard_matches)120 IFACEMETHODIMP V8LocalDataModel::InitializeObject(
121 IModelObject* model_object,
122 IDebugHostTypeSignature* matching_type_signature,
123 IDebugHostSymbolEnumerator* wildcard_matches) noexcept {
124 return S_OK;
125 }
126
GetName(BSTR * model_name)127 IFACEMETHODIMP V8LocalDataModel::GetName(BSTR* model_name) noexcept {
128 return E_NOTIMPL;
129 }
130
InitializeObject(IModelObject * model_object,IDebugHostTypeSignature * matching_type_signature,IDebugHostSymbolEnumerator * wildcard_matches)131 IFACEMETHODIMP V8ObjectDataModel::InitializeObject(
132 IModelObject* model_object,
133 IDebugHostTypeSignature* matching_type_signature,
134 IDebugHostSymbolEnumerator* wildcard_matches) noexcept {
135 return S_OK;
136 }
137
GetName(BSTR * model_name)138 IFACEMETHODIMP V8ObjectDataModel::GetName(BSTR* model_name) noexcept {
139 return E_NOTIMPL;
140 }
141
ToDisplayString(IModelObject * context_object,IKeyStore * metadata,BSTR * display_string)142 IFACEMETHODIMP V8ObjectDataModel::ToDisplayString(
143 IModelObject* context_object, IKeyStore* metadata,
144 BSTR* display_string) noexcept {
145 WRL::ComPtr<IV8CachedObject> sp_v8_cached_object;
146 RETURN_IF_FAIL(GetCachedObject(context_object, &sp_v8_cached_object));
147 V8HeapObject* p_v8_heap_object;
148 RETURN_IF_FAIL(sp_v8_cached_object->GetCachedV8HeapObject(&p_v8_heap_object));
149 *display_string = ::SysAllocString(
150 reinterpret_cast<const wchar_t*>(p_v8_heap_object->friendly_name.data()));
151 return S_OK;
152 }
153
154 namespace {
155
156 // Creates a synthetic object, attaches a parent model, and sets the context
157 // object for that parent data model. Caller is responsible for ensuring that
158 // the parent model's Concepts have been initialized correctly and that the
159 // data model context is of an appropriate type for the parent model.
CreateSyntheticObjectWithParentAndDataContext(IDebugHostContext * ctx,IModelObject * parent_model,IUnknown * data_context,IModelObject ** result)160 HRESULT CreateSyntheticObjectWithParentAndDataContext(
161 IDebugHostContext* ctx, IModelObject* parent_model, IUnknown* data_context,
162 IModelObject** result) {
163 WRL::ComPtr<IModelObject> value;
164 RETURN_IF_FAIL(sp_data_model_manager->CreateSyntheticObject(ctx, &value));
165 RETURN_IF_FAIL(
166 value->AddParentModel(parent_model, nullptr, true /*override*/));
167 RETURN_IF_FAIL(value->SetContextForDataModel(parent_model, data_context));
168 *result = value.Detach();
169 return S_OK;
170 }
171
172 // Creates an IModelObject for a V8 object whose value is represented by the
173 // data in cached_object. This is an alternative to CreateTypedObject for
174 // particularly complex cases (compressed values and those that don't exist
175 // anywhere in memory).
CreateSyntheticObjectForV8Object(IDebugHostContext * ctx,V8CachedObject * cached_object,IModelObject ** result)176 HRESULT CreateSyntheticObjectForV8Object(IDebugHostContext* ctx,
177 V8CachedObject* cached_object,
178 IModelObject** result) {
179 // Explicitly add the parent model and data context. On a plain typed object,
180 // the parent model would be attached automatically because we registered for
181 // a matching type signature, and the data context would be set during
182 // V8ObjectDataModel::GetCachedObject.
183 return CreateSyntheticObjectWithParentAndDataContext(
184 ctx, Extension::Current()->GetObjectDataModel(), cached_object, result);
185 }
186
187 // Creates an IModelObject to represent a field that is not a struct or array.
GetModelForBasicField(const uint64_t address,const std::u16string & type_name,const std::string & uncompressed_type_name,WRL::ComPtr<IDebugHostContext> & sp_ctx,IModelObject ** result)188 HRESULT GetModelForBasicField(const uint64_t address,
189 const std::u16string& type_name,
190 const std::string& uncompressed_type_name,
191 WRL::ComPtr<IDebugHostContext>& sp_ctx,
192 IModelObject** result) {
193 if (type_name == ConvertToU16String(uncompressed_type_name)) {
194 // For untagged and uncompressed tagged fields, create an IModelObject
195 // representing a normal native data type.
196 WRL::ComPtr<IDebugHostType> type =
197 Extension::Current()->GetTypeFromV8Module(sp_ctx, type_name.c_str());
198 if (type == nullptr) return E_FAIL;
199 return sp_data_model_manager->CreateTypedObject(
200 sp_ctx.Get(), Location{address}, type.Get(), result);
201 }
202
203 // For compressed tagged fields, we need to do something a little more
204 // complicated. We could just use CreateTypedObject with the type
205 // v8::internal::TaggedValue, but then we'd sacrifice any other data
206 // that we've learned about the field's specific type. So instead we
207 // create a synthetic object.
208 WRL::ComPtr<V8CachedObject> cached_object = WRL::Make<V8CachedObject>(
209 Location(address), uncompressed_type_name, sp_ctx,
210 /*is_compressed=*/true);
211 return CreateSyntheticObjectForV8Object(sp_ctx.Get(), cached_object.Get(),
212 result);
213 }
214
215 // Creates an IModelObject representing the value of a bitfield.
GetModelForBitField(uint64_t address,const uint8_t num_bits,uint8_t shift_bits,const std::u16string & type_name,WRL::ComPtr<IDebugHostContext> & sp_ctx,IModelObject ** result)216 HRESULT GetModelForBitField(uint64_t address, const uint8_t num_bits,
217 uint8_t shift_bits, const std::u16string& type_name,
218 WRL::ComPtr<IDebugHostContext>& sp_ctx,
219 IModelObject** result) {
220 // Look up the type by name.
221 WRL::ComPtr<IDebugHostType> type =
222 Extension::Current()->GetTypeFromV8Module(sp_ctx, type_name.c_str());
223 if (type == nullptr) return E_FAIL;
224
225 // Figure out exactly which bytes contain the bitfield's data. This depends on
226 // platform byte order (little-endian for Windows).
227 constexpr int kBitsPerByte = 8;
228 uint8_t shift_bytes = shift_bits / kBitsPerByte;
229 address += shift_bytes;
230 shift_bits -= shift_bytes * kBitsPerByte;
231 size_t bits_to_read = shift_bits + num_bits;
232 size_t bytes_to_read = (bits_to_read + kBitsPerByte - 1) / kBitsPerByte;
233
234 uintptr_t value = 0;
235
236 // V8 guarantees that bitfield structs are no bigger than a single pointer.
237 if (bytes_to_read > sizeof(value)) {
238 std::stringstream message;
239 message << "Fatal v8windbg error: found bitfield struct of "
240 << bytes_to_read << "bytes, which exceeds the supported size of "
241 << sizeof(value);
242 return CreateString(ConvertToU16String(message.str()), result);
243 }
244
245 uint64_t bytes_read;
246 HRESULT hr = sp_debug_host_memory->ReadBytes(sp_ctx.Get(), address,
247 reinterpret_cast<void*>(&value),
248 bytes_to_read, &bytes_read);
249
250 // S_FALSE can be returned if fewer bytes were read than were requested. We
251 // need all of the bytes, so check for S_OK.
252 if (hr != S_OK) {
253 std::stringstream message;
254 message << "Unable to read memory at 0x" << std::hex << address;
255 return CreateString(ConvertToU16String(message.str()), result);
256 }
257
258 // Decode the bitfield.
259 value = (value >> shift_bits) & ((1 << num_bits) - 1);
260
261 return CreateTypedIntrinsic(value, type.Get(), result);
262 }
263
264 // Creates an IModelObject to represent the packed fields in a Torque struct.
265 // Note that Torque structs are not C++ structs and do not have any type
266 // definitions in the V8 symbols.
GetModelForStruct(const uint64_t address,const std::vector<StructField> & fields,WRL::ComPtr<IDebugHostContext> & sp_ctx,IModelObject ** result)267 HRESULT GetModelForStruct(const uint64_t address,
268 const std::vector<StructField>& fields,
269 WRL::ComPtr<IDebugHostContext>& sp_ctx,
270 IModelObject** result) {
271 WRL::ComPtr<IModelObject> sp_value;
272 RETURN_IF_FAIL(
273 sp_data_model_manager->CreateSyntheticObject(sp_ctx.Get(), &sp_value));
274
275 // There's no need for any fancy Concepts here; just add key-value pairs for
276 // each field.
277 for (const StructField& field : fields) {
278 WRL::ComPtr<IModelObject> field_model;
279 if (field.num_bits == 0) {
280 RETURN_IF_FAIL(GetModelForBasicField(
281 address + field.offset, field.type_name, field.uncompressed_type_name,
282 sp_ctx, &field_model));
283 } else {
284 RETURN_IF_FAIL(GetModelForBitField(address + field.offset, field.num_bits,
285 field.shift_bits, field.type_name,
286 sp_ctx, &field_model));
287 }
288 RETURN_IF_FAIL(
289 sp_value->SetKey(reinterpret_cast<const wchar_t*>(field.name.c_str()),
290 field_model.Get(), nullptr));
291 }
292
293 *result = sp_value.Detach();
294 return S_OK;
295 }
296
297 // Creates an IModelObject representing an array of some type that we expect to
298 // be defined in the V8 symbols.
GetModelForNativeArray(const uint64_t address,const std::u16string & type_name,size_t count,WRL::ComPtr<IDebugHostContext> & sp_ctx,IModelObject ** result)299 HRESULT GetModelForNativeArray(const uint64_t address,
300 const std::u16string& type_name, size_t count,
301 WRL::ComPtr<IDebugHostContext>& sp_ctx,
302 IModelObject** result) {
303 WRL::ComPtr<IDebugHostType> type =
304 Extension::Current()->GetTypeFromV8Module(sp_ctx, type_name.c_str());
305 if (type == nullptr) return E_FAIL;
306
307 ULONG64 object_size{};
308 RETURN_IF_FAIL(type->GetSize(&object_size));
309
310 ArrayDimension dimensions[] = {
311 {/*start=*/0, /*length=*/count, /*stride=*/object_size}};
312 WRL::ComPtr<IDebugHostType> array_type;
313 RETURN_IF_FAIL(
314 type->CreateArrayOf(/*dimensions=*/1, dimensions, &array_type));
315
316 return sp_data_model_manager->CreateTypedObject(
317 sp_ctx.Get(), Location{address}, array_type.Get(), result);
318 }
319
320 // Creates an IModelObject that represents an array of structs or compressed
321 // tagged values.
GetModelForCustomArray(const Property & prop,WRL::ComPtr<IDebugHostContext> & sp_ctx,IModelObject ** result)322 HRESULT GetModelForCustomArray(const Property& prop,
323 WRL::ComPtr<IDebugHostContext>& sp_ctx,
324 IModelObject** result) {
325 // Create the context which should be provided to the indexing and iterating
326 // functionality provided by the parent model. This is instance-specific data,
327 // whereas the parent model object could be shared among many custom arrays.
328 WRL::ComPtr<IndexedFieldData> context_data =
329 WRL::Make<IndexedFieldData>(prop);
330
331 return CreateSyntheticObjectWithParentAndDataContext(
332 sp_ctx.Get(), Extension::Current()->GetIndexedFieldDataModel(),
333 context_data.Get(), result);
334 }
335
336
337 // Creates an IModelObject representing the data in an array at the given index.
338 // context_object is expected to be an object of the form created by
339 // GetModelForCustomArray, meaning its context for the IndexedFieldParent data
340 // model is an IIndexedFieldData containing the description of the array.
GetModelForCustomArrayElement(IModelObject * context_object,size_t index,IModelObject ** object)341 HRESULT GetModelForCustomArrayElement(IModelObject* context_object,
342 size_t index, IModelObject** object) {
343 // Open a few layers of wrapper objects to get to the Property object that
344 // describes the array.
345 WRL::ComPtr<IUnknown> data_model_context;
346 RETURN_IF_FAIL(context_object->GetContextForDataModel(
347 Extension::Current()->GetIndexedFieldDataModel(), &data_model_context));
348 WRL::ComPtr<IIndexedFieldData> indexed_field_data;
349 RETURN_IF_FAIL(data_model_context.As(&indexed_field_data));
350 Property* prop;
351 RETURN_IF_FAIL(indexed_field_data->GetProperty(&prop));
352
353 if (index >= prop->length) {
354 return E_BOUNDS;
355 }
356
357 WRL::ComPtr<IDebugHostContext> sp_ctx;
358 RETURN_IF_FAIL(context_object->GetContext(&sp_ctx));
359
360 ULONG64 address = prop->addr_value + index * prop->item_size;
361
362 switch (prop->type) {
363 case PropertyType::kArray:
364 return GetModelForBasicField(address, prop->type_name,
365 prop->uncompressed_type_name, sp_ctx,
366 object);
367 case PropertyType::kStructArray:
368 return GetModelForStruct(address, prop->fields, sp_ctx, object);
369 default:
370 return E_FAIL; // Only array properties should be possible here.
371 }
372 }
373
374 } // namespace
375
InitializeObject(IModelObject * model_object,IDebugHostTypeSignature * matching_type_signature,IDebugHostSymbolEnumerator * wildcard_matches)376 IFACEMETHODIMP IndexedFieldParent::InitializeObject(
377 IModelObject* model_object,
378 IDebugHostTypeSignature* matching_type_signature,
379 IDebugHostSymbolEnumerator* wildcard_matches) noexcept {
380 return S_OK;
381 }
382
GetName(BSTR * model_name)383 IFACEMETHODIMP IndexedFieldParent::GetName(BSTR* model_name) noexcept {
384 return E_NOTIMPL;
385 }
386
GetDimensionality(IModelObject * context_object,ULONG64 * dimensionality)387 IFACEMETHODIMP IndexedFieldParent::GetDimensionality(
388 IModelObject* context_object, ULONG64* dimensionality) noexcept {
389 *dimensionality = 1;
390 return S_OK;
391 }
392
GetAt(IModelObject * context_object,ULONG64 indexer_count,IModelObject ** indexers,IModelObject ** object,IKeyStore ** metadata)393 IFACEMETHODIMP IndexedFieldParent::GetAt(IModelObject* context_object,
394 ULONG64 indexer_count,
395 IModelObject** indexers,
396 IModelObject** object,
397 IKeyStore** metadata) noexcept {
398 if (indexer_count != 1) return E_INVALIDARG;
399 if (metadata != nullptr) *metadata = nullptr;
400
401 ULONG64 index;
402 RETURN_IF_FAIL(UnboxULong64(indexers[0], &index, /*convert=*/true));
403
404 return GetModelForCustomArrayElement(context_object, index, object);
405 }
406
SetAt(IModelObject * context_object,ULONG64 indexer_count,IModelObject ** indexers,IModelObject * value)407 IFACEMETHODIMP IndexedFieldParent::SetAt(IModelObject* context_object,
408 ULONG64 indexer_count,
409 IModelObject** indexers,
410 IModelObject* value) noexcept {
411 return E_NOTIMPL;
412 }
413
GetDefaultIndexDimensionality(IModelObject * context_object,ULONG64 * dimensionality)414 IFACEMETHODIMP IndexedFieldParent::GetDefaultIndexDimensionality(
415 IModelObject* context_object, ULONG64* dimensionality) noexcept {
416 *dimensionality = 1;
417 return S_OK;
418 }
419
GetIterator(IModelObject * context_object,IModelIterator ** iterator)420 IFACEMETHODIMP IndexedFieldParent::GetIterator(
421 IModelObject* context_object, IModelIterator** iterator) noexcept {
422 auto indexed_field_iterator{WRL::Make<IndexedFieldIterator>(context_object)};
423 *iterator = indexed_field_iterator.Detach();
424 return S_OK;
425 }
426
IndexedFieldIterator(IModelObject * context_object)427 IndexedFieldIterator::IndexedFieldIterator(IModelObject* context_object)
428 : context_object_(context_object) {}
429 IndexedFieldIterator::~IndexedFieldIterator() = default;
430
Reset()431 IFACEMETHODIMP IndexedFieldIterator::Reset() noexcept {
432 next_ = 0;
433 return S_OK;
434 }
435
GetNext(IModelObject ** object,ULONG64 dimensions,IModelObject ** indexers,IKeyStore ** metadata)436 IFACEMETHODIMP IndexedFieldIterator::GetNext(IModelObject** object,
437 ULONG64 dimensions,
438 IModelObject** indexers,
439 IKeyStore** metadata) noexcept {
440 if (dimensions > 1) return E_INVALIDARG;
441
442 WRL::ComPtr<IModelObject> sp_index, sp_value;
443 RETURN_IF_FAIL(
444 GetModelForCustomArrayElement(context_object_.Get(), next_, &sp_value));
445 RETURN_IF_FAIL(CreateULong64(next_, &sp_index));
446
447 // Everything that could fail (including the bounds check) has succeeded, so
448 // increment the index.
449 ++next_;
450
451 // Write results (none of these steps can fail, which is important because we
452 // transfer ownership of two separate objects).
453 if (dimensions == 1) {
454 indexers[0] = sp_index.Detach();
455 }
456 *object = sp_value.Detach();
457 if (metadata != nullptr) *metadata = nullptr;
458 return S_OK;
459 }
460
GetKey(IModelObject * context_object,PCWSTR key,IModelObject ** key_value,IKeyStore ** metadata,bool * has_key)461 IFACEMETHODIMP V8ObjectDataModel::GetKey(IModelObject* context_object,
462 PCWSTR key, IModelObject** key_value,
463 IKeyStore** metadata,
464 bool* has_key) noexcept {
465 if (metadata != nullptr) *metadata = nullptr;
466
467 WRL::ComPtr<IV8CachedObject> sp_v8_cached_object;
468 RETURN_IF_FAIL(GetCachedObject(context_object, &sp_v8_cached_object));
469 V8HeapObject* p_v8_heap_object;
470 RETURN_IF_FAIL(sp_v8_cached_object->GetCachedV8HeapObject(&p_v8_heap_object));
471
472 *has_key = false;
473 for (const auto& prop : p_v8_heap_object->properties) {
474 const char16_t* p_key = reinterpret_cast<const char16_t*>(key);
475 if (prop.name.compare(p_key) == 0) {
476 *has_key = true;
477 if (key_value != nullptr) {
478 WRL::ComPtr<IDebugHostContext> sp_ctx;
479 RETURN_IF_FAIL(context_object->GetContext(&sp_ctx));
480 RETURN_IF_FAIL(GetModelForProperty(prop, sp_ctx, key_value));
481 }
482 return S_OK;
483 }
484 }
485
486 return S_OK;
487 }
488
SetKey(IModelObject * context_object,PCWSTR key,IModelObject * key_value,IKeyStore * metadata)489 IFACEMETHODIMP V8ObjectDataModel::SetKey(IModelObject* context_object,
490 PCWSTR key, IModelObject* key_value,
491 IKeyStore* metadata) noexcept {
492 return E_NOTIMPL;
493 }
494
EnumerateKeys(IModelObject * context_object,IKeyEnumerator ** pp_enumerator)495 IFACEMETHODIMP V8ObjectDataModel::EnumerateKeys(
496 IModelObject* context_object, IKeyEnumerator** pp_enumerator) noexcept {
497 WRL::ComPtr<IV8CachedObject> sp_v8_cached_object;
498 RETURN_IF_FAIL(GetCachedObject(context_object, &sp_v8_cached_object));
499
500 auto enumerator{WRL::Make<V8ObjectKeyEnumerator>(sp_v8_cached_object)};
501 *pp_enumerator = enumerator.Detach();
502 return S_OK;
503 }
504
GetValue(PCWSTR pwsz_key,IModelObject * p_v8_local_instance,IModelObject ** pp_value)505 IFACEMETHODIMP V8LocalValueProperty::GetValue(
506 PCWSTR pwsz_key, IModelObject* p_v8_local_instance,
507 IModelObject** pp_value) noexcept {
508 // Get the parametric type within v8::Local<*>
509 // Set value to a pointer to an instance of this type.
510
511 WRL::ComPtr<IDebugHostType> sp_type;
512 RETURN_IF_FAIL(p_v8_local_instance->GetTypeInfo(&sp_type));
513
514 bool is_generic;
515 RETURN_IF_FAIL(sp_type->IsGeneric(&is_generic));
516 if (!is_generic) return E_FAIL;
517
518 WRL::ComPtr<IDebugHostSymbol> sp_generic_arg;
519 RETURN_IF_FAIL(sp_type->GetGenericArgumentAt(0, &sp_generic_arg));
520
521 _bstr_t generic_name;
522 RETURN_IF_FAIL(sp_generic_arg->GetName(generic_name.GetAddress()));
523
524 WRL::ComPtr<IDebugHostContext> sp_ctx;
525 RETURN_IF_FAIL(p_v8_local_instance->GetContext(&sp_ctx));
526
527 WRL::ComPtr<IDebugHostType> sp_value_type =
528 Extension::Current()->GetTypeFromV8Module(
529 sp_ctx, reinterpret_cast<const char16_t*>(
530 static_cast<const wchar_t*>(generic_name)));
531 if (sp_value_type == nullptr ||
532 !Extension::Current()->DoesTypeDeriveFromObject(sp_value_type)) {
533 // The value type doesn't derive from v8::internal::Object (probably a
534 // public API type), so just use plain v8::internal::Object. We could
535 // consider mapping some public API types to their corresponding internal
536 // types here, at the possible cost of increased maintenance.
537 sp_value_type = Extension::Current()->GetV8ObjectType(sp_ctx);
538 }
539
540 Location loc;
541 RETURN_IF_FAIL(p_v8_local_instance->GetLocation(&loc));
542
543 // Read the pointer at the Object location
544 ULONG64 obj_address;
545 RETURN_IF_FAIL(
546 sp_debug_host_memory->ReadPointers(sp_ctx.Get(), loc, 1, &obj_address));
547
548 // If the val_ is a nullptr, then there is no value in the Local.
549 if (obj_address == 0) {
550 RETURN_IF_FAIL(CreateString(std::u16string{u"<empty>"}, pp_value));
551 } else {
552 // Should be a v8::internal::Object at the address
553 RETURN_IF_FAIL(sp_data_model_manager->CreateTypedObject(
554 sp_ctx.Get(), obj_address, sp_value_type.Get(), pp_value));
555 }
556
557 return S_OK;
558 }
559
SetValue(PCWSTR,IModelObject *,IModelObject *)560 IFACEMETHODIMP V8LocalValueProperty::SetValue(
561 PCWSTR /*pwsz_key*/, IModelObject* /*p_process_instance*/,
562 IModelObject* /*p_value*/) noexcept {
563 return E_NOTIMPL;
564 }
565
GetValue(PCWSTR pwsz_key,IModelObject * p_v8_compiler_node_instance,IModelObject ** pp_value)566 IFACEMETHODIMP V8InternalCompilerNodeIdProperty::GetValue(
567 PCWSTR pwsz_key, IModelObject* p_v8_compiler_node_instance,
568 IModelObject** pp_value) noexcept {
569 WRL::ComPtr<IModelObject> sp_bit_field;
570 RETURN_IF_FAIL(p_v8_compiler_node_instance->GetRawValue(
571 SymbolKind::SymbolField, L"bit_field_", RawSearchNone, &sp_bit_field));
572
573 uint64_t bit_field_value;
574 RETURN_IF_FAIL(
575 UnboxULong64(sp_bit_field.Get(), &bit_field_value, true /*convert*/));
576
577 WRL::ComPtr<IDebugHostContext> sp_host_context;
578 RETURN_IF_FAIL(p_v8_compiler_node_instance->GetContext(&sp_host_context));
579
580 WRL::ComPtr<IDebugHostType> sp_id_field_type;
581 RETURN_IF_FAIL(Extension::Current()
582 ->GetV8Module(sp_host_context)
583 ->FindTypeByName(L"v8::internal::compiler::Node::IdField",
584 &sp_id_field_type));
585
586 // Get 2nd template parameter as 24 in class.
587 // v8::base::BitField<v8::internal::compiler::NodeId, 0, 24>.
588 bool is_generic;
589 RETURN_IF_FAIL(sp_id_field_type->IsGeneric(&is_generic));
590 if (!is_generic) return E_FAIL;
591
592 WRL::ComPtr<IDebugHostSymbol> sp_k_size_arg;
593 RETURN_IF_FAIL(sp_id_field_type->GetGenericArgumentAt(2, &sp_k_size_arg));
594
595 WRL::ComPtr<IDebugHostConstant> sp_k_size_constant;
596 RETURN_IF_FAIL(sp_k_size_arg.As(&sp_k_size_constant));
597
598 int k_size;
599 RETURN_IF_FAIL(GetInt32(sp_k_size_constant.Get(), &k_size));
600
601 // Compute node_id.
602 uint32_t node_id = bit_field_value & (0xFFFFFFFF >> k_size);
603 RETURN_IF_FAIL(CreateUInt32(node_id, pp_value));
604
605 return S_OK;
606 }
607
SetValue(PCWSTR,IModelObject *,IModelObject *)608 IFACEMETHODIMP V8InternalCompilerNodeIdProperty::SetValue(
609 PCWSTR /*pwsz_key*/, IModelObject* /*p_process_instance*/,
610 IModelObject* /*p_value*/) noexcept {
611 return E_NOTIMPL;
612 }
613
GetValue(PCWSTR pwsz_key,IModelObject * p_v8_compiler_type_instance,IModelObject ** pp_value)614 IFACEMETHODIMP V8InternalCompilerBitsetNameProperty::GetValue(
615 PCWSTR pwsz_key, IModelObject* p_v8_compiler_type_instance,
616 IModelObject** pp_value) noexcept {
617 WRL::ComPtr<IModelObject> sp_payload;
618 RETURN_IF_FAIL(p_v8_compiler_type_instance->GetRawValue(
619 SymbolKind::SymbolField, L"payload_", RawSearchNone, &sp_payload));
620
621 uint64_t payload_value;
622 RETURN_IF_FAIL(
623 UnboxULong64(sp_payload.Get(), &payload_value, true /*convert*/));
624
625 const char* bitset_name = ::BitsetName(payload_value);
626 if (!bitset_name) return E_FAIL;
627 std::string name(bitset_name);
628 RETURN_IF_FAIL(CreateString(ConvertToU16String(name), pp_value));
629
630 return S_OK;
631 }
632
SetValue(PCWSTR,IModelObject *,IModelObject *)633 IFACEMETHODIMP V8InternalCompilerBitsetNameProperty::SetValue(
634 PCWSTR /*pwsz_key*/, IModelObject* /*p_process_instance*/,
635 IModelObject* /*p_value*/) noexcept {
636 return E_NOTIMPL;
637 }
638
639 constexpr wchar_t usage[] =
640 LR"(Invalid arguments.
641 First argument should be a uint64 representing the tagged value to investigate.
642 Second argument is optional, and may be a fully-qualified type name such as
643 v8::internal::String.)";
644
Call(IModelObject * p_context_object,ULONG64 arg_count,_In_reads_ (arg_count)IModelObject ** pp_arguments,IModelObject ** pp_result,IKeyStore ** pp_metadata)645 IFACEMETHODIMP InspectV8ObjectMethod::Call(IModelObject* p_context_object,
646 ULONG64 arg_count,
647 _In_reads_(arg_count)
648 IModelObject** pp_arguments,
649 IModelObject** pp_result,
650 IKeyStore** pp_metadata) noexcept {
651 // Read the arguments.
652 ULONG64 tagged_value;
653 _bstr_t type_name;
654 if (arg_count < 1 ||
655 FAILED(UnboxULong64(pp_arguments[0], &tagged_value, /*convert=*/true)) ||
656 (arg_count >= 2 &&
657 FAILED(UnboxString(pp_arguments[1], type_name.GetAddress())))) {
658 sp_data_model_manager->CreateErrorObject(E_INVALIDARG, usage, pp_result);
659 return E_INVALIDARG;
660 }
661
662 WRL::ComPtr<IDebugHostContext> sp_ctx;
663 RETURN_IF_FAIL(sp_debug_host->GetCurrentContext(&sp_ctx));
664
665 // We can't use CreateTypedObject for a value which may not actually reside
666 // anywhere in memory, so create a synthetic object.
667 WRL::ComPtr<V8CachedObject> cached_object =
668 WRL::Make<V8CachedObject>(::GetHeapObject(
669 sp_ctx, tagged_value, 0, static_cast<const char*>(type_name),
670 /*is_compressed=*/false));
671 return CreateSyntheticObjectForV8Object(sp_ctx.Get(), cached_object.Get(),
672 pp_result);
673 }
674
675 // Creates an IModelObject representing the data in the given property.
GetModelForProperty(const Property & prop,WRL::ComPtr<IDebugHostContext> & sp_ctx,IModelObject ** result)676 HRESULT GetModelForProperty(const Property& prop,
677 WRL::ComPtr<IDebugHostContext>& sp_ctx,
678 IModelObject** result) {
679 switch (prop.type) {
680 case PropertyType::kPointer:
681 return GetModelForBasicField(prop.addr_value, prop.type_name,
682 prop.uncompressed_type_name, sp_ctx, result);
683 case PropertyType::kStruct:
684 return GetModelForStruct(prop.addr_value, prop.fields, sp_ctx, result);
685 case PropertyType::kArray:
686 case PropertyType::kStructArray:
687 if (prop.type == PropertyType::kArray &&
688 prop.type_name == ConvertToU16String(prop.uncompressed_type_name)) {
689 // An array of things that are not structs or compressed tagged values
690 // is most cleanly represented by a native array.
691 return GetModelForNativeArray(prop.addr_value, prop.type_name,
692 prop.length, sp_ctx, result);
693 }
694 // Otherwise, we must construct a custom iterable object.
695 return GetModelForCustomArray(prop, sp_ctx, result);
696 default:
697 return E_FAIL;
698 }
699 }
700