Add the ability to get a C++ vtable ValueObject from another ValueObj… (#67599)
Add the ability to get a C++ vtable ValueObject from another
ValueObject.
This patch adds the ability to ask a ValueObject for a ValueObject that
represents the virtual function table for a C++ class. If the
ValueObject is not a C++ class with a vtable, a valid ValueObject value
will be returned that contains an appropriate error. If it is successful
a valid ValueObject that represents vtable will be returned. The
ValueObject that is returned will have a name that matches the demangled
value for a C++ vtable mangled name like "vtable for <class-name>". It
will have N children, one for each virtual function pointer. Each
child's value is the function pointer itself, the summary is the
symbolication of this function pointer, and the type will be a valid
function pointer from the debug info if there is debug information
corresponding to the virtual function pointer.
The vtable SBValue will have the following:
- SBValue::GetName() returns "vtable for <class>"
- SBValue::GetValue() returns a string representation of the vtable
address
- SBValue::GetSummary() returns NULL
- SBValue::GetType() returns a type appropriate for a uintptr_t type for
the current process
- SBValue::GetLoadAddress() returns the address of the vtable adderess
- SBValue::GetValueAsUnsigned(...) returns the vtable address
- SBValue::GetNumChildren() returns the number of virtual function
pointers in the vtable
- SBValue::GetChildAtIndex(...) returns a SBValue that represents a
virtual function pointer
The child SBValue objects that represent a virtual function pointer has
the following values:
- SBValue::GetName() returns "[%u]" where %u is the vtable function
pointer index
- SBValue::GetValue() returns a string representation of the virtual
function pointer
- SBValue::GetSummary() returns a symbolicated respresentation of the
virtual function pointer
- SBValue::GetType() returns the function prototype type if there is
debug info, or a generic funtion prototype if there is no debug info
- SBValue::GetLoadAddress() returns the address of the virtual function
pointer
- SBValue::GetValueAsUnsigned(...) returns the virtual function pointer
- SBValue::GetNumChildren() returns 0
- SBValue::GetChildAtIndex(...) returns invalid SBValue for any index
Examples of using this API via python:
```
(lldb) script vtable = lldb.frame.FindVariable("shape_ptr").GetVTable()
(lldb) script vtable
vtable for Shape = 0x0000000100004088 {
[0] = 0x0000000100003d20 a.out`Shape::~Shape() at main.cpp:3
[1] = 0x0000000100003e4c a.out`Shape::~Shape() at main.cpp:3
[2] = 0x0000000100003e7c a.out`Shape::area() at main.cpp:4
[3] = 0x0000000100003e3c a.out`Shape::optional() at main.cpp:7
}
(lldb) script c = vtable.GetChildAtIndex(0)
(lldb) script c
(void ()) [0] = 0x0000000100003d20 a.out`Shape::~Shape() at main.cpp:3
```
This commit is contained in:
@@ -139,19 +139,6 @@ SBType supports the eq/ne operator. For example,::
|
||||
"
|
||||
) lldb::SBType::IsReferenceType;
|
||||
|
||||
%feature("docstring",
|
||||
"Returns true if this type is a function type.
|
||||
|
||||
Language-specific behaviour:
|
||||
|
||||
* C: Returns true for types that represent functions. Note that function
|
||||
pointers are not function types (but their `GetPointeeType()` are function
|
||||
types).
|
||||
* C++: Same as in C.
|
||||
* Objective-C: Returns false for all types.
|
||||
"
|
||||
) lldb::SBType::IsPolymorphicClass;
|
||||
|
||||
%feature("docstring",
|
||||
"Returns true if this type is a polymorphic type.
|
||||
|
||||
|
||||
@@ -374,6 +374,52 @@ public:
|
||||
lldb::SBWatchpoint WatchPointee(bool resolve_location, bool read, bool write,
|
||||
SBError &error);
|
||||
|
||||
/// If this value represents a C++ class that has a vtable, return an value
|
||||
/// that represents the virtual function table.
|
||||
///
|
||||
/// SBValue::GetError() will be in the success state if this value represents
|
||||
/// a C++ class with a vtable, or an appropriate error describing that the
|
||||
/// object isn't a C++ class with a vtable or not a C++ class.
|
||||
///
|
||||
/// SBValue::GetName() will be the demangled symbol name for the virtual
|
||||
/// function table like "vtable for <classname>".
|
||||
///
|
||||
/// SBValue::GetValue() will be the address of the first vtable entry if the
|
||||
/// current SBValue is a class with a vtable, or nothing the current SBValue
|
||||
/// is not a C++ class or not a C++ class that has a vtable.
|
||||
///
|
||||
/// SBValue::GetValueAtUnsigned(...) will return the address of the first
|
||||
/// vtable entry.
|
||||
///
|
||||
/// SBValue::GetLoadAddress() will return the address of the vtable pointer
|
||||
/// found in the parent SBValue.
|
||||
///
|
||||
/// SBValue::GetNumChildren() will return the number of virtual function
|
||||
/// pointers in the vtable, or zero on error.
|
||||
///
|
||||
/// SBValue::GetChildAtIndex(...) will return each virtual function pointer
|
||||
/// as a SBValue object.
|
||||
///
|
||||
/// The child SBValue objects will have the following values:
|
||||
///
|
||||
/// SBValue::GetError() will indicate success if the vtable entry was
|
||||
/// successfully read from memory, or an error if not.
|
||||
///
|
||||
/// SBValue::GetName() will be the vtable function index in the form "[%u]"
|
||||
/// where %u is the index.
|
||||
///
|
||||
/// SBValue::GetValue() will be the virtual function pointer value as a
|
||||
/// string.
|
||||
///
|
||||
/// SBValue::GetValueAtUnsigned(...) will return the virtual function
|
||||
/// pointer value.
|
||||
///
|
||||
/// SBValue::GetLoadAddress() will return the address of the virtual function
|
||||
/// pointer.
|
||||
///
|
||||
/// SBValue::GetNumChildren() returns 0
|
||||
lldb::SBValue GetVTable();
|
||||
|
||||
protected:
|
||||
friend class SBBlock;
|
||||
friend class SBFrame;
|
||||
|
||||
@@ -620,6 +620,10 @@ public:
|
||||
virtual lldb::ValueObjectSP CastPointerType(const char *name,
|
||||
lldb::TypeSP &type_sp);
|
||||
|
||||
/// If this object represents a C++ class with a vtable, return an object
|
||||
/// that represents the virtual function table. If the object isn't a class
|
||||
/// with a vtable, return a valid ValueObject with the error set correctly.
|
||||
lldb::ValueObjectSP GetVTable();
|
||||
// The backing bits of this value object were updated, clear any descriptive
|
||||
// string, so we know we have to refetch them.
|
||||
void ValueUpdated() {
|
||||
|
||||
@@ -73,6 +73,7 @@ protected:
|
||||
friend class ValueObject;
|
||||
friend class ValueObjectConstResult;
|
||||
friend class ValueObjectConstResultImpl;
|
||||
friend class ValueObjectVTable;
|
||||
|
||||
ValueObjectChild(ValueObject &parent, const CompilerType &compiler_type,
|
||||
ConstString name, uint64_t byte_size,
|
||||
|
||||
105
lldb/include/lldb/Core/ValueObjectVTable.h
Normal file
105
lldb/include/lldb/Core/ValueObjectVTable.h
Normal file
@@ -0,0 +1,105 @@
|
||||
//===-- ValueObjectVTable.h -------------------------------------*- C++ -*-===//
|
||||
//
|
||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#ifndef LLDB_CORE_VALUEOBJECTVTABLE_H
|
||||
#define LLDB_CORE_VALUEOBJECTVTABLE_H
|
||||
|
||||
#include "lldb/Core/ValueObject.h"
|
||||
|
||||
namespace lldb_private {
|
||||
|
||||
/// A class that represents a virtual function table for a C++ class.
|
||||
///
|
||||
/// ValueObject::GetError() will be in the success state if this value
|
||||
/// represents a C++ class with a vtable, or an appropriate error describing
|
||||
/// that the object isn't a C++ class with a vtable or not a C++ class.
|
||||
///
|
||||
/// ValueObject::GetName() will be the demangled symbol name for the virtual
|
||||
/// function table like "vtable for <classname>".
|
||||
///
|
||||
/// ValueObject::GetValueAsCString() will be the address of the first vtable
|
||||
/// entry if the current ValueObject is a class with a vtable, or nothing the
|
||||
/// current ValueObject is not a C++ class or not a C++ class that has a
|
||||
/// vtable.
|
||||
///
|
||||
/// ValueObject::GetValueAtUnsigned(...) will return the address of the first
|
||||
/// vtable entry.
|
||||
///
|
||||
/// ValueObject::GetAddressOf() will return the address of the vtable pointer
|
||||
/// found in the parent ValueObject.
|
||||
///
|
||||
/// ValueObject::GetNumChildren() will return the number of virtual function
|
||||
/// pointers in the vtable, or zero on error.
|
||||
///
|
||||
/// ValueObject::GetChildAtIndex(...) will return each virtual function pointer
|
||||
/// as a ValueObject object.
|
||||
///
|
||||
/// The child ValueObjects will have the following values:
|
||||
///
|
||||
/// ValueObject::GetError() will indicate success if the vtable entry was
|
||||
/// successfully read from memory, or an error if not.
|
||||
///
|
||||
/// ValueObject::GetName() will be the vtable function index in the form "[%u]"
|
||||
/// where %u is the index.
|
||||
///
|
||||
/// ValueObject::GetValueAsCString() will be the virtual function pointer value
|
||||
///
|
||||
/// ValueObject::GetValueAtUnsigned(...) will return the virtual function
|
||||
/// pointer value.
|
||||
///
|
||||
/// ValueObject::GetAddressOf() will return the address of the virtual function
|
||||
/// pointer.
|
||||
///
|
||||
/// ValueObject::GetNumChildren() returns 0
|
||||
class ValueObjectVTable : public ValueObject {
|
||||
public:
|
||||
~ValueObjectVTable() override;
|
||||
|
||||
static lldb::ValueObjectSP Create(ValueObject &parent);
|
||||
|
||||
std::optional<uint64_t> GetByteSize() override;
|
||||
|
||||
size_t CalculateNumChildren(uint32_t max) override;
|
||||
|
||||
ValueObject *CreateChildAtIndex(size_t idx, bool synthetic_array_member,
|
||||
int32_t synthetic_index) override;
|
||||
|
||||
lldb::ValueType GetValueType() const override;
|
||||
|
||||
ConstString GetTypeName() override;
|
||||
|
||||
ConstString GetQualifiedTypeName() override;
|
||||
|
||||
ConstString GetDisplayTypeName() override;
|
||||
|
||||
bool IsInScope() override;
|
||||
|
||||
protected:
|
||||
bool UpdateValue() override;
|
||||
|
||||
CompilerType GetCompilerTypeImpl() override;
|
||||
|
||||
/// The symbol for the C++ virtual function table.
|
||||
const Symbol *m_vtable_symbol = nullptr;
|
||||
/// Cache the number of vtable children when we update the value.
|
||||
uint32_t m_num_vtable_entries = 0;
|
||||
/// Cache the address size in bytes to avoid checking with the process to
|
||||
/// many times.
|
||||
uint32_t m_addr_size = 0;
|
||||
|
||||
private:
|
||||
ValueObjectVTable(ValueObject &parent);
|
||||
|
||||
// For ValueObject only
|
||||
ValueObjectVTable(const ValueObjectVTable &) = delete;
|
||||
const ValueObjectVTable &operator=(const ValueObjectVTable &) = delete;
|
||||
};
|
||||
|
||||
} // namespace lldb_private
|
||||
|
||||
#endif // LLDB_CORE_VALUEOBJECTVTABLE_H
|
||||
@@ -420,6 +420,8 @@ public:
|
||||
|
||||
void SetName(const char *type_name_cstr);
|
||||
|
||||
void SetName(llvm::StringRef name);
|
||||
|
||||
void SetTypeSP(lldb::TypeSP type_sp);
|
||||
|
||||
void SetCompilerType(CompilerType compiler_type);
|
||||
|
||||
@@ -437,6 +437,10 @@ public:
|
||||
|
||||
virtual CompilerType GetBasicTypeFromAST(lldb::BasicType basic_type) = 0;
|
||||
|
||||
virtual CompilerType CreateGenericFunctionPrototype() {
|
||||
return CompilerType();
|
||||
}
|
||||
|
||||
virtual CompilerType
|
||||
GetBuiltinTypeForEncodingAndBitSize(lldb::Encoding encoding,
|
||||
size_t bit_size) = 0;
|
||||
|
||||
@@ -78,6 +78,32 @@ public:
|
||||
virtual bool GetObjectDescription(Stream &str, Value &value,
|
||||
ExecutionContextScope *exe_scope) = 0;
|
||||
|
||||
|
||||
struct VTableInfo {
|
||||
Address addr; /// Address of the vtable's virtual function table
|
||||
Symbol *symbol; /// The vtable symbol from the symbol table
|
||||
};
|
||||
/// Get the vtable information for a given value.
|
||||
///
|
||||
/// \param[in] in_value
|
||||
/// The value object to try and extract the VTableInfo from.
|
||||
///
|
||||
/// \param[in] check_type
|
||||
/// If true, the compiler type of \a in_value will be checked to see if
|
||||
/// it is an instance to, or pointer or reference to a class or struct
|
||||
/// that has a vtable. If the type doesn't meet the requirements, an
|
||||
/// error will be returned explaining why the type isn't suitable.
|
||||
///
|
||||
/// \return
|
||||
/// An error if anything goes wrong while trying to extract the vtable
|
||||
/// or if \a check_type is true and the type doesn't have a vtable.
|
||||
virtual llvm::Expected<VTableInfo> GetVTableInfo(ValueObject &in_value,
|
||||
bool check_type) {
|
||||
return llvm::createStringError(
|
||||
std::errc::invalid_argument,
|
||||
"language doesn't support getting vtable information");
|
||||
}
|
||||
|
||||
// this call should return true if it could set the name and/or the type
|
||||
virtual bool GetDynamicTypeAndAddress(ValueObject &in_value,
|
||||
lldb::DynamicValueType use_dynamic,
|
||||
|
||||
@@ -322,7 +322,9 @@ enum ValueType {
|
||||
eValueTypeRegister = 5, ///< stack frame register value
|
||||
eValueTypeRegisterSet = 6, ///< A collection of stack frame register values
|
||||
eValueTypeConstResult = 7, ///< constant result variables
|
||||
eValueTypeVariableThreadLocal = 8 ///< thread local storage variable
|
||||
eValueTypeVariableThreadLocal = 8, ///< thread local storage variable
|
||||
eValueTypeVTable = 9, ///< virtual function table
|
||||
eValueTypeVTableEntry = 10, ///< function pointer in virtual function table
|
||||
};
|
||||
|
||||
/// Token size/granularities for Input Readers.
|
||||
|
||||
@@ -114,7 +114,7 @@ public:
|
||||
|
||||
Target *target = value_sp->GetTargetSP().get();
|
||||
// If this ValueObject holds an error, then it is valuable for that.
|
||||
if (value_sp->GetError().Fail())
|
||||
if (value_sp->GetError().Fail())
|
||||
return value_sp;
|
||||
|
||||
if (!target)
|
||||
@@ -1038,8 +1038,8 @@ lldb::ValueObjectSP SBValue::GetSP(ValueLocker &locker) const {
|
||||
// IsValid means that the SBValue has a value in it. But that's not the
|
||||
// only time that ValueObjects are useful. We also want to return the value
|
||||
// if there's an error state in it.
|
||||
if (!m_opaque_sp || (!m_opaque_sp->IsValid()
|
||||
&& (m_opaque_sp->GetRootSP()
|
||||
if (!m_opaque_sp || (!m_opaque_sp->IsValid()
|
||||
&& (m_opaque_sp->GetRootSP()
|
||||
&& !m_opaque_sp->GetRootSP()->GetError().Fail()))) {
|
||||
locker.GetError().SetErrorString("No value");
|
||||
return ValueObjectSP();
|
||||
@@ -1505,3 +1505,14 @@ lldb::SBValue SBValue::Persist() {
|
||||
}
|
||||
return persisted_sb;
|
||||
}
|
||||
|
||||
lldb::SBValue SBValue::GetVTable() {
|
||||
SBValue vtable_sb;
|
||||
ValueLocker locker;
|
||||
lldb::ValueObjectSP value_sp(GetSP(locker));
|
||||
if (!value_sp)
|
||||
return vtable_sb;
|
||||
|
||||
vtable_sb.SetSP(value_sp->GetVTable());
|
||||
return vtable_sb;
|
||||
}
|
||||
|
||||
@@ -490,6 +490,8 @@ protected:
|
||||
case eValueTypeRegisterSet:
|
||||
case eValueTypeConstResult:
|
||||
case eValueTypeVariableThreadLocal:
|
||||
case eValueTypeVTable:
|
||||
case eValueTypeVTableEntry:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,6 +71,7 @@ add_lldb_library(lldbCore
|
||||
ValueObjectSyntheticFilter.cpp
|
||||
ValueObjectUpdater.cpp
|
||||
ValueObjectVariable.cpp
|
||||
ValueObjectVTable.cpp
|
||||
|
||||
DEPENDS
|
||||
clang-tablegen-targets
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "lldb/Core/ValueObjectDynamicValue.h"
|
||||
#include "lldb/Core/ValueObjectMemory.h"
|
||||
#include "lldb/Core/ValueObjectSyntheticFilter.h"
|
||||
#include "lldb/Core/ValueObjectVTable.h"
|
||||
#include "lldb/DataFormatters/DataVisualization.h"
|
||||
#include "lldb/DataFormatters/DumpValueObjectOptions.h"
|
||||
#include "lldb/DataFormatters/FormatManager.h"
|
||||
@@ -3155,3 +3156,7 @@ ValueObjectSP ValueObject::Persist() {
|
||||
|
||||
return persistent_var_sp->GetValueObject();
|
||||
}
|
||||
|
||||
lldb::ValueObjectSP ValueObject::GetVTable() {
|
||||
return ValueObjectVTable::Create(*this);
|
||||
}
|
||||
|
||||
274
lldb/source/Core/ValueObjectVTable.cpp
Normal file
274
lldb/source/Core/ValueObjectVTable.cpp
Normal file
@@ -0,0 +1,274 @@
|
||||
//===-- ValueObjectVTable.cpp ---------------------------------------------===//
|
||||
//
|
||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "lldb/Core/ValueObjectVTable.h"
|
||||
#include "lldb/Core/Module.h"
|
||||
#include "lldb/Core/ValueObjectChild.h"
|
||||
#include "lldb/Symbol/Function.h"
|
||||
#include "lldb/Target/Language.h"
|
||||
#include "lldb/Target/LanguageRuntime.h"
|
||||
#include "lldb/lldb-defines.h"
|
||||
#include "lldb/lldb-enumerations.h"
|
||||
#include "lldb/lldb-forward.h"
|
||||
#include "lldb/lldb-private-enumerations.h"
|
||||
|
||||
using namespace lldb;
|
||||
using namespace lldb_private;
|
||||
|
||||
class ValueObjectVTableChild : public ValueObject {
|
||||
public:
|
||||
ValueObjectVTableChild(ValueObject &parent, uint32_t func_idx,
|
||||
uint64_t addr_size)
|
||||
: ValueObject(parent), m_func_idx(func_idx), m_addr_size(addr_size) {
|
||||
SetFormat(eFormatPointer);
|
||||
SetName(ConstString(llvm::formatv("[{0}]", func_idx).str()));
|
||||
}
|
||||
|
||||
~ValueObjectVTableChild() override = default;
|
||||
|
||||
std::optional<uint64_t> GetByteSize() override { return m_addr_size; };
|
||||
|
||||
size_t CalculateNumChildren(uint32_t max) override { return 0; };
|
||||
|
||||
ValueType GetValueType() const override { return eValueTypeVTableEntry; };
|
||||
|
||||
bool IsInScope() override {
|
||||
if (ValueObject *parent = GetParent())
|
||||
return parent->IsInScope();
|
||||
return false;
|
||||
};
|
||||
|
||||
protected:
|
||||
bool UpdateValue() override {
|
||||
SetValueIsValid(false);
|
||||
m_value.Clear();
|
||||
ValueObject *parent = GetParent();
|
||||
if (!parent) {
|
||||
m_error.SetErrorString("owning vtable object not valid");
|
||||
return false;
|
||||
}
|
||||
|
||||
addr_t parent_addr = parent->GetValueAsUnsigned(LLDB_INVALID_ADDRESS);
|
||||
if (parent_addr == LLDB_INVALID_ADDRESS) {
|
||||
m_error.SetErrorString("invalid vtable address");
|
||||
return false;
|
||||
}
|
||||
|
||||
ProcessSP process_sp = GetProcessSP();
|
||||
if (!process_sp) {
|
||||
m_error.SetErrorString("no process");
|
||||
return false;
|
||||
}
|
||||
|
||||
TargetSP target_sp = GetTargetSP();
|
||||
if (!target_sp) {
|
||||
m_error.SetErrorString("no target");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Each `vtable_entry_addr` points to the function pointer.
|
||||
addr_t vtable_entry_addr = parent_addr + m_func_idx * m_addr_size;
|
||||
addr_t vfunc_ptr =
|
||||
process_sp->ReadPointerFromMemory(vtable_entry_addr, m_error);
|
||||
if (m_error.Fail()) {
|
||||
m_error.SetErrorStringWithFormat(
|
||||
"failed to read virtual function entry 0x%16.16" PRIx64,
|
||||
vtable_entry_addr);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Set our value to be the load address of the function pointer in memory
|
||||
// and our type to be the function pointer type.
|
||||
m_value.SetValueType(Value::ValueType::LoadAddress);
|
||||
m_value.GetScalar() = vtable_entry_addr;
|
||||
|
||||
// See if our resolved address points to a function in the debug info. If
|
||||
// it does, then we can report the type as a function prototype for this
|
||||
// function.
|
||||
Function *function = nullptr;
|
||||
Address resolved_vfunc_ptr_address;
|
||||
target_sp->ResolveLoadAddress(vfunc_ptr, resolved_vfunc_ptr_address);
|
||||
if (resolved_vfunc_ptr_address.IsValid())
|
||||
function = resolved_vfunc_ptr_address.CalculateSymbolContextFunction();
|
||||
if (function) {
|
||||
m_value.SetCompilerType(function->GetCompilerType().GetPointerType());
|
||||
} else {
|
||||
// Set our value's compiler type to a generic function protoype so that
|
||||
// it displays as a hex function pointer for the value and the summary
|
||||
// will display the address description.
|
||||
|
||||
// Get the original type that this vtable is based off of so we can get
|
||||
// the language from it correctly.
|
||||
ValueObject *val = parent->GetParent();
|
||||
auto type_system = target_sp->GetScratchTypeSystemForLanguage(
|
||||
val ? val->GetObjectRuntimeLanguage() : eLanguageTypeC_plus_plus);
|
||||
if (type_system) {
|
||||
m_value.SetCompilerType(
|
||||
(*type_system)->CreateGenericFunctionPrototype().GetPointerType());
|
||||
} else {
|
||||
consumeError(type_system.takeError());
|
||||
}
|
||||
}
|
||||
|
||||
// Now read our value into m_data so that our we can use the default
|
||||
// summary provider for C++ for function pointers which will get the
|
||||
// address description for our function pointer.
|
||||
if (m_error.Success()) {
|
||||
const bool thread_and_frame_only_if_stopped = true;
|
||||
ExecutionContext exe_ctx(
|
||||
GetExecutionContextRef().Lock(thread_and_frame_only_if_stopped));
|
||||
m_error = m_value.GetValueAsData(&exe_ctx, m_data, GetModule().get());
|
||||
}
|
||||
SetValueDidChange(true);
|
||||
SetValueIsValid(true);
|
||||
return true;
|
||||
};
|
||||
|
||||
CompilerType GetCompilerTypeImpl() override {
|
||||
return m_value.GetCompilerType();
|
||||
};
|
||||
|
||||
const uint32_t m_func_idx;
|
||||
const uint64_t m_addr_size;
|
||||
|
||||
private:
|
||||
// For ValueObject only
|
||||
ValueObjectVTableChild(const ValueObjectVTableChild &) = delete;
|
||||
const ValueObjectVTableChild &
|
||||
operator=(const ValueObjectVTableChild &) = delete;
|
||||
};
|
||||
|
||||
ValueObjectSP ValueObjectVTable::Create(ValueObject &parent) {
|
||||
return (new ValueObjectVTable(parent))->GetSP();
|
||||
}
|
||||
|
||||
ValueObjectVTable::ValueObjectVTable(ValueObject &parent)
|
||||
: ValueObject(parent) {
|
||||
SetFormat(eFormatPointer);
|
||||
}
|
||||
|
||||
std::optional<uint64_t> ValueObjectVTable::GetByteSize() {
|
||||
if (m_vtable_symbol)
|
||||
return m_vtable_symbol->GetByteSize();
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
size_t ValueObjectVTable::CalculateNumChildren(uint32_t max) {
|
||||
if (UpdateValueIfNeeded(false))
|
||||
return m_num_vtable_entries <= max ? m_num_vtable_entries : max;
|
||||
return 0;
|
||||
}
|
||||
|
||||
ValueType ValueObjectVTable::GetValueType() const { return eValueTypeVTable; }
|
||||
|
||||
ConstString ValueObjectVTable::GetTypeName() {
|
||||
if (m_vtable_symbol)
|
||||
return m_vtable_symbol->GetName();
|
||||
return ConstString();
|
||||
}
|
||||
|
||||
ConstString ValueObjectVTable::GetQualifiedTypeName() { return GetTypeName(); }
|
||||
|
||||
ConstString ValueObjectVTable::GetDisplayTypeName() {
|
||||
if (m_vtable_symbol)
|
||||
return m_vtable_symbol->GetDisplayName();
|
||||
return ConstString();
|
||||
}
|
||||
|
||||
bool ValueObjectVTable::IsInScope() { return GetParent()->IsInScope(); }
|
||||
|
||||
ValueObject *ValueObjectVTable::CreateChildAtIndex(size_t idx,
|
||||
bool synthetic_array_member,
|
||||
int32_t synthetic_index) {
|
||||
if (synthetic_array_member)
|
||||
return nullptr;
|
||||
return new ValueObjectVTableChild(*this, idx, m_addr_size);
|
||||
}
|
||||
|
||||
bool ValueObjectVTable::UpdateValue() {
|
||||
m_error.Clear();
|
||||
m_flags.m_children_count_valid = false;
|
||||
SetValueIsValid(false);
|
||||
m_num_vtable_entries = 0;
|
||||
ValueObject *parent = GetParent();
|
||||
if (!parent) {
|
||||
m_error.SetErrorString("no parent object");
|
||||
return false;
|
||||
}
|
||||
|
||||
ProcessSP process_sp = GetProcessSP();
|
||||
if (!process_sp) {
|
||||
m_error.SetErrorString("no process");
|
||||
return false;
|
||||
}
|
||||
|
||||
const LanguageType language = parent->GetObjectRuntimeLanguage();
|
||||
LanguageRuntime *language_runtime = process_sp->GetLanguageRuntime(language);
|
||||
|
||||
if (language_runtime == nullptr) {
|
||||
m_error.SetErrorStringWithFormat(
|
||||
"no language runtime support for the language \"%s\"",
|
||||
Language::GetNameForLanguageType(language));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the vtable information from the language runtime.
|
||||
llvm::Expected<LanguageRuntime::VTableInfo> vtable_info_or_err =
|
||||
language_runtime->GetVTableInfo(*parent, /*check_type=*/true);
|
||||
if (!vtable_info_or_err) {
|
||||
m_error = vtable_info_or_err.takeError();
|
||||
return false;
|
||||
}
|
||||
|
||||
TargetSP target_sp = GetTargetSP();
|
||||
const addr_t vtable_start_addr =
|
||||
vtable_info_or_err->addr.GetLoadAddress(target_sp.get());
|
||||
|
||||
m_vtable_symbol = vtable_info_or_err->symbol;
|
||||
if (!m_vtable_symbol) {
|
||||
m_error.SetErrorStringWithFormat(
|
||||
"no vtable symbol found containing 0x%" PRIx64, vtable_start_addr);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Now that we know it's a vtable, we update the object's state.
|
||||
SetName(GetTypeName());
|
||||
|
||||
// Calculate the number of entries
|
||||
if (!m_vtable_symbol->GetByteSizeIsValid()) {
|
||||
m_error.SetErrorStringWithFormat(
|
||||
"vtable symbol \"%s\" doesn't have a valid size",
|
||||
m_vtable_symbol->GetMangled().GetDemangledName().GetCString());
|
||||
return false;
|
||||
}
|
||||
|
||||
m_addr_size = process_sp->GetAddressByteSize();
|
||||
const addr_t vtable_end_addr =
|
||||
m_vtable_symbol->GetLoadAddress(target_sp.get()) +
|
||||
m_vtable_symbol->GetByteSize();
|
||||
m_num_vtable_entries = (vtable_end_addr - vtable_start_addr) / m_addr_size;
|
||||
|
||||
m_value.SetValueType(Value::ValueType::LoadAddress);
|
||||
m_value.GetScalar() = parent->GetAddressOf();
|
||||
auto type_system_or_err =
|
||||
target_sp->GetScratchTypeSystemForLanguage(eLanguageTypeC_plus_plus);
|
||||
if (type_system_or_err) {
|
||||
m_value.SetCompilerType(
|
||||
(*type_system_or_err)->GetBasicTypeFromAST(eBasicTypeUnsignedLong));
|
||||
} else {
|
||||
consumeError(type_system_or_err.takeError());
|
||||
}
|
||||
SetValueDidChange(true);
|
||||
SetValueIsValid(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
CompilerType ValueObjectVTable::GetCompilerTypeImpl() { return CompilerType(); }
|
||||
|
||||
ValueObjectVTable::~ValueObjectVTable() = default;
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "lldb/Target/SectionLoadList.h"
|
||||
#include "lldb/Target/Target.h"
|
||||
#include "lldb/Utility/Stream.h"
|
||||
#include "lldb/lldb-enumerations.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
@@ -76,7 +77,10 @@ bool lldb_private::formatters::CXXFunctionPointerSummaryProvider(
|
||||
}
|
||||
}
|
||||
if (sstr.GetSize() > 0) {
|
||||
stream.Printf("(%s)", sstr.GetData());
|
||||
if (valobj.GetValueType() == lldb::eValueTypeVTableEntry)
|
||||
stream.PutCString(sstr.GetData());
|
||||
else
|
||||
stream.Printf("(%s)", sstr.GetData());
|
||||
return true;
|
||||
} else
|
||||
return false;
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
#include "LibCxxVariant.h"
|
||||
#include "LibStdcpp.h"
|
||||
#include "MSVCUndecoratedNameParser.h"
|
||||
#include "lldb/lldb-enumerations.h"
|
||||
|
||||
using namespace lldb;
|
||||
using namespace lldb_private;
|
||||
@@ -1415,7 +1416,8 @@ CPlusPlusLanguage::GetHardcodedSummaries() {
|
||||
lldb_private::formatters::CXXFunctionPointerSummaryProvider,
|
||||
"Function pointer summary provider"));
|
||||
if (CompilerType CT = valobj.GetCompilerType();
|
||||
CT.IsFunctionPointerType() || CT.IsMemberFunctionPointerType()) {
|
||||
CT.IsFunctionPointerType() || CT.IsMemberFunctionPointerType() ||
|
||||
valobj.GetValueType() == lldb::eValueTypeVTableEntry) {
|
||||
return formatter_sp;
|
||||
}
|
||||
return nullptr;
|
||||
|
||||
@@ -54,134 +54,228 @@ bool ItaniumABILanguageRuntime::CouldHaveDynamicValue(ValueObject &in_value) {
|
||||
check_objc);
|
||||
}
|
||||
|
||||
TypeAndOrName ItaniumABILanguageRuntime::GetTypeInfoFromVTableAddress(
|
||||
ValueObject &in_value, lldb::addr_t original_ptr,
|
||||
lldb::addr_t vtable_load_addr) {
|
||||
if (m_process && vtable_load_addr != LLDB_INVALID_ADDRESS) {
|
||||
// Find the symbol that contains the "vtable_load_addr" address
|
||||
Address vtable_addr;
|
||||
Target &target = m_process->GetTarget();
|
||||
if (!target.GetSectionLoadList().IsEmpty()) {
|
||||
if (target.GetSectionLoadList().ResolveLoadAddress(vtable_load_addr,
|
||||
vtable_addr)) {
|
||||
// See if we have cached info for this type already
|
||||
TypeAndOrName type_info = GetDynamicTypeInfo(vtable_addr);
|
||||
if (type_info)
|
||||
return type_info;
|
||||
TypeAndOrName ItaniumABILanguageRuntime::GetTypeInfo(
|
||||
ValueObject &in_value, const VTableInfo &vtable_info) {
|
||||
if (vtable_info.addr.IsSectionOffset()) {
|
||||
// See if we have cached info for this type already
|
||||
TypeAndOrName type_info = GetDynamicTypeInfo(vtable_info.addr);
|
||||
if (type_info)
|
||||
return type_info;
|
||||
|
||||
SymbolContext sc;
|
||||
target.GetImages().ResolveSymbolContextForAddress(
|
||||
vtable_addr, eSymbolContextSymbol, sc);
|
||||
Symbol *symbol = sc.symbol;
|
||||
if (symbol != nullptr) {
|
||||
const char *name =
|
||||
symbol->GetMangled().GetDemangledName().AsCString();
|
||||
if (name && strstr(name, vtable_demangled_prefix) == name) {
|
||||
Log *log = GetLog(LLDBLog::Object);
|
||||
LLDB_LOGF(log,
|
||||
"0x%16.16" PRIx64
|
||||
": static-type = '%s' has vtable symbol '%s'\n",
|
||||
original_ptr, in_value.GetTypeName().GetCString(), name);
|
||||
// We are a C++ class, that's good. Get the class name and look it
|
||||
// up:
|
||||
const char *class_name = name + strlen(vtable_demangled_prefix);
|
||||
// We know the class name is absolute, so tell FindTypes that by
|
||||
// prefixing it with the root namespace:
|
||||
std::string lookup_name("::");
|
||||
lookup_name.append(class_name);
|
||||
|
||||
type_info.SetName(class_name);
|
||||
const bool exact_match = true;
|
||||
TypeList class_types;
|
||||
if (vtable_info.symbol) {
|
||||
Log *log = GetLog(LLDBLog::Object);
|
||||
llvm::StringRef symbol_name =
|
||||
vtable_info.symbol->GetMangled().GetDemangledName().GetStringRef();
|
||||
LLDB_LOGF(log,
|
||||
"0x%16.16" PRIx64
|
||||
": static-type = '%s' has vtable symbol '%s'\n",
|
||||
in_value.GetPointerValue(),
|
||||
in_value.GetTypeName().GetCString(),
|
||||
symbol_name.str().c_str());
|
||||
// We are a C++ class, that's good. Get the class name and look it
|
||||
// up:
|
||||
llvm::StringRef class_name = symbol_name;
|
||||
class_name.consume_front(vtable_demangled_prefix);
|
||||
// We know the class name is absolute, so tell FindTypes that by
|
||||
// prefixing it with the root namespace:
|
||||
std::string lookup_name("::");
|
||||
lookup_name.append(class_name.data(), class_name.size());
|
||||
|
||||
// First look in the module that the vtable symbol came from and
|
||||
// look for a single exact match.
|
||||
llvm::DenseSet<SymbolFile *> searched_symbol_files;
|
||||
if (sc.module_sp)
|
||||
sc.module_sp->FindTypes(ConstString(lookup_name), exact_match, 1,
|
||||
type_info.SetName(class_name);
|
||||
const bool exact_match = true;
|
||||
TypeList class_types;
|
||||
|
||||
// First look in the module that the vtable symbol came from and
|
||||
// look for a single exact match.
|
||||
llvm::DenseSet<SymbolFile *> searched_symbol_files;
|
||||
ModuleSP module_sp = vtable_info.symbol->CalculateSymbolContextModule();
|
||||
if (module_sp)
|
||||
module_sp->FindTypes(ConstString(lookup_name), exact_match, 1,
|
||||
searched_symbol_files, class_types);
|
||||
|
||||
// If we didn't find a symbol, then move on to the entire module
|
||||
// list in the target and get as many unique matches as possible
|
||||
Target &target = m_process->GetTarget();
|
||||
if (class_types.Empty())
|
||||
target.GetImages().FindTypes(nullptr, ConstString(lookup_name),
|
||||
exact_match, UINT32_MAX,
|
||||
searched_symbol_files, class_types);
|
||||
|
||||
// If we didn't find a symbol, then move on to the entire module
|
||||
// list in the target and get as many unique matches as possible
|
||||
if (class_types.Empty())
|
||||
target.GetImages().FindTypes(nullptr, ConstString(lookup_name),
|
||||
exact_match, UINT32_MAX,
|
||||
searched_symbol_files, class_types);
|
||||
|
||||
lldb::TypeSP type_sp;
|
||||
if (class_types.Empty()) {
|
||||
LLDB_LOGF(log, "0x%16.16" PRIx64 ": is not dynamic\n",
|
||||
original_ptr);
|
||||
return TypeAndOrName();
|
||||
}
|
||||
if (class_types.GetSize() == 1) {
|
||||
type_sp = class_types.GetTypeAtIndex(0);
|
||||
if (type_sp) {
|
||||
if (TypeSystemClang::IsCXXClassType(
|
||||
type_sp->GetForwardCompilerType())) {
|
||||
LLDB_LOGF(
|
||||
log,
|
||||
"0x%16.16" PRIx64
|
||||
": static-type = '%s' has dynamic type: uid={0x%" PRIx64
|
||||
"}, type-name='%s'\n",
|
||||
original_ptr, in_value.GetTypeName().AsCString(),
|
||||
type_sp->GetID(), type_sp->GetName().GetCString());
|
||||
type_info.SetTypeSP(type_sp);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
size_t i;
|
||||
if (log) {
|
||||
for (i = 0; i < class_types.GetSize(); i++) {
|
||||
type_sp = class_types.GetTypeAtIndex(i);
|
||||
if (type_sp) {
|
||||
LLDB_LOGF(
|
||||
log,
|
||||
"0x%16.16" PRIx64
|
||||
": static-type = '%s' has multiple matching dynamic "
|
||||
"types: uid={0x%" PRIx64 "}, type-name='%s'\n",
|
||||
original_ptr, in_value.GetTypeName().AsCString(),
|
||||
type_sp->GetID(), type_sp->GetName().GetCString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < class_types.GetSize(); i++) {
|
||||
type_sp = class_types.GetTypeAtIndex(i);
|
||||
if (type_sp) {
|
||||
if (TypeSystemClang::IsCXXClassType(
|
||||
type_sp->GetForwardCompilerType())) {
|
||||
LLDB_LOGF(
|
||||
log,
|
||||
"0x%16.16" PRIx64 ": static-type = '%s' has multiple "
|
||||
"matching dynamic types, picking "
|
||||
"this one: uid={0x%" PRIx64 "}, type-name='%s'\n",
|
||||
original_ptr, in_value.GetTypeName().AsCString(),
|
||||
type_sp->GetID(), type_sp->GetName().GetCString());
|
||||
type_info.SetTypeSP(type_sp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (log) {
|
||||
LLDB_LOGF(log,
|
||||
"0x%16.16" PRIx64
|
||||
": static-type = '%s' has multiple matching dynamic "
|
||||
"types, didn't find a C++ match\n",
|
||||
original_ptr, in_value.GetTypeName().AsCString());
|
||||
}
|
||||
}
|
||||
if (type_info)
|
||||
SetDynamicTypeInfo(vtable_addr, type_info);
|
||||
return type_info;
|
||||
lldb::TypeSP type_sp;
|
||||
if (class_types.Empty()) {
|
||||
LLDB_LOGF(log, "0x%16.16" PRIx64 ": is not dynamic\n",
|
||||
in_value.GetPointerValue());
|
||||
return TypeAndOrName();
|
||||
}
|
||||
if (class_types.GetSize() == 1) {
|
||||
type_sp = class_types.GetTypeAtIndex(0);
|
||||
if (type_sp) {
|
||||
if (TypeSystemClang::IsCXXClassType(
|
||||
type_sp->GetForwardCompilerType())) {
|
||||
LLDB_LOGF(
|
||||
log,
|
||||
"0x%16.16" PRIx64
|
||||
": static-type = '%s' has dynamic type: uid={0x%" PRIx64
|
||||
"}, type-name='%s'\n",
|
||||
in_value.GetPointerValue(), in_value.GetTypeName().AsCString(),
|
||||
type_sp->GetID(), type_sp->GetName().GetCString());
|
||||
type_info.SetTypeSP(type_sp);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
size_t i;
|
||||
if (log) {
|
||||
for (i = 0; i < class_types.GetSize(); i++) {
|
||||
type_sp = class_types.GetTypeAtIndex(i);
|
||||
if (type_sp) {
|
||||
LLDB_LOGF(
|
||||
log,
|
||||
"0x%16.16" PRIx64
|
||||
": static-type = '%s' has multiple matching dynamic "
|
||||
"types: uid={0x%" PRIx64 "}, type-name='%s'\n",
|
||||
in_value.GetPointerValue(),
|
||||
in_value.GetTypeName().AsCString(),
|
||||
type_sp->GetID(), type_sp->GetName().GetCString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < class_types.GetSize(); i++) {
|
||||
type_sp = class_types.GetTypeAtIndex(i);
|
||||
if (type_sp) {
|
||||
if (TypeSystemClang::IsCXXClassType(
|
||||
type_sp->GetForwardCompilerType())) {
|
||||
LLDB_LOGF(
|
||||
log,
|
||||
"0x%16.16" PRIx64 ": static-type = '%s' has multiple "
|
||||
"matching dynamic types, picking "
|
||||
"this one: uid={0x%" PRIx64 "}, type-name='%s'\n",
|
||||
in_value.GetPointerValue(),
|
||||
in_value.GetTypeName().AsCString(),
|
||||
type_sp->GetID(), type_sp->GetName().GetCString());
|
||||
type_info.SetTypeSP(type_sp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (log) {
|
||||
LLDB_LOGF(log,
|
||||
"0x%16.16" PRIx64
|
||||
": static-type = '%s' has multiple matching dynamic "
|
||||
"types, didn't find a C++ match\n",
|
||||
in_value.GetPointerValue(),
|
||||
in_value.GetTypeName().AsCString());
|
||||
}
|
||||
}
|
||||
if (type_info)
|
||||
SetDynamicTypeInfo(vtable_info.addr, type_info);
|
||||
return type_info;
|
||||
}
|
||||
}
|
||||
return TypeAndOrName();
|
||||
}
|
||||
|
||||
llvm::Error ItaniumABILanguageRuntime::TypeHasVTable(CompilerType type) {
|
||||
// Check to make sure the class has a vtable.
|
||||
CompilerType original_type = type;
|
||||
if (type.IsPointerOrReferenceType()) {
|
||||
CompilerType pointee_type = type.GetPointeeType();
|
||||
if (pointee_type)
|
||||
type = pointee_type;
|
||||
}
|
||||
|
||||
// Make sure this is a class or a struct first by checking the type class
|
||||
// bitfield that gets returned.
|
||||
if ((type.GetTypeClass() & (eTypeClassStruct | eTypeClassClass)) == 0) {
|
||||
return llvm::createStringError(std::errc::invalid_argument,
|
||||
"type \"%s\" is not a class or struct or a pointer to one",
|
||||
original_type.GetTypeName().AsCString("<invalid>"));
|
||||
}
|
||||
|
||||
// Check if the type has virtual functions by asking it if it is polymorphic.
|
||||
if (!type.IsPolymorphicClass()) {
|
||||
return llvm::createStringError(std::errc::invalid_argument,
|
||||
"type \"%s\" doesn't have a vtable",
|
||||
type.GetTypeName().AsCString("<invalid>"));
|
||||
}
|
||||
return llvm::Error::success();
|
||||
}
|
||||
|
||||
// This function can accept both pointers or references to classes as well as
|
||||
// instances of classes. If you are using this function during dynamic type
|
||||
// detection, only valid ValueObjects that return true to
|
||||
// CouldHaveDynamicValue(...) should call this function and \a check_type
|
||||
// should be set to false. This function is also used by ValueObjectVTable
|
||||
// and is can pass in instances of classes which is not suitable for dynamic
|
||||
// type detection, these cases should pass true for \a check_type.
|
||||
llvm::Expected<LanguageRuntime::VTableInfo>
|
||||
ItaniumABILanguageRuntime::GetVTableInfo(ValueObject &in_value,
|
||||
bool check_type) {
|
||||
|
||||
CompilerType type = in_value.GetCompilerType();
|
||||
if (check_type) {
|
||||
if (llvm::Error err = TypeHasVTable(type))
|
||||
return std::move(err);
|
||||
}
|
||||
ExecutionContext exe_ctx(in_value.GetExecutionContextRef());
|
||||
Process *process = exe_ctx.GetProcessPtr();
|
||||
if (process == nullptr)
|
||||
return llvm::createStringError(std::errc::invalid_argument,
|
||||
"invalid process");
|
||||
|
||||
AddressType address_type;
|
||||
lldb::addr_t original_ptr = LLDB_INVALID_ADDRESS;
|
||||
if (type.IsPointerOrReferenceType())
|
||||
original_ptr = in_value.GetPointerValue(&address_type);
|
||||
else
|
||||
original_ptr = in_value.GetAddressOf(/*scalar_is_load_address=*/true,
|
||||
&address_type);
|
||||
if (original_ptr == LLDB_INVALID_ADDRESS || address_type != eAddressTypeLoad)
|
||||
return llvm::createStringError(std::errc::invalid_argument,
|
||||
"failed to get the address of the value");
|
||||
|
||||
Status error;
|
||||
const lldb::addr_t vtable_load_addr =
|
||||
process->ReadPointerFromMemory(original_ptr, error);
|
||||
|
||||
if (!error.Success() || vtable_load_addr == LLDB_INVALID_ADDRESS)
|
||||
return llvm::createStringError(std::errc::invalid_argument,
|
||||
"failed to read vtable pointer from memory at 0x%" PRIx64,
|
||||
original_ptr);
|
||||
;
|
||||
|
||||
// Find the symbol that contains the "vtable_load_addr" address
|
||||
Address vtable_addr;
|
||||
if (!process->GetTarget().ResolveLoadAddress(vtable_load_addr, vtable_addr))
|
||||
return llvm::createStringError(std::errc::invalid_argument,
|
||||
"failed to resolve vtable pointer 0x%"
|
||||
PRIx64 "to a section", vtable_load_addr);
|
||||
|
||||
// Check our cache first to see if we already have this info
|
||||
{
|
||||
std::lock_guard<std::mutex> locker(m_mutex);
|
||||
auto pos = m_vtable_info_map.find(vtable_addr);
|
||||
if (pos != m_vtable_info_map.end())
|
||||
return pos->second;
|
||||
}
|
||||
|
||||
Symbol *symbol = vtable_addr.CalculateSymbolContextSymbol();
|
||||
if (symbol == nullptr)
|
||||
return llvm::createStringError(std::errc::invalid_argument,
|
||||
"no symbol found for 0x%" PRIx64,
|
||||
vtable_load_addr);
|
||||
llvm::StringRef name = symbol->GetMangled().GetDemangledName().GetStringRef();
|
||||
if (name.startswith(vtable_demangled_prefix)) {
|
||||
VTableInfo info = {vtable_addr, symbol};
|
||||
std::lock_guard<std::mutex> locker(m_mutex);
|
||||
auto pos = m_vtable_info_map[vtable_addr] = info;
|
||||
return info;
|
||||
}
|
||||
return llvm::createStringError(std::errc::invalid_argument,
|
||||
"symbol found that contains 0x%" PRIx64 " is not a vtable symbol",
|
||||
vtable_load_addr);
|
||||
}
|
||||
|
||||
bool ItaniumABILanguageRuntime::GetDynamicTypeAndAddress(
|
||||
ValueObject &in_value, lldb::DynamicValueType use_dynamic,
|
||||
TypeAndOrName &class_type_or_name, Address &dynamic_address,
|
||||
@@ -198,33 +292,23 @@ bool ItaniumABILanguageRuntime::GetDynamicTypeAndAddress(
|
||||
class_type_or_name.Clear();
|
||||
value_type = Value::ValueType::Scalar;
|
||||
|
||||
// Only a pointer or reference type can have a different dynamic and static
|
||||
// type:
|
||||
if (!CouldHaveDynamicValue(in_value))
|
||||
return false;
|
||||
|
||||
// First job, pull out the address at 0 offset from the object.
|
||||
AddressType address_type;
|
||||
lldb::addr_t original_ptr = in_value.GetPointerValue(&address_type);
|
||||
if (original_ptr == LLDB_INVALID_ADDRESS)
|
||||
// Check if we have a vtable pointer in this value. If we don't it will
|
||||
// return an error, else it will return a valid resolved address. We don't
|
||||
// want GetVTableInfo to check the type since we accept void * as a possible
|
||||
// dynamic type and that won't pass the type check. We already checked the
|
||||
// type above in CouldHaveDynamicValue(...).
|
||||
llvm::Expected<VTableInfo> vtable_info_or_err =
|
||||
GetVTableInfo(in_value, /*check_type=*/false);
|
||||
if (!vtable_info_or_err) {
|
||||
llvm::consumeError(vtable_info_or_err.takeError());
|
||||
return false;
|
||||
}
|
||||
|
||||
ExecutionContext exe_ctx(in_value.GetExecutionContextRef());
|
||||
|
||||
Process *process = exe_ctx.GetProcessPtr();
|
||||
|
||||
if (process == nullptr)
|
||||
return false;
|
||||
|
||||
Status error;
|
||||
const lldb::addr_t vtable_address_point =
|
||||
process->ReadPointerFromMemory(original_ptr, error);
|
||||
|
||||
if (!error.Success() || vtable_address_point == LLDB_INVALID_ADDRESS)
|
||||
return false;
|
||||
|
||||
class_type_or_name = GetTypeInfoFromVTableAddress(in_value, original_ptr,
|
||||
vtable_address_point);
|
||||
const VTableInfo &vtable_info = vtable_info_or_err.get();
|
||||
class_type_or_name = GetTypeInfo(in_value, vtable_info);
|
||||
|
||||
if (!class_type_or_name)
|
||||
return false;
|
||||
@@ -244,22 +328,27 @@ bool ItaniumABILanguageRuntime::GetDynamicTypeAndAddress(
|
||||
}
|
||||
|
||||
// The offset_to_top is two pointers above the vtable pointer.
|
||||
const uint32_t addr_byte_size = process->GetAddressByteSize();
|
||||
const lldb::addr_t offset_to_top_location =
|
||||
vtable_address_point - 2 * addr_byte_size;
|
||||
// Watch for underflow, offset_to_top_location should be less than
|
||||
// vtable_address_point
|
||||
if (offset_to_top_location >= vtable_address_point)
|
||||
Target &target = m_process->GetTarget();
|
||||
const addr_t vtable_load_addr = vtable_info.addr.GetLoadAddress(&target);
|
||||
if (vtable_load_addr == LLDB_INVALID_ADDRESS)
|
||||
return false;
|
||||
const int64_t offset_to_top = process->ReadSignedIntegerFromMemory(
|
||||
const uint32_t addr_byte_size = m_process->GetAddressByteSize();
|
||||
const lldb::addr_t offset_to_top_location =
|
||||
vtable_load_addr - 2 * addr_byte_size;
|
||||
// Watch for underflow, offset_to_top_location should be less than
|
||||
// vtable_load_addr
|
||||
if (offset_to_top_location >= vtable_load_addr)
|
||||
return false;
|
||||
Status error;
|
||||
const int64_t offset_to_top = m_process->ReadSignedIntegerFromMemory(
|
||||
offset_to_top_location, addr_byte_size, INT64_MIN, error);
|
||||
|
||||
if (offset_to_top == INT64_MIN)
|
||||
return false;
|
||||
// So the dynamic type is a value that starts at offset_to_top above
|
||||
// the original address.
|
||||
lldb::addr_t dynamic_addr = original_ptr + offset_to_top;
|
||||
if (!process->GetTarget().GetSectionLoadList().ResolveLoadAddress(
|
||||
lldb::addr_t dynamic_addr = in_value.GetPointerValue() + offset_to_top;
|
||||
if (!m_process->GetTarget().ResolveLoadAddress(
|
||||
dynamic_addr, dynamic_address)) {
|
||||
dynamic_address.SetRawAddress(dynamic_addr);
|
||||
}
|
||||
@@ -582,10 +671,10 @@ ValueObjectSP ItaniumABILanguageRuntime::GetExceptionObjectForThread(
|
||||
ValueObjectSP exception = ValueObject::CreateValueObjectFromData(
|
||||
"exception", exception_isw.GetAsData(m_process->GetByteOrder()), exe_ctx,
|
||||
voidstar);
|
||||
ValueObjectSP dyn_exception
|
||||
ValueObjectSP dyn_exception
|
||||
= exception->GetDynamicValue(eDynamicDontRunTarget);
|
||||
// If we succeed in making a dynamic value, return that:
|
||||
if (dyn_exception)
|
||||
if (dyn_exception)
|
||||
return dyn_exception;
|
||||
|
||||
return exception;
|
||||
@@ -593,7 +682,7 @@ ValueObjectSP ItaniumABILanguageRuntime::GetExceptionObjectForThread(
|
||||
|
||||
TypeAndOrName ItaniumABILanguageRuntime::GetDynamicTypeInfo(
|
||||
const lldb_private::Address &vtable_addr) {
|
||||
std::lock_guard<std::mutex> locker(m_dynamic_type_map_mutex);
|
||||
std::lock_guard<std::mutex> locker(m_mutex);
|
||||
DynamicTypeCache::const_iterator pos = m_dynamic_type_map.find(vtable_addr);
|
||||
if (pos == m_dynamic_type_map.end())
|
||||
return TypeAndOrName();
|
||||
@@ -603,6 +692,6 @@ TypeAndOrName ItaniumABILanguageRuntime::GetDynamicTypeInfo(
|
||||
|
||||
void ItaniumABILanguageRuntime::SetDynamicTypeInfo(
|
||||
const lldb_private::Address &vtable_addr, const TypeAndOrName &type_info) {
|
||||
std::lock_guard<std::mutex> locker(m_dynamic_type_map_mutex);
|
||||
std::lock_guard<std::mutex> locker(m_mutex);
|
||||
m_dynamic_type_map[vtable_addr] = type_info;
|
||||
}
|
||||
|
||||
@@ -47,6 +47,10 @@ public:
|
||||
return runtime->isA(&ID);
|
||||
}
|
||||
|
||||
|
||||
llvm::Expected<LanguageRuntime::VTableInfo>
|
||||
GetVTableInfo(ValueObject &in_value, bool check_type) override;
|
||||
|
||||
bool GetDynamicTypeAndAddress(ValueObject &in_value,
|
||||
lldb::DynamicValueType use_dynamic,
|
||||
TypeAndOrName &class_type_or_name,
|
||||
@@ -71,7 +75,7 @@ public:
|
||||
bool catch_bp, bool throw_bp) override;
|
||||
|
||||
lldb::SearchFilterSP CreateExceptionSearchFilter() override;
|
||||
|
||||
|
||||
lldb::ValueObjectSP GetExceptionObjectForThread(
|
||||
lldb::ThreadSP thread_sp) override;
|
||||
|
||||
@@ -89,24 +93,33 @@ protected:
|
||||
|
||||
private:
|
||||
typedef std::map<lldb_private::Address, TypeAndOrName> DynamicTypeCache;
|
||||
typedef std::map<lldb_private::Address, VTableInfo> VTableInfoCache;
|
||||
|
||||
ItaniumABILanguageRuntime(Process *process)
|
||||
: // Call CreateInstance instead.
|
||||
lldb_private::CPPLanguageRuntime(process), m_cxx_exception_bp_sp(),
|
||||
m_dynamic_type_map(), m_dynamic_type_map_mutex() {}
|
||||
lldb_private::CPPLanguageRuntime(process) {}
|
||||
|
||||
lldb::BreakpointSP m_cxx_exception_bp_sp;
|
||||
DynamicTypeCache m_dynamic_type_map;
|
||||
std::mutex m_dynamic_type_map_mutex;
|
||||
VTableInfoCache m_vtable_info_map;
|
||||
std::mutex m_mutex;
|
||||
|
||||
TypeAndOrName GetTypeInfoFromVTableAddress(ValueObject &in_value,
|
||||
lldb::addr_t original_ptr,
|
||||
lldb::addr_t vtable_addr);
|
||||
TypeAndOrName GetTypeInfo(ValueObject &in_value,
|
||||
const VTableInfo &vtable_info);
|
||||
|
||||
TypeAndOrName GetDynamicTypeInfo(const lldb_private::Address &vtable_addr);
|
||||
|
||||
void SetDynamicTypeInfo(const lldb_private::Address &vtable_addr,
|
||||
const TypeAndOrName &type_info);
|
||||
|
||||
// Check if a compiler type has a vtable.
|
||||
//
|
||||
// If the compiler type is a pointer or a reference, this function will check
|
||||
// if the pointee type has a vtable, else it will check the type passed in.
|
||||
//
|
||||
// Returns an error if the type of the value doesn't have a vtable with an
|
||||
// explanation why, or returns an Error::success() if the type has a vtable.
|
||||
llvm::Error TypeHasVTable(CompilerType compiler_type);
|
||||
};
|
||||
|
||||
} // namespace lldb_private
|
||||
|
||||
@@ -3557,8 +3557,15 @@ bool TypeSystemClang::IsPolymorphicClass(lldb::opaque_compiler_type_t type) {
|
||||
if (record_decl) {
|
||||
const clang::CXXRecordDecl *cxx_record_decl =
|
||||
llvm::dyn_cast<clang::CXXRecordDecl>(record_decl);
|
||||
if (cxx_record_decl)
|
||||
return cxx_record_decl->isPolymorphic();
|
||||
if (cxx_record_decl) {
|
||||
// We can't just call is isPolymorphic() here because that just
|
||||
// means the current class has virtual functions, it doesn't check
|
||||
// if any inherited classes have virtual functions. The doc string
|
||||
// in SBType::IsPolymorphicClass() says it is looking for both
|
||||
// if the class has virtual methods or if any bases do, so this
|
||||
// should be more correct.
|
||||
return cxx_record_decl->isDynamicClass();
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -4708,6 +4715,21 @@ TypeSystemClang::GetTypedefedType(lldb::opaque_compiler_type_t type) {
|
||||
CompilerType TypeSystemClang::GetBasicTypeFromAST(lldb::BasicType basic_type) {
|
||||
return TypeSystemClang::GetBasicType(basic_type);
|
||||
}
|
||||
|
||||
CompilerType TypeSystemClang::CreateGenericFunctionPrototype() {
|
||||
clang::ASTContext &ast = getASTContext();
|
||||
const FunctionType::ExtInfo generic_ext_info(
|
||||
/*noReturn=*/false,
|
||||
/*hasRegParm=*/false,
|
||||
/*regParm=*/0,
|
||||
CallingConv::CC_C,
|
||||
/*producesResult=*/false,
|
||||
/*noCallerSavedRegs=*/false,
|
||||
/*NoCfCheck=*/false,
|
||||
/*cmseNSCall=*/false);
|
||||
QualType func_type = ast.getFunctionNoProtoType(ast.VoidTy, generic_ext_info);
|
||||
return GetType(func_type);
|
||||
}
|
||||
// Exploring the type
|
||||
|
||||
const llvm::fltSemantics &
|
||||
@@ -4824,7 +4846,7 @@ lldb::Encoding TypeSystemClang::GetEncoding(lldb::opaque_compiler_type_t type,
|
||||
|
||||
case clang::Type::FunctionNoProto:
|
||||
case clang::Type::FunctionProto:
|
||||
break;
|
||||
return lldb::eEncodingUint;
|
||||
|
||||
case clang::Type::IncompleteArray:
|
||||
case clang::Type::VariableArray:
|
||||
|
||||
@@ -802,6 +802,10 @@ public:
|
||||
// Create related types using the current type's AST
|
||||
CompilerType GetBasicTypeFromAST(lldb::BasicType basic_type) override;
|
||||
|
||||
// Create a generic function prototype that can be used in ValuObject types
|
||||
// to correctly display a function pointer with the right value and summary.
|
||||
CompilerType CreateGenericFunctionPrototype() override;
|
||||
|
||||
// Exploring the type
|
||||
|
||||
const llvm::fltSemantics &GetFloatTypeSemantics(size_t byte_size) override;
|
||||
|
||||
@@ -748,6 +748,10 @@ void TypeAndOrName::SetName(const char *type_name_cstr) {
|
||||
m_type_name.SetCString(type_name_cstr);
|
||||
}
|
||||
|
||||
void TypeAndOrName::SetName(llvm::StringRef type_name) {
|
||||
m_type_name.SetString(type_name);
|
||||
}
|
||||
|
||||
void TypeAndOrName::SetTypeSP(lldb::TypeSP type_sp) {
|
||||
if (type_sp) {
|
||||
m_compiler_type = type_sp->GetForwardCompilerType();
|
||||
|
||||
3
lldb/test/API/functionalities/vtable/Makefile
Normal file
3
lldb/test/API/functionalities/vtable/Makefile
Normal file
@@ -0,0 +1,3 @@
|
||||
CXX_SOURCES := main.cpp
|
||||
|
||||
include Makefile.rules
|
||||
186
lldb/test/API/functionalities/vtable/TestVTableValue.py
Normal file
186
lldb/test/API/functionalities/vtable/TestVTableValue.py
Normal file
@@ -0,0 +1,186 @@
|
||||
"""
|
||||
Make sure the getting a variable path works and doesn't crash.
|
||||
"""
|
||||
|
||||
|
||||
import lldb
|
||||
import lldbsuite.test.lldbutil as lldbutil
|
||||
from lldbsuite.test.decorators import *
|
||||
from lldbsuite.test.lldbtest import *
|
||||
|
||||
class TestVTableValue(TestBase):
|
||||
# If your test case doesn't stress debug info, then
|
||||
# set this to true. That way it won't be run once for
|
||||
# each debug info format.
|
||||
NO_DEBUG_INFO_TESTCASE = True
|
||||
|
||||
@skipUnlessPlatform(["linux", "macosx"])
|
||||
def test_vtable(self):
|
||||
self.build()
|
||||
lldbutil.run_to_source_breakpoint(
|
||||
self, "At the end", lldb.SBFileSpec("main.cpp")
|
||||
)
|
||||
|
||||
# Test a shape instance to make sure we get the vtable correctly.
|
||||
shape = self.frame().FindVariable("shape")
|
||||
vtable = shape.GetVTable()
|
||||
self.assertEquals(vtable.GetName(), "vtable for Shape")
|
||||
self.assertEquals(vtable.GetTypeName(), "vtable for Shape")
|
||||
# Make sure we have the right number of virtual functions in our vtable
|
||||
# for the shape class.
|
||||
self.assertEquals(vtable.GetNumChildren(), 4)
|
||||
|
||||
# Verify vtable address
|
||||
vtable_addr = vtable.GetValueAsUnsigned(0)
|
||||
expected_addr = self.expected_vtable_addr(shape)
|
||||
self.assertEquals(vtable_addr, expected_addr)
|
||||
|
||||
for (idx, vtable_entry) in enumerate(vtable.children):
|
||||
self.verify_vtable_entry(vtable_entry, vtable_addr, idx)
|
||||
|
||||
# Test a shape reference to make sure we get the vtable correctly.
|
||||
shape = self.frame().FindVariable("shape_ref")
|
||||
vtable = shape.GetVTable()
|
||||
self.assertEquals(vtable.GetName(), "vtable for Shape")
|
||||
self.assertEquals(vtable.GetTypeName(), "vtable for Shape")
|
||||
# Make sure we have the right number of virtual functions in our vtable
|
||||
# for the shape class.
|
||||
self.assertEquals(vtable.GetNumChildren(), 4)
|
||||
|
||||
# Verify vtable address
|
||||
vtable_addr = vtable.GetValueAsUnsigned(0)
|
||||
expected_addr = self.expected_vtable_addr(shape)
|
||||
self.assertEquals(vtable_addr, expected_addr)
|
||||
|
||||
for (idx, vtable_entry) in enumerate(vtable.children):
|
||||
self.verify_vtable_entry(vtable_entry, vtable_addr, idx)
|
||||
|
||||
|
||||
# Test we get the right vtable for the Rectangle instance.
|
||||
rect = self.frame().FindVariable("rect")
|
||||
vtable = rect.GetVTable()
|
||||
self.assertEquals(vtable.GetName(), "vtable for Rectangle")
|
||||
self.assertEquals(vtable.GetTypeName(), "vtable for Rectangle")
|
||||
|
||||
# Make sure we have the right number of virtual functions in our vtable
|
||||
# with the extra virtual function added by the Rectangle class
|
||||
self.assertEquals(vtable.GetNumChildren(), 5)
|
||||
|
||||
# Verify vtable address
|
||||
vtable_addr = vtable.GetValueAsUnsigned()
|
||||
expected_addr = self.expected_vtable_addr(rect)
|
||||
self.assertEquals(vtable_addr, expected_addr)
|
||||
|
||||
for (idx, vtable_entry) in enumerate(vtable.children):
|
||||
self.verify_vtable_entry(vtable_entry, vtable_addr, idx)
|
||||
|
||||
@skipUnlessPlatform(["linux", "macosx"])
|
||||
def test_base_class_ptr(self):
|
||||
self.build()
|
||||
(target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(
|
||||
self, "Shape is Rectangle", lldb.SBFileSpec("main.cpp")
|
||||
)
|
||||
|
||||
shape = self.frame().FindVariable("shape")
|
||||
rect = self.frame().FindVariable("rect")
|
||||
|
||||
shape_ptr = self.frame().FindVariable("shape_ptr")
|
||||
shape_ptr_vtable = shape_ptr.GetVTable()
|
||||
self.assertEquals(shape_ptr_vtable.GetName(), "vtable for Rectangle")
|
||||
self.assertEquals(shape_ptr_vtable.GetNumChildren(), 5)
|
||||
self.assertEquals(shape_ptr.GetValueAsUnsigned(0),
|
||||
rect.GetLoadAddress())
|
||||
lldbutil.continue_to_source_breakpoint(
|
||||
self, process, "Shape is Shape", lldb.SBFileSpec("main.cpp")
|
||||
)
|
||||
self.assertEquals(shape_ptr.GetValueAsUnsigned(0),
|
||||
shape.GetLoadAddress())
|
||||
self.assertEquals(shape_ptr_vtable.GetNumChildren(), 4)
|
||||
self.assertEquals(shape_ptr_vtable.GetName(), "vtable for Shape")
|
||||
|
||||
@skipUnlessPlatform(["linux", "macosx"])
|
||||
def test_no_vtable(self):
|
||||
self.build()
|
||||
lldbutil.run_to_source_breakpoint(
|
||||
self, "At the end", lldb.SBFileSpec("main.cpp")
|
||||
)
|
||||
|
||||
var = self.frame().FindVariable("not_virtual")
|
||||
self.assertEqual(var.GetVTable().GetError().GetCString(),
|
||||
'type "NotVirtual" doesn\'t have a vtable')
|
||||
|
||||
var = self.frame().FindVariable("argc")
|
||||
self.assertEqual(var.GetVTable().GetError().GetCString(),
|
||||
'no language runtime support for the language "c"')
|
||||
|
||||
@skipUnlessPlatform(["linux", "macosx"])
|
||||
def test_overwrite_vtable(self):
|
||||
self.build()
|
||||
(target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(
|
||||
self, "At the end", lldb.SBFileSpec("main.cpp")
|
||||
)
|
||||
|
||||
# Test a shape instance to make sure we get the vtable correctly.
|
||||
shape = self.frame().FindVariable("shape")
|
||||
vtable = shape.GetVTable()
|
||||
self.assertEquals(vtable.GetName(), "vtable for Shape")
|
||||
self.assertEquals(vtable.GetTypeName(), "vtable for Shape")
|
||||
# Make sure we have the right number of virtual functions in our vtable
|
||||
# for the shape class.
|
||||
self.assertEquals(vtable.GetNumChildren(), 4)
|
||||
|
||||
# Overwrite the first entry in the vtable and make sure we can still
|
||||
# see the bogus value which should have no summary
|
||||
vtable_addr = vtable.GetValueAsUnsigned()
|
||||
data = str("\x01\x01\x01\x01\x01\x01\x01\x01")
|
||||
error = lldb.SBError()
|
||||
process.WriteMemory(vtable_addr, data, error)
|
||||
|
||||
scribbled_child = vtable.GetChildAtIndex(0)
|
||||
self.assertEquals(scribbled_child.GetValueAsUnsigned(0),
|
||||
0x0101010101010101)
|
||||
self.assertEquals(scribbled_child.GetSummary(), None)
|
||||
|
||||
def expected_vtable_addr(self, var: lldb.SBValue) -> int:
|
||||
load_addr = var.GetLoadAddress()
|
||||
read_from_memory_error = lldb.SBError()
|
||||
vtable_addr = self.process().ReadPointerFromMemory(
|
||||
load_addr, read_from_memory_error
|
||||
)
|
||||
self.assertTrue(read_from_memory_error.Success())
|
||||
return vtable_addr
|
||||
|
||||
def expected_vtable_entry_func_ptr(self, vtable_addr: int, idx: int):
|
||||
vtable_entry_addr = vtable_addr + idx * self.process().GetAddressByteSize()
|
||||
read_func_ptr_error = lldb.SBError()
|
||||
func_ptr = self.process().ReadPointerFromMemory(vtable_entry_addr,
|
||||
read_func_ptr_error)
|
||||
self.assertTrue(read_func_ptr_error.Success())
|
||||
return func_ptr
|
||||
|
||||
def verify_vtable_entry(self, vtable_entry: lldb.SBValue, vtable_addr: int,
|
||||
idx: int):
|
||||
"""Verify the vtable entry looks something like:
|
||||
|
||||
(double ()) [0] = 0x0000000100003a10 a.out`Rectangle::Area() at main.cpp:14
|
||||
|
||||
"""
|
||||
# Check function ptr
|
||||
vtable_entry_func_ptr = vtable_entry.GetValueAsUnsigned(0)
|
||||
self.assertEquals(
|
||||
vtable_entry_func_ptr,
|
||||
self.expected_vtable_entry_func_ptr(vtable_addr, idx),
|
||||
)
|
||||
|
||||
sb_addr = self.target().ResolveLoadAddress(vtable_entry_func_ptr)
|
||||
sym_ctx = sb_addr.GetSymbolContext(lldb.eSymbolContextEverything)
|
||||
|
||||
# Make sure the type is the same as the function type
|
||||
func_type = sym_ctx.GetFunction().GetType()
|
||||
if func_type.IsValid():
|
||||
self.assertEquals(vtable_entry.GetType(),
|
||||
func_type.GetPointerType())
|
||||
|
||||
# The summary should be the address description of the function pointer
|
||||
summary = vtable_entry.GetSummary()
|
||||
self.assertEquals(str(sb_addr), summary)
|
||||
38
lldb/test/API/functionalities/vtable/main.cpp
Normal file
38
lldb/test/API/functionalities/vtable/main.cpp
Normal file
@@ -0,0 +1,38 @@
|
||||
class Shape {
|
||||
public:
|
||||
virtual double Area() { return 1.0; }
|
||||
virtual double Perimeter() { return 1.0; }
|
||||
// Note that destructors generate two entries in the vtable: base object
|
||||
// destructor and deleting destructor.
|
||||
virtual ~Shape() = default;
|
||||
};
|
||||
|
||||
class Rectangle : public Shape {
|
||||
public:
|
||||
~Rectangle() override = default;
|
||||
double Area() override { return 2.0; }
|
||||
double Perimeter() override { return 2.0; }
|
||||
virtual void RectangleOnly() {}
|
||||
// This *shouldn't* show up in the vtable.
|
||||
void RectangleSpecific() { return; }
|
||||
};
|
||||
|
||||
// Make a class that looks like it would be virtual because the first ivar is
|
||||
// a virtual class and if we inspect memory at the address of this class it
|
||||
// would appear to be a virtual class. We need to make sure we don't get a
|
||||
// valid vtable from this object.
|
||||
class NotVirtual {
|
||||
Rectangle m_rect;
|
||||
public:
|
||||
NotVirtual() = default;
|
||||
};
|
||||
|
||||
int main(int argc, const char **argv) {
|
||||
Shape shape;
|
||||
Rectangle rect;
|
||||
Shape *shape_ptr = ▭
|
||||
Shape &shape_ref = shape;
|
||||
shape_ptr = &shape; // Shape is Rectangle
|
||||
NotVirtual not_virtual; // Shape is Shape
|
||||
return 0; // At the end
|
||||
}
|
||||
Reference in New Issue
Block a user