// Copyright 2018 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "src/inspector/value-mirror.h" #include #include #include "src/debug/debug-interface.h" #include "src/inspector/v8-debugger.h" #include "src/inspector/v8-inspector-impl.h" #include "src/inspector/v8-value-utils.h" namespace v8_inspector { using protocol::Response; using protocol::Runtime::EntryPreview; using protocol::Runtime::ObjectPreview; using protocol::Runtime::PropertyPreview; using protocol::Runtime::RemoteObject; namespace { V8InspectorClient* clientFor(v8::Local context) { return static_cast( v8::debug::GetInspector(context->GetIsolate())) ->client(); } V8InternalValueType v8InternalValueTypeFrom(v8::Local context, v8::Local value) { if (!value->IsObject()) return V8InternalValueType::kNone; V8InspectorImpl* inspector = static_cast( v8::debug::GetInspector(context->GetIsolate())); int contextId = InspectedContext::contextId(context); InspectedContext* inspectedContext = inspector->getContext(contextId); if (!inspectedContext) return V8InternalValueType::kNone; return inspectedContext->getInternalType(value.As()); } Response toProtocolValue(v8::Local context, v8::Local value, int maxDepth, std::unique_ptr* result) { if (!maxDepth) return Response::Error("Object reference chain is too long"); maxDepth--; if (value->IsNull() || value->IsUndefined()) { *result = protocol::Value::null(); return Response::OK(); } if (value->IsBoolean()) { *result = protocol::FundamentalValue::create(value.As()->Value()); return Response::OK(); } if (value->IsNumber()) { double doubleValue = value.As()->Value(); if (doubleValue >= std::numeric_limits::min() && doubleValue <= std::numeric_limits::max() && bit_cast(doubleValue) != bit_cast(-0.0)) { int intValue = static_cast(doubleValue); if (intValue == doubleValue) { *result = protocol::FundamentalValue::create(intValue); return Response::OK(); } } *result = protocol::FundamentalValue::create(doubleValue); return Response::OK(); } if (value->IsString()) { *result = protocol::StringValue::create( toProtocolString(context->GetIsolate(), value.As())); return Response::OK(); } if (value->IsArray()) { v8::Local array = value.As(); std::unique_ptr inspectorArray = protocol::ListValue::create(); uint32_t length = array->Length(); for (uint32_t i = 0; i < length; i++) { v8::Local value; if (!array->Get(context, i).ToLocal(&value)) return Response::InternalError(); std::unique_ptr element; Response response = toProtocolValue(context, value, maxDepth, &element); if (!response.isSuccess()) return response; inspectorArray->pushValue(std::move(element)); } *result = std::move(inspectorArray); return Response::OK(); } if (value->IsObject()) { std::unique_ptr jsonObject = protocol::DictionaryValue::create(); v8::Local object = v8::Local::Cast(value); v8::Local propertyNames; if (!object->GetPropertyNames(context).ToLocal(&propertyNames)) return Response::InternalError(); uint32_t length = propertyNames->Length(); for (uint32_t i = 0; i < length; i++) { v8::Local name; if (!propertyNames->Get(context, i).ToLocal(&name)) return Response::InternalError(); // FIXME(yurys): v8::Object should support GetOwnPropertyNames if (name->IsString()) { v8::Maybe hasRealNamedProperty = object->HasRealNamedProperty( context, v8::Local::Cast(name)); if (hasRealNamedProperty.IsNothing() || !hasRealNamedProperty.FromJust()) continue; } v8::Local propertyName; if (!name->ToString(context).ToLocal(&propertyName)) continue; v8::Local property; if (!object->Get(context, name).ToLocal(&property)) return Response::InternalError(); if (property->IsUndefined()) continue; std::unique_ptr propertyValue; Response response = toProtocolValue(context, property, maxDepth, &propertyValue); if (!response.isSuccess()) return response; jsonObject->setValue( toProtocolString(context->GetIsolate(), propertyName), std::move(propertyValue)); } *result = std::move(jsonObject); return Response::OK(); } return Response::Error("Object couldn't be returned by value"); } Response toProtocolValue(v8::Local context, v8::Local value, std::unique_ptr* result) { if (value->IsUndefined()) return Response::OK(); return toProtocolValue(context, value, 1000, result); } enum AbbreviateMode { kMiddle, kEnd }; String16 abbreviateString(const String16& value, AbbreviateMode mode) { const size_t maxLength = 100; if (value.length() <= maxLength) return value; UChar ellipsis = static_cast(0x2026); if (mode == kMiddle) { return String16::concat( value.substring(0, maxLength / 2), String16(&ellipsis, 1), value.substring(value.length() - maxLength / 2 + 1)); } return String16::concat(value.substring(0, maxLength - 1), ellipsis); } String16 descriptionForSymbol(v8::Local context, v8::Local symbol) { return String16::concat( "Symbol(", toProtocolStringWithTypeCheck(context->GetIsolate(), symbol->Name()), ")"); } String16 descriptionForBigInt(v8::Local context, v8::Local value) { v8::Isolate* isolate = context->GetIsolate(); v8::TryCatch tryCatch(isolate); v8::Local description; if (!value->ToString(context).ToLocal(&description)) return String16(); return toProtocolString(isolate, description) + "n"; } String16 descriptionForPrimitiveType(v8::Local context, v8::Local value) { if (value->IsUndefined()) return RemoteObject::TypeEnum::Undefined; if (value->IsNull()) return RemoteObject::SubtypeEnum::Null; if (value->IsBoolean()) { return value.As()->Value() ? "true" : "false"; } if (value->IsString()) { return toProtocolString(context->GetIsolate(), value.As()); } UNREACHABLE(); return String16(); } String16 descriptionForRegExp(v8::Isolate* isolate, v8::Local value) { String16Builder description; description.append('/'); description.append(toProtocolString(isolate, value->GetSource())); description.append('/'); v8::RegExp::Flags flags = value->GetFlags(); if (flags & v8::RegExp::Flags::kGlobal) description.append('g'); if (flags & v8::RegExp::Flags::kIgnoreCase) description.append('i'); if (flags & v8::RegExp::Flags::kMultiline) description.append('m'); if (flags & v8::RegExp::Flags::kDotAll) description.append('s'); if (flags & v8::RegExp::Flags::kUnicode) description.append('u'); if (flags & v8::RegExp::Flags::kSticky) description.append('y'); return description.toString(); } enum class ErrorType { kNative, kClient }; String16 descriptionForError(v8::Local context, v8::Local object, ErrorType type) { v8::Isolate* isolate = context->GetIsolate(); v8::TryCatch tryCatch(isolate); String16 className = toProtocolString(isolate, object->GetConstructorName()); v8::Local stackValue; if (!object->Get(context, toV8String(isolate, "stack")) .ToLocal(&stackValue) || !stackValue->IsString()) { return className; } String16 stack = toProtocolString(isolate, stackValue.As()); String16 description = stack; if (type == ErrorType::kClient) { if (stack.substring(0, className.length()) != className) { v8::Local messageValue; if (!object->Get(context, toV8String(isolate, "message")) .ToLocal(&messageValue) || !messageValue->IsString()) { return stack; } String16 message = toProtocolStringWithTypeCheck(isolate, messageValue); size_t index = stack.find(message); String16 stackWithoutMessage = index != String16::kNotFound ? stack.substring(index + message.length()) : String16(); description = className + ": " + message + stackWithoutMessage; } } return description; } String16 descriptionForObject(v8::Isolate* isolate, v8::Local object) { return toProtocolString(isolate, object->GetConstructorName()); } String16 descriptionForDate(v8::Local context, v8::Local date) { v8::Isolate* isolate = context->GetIsolate(); v8::TryCatch tryCatch(isolate); v8::Local description; if (!date->ToString(context).ToLocal(&description)) { return descriptionForObject(isolate, date); } return toProtocolString(isolate, description); } String16 descriptionForScopeList(v8::Local list) { return String16::concat( "Scopes[", String16::fromInteger(static_cast(list->Length())), ']'); } String16 descriptionForScope(v8::Local context, v8::Local object) { v8::Isolate* isolate = context->GetIsolate(); v8::Local value; if (!object->GetRealNamedProperty(context, toV8String(isolate, "description")) .ToLocal(&value)) { return String16(); } return toProtocolStringWithTypeCheck(isolate, value); } String16 descriptionForCollection(v8::Isolate* isolate, v8::Local object, size_t length) { String16 className = toProtocolString(isolate, object->GetConstructorName()); return String16::concat(className, '(', String16::fromInteger(length), ')'); } String16 descriptionForEntry(v8::Local context, v8::Local object) { v8::Isolate* isolate = context->GetIsolate(); String16 key; v8::Local tmp; if (object->GetRealNamedProperty(context, toV8String(isolate, "key")) .ToLocal(&tmp)) { auto wrapper = ValueMirror::create(context, tmp); if (wrapper) { std::unique_ptr preview; int limit = 5; wrapper->buildEntryPreview(context, &limit, &limit, &preview); if (preview) { key = preview->getDescription(String16()); if (preview->getType() == RemoteObject::TypeEnum::String) { key = String16::concat('\"', key, '\"'); } } } } String16 value; if (object->GetRealNamedProperty(context, toV8String(isolate, "value")) .ToLocal(&tmp)) { auto wrapper = ValueMirror::create(context, tmp); if (wrapper) { std::unique_ptr preview; int limit = 5; wrapper->buildEntryPreview(context, &limit, &limit, &preview); if (preview) { value = preview->getDescription(String16()); if (preview->getType() == RemoteObject::TypeEnum::String) { value = String16::concat('\"', value, '\"'); } } } } return key.length() ? ("{" + key + " => " + value + "}") : value; } String16 descriptionForFunction(v8::Local context, v8::Local value) { v8::Isolate* isolate = context->GetIsolate(); v8::TryCatch tryCatch(isolate); v8::Local description; if (!value->ToString(context).ToLocal(&description)) { return descriptionForObject(isolate, value); } return toProtocolString(isolate, description); } class PrimitiveValueMirror final : public ValueMirror { public: PrimitiveValueMirror(v8::Local value, const String16& type) : m_value(value), m_type(type) {} v8::Local v8Value() const override { return m_value; } Response buildRemoteObject( v8::Local context, WrapMode mode, std::unique_ptr* result) const override { std::unique_ptr protocolValue; toProtocolValue(context, m_value, &protocolValue); *result = RemoteObject::create() .setType(m_type) .setValue(std::move(protocolValue)) .build(); if (m_value->IsNull()) (*result)->setSubtype(RemoteObject::SubtypeEnum::Null); return Response::OK(); } void buildEntryPreview( v8::Local context, int* nameLimit, int* indexLimit, std::unique_ptr* preview) const override { *preview = ObjectPreview::create() .setType(m_type) .setDescription(descriptionForPrimitiveType(context, m_value)) .setOverflow(false) .setProperties( v8::base::make_unique>()) .build(); if (m_value->IsNull()) (*preview)->setSubtype(RemoteObject::SubtypeEnum::Null); } void buildPropertyPreview( v8::Local context, const String16& name, std::unique_ptr* preview) const override { *preview = PropertyPreview::create() .setName(name) .setValue(abbreviateString( descriptionForPrimitiveType(context, m_value), kMiddle)) .setType(m_type) .build(); if (m_value->IsNull()) (*preview)->setSubtype(RemoteObject::SubtypeEnum::Null); } private: v8::Local m_value; String16 m_type; String16 m_subtype; }; class NumberMirror final : public ValueMirror { public: explicit NumberMirror(v8::Local value) : m_value(value) {} v8::Local v8Value() const override { return m_value; } Response buildRemoteObject( v8::Local context, WrapMode mode, std::unique_ptr* result) const override { bool unserializable = false; String16 descriptionValue = description(&unserializable); *result = RemoteObject::create() .setType(RemoteObject::TypeEnum::Number) .setDescription(descriptionValue) .build(); if (unserializable) { (*result)->setUnserializableValue(descriptionValue); } else { (*result)->setValue(protocol::FundamentalValue::create(m_value->Value())); } return Response::OK(); } void buildPropertyPreview( v8::Local context, const String16& name, std::unique_ptr* result) const override { bool unserializable = false; *result = PropertyPreview::create() .setName(name) .setType(RemoteObject::TypeEnum::Number) .setValue(description(&unserializable)) .build(); } void buildEntryPreview( v8::Local context, int* nameLimit, int* indexLimit, std::unique_ptr* preview) const override { bool unserializable = false; *preview = ObjectPreview::create() .setType(RemoteObject::TypeEnum::Number) .setDescription(description(&unserializable)) .setOverflow(false) .setProperties( v8::base::make_unique>()) .build(); } private: String16 description(bool* unserializable) const { *unserializable = true; double rawValue = m_value->Value(); if (std::isnan(rawValue)) return "NaN"; if (rawValue == 0.0 && std::signbit(rawValue)) return "-0"; if (std::isinf(rawValue)) { return std::signbit(rawValue) ? "-Infinity" : "Infinity"; } *unserializable = false; return String16::fromDouble(rawValue); } v8::Local m_value; }; class BigIntMirror final : public ValueMirror { public: explicit BigIntMirror(v8::Local value) : m_value(value) {} Response buildRemoteObject( v8::Local context, WrapMode mode, std::unique_ptr* result) const override { String16 description = descriptionForBigInt(context, m_value); *result = RemoteObject::create() .setType(RemoteObject::TypeEnum::Bigint) .setUnserializableValue(description) .setDescription(description) .build(); return Response::OK(); } void buildPropertyPreview(v8::Local context, const String16& name, std::unique_ptr* preview) const override { *preview = PropertyPreview::create() .setName(name) .setType(RemoteObject::TypeEnum::Bigint) .setValue(abbreviateString( descriptionForBigInt(context, m_value), kMiddle)) .build(); } void buildEntryPreview(v8::Local context, int* nameLimit, int* indexLimit, std::unique_ptr* preview) const override { *preview = ObjectPreview::create() .setType(RemoteObject::TypeEnum::Bigint) .setDescription(descriptionForBigInt(context, m_value)) .setOverflow(false) .setProperties( v8::base::make_unique>()) .build(); } v8::Local v8Value() const override { return m_value; } private: v8::Local m_value; }; class SymbolMirror final : public ValueMirror { public: explicit SymbolMirror(v8::Local value) : m_symbol(value.As()) {} Response buildRemoteObject( v8::Local context, WrapMode mode, std::unique_ptr* result) const override { if (mode == WrapMode::kForceValue) { return Response::Error("Object couldn't be returned by value"); } *result = RemoteObject::create() .setType(RemoteObject::TypeEnum::Symbol) .setDescription(descriptionForSymbol(context, m_symbol)) .build(); return Response::OK(); } void buildPropertyPreview(v8::Local context, const String16& name, std::unique_ptr* preview) const override { *preview = PropertyPreview::create() .setName(name) .setType(RemoteObject::TypeEnum::Symbol) .setValue(abbreviateString( descriptionForSymbol(context, m_symbol), kEnd)) .build(); } v8::Local v8Value() const override { return m_symbol; } private: v8::Local m_symbol; }; class LocationMirror final : public ValueMirror { public: static std::unique_ptr create( v8::Local function) { return create(function, function->ScriptId(), function->GetScriptLineNumber(), function->GetScriptColumnNumber()); } static std::unique_ptr createForGenerator( v8::Local value) { v8::Local generatorObject = v8::debug::GeneratorObject::Cast(value); if (!generatorObject->IsSuspended()) { return create(generatorObject->Function()); } v8::Local script; if (!generatorObject->Script().ToLocal(&script)) return nullptr; v8::debug::Location suspendedLocation = generatorObject->SuspendedLocation(); return create(value, script->Id(), suspendedLocation.GetLineNumber(), suspendedLocation.GetColumnNumber()); } Response buildRemoteObject( v8::Local context, WrapMode mode, std::unique_ptr* result) const override { auto location = protocol::DictionaryValue::create(); location->setString("scriptId", String16::fromInteger(m_scriptId)); location->setInteger("lineNumber", m_lineNumber); location->setInteger("columnNumber", m_columnNumber); *result = RemoteObject::create() .setType(RemoteObject::TypeEnum::Object) .setSubtype("internal#location") .setDescription("Object") .setValue(std::move(location)) .build(); return Response::OK(); } v8::Local v8Value() const override { return m_value; } private: static std::unique_ptr create(v8::Local value, int scriptId, int lineNumber, int columnNumber) { if (scriptId == v8::UnboundScript::kNoScriptId) return nullptr; if (lineNumber == v8::Function::kLineOffsetNotFound || columnNumber == v8::Function::kLineOffsetNotFound) { return nullptr; } return std::unique_ptr( new LocationMirror(value, scriptId, lineNumber, columnNumber)); } LocationMirror(v8::Local value, int scriptId, int lineNumber, int columnNumber) : m_value(value), m_scriptId(scriptId), m_lineNumber(lineNumber), m_columnNumber(columnNumber) {} v8::Local m_value; int m_scriptId; int m_lineNumber; int m_columnNumber; }; class FunctionMirror final : public ValueMirror { public: explicit FunctionMirror(v8::Local value) : m_value(value.As()) {} v8::Local v8Value() const override { return m_value; } Response buildRemoteObject( v8::Local context, WrapMode mode, std::unique_ptr* result) const override { // TODO(alph): drop this functionality. if (mode == WrapMode::kForceValue) { std::unique_ptr protocolValue; Response response = toProtocolValue(context, m_value, &protocolValue); if (!response.isSuccess()) return response; *result = RemoteObject::create() .setType(RemoteObject::TypeEnum::Function) .setValue(std::move(protocolValue)) .build(); } else { *result = RemoteObject::create() .setType(RemoteObject::TypeEnum::Function) .setClassName(toProtocolStringWithTypeCheck( context->GetIsolate(), m_value->GetConstructorName())) .setDescription(descriptionForFunction(context, m_value)) .build(); } return Response::OK(); } void buildPropertyPreview( v8::Local context, const String16& name, std::unique_ptr* result) const override { *result = PropertyPreview::create() .setName(name) .setType(RemoteObject::TypeEnum::Function) .setValue(String16()) .build(); } void buildEntryPreview( v8::Local context, int* nameLimit, int* indexLimit, std::unique_ptr* preview) const override { *preview = ObjectPreview::create() .setType(RemoteObject::TypeEnum::Function) .setDescription(descriptionForFunction(context, m_value)) .setOverflow(false) .setProperties( v8::base::make_unique>()) .build(); } private: v8::Local m_value; }; bool isArrayLike(v8::Local context, v8::Local value, size_t* length) { if (!value->IsObject()) return false; v8::Isolate* isolate = context->GetIsolate(); v8::TryCatch tryCatch(isolate); v8::MicrotasksScope microtasksScope(isolate, v8::MicrotasksScope::kDoNotRunMicrotasks); v8::Local object = value.As(); v8::Local spliceValue; if (!object->IsArgumentsObject() && (!object->GetRealNamedProperty(context, toV8String(isolate, "splice")) .ToLocal(&spliceValue) || !spliceValue->IsFunction())) { return false; } v8::Local lengthValue; v8::Maybe result = object->HasOwnProperty(context, toV8String(isolate, "length")); if (result.IsNothing()) return false; if (!result.FromJust() || !object->Get(context, toV8String(isolate, "length")) .ToLocal(&lengthValue) || !lengthValue->IsUint32()) { return false; } *length = v8::Local::Cast(lengthValue)->Value(); return true; } struct EntryMirror { std::unique_ptr key; std::unique_ptr value; static bool getEntries(v8::Local context, v8::Local object, size_t limit, bool* overflow, std::vector* mirrors) { bool isKeyValue = false; v8::Local entries; if (!object->PreviewEntries(&isKeyValue).ToLocal(&entries)) return false; for (uint32_t i = 0; i < entries->Length(); i += isKeyValue ? 2 : 1) { v8::Local tmp; std::unique_ptr keyMirror; if (isKeyValue && entries->Get(context, i).ToLocal(&tmp)) { keyMirror = ValueMirror::create(context, tmp); } std::unique_ptr valueMirror; if (entries->Get(context, isKeyValue ? i + 1 : i).ToLocal(&tmp)) { valueMirror = ValueMirror::create(context, tmp); } else { continue; } if (mirrors->size() == limit) { *overflow = true; return true; } mirrors->emplace_back( EntryMirror{std::move(keyMirror), std::move(valueMirror)}); } return mirrors->size() > 0; } }; class PreviewPropertyAccumulator : public ValueMirror::PropertyAccumulator { public: PreviewPropertyAccumulator(const std::vector& blacklist, int skipIndex, int* nameLimit, int* indexLimit, bool* overflow, std::vector* mirrors) : m_blacklist(blacklist), m_skipIndex(skipIndex), m_nameLimit(nameLimit), m_indexLimit(indexLimit), m_overflow(overflow), m_mirrors(mirrors) {} bool Add(PropertyMirror mirror) override { if (mirror.exception) return true; if ((!mirror.getter || !mirror.getter->v8Value()->IsFunction()) && !mirror.value) { return true; } if (!mirror.isOwn) return true; if (std::find(m_blacklist.begin(), m_blacklist.end(), mirror.name) != m_blacklist.end()) { return true; } if (mirror.isIndex && m_skipIndex > 0) { --m_skipIndex; if (m_skipIndex > 0) return true; } int* limit = mirror.isIndex ? m_indexLimit : m_nameLimit; if (!*limit) { *m_overflow = true; return false; } --*limit; m_mirrors->push_back(std::move(mirror)); return true; } private: std::vector m_blacklist; int m_skipIndex; int* m_nameLimit; int* m_indexLimit; bool* m_overflow; std::vector* m_mirrors; }; bool getPropertiesForPreview(v8::Local context, v8::Local object, int* nameLimit, int* indexLimit, bool* overflow, std::vector* properties) { std::vector blacklist; size_t length = 0; if (object->IsArray() || isArrayLike(context, object, &length) || object->IsStringObject()) { blacklist.push_back("length"); } else { auto clientSubtype = clientFor(context)->valueSubtype(object); if (clientSubtype && toString16(clientSubtype->string()) == "array") { blacklist.push_back("length"); } } if (object->IsArrayBuffer() || object->IsSharedArrayBuffer()) { blacklist.push_back("[[Int8Array]]"); blacklist.push_back("[[Uint8Array]]"); blacklist.push_back("[[Int16Array]]"); blacklist.push_back("[[Int32Array]]"); } int skipIndex = object->IsStringObject() ? object.As()->ValueOf()->Length() + 1 : -1; PreviewPropertyAccumulator accumulator(blacklist, skipIndex, nameLimit, indexLimit, overflow, properties); return ValueMirror::getProperties(context, object, false, false, &accumulator); } void getInternalPropertiesForPreview( v8::Local context, v8::Local object, int* nameLimit, bool* overflow, std::vector* properties) { std::vector mirrors; ValueMirror::getInternalProperties(context, object, &mirrors); std::vector whitelist; if (object->IsBooleanObject() || object->IsNumberObject() || object->IsStringObject() || object->IsSymbolObject() || object->IsBigIntObject()) { whitelist.emplace_back("[[PrimitiveValue]]"); } else if (object->IsPromise()) { whitelist.emplace_back("[[PromiseStatus]]"); whitelist.emplace_back("[[PromiseValue]]"); } else if (object->IsGeneratorObject()) { whitelist.emplace_back("[[GeneratorStatus]]"); } for (auto& mirror : mirrors) { if (std::find(whitelist.begin(), whitelist.end(), mirror.name) == whitelist.end()) { continue; } if (!*nameLimit) { *overflow = true; return; } --*nameLimit; properties->push_back(std::move(mirror)); } } void getPrivatePropertiesForPreview( v8::Local context, v8::Local object, int* nameLimit, bool* overflow, protocol::Array* privateProperties) { std::vector mirrors = ValueMirror::getPrivateProperties(context, object); std::vector whitelist; for (auto& mirror : mirrors) { std::unique_ptr propertyPreview; mirror.value->buildPropertyPreview(context, mirror.name, &propertyPreview); if (!propertyPreview) continue; if (!*nameLimit) { *overflow = true; return; } --*nameLimit; privateProperties->emplace_back(std::move(propertyPreview)); } } class ObjectMirror final : public ValueMirror { public: ObjectMirror(v8::Local value, const String16& description) : m_value(value.As()), m_description(description), m_hasSubtype(false) {} ObjectMirror(v8::Local value, const String16& subtype, const String16& description) : m_value(value.As()), m_description(description), m_hasSubtype(true), m_subtype(subtype) {} v8::Local v8Value() const override { return m_value; } Response buildRemoteObject( v8::Local context, WrapMode mode, std::unique_ptr* result) const override { if (mode == WrapMode::kForceValue) { std::unique_ptr protocolValue; Response response = toProtocolValue(context, m_value, &protocolValue); if (!response.isSuccess()) return response; *result = RemoteObject::create() .setType(RemoteObject::TypeEnum::Object) .setValue(std::move(protocolValue)) .build(); } else { v8::Isolate* isolate = context->GetIsolate(); *result = RemoteObject::create() .setType(RemoteObject::TypeEnum::Object) .setClassName(toProtocolString( isolate, m_value->GetConstructorName())) .setDescription(m_description) .build(); if (m_hasSubtype) (*result)->setSubtype(m_subtype); if (mode == WrapMode::kWithPreview) { std::unique_ptr previewValue; int nameLimit = 5; int indexLimit = 100; buildObjectPreview(context, false, &nameLimit, &indexLimit, &previewValue); (*result)->setPreview(std::move(previewValue)); } } return Response::OK(); } void buildObjectPreview( v8::Local context, bool generatePreviewForTable, int* nameLimit, int* indexLimit, std::unique_ptr* result) const override { buildObjectPreviewInternal(context, false /* forEntry */, generatePreviewForTable, nameLimit, indexLimit, result); } void buildEntryPreview( v8::Local context, int* nameLimit, int* indexLimit, std::unique_ptr* result) const override { buildObjectPreviewInternal(context, true /* forEntry */, false /* generatePreviewForTable */, nameLimit, indexLimit, result); } void buildPropertyPreview( v8::Local context, const String16& name, std::unique_ptr* result) const override { *result = PropertyPreview::create() .setName(name) .setType(RemoteObject::TypeEnum::Object) .setValue(abbreviateString( m_description, m_subtype == RemoteObject::SubtypeEnum::Regexp ? kMiddle : kEnd)) .build(); if (m_hasSubtype) (*result)->setSubtype(m_subtype); } private: void buildObjectPreviewInternal( v8::Local context, bool forEntry, bool generatePreviewForTable, int* nameLimit, int* indexLimit, std::unique_ptr* result) const { auto properties = v8::base::make_unique>(); std::unique_ptr> entriesPreview; bool overflow = false; v8::Local value = m_value; while (value->IsProxy()) value = value.As()->GetTarget(); if (value->IsObject() && !value->IsProxy()) { v8::Local objectForPreview = value.As(); std::vector internalProperties; getInternalPropertiesForPreview(context, objectForPreview, nameLimit, &overflow, &internalProperties); for (size_t i = 0; i < internalProperties.size(); ++i) { std::unique_ptr propertyPreview; internalProperties[i].value->buildPropertyPreview( context, internalProperties[i].name, &propertyPreview); if (propertyPreview) { properties->emplace_back(std::move(propertyPreview)); } } getPrivatePropertiesForPreview(context, objectForPreview, nameLimit, &overflow, properties.get()); std::vector mirrors; if (getPropertiesForPreview(context, objectForPreview, nameLimit, indexLimit, &overflow, &mirrors)) { for (size_t i = 0; i < mirrors.size(); ++i) { std::unique_ptr preview; std::unique_ptr valuePreview; if (mirrors[i].value) { mirrors[i].value->buildPropertyPreview(context, mirrors[i].name, &preview); if (generatePreviewForTable) { int tableLimit = 1000; mirrors[i].value->buildObjectPreview(context, false, &tableLimit, &tableLimit, &valuePreview); } } else { preview = PropertyPreview::create() .setName(mirrors[i].name) .setType(PropertyPreview::TypeEnum::Accessor) .build(); } if (valuePreview) { preview->setValuePreview(std::move(valuePreview)); } properties->emplace_back(std::move(preview)); } } std::vector entries; if (EntryMirror::getEntries(context, objectForPreview, 5, &overflow, &entries)) { if (forEntry) { overflow = true; } else { entriesPreview = v8::base::make_unique>(); for (const auto& entry : entries) { std::unique_ptr valuePreview; entry.value->buildEntryPreview(context, nameLimit, indexLimit, &valuePreview); if (!valuePreview) continue; std::unique_ptr keyPreview; if (entry.key) { entry.key->buildEntryPreview(context, nameLimit, indexLimit, &keyPreview); if (!keyPreview) continue; } std::unique_ptr entryPreview = EntryPreview::create() .setValue(std::move(valuePreview)) .build(); if (keyPreview) entryPreview->setKey(std::move(keyPreview)); entriesPreview->emplace_back(std::move(entryPreview)); } } } } *result = ObjectPreview::create() .setType(RemoteObject::TypeEnum::Object) .setDescription(m_description) .setOverflow(overflow) .setProperties(std::move(properties)) .build(); if (m_hasSubtype) (*result)->setSubtype(m_subtype); if (entriesPreview) (*result)->setEntries(std::move(entriesPreview)); } v8::Local m_value; String16 m_description; bool m_hasSubtype; String16 m_subtype; }; void nativeGetterCallback(const v8::FunctionCallbackInfo& info) { v8::Local data = info.Data().As(); v8::Isolate* isolate = info.GetIsolate(); v8::Local context = isolate->GetCurrentContext(); v8::Local name; if (!data->GetRealNamedProperty(context, toV8String(isolate, "name")) .ToLocal(&name)) { return; } v8::Local object; if (!data->GetRealNamedProperty(context, toV8String(isolate, "object")) .ToLocal(&object) || !object->IsObject()) { return; } v8::Local value; if (!object.As()->Get(context, name).ToLocal(&value)) return; info.GetReturnValue().Set(value); } std::unique_ptr createNativeGetter(v8::Local context, v8::Local object, v8::Local name) { v8::Isolate* isolate = context->GetIsolate(); v8::TryCatch tryCatch(isolate); v8::Local data = v8::Object::New(isolate); if (data->Set(context, toV8String(isolate, "name"), name).IsNothing()) { return nullptr; } if (data->Set(context, toV8String(isolate, "object"), object).IsNothing()) { return nullptr; } v8::Local function; if (!v8::Function::New(context, nativeGetterCallback, data, 0, v8::ConstructorBehavior::kThrow) .ToLocal(&function)) { return nullptr; } return ValueMirror::create(context, function); } void nativeSetterCallback(const v8::FunctionCallbackInfo& info) { if (info.Length() < 1) return; v8::Local data = info.Data().As(); v8::Isolate* isolate = info.GetIsolate(); v8::Local context = isolate->GetCurrentContext(); v8::Local name; if (!data->GetRealNamedProperty(context, toV8String(isolate, "name")) .ToLocal(&name)) { return; } v8::Local object; if (!data->GetRealNamedProperty(context, toV8String(isolate, "object")) .ToLocal(&object) || !object->IsObject()) { return; } v8::Local value; if (!object.As()->Set(context, name, info[0]).IsNothing()) return; } std::unique_ptr createNativeSetter(v8::Local context, v8::Local object, v8::Local name) { v8::Isolate* isolate = context->GetIsolate(); v8::TryCatch tryCatch(isolate); v8::Local data = v8::Object::New(isolate); if (data->Set(context, toV8String(isolate, "name"), name).IsNothing()) { return nullptr; } if (data->Set(context, toV8String(isolate, "object"), object).IsNothing()) { return nullptr; } v8::Local function; if (!v8::Function::New(context, nativeSetterCallback, data, 1, v8::ConstructorBehavior::kThrow) .ToLocal(&function)) { return nullptr; } return ValueMirror::create(context, function); } bool doesAttributeHaveObservableSideEffectOnGet(v8::Local context, v8::Local object, v8::Local name) { // TODO(dgozman): we should remove this, annotate more embedder properties as // side-effect free, and call all getters which do not produce side effects. if (!name->IsString()) return false; v8::Isolate* isolate = context->GetIsolate(); if (!name.As()->StringEquals(toV8String(isolate, "body"))) { return false; } v8::TryCatch tryCatch(isolate); v8::Local request; if (context->Global() ->GetRealNamedProperty(context, toV8String(isolate, "Request")) .ToLocal(&request)) { if (request->IsObject() && object->InstanceOf(context, request.As()) .FromMaybe(false)) { return true; } } if (tryCatch.HasCaught()) tryCatch.Reset(); v8::Local response; if (context->Global() ->GetRealNamedProperty(context, toV8String(isolate, "Response")) .ToLocal(&response)) { if (response->IsObject() && object->InstanceOf(context, response.As()) .FromMaybe(false)) { return true; } } return false; } template void addTypedArrayView(v8::Local context, v8::Local buffer, size_t length, const char* name, ValueMirror::PropertyAccumulator* accumulator) { accumulator->Add(PropertyMirror{ String16(name), false, false, false, true, false, ValueMirror::create(context, ArrayView::New(buffer, 0, length)), nullptr, nullptr, nullptr, nullptr}); } template void addTypedArrayViews(v8::Local context, v8::Local buffer, ValueMirror::PropertyAccumulator* accumulator) { // TODO(alph): these should be internal properties. // TODO(v8:9308): Reconsider how large arrays are previewed. const size_t byte_length = buffer->ByteLength(); size_t length = byte_length; if (length > v8::TypedArray::kMaxLength) return; addTypedArrayView(context, buffer, length, "[[Int8Array]]", accumulator); addTypedArrayView(context, buffer, length, "[[Uint8Array]]", accumulator); length = byte_length / 2; if (length > v8::TypedArray::kMaxLength || (byte_length % 2) != 0) return; addTypedArrayView(context, buffer, length, "[[Int16Array]]", accumulator); length = byte_length / 4; if (length > v8::TypedArray::kMaxLength || (byte_length % 4) != 0) return; addTypedArrayView(context, buffer, length, "[[Int32Array]]", accumulator); } } // anonymous namespace ValueMirror::~ValueMirror() = default; // static bool ValueMirror::getProperties(v8::Local context, v8::Local object, bool ownProperties, bool accessorPropertiesOnly, PropertyAccumulator* accumulator) { v8::Isolate* isolate = context->GetIsolate(); v8::TryCatch tryCatch(isolate); v8::Local set = v8::Set::New(isolate); v8::MicrotasksScope microtasksScope(isolate, v8::MicrotasksScope::kDoNotRunMicrotasks); V8InternalValueType internalType = v8InternalValueTypeFrom(context, object); if (internalType == V8InternalValueType::kScope) { v8::Local value; if (!object->Get(context, toV8String(isolate, "object")).ToLocal(&value) || !value->IsObject()) { return false; } else { object = value.As(); } } if (internalType == V8InternalValueType::kScopeList) { if (!set->Add(context, toV8String(isolate, "length")).ToLocal(&set)) { return false; } } bool shouldSkipProto = internalType == V8InternalValueType::kScopeList; bool formatAccessorsAsProperties = clientFor(context)->formatAccessorsAsProperties(object); if (object->IsArrayBuffer()) { addTypedArrayViews(context, object.As(), accumulator); } if (object->IsSharedArrayBuffer()) { addTypedArrayViews(context, object.As(), accumulator); } for (auto iterator = v8::debug::PropertyIterator::Create(object); !iterator->Done(); iterator->Advance()) { bool isOwn = iterator->is_own(); if (!isOwn && ownProperties) break; v8::Local v8Name = iterator->name(); v8::Maybe result = set->Has(context, v8Name); if (result.IsNothing()) return false; if (result.FromJust()) continue; if (!set->Add(context, v8Name).ToLocal(&set)) return false; String16 name; std::unique_ptr symbolMirror; if (v8Name->IsString()) { name = toProtocolString(isolate, v8Name.As()); } else { v8::Local symbol = v8Name.As(); name = descriptionForSymbol(context, symbol); symbolMirror = ValueMirror::create(context, symbol); } v8::PropertyAttribute attributes; std::unique_ptr valueMirror; std::unique_ptr getterMirror; std::unique_ptr setterMirror; std::unique_ptr exceptionMirror; bool writable = false; bool enumerable = false; bool configurable = false; bool isAccessorProperty = false; v8::TryCatch tryCatch(isolate); if (!iterator->attributes().To(&attributes)) { exceptionMirror = ValueMirror::create(context, tryCatch.Exception()); } else { if (iterator->is_native_accessor()) { if (iterator->has_native_getter()) { getterMirror = createNativeGetter(context, object, v8Name); } if (iterator->has_native_setter()) { setterMirror = createNativeSetter(context, object, v8Name); } writable = !(attributes & v8::PropertyAttribute::ReadOnly); enumerable = !(attributes & v8::PropertyAttribute::DontEnum); configurable = !(attributes & v8::PropertyAttribute::DontDelete); isAccessorProperty = getterMirror || setterMirror; } else { v8::TryCatch tryCatch(isolate); v8::debug::PropertyDescriptor descriptor; if (!iterator->descriptor().To(&descriptor)) { exceptionMirror = ValueMirror::create(context, tryCatch.Exception()); } else { writable = descriptor.has_writable ? descriptor.writable : false; enumerable = descriptor.has_enumerable ? descriptor.enumerable : false; configurable = descriptor.has_configurable ? descriptor.configurable : false; if (!descriptor.value.IsEmpty()) { valueMirror = ValueMirror::create(context, descriptor.value); } bool getterIsNativeFunction = false; if (!descriptor.get.IsEmpty()) { v8::Local get = descriptor.get; getterMirror = ValueMirror::create(context, get); getterIsNativeFunction = get->IsFunction() && get.As()->ScriptId() == v8::UnboundScript::kNoScriptId; } if (!descriptor.set.IsEmpty()) { setterMirror = ValueMirror::create(context, descriptor.set); } isAccessorProperty = getterMirror || setterMirror; bool isSymbolDescription = object->IsSymbol() && name == "description"; if (isSymbolDescription || (name != "__proto__" && getterIsNativeFunction && formatAccessorsAsProperties && !doesAttributeHaveObservableSideEffectOnGet(context, object, v8Name))) { v8::TryCatch tryCatch(isolate); v8::Local value; if (object->Get(context, v8Name).ToLocal(&value)) { valueMirror = ValueMirror::create(context, value); isOwn = true; setterMirror = nullptr; getterMirror = nullptr; } } } } } if (accessorPropertiesOnly && !isAccessorProperty) continue; auto mirror = PropertyMirror{name, writable, configurable, enumerable, isOwn, iterator->is_array_index(), std::move(valueMirror), std::move(getterMirror), std::move(setterMirror), std::move(symbolMirror), std::move(exceptionMirror)}; if (!accumulator->Add(std::move(mirror))) return true; } if (!shouldSkipProto && ownProperties && !object->IsProxy() && !accessorPropertiesOnly) { v8::Local prototype = object->GetPrototype(); if (prototype->IsObject()) { accumulator->Add(PropertyMirror{String16("__proto__"), true, true, false, true, false, ValueMirror::create(context, prototype), nullptr, nullptr, nullptr, nullptr}); } } return true; } // static void ValueMirror::getInternalProperties( v8::Local context, v8::Local object, std::vector* mirrors) { v8::Isolate* isolate = context->GetIsolate(); v8::MicrotasksScope microtasksScope(isolate, v8::MicrotasksScope::kDoNotRunMicrotasks); v8::TryCatch tryCatch(isolate); if (object->IsFunction()) { v8::Local function = object.As(); auto location = LocationMirror::create(function); if (location) { mirrors->emplace_back(InternalPropertyMirror{ String16("[[FunctionLocation]]"), std::move(location)}); } if (function->IsGeneratorFunction()) { mirrors->emplace_back(InternalPropertyMirror{ String16("[[IsGenerator]]"), ValueMirror::create(context, v8::True(context->GetIsolate()))}); } } if (object->IsGeneratorObject()) { auto location = LocationMirror::createForGenerator(object); if (location) { mirrors->emplace_back(InternalPropertyMirror{ String16("[[GeneratorLocation]]"), std::move(location)}); } } V8Debugger* debugger = static_cast(v8::debug::GetInspector(isolate)) ->debugger(); v8::Local properties; if (debugger->internalProperties(context, object).ToLocal(&properties)) { for (uint32_t i = 0; i < properties->Length(); i += 2) { v8::Local name; if (!properties->Get(context, i).ToLocal(&name) || !name->IsString()) { tryCatch.Reset(); continue; } v8::Local value; if (!properties->Get(context, i + 1).ToLocal(&value)) { tryCatch.Reset(); continue; } auto wrapper = ValueMirror::create(context, value); if (wrapper) { mirrors->emplace_back(InternalPropertyMirror{ toProtocolStringWithTypeCheck(context->GetIsolate(), name), std::move(wrapper)}); } } } } // static std::vector ValueMirror::getPrivateProperties( v8::Local context, v8::Local object) { std::vector mirrors; v8::Isolate* isolate = context->GetIsolate(); v8::MicrotasksScope microtasksScope(isolate, v8::MicrotasksScope::kDoNotRunMicrotasks); v8::TryCatch tryCatch(isolate); v8::Local privateProperties; if (!v8::debug::GetPrivateFields(context, object).ToLocal(&privateProperties)) return mirrors; for (uint32_t i = 0; i < privateProperties->Length(); i += 2) { v8::Local name; if (!privateProperties->Get(context, i).ToLocal(&name)) { tryCatch.Reset(); continue; } // Weirdly, v8::Private is set to be a subclass of v8::Data and // not v8::Value, meaning, we first need to upcast to v8::Data // and then downcast to v8::Private. Changing the hierarchy is a // breaking change now. Not sure if that's possible. // // TODO(gsathya): Add an IsPrivate method to the v8::Private and // assert here. v8::Local private_field = v8::Local::Cast(name); v8::Local private_name = private_field->Name(); DCHECK(!private_name->IsUndefined()); v8::Local value; if (!privateProperties->Get(context, i + 1).ToLocal(&value)) { tryCatch.Reset(); continue; } auto wrapper = ValueMirror::create(context, value); if (wrapper) { mirrors.emplace_back(PrivatePropertyMirror{ toProtocolStringWithTypeCheck(context->GetIsolate(), private_name), std::move(wrapper)}); } } return mirrors; } String16 descriptionForNode(v8::Local context, v8::Local value) { if (!value->IsObject()) return String16(); v8::Local object = value.As(); v8::Isolate* isolate = context->GetIsolate(); v8::TryCatch tryCatch(isolate); v8::Local nodeName; if (!object->Get(context, toV8String(isolate, "nodeName")) .ToLocal(&nodeName)) { return String16(); } String16 description; v8::Local toLowerCase = v8::debug::GetBuiltin(isolate, v8::debug::kStringToLowerCase); if (nodeName->IsString()) { if (!toLowerCase->Call(context, nodeName, 0, nullptr).ToLocal(&nodeName)) return String16(); if (nodeName->IsString()) { description = toProtocolString(isolate, nodeName.As()); } } if (!description.length()) { v8::Local value; if (!object->Get(context, toV8String(isolate, "constructor")) .ToLocal(&value) || !value->IsObject()) { return String16(); } if (!value.As() ->Get(context, toV8String(isolate, "name")) .ToLocal(&value) || !value->IsString()) { return String16(); } description = toProtocolString(isolate, value.As()); } v8::Local nodeType; if (!object->Get(context, toV8String(isolate, "nodeType")) .ToLocal(&nodeType) || !nodeType->IsInt32()) { return description; } if (nodeType.As()->Value() == 1) { v8::Local idValue; if (!object->Get(context, toV8String(isolate, "id")).ToLocal(&idValue)) { return description; } if (idValue->IsString()) { String16 id = toProtocolString(isolate, idValue.As()); if (id.length()) { description = String16::concat(description, '#', id); } } v8::Local classNameValue; if (!object->Get(context, toV8String(isolate, "className")) .ToLocal(&classNameValue)) { return description; } if (classNameValue->IsString() && classNameValue.As()->Length()) { String16 classes = toProtocolString(isolate, classNameValue.As()); String16Builder output; bool previousIsDot = false; for (size_t i = 0; i < classes.length(); ++i) { if (classes[i] == ' ') { if (!previousIsDot) { output.append('.'); previousIsDot = true; } } else { output.append(classes[i]); previousIsDot = classes[i] == '.'; } } description = String16::concat(description, '.', output.toString()); } } else if (nodeType.As()->Value() == 1) { return String16::concat("'); } return description; } std::unique_ptr clientMirror(v8::Local context, v8::Local value, const String16& subtype) { // TODO(alph): description and length retrieval should move to embedder. if (subtype == "node") { return v8::base::make_unique( value, subtype, descriptionForNode(context, value)); } if (subtype == "error") { return v8::base::make_unique( value, RemoteObject::SubtypeEnum::Error, descriptionForError(context, value.As(), ErrorType::kClient)); } if (subtype == "array" && value->IsObject()) { v8::Isolate* isolate = context->GetIsolate(); v8::TryCatch tryCatch(isolate); v8::Local object = value.As(); v8::Local lengthValue; if (object->Get(context, toV8String(isolate, "length")) .ToLocal(&lengthValue)) { if (lengthValue->IsInt32()) { return v8::base::make_unique( value, RemoteObject::SubtypeEnum::Array, descriptionForCollection(isolate, object, lengthValue.As()->Value())); } } } return v8::base::make_unique( value, descriptionForObject(context->GetIsolate(), value.As())); } std::unique_ptr ValueMirror::create(v8::Local context, v8::Local value) { if (value->IsNull()) { return v8::base::make_unique( value, RemoteObject::TypeEnum::Object); } if (value->IsBoolean()) { return v8::base::make_unique( value, RemoteObject::TypeEnum::Boolean); } if (value->IsNumber()) { return v8::base::make_unique(value.As()); } v8::Isolate* isolate = context->GetIsolate(); if (value->IsString()) { return v8::base::make_unique( value, RemoteObject::TypeEnum::String); } if (value->IsBigInt()) { return v8::base::make_unique(value.As()); } if (value->IsSymbol()) { return v8::base::make_unique(value.As()); } auto clientSubtype = (value->IsUndefined() || value->IsObject()) ? clientFor(context)->valueSubtype(value) : nullptr; if (clientSubtype) { String16 subtype = toString16(clientSubtype->string()); return clientMirror(context, value, subtype); } if (value->IsUndefined()) { return v8::base::make_unique( value, RemoteObject::TypeEnum::Undefined); } if (value->IsRegExp()) { return v8::base::make_unique( value, RemoteObject::SubtypeEnum::Regexp, descriptionForRegExp(isolate, value.As())); } if (value->IsFunction()) { return v8::base::make_unique(value); } if (value->IsProxy()) { return v8::base::make_unique( value, RemoteObject::SubtypeEnum::Proxy, "Proxy"); } if (value->IsDate()) { return v8::base::make_unique( value, RemoteObject::SubtypeEnum::Date, descriptionForDate(context, value.As())); } if (value->IsPromise()) { v8::Local promise = value.As(); return v8::base::make_unique( promise, RemoteObject::SubtypeEnum::Promise, descriptionForObject(isolate, promise)); } if (value->IsNativeError()) { return v8::base::make_unique( value, RemoteObject::SubtypeEnum::Error, descriptionForError(context, value.As(), ErrorType::kNative)); } if (value->IsMap()) { v8::Local map = value.As(); return v8::base::make_unique( value, RemoteObject::SubtypeEnum::Map, descriptionForCollection(isolate, map, map->Size())); } if (value->IsSet()) { v8::Local set = value.As(); return v8::base::make_unique( value, RemoteObject::SubtypeEnum::Set, descriptionForCollection(isolate, set, set->Size())); } if (value->IsWeakMap()) { return v8::base::make_unique( value, RemoteObject::SubtypeEnum::Weakmap, descriptionForObject(isolate, value.As())); } if (value->IsWeakSet()) { return v8::base::make_unique( value, RemoteObject::SubtypeEnum::Weakset, descriptionForObject(isolate, value.As())); } if (value->IsMapIterator() || value->IsSetIterator()) { return v8::base::make_unique( value, RemoteObject::SubtypeEnum::Iterator, descriptionForObject(isolate, value.As())); } if (value->IsGeneratorObject()) { v8::Local object = value.As(); return v8::base::make_unique( object, RemoteObject::SubtypeEnum::Generator, descriptionForObject(isolate, object)); } if (value->IsTypedArray()) { v8::Local array = value.As(); return v8::base::make_unique( value, RemoteObject::SubtypeEnum::Typedarray, descriptionForCollection(isolate, array, array->Length())); } if (value->IsArrayBuffer()) { v8::Local buffer = value.As(); return v8::base::make_unique( value, RemoteObject::SubtypeEnum::Arraybuffer, descriptionForCollection(isolate, buffer, buffer->ByteLength())); } if (value->IsSharedArrayBuffer()) { v8::Local buffer = value.As(); return v8::base::make_unique( value, RemoteObject::SubtypeEnum::Arraybuffer, descriptionForCollection(isolate, buffer, buffer->ByteLength())); } if (value->IsDataView()) { v8::Local view = value.As(); return v8::base::make_unique( value, RemoteObject::SubtypeEnum::Dataview, descriptionForCollection(isolate, view, view->ByteLength())); } V8InternalValueType internalType = v8InternalValueTypeFrom(context, v8::Local::Cast(value)); if (value->IsArray() && internalType == V8InternalValueType::kScopeList) { return v8::base::make_unique( value, "internal#scopeList", descriptionForScopeList(value.As())); } if (value->IsObject() && internalType == V8InternalValueType::kEntry) { return v8::base::make_unique( value, "internal#entry", descriptionForEntry(context, value.As())); } if (value->IsObject() && internalType == V8InternalValueType::kScope) { return v8::base::make_unique( value, "internal#scope", descriptionForScope(context, value.As())); } size_t length = 0; if (value->IsArray() || isArrayLike(context, value, &length)) { length = value->IsArray() ? value.As()->Length() : length; return v8::base::make_unique( value, RemoteObject::SubtypeEnum::Array, descriptionForCollection(isolate, value.As(), length)); } if (value->IsObject()) { return v8::base::make_unique( value, descriptionForObject(isolate, value.As())); } return nullptr; } } // namespace v8_inspector