blob: 72eef3cd3f6a604d7556579f5687b53a3e426b34 [file] [log] [blame]
// 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 <algorithm>
#include <cmath>
#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<v8::Context> context) {
return static_cast<V8InspectorImpl*>(
v8::debug::GetInspector(context->GetIsolate()))
->client();
}
V8InternalValueType v8InternalValueTypeFrom(v8::Local<v8::Context> context,
v8::Local<v8::Value> value) {
if (!value->IsObject()) return V8InternalValueType::kNone;
V8InspectorImpl* inspector = static_cast<V8InspectorImpl*>(
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<v8::Object>());
}
Response toProtocolValue(v8::Local<v8::Context> context,
v8::Local<v8::Value> value, int maxDepth,
std::unique_ptr<protocol::Value>* 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<v8::Boolean>()->Value());
return Response::OK();
}
if (value->IsNumber()) {
double doubleValue = value.As<v8::Number>()->Value();
if (doubleValue >= std::numeric_limits<int>::min() &&
doubleValue <= std::numeric_limits<int>::max() &&
bit_cast<int64_t>(doubleValue) != bit_cast<int64_t>(-0.0)) {
int intValue = static_cast<int>(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<v8::String>()));
return Response::OK();
}
if (value->IsArray()) {
v8::Local<v8::Array> array = value.As<v8::Array>();
std::unique_ptr<protocol::ListValue> inspectorArray =
protocol::ListValue::create();
uint32_t length = array->Length();
for (uint32_t i = 0; i < length; i++) {
v8::Local<v8::Value> value;
if (!array->Get(context, i).ToLocal(&value))
return Response::InternalError();
std::unique_ptr<protocol::Value> 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<protocol::DictionaryValue> jsonObject =
protocol::DictionaryValue::create();
v8::Local<v8::Object> object = v8::Local<v8::Object>::Cast(value);
v8::Local<v8::Array> 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<v8::Value> name;
if (!propertyNames->Get(context, i).ToLocal(&name))
return Response::InternalError();
// FIXME(yurys): v8::Object should support GetOwnPropertyNames
if (name->IsString()) {
v8::Maybe<bool> hasRealNamedProperty = object->HasRealNamedProperty(
context, v8::Local<v8::String>::Cast(name));
if (hasRealNamedProperty.IsNothing() ||
!hasRealNamedProperty.FromJust())
continue;
}
v8::Local<v8::String> propertyName;
if (!name->ToString(context).ToLocal(&propertyName)) continue;
v8::Local<v8::Value> property;
if (!object->Get(context, name).ToLocal(&property))
return Response::InternalError();
if (property->IsUndefined()) continue;
std::unique_ptr<protocol::Value> 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<v8::Context> context,
v8::Local<v8::Value> value,
std::unique_ptr<protocol::Value>* 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<UChar>(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<v8::Context> context,
v8::Local<v8::Symbol> symbol) {
return String16::concat(
"Symbol(",
toProtocolStringWithTypeCheck(context->GetIsolate(), symbol->Name()),
")");
}
String16 descriptionForBigInt(v8::Local<v8::Context> context,
v8::Local<v8::BigInt> value) {
v8::Isolate* isolate = context->GetIsolate();
v8::TryCatch tryCatch(isolate);
v8::Local<v8::String> description;
if (!value->ToString(context).ToLocal(&description)) return String16();
return toProtocolString(isolate, description) + "n";
}
String16 descriptionForPrimitiveType(v8::Local<v8::Context> context,
v8::Local<v8::Value> value) {
if (value->IsUndefined()) return RemoteObject::TypeEnum::Undefined;
if (value->IsNull()) return RemoteObject::SubtypeEnum::Null;
if (value->IsBoolean()) {
return value.As<v8::Boolean>()->Value() ? "true" : "false";
}
if (value->IsString()) {
return toProtocolString(context->GetIsolate(), value.As<v8::String>());
}
UNREACHABLE();
return String16();
}
String16 descriptionForRegExp(v8::Isolate* isolate,
v8::Local<v8::RegExp> 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<v8::Context> context,
v8::Local<v8::Object> object, ErrorType type) {
v8::Isolate* isolate = context->GetIsolate();
v8::TryCatch tryCatch(isolate);
String16 className = toProtocolString(isolate, object->GetConstructorName());
v8::Local<v8::Value> stackValue;
if (!object->Get(context, toV8String(isolate, "stack"))
.ToLocal(&stackValue) ||
!stackValue->IsString()) {
return className;
}
String16 stack = toProtocolString(isolate, stackValue.As<v8::String>());
String16 description = stack;
if (type == ErrorType::kClient) {
if (stack.substring(0, className.length()) != className) {
v8::Local<v8::Value> 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<v8::Object> object) {
return toProtocolString(isolate, object->GetConstructorName());
}
String16 descriptionForDate(v8::Local<v8::Context> context,
v8::Local<v8::Date> date) {
v8::Isolate* isolate = context->GetIsolate();
v8::TryCatch tryCatch(isolate);
v8::Local<v8::String> description;
if (!date->ToString(context).ToLocal(&description)) {
return descriptionForObject(isolate, date);
}
return toProtocolString(isolate, description);
}
String16 descriptionForScopeList(v8::Local<v8::Array> list) {
return String16::concat(
"Scopes[", String16::fromInteger(static_cast<size_t>(list->Length())),
']');
}
String16 descriptionForScope(v8::Local<v8::Context> context,
v8::Local<v8::Object> object) {
v8::Isolate* isolate = context->GetIsolate();
v8::Local<v8::Value> value;
if (!object->GetRealNamedProperty(context, toV8String(isolate, "description"))
.ToLocal(&value)) {
return String16();
}
return toProtocolStringWithTypeCheck(isolate, value);
}
String16 descriptionForCollection(v8::Isolate* isolate,
v8::Local<v8::Object> object, size_t length) {
String16 className = toProtocolString(isolate, object->GetConstructorName());
return String16::concat(className, '(', String16::fromInteger(length), ')');
}
String16 descriptionForEntry(v8::Local<v8::Context> context,
v8::Local<v8::Object> object) {
v8::Isolate* isolate = context->GetIsolate();
String16 key;
v8::Local<v8::Value> tmp;
if (object->GetRealNamedProperty(context, toV8String(isolate, "key"))
.ToLocal(&tmp)) {
auto wrapper = ValueMirror::create(context, tmp);
if (wrapper) {
std::unique_ptr<ObjectPreview> 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<ObjectPreview> 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<v8::Context> context,
v8::Local<v8::Function> value) {
v8::Isolate* isolate = context->GetIsolate();
v8::TryCatch tryCatch(isolate);
v8::Local<v8::String> description;
if (!value->ToString(context).ToLocal(&description)) {
return descriptionForObject(isolate, value);
}
return toProtocolString(isolate, description);
}
class PrimitiveValueMirror final : public ValueMirror {
public:
PrimitiveValueMirror(v8::Local<v8::Value> value, const String16& type)
: m_value(value), m_type(type) {}
v8::Local<v8::Value> v8Value() const override { return m_value; }
Response buildRemoteObject(
v8::Local<v8::Context> context, WrapMode mode,
std::unique_ptr<RemoteObject>* result) const override {
std::unique_ptr<protocol::Value> 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<v8::Context> context, int* nameLimit, int* indexLimit,
std::unique_ptr<ObjectPreview>* preview) const override {
*preview =
ObjectPreview::create()
.setType(m_type)
.setDescription(descriptionForPrimitiveType(context, m_value))
.setOverflow(false)
.setProperties(protocol::Array<PropertyPreview>::create())
.build();
if (m_value->IsNull())
(*preview)->setSubtype(RemoteObject::SubtypeEnum::Null);
}
void buildPropertyPreview(
v8::Local<v8::Context> context, const String16& name,
std::unique_ptr<PropertyPreview>* 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<v8::Value> m_value;
String16 m_type;
String16 m_subtype;
};
class NumberMirror final : public ValueMirror {
public:
explicit NumberMirror(v8::Local<v8::Number> value) : m_value(value) {}
v8::Local<v8::Value> v8Value() const override { return m_value; }
Response buildRemoteObject(
v8::Local<v8::Context> context, WrapMode mode,
std::unique_ptr<RemoteObject>* 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<v8::Context> context, const String16& name,
std::unique_ptr<PropertyPreview>* result) const override {
bool unserializable = false;
*result = PropertyPreview::create()
.setName(name)
.setType(RemoteObject::TypeEnum::Number)
.setValue(description(&unserializable))
.build();
}
void buildEntryPreview(
v8::Local<v8::Context> context, int* nameLimit, int* indexLimit,
std::unique_ptr<ObjectPreview>* preview) const override {
bool unserializable = false;
*preview = ObjectPreview::create()
.setType(RemoteObject::TypeEnum::Number)
.setDescription(description(&unserializable))
.setOverflow(false)
.setProperties(protocol::Array<PropertyPreview>::create())
.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<v8::Number> m_value;
};
class BigIntMirror final : public ValueMirror {
public:
explicit BigIntMirror(v8::Local<v8::BigInt> value) : m_value(value) {}
Response buildRemoteObject(
v8::Local<v8::Context> context, WrapMode mode,
std::unique_ptr<RemoteObject>* 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<v8::Context> context,
const String16& name,
std::unique_ptr<protocol::Runtime::PropertyPreview>*
preview) const override {
*preview = PropertyPreview::create()
.setName(name)
.setType(RemoteObject::TypeEnum::Bigint)
.setValue(abbreviateString(
descriptionForBigInt(context, m_value), kMiddle))
.build();
}
void buildEntryPreview(v8::Local<v8::Context> context, int* nameLimit,
int* indexLimit,
std::unique_ptr<protocol::Runtime::ObjectPreview>*
preview) const override {
*preview = ObjectPreview::create()
.setType(RemoteObject::TypeEnum::Bigint)
.setDescription(descriptionForBigInt(context, m_value))
.setOverflow(false)
.setProperties(protocol::Array<PropertyPreview>::create())
.build();
}
v8::Local<v8::Value> v8Value() const override { return m_value; }
private:
v8::Local<v8::BigInt> m_value;
};
class SymbolMirror final : public ValueMirror {
public:
explicit SymbolMirror(v8::Local<v8::Value> value)
: m_symbol(value.As<v8::Symbol>()) {}
Response buildRemoteObject(
v8::Local<v8::Context> context, WrapMode mode,
std::unique_ptr<RemoteObject>* 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<v8::Context> context,
const String16& name,
std::unique_ptr<protocol::Runtime::PropertyPreview>*
preview) const override {
*preview = PropertyPreview::create()
.setName(name)
.setType(RemoteObject::TypeEnum::Symbol)
.setValue(abbreviateString(
descriptionForSymbol(context, m_symbol), kEnd))
.build();
}
v8::Local<v8::Value> v8Value() const override { return m_symbol; }
private:
v8::Local<v8::Symbol> m_symbol;
};
class LocationMirror final : public ValueMirror {
public:
static std::unique_ptr<LocationMirror> create(
v8::Local<v8::Function> function) {
return create(function, function->ScriptId(),
function->GetScriptLineNumber(),
function->GetScriptColumnNumber());
}
static std::unique_ptr<LocationMirror> createForGenerator(
v8::Local<v8::Value> value) {
v8::Local<v8::debug::GeneratorObject> generatorObject =
v8::debug::GeneratorObject::Cast(value);
if (!generatorObject->IsSuspended()) {
return create(generatorObject->Function());
}
v8::Local<v8::debug::Script> 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<v8::Context> context, WrapMode mode,
std::unique_ptr<RemoteObject>* 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<v8::Value> v8Value() const override { return m_value; }
private:
static std::unique_ptr<LocationMirror> create(v8::Local<v8::Value> 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<LocationMirror>(
new LocationMirror(value, scriptId, lineNumber, columnNumber));
}
LocationMirror(v8::Local<v8::Value> value, int scriptId, int lineNumber,
int columnNumber)
: m_value(value),
m_scriptId(scriptId),
m_lineNumber(lineNumber),
m_columnNumber(columnNumber) {}
v8::Local<v8::Value> m_value;
int m_scriptId;
int m_lineNumber;
int m_columnNumber;
};
class FunctionMirror final : public ValueMirror {
public:
explicit FunctionMirror(v8::Local<v8::Value> value)
: m_value(value.As<v8::Function>()) {}
v8::Local<v8::Value> v8Value() const override { return m_value; }
Response buildRemoteObject(
v8::Local<v8::Context> context, WrapMode mode,
std::unique_ptr<RemoteObject>* result) const override {
// TODO(alph): drop this functionality.
if (mode == WrapMode::kForceValue) {
std::unique_ptr<protocol::Value> 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<v8::Context> context, const String16& name,
std::unique_ptr<PropertyPreview>* result) const override {
*result = PropertyPreview::create()
.setName(name)
.setType(RemoteObject::TypeEnum::Function)
.setValue(String16())
.build();
}
void buildEntryPreview(
v8::Local<v8::Context> context, int* nameLimit, int* indexLimit,
std::unique_ptr<ObjectPreview>* preview) const override {
*preview = ObjectPreview::create()
.setType(RemoteObject::TypeEnum::Function)
.setDescription(descriptionForFunction(context, m_value))
.setOverflow(false)
.setProperties(protocol::Array<PropertyPreview>::create())
.build();
}
private:
v8::Local<v8::Function> m_value;
};
bool isArrayLike(v8::Local<v8::Context> context, v8::Local<v8::Value> 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<v8::Object> object = value.As<v8::Object>();
v8::Local<v8::Value> spliceValue;
if (!object->IsArgumentsObject() &&
(!object->GetRealNamedProperty(context, toV8String(isolate, "splice"))
.ToLocal(&spliceValue) ||
!spliceValue->IsFunction())) {
return false;
}
v8::Local<v8::Value> lengthValue;
v8::Maybe<bool> 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<v8::Uint32>::Cast(lengthValue)->Value();
return true;
}
struct EntryMirror {
std::unique_ptr<ValueMirror> key;
std::unique_ptr<ValueMirror> value;
static bool getEntries(v8::Local<v8::Context> context,
v8::Local<v8::Object> object, size_t limit,
bool* overflow, std::vector<EntryMirror>* mirrors) {
bool isKeyValue = false;
v8::Local<v8::Array> entries;
if (!object->PreviewEntries(&isKeyValue).ToLocal(&entries)) return false;
for (uint32_t i = 0; i < entries->Length(); i += isKeyValue ? 2 : 1) {
v8::Local<v8::Value> tmp;
std::unique_ptr<ValueMirror> keyMirror;
if (isKeyValue && entries->Get(context, i).ToLocal(&tmp)) {
keyMirror = ValueMirror::create(context, tmp);
}
std::unique_ptr<ValueMirror> 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<String16>& blacklist,
int skipIndex, int* nameLimit, int* indexLimit,
bool* overflow,
std::vector<PropertyMirror>* 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<String16> m_blacklist;
int m_skipIndex;
int* m_nameLimit;
int* m_indexLimit;
bool* m_overflow;
std::vector<PropertyMirror>* m_mirrors;
};
bool getPropertiesForPreview(v8::Local<v8::Context> context,
v8::Local<v8::Object> object, int* nameLimit,
int* indexLimit, bool* overflow,
std::vector<PropertyMirror>* properties) {
std::vector<String16> 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<v8::StringObject>()->ValueOf()->Length() + 1
: -1;
PreviewPropertyAccumulator accumulator(blacklist, skipIndex, nameLimit,
indexLimit, overflow, properties);
return ValueMirror::getProperties(context, object, false, false,
&accumulator);
}
void getInternalPropertiesForPreview(
v8::Local<v8::Context> context, v8::Local<v8::Object> object,
int* nameLimit, bool* overflow,
std::vector<InternalPropertyMirror>* properties) {
std::vector<InternalPropertyMirror> mirrors;
ValueMirror::getInternalProperties(context, object, &mirrors);
std::vector<String16> 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<v8::Context> context, v8::Local<v8::Object> object,
int* nameLimit, bool* overflow,
protocol::Array<PropertyPreview>* privateProperties) {
std::vector<PrivatePropertyMirror> mirrors =
ValueMirror::getPrivateProperties(context, object);
std::vector<String16> whitelist;
for (auto& mirror : mirrors) {
std::unique_ptr<PropertyPreview> propertyPreview;
mirror.value->buildPropertyPreview(context, mirror.name, &propertyPreview);
if (!propertyPreview) continue;
if (!*nameLimit) {
*overflow = true;
return;
}
--*nameLimit;
privateProperties->addItem(std::move(propertyPreview));
}
}
class ObjectMirror final : public ValueMirror {
public:
ObjectMirror(v8::Local<v8::Value> value, const String16& description)
: m_value(value.As<v8::Object>()),
m_description(description),
m_hasSubtype(false) {}
ObjectMirror(v8::Local<v8::Value> value, const String16& subtype,
const String16& description)
: m_value(value.As<v8::Object>()),
m_description(description),
m_hasSubtype(true),
m_subtype(subtype) {}
v8::Local<v8::Value> v8Value() const override { return m_value; }
Response buildRemoteObject(
v8::Local<v8::Context> context, WrapMode mode,
std::unique_ptr<RemoteObject>* result) const override {
if (mode == WrapMode::kForceValue) {
std::unique_ptr<protocol::Value> 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<ObjectPreview> 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<v8::Context> context, bool generatePreviewForTable,
int* nameLimit, int* indexLimit,
std::unique_ptr<ObjectPreview>* result) const override {
buildObjectPreviewInternal(context, false /* forEntry */,
generatePreviewForTable, nameLimit, indexLimit,
result);
}
void buildEntryPreview(
v8::Local<v8::Context> context, int* nameLimit, int* indexLimit,
std::unique_ptr<ObjectPreview>* result) const override {
buildObjectPreviewInternal(context, true /* forEntry */,
false /* generatePreviewForTable */, nameLimit,
indexLimit, result);
}
void buildPropertyPreview(
v8::Local<v8::Context> context, const String16& name,
std::unique_ptr<PropertyPreview>* 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<v8::Context> context, bool forEntry,
bool generatePreviewForTable, int* nameLimit, int* indexLimit,
std::unique_ptr<ObjectPreview>* result) const {
std::unique_ptr<protocol::Array<PropertyPreview>> properties =
protocol::Array<PropertyPreview>::create();
std::unique_ptr<protocol::Array<EntryPreview>> entriesPreview;
bool overflow = false;
v8::Local<v8::Value> value = m_value;
while (value->IsProxy()) value = value.As<v8::Proxy>()->GetTarget();
if (value->IsObject() && !value->IsProxy()) {
v8::Local<v8::Object> objectForPreview = value.As<v8::Object>();
std::vector<InternalPropertyMirror> internalProperties;
getInternalPropertiesForPreview(context, objectForPreview, nameLimit,
&overflow, &internalProperties);
for (size_t i = 0; i < internalProperties.size(); ++i) {
std::unique_ptr<PropertyPreview> propertyPreview;
internalProperties[i].value->buildPropertyPreview(
context, internalProperties[i].name, &propertyPreview);
if (propertyPreview) {
properties->addItem(std::move(propertyPreview));
}
}
getPrivatePropertiesForPreview(context, objectForPreview, nameLimit,
&overflow, properties.get());
std::vector<PropertyMirror> mirrors;
if (getPropertiesForPreview(context, objectForPreview, nameLimit,
indexLimit, &overflow, &mirrors)) {
for (size_t i = 0; i < mirrors.size(); ++i) {
std::unique_ptr<PropertyPreview> preview;
std::unique_ptr<ObjectPreview> 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->addItem(std::move(preview));
}
}
std::vector<EntryMirror> entries;
if (EntryMirror::getEntries(context, objectForPreview, 5, &overflow,
&entries)) {
if (forEntry) {
overflow = true;
} else {
entriesPreview = protocol::Array<EntryPreview>::create();
for (const auto& entry : entries) {
std::unique_ptr<ObjectPreview> valuePreview;
entry.value->buildEntryPreview(context, nameLimit, indexLimit,
&valuePreview);
if (!valuePreview) continue;
std::unique_ptr<ObjectPreview> keyPreview;
if (entry.key) {
entry.key->buildEntryPreview(context, nameLimit, indexLimit,
&keyPreview);
if (!keyPreview) continue;
}
std::unique_ptr<EntryPreview> entryPreview =
EntryPreview::create()
.setValue(std::move(valuePreview))
.build();
if (keyPreview) entryPreview->setKey(std::move(keyPreview));
entriesPreview->addItem(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<v8::Object> m_value;
String16 m_description;
bool m_hasSubtype;
String16 m_subtype;
};
void nativeGetterCallback(const v8::FunctionCallbackInfo<v8::Value>& info) {
v8::Local<v8::Object> data = info.Data().As<v8::Object>();
v8::Isolate* isolate = info.GetIsolate();
v8::Local<v8::Context> context = isolate->GetCurrentContext();
v8::Local<v8::Value> name;
if (!data->GetRealNamedProperty(context, toV8String(isolate, "name"))
.ToLocal(&name)) {
return;
}
v8::Local<v8::Value> object;
if (!data->GetRealNamedProperty(context, toV8String(isolate, "object"))
.ToLocal(&object) ||
!object->IsObject()) {
return;
}
v8::Local<v8::Value> value;
if (!object.As<v8::Object>()->Get(context, name).ToLocal(&value)) return;
info.GetReturnValue().Set(value);
}
std::unique_ptr<ValueMirror> createNativeGetter(v8::Local<v8::Context> context,
v8::Local<v8::Value> object,
v8::Local<v8::Name> name) {
v8::Isolate* isolate = context->GetIsolate();
v8::TryCatch tryCatch(isolate);
v8::Local<v8::Object> 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<v8::Function> 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<v8::Value>& info) {
if (info.Length() < 1) return;
v8::Local<v8::Object> data = info.Data().As<v8::Object>();
v8::Isolate* isolate = info.GetIsolate();
v8::Local<v8::Context> context = isolate->GetCurrentContext();
v8::Local<v8::Value> name;
if (!data->GetRealNamedProperty(context, toV8String(isolate, "name"))
.ToLocal(&name)) {
return;
}
v8::Local<v8::Value> object;
if (!data->GetRealNamedProperty(context, toV8String(isolate, "object"))
.ToLocal(&object) ||
!object->IsObject()) {
return;
}
v8::Local<v8::Value> value;
if (!object.As<v8::Object>()->Set(context, name, info[0]).IsNothing()) return;
}
std::unique_ptr<ValueMirror> createNativeSetter(v8::Local<v8::Context> context,
v8::Local<v8::Value> object,
v8::Local<v8::Name> name) {
v8::Isolate* isolate = context->GetIsolate();
v8::TryCatch tryCatch(isolate);
v8::Local<v8::Object> 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<v8::Function> 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<v8::Context> context,
v8::Local<v8::Object> object,
v8::Local<v8::Name> 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<v8::String>()->StringEquals(toV8String(isolate, "body"))) {
return false;
}
v8::TryCatch tryCatch(isolate);
v8::Local<v8::Value> request;
if (context->Global()
->GetRealNamedProperty(context, toV8String(isolate, "Request"))
.ToLocal(&request)) {
if (request->IsObject() &&
object->InstanceOf(context, request.As<v8::Object>())
.FromMaybe(false)) {
return true;
}
}
if (tryCatch.HasCaught()) tryCatch.Reset();
v8::Local<v8::Value> response;
if (context->Global()
->GetRealNamedProperty(context, toV8String(isolate, "Response"))
.ToLocal(&response)) {
if (response->IsObject() &&
object->InstanceOf(context, response.As<v8::Object>())
.FromMaybe(false)) {
return true;
}
}
return false;
}
template <typename ArrayView, typename ArrayBuffer>
void addTypedArrayView(v8::Local<v8::Context> context,
v8::Local<ArrayBuffer> 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 <typename ArrayBuffer>
void addTypedArrayViews(v8::Local<v8::Context> context,
v8::Local<ArrayBuffer> buffer,
ValueMirror::PropertyAccumulator* accumulator) {
// TODO(alph): these should be internal properties.
size_t length = buffer->ByteLength();
addTypedArrayView<v8::Int8Array>(context, buffer, length, "[[Int8Array]]",
accumulator);
addTypedArrayView<v8::Uint8Array>(context, buffer, length, "[[Uint8Array]]",
accumulator);
if (buffer->ByteLength() % 2 == 0) {
addTypedArrayView<v8::Int16Array>(context, buffer, length / 2,
"[[Int16Array]]", accumulator);
}
if (buffer->ByteLength() % 4 == 0) {
addTypedArrayView<v8::Int32Array>(context, buffer, length / 4,
"[[Int32Array]]", accumulator);
}
}
} // anonymous namespace
ValueMirror::~ValueMirror() = default;
// static
bool ValueMirror::getProperties(v8::Local<v8::Context> context,
v8::Local<v8::Object> object,
bool ownProperties, bool accessorPropertiesOnly,
PropertyAccumulator* accumulator) {
v8::Isolate* isolate = context->GetIsolate();
v8::TryCatch tryCatch(isolate);
v8::Local<v8::Set> set = v8::Set::New(isolate);
v8::MicrotasksScope microtasksScope(isolate,
v8::MicrotasksScope::kDoNotRunMicrotasks);
V8InternalValueType internalType = v8InternalValueTypeFrom(context, object);
if (internalType == V8InternalValueType::kScope) {
v8::Local<v8::Value> value;
if (!object->Get(context, toV8String(isolate, "object")).ToLocal(&value) ||
!value->IsObject()) {
return false;
} else {
object = value.As<v8::Object>();
}
}
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<v8::ArrayBuffer>(), accumulator);
}
if (object->IsSharedArrayBuffer()) {
addTypedArrayViews(context, object.As<v8::SharedArrayBuffer>(),
accumulator);
}
for (auto iterator = v8::debug::PropertyIterator::Create(object);
!iterator->Done(); iterator->Advance()) {
bool isOwn = iterator->is_own();
if (!isOwn && ownProperties) break;
v8::Local<v8::Name> v8Name = iterator->name();
v8::Maybe<bool> 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<ValueMirror> symbolMirror;
if (v8Name->IsString()) {
name = toProtocolString(isolate, v8Name.As<v8::String>());
} else {
v8::Local<v8::Symbol> symbol = v8Name.As<v8::Symbol>();
name = descriptionForSymbol(context, symbol);
symbolMirror = ValueMirror::create(context, symbol);
}
v8::PropertyAttribute attributes;
std::unique_ptr<ValueMirror> valueMirror;
std::unique_ptr<ValueMirror> getterMirror;
std::unique_ptr<ValueMirror> setterMirror;
std::unique_ptr<ValueMirror> 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<v8::Value> get = descriptor.get;
getterMirror = ValueMirror::create(context, get);
getterIsNativeFunction =
get->IsFunction() && get.As<v8::Function>()->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<v8::Value> 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<v8::Value> 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<v8::Context> context, v8::Local<v8::Object> object,
std::vector<InternalPropertyMirror>* mirrors) {
v8::Isolate* isolate = context->GetIsolate();
v8::MicrotasksScope microtasksScope(isolate,
v8::MicrotasksScope::kDoNotRunMicrotasks);
v8::TryCatch tryCatch(isolate);
if (object->IsFunction()) {
v8::Local<v8::Function> function = object.As<v8::Function>();
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<V8InspectorImpl*>(v8::debug::GetInspector(isolate))
->debugger();
v8::Local<v8::Array> properties;
if (debugger->internalProperties(context, object).ToLocal(&properties)) {
for (uint32_t i = 0; i < properties->Length(); i += 2) {
v8::Local<v8::Value> name;
if (!properties->Get(context, i).ToLocal(&name) || !name->IsString()) {
tryCatch.Reset();
continue;
}
v8::Local<v8::Value> 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<PrivatePropertyMirror> ValueMirror::getPrivateProperties(
v8::Local<v8::Context> context, v8::Local<v8::Object> object) {
std::vector<PrivatePropertyMirror> mirrors;
v8::Isolate* isolate = context->GetIsolate();
v8::MicrotasksScope microtasksScope(isolate,
v8::MicrotasksScope::kDoNotRunMicrotasks);
v8::TryCatch tryCatch(isolate);
v8::Local<v8::Array> privateProperties;
if (!v8::debug::GetPrivateFields(context, object).ToLocal(&privateProperties))
return mirrors;
for (uint32_t i = 0; i < privateProperties->Length(); i += 2) {
v8::Local<v8::Value> 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<v8::Private> private_field = v8::Local<v8::Private>::Cast(name);
v8::Local<v8::Value> private_name = private_field->Name();
DCHECK(!private_name->IsUndefined());
v8::Local<v8::Value> 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<v8::Context> context,
v8::Local<v8::Value> value) {
if (!value->IsObject()) return String16();
v8::Local<v8::Object> object = value.As<v8::Object>();
v8::Isolate* isolate = context->GetIsolate();
v8::TryCatch tryCatch(isolate);
v8::Local<v8::Value> nodeName;
if (!object->Get(context, toV8String(isolate, "nodeName"))
.ToLocal(&nodeName)) {
return String16();
}
String16 description;
v8::Local<v8::Function> 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<v8::String>());
}
}
if (!description.length()) {
v8::Local<v8::Value> value;
if (!object->Get(context, toV8String(isolate, "constructor"))
.ToLocal(&value) ||
!value->IsObject()) {
return String16();
}
if (!value.As<v8::Object>()
->Get(context, toV8String(isolate, "name"))
.ToLocal(&value) ||
!value->IsString()) {
return String16();
}
description = toProtocolString(isolate, value.As<v8::String>());
}
v8::Local<v8::Value> nodeType;
if (!object->Get(context, toV8String(isolate, "nodeType"))
.ToLocal(&nodeType) ||
!nodeType->IsInt32()) {
return description;
}
if (nodeType.As<v8::Int32>()->Value() == 1) {
v8::Local<v8::Value> idValue;
if (!object->Get(context, toV8String(isolate, "id")).ToLocal(&idValue)) {
return description;
}
if (idValue->IsString()) {
String16 id = toProtocolString(isolate, idValue.As<v8::String>());
if (id.length()) {
description = String16::concat(description, '#', id);
}
}
v8::Local<v8::Value> classNameValue;
if (!object->Get(context, toV8String(isolate, "className"))
.ToLocal(&classNameValue)) {
return description;
}
if (classNameValue->IsString() &&
classNameValue.As<v8::String>()->Length()) {
String16 classes =
toProtocolString(isolate, classNameValue.As<v8::String>());
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<v8::Int32>()->Value() == 1) {
return String16::concat("<!DOCTYPE ", description, '>');
}
return description;
}
std::unique_ptr<ValueMirror> clientMirror(v8::Local<v8::Context> context,
v8::Local<v8::Value> value,
const String16& subtype) {
// TODO(alph): description and length retrieval should move to embedder.
if (subtype == "node") {
return v8::base::make_unique<ObjectMirror>(
value, subtype, descriptionForNode(context, value));
}
if (subtype == "error") {
return v8::base::make_unique<ObjectMirror>(
value, RemoteObject::SubtypeEnum::Error,
descriptionForError(context, value.As<v8::Object>(),
ErrorType::kClient));
}
if (subtype == "array" && value->IsObject()) {
v8::Isolate* isolate = context->GetIsolate();
v8::TryCatch tryCatch(isolate);
v8::Local<v8::Object> object = value.As<v8::Object>();
v8::Local<v8::Value> lengthValue;
if (object->Get(context, toV8String(isolate, "length"))
.ToLocal(&lengthValue)) {
if (lengthValue->IsInt32()) {
return v8::base::make_unique<ObjectMirror>(
value, RemoteObject::SubtypeEnum::Array,
descriptionForCollection(isolate, object,
lengthValue.As<v8::Int32>()->Value()));
}
}
}
return v8::base::make_unique<ObjectMirror>(
value,
descriptionForObject(context->GetIsolate(), value.As<v8::Object>()));
}
std::unique_ptr<ValueMirror> ValueMirror::create(v8::Local<v8::Context> context,
v8::Local<v8::Value> value) {
if (value->IsNull()) {
return v8::base::make_unique<PrimitiveValueMirror>(
value, RemoteObject::TypeEnum::Object);
}
if (value->IsBoolean()) {
return v8::base::make_unique<PrimitiveValueMirror>(
value, RemoteObject::TypeEnum::Boolean);
}
if (value->IsNumber()) {
return v8::base::make_unique<NumberMirror>(value.As<v8::Number>());
}
v8::Isolate* isolate = context->GetIsolate();
if (value->IsString()) {
return v8::base::make_unique<PrimitiveValueMirror>(
value, RemoteObject::TypeEnum::String);
}
if (value->IsBigInt()) {
return v8::base::make_unique<BigIntMirror>(value.As<v8::BigInt>());
}
if (value->IsSymbol()) {
return v8::base::make_unique<SymbolMirror>(value.As<v8::Symbol>());
}
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<PrimitiveValueMirror>(
value, RemoteObject::TypeEnum::Undefined);
}
if (value->IsRegExp()) {
return v8::base::make_unique<ObjectMirror>(
value, RemoteObject::SubtypeEnum::Regexp,
descriptionForRegExp(isolate, value.As<v8::RegExp>()));
}
if (value->IsFunction()) {
return v8::base::make_unique<FunctionMirror>(value);
}
if (value->IsProxy()) {
return v8::base::make_unique<ObjectMirror>(
value, RemoteObject::SubtypeEnum::Proxy, "Proxy");
}
if (value->IsDate()) {
return v8::base::make_unique<ObjectMirror>(
value, RemoteObject::SubtypeEnum::Date,
descriptionForDate(context, value.As<v8::Date>()));
}
if (value->IsPromise()) {
v8::Local<v8::Promise> promise = value.As<v8::Promise>();
return v8::base::make_unique<ObjectMirror>(
promise, RemoteObject::SubtypeEnum::Promise,
descriptionForObject(isolate, promise));
}
if (value->IsNativeError()) {
return v8::base::make_unique<ObjectMirror>(
value, RemoteObject::SubtypeEnum::Error,
descriptionForError(context, value.As<v8::Object>(),
ErrorType::kNative));
}
if (value->IsMap()) {
v8::Local<v8::Map> map = value.As<v8::Map>();
return v8::base::make_unique<ObjectMirror>(
value, RemoteObject::SubtypeEnum::Map,
descriptionForCollection(isolate, map, map->Size()));
}
if (value->IsSet()) {
v8::Local<v8::Set> set = value.As<v8::Set>();
return v8::base::make_unique<ObjectMirror>(
value, RemoteObject::SubtypeEnum::Set,
descriptionForCollection(isolate, set, set->Size()));
}
if (value->IsWeakMap()) {
return v8::base::make_unique<ObjectMirror>(
value, RemoteObject::SubtypeEnum::Weakmap,
descriptionForObject(isolate, value.As<v8::Object>()));
}
if (value->IsWeakSet()) {
return v8::base::make_unique<ObjectMirror>(
value, RemoteObject::SubtypeEnum::Weakset,
descriptionForObject(isolate, value.As<v8::Object>()));
}
if (value->IsMapIterator() || value->IsSetIterator()) {
return v8::base::make_unique<ObjectMirror>(
value, RemoteObject::SubtypeEnum::Iterator,
descriptionForObject(isolate, value.As<v8::Object>()));
}
if (value->IsGeneratorObject()) {
v8::Local<v8::Object> object = value.As<v8::Object>();
return v8::base::make_unique<ObjectMirror>(
object, RemoteObject::SubtypeEnum::Generator,
descriptionForObject(isolate, object));
}
if (value->IsTypedArray()) {
v8::Local<v8::TypedArray> array = value.As<v8::TypedArray>();
return v8::base::make_unique<ObjectMirror>(
value, RemoteObject::SubtypeEnum::Typedarray,
descriptionForCollection(isolate, array, array->Length()));
}
if (value->IsArrayBuffer()) {
v8::Local<v8::ArrayBuffer> buffer = value.As<v8::ArrayBuffer>();
return v8::base::make_unique<ObjectMirror>(
value, RemoteObject::SubtypeEnum::Arraybuffer,
descriptionForCollection(isolate, buffer, buffer->ByteLength()));
}
if (value->IsSharedArrayBuffer()) {
v8::Local<v8::SharedArrayBuffer> buffer = value.As<v8::SharedArrayBuffer>();
return v8::base::make_unique<ObjectMirror>(
value, RemoteObject::SubtypeEnum::Arraybuffer,
descriptionForCollection(isolate, buffer, buffer->ByteLength()));
}
if (value->IsDataView()) {
v8::Local<v8::DataView> view = value.As<v8::DataView>();
return v8::base::make_unique<ObjectMirror>(
value, RemoteObject::SubtypeEnum::Dataview,
descriptionForCollection(isolate, view, view->ByteLength()));
}
V8InternalValueType internalType =
v8InternalValueTypeFrom(context, v8::Local<v8::Object>::Cast(value));
if (value->IsArray() && internalType == V8InternalValueType::kScopeList) {
return v8::base::make_unique<ObjectMirror>(
value, "internal#scopeList",
descriptionForScopeList(value.As<v8::Array>()));
}
if (value->IsObject() && internalType == V8InternalValueType::kEntry) {
return v8::base::make_unique<ObjectMirror>(
value, "internal#entry",
descriptionForEntry(context, value.As<v8::Object>()));
}
if (value->IsObject() && internalType == V8InternalValueType::kScope) {
return v8::base::make_unique<ObjectMirror>(
value, "internal#scope",
descriptionForScope(context, value.As<v8::Object>()));
}
size_t length = 0;
if (value->IsArray() || isArrayLike(context, value, &length)) {
length = value->IsArray() ? value.As<v8::Array>()->Length() : length;
return v8::base::make_unique<ObjectMirror>(
value, RemoteObject::SubtypeEnum::Array,
descriptionForCollection(isolate, value.As<v8::Object>(), length));
}
if (value->IsObject()) {
return v8::base::make_unique<ObjectMirror>(
value, descriptionForObject(isolate, value.As<v8::Object>()));
}
return nullptr;
}
} // namespace v8_inspector