[lldb] SBValue::GetVTable() returns the object's address instead of the vtable for pointer and reference values (eNoDynamicValues)
- Dominant language
- LLVM
- Stars
- 40.5k
- Forks
- 18.7k
- PR merge metrics
- PR metrics pending
Description
## Summary
`SBValue::GetVTable()` returns the wrong address when the value it is called on
is a pointer or a reference to a polymorphic object **and** the value was
obtained with `lldb::eNoDynamicValues`. The returned "vtable" is the address of
the object, i.e. the content of the pointer/reference slot, instead of the vtable
pointer stored in the object. Calling it on the object by value is correct, which
makes the by-value case a control.
The defect is invisible with dynamic values enabled: the `ValueObjectDynamicValue`
wrapper normalises a pointer or reference to the referent's address before
`ValueObjectVTable` sees it, so the in-tree test
`lldb/test/API/functionalities/vtable/TestVTableValue.py` — which uses dynamic
values throughout — passes.
## Environment
- Platform: Linux, x86_64 (`x86_64-unknown-linux-gnu`).
- LLDB build: `cmake -G Ninja`, `CMAKE_BUILD_TYPE=Release`,
`LLVM_ENABLE_ASSERTIONS=ON`, `LLVM_ENABLE_PROJECTS="clang;lldb"`,
`LLVM_TARGETS_TO_BUILD=X86`, `LLDB_ENABLE_LIBEDIT=OFF`,
`LLDB_ENABLE_CURSES=OFF`, `LLDB_INCLUDE_TESTS=ON`; linker `lld`; SWIG 4.2;
Python 3.12; host compiler clang 18.
- Test program compiled with the `clang` built in the same tree
(21.1.7 and 22.1.8 respectively).
## Reproducer
There is no command-line surface for this API (`apropos vtable` finds no
command), so the reproducer uses the SB API, as the in-tree test does.
`repro.cpp` — 11 lines:
```cpp
struct Shape {
virtual ~Shape() = default;
virtual int Area() { return 1; }
};
int main() {
Shape shape;
Shape &ref = shape;
Shape *ptr = &shape;
return ref.Area() + ptr->Area(); // break here
}
```
`repro.py`:
```python
import lldb, sys
exe, line = sys.argv[1], int(sys.argv[2])
dbg = lldb.SBDebugger.Create()
dbg.SetAsync(False)
target = dbg.CreateTarget(exe)
target.BreakpointCreateByLocation("repro.cpp", line)
process = target.LaunchSimple(None, None, None)
frame = process.GetSelectedThread().GetSelectedFrame()
for name in ("shape", "ref", "ptr"):
v = frame.FindVariable(name, lldb.eNoDynamicValues)
vt = v.GetVTable()
print("%-6s %-9s load_addr=0x%x GetVTable()->GetValueAsUnsigned=0x%x (%s)"
% (name, v.GetTypeName(), v.GetLoadAddress(), vt.GetValueAsUnsigned(0),
vt.GetError().GetCString() or "no error"))
print("expected vtable address, read out of the object itself: 0x%x"
% frame.EvaluateExpression("*(unsigned long *)&shape").GetValueAsUnsigned(0))
```
Commands (line 10 is the `// break here` line):
```console
$ clang++ -g -O0 -std=c++17 repro.cpp -o repro
$ PYTHONPATH=$(lldb -P) python3 repro.py ./repro 10
```
## Observed
Identical on both releases (addresses from the 22.1.8 run; the 21.1.7 run
produced the same values):
```
shape Shape load_addr=0x7fffffffdfb0 GetVTable()->GetValueAsUnsigned=0x555555557d88 (no error)
ref Shape & load_addr=0x7fffffffdfa8 GetVTable()->GetValueAsUnsigned=0x7fffffffdfb0 (no error)
ptr Shape * load_addr=0x7fffffffdfa0 GetVTable()->GetValueAsUnsigned=0x7fffffffdfb0 (no error)
expected vtable address, read out of the object itself: 0x555555557d88
```
`0x7fffffffdfb0` is `&shape` — the address of the object, which is what both the
reference and the pointer hold. It is not a vtable address. No error is
reported: `GetError()` is success in all three cases, so a caller has nothing to
check.
## Expected
All three should report the object's vtable, `0x555555557d88`:
```
shape … GetVTable()->GetValueAsUnsigned=0x555555557d88
ref … GetVTable()->GetValueAsUnsigned=0x555555557d88
ptr … GetVTable()->GetValueAsUnsigned=0x555555557d88
```
Every value in the reproducer denotes the same object, so `GetVTable()` should
answer the same for all three.
Contributor guide
Research direction
Start with lldb/test/API/functionalities/vtable/TestVTableValue.py and the SBValue::GetVTable path, including ValueObjectVTable. Reproduce the issue with the provided repro.cpp and repro.py using lldb::eNoDynamicValues, then add coverage for by-value, reference, and pointer values. Done means all three return the object's vtable address and the regression test passes.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp, python
- Domain
- devtools
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 58/100