|  | // 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/base/optional.h" | 
|  | #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>()); | 
|  | } | 
|  |  | 
|  | template <typename ResultType> | 
|  | ResultType unpackWasmValue(v8::Local<v8::Context> context, | 
|  | v8::Local<v8::Array> array) { | 
|  | ResultType result; | 
|  | constexpr int kSize = sizeof(result); | 
|  | uint8_t buffer[kSize]; | 
|  | for (int i = 0; i < kSize; i++) { | 
|  | v8::Local<v8::Int32> i32 = | 
|  | array->Get(context, i).ToLocalChecked().As<v8::Int32>(); | 
|  | buffer[i] = static_cast<uint8_t>(i32->Value()); | 
|  | } | 
|  | memcpy(&result, buffer, kSize); | 
|  | return result; | 
|  | } | 
|  |  | 
|  | String16 descriptionForWasmS128(std::array<uint8_t, 16> arr) { | 
|  | String16Builder builder; | 
|  | for (int i = 0; i < 16; i++) { | 
|  | builder.appendUnsignedAsHex(arr.at(i)); | 
|  | builder.append(" "); | 
|  | } | 
|  | return builder.toString(); | 
|  | } | 
|  |  | 
|  | // Partial list of Wasm's ValueType, copied here to avoid including internal | 
|  | // header. Using an unscoped enumeration here to allow implicit conversions from | 
|  | // int. Keep in sync with ValueType::Kind in wasm/value-type.h. | 
|  | enum WasmValueType { kStmt, kI32, kI64, kF32, kF64, kS128, kExternRef }; | 
|  |  | 
|  | Response toProtocolValue(v8::Local<v8::Context> context, | 
|  | v8::Local<v8::Value> value, int maxDepth, | 
|  | std::unique_ptr<protocol::Value>* result) { | 
|  | if (!maxDepth) | 
|  | return Response::ServerError("Object reference chain is too long"); | 
|  | maxDepth--; | 
|  |  | 
|  | if (value->IsNull() || value->IsUndefined()) { | 
|  | *result = protocol::Value::null(); | 
|  | return Response::Success(); | 
|  | } | 
|  | if (value->IsBoolean()) { | 
|  | *result = | 
|  | protocol::FundamentalValue::create(value.As<v8::Boolean>()->Value()); | 
|  | return Response::Success(); | 
|  | } | 
|  | 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::Success(); | 
|  | } | 
|  | } | 
|  | *result = protocol::FundamentalValue::create(doubleValue); | 
|  | return Response::Success(); | 
|  | } | 
|  | if (value->IsString()) { | 
|  | *result = protocol::StringValue::create( | 
|  | toProtocolString(context->GetIsolate(), value.As<v8::String>())); | 
|  | return Response::Success(); | 
|  | } | 
|  | 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::Success(); | 
|  | } | 
|  | 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::Success(); | 
|  | } | 
|  |  | 
|  | if (v8::debug::WasmValue::IsWasmValue(value)) { | 
|  | auto wasmValue = value.As<v8::debug::WasmValue>(); | 
|  |  | 
|  | // Convert serializable Wasm values (i32, f32, f64) into protocol values. | 
|  | // Not all i64 values are representable by double, so always represent it as | 
|  | // a String here. | 
|  | switch (wasmValue->value_type()) { | 
|  | case kI32: { | 
|  | *result = protocol::FundamentalValue::create( | 
|  | unpackWasmValue<int32_t>(context, wasmValue->bytes())); | 
|  | break; | 
|  | } | 
|  | case kI64: { | 
|  | *result = protocol::StringValue::create(String16::fromInteger64( | 
|  | unpackWasmValue<int64_t>(context, wasmValue->bytes()))); | 
|  | break; | 
|  | } | 
|  | case kF32: { | 
|  | *result = protocol::FundamentalValue::create( | 
|  | unpackWasmValue<float>(context, wasmValue->bytes())); | 
|  | break; | 
|  | } | 
|  | case kF64: { | 
|  | *result = protocol::FundamentalValue::create( | 
|  | unpackWasmValue<double>(context, wasmValue->bytes())); | 
|  | break; | 
|  | } | 
|  | case kS128: { | 
|  | auto bytes = wasmValue->bytes(); | 
|  | DCHECK_EQ(16, bytes->Length()); | 
|  | auto s128 = unpackWasmValue<std::array<uint8_t, 16>>(context, bytes); | 
|  | String16 desc = descriptionForWasmS128(s128); | 
|  | *result = protocol::StringValue::create(desc); | 
|  | break; | 
|  | } | 
|  | case kExternRef: { | 
|  | std::unique_ptr<protocol::Value> externrefValue; | 
|  | Response response = toProtocolValue(context, wasmValue->ref(), maxDepth, | 
|  | &externrefValue); | 
|  | if (!response.IsSuccess()) return response; | 
|  | *result = std::move(externrefValue); | 
|  | break; | 
|  | } | 
|  | default: { | 
|  | UNIMPLEMENTED(); | 
|  | } | 
|  | } | 
|  | return Response::Success(); | 
|  | } | 
|  |  | 
|  | return Response::ServerError("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::Success(); | 
|  | 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->Description()), | 
|  | ")"); | 
|  | } | 
|  |  | 
|  | 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::kLinear) description.append('l'); | 
|  | 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 }; | 
|  |  | 
|  | // Build a description from an exception using the following rules: | 
|  | //   * Usually return the stack trace found in the {stack} property. | 
|  | //   * If the stack trace does not start with the class name of the passed | 
|  | //     exception, try to build a description from the class name, the | 
|  | //     {message} property and the rest of the stack trace. | 
|  | //     (The stack trace is only used if {message} was also found in | 
|  | //     said stack trace). | 
|  | 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::base::Optional<String16> stack; | 
|  | { | 
|  | v8::Local<v8::Value> stackValue; | 
|  | if (object->Get(context, toV8String(isolate, "stack")) | 
|  | .ToLocal(&stackValue) && | 
|  | stackValue->IsString()) { | 
|  | stack = toProtocolString(isolate, stackValue.As<v8::String>()); | 
|  | } | 
|  | } | 
|  |  | 
|  | if (type == ErrorType::kNative && stack) return *stack; | 
|  |  | 
|  | if (stack && stack->substring(0, className.length()) == className) { | 
|  | return *stack; | 
|  | } | 
|  |  | 
|  | v8::base::Optional<String16> message; | 
|  | { | 
|  | v8::Local<v8::Value> messageValue; | 
|  | if (object->Get(context, toV8String(isolate, "message")) | 
|  | .ToLocal(&messageValue) && | 
|  | messageValue->IsString()) { | 
|  | String16 msg = toProtocolStringWithTypeCheck(isolate, messageValue); | 
|  | if (!msg.isEmpty()) message = msg; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (!message) return stack ? *stack : className; | 
|  |  | 
|  | String16 description = className + ": " + *message; | 
|  | if (!stack) return description; | 
|  |  | 
|  | DCHECK(stack && message); | 
|  | size_t index = stack->find(*message); | 
|  | String16 stackWithoutMessage = | 
|  | index != String16::kNotFound ? stack->substring(index + message->length()) | 
|  | : String16(); | 
|  | return description + stackWithoutMessage; | 
|  | } | 
|  |  | 
|  | 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::Success(); | 
|  | } | 
|  |  | 
|  | 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(std::make_unique<protocol::Array<PropertyPreview>>()) | 
|  | .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 WasmValueMirror final : public ValueMirror { | 
|  | public: | 
|  | explicit WasmValueMirror(v8::Local<v8::debug::WasmValue> 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 serializable; | 
|  | String16 descriptionValue = description(context, &serializable); | 
|  | *result = RemoteObject::create() | 
|  | .setType(RemoteObject::TypeEnum::Wasm) | 
|  | .setSubtype(subtype()) | 
|  | .setDescription(descriptionValue) | 
|  | .build(); | 
|  | if (serializable) { | 
|  | std::unique_ptr<protocol::Value> protocolValue; | 
|  | toProtocolValue(context, m_value, &protocolValue); | 
|  | (*result)->setValue(std::move(protocolValue)); | 
|  | } else { | 
|  | (*result)->setUnserializableValue(descriptionValue); | 
|  | } | 
|  | return Response::Success(); | 
|  | } | 
|  |  | 
|  | void buildPropertyPreview( | 
|  | v8::Local<v8::Context> context, const String16& name, | 
|  | std::unique_ptr<PropertyPreview>* result) const override { | 
|  | bool serializable; | 
|  | *result = PropertyPreview::create() | 
|  | .setName(name) | 
|  | .setType(RemoteObject::TypeEnum::Wasm) | 
|  | .setSubtype(subtype()) | 
|  | .setValue(description(context, &serializable)) | 
|  | .build(); | 
|  | } | 
|  |  | 
|  | void buildEntryPreview( | 
|  | v8::Local<v8::Context> context, int* nameLimit, int* indexLimit, | 
|  | std::unique_ptr<ObjectPreview>* preview) const override { | 
|  | bool serializable; | 
|  | *preview = | 
|  | ObjectPreview::create() | 
|  | .setType(RemoteObject::TypeEnum::Wasm) | 
|  | .setSubtype(subtype()) | 
|  | .setDescription(description(context, &serializable)) | 
|  | .setOverflow(false) | 
|  | .setProperties(std::make_unique<protocol::Array<PropertyPreview>>()) | 
|  | .build(); | 
|  | } | 
|  |  | 
|  | private: | 
|  | String16 subtype() const { | 
|  | switch (m_value->value_type()) { | 
|  | case kI32: | 
|  | return RemoteObject::SubtypeEnum::I32; | 
|  | case kI64: | 
|  | return RemoteObject::SubtypeEnum::I64; | 
|  | case kF32: | 
|  | return RemoteObject::SubtypeEnum::F32; | 
|  | case kF64: | 
|  | return RemoteObject::SubtypeEnum::F64; | 
|  | case kS128: | 
|  | return RemoteObject::SubtypeEnum::V128; | 
|  | case kExternRef: | 
|  | return RemoteObject::SubtypeEnum::Externref; | 
|  | default: | 
|  | UNREACHABLE(); | 
|  | } | 
|  | } | 
|  |  | 
|  | String16 description(v8::Local<v8::Context> context, | 
|  | bool* serializable) const { | 
|  | *serializable = true; | 
|  | switch (m_value->value_type()) { | 
|  | case kI32: { | 
|  | return String16::fromInteger( | 
|  | unpackWasmValue<int32_t>(context, m_value->bytes())); | 
|  | } | 
|  | case kI64: { | 
|  | *serializable = false; | 
|  | return String16::fromInteger64( | 
|  | unpackWasmValue<int64_t>(context, m_value->bytes())); | 
|  | } | 
|  | case kF32: { | 
|  | return String16::fromDouble( | 
|  | unpackWasmValue<float>(context, m_value->bytes())); | 
|  | } | 
|  | case kF64: { | 
|  | return String16::fromDouble( | 
|  | unpackWasmValue<double>(context, m_value->bytes())); | 
|  | } | 
|  | case kS128: { | 
|  | *serializable = false; | 
|  | auto bytes = m_value->bytes(); | 
|  | DCHECK_EQ(16, bytes->Length()); | 
|  | auto s128 = unpackWasmValue<std::array<uint8_t, 16>>(context, bytes); | 
|  | return descriptionForWasmS128(s128); | 
|  | } | 
|  | case kExternRef: { | 
|  | return descriptionForObject(context->GetIsolate(), | 
|  | m_value->ref().As<v8::Object>()); | 
|  | } | 
|  | default: { | 
|  | *serializable = false; | 
|  | return String16("Unknown"); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | v8::Local<v8::debug::WasmValue> m_value; | 
|  | }; | 
|  |  | 
|  | 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::Success(); | 
|  | } | 
|  | 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(std::make_unique<protocol::Array<PropertyPreview>>()) | 
|  | .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::Success(); | 
|  | } | 
|  |  | 
|  | 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(std::make_unique<protocol::Array<PropertyPreview>>()) | 
|  | .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::ServerError("Object couldn't be returned by value"); | 
|  | } | 
|  | *result = RemoteObject::create() | 
|  | .setType(RemoteObject::TypeEnum::Symbol) | 
|  | .setDescription(descriptionForSymbol(context, m_symbol)) | 
|  | .build(); | 
|  | return Response::Success(); | 
|  | } | 
|  |  | 
|  | 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::Success(); | 
|  | } | 
|  | 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::Success(); | 
|  | } | 
|  |  | 
|  | 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(std::make_unique<protocol::Array<PropertyPreview>>()) | 
|  | .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>& blocklist, | 
|  | int skipIndex, int* nameLimit, int* indexLimit, | 
|  | bool* overflow, | 
|  | std::vector<PropertyMirror>* mirrors) | 
|  | : m_blocklist(blocklist), | 
|  | 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_blocklist.begin(), m_blocklist.end(), mirror.name) != | 
|  | m_blocklist.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_blocklist; | 
|  | 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> blocklist; | 
|  | size_t length = 0; | 
|  | if (object->IsArray() || isArrayLike(context, object, &length) || | 
|  | object->IsStringObject()) { | 
|  | blocklist.push_back("length"); | 
|  | } else { | 
|  | auto clientSubtype = clientFor(context)->valueSubtype(object); | 
|  | if (clientSubtype && toString16(clientSubtype->string()) == "array") { | 
|  | blocklist.push_back("length"); | 
|  | } | 
|  | } | 
|  | if (object->IsArrayBuffer() || object->IsSharedArrayBuffer()) { | 
|  | blocklist.push_back("[[Int8Array]]"); | 
|  | blocklist.push_back("[[Uint8Array]]"); | 
|  | blocklist.push_back("[[Int16Array]]"); | 
|  | blocklist.push_back("[[Int32Array]]"); | 
|  | } | 
|  | int skipIndex = object->IsStringObject() | 
|  | ? object.As<v8::StringObject>()->ValueOf()->Length() + 1 | 
|  | : -1; | 
|  | PreviewPropertyAccumulator accumulator(blocklist, 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> allowlist; | 
|  | if (object->IsBooleanObject() || object->IsNumberObject() || | 
|  | object->IsStringObject() || object->IsSymbolObject() || | 
|  | object->IsBigIntObject()) { | 
|  | allowlist.emplace_back("[[PrimitiveValue]]"); | 
|  | } else if (object->IsPromise()) { | 
|  | allowlist.emplace_back("[[PromiseState]]"); | 
|  | allowlist.emplace_back("[[PromiseResult]]"); | 
|  | } else if (object->IsGeneratorObject()) { | 
|  | allowlist.emplace_back("[[GeneratorState]]"); | 
|  | } | 
|  | for (auto& mirror : mirrors) { | 
|  | if (std::find(allowlist.begin(), allowlist.end(), mirror.name) == | 
|  | allowlist.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); | 
|  | for (auto& mirror : mirrors) { | 
|  | std::unique_ptr<PropertyPreview> propertyPreview; | 
|  | if (mirror.value) { | 
|  | mirror.value->buildPropertyPreview(context, mirror.name, | 
|  | &propertyPreview); | 
|  | } else { | 
|  | propertyPreview = PropertyPreview::create() | 
|  | .setName(mirror.name) | 
|  | .setType(PropertyPreview::TypeEnum::Accessor) | 
|  | .build(); | 
|  | } | 
|  | if (!propertyPreview) continue; | 
|  | if (!*nameLimit) { | 
|  | *overflow = true; | 
|  | return; | 
|  | } | 
|  | --*nameLimit; | 
|  | privateProperties->emplace_back(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::Success(); | 
|  | } | 
|  |  | 
|  | 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 { | 
|  | auto properties = std::make_unique<protocol::Array<PropertyPreview>>(); | 
|  | 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->emplace_back(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->emplace_back(std::move(preview)); | 
|  | } | 
|  | } | 
|  |  | 
|  | std::vector<EntryMirror> entries; | 
|  | if (EntryMirror::getEntries(context, objectForPreview, 5, &overflow, | 
|  | &entries)) { | 
|  | if (forEntry) { | 
|  | overflow = true; | 
|  | } else { | 
|  | entriesPreview = std::make_unique<protocol::Array<EntryPreview>>(); | 
|  | 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->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<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. | 
|  | // 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<v8::Int8Array>(context, buffer, length, "[[Int8Array]]", | 
|  | accumulator); | 
|  | addTypedArrayView<v8::Uint8Array>(context, buffer, length, "[[Uint8Array]]", | 
|  | accumulator); | 
|  |  | 
|  | length = byte_length / 2; | 
|  | if (length > v8::TypedArray::kMaxLength || (byte_length % 2) != 0) return; | 
|  |  | 
|  | addTypedArrayView<v8::Int16Array>(context, buffer, length, "[[Int16Array]]", | 
|  | accumulator); | 
|  |  | 
|  | length = byte_length / 4; | 
|  | if (length > v8::TypedArray::kMaxLength || (byte_length % 4) != 0) return; | 
|  |  | 
|  | addTypedArrayView<v8::Int32Array>(context, buffer, length, "[[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; | 
|  |  | 
|  | std::vector<v8::Local<v8::Value>> names; | 
|  | std::vector<v8::Local<v8::Value>> values; | 
|  | if (!v8::debug::GetPrivateMembers(context, object, &names, &values)) | 
|  | return mirrors; | 
|  |  | 
|  | size_t len = values.size(); | 
|  | for (size_t i = 0; i < len; i++) { | 
|  | v8::Local<v8::Value> name = names[i]; | 
|  | DCHECK(name->IsString()); | 
|  | v8::Local<v8::Value> value = values[i]; | 
|  |  | 
|  | std::unique_ptr<ValueMirror> valueMirror; | 
|  | std::unique_ptr<ValueMirror> getterMirror; | 
|  | std::unique_ptr<ValueMirror> setterMirror; | 
|  | if (v8::debug::AccessorPair::IsAccessorPair(value)) { | 
|  | v8::Local<v8::debug::AccessorPair> accessors = | 
|  | value.As<v8::debug::AccessorPair>(); | 
|  | v8::Local<v8::Value> getter = accessors->getter(); | 
|  | v8::Local<v8::Value> setter = accessors->setter(); | 
|  | if (!getter->IsNull()) { | 
|  | getterMirror = ValueMirror::create(context, getter); | 
|  | } | 
|  | if (!setter->IsNull()) { | 
|  | setterMirror = ValueMirror::create(context, setter); | 
|  | } | 
|  | } else { | 
|  | valueMirror = ValueMirror::create(context, value); | 
|  | } | 
|  |  | 
|  | mirrors.emplace_back(PrivatePropertyMirror{ | 
|  | toProtocolStringWithTypeCheck(context->GetIsolate(), name), | 
|  | std::move(valueMirror), std::move(getterMirror), | 
|  | std::move(setterMirror)}); | 
|  | } | 
|  | 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; | 
|  | } | 
|  |  | 
|  | String16 descriptionForTrustedType(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::String> description; | 
|  | if (!object->ToString(context).ToLocal(&description)) return String16(); | 
|  | return toProtocolString(isolate, 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. | 
|  | auto descriptionForValueSubtype = | 
|  | clientFor(context)->descriptionForValueSubtype(context, value); | 
|  | if (descriptionForValueSubtype) { | 
|  | return std::make_unique<ObjectMirror>( | 
|  | value, subtype, toString16(descriptionForValueSubtype->string())); | 
|  | } | 
|  | if (subtype == "node") { | 
|  | return std::make_unique<ObjectMirror>(value, subtype, | 
|  | descriptionForNode(context, value)); | 
|  | } | 
|  | if (subtype == "trustedtype") { | 
|  | return std::make_unique<ObjectMirror>( | 
|  | value, subtype, descriptionForTrustedType(context, value)); | 
|  | } | 
|  | if (subtype == "error") { | 
|  | return std::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 std::make_unique<ObjectMirror>( | 
|  | value, RemoteObject::SubtypeEnum::Array, | 
|  | descriptionForCollection(isolate, object, | 
|  | lengthValue.As<v8::Int32>()->Value())); | 
|  | } | 
|  | } | 
|  | } | 
|  | return std::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 std::make_unique<PrimitiveValueMirror>( | 
|  | value, RemoteObject::TypeEnum::Object); | 
|  | } | 
|  | if (value->IsBoolean()) { | 
|  | return std::make_unique<PrimitiveValueMirror>( | 
|  | value, RemoteObject::TypeEnum::Boolean); | 
|  | } | 
|  | if (value->IsNumber()) { | 
|  | return std::make_unique<NumberMirror>(value.As<v8::Number>()); | 
|  | } | 
|  | v8::Isolate* isolate = context->GetIsolate(); | 
|  | if (value->IsString()) { | 
|  | return std::make_unique<PrimitiveValueMirror>( | 
|  | value, RemoteObject::TypeEnum::String); | 
|  | } | 
|  | if (value->IsBigInt()) { | 
|  | return std::make_unique<BigIntMirror>(value.As<v8::BigInt>()); | 
|  | } | 
|  | if (value->IsSymbol()) { | 
|  | return std::make_unique<SymbolMirror>(value.As<v8::Symbol>()); | 
|  | } | 
|  | if (v8::debug::WasmValue::IsWasmValue(value)) { | 
|  | return std::make_unique<WasmValueMirror>(value.As<v8::debug::WasmValue>()); | 
|  | } | 
|  | 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 std::make_unique<PrimitiveValueMirror>( | 
|  | value, RemoteObject::TypeEnum::Undefined); | 
|  | } | 
|  | if (value->IsRegExp()) { | 
|  | return std::make_unique<ObjectMirror>( | 
|  | value, RemoteObject::SubtypeEnum::Regexp, | 
|  | descriptionForRegExp(isolate, value.As<v8::RegExp>())); | 
|  | } | 
|  | if (value->IsProxy()) { | 
|  | return std::make_unique<ObjectMirror>( | 
|  | value, RemoteObject::SubtypeEnum::Proxy, "Proxy"); | 
|  | } | 
|  | if (value->IsFunction()) { | 
|  | return std::make_unique<FunctionMirror>(value); | 
|  | } | 
|  | if (value->IsDate()) { | 
|  | return std::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 std::make_unique<ObjectMirror>( | 
|  | promise, RemoteObject::SubtypeEnum::Promise, | 
|  | descriptionForObject(isolate, promise)); | 
|  | } | 
|  | if (value->IsNativeError()) { | 
|  | return std::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 std::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 std::make_unique<ObjectMirror>( | 
|  | value, RemoteObject::SubtypeEnum::Set, | 
|  | descriptionForCollection(isolate, set, set->Size())); | 
|  | } | 
|  | if (value->IsWeakMap()) { | 
|  | return std::make_unique<ObjectMirror>( | 
|  | value, RemoteObject::SubtypeEnum::Weakmap, | 
|  | descriptionForObject(isolate, value.As<v8::Object>())); | 
|  | } | 
|  | if (value->IsWeakSet()) { | 
|  | return std::make_unique<ObjectMirror>( | 
|  | value, RemoteObject::SubtypeEnum::Weakset, | 
|  | descriptionForObject(isolate, value.As<v8::Object>())); | 
|  | } | 
|  | if (value->IsMapIterator() || value->IsSetIterator()) { | 
|  | return std::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 std::make_unique<ObjectMirror>( | 
|  | object, RemoteObject::SubtypeEnum::Generator, | 
|  | descriptionForObject(isolate, object)); | 
|  | } | 
|  | if (value->IsTypedArray()) { | 
|  | v8::Local<v8::TypedArray> array = value.As<v8::TypedArray>(); | 
|  | return std::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 std::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 std::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 std::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 std::make_unique<ObjectMirror>( | 
|  | value, "internal#scopeList", | 
|  | descriptionForScopeList(value.As<v8::Array>())); | 
|  | } | 
|  | if (value->IsObject() && internalType == V8InternalValueType::kEntry) { | 
|  | return std::make_unique<ObjectMirror>( | 
|  | value, "internal#entry", | 
|  | descriptionForEntry(context, value.As<v8::Object>())); | 
|  | } | 
|  | if (value->IsObject() && internalType == V8InternalValueType::kScope) { | 
|  | return std::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 std::make_unique<ObjectMirror>( | 
|  | value, RemoteObject::SubtypeEnum::Array, | 
|  | descriptionForCollection(isolate, value.As<v8::Object>(), length)); | 
|  | } | 
|  | if (value->IsObject()) { | 
|  | return std::make_unique<ObjectMirror>( | 
|  | value, descriptionForObject(isolate, value.As<v8::Object>())); | 
|  | } | 
|  | return nullptr; | 
|  | } | 
|  | }  // namespace v8_inspector |