v8/tools/debug_helper/get-object-properties.cc
Clemens Hammacher 859b2d77c6 Replace base::make_unique by std::make_unique
Since we switched to C++14 now, we can use {std::make_unique} instead
of our own {base::make_unique} from {template-utils.h}.

R=mstarzinger@chromium.org, yangguo@chromium.org

Bug: v8:9687
No-Try: true
Change-Id: I660eb30038bbb079cee93c7861cd87ccd134f01b
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1789300
Commit-Queue: Clemens Hammacher <clemensh@chromium.org>
Reviewed-by: Yang Guo <yangguo@chromium.org>
Reviewed-by: Michael Starzinger <mstarzinger@chromium.org>
Cr-Commit-Position: refs/heads/master@{#63642}
2019-09-10 11:21:51 +00:00

537 lines
22 KiB
C++

// Copyright 2019 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 <sstream>
#include "debug-helper-internal.h"
#include "heap-constants.h"
#include "include/v8-internal.h"
#include "src/common/ptr-compr-inl.h"
#include "src/objects/string-inl.h"
#include "src/strings/unicode-inl.h"
#include "torque-generated/class-debug-readers-tq.h"
namespace i = v8::internal;
namespace v8_debug_helper_internal {
// INSTANCE_TYPE_CHECKERS_SINGLE_BASE, trimmed down to only classes that have
// layouts defined in .tq files (this subset relationship is asserted below).
// For now, this is a hand-maintained list.
// TODO(v8:7793): Torque should know enough about instance types to generate
// this list.
#define TQ_INSTANCE_TYPES_SINGLE_BASE(V) \
V(ByteArray, BYTE_ARRAY_TYPE) \
V(BytecodeArray, BYTECODE_ARRAY_TYPE) \
V(CallHandlerInfo, CALL_HANDLER_INFO_TYPE) \
V(Cell, CELL_TYPE) \
V(DescriptorArray, DESCRIPTOR_ARRAY_TYPE) \
V(EmbedderDataArray, EMBEDDER_DATA_ARRAY_TYPE) \
V(FeedbackCell, FEEDBACK_CELL_TYPE) \
V(FeedbackVector, FEEDBACK_VECTOR_TYPE) \
V(FixedDoubleArray, FIXED_DOUBLE_ARRAY_TYPE) \
V(Foreign, FOREIGN_TYPE) \
V(FreeSpace, FREE_SPACE_TYPE) \
V(HeapNumber, HEAP_NUMBER_TYPE) \
V(JSArgumentsObject, JS_ARGUMENTS_TYPE) \
V(JSArray, JS_ARRAY_TYPE) \
V(JSArrayBuffer, JS_ARRAY_BUFFER_TYPE) \
V(JSArrayIterator, JS_ARRAY_ITERATOR_TYPE) \
V(JSAsyncFromSyncIterator, JS_ASYNC_FROM_SYNC_ITERATOR_TYPE) \
V(JSAsyncFunctionObject, JS_ASYNC_FUNCTION_OBJECT_TYPE) \
V(JSAsyncGeneratorObject, JS_ASYNC_GENERATOR_OBJECT_TYPE) \
V(JSBoundFunction, JS_BOUND_FUNCTION_TYPE) \
V(JSDataView, JS_DATA_VIEW_TYPE) \
V(JSDate, JS_DATE_TYPE) \
V(JSFunction, JS_FUNCTION_TYPE) \
V(JSGlobalObject, JS_GLOBAL_OBJECT_TYPE) \
V(JSGlobalProxy, JS_GLOBAL_PROXY_TYPE) \
V(JSMap, JS_MAP_TYPE) \
V(JSMessageObject, JS_MESSAGE_OBJECT_TYPE) \
V(JSModuleNamespace, JS_MODULE_NAMESPACE_TYPE) \
V(JSPromise, JS_PROMISE_TYPE) \
V(JSProxy, JS_PROXY_TYPE) \
V(JSRegExp, JS_REGEXP_TYPE) \
V(JSRegExpStringIterator, JS_REGEXP_STRING_ITERATOR_TYPE) \
V(JSSet, JS_SET_TYPE) \
V(JSStringIterator, JS_STRING_ITERATOR_TYPE) \
V(JSTypedArray, JS_TYPED_ARRAY_TYPE) \
V(JSPrimitiveWrapper, JS_PRIMITIVE_WRAPPER_TYPE) \
V(JSFinalizationGroup, JS_FINALIZATION_GROUP_TYPE) \
V(JSFinalizationGroupCleanupIterator, \
JS_FINALIZATION_GROUP_CLEANUP_ITERATOR_TYPE) \
V(JSWeakMap, JS_WEAK_MAP_TYPE) \
V(JSWeakRef, JS_WEAK_REF_TYPE) \
V(JSWeakSet, JS_WEAK_SET_TYPE) \
V(Map, MAP_TYPE) \
V(Oddball, ODDBALL_TYPE) \
V(PreparseData, PREPARSE_DATA_TYPE) \
V(PropertyArray, PROPERTY_ARRAY_TYPE) \
V(PropertyCell, PROPERTY_CELL_TYPE) \
V(SharedFunctionInfo, SHARED_FUNCTION_INFO_TYPE) \
V(Symbol, SYMBOL_TYPE) \
V(WasmExceptionObject, WASM_EXCEPTION_TYPE) \
V(WasmGlobalObject, WASM_GLOBAL_TYPE) \
V(WasmMemoryObject, WASM_MEMORY_TYPE) \
V(WasmModuleObject, WASM_MODULE_TYPE) \
V(WasmTableObject, WASM_TABLE_TYPE) \
V(WeakArrayList, WEAK_ARRAY_LIST_TYPE) \
V(WeakCell, WEAK_CELL_TYPE)
#ifdef V8_INTL_SUPPORT
#define TQ_INSTANCE_TYPES_SINGLE_NOSTRUCTS(V) \
TQ_INSTANCE_TYPES_SINGLE_BASE(V) \
V(JSV8BreakIterator, JS_INTL_V8_BREAK_ITERATOR_TYPE) \
V(JSCollator, JS_INTL_COLLATOR_TYPE) \
V(JSDateTimeFormat, JS_INTL_DATE_TIME_FORMAT_TYPE) \
V(JSListFormat, JS_INTL_LIST_FORMAT_TYPE) \
V(JSLocale, JS_INTL_LOCALE_TYPE) \
V(JSNumberFormat, JS_INTL_NUMBER_FORMAT_TYPE) \
V(JSPluralRules, JS_INTL_PLURAL_RULES_TYPE) \
V(JSRelativeTimeFormat, JS_INTL_RELATIVE_TIME_FORMAT_TYPE) \
V(JSSegmentIterator, JS_INTL_SEGMENT_ITERATOR_TYPE) \
V(JSSegmenter, JS_INTL_SEGMENTER_TYPE)
#else
#define TQ_INSTANCE_TYPES_SINGLE_NOSTRUCTS(V) TQ_INSTANCE_TYPES_SINGLE_BASE(V)
#endif // V8_INTL_SUPPORT
enum class InstanceTypeCheckersSingle {
#define ENUM_VALUE(ClassName, INSTANCE_TYPE) k##ClassName = i::INSTANCE_TYPE,
INSTANCE_TYPE_CHECKERS_SINGLE(ENUM_VALUE)
#undef ENUM_VALUE
};
#define CHECK_VALUE(ClassName, INSTANCE_TYPE) \
static_assert( \
static_cast<i::InstanceType>( \
InstanceTypeCheckersSingle::k##ClassName) == i::INSTANCE_TYPE, \
"TQ_INSTANCE_TYPES_SINGLE_NOSTRUCTS must be subset of " \
"INSTANCE_TYPE_CHECKERS_SINGLE. Invalid class: " #ClassName);
TQ_INSTANCE_TYPES_SINGLE_NOSTRUCTS(CHECK_VALUE)
#undef CHECK_VALUE
// Adapts one STRUCT_LIST_GENERATOR entry to (Name, NAME) format.
#define STRUCT_INSTANCE_TYPE_ADAPTER(V, NAME, Name, name) V(Name, NAME)
#define TQ_INSTANCE_TYPES_SINGLE(V) \
TQ_INSTANCE_TYPES_SINGLE_NOSTRUCTS(V) \
STRUCT_LIST_GENERATOR(STRUCT_INSTANCE_TYPE_ADAPTER, V)
// Likewise, these are the subset of INSTANCE_TYPE_CHECKERS_RANGE that have
// definitions in .tq files, rearranged with more specific things first. Also
// includes JSObject and JSReceiver, which in the runtime are optimized to use
// a one-sided check.
#define TQ_INSTANCE_TYPES_RANGE(V) \
V(Context, FIRST_CONTEXT_TYPE, LAST_CONTEXT_TYPE) \
V(FixedArray, FIRST_FIXED_ARRAY_TYPE, LAST_FIXED_ARRAY_TYPE) \
V(Microtask, FIRST_MICROTASK_TYPE, LAST_MICROTASK_TYPE) \
V(String, FIRST_STRING_TYPE, LAST_STRING_TYPE) \
V(Name, FIRST_NAME_TYPE, LAST_NAME_TYPE) \
V(WeakFixedArray, FIRST_WEAK_FIXED_ARRAY_TYPE, LAST_WEAK_FIXED_ARRAY_TYPE) \
V(JSObject, FIRST_JS_OBJECT_TYPE, LAST_JS_OBJECT_TYPE) \
V(JSReceiver, FIRST_JS_RECEIVER_TYPE, LAST_JS_RECEIVER_TYPE)
std::string AppendAddressAndType(const std::string& brief, uintptr_t address,
const char* type) {
std::stringstream brief_stream;
brief_stream << "0x" << std::hex << address << " <" << type << ">";
return brief.empty() ? brief_stream.str()
: brief + " (" + brief_stream.str() + ")";
}
struct TypedObject {
TypedObject(d::TypeCheckResult type_check_result,
std::unique_ptr<TqObject> object)
: type_check_result(type_check_result), object(std::move(object)) {}
d::TypeCheckResult type_check_result;
std::unique_ptr<TqObject> object;
};
TypedObject GetTypedObjectByHint(uintptr_t address,
std::string type_hint_string) {
#define TYPE_NAME_CASE(ClassName, ...) \
if (type_hint_string == "v8::internal::" #ClassName) { \
return {d::TypeCheckResult::kUsedTypeHint, \
std::make_unique<Tq##ClassName>(address)}; \
}
TQ_INSTANCE_TYPES_SINGLE(TYPE_NAME_CASE)
TQ_INSTANCE_TYPES_RANGE(TYPE_NAME_CASE)
#undef TYPE_NAME_CASE
return {d::TypeCheckResult::kUnknownTypeHint,
std::make_unique<TqHeapObject>(address)};
}
TypedObject GetTypedObjectForString(uintptr_t address, i::InstanceType type) {
class StringGetDispatcher : public i::AllStatic {
public:
#define DEFINE_METHOD(ClassName) \
static inline TypedObject Handle##ClassName(uintptr_t address) { \
return {d::TypeCheckResult::kUsedMap, \
std::make_unique<Tq##ClassName>(address)}; \
}
STRING_CLASS_TYPES(DEFINE_METHOD)
#undef DEFINE_METHOD
static inline TypedObject HandleInvalidString(uintptr_t address) {
return {d::TypeCheckResult::kUnknownInstanceType,
std::make_unique<TqString>(address)};
}
};
return i::StringShape(type)
.DispatchToSpecificTypeWithoutCast<StringGetDispatcher, TypedObject>(
address);
}
TypedObject GetTypedHeapObject(uintptr_t address, d::MemoryAccessor accessor,
const char* type_hint) {
auto heap_object = std::make_unique<TqHeapObject>(address);
Value<uintptr_t> map_ptr = heap_object->GetMapValue(accessor);
if (map_ptr.validity != d::MemoryAccessResult::kOk) {
return {map_ptr.validity == d::MemoryAccessResult::kAddressNotValid
? d::TypeCheckResult::kObjectPointerInvalid
: d::TypeCheckResult::kObjectPointerValidButInaccessible,
std::move(heap_object)};
}
Value<i::InstanceType> type =
TqMap(map_ptr.value).GetInstanceTypeValue(accessor);
if (type.validity == d::MemoryAccessResult::kOk) {
// Dispatch to the appropriate method for each instance type. After calling
// the generated method to fetch properties, we can add custom properties.
switch (type.value) {
#define INSTANCE_TYPE_CASE(ClassName, INSTANCE_TYPE) \
case i::INSTANCE_TYPE: \
return {d::TypeCheckResult::kUsedMap, \
std::make_unique<Tq##ClassName>(address)};
TQ_INSTANCE_TYPES_SINGLE(INSTANCE_TYPE_CASE)
#undef INSTANCE_TYPE_CASE
default:
// Special case: concrete subtypes of String are not included in the
// main instance type list because they use the low bits of the instance
// type enum as flags.
if (type.value <= i::LAST_STRING_TYPE) {
return GetTypedObjectForString(address, type.value);
}
#define INSTANCE_RANGE_CASE(ClassName, FIRST_TYPE, LAST_TYPE) \
if (type.value >= i::FIRST_TYPE && type.value <= i::LAST_TYPE) { \
return {d::TypeCheckResult::kUsedMap, \
std::make_unique<Tq##ClassName>(address)}; \
}
TQ_INSTANCE_TYPES_RANGE(INSTANCE_RANGE_CASE)
#undef INSTANCE_RANGE_CASE
return {d::TypeCheckResult::kUnknownInstanceType,
std::move(heap_object)};
break;
}
} else if (type_hint != nullptr) {
// Try to use the provided type hint, since the real instance type is
// unavailable.
return GetTypedObjectByHint(address, type_hint);
} else {
// TODO(v8:9376): Use known maps here. If known map is just a guess (because
// heap page addresses weren't provided), then create a synthetic property
// with the more specific type. Then the caller could presumably ask us
// again with the type hint we provided. Otherwise, just go ahead and use it
// to generate properties.
return {type.validity == d::MemoryAccessResult::kAddressNotValid
? d::TypeCheckResult::kMapPointerInvalid
: d::TypeCheckResult::kMapPointerValidButInaccessible,
std::move(heap_object)};
}
}
#undef STRUCT_INSTANCE_TYPE_ADAPTER
#undef TQ_INSTANCE_TYPES_SINGLE_BASE
#undef TQ_INSTANCE_TYPES_SINGLE
#undef TQ_INSTANCE_TYPES_SINGLE_NOSTRUCTS
#undef TQ_INSTANCE_TYPES_RANGE
// An object visitor that accumulates the first few characters of a string.
class ReadStringVisitor : public TqObjectVisitor {
public:
ReadStringVisitor(d::MemoryAccessor accessor)
: accessor_(accessor), index_(0), limit_(INT32_MAX), done_(false) {}
// Returns the result as UTF-8 once visiting is complete.
std::string GetString() {
std::vector<char> result(
string_.size() * unibrow::Utf16::kMaxExtraUtf8BytesForOneUtf16CodeUnit);
unsigned write_index = 0;
int prev_char = unibrow::Utf16::kNoPreviousCharacter;
for (size_t read_index = 0; read_index < string_.size(); ++read_index) {
uint16_t character = string_[read_index];
write_index +=
unibrow::Utf8::Encode(result.data() + write_index, character,
prev_char, /*replace_invalid=*/true);
prev_char = character;
}
return {result.data(), write_index};
}
template <typename T>
void ReadSeqString(const T* object) {
int32_t length = GetOrFinish(object->GetLengthValue(accessor_));
for (; index_ < length && index_ < limit_ && !done_; ++index_) {
char16_t c = static_cast<char16_t>(
GetOrFinish(object->GetCharsValue(accessor_, index_)));
if (!done_) AddCharacter(c);
}
}
void VisitSeqOneByteString(const TqSeqOneByteString* object) override {
ReadSeqString(object);
}
void VisitSeqTwoByteString(const TqSeqTwoByteString* object) override {
ReadSeqString(object);
}
void VisitConsString(const TqConsString* object) override {
uintptr_t first_address = GetOrFinish(object->GetFirstValue(accessor_));
if (done_) return;
auto first = GetTypedHeapObject(first_address, accessor_, nullptr).object;
first->Visit(this);
if (done_) return;
int32_t first_length = GetOrFinish(
static_cast<TqString*>(first.get())->GetLengthValue(accessor_));
uintptr_t second = GetOrFinish(object->GetSecondValue(accessor_));
if (done_) return;
IndexModifier modifier(this, -first_length, -first_length);
GetTypedHeapObject(second, accessor_, nullptr).object->Visit(this);
}
void VisitSlicedString(const TqSlicedString* object) override {
uintptr_t parent = GetOrFinish(object->GetParentValue(accessor_));
int32_t length = GetOrFinish(object->GetLengthValue(accessor_));
int32_t offset = i::PlatformSmiTagging::SmiToInt(
GetOrFinish(object->GetOffsetValue(accessor_)));
if (done_) return;
int32_t limit_adjust = offset + length - limit_;
IndexModifier modifier(this, offset, limit_adjust < 0 ? limit_adjust : 0);
GetTypedHeapObject(parent, accessor_, nullptr).object->Visit(this);
}
void VisitThinString(const TqThinString* object) override {
uintptr_t actual = GetOrFinish(object->GetActualValue(accessor_));
if (done_) return;
GetTypedHeapObject(actual, accessor_, nullptr).object->Visit(this);
}
void VisitExternalString(const TqExternalString* object) override {
// TODO(v8:9376): External strings are very common and important when
// attempting to print the source of a function in the browser. For now
// we're just ignoring them, but eventually we'll want some kind of
// mechanism where the user of this library can provide a callback function
// that fetches data from external strings.
AddEllipsisAndFinish();
}
void VisitObject(const TqObject* object) override {
// If we fail to find a specific type for a sub-object within a cons string,
// sliced string, or thin string, we will end up here.
AddEllipsisAndFinish();
}
private:
// Unpacks a value that was fetched from the debuggee. If the value indicates
// that it couldn't successfully fetch memory, then prevents further work.
template <typename T>
T GetOrFinish(Value<T> value) {
if (value.validity != d::MemoryAccessResult::kOk) {
AddEllipsisAndFinish();
}
return value.value;
}
void AddEllipsisAndFinish() {
if (!done_) {
string_ += u"...";
done_ = true;
}
}
void AddCharacter(char16_t c) {
if (string_.size() >= kMaxCharacters) {
AddEllipsisAndFinish();
} else {
string_.push_back(c);
}
}
// Temporarily adds offsets to both index_ and limit_, to handle ConsString
// and SlicedString.
class IndexModifier {
public:
IndexModifier(ReadStringVisitor* that, int32_t index_adjust,
int32_t limit_adjust)
: that_(that),
index_adjust_(index_adjust),
limit_adjust_(limit_adjust) {
that_->index_ += index_adjust_;
that_->limit_ += limit_adjust_;
}
~IndexModifier() {
that_->index_ -= index_adjust_;
that_->limit_ -= limit_adjust_;
}
private:
ReadStringVisitor* that_;
int32_t index_adjust_;
int32_t limit_adjust_;
DISALLOW_COPY_AND_ASSIGN(IndexModifier);
};
static constexpr int kMaxCharacters = 80; // How many characters to print.
std::u16string string_; // Result string.
d::MemoryAccessor accessor_;
int32_t index_; // Index of next char to read.
int32_t limit_; // Don't read past this index (set by SlicedString).
bool done_; // Whether to stop further work.
};
// An object visitor that adds extra debugging information for some types.
class AddInfoVisitor : public TqObjectVisitor {
public:
AddInfoVisitor(const std::string& brief, d::MemoryAccessor accessor)
: accessor_(accessor), brief_(brief) {}
// Returns the brief object description, once visiting is complete.
const std::string& GetBrief() { return brief_; }
void VisitString(const TqString* object) override {
ReadStringVisitor visitor(accessor_);
object->Visit(&visitor);
if (!brief_.empty()) brief_ += " ";
brief_ += "\"" + visitor.GetString() + "\"";
}
private:
d::MemoryAccessor accessor_;
std::string brief_;
};
std::unique_ptr<ObjectPropertiesResult> GetHeapObjectProperties(
uintptr_t address, d::MemoryAccessor accessor, const char* type_hint,
std::string brief) {
TypedObject typed = GetTypedHeapObject(address, accessor, type_hint);
// TODO(v8:9376): Many object types need additional data that is not included
// in their Torque layout definitions. For example, JSObject has an array of
// in-object properties after its Torque-defined fields, which at a minimum
// should be represented as an array in this response. If the relevant memory
// is available, we should instead represent those properties (and any out-of-
// object properties) using their JavaScript property names.
AddInfoVisitor visitor(brief, accessor);
typed.object->Visit(&visitor);
brief = visitor.GetBrief();
brief = AppendAddressAndType(brief, address, typed.object->GetName());
return std::make_unique<ObjectPropertiesResult>(
typed.type_check_result, brief, typed.object->GetName(),
typed.object->GetProperties(accessor));
}
std::unique_ptr<ObjectPropertiesResult> GetHeapObjectProperties(
uintptr_t address, d::MemoryAccessor memory_accessor,
d::HeapAddresses heap_addresses, const char* type_hint) {
// Try to figure out the heap range, for pointer compression (this is unused
// if pointer compression is disabled).
uintptr_t any_uncompressed_ptr = 0;
if (!IsPointerCompressed(address)) any_uncompressed_ptr = address;
if (any_uncompressed_ptr == 0)
any_uncompressed_ptr = heap_addresses.any_heap_pointer;
if (any_uncompressed_ptr == 0)
any_uncompressed_ptr = heap_addresses.map_space_first_page;
if (any_uncompressed_ptr == 0)
any_uncompressed_ptr = heap_addresses.old_space_first_page;
if (any_uncompressed_ptr == 0)
any_uncompressed_ptr = heap_addresses.read_only_space_first_page;
FillInUnknownHeapAddresses(&heap_addresses, any_uncompressed_ptr);
if (any_uncompressed_ptr == 0) {
// We can't figure out the heap range. Just check for known objects.
std::string brief = FindKnownObject(address, heap_addresses);
brief = AppendAddressAndType(brief, address, "v8::internal::TaggedValue");
return std::make_unique<ObjectPropertiesResult>(
d::TypeCheckResult::kUnableToDecompress, brief,
"v8::internal::TaggedValue",
std::vector<std::unique_ptr<ObjectProperty>>());
}
address = Decompress(address, any_uncompressed_ptr);
// From here on all addresses should be decompressed.
// Regardless of whether we can read the object itself, maybe we can find its
// pointer in the list of known objects.
std::string brief = FindKnownObject(address, heap_addresses);
return GetHeapObjectProperties(address, memory_accessor, type_hint, brief);
}
std::unique_ptr<ObjectPropertiesResult> GetObjectPropertiesImpl(
uintptr_t address, d::MemoryAccessor memory_accessor,
const d::HeapAddresses& heap_addresses, const char* type_hint) {
std::vector<std::unique_ptr<ObjectProperty>> props;
if (static_cast<uint32_t>(address) == i::kClearedWeakHeapObjectLower32) {
return std::make_unique<ObjectPropertiesResult>(
d::TypeCheckResult::kWeakRef, "cleared weak ref",
"v8::internal::HeapObject", std::move(props));
}
bool is_weak = (address & i::kHeapObjectTagMask) == i::kWeakHeapObjectTag;
if (is_weak) {
address &= ~i::kWeakHeapObjectMask;
}
if (i::Internals::HasHeapObjectTag(address)) {
std::unique_ptr<ObjectPropertiesResult> result = GetHeapObjectProperties(
address, memory_accessor, heap_addresses, type_hint);
if (is_weak) {
result->Prepend("weak ref to ");
}
return result;
}
// For smi values, construct a response with a description representing the
// untagged value.
int32_t value = i::PlatformSmiTagging::SmiToInt(address);
std::stringstream stream;
stream << value << " (0x" << std::hex << value << ")";
return std::make_unique<ObjectPropertiesResult>(
d::TypeCheckResult::kSmi, stream.str(), "v8::internal::Smi",
std::move(props));
}
} // namespace v8_debug_helper_internal
namespace di = v8_debug_helper_internal;
extern "C" {
V8_DEBUG_HELPER_EXPORT d::ObjectPropertiesResult*
_v8_debug_helper_GetObjectProperties(uintptr_t object,
d::MemoryAccessor memory_accessor,
const d::HeapAddresses& heap_addresses,
const char* type_hint) {
return di::GetObjectPropertiesImpl(object, memory_accessor, heap_addresses,
type_hint)
.release()
->GetPublicView();
}
V8_DEBUG_HELPER_EXPORT void _v8_debug_helper_Free_ObjectPropertiesResult(
d::ObjectPropertiesResult* result) {
std::unique_ptr<di::ObjectPropertiesResult> ptr(
static_cast<di::ObjectPropertiesResultExtended*>(result)->base);
}
}