diff --git a/test/cctest/test-debug-helper.cc b/test/cctest/test-debug-helper.cc index 76cac13a66..513b9ac1b9 100644 --- a/test/cctest/test-debug-helper.cc +++ b/test/cctest/test-debug-helper.cc @@ -102,6 +102,21 @@ TValue ReadProp(const d::ObjectPropertiesResult& props, std::string name) { return *reinterpret_cast(prop.address); } +// A simple implementation of ExternalStringResource that lets us control the +// result of IsCacheable(). +class StringResource : public v8::String::ExternalStringResource { + public: + explicit StringResource(bool cacheable) : cacheable_(cacheable) {} + const uint16_t* data() const override { + return reinterpret_cast(u"abcde"); + } + size_t length() const override { return 5; } + bool IsCacheable() const override { return cacheable_; } + + private: + bool cacheable_; +}; + } // namespace TEST(GetObjectProperties) { @@ -290,6 +305,19 @@ TEST(GetObjectProperties) { props = d::GetObjectProperties(o->ptr(), &ReadMemory, heap_addresses); CHECK(Contains(props->brief, "\"" + std::string(80, 'a') + "...\"")); + // GetObjectProperties can read cacheable external strings. + auto external_string = + v8::String::NewExternalTwoByte(isolate, new StringResource(true)); + o = v8::Utils::OpenHandle(*external_string.ToLocalChecked()); + props = d::GetObjectProperties(o->ptr(), &ReadMemory, heap_addresses); + CHECK(Contains(props->brief, "\"abcde\"")); + // GetObjectProperties cannot read uncacheable external strings. + external_string = + v8::String::NewExternalTwoByte(isolate, new StringResource(false)); + o = v8::Utils::OpenHandle(*external_string.ToLocalChecked()); + props = d::GetObjectProperties(o->ptr(), &ReadMemory, heap_addresses); + CHECK_EQ(std::string(props->brief).find("\""), std::string::npos); + // Build a basic JS object and get its properties. v = CompileRun("({a: 1, b: 2})"); o = v8::Utils::OpenHandle(*v); diff --git a/tools/debug_helper/get-object-properties.cc b/tools/debug_helper/get-object-properties.cc index 13012f4bf0..f3bd4811f2 100644 --- a/tools/debug_helper/get-object-properties.cc +++ b/tools/debug_helper/get-object-properties.cc @@ -153,7 +153,7 @@ TypedObject GetTypedHeapObject(uintptr_t address, d::MemoryAccessor accessor, // We can't read the Map, so check whether it is in the list of known Maps, // as another way to get its instance type. KnownInstanceType known_map_type = - FindKnownMapInstanceType(map_ptr.value, heap_addresses); + FindKnownMapInstanceTypes(map_ptr.value, heap_addresses); if (known_map_type.confidence == KnownInstanceType::Confidence::kHigh) { DCHECK_EQ(known_map_type.types.size(), 1); return GetTypedObjectByInstanceType(address, known_map_type.types[0], @@ -195,16 +195,17 @@ TypedObject GetTypedHeapObject(uintptr_t address, d::MemoryAccessor accessor, // An object visitor that accumulates the first few characters of a string. class ReadStringVisitor : public TqObjectVisitor { public: - ReadStringVisitor(d::MemoryAccessor accessor, - const d::HeapAddresses& heap_addresses) - : accessor_(accessor), - heap_addresses_(heap_addresses), - index_(0), - limit_(INT32_MAX), - done_(false) {} + static v8::base::Optional Visit( + d::MemoryAccessor accessor, const d::HeapAddresses& heap_addresses, + const TqString* object) { + ReadStringVisitor visitor(accessor, heap_addresses); + object->Visit(&visitor); + return visitor.GetString(); + } // Returns the result as UTF-8 once visiting is complete. - std::string GetString() { + v8::base::Optional GetString() { + if (failed_) return {}; std::vector result( string_.size() * unibrow::Utf16::kMaxExtraUtf8BytesForOneUtf16CodeUnit); unsigned write_index = 0; @@ -216,25 +217,40 @@ class ReadStringVisitor : public TqObjectVisitor { prev_char, /*replace_invalid=*/true); prev_char = character; } - return {result.data(), write_index}; + return std::string(result.data(), write_index); } - template - void ReadSeqString(const T* object) { + template + Value ReadCharacter(uintptr_t data_address, int32_t index) { + TChar value{}; + d::MemoryAccessResult validity = + accessor_(data_address + index * sizeof(TChar), + reinterpret_cast(&value), sizeof(value)); + return {validity, value}; + } + + template + void ReadStringCharacters(const TqString* object, uintptr_t data_address) { int32_t length = GetOrFinish(object->GetLengthValue(accessor_)); for (; index_ < length && index_ < limit_ && !done_; ++index_) { + STATIC_ASSERT(sizeof(TChar) <= sizeof(char16_t)); char16_t c = static_cast( - GetOrFinish(object->GetCharsValue(accessor_, index_))); + GetOrFinish(ReadCharacter(data_address, index_))); if (!done_) AddCharacter(c); } } + template + void ReadSeqString(const TString* object) { + ReadStringCharacters(object, object->GetCharsAddress()); + } + void VisitSeqOneByteString(const TqSeqOneByteString* object) override { - ReadSeqString(object); + ReadSeqString(object); } void VisitSeqTwoByteString(const TqSeqTwoByteString* object) override { - ReadSeqString(object); + ReadSeqString(object); } void VisitConsString(const TqConsString* object) override { @@ -273,13 +289,57 @@ class ReadStringVisitor : public TqObjectVisitor { .object->Visit(this); } - void VisitExternalString(const TqExternalString* object) override { - // TODO(v8:9376): External strings are very common and important when - // attempting to print the source of a function in the browser. For now - // we're just ignoring them, but eventually we'll want some kind of - // mechanism where the user of this library can provide a callback function - // that fetches data from external strings. - AddEllipsisAndFinish(); + bool IsExternalStringCached(const TqExternalString* object) { + // The safest way to get the instance type is to use known map pointers, in + // case the map data is not available. + uintptr_t map = GetOrFinish(object->GetMapValue(accessor_)); + if (done_) return false; + auto instance_types = FindKnownMapInstanceTypes(map, heap_addresses_); + // Exactly one of the matched instance types should be a string type, + // because all maps for string types are in the same space (read-only + // space). The "uncached" flag on that instance type tells us whether it's + // safe to read the cached data. + for (const auto& type : instance_types.types) { + if ((type & i::kIsNotStringMask) == i::kStringTag && + (type & i::kStringRepresentationMask) == i::kExternalStringTag) { + return (type & i::kUncachedExternalStringMask) != + i::kUncachedExternalStringTag; + } + } + + // If for some reason we can't find an external string type here (maybe the + // caller provided an external string type as the type hint, but it doesn't + // actually match the in-memory map pointer), then we can't safely use the + // cached data. + return false; + } + + template + void ReadExternalString(const TqExternalString* object) { + // Cached external strings are easy to read; uncached external strings + // require knowledge of the embedder. For now, we only read cached external + // strings. + if (IsExternalStringCached(object)) { + uintptr_t data_address = reinterpret_cast( + GetOrFinish(object->GetResourceDataValue(accessor_))); + if (done_) return; + ReadStringCharacters(object, data_address); + } else { + // TODO(v8:9376): Come up with some way that a caller with full knowledge + // of a particular embedder could provide a callback function for getting + // uncached string data. + AddEllipsisAndFinish(); + } + } + + void VisitExternalOneByteString( + const TqExternalOneByteString* object) override { + ReadExternalString(object); + } + + void VisitExternalTwoByteString( + const TqExternalTwoByteString* object) override { + ReadExternalString(object); } void VisitObject(const TqObject* object) override { @@ -289,6 +349,15 @@ class ReadStringVisitor : public TqObjectVisitor { } private: + ReadStringVisitor(d::MemoryAccessor accessor, + const d::HeapAddresses& heap_addresses) + : accessor_(accessor), + heap_addresses_(heap_addresses), + index_(0), + limit_(INT32_MAX), + done_(false), + failed_(false) {} + // Unpacks a value that was fetched from the debuggee. If the value indicates // that it couldn't successfully fetch memory, then prevents further work. template @@ -301,8 +370,12 @@ class ReadStringVisitor : public TqObjectVisitor { void AddEllipsisAndFinish() { if (!done_) { - string_ += u"..."; done_ = true; + if (string_.empty()) { + failed_ = true; + } else { + string_ += u"..."; + } } } @@ -346,6 +419,7 @@ class ReadStringVisitor : public TqObjectVisitor { int32_t index_; // Index of next char to read. int32_t limit_; // Don't read past this index (set by SlicedString). bool done_; // Whether to stop further work. + bool failed_; // Whether an error was encountered before any valid data. }; // An object visitor that supplies extra information for some types. @@ -363,9 +437,10 @@ class AddInfoVisitor : public TqObjectVisitor { } void VisitString(const TqString* object) override { - ReadStringVisitor visitor(accessor_, heap_addresses_); - object->Visit(&visitor); - brief_ = "\"" + visitor.GetString() + "\""; + auto str = ReadStringVisitor::Visit(accessor_, heap_addresses_, object); + if (str.has_value()) { + brief_ = "\"" + *str + "\""; + } } void VisitJSObject(const TqJSObject* object) override { diff --git a/tools/debug_helper/heap-constants.cc b/tools/debug_helper/heap-constants.cc index 9b9ed04cc1..edb47c80b1 100644 --- a/tools/debug_helper/heap-constants.cc +++ b/tools/debug_helper/heap-constants.cc @@ -49,7 +49,7 @@ std::string FindKnownObject(uintptr_t address, return result; } -KnownInstanceType FindKnownMapInstanceType( +KnownInstanceType FindKnownMapInstanceTypes( uintptr_t address, const d::HeapAddresses& heap_addresses) { uintptr_t containing_page = address & ~i::kPageAlignmentMask; uintptr_t offset_in_page = address & i::kPageAlignmentMask; diff --git a/tools/debug_helper/heap-constants.h b/tools/debug_helper/heap-constants.h index 6c1f17dc82..fb2d20df12 100644 --- a/tools/debug_helper/heap-constants.h +++ b/tools/debug_helper/heap-constants.h @@ -59,7 +59,7 @@ struct KnownInstanceType { // Returns information about the instance type of the Map at the given address, // based on the list of known Maps. -KnownInstanceType FindKnownMapInstanceType( +KnownInstanceType FindKnownMapInstanceTypes( uintptr_t address, const d::HeapAddresses& heap_addresses); } // namespace v8_debug_helper_internal