llvm / llvm/llvm-project

[lldb] SBValue::GetVTable() returns the object's address instead of the vtable for pointer and reference values (eNoDynamicValues)

Open
#212,766 1 comment 0 reactions 0 assignees View on GitHub
lldb
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.