[lldb] Add pointer arithmetics for addition and subtraction to DIL (#184652)

This commit is contained in:
Ilia Kuklin 2026-03-17 18:10:29 +05:00 committed by GitHub
parent dc5c6d008f
commit 9f4fbe86a5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 273 additions and 19 deletions

View File

@ -89,6 +89,13 @@ private:
llvm::Expected<CompilerType> ArithmeticConversion(lldb::ValueObjectSP &lhs,
lldb::ValueObjectSP &rhs,
uint32_t location);
/// Add or subtract the offset to the pointer according to the pointee type
/// byte size.
/// \returns A new `ValueObject` with a new pointer value.
llvm::Expected<lldb::ValueObjectSP> PointerOffset(lldb::ValueObjectSP ptr,
lldb::ValueObjectSP offset,
BinaryOpKind operation,
uint32_t location);
llvm::Expected<lldb::ValueObjectSP> EvaluateScalarOp(BinaryOpKind kind,
lldb::ValueObjectSP lhs,
lldb::ValueObjectSP rhs,

View File

@ -538,6 +538,43 @@ Interpreter::Visit(const UnaryOpNode &node) {
node.GetLocation());
}
llvm::Expected<lldb::ValueObjectSP>
Interpreter::PointerOffset(lldb::ValueObjectSP ptr, lldb::ValueObjectSP offset,
BinaryOpKind operation, uint32_t location) {
assert(operation == BinaryOpKind::Add || operation == BinaryOpKind::Sub);
if (ptr->GetCompilerType().IsPointerToVoid())
return llvm::make_error<DILDiagnosticError>(
m_expr, "arithmetic on a pointer to void", location);
if (ptr->GetValueAsUnsigned(0) == 0 && offset != 0)
return llvm::make_error<DILDiagnosticError>(
m_expr, "arithmetic on a nullptr is undefined", location);
bool success;
int64_t offset_int = offset->GetValueAsSigned(0, &success);
if (!success) {
std::string errMsg = llvm::formatv("could not get the offset: {0}",
offset->GetError().AsCString());
return llvm::make_error<DILDiagnosticError>(m_expr, std::move(errMsg),
location);
}
llvm::Expected<uint64_t> byte_size =
ptr->GetCompilerType().GetPointeeType().GetByteSize(
m_exe_ctx_scope.get());
if (!byte_size)
return byte_size.takeError();
uint64_t ptr_addr = ptr->GetValueAsUnsigned(0);
if (operation == BinaryOpKind::Sub)
ptr_addr -= offset_int * (*byte_size);
else
ptr_addr += offset_int * (*byte_size);
ExecutionContext exe_ctx(m_target.get(), false);
Scalar scalar(ptr_addr);
return ValueObject::CreateValueObjectFromScalar(
m_exe_ctx_scope, scalar, ptr->GetCompilerType(), "result");
}
llvm::Expected<lldb::ValueObjectSP>
Interpreter::EvaluateScalarOp(BinaryOpKind kind, lldb::ValueObjectSP lhs,
lldb::ValueObjectSP rhs, CompilerType result_type,
@ -569,7 +606,8 @@ llvm::Expected<lldb::ValueObjectSP> Interpreter::EvaluateBinaryAddition(
lldb::ValueObjectSP lhs, lldb::ValueObjectSP rhs, uint32_t location) {
// Operation '+' works for:
// {scalar,unscoped_enum} <-> {scalar,unscoped_enum}
// TODO: Pointer arithmetics
// {integer,unscoped_enum} <-> pointer
// pointer <-> {integer,unscoped_enum}
auto orig_lhs_type = lhs->GetCompilerType();
auto orig_rhs_type = rhs->GetCompilerType();
auto type_or_err = ArithmeticConversion(lhs, rhs, location);
@ -580,18 +618,34 @@ llvm::Expected<lldb::ValueObjectSP> Interpreter::EvaluateBinaryAddition(
if (result_type.IsScalarType())
return EvaluateScalarOp(BinaryOpKind::Add, lhs, rhs, result_type, location);
std::string errMsg =
llvm::formatv("invalid operands to binary expression ('{0}' and '{1}')",
orig_lhs_type.GetTypeName(), orig_rhs_type.GetTypeName());
return llvm::make_error<DILDiagnosticError>(m_expr, std::move(errMsg),
location);
// Check for pointer arithmetics.
// One of the operands must be a pointer and the other one an integer.
lldb::ValueObjectSP ptr, offset;
if (lhs->GetCompilerType().IsPointerType()) {
ptr = lhs;
offset = rhs;
} else if (rhs->GetCompilerType().IsPointerType()) {
ptr = rhs;
offset = lhs;
}
if (!ptr || !offset->GetCompilerType().IsInteger()) {
std::string errMsg =
llvm::formatv("invalid operands to binary expression ('{0}' and '{1}')",
orig_lhs_type.GetTypeName(), orig_rhs_type.GetTypeName());
return llvm::make_error<DILDiagnosticError>(m_expr, std::move(errMsg),
location);
}
return PointerOffset(ptr, offset, BinaryOpKind::Add, location);
}
llvm::Expected<lldb::ValueObjectSP> Interpreter::EvaluateBinarySubtraction(
lldb::ValueObjectSP lhs, lldb::ValueObjectSP rhs, uint32_t location) {
// Operation '-' works for:
// {scalar,unscoped_enum} <-> {scalar,unscoped_enum}
// TODO: Pointer arithmetics
// pointer <-> {integer,unscoped_enum}
// pointer <-> pointer (if pointee types are compatible)
auto orig_lhs_type = lhs->GetCompilerType();
auto orig_rhs_type = rhs->GetCompilerType();
auto type_or_err = ArithmeticConversion(lhs, rhs, location);
@ -602,6 +656,60 @@ llvm::Expected<lldb::ValueObjectSP> Interpreter::EvaluateBinarySubtraction(
if (result_type.IsScalarType())
return EvaluateScalarOp(BinaryOpKind::Sub, lhs, rhs, result_type, location);
auto lhs_type = lhs->GetCompilerType();
auto rhs_type = rhs->GetCompilerType();
// "pointer - integer" operation.
if (lhs_type.IsPointerType() && rhs_type.IsInteger())
return PointerOffset(lhs, rhs, BinaryOpKind::Sub, location);
// "pointer - pointer" operation.
if (lhs_type.IsPointerType() && rhs_type.IsPointerType()) {
if (lhs_type.IsPointerToVoid() && rhs_type.IsPointerToVoid()) {
return llvm::make_error<DILDiagnosticError>(
m_expr, "arithmetic on pointers to void", location);
}
// Compare canonical unqualified pointer types.
CompilerType lhs_unqualified_type = lhs_type.GetCanonicalType();
CompilerType rhs_unqualified_type = rhs_type.GetCanonicalType();
if (!lhs_unqualified_type.CompareTypes(rhs_unqualified_type)) {
std::string errMsg = llvm::formatv(
"'{0}' and '{1}' are not pointers to compatible types",
orig_lhs_type.GetTypeName(), orig_rhs_type.GetTypeName());
return llvm::make_error<DILDiagnosticError>(m_expr, errMsg, location);
}
llvm::Expected<uint64_t> lhs_byte_size =
lhs_type.GetPointeeType().GetByteSize(m_exe_ctx_scope.get());
if (!lhs_byte_size)
return lhs_byte_size.takeError();
// Since pointers have compatible types, both have the same pointee size.
int64_t item_size = *lhs_byte_size;
int64_t diff = static_cast<int64_t>(lhs->GetValueAsUnsigned(0) -
rhs->GetValueAsUnsigned(0));
assert(item_size > 0 && "Pointee size cannot be 0");
if (diff % item_size != 0) {
// If address difference isn't divisible by pointee size then performing
// the operation is undefined behaviour.
return llvm::make_error<DILDiagnosticError>(
m_expr, "undefined pointer arithmetic", location);
}
diff /= item_size;
llvm::Expected<lldb::TypeSystemSP> type_system =
GetTypeSystemFromCU(m_exe_ctx_scope);
if (!type_system)
return type_system.takeError();
CompilerType ptrdiff_type = type_system.get()->GetPointerDiffType(true);
if (!ptrdiff_type)
return llvm::make_error<DILDiagnosticError>(
m_expr, "unable to determine pointer diff type", location);
Scalar scalar(diff);
return ValueObject::CreateValueObjectFromScalar(m_exe_ctx_scope, scalar,
ptrdiff_type, "result");
}
std::string errMsg =
llvm::formatv("invalid operands to binary expression ('{0}' and '{1}')",
orig_lhs_type.GetTypeName(), orig_rhs_type.GetTypeName());

View File

@ -102,15 +102,3 @@ class TestFrameVarDILArithmetic(TestBase):
self.expect_var_path("my_ref - 1", value="1")
self.expect_var_path("ref + my_ref", value="4")
self.expect_var_path("ref - my_ref", value="0")
# TODO: Pointer arithmetics
self.expect(
"frame var -- 'p + 1'",
error=True,
substrs=["invalid operands to binary expression ('int *' and 'int')"],
)
self.expect(
"frame var -- 'p - 1'",
error=True,
substrs=["invalid operands to binary expression ('int *' and 'int')"],
)

View File

@ -22,8 +22,139 @@ class TestFrameVarDILExprPointerArithmetic(TestBase):
self.expect_var_path("+array", type="int *")
self.expect_var_path("+array_ref", type="int *")
self.expect_var_path("+p_int0", type="int *")
# Binary operations
self.expect_var_path("p_char", type="const char *")
self.expect_var_path("p_char + 1", type="const char *")
self.expect_var_path("p_char + offset", type="const char *")
self.expect_var_path("p_char5 + -1", type="const char *")
self.expect_var_path("p_char5 - 1", type="const char *")
self.expect_var_path("p_char5 - offset", type="const char *")
self.expect_var_path("my_p_char", type="my_char_ptr")
self.expect_var_path("my_p_char + 1", type="my_char_ptr")
self.expect_var_path("my_p_char - 1", type="my_char_ptr")
self.expect_var_path("*(p_char + 0)", value="'h'")
self.expect_var_path("*(5 + p_char)", value="'!'")
self.expect_var_path("*(p_char5 + -5)", value="'h'")
self.expect_var_path("*(p_char5 - 5)", value="'h'")
self.expect_var_path("*(p_char - -5)", value="'!'")
self.expect_var_path("*(p_char5 - offset + 5)", value="'!'")
self.expect_var_path("*((p_char + offset) - 5)", value="'h'")
self.expect_var_path("*(p_char + (offset - 5))", value="'h'")
self.expect_var_path("*p_int0", value="0")
self.expect_var_path("*cp_int5", value="5")
self.expect_var_path("*(&*(cp_int5 + 1) - 1)", value="5")
self.expect_var_path("p_int0 - p_int0", value="0", type="__ptrdiff_t")
self.expect_var_path("cp_int5 - p_int0", value="5", type="__ptrdiff_t")
self.expect_var_path("cp_int5 - td_int_ptr0", value="5", type="__ptrdiff_t")
self.expect_var_path("td_int_ptr0 - cp_int5", value="-5", type="__ptrdiff_t")
# Check arrays
self.expect_var_path("array + 1", type="int *")
self.expect_var_path("1 + array", type="int *")
self.expect_var_path("array_ref + 1", type="int *")
self.expect_var_path("1 + array_ref", type="int *")
self.expect_var_path("array - 1", type="int *")
self.expect_var_path("array_ref - 1", type="int *")
self.expect_var_path("array - array", value="0", type="__ptrdiff_t")
self.expect_var_path("array - array_ref", value="0", type="__ptrdiff_t")
self.expect_var_path("array_ref - array_ref", value="0", type="__ptrdiff_t")
# Errors
self.expect(
"frame var -- '-p_int0'",
error=True,
substrs=["invalid argument type 'int *' to unary expression"],
)
self.expect(
"frame var -- 'cp_int5 - p_char'",
error=True,
substrs=[
"'const int *' and 'const char *' are not pointers to compatible types"
],
)
self.expect(
"frame var -- 'p_int0 + cp_int5'",
error=True,
substrs=[
"invalid operands to binary expression ('int *' and 'const int *')"
],
)
self.expect(
"frame var -- 'p_void + 1'",
error=True,
substrs=["arithmetic on a pointer to void"],
)
self.expect(
"frame var -- 'p_void - 1'",
error=True,
substrs=["arithmetic on a pointer to void"],
)
self.expect(
"frame var -- 'p_void - p_char'",
error=True,
substrs=[
"'void *' and 'const char *' are not pointers to compatible types"
],
)
self.expect(
"frame var -- 'p_void - p_void'",
error=True,
substrs=["arithmetic on pointers to void"],
)
self.expect(
"frame var -- 'pp_void0 - p_char'",
error=True,
substrs=[
"'void **' and 'const char *' are not pointers to compatible types"
],
)
self.expect(
"frame var -- 'p_int0 - 1.0'",
error=True,
substrs=["invalid operands to binary expression ('int *' and 'double')"],
)
self.expect(
"frame var -- '1.0f + p_int0'",
error=True,
substrs=["invalid operands to binary expression ('float' and 'int *')"],
)
self.expect(
"frame var -- '1 - array'",
error=True,
substrs=["invalid operands to binary expression ('int' and 'int[10]')"],
)
self.expect(
"frame var -- 'array + array'",
error=True,
substrs=["invalid operands to binary expression ('int[10]' and 'int[10]')"],
)
self.expect(
"frame var -- 'array + array'",
error=True,
substrs=["invalid operands to binary expression ('int[10]' and 'int[10]')"],
)
self.expect(
"frame var -- 'int_null + 1'",
error=True,
substrs=["arithmetic on a nullptr is undefined"],
)
self.expect(
"frame var -- 'int_null - 1'",
error=True,
substrs=["arithmetic on a nullptr is undefined"],
)
self.expect(
"frame var -- 'p_char + *((int*) 0)'",
error=True,
substrs=["could not get the offset: parent is NULL"],
)
self.expect(
"frame var -- 'p_char - *((int*) 0)'",
error=True,
substrs=["could not get the offset: parent is NULL"],
)

View File

@ -1,11 +1,31 @@
void stop() {}
int main(int argc, char **argv) {
int offset = 5;
int array[10];
array[0] = 0;
array[offset] = offset;
int (&array_ref)[10] = array;
int *p_int0 = &array[0];
const char *p_char = "hello!";
const char *p_char5 = p_char + 5;
typedef const char *my_char_ptr;
my_char_ptr my_p_char = p_char;
int **pp_int0 = &p_int0;
const int *cp_int0 = &array[0];
const int *cp_int5 = &array[offset];
typedef int *td_int_ptr_t;
td_int_ptr_t td_int_ptr0 = &array[0];
void *p_void = (void *)p_char;
void **pp_void0 = &p_void;
void **pp_void1 = pp_void0 + 1;
int *int_null = nullptr;
stop(); // Set a breakpoint here
return 0;
}