diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 510a0ec2..ee3401f0 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -16,9 +16,8 @@ jobs: fail-fast: false matrix: node: - - version: 8.x - version: 10.x - - version: 11.x + - version: 12.x - version: 14.x mirror: https://nodejs.org/download/nightly - version: 14.x diff --git a/package.json b/package.json index 65a60e16..fecbbe20 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "llnode", - "version": "2.2.0", + "version": "3.0.0", "description": "An lldb plugin for Node.js and V8, which enables inspection of JavaScript states for insights into Node.js processes and their core dumps.", "main": "index.js", "directories": { diff --git a/src/constants.h b/src/constants.h index 1057b22d..b0b26f6c 100644 --- a/src/constants.h +++ b/src/constants.h @@ -48,8 +48,6 @@ class Constant { inline std::string name() { return name_; } - protected: - friend class Constants; explicit Constant(T value) : value_(value), status_(kValid), name_("") {} Constant(T value, std::string name) : value_(value), status_(kLoaded), name_(name) {} diff --git a/src/llscan.cc b/src/llscan.cc index 6755d57a..7162878c 100644 --- a/src/llscan.cc +++ b/src/llscan.cc @@ -1563,7 +1563,7 @@ bool FindJSObjectsVisitor::MapCacheEntry::Load(v8::Map map, if (is_histogram) type_name = heap_object.GetTypeName(err); v8::HeapObject descriptors_obj = map.InstanceDescriptors(err); - if (err.Fail()) return false; + RETURN_IF_INVALID(descriptors_obj, false); v8::DescriptorArray descriptors(descriptors_obj); own_descriptors_count_ = map.NumberOfOwnDescriptors(err); @@ -1579,8 +1579,8 @@ bool FindJSObjectsVisitor::MapCacheEntry::Load(v8::Map map, } for (uint64_t i = 0; i < own_descriptors_count_; i++) { - v8::Value key = descriptors.GetKey(i, err); - if (err.Fail()) continue; + v8::Value key = descriptors.GetKey(i); + if (!key.Check()) continue; properties_.emplace_back(key.ToString(err)); } diff --git a/src/llv8-constants.cc b/src/llv8-constants.cc index 2f1f4684..4fb97e26 100644 --- a/src/llv8-constants.cc +++ b/src/llv8-constants.cc @@ -95,6 +95,7 @@ void Map::Load() { "class_Map__constructor__Object"); kInstanceDescriptorsOffset = LoadConstant({ "class_Map__instance_descriptors__DescriptorArray", + "class_Map__instance_descriptors_offset", }); kBitField3Offset = LoadConstant("class_Map__bit_field3__int", "class_Map__bit_field3__SMI"); @@ -322,18 +323,22 @@ void TwoByteString::Load() { void ConsString::Load() { - kFirstOffset = LoadConstant("class_ConsString__first__String"); - kSecondOffset = LoadConstant("class_ConsString__second__String"); + kFirstOffset = LoadConstant({"class_ConsString__first__String", + "class_ConsString__first_offset__int"}); + kSecondOffset = LoadConstant({"class_ConsString__second__String", + "class_ConsString__second_offset__int"}); } void SlicedString::Load() { kParentOffset = LoadConstant("class_SlicedString__parent__String"); - kOffsetOffset = LoadConstant("class_SlicedString__offset__SMI"); + kOffsetOffset = LoadConstant({"class_SlicedString__offset__SMI", + "class_SlicedString__offset_offset__int"}); } void ThinString::Load() { - kActualOffset = LoadConstant("class_ThinString__actual__String"); + kActualOffset = LoadConstant({"class_ThinString__actual__String", + "class_ThinString__actual_offset__int"}); } void FixedArrayBase::Load() { @@ -347,12 +352,26 @@ void FixedArray::Load() { void FixedTypedArrayBase::Load() { + kBasePointerOffset = LoadOptionalConstant( + {"class_FixedTypedArrayBase__base_pointer__Object"}, 0); + kExternalPointerOffset = LoadOptionalConstant( + {"class_FixedTypedArrayBase__external_pointer__Object", + "class_FixedTypedArrayBase__external_pointer__uintptr_t"}, + 0); +} + +void JSTypedArray::Load() { kBasePointerOffset = - LoadConstant("class_FixedTypedArrayBase__base_pointer__Object"); - kExternalPointerOffset = - LoadConstant("class_FixedTypedArrayBase__external_pointer__Object"); + LoadOptionalConstant({"class_JSTypedArray__base_pointer__Object"}, 0); + kExternalPointerOffset = LoadOptionalConstant( + {"class_JSTypedArray__external_pointer__uintptr_t"}, 0); } +// TODO(mmarchini): proper validation so that we log an error if neither classes +// were able to load their constants. +bool JSTypedArray::IsDataPointerInJSTypedArray() { + return kBasePointerOffset.Loaded() && kExternalPointerOffset.Loaded(); +} void Oddball::Load() { kKindOffset = LoadConstant("class_Oddball__kind_offset__int"); @@ -369,19 +388,15 @@ void Oddball::Load() { void JSArrayBuffer::Load() { kBackingStoreOffset = - LoadConstant("class_JSArrayBuffer__backing_store__Object"); - kByteLengthOffset = LoadConstant("class_JSArrayBuffer__byte_length__Object"); - - // v4 compatibility fix - if (kBackingStoreOffset == -1) { - common_->Load(); + LoadConstant({"class_JSArrayBuffer__backing_store__Object", + "class_JSArrayBuffer__backing_store__uintptr_t"}); + kByteLengthOffset = + LoadConstant({"class_JSArrayBuffer__byte_length__Object", + "class_JSArrayBuffer__byte_length__size_t"}); - kBackingStoreOffset = kByteLengthOffset + common_->kPointerSize; + if (kBackingStoreOffset.Check()) { } - kBitFieldOffset = kBackingStoreOffset + common_->kPointerSize; - if (common_->kPointerSize == 8) kBitFieldOffset += 4; - kWasNeuteredMask = LoadConstant("jsarray_buffer_was_neutered_mask"); kWasNeuteredShift = LoadConstant("jsarray_buffer_was_neutered_shift"); @@ -393,19 +408,45 @@ void JSArrayBuffer::Load() { } +bool JSArrayBuffer::IsByteLengthScalar() { + return kByteLengthOffset.name() == + "v8dbg_class_JSArrayBuffer__byte_length__size_t"; +} + +Constant JSArrayBuffer::BitFieldOffset() { + if (!kBackingStoreOffset.Check()) return Constant(); + + common_->Load(); + int64_t kBitFieldOffset = *kBackingStoreOffset + common_->kPointerSize; + if (common_->kPointerSize == 8) kBitFieldOffset += 4; + return Constant(kBitFieldOffset); +} + + void JSArrayBufferView::Load() { kBufferOffset = LoadConstant("class_JSArrayBufferView__buffer__Object"); kByteOffsetOffset = - LoadConstant("class_JSArrayBufferView__raw_byte_offset__Object"); + LoadConstant({"class_JSArrayBufferView__raw_byte_offset__Object", + "class_JSArrayBufferView__byte_offset__size_t"}); kByteLengthOffset = - LoadConstant("class_JSArrayBufferView__raw_byte_length__Object"); + LoadConstant({"class_JSArrayBufferView__raw_byte_length__Object", + "class_JSArrayBufferView__byte_length__size_t"}); } +bool JSArrayBufferView::IsByteLengthScalar() { + return kByteLengthOffset.name() == + "v8dbg_class_JSArrayBufferView__byte_length__size_t"; +} + +bool JSArrayBufferView::IsByteOffsetScalar() { + return kByteOffsetOffset.name() == + "v8dbg_class_JSArrayBufferView__byte_offset__size_t"; +} void DescriptorArray::Load() { - kDetailsOffset = LoadConstant("prop_desc_details"); - kKeyOffset = LoadConstant("prop_desc_key"); - kValueOffset = LoadConstant("prop_desc_value"); + kDetailsOffset = LoadConstant({"prop_desc_details"}); + kKeyOffset = LoadConstant({"prop_desc_key"}); + kValueOffset = LoadConstant({"prop_desc_value"}); kPropertyIndexMask = LoadConstant("prop_index_mask"); kPropertyIndexShift = LoadConstant("prop_index_shift"); @@ -455,8 +496,12 @@ void DescriptorArray::Load() { kRepresentationDouble = 7; } - kFirstIndex = LoadConstant("prop_idx_first"); - kSize = LoadConstant("prop_desc_size"); + // NOTE(mmarchini): removed from V8 7.2. + // https://github.com/v8/v8/commit/1ad0cd5 + kFirstIndex = LoadOptionalConstant({"prop_idx_first"}, 0); + kSize = LoadConstant({"prop_desc_size"}); + kHeaderSize = LoadOptionalConstant( + {"class_DescriptorArray__header_size__uintptr_t"}, 24); } @@ -505,7 +550,24 @@ void Frame::Load() { void Symbol::Load() { - kNameOffset = LoadConstant("class_Symbol__name__Object"); + // map is the last field of HeapObject + Constant maybe_name_offset = + LoadConstant({"class_HeapObject__map__Map"}); + common_->Load(); + if (maybe_name_offset.Check()) { + int name_offset = *maybe_name_offset; + + name_offset += common_->kPointerSize; + // class Name extends HeapObject and has only one uint32 field + name_offset += sizeof(uint32_t); + // class Symbol extends Name and has one int32 field before name + name_offset += sizeof(int32_t); + + kNameOffset = + LoadOptionalConstant({"class_Symbol__name__Object"}, name_offset); + } else { + kNameOffset = LoadConstant({"class_Symbol__name__Object"}); + } } diff --git a/src/llv8-constants.h b/src/llv8-constants.h index cf297419..7d3775ec 100644 --- a/src/llv8-constants.h +++ b/src/llv8-constants.h @@ -296,8 +296,8 @@ class ConsString : public Module { public: CONSTANTS_DEFAULT_METHODS(ConsString); - int64_t kFirstOffset; - int64_t kSecondOffset; + Constant kFirstOffset; + Constant kSecondOffset; protected: void Load(); @@ -308,7 +308,7 @@ class SlicedString : public Module { CONSTANTS_DEFAULT_METHODS(SlicedString); int64_t kParentOffset; - int64_t kOffsetOffset; + Constant kOffsetOffset; protected: void Load(); @@ -318,7 +318,7 @@ class ThinString : public Module { public: CONSTANTS_DEFAULT_METHODS(ThinString); - int64_t kActualOffset; + Constant kActualOffset; protected: void Load(); @@ -348,8 +348,21 @@ class FixedTypedArrayBase : public Module { public: CONSTANTS_DEFAULT_METHODS(FixedTypedArrayBase); - int64_t kBasePointerOffset; - int64_t kExternalPointerOffset; + Constant kBasePointerOffset; + Constant kExternalPointerOffset; + + protected: + void Load(); +}; + +class JSTypedArray : public Module { + public: + CONSTANTS_DEFAULT_METHODS(JSTypedArray); + + Constant kBasePointerOffset; + Constant kExternalPointerOffset; + + bool IsDataPointerInJSTypedArray(); protected: void Load(); @@ -379,13 +392,15 @@ class JSArrayBuffer : public Module { int64_t kKindOffset; - int64_t kBackingStoreOffset; - int64_t kByteLengthOffset; - int64_t kBitFieldOffset; + Constant kBackingStoreOffset; + Constant kByteLengthOffset; int64_t kWasNeuteredMask; int64_t kWasNeuteredShift; + Constant BitFieldOffset(); + bool IsByteLengthScalar(); + protected: void Load(); }; @@ -395,8 +410,11 @@ class JSArrayBufferView : public Module { CONSTANTS_DEFAULT_METHODS(JSArrayBufferView); int64_t kBufferOffset; - int64_t kByteOffsetOffset; - int64_t kByteLengthOffset; + Constant kByteOffsetOffset; + Constant kByteLengthOffset; + + bool IsByteLengthScalar(); + bool IsByteOffsetScalar(); protected: void Load(); @@ -406,9 +424,9 @@ class DescriptorArray : public Module { public: CONSTANTS_DEFAULT_METHODS(DescriptorArray); - int64_t kDetailsOffset; - int64_t kKeyOffset; - int64_t kValueOffset; + Constant kDetailsOffset; + Constant kKeyOffset; + Constant kValueOffset; int64_t kPropertyIndexMask; int64_t kPropertyIndexShift; @@ -417,8 +435,10 @@ class DescriptorArray : public Module { int64_t kRepresentationDouble; - int64_t kFirstIndex; - int64_t kSize; + Constant kFirstIndex; + Constant kHeaderSize; + Constant kSize; + Constant kEntrySize; // node.js <= 7 int64_t kPropertyTypeMask = -1; @@ -489,7 +509,7 @@ class Symbol : public Module { public: CONSTANTS_DEFAULT_METHODS(Symbol); - int64_t kNameOffset; + Constant kNameOffset; protected: void Load(); diff --git a/src/llv8-inl.h b/src/llv8-inl.h index cd00e0d9..0293eb86 100644 --- a/src/llv8-inl.h +++ b/src/llv8-inl.h @@ -7,6 +7,33 @@ namespace llnode { namespace v8 { +using lldb::addr_t; +using lldb::SBError; + +template +inline std::string CheckedType::ToString(const char* fmt) { + if (!Check()) return "???"; + + char buf[20]; + snprintf(buf, sizeof(buf), fmt, val_); + return std::string(buf); +} + +template +inline CheckedType LLV8::LoadUnsigned(int64_t addr, uint32_t byte_size) { + SBError sberr; + int64_t value = process_.ReadUnsignedFromMemory(static_cast(addr), + byte_size, sberr); + + if (sberr.Fail()) { + PRINT_DEBUG("Failed to load unsigned from v8 memory. Reason: %s", + sberr.GetCString()); + return CheckedType(); + } + + return CheckedType(value); +} + template <> inline double LLV8::LoadValue(int64_t addr, Error& err) { return LoadDouble(addr, err); @@ -67,6 +94,14 @@ inline int64_t HeapObject::LoadField(int64_t off, Error& err) { } +template +inline CheckedType HeapObject::LoadCheckedField(Constant off) { + RETURN_IF_THIS_INVALID(CheckedType()); + RETURN_IF_INVALID(off, CheckedType()); + return v8()->LoadUnsigned(LeaField(*off), 8); +} + + template <> inline int32_t HeapObject::LoadFieldValue(int64_t off, Error& err) { return v8()->LoadValue(LeaField(off), err); @@ -246,7 +281,7 @@ ACCESSOR(Map, MaybeConstructor, map()->kMaybeConstructorOffset, HeapObject) SAFE_ACCESSOR(Map, InstanceDescriptors, map()->kInstanceDescriptorsOffset, HeapObject) -ACCESSOR(Symbol, Name, symbol()->kNameOffset, HeapObject) +SAFE_ACCESSOR(Symbol, Name, symbol()->kNameOffset, HeapObject) inline int64_t Map::BitField3(Error& err) { return v8()->LoadUnsigned(LeaField(v8()->map()->kBitField3Offset), 4, err); @@ -271,6 +306,7 @@ inline bool Context::IsContext(LLV8* v8, HeapObject heap_object, Error& err) { } inline int64_t Map::InObjectProperties(Error& err) { + RETURN_IF_THIS_INVALID(-1); if (!IsJSObjectMap(err)) { err = Error::Failure( "Invalid call to Map::InObjectProperties with a non-JsObject type"); @@ -498,22 +534,139 @@ inline int64_t Code::Size(Error& err) { ACCESSOR(Oddball, Kind, oddball()->kKindOffset, Smi) -inline int64_t JSArrayBuffer::BackingStore(Error& err) { - return LoadField(v8()->js_array_buffer()->kBackingStoreOffset, err); +inline CheckedType JSArrayBuffer::BackingStore() { + RETURN_IF_THIS_INVALID(CheckedType()); + + return LoadCheckedField( + v8()->js_array_buffer()->kBackingStoreOffset); } -inline int64_t JSArrayBuffer::BitField(Error& err) { - return LoadField(v8()->js_array_buffer()->kBitFieldOffset, err) & 0xffffffff; +inline CheckedType JSArrayBuffer::ByteLength() { + RETURN_IF_THIS_INVALID(CheckedType()); + + if (!v8()->js_array_buffer()->IsByteLengthScalar()) { + Error err; + Smi len = byte_length(err); + RETURN_IF_INVALID(len, CheckedType()); + + return CheckedType(len.GetValue()); + } + + return LoadCheckedField(v8()->js_array_buffer()->kByteLengthOffset); +} + +inline CheckedType JSArrayBuffer::BitField() { + RETURN_IF_THIS_INVALID(CheckedType()); + CheckedType bit_fields = + LoadCheckedField(v8()->js_array_buffer()->BitFieldOffset()); + RETURN_IF_INVALID(bit_fields, CheckedType()); + return CheckedType(*bit_fields & 0xffffffff); } -ACCESSOR(JSArrayBuffer, ByteLength, js_array_buffer()->kByteLengthOffset, Smi) +SAFE_ACCESSOR(JSArrayBuffer, byte_length, js_array_buffer()->kByteLengthOffset, + Smi) ACCESSOR(JSArrayBufferView, Buffer, js_array_buffer_view()->kBufferOffset, JSArrayBuffer) -ACCESSOR(JSArrayBufferView, ByteOffset, - js_array_buffer_view()->kByteOffsetOffset, Smi) -ACCESSOR(JSArrayBufferView, ByteLength, - js_array_buffer_view()->kByteLengthOffset, Smi) + +inline CheckedType JSArrayBufferView::ByteLength() { + RETURN_IF_THIS_INVALID(CheckedType()); + + if (!v8()->js_array_buffer_view()->IsByteLengthScalar()) { + Error err; + Smi len = byte_length(err); + RETURN_IF_INVALID(len, CheckedType()); + + return CheckedType(len.GetValue()); + } + + return LoadCheckedField( + v8()->js_array_buffer_view()->kByteLengthOffset); +} + +inline CheckedType JSArrayBufferView::ByteOffset() { + RETURN_IF_THIS_INVALID(CheckedType()); + + if (!v8()->js_array_buffer_view()->IsByteOffsetScalar()) { + Error err; + Smi len = byte_offset(err); + RETURN_IF_INVALID(len, CheckedType()); + + return CheckedType(len.GetValue()); + } + + return LoadCheckedField( + v8()->js_array_buffer_view()->kByteOffsetOffset); +} + +SAFE_ACCESSOR(JSArrayBufferView, byte_offset, + js_array_buffer_view()->kByteOffsetOffset, Smi) +SAFE_ACCESSOR(JSArrayBufferView, byte_length, + js_array_buffer_view()->kByteLengthOffset, Smi) + +inline CheckedType JSTypedArray::base() { + return LoadCheckedField(v8()->js_typed_array()->kBasePointerOffset); +} + +inline CheckedType JSTypedArray::external() { + return LoadCheckedField( + v8()->js_typed_array()->kExternalPointerOffset); +} + +inline CheckedType JSTypedArray::GetExternal() { + if (v8()->js_typed_array()->IsDataPointerInJSTypedArray()) { + PRINT_DEBUG("OHALO"); + return external(); + } else { + PRINT_DEBUG("NAY"); + // TODO(mmarchini): don't rely on Error + Error err; + v8::HeapObject elements_obj = Elements(err); + RETURN_IF_INVALID(elements_obj, CheckedType()); + v8::FixedTypedArrayBase elements(elements_obj); + return elements.GetExternal(); + } +} + +inline CheckedType JSTypedArray::GetBase() { + if (v8()->js_typed_array()->IsDataPointerInJSTypedArray()) { + PRINT_DEBUG("ALOHA"); + return base(); + } else { + PRINT_DEBUG("NEY"); + // TODO(mmarchini): don't rely on Error + Error err; + v8::HeapObject elements_obj = Elements(err); + RETURN_IF_INVALID(elements_obj, CheckedType()); + v8::FixedTypedArrayBase elements(elements_obj); + return elements.GetBase(); + } +} + +inline CheckedType JSTypedArray::GetData() { + // TODO(mmarchini): don't rely on Error + Error err; + v8::JSArrayBuffer buf = Buffer(err); + if (err.Fail()) return CheckedType(); + + v8::CheckedType data = buf.BackingStore(); + // TODO(mmarchini): be more lenient to failed load + RETURN_IF_INVALID(data, CheckedType()); + + if (*data == 0) { + // The backing store has not been materialized yet. + + CheckedType base = GetBase(); + RETURN_IF_INVALID(base, v8::CheckedType()); + + CheckedType external = GetExternal(); + RETURN_IF_INVALID(external, v8::CheckedType()); + + data = v8::CheckedType(*base + *external); + } + return data; +} + inline ScopeInfo::PositionInfo ScopeInfo::MaybePositionInfo(Error& err) { ScopeInfo::PositionInfo position_info = { @@ -621,22 +774,24 @@ ACCESSOR(JSFunction, Info, js_function()->kSharedInfoOffset, SharedFunctionInfo); ACCESSOR(JSFunction, GetContext, js_function()->kContextOffset, HeapObject); -ACCESSOR(ConsString, First, cons_string()->kFirstOffset, String); -ACCESSOR(ConsString, Second, cons_string()->kSecondOffset, String); +SAFE_ACCESSOR(ConsString, First, cons_string()->kFirstOffset, String); +SAFE_ACCESSOR(ConsString, Second, cons_string()->kSecondOffset, String); ACCESSOR(SlicedString, Parent, sliced_string()->kParentOffset, String); -ACCESSOR(SlicedString, Offset, sliced_string()->kOffsetOffset, Smi); +SAFE_ACCESSOR(SlicedString, Offset, sliced_string()->kOffsetOffset, Smi); -ACCESSOR(ThinString, Actual, thin_string()->kActualOffset, String); +SAFE_ACCESSOR(ThinString, Actual, thin_string()->kActualOffset, String); ACCESSOR(FixedArrayBase, Length, fixed_array_base()->kLengthOffset, Smi); -inline int64_t FixedTypedArrayBase::GetBase(Error& err) { - return LoadField(v8()->fixed_typed_array_base()->kBasePointerOffset, err); +inline CheckedType FixedTypedArrayBase::GetBase() { + return LoadCheckedField( + v8()->fixed_typed_array_base()->kBasePointerOffset); } -inline int64_t FixedTypedArrayBase::GetExternal(Error& err) { - return LoadField(v8()->fixed_typed_array_base()->kExternalPointerOffset, err); +inline CheckedType FixedTypedArrayBase::GetExternal() { + return LoadCheckedField( + v8()->fixed_typed_array_base()->kExternalPointerOffset); } inline std::string OneByteString::ToString(Error& err) { @@ -724,25 +879,42 @@ inline T FixedArray::Get(int index, Error& err) { return LoadFieldValue(off, err); } -inline Smi DescriptorArray::GetDetails(int index, Error& err) { - return Get(v8()->descriptor_array()->kFirstIndex + - index * v8()->descriptor_array()->kSize + - v8()->descriptor_array()->kDetailsOffset, - err); +template +inline T DescriptorArray::Get(int index, int64_t offset) { + // TODO(mmarchini): shouldn't need Error here. + Error err; + RETURN_IF_INVALID(v8()->descriptor_array()->kSize, T()); + + index = index * *(v8()->descriptor_array()->kSize); + if (v8()->descriptor_array()->kFirstIndex.Loaded()) { + return FixedArray::Get( + *(v8()->descriptor_array()->kFirstIndex) + index + offset, err); + } else if (v8()->descriptor_array()->kHeaderSize.Check()) { + index *= v8()->common()->kPointerSize; + index += *(v8()->descriptor_array()->kHeaderSize); + index += (v8()->common()->kPointerSize * offset); + return LoadFieldValue(index, err); + } else { + PRINT_DEBUG( + "Missing FirstIndex and HeaderSize constants, can't get key from " + "DescriptorArray"); + return T(); + } } -inline Value DescriptorArray::GetKey(int index, Error& err) { - return Get(v8()->descriptor_array()->kFirstIndex + - index * v8()->descriptor_array()->kSize + - v8()->descriptor_array()->kKeyOffset, - err); +inline Smi DescriptorArray::GetDetails(int index) { + RETURN_IF_INVALID(v8()->descriptor_array()->kDetailsOffset, Smi()); + return Get(index, *v8()->descriptor_array()->kDetailsOffset); } -inline Value DescriptorArray::GetValue(int index, Error& err) { - return Get(v8()->descriptor_array()->kFirstIndex + - index * v8()->descriptor_array()->kSize + - v8()->descriptor_array()->kValueOffset, - err); +inline Value DescriptorArray::GetKey(int index) { + RETURN_IF_INVALID(v8()->descriptor_array()->kKeyOffset, Value()); + return Get(index, *v8()->descriptor_array()->kKeyOffset); +} + +inline Value DescriptorArray::GetValue(int index) { + RETURN_IF_INVALID(v8()->descriptor_array()->kValueOffset, Value()); + return Get(index, *v8()->descriptor_array()->kValueOffset); } inline bool DescriptorArray::IsDescriptorDetails(Smi details) { @@ -955,10 +1127,12 @@ inline bool Oddball::IsHole(Error& err) { return kind.GetValue() == v8()->oddball()->kTheHole; } +// TODO(mmarchini): return CheckedType inline bool JSArrayBuffer::WasNeutered(Error& err) { - int64_t field = BitField(err); - if (err.Fail()) return false; + CheckedType bit_field = BitField(); + RETURN_IF_INVALID(bit_field, false); + int64_t field = *bit_field; field &= v8()->js_array_buffer()->kWasNeuteredMask; field >>= v8()->js_array_buffer()->kWasNeuteredShift; return field != 0; diff --git a/src/llv8.cc b/src/llv8.cc index b783a2d2..f70e18e5 100644 --- a/src/llv8.cc +++ b/src/llv8.cc @@ -53,6 +53,7 @@ void LLV8::Load(SBTarget target) { fixed_array_base.Assign(target, &common); fixed_array.Assign(target, &common); fixed_typed_array_base.Assign(target, &common); + js_typed_array.Assign(target, &common); oddball.Assign(target, &common); js_array_buffer.Assign(target, &common); js_array_buffer_view.Assign(target, &common); @@ -80,21 +81,6 @@ int64_t LLV8::LoadPtr(int64_t addr, Error& err) { return value; } -template -CheckedType LLV8::LoadUnsigned(int64_t addr, uint32_t byte_size) { - SBError sberr; - int64_t value = process_.ReadUnsignedFromMemory(static_cast(addr), - byte_size, sberr); - - if (sberr.Fail()) { - PRINT_DEBUG("Failed to load unsigned from v8 memory. Reason: %s", - sberr.GetCString()); - return CheckedType(); - } - - return CheckedType(value); -} - int64_t LLV8::LoadUnsigned(int64_t addr, uint32_t byte_size, Error& err) { SBError sberr; int64_t value = process_.ReadUnsignedFromMemory(static_cast(addr), @@ -571,6 +557,8 @@ std::string Value::GetTypeName(Error& err) { std::string Value::ToString(Error& err) { + RETURN_IF_THIS_INVALID(std::string()); + Smi smi(this); if (smi.Check()) return smi.ToString(err); @@ -741,6 +729,7 @@ std::string Symbol::ToString(Error& err) { return "Symbol()"; } HeapObject name = Name(err); + RETURN_IF_INVALID(name, "Symbol(???)"); return "Symbol('" + String(name).ToString(err) + "')"; } @@ -922,7 +911,7 @@ std::vector> JSObject::DictionaryEntries(Error& err) { std::vector> JSObject::DescriptorEntries(Map map, Error& err) { HeapObject descriptors_obj = map.InstanceDescriptors(err); - if (err.Fail()) return {}; + RETURN_IF_INVALID(descriptors_obj, {}); DescriptorArray descriptors(descriptors_obj); @@ -942,18 +931,22 @@ std::vector> JSObject::DescriptorEntries(Map map, std::vector> entries; for (int64_t i = 0; i < own_descriptors_count; i++) { - Smi details = descriptors.GetDetails(i, err); - if (err.Fail()) continue; + Smi details = descriptors.GetDetails(i); + if (!details.Check()) { + PRINT_DEBUG("Failed to get details for index %ld", i); + entries.push_back(std::pair(Value(), Value())); + continue; + } - Value key = descriptors.GetKey(i, err); - if (err.Fail()) continue; + Value key = descriptors.GetKey(i); + if (!key.Check()) continue; if (descriptors.IsConstFieldDetails(details) || descriptors.IsDescriptorDetails(details)) { Value value; - value = descriptors.GetValue(i, err); - if (err.Fail()) continue; + value = descriptors.GetValue(i); + if (!value.Check()) continue; entries.push_back(std::pair(key, value)); continue; @@ -1037,18 +1030,22 @@ void JSObject::DictionaryKeys(std::vector& keys, Error& err) { void JSObject::DescriptorKeys(std::vector& keys, Map map, Error& err) { HeapObject descriptors_obj = map.InstanceDescriptors(err); - if (err.Fail()) return; + RETURN_IF_INVALID(descriptors_obj, ); DescriptorArray descriptors(descriptors_obj); int64_t own_descriptors_count = map.NumberOfOwnDescriptors(err); if (err.Fail()) return; for (int64_t i = 0; i < own_descriptors_count; i++) { - Smi details = descriptors.GetDetails(i, err); - if (err.Fail()) return; + Smi details = descriptors.GetDetails(i); + if (!details.Check()) { + PRINT_DEBUG("Failed to get details for index %ld", i); + keys.push_back("???"); + continue; + } - Value key = descriptors.GetKey(i, err); - if (err.Fail()) return; + Value key = descriptors.GetKey(i); + RETURN_IF_INVALID(key, ); // Skip non-fields for now, Object.keys(obj) does // not seem to return these (for example the "length" @@ -1124,7 +1121,7 @@ Value JSObject::GetDictionaryProperty(std::string key_name, Error& err) { Value JSObject::GetDescriptorProperty(std::string key_name, Map map, Error& err) { HeapObject descriptors_obj = map.InstanceDescriptors(err); - if (err.Fail()) return Value(); + RETURN_IF_INVALID(descriptors_obj, Value()); DescriptorArray descriptors(descriptors_obj); int64_t own_descriptors_count = map.NumberOfOwnDescriptors(err); @@ -1142,11 +1139,14 @@ Value JSObject::GetDescriptorProperty(std::string key_name, Map map, FixedArray extra_properties(extra_properties_obj); for (int64_t i = 0; i < own_descriptors_count; i++) { - Smi details = descriptors.GetDetails(i, err); - if (err.Fail()) return Value(); + Smi details = descriptors.GetDetails(i); + if (!details.Check()) { + PRINT_DEBUG("Failed to get details for index %ld", i); + continue; + } - Value key = descriptors.GetKey(i, err); - if (err.Fail()) return Value(); + Value key = descriptors.GetKey(i); + RETURN_IF_INVALID(key, Value()); if (key.ToString(err) != key_name) { continue; @@ -1159,8 +1159,8 @@ Value JSObject::GetDescriptorProperty(std::string key_name, Map map, descriptors.IsDescriptorDetails(details)) { Value value; - value = descriptors.GetValue(i, err); - if (err.Fail()) return Value(); + value = descriptors.GetValue(i); + RETURN_IF_INVALID(value, Value()); continue; } diff --git a/src/llv8.h b/src/llv8.h index 5c8706c6..7ba75c4b 100644 --- a/src/llv8.h +++ b/src/llv8.h @@ -60,6 +60,8 @@ class CheckedType { } inline bool Check() const { return valid_; } + inline std::string ToString(const char* fmt); + private: T val_; bool valid_; @@ -117,6 +119,9 @@ class HeapObject : public Value { inline int64_t LeaField(int64_t off) const; inline int64_t LoadField(int64_t off, Error& err); + template + inline CheckedType LoadCheckedField(Constant off); + template inline T LoadFieldValue(int64_t off, Error& err); @@ -447,19 +452,22 @@ class FixedTypedArrayBase : public FixedArrayBase { public: V8_VALUE_DEFAULT_METHODS(FixedTypedArrayBase, FixedArrayBase) - inline int64_t GetBase(Error& err); - inline int64_t GetExternal(Error& err); + inline CheckedType GetBase(); + inline CheckedType GetExternal(); }; class DescriptorArray : public FixedArray { public: V8_VALUE_DEFAULT_METHODS(DescriptorArray, FixedArray) - inline Smi GetDetails(int index, Error& err); - inline Value GetKey(int index, Error& err); + template + inline T Get(int index, int64_t offset); + + inline Smi GetDetails(int index); + inline Value GetKey(int index); // NOTE: Only for DATA_CONSTANT - inline Value GetValue(int index, Error& err); + inline Value GetValue(int index); inline bool IsFieldDetails(Smi details); inline bool IsDescriptorDetails(Smi details); @@ -562,11 +570,14 @@ class JSArrayBuffer : public JSObject { public: V8_VALUE_DEFAULT_METHODS(JSArrayBuffer, JSObject) - inline int64_t BackingStore(Error& err); - inline int64_t BitField(Error& err); - inline Smi ByteLength(Error& err); + inline CheckedType BackingStore(); + inline CheckedType BitField(); + inline CheckedType ByteLength(); inline bool WasNeutered(Error& err); + + private: + inline Smi byte_length(Error& err); }; class JSArrayBufferView : public JSObject { @@ -574,8 +585,25 @@ class JSArrayBufferView : public JSObject { V8_VALUE_DEFAULT_METHODS(JSArrayBufferView, JSObject) inline JSArrayBuffer Buffer(Error& err); - inline Smi ByteOffset(Error& err); - inline Smi ByteLength(Error& err); + inline CheckedType ByteOffset(); + inline CheckedType ByteLength(); + + private: + inline Smi byte_offset(Error& err); + inline Smi byte_length(Error& err); +}; + +class JSTypedArray : public JSArrayBufferView { + public: + V8_VALUE_DEFAULT_METHODS(JSTypedArray, JSArrayBufferView) + + inline CheckedType GetExternal(); + inline CheckedType GetBase(); + inline CheckedType GetData(); + + private: + inline CheckedType external(); + inline CheckedType base(); }; class JSFrame : public Value { @@ -614,7 +642,7 @@ class LLV8 { int64_t LoadConstant(const char* name); int64_t LoadPtr(int64_t addr, Error& err); template - CheckedType LoadUnsigned(int64_t addr, uint32_t byte_size); + inline CheckedType LoadUnsigned(int64_t addr, uint32_t byte_size); int64_t LoadUnsigned(int64_t addr, uint32_t byte_size, Error& err); double LoadDouble(int64_t addr, Error& err); std::string LoadBytes(int64_t addr, int64_t length, Error& err); @@ -647,6 +675,7 @@ class LLV8 { constants::ThinString thin_string; constants::FixedArrayBase fixed_array_base; constants::FixedTypedArrayBase fixed_typed_array_base; + constants::JSTypedArray js_typed_array; constants::FixedArray fixed_array; constants::Oddball oddball; constants::JSArrayBuffer js_array_buffer; @@ -682,6 +711,7 @@ class LLV8 { friend class FixedArrayBase; friend class FixedArray; friend class FixedTypedArrayBase; + friend class JSTypedArray; friend class DescriptorArray; friend class NameDictionary; friend class Context; diff --git a/src/printer.cc b/src/printer.cc index 507e5c3b..7a712f85 100644 --- a/src/printer.cc +++ b/src/printer.cc @@ -345,18 +345,13 @@ std::string Printer::Stringify(v8::JSArrayBuffer js_array_buffer, Error& err) { return ss.str(); } - int64_t data = js_array_buffer.BackingStore(err); - if (err.Fail()) return std::string(); - - v8::Smi length = js_array_buffer.ByteLength(err); - if (err.Fail()) return std::string(); - - int byte_length = static_cast(length.GetValue()); + v8::CheckedType data = js_array_buffer.BackingStore(); + v8::CheckedType byte_length = js_array_buffer.ByteLength(); char tmp[128]; - snprintf(tmp, sizeof(tmp), - "(byte_length, options_.length); - res += llv8_->LoadBytes(data, display_length, err); + int display_length = std::min(*byte_length, options_.length); + res += llv8_->LoadBytes(*data, display_length, err); - if (display_length < byte_length) { - res += " ..."; + if (display_length < *byte_length) { + res += " ..."; + } + + res += "\n]"; } - res += "\n]"; ss.str(""); ss.clear(); ss << res << rang::fg::reset; @@ -390,9 +390,9 @@ std::string Printer::Stringify(v8::JSArrayBuffer js_array_buffer, Error& err) { template <> -std::string Printer::Stringify(v8::JSArrayBufferView js_array_buffer_view, - Error& err) { - v8::JSArrayBuffer buf = js_array_buffer_view.Buffer(err); +std::string Printer::Stringify(v8::JSTypedArray js_typed_array, Error& err) { + // TODO(mmarchini): shouldn't need to fetch buffer here + v8::JSArrayBuffer buf = js_typed_array.Buffer(err); if (err.Fail()) return std::string(); bool neutered = buf.WasNeutered(err); @@ -404,34 +404,22 @@ std::string Printer::Stringify(v8::JSArrayBufferView js_array_buffer_view, return ss.str().c_str(); } - int64_t data = buf.BackingStore(err); - if (err.Fail()) return std::string(); - - if (data == 0) { - // The backing store has not been materialized yet. - v8::HeapObject elements_obj = js_array_buffer_view.Elements(err); - if (err.Fail()) return std::string(); - v8::FixedTypedArrayBase elements(elements_obj); - int64_t base = elements.GetBase(err); - if (err.Fail()) return std::string(); - int64_t external = elements.GetExternal(err); - if (err.Fail()) return std::string(); - data = base + external; - } + v8::CheckedType data = js_typed_array.GetData(); + // TODO(mmarchini): be more lenient to failed load + RETURN_IF_INVALID(data, std::string()); - v8::Smi off = js_array_buffer_view.ByteOffset(err); - if (err.Fail()) return std::string(); + v8::CheckedType byte_offset = js_typed_array.ByteOffset(); + RETURN_IF_INVALID(byte_offset, std::string()); - v8::Smi length = js_array_buffer_view.ByteLength(err); - if (err.Fail()) return std::string(); + v8::CheckedType byte_length = js_typed_array.ByteLength(); + RETURN_IF_INVALID(byte_length, std::string()); - int byte_length = static_cast(length.GetValue()); - int byte_offset = static_cast(off.GetValue()); char tmp[128]; snprintf(tmp, sizeof(tmp), - "(byte_length, options_.length); - res += llv8_->LoadBytes(data + byte_offset, display_length, err); + int display_length = std::min(*byte_length, options_.length); + res += llv8_->LoadBytes(*data + *byte_offset, display_length, err); - if (display_length < byte_length) { + if (display_length < *byte_length) { res += " ..."; } @@ -467,9 +455,7 @@ std::string Printer::Stringify(v8::JSArrayBufferView js_array_buffer_view, template <> std::string Printer::Stringify(v8::Map map, Error& err) { - v8::HeapObject descriptors_obj = map.InstanceDescriptors(err); - if (err.Fail()) return std::string(); - + // TODO(mmarchini): don't fail if can't load NumberOfOwnDescriptors int64_t own_descriptors_count = map.NumberOfOwnDescriptors(err); if (err.Fail()) return std::string(); @@ -492,25 +478,41 @@ std::string Printer::Stringify(v8::Map map, Error& err) { char tmp[256]; std::stringstream ss; ss << rang::fg::yellow - << "(own_descriptors_count), in_object_properties_or_constructor.c_str(), static_cast(in_object_properties_or_constructor_index), - static_cast(instance_size), descriptors_obj.raw()); + static_cast(instance_size)); if (!options_.detailed) { return std::string(tmp) + ">"; } - v8::DescriptorArray descriptors(descriptors_obj); - if (err.Fail()) return std::string(); + if (descriptors_obj.Check()) { + v8::DescriptorArray descriptors(descriptors_obj); + if (err.Fail()) return std::string(); - return std::string(tmp) + ":" + Stringify(descriptors, err) + - ">"; + return std::string(tmp) + ":" + + Stringify(descriptors, err) + ">"; + } else { + std::string(tmp) + ">"; + } } template <> @@ -751,8 +753,8 @@ std::string Printer::Stringify(v8::HeapObject heap_object, Error& err) { } if (type == llv8_->types()->kJSTypedArrayType) { - v8::JSArrayBufferView view(heap_object); - return pre + Stringify(view, err); + v8::JSTypedArray typed_array(heap_object); + return pre + Stringify(typed_array, err); } if (type == llv8_->types()->kJSDateType) { @@ -965,7 +967,7 @@ std::string Printer::StringifyDictionary(v8::JSObject js_object, Error& err) { std::string Printer::StringifyDescriptors(v8::JSObject js_object, v8::Map map, Error& err) { v8::HeapObject descriptors_obj = map.InstanceDescriptors(err); - if (err.Fail()) return std::string(); + RETURN_IF_INVALID(descriptors_obj, std::string()); v8::DescriptorArray descriptors(descriptors_obj); int64_t own_descriptors_count = map.NumberOfOwnDescriptors(err); @@ -987,26 +989,37 @@ std::string Printer::StringifyDescriptors(v8::JSObject js_object, v8::Map map, std::string res; std::stringstream ss; for (int64_t i = 0; i < own_descriptors_count; i++) { - v8::Smi details = descriptors.GetDetails(i, err); - if (err.Fail()) return std::string(); - - v8::Value key = descriptors.GetKey(i, err); - if (err.Fail()) return std::string(); - if (!res.empty()) res += ",\n"; + v8::Value key = descriptors.GetKey(i); + ss.str(""); ss.clear(); - ss << rang::style::bold << rang::fg::yellow << " ." + key.ToString(err) - << rang::fg::reset << rang::style::reset; + ss << rang::style::bold << rang::fg::yellow << " ."; + if (key.Check()) { + ss << key.ToString(err); + } else { + PRINT_DEBUG("Failed to get key for index %ld", i); + ss << "???"; + } + ss << rang::fg::reset << rang::style::reset; + res += ss.str() + "="; if (err.Fail()) return std::string(); + v8::Smi details = descriptors.GetDetails(i); + if (!details.Check()) { + PRINT_DEBUG("Failed to get details for index %ld", i); + res += "???"; + continue; + } + if (descriptors.IsConstFieldDetails(details) || descriptors.IsDescriptorDetails(details)) { v8::Value value; - value = descriptors.GetValue(i, err); + value = descriptors.GetValue(i); + RETURN_IF_INVALID(value, std::string()); if (err.Fail()) return std::string(); res += printer.Stringify(value, err); diff --git a/test/common.js b/test/common.js index 50731a05..14b890e9 100644 --- a/test/common.js +++ b/test/common.js @@ -332,3 +332,10 @@ Session.prototype.hasSymbol = function hasSymbol(symbol, callback) { } }); }; + +function nodejsVersion() { + const version = process.version.substring(1, process.version.indexOf('-')); + const versionArray = version.split('.').map(s => Number(s)); + return versionArray; +} +exports.nodejsVersion = nodejsVersion; diff --git a/test/plugin/frame-test.js b/test/plugin/frame-test.js index f4304e91..7766b8a2 100644 --- a/test/plugin/frame-test.js +++ b/test/plugin/frame-test.js @@ -5,6 +5,7 @@ const { promisify } = require('util'); const tape = require('tape'); const common = require('../common'); +const { nodejsVersion } = common; const sourceCodes = { "fnFunctionName": [ @@ -86,7 +87,8 @@ tape('v8 stack', async (t) => { t.ok(//.test(exit), 'exit frame'); t.ok(/crasher/.test(crasher), 'crasher frame'); t.ok(//.test(adapter), 'arguments adapter frame'); - t.ok(/\sfnInferredName\(/.test(fnInferredName), 'fnInferredName frame'); + if (nodejsVersion()[0] < 12) + t.ok(/\sfnInferredName\(/.test(fnInferredName), 'fnInferredName frame'); t.ok(/\sModule.fnInferredNamePrototype\(/.test(fnInferredNamePrototype), 'fnInferredNamePrototype frame'); t.ok(/\sfnFunctionName\(/.test(fnFunctionName), 'fnFunctionName frame'); @@ -111,14 +113,16 @@ tape('v8 stack', async (t) => { fatalError(t, sess, "Couldn't determine fnFunctionName's frame number"); } - const fnInferredNamePrototypeFrame = - fnInferredNamePrototype.match(/frame #([0-9]+)/)[1]; - if (fnInferredNamePrototypeFrame) { - await testFrameList(t, sess, fnInferredNamePrototypeFrame, - sourceCodes['fnInferredNamePrototype']); - } else { - fatalError(t, sess, - "Couldn't determine fnInferredNamePrototype's frame number"); + if (nodejsVersion()[0] < 12) { + const fnInferredNamePrototypeFrame = + fnInferredNamePrototype.match(/frame #([0-9]+)/)[1]; + if (fnInferredNamePrototypeFrame) { + await testFrameList(t, sess, fnInferredNamePrototypeFrame, + sourceCodes['fnInferredNamePrototype']); + } else { + fatalError(t, sess, + "Couldn't determine fnInferredNamePrototype's frame number"); + } } const fnInferredNameFrame = fnInferredName.match(/frame #([0-9]+)/)[1];