Author: cmtice Date: 2026-01-07T15:04:13-08:00 New Revision: 539cf92944421f66eb02316f4a9fc30ae0aecf36
URL: https://github.com/llvm/llvm-project/commit/539cf92944421f66eb02316f4a9fc30ae0aecf36 DIFF: https://github.com/llvm/llvm-project/commit/539cf92944421f66eb02316f4a9fc30ae0aecf36.diff LOG: [LLDB] Add type casting to DIL, part 2 or 3 (#170332) This PR implements the actual type casting part. With this, type casting to builtin types should work. The third PR, which will be put up after this one is merged, will expand the type name parsing to allow casting to user-defined types. Added: lldb/test/API/commands/frame/var-dil/expr/Casts/Makefile lldb/test/API/commands/frame/var-dil/expr/Casts/TestFrameVarDILCast.py lldb/test/API/commands/frame/var-dil/expr/Casts/main.cpp Modified: lldb/include/lldb/ValueObject/DILAST.h lldb/include/lldb/ValueObject/DILEval.h lldb/source/ValueObject/DILEval.cpp lldb/test/API/commands/frame/var-dil/basics/LocalVars/TestFrameVarDILLocalVars.py Removed: ################################################################################ diff --git a/lldb/include/lldb/ValueObject/DILAST.h b/lldb/include/lldb/ValueObject/DILAST.h index 247ea392727ff..da7659959093a 100644 --- a/lldb/include/lldb/ValueObject/DILAST.h +++ b/lldb/include/lldb/ValueObject/DILAST.h @@ -40,10 +40,10 @@ enum class UnaryOpKind { /// The type casts allowed by DIL. enum class CastKind { + eArithmetic, ///< Casting to a scalar. eEnumeration, ///< Casting from a scalar to an enumeration type - eNullptr, ///< Casting to a nullptr type - eReference, ///< Casting to a reference type - eNone, ///< Type promotion casting + ePointer, ///< Casting to a pointer type. + eNone, ///< Invalid promotion type (results in error). }; /// Forward declaration, for use in DIL AST nodes. Definition is at the very diff --git a/lldb/include/lldb/ValueObject/DILEval.h b/lldb/include/lldb/ValueObject/DILEval.h index d73e1182ab01b..550f5083b1dc6 100644 --- a/lldb/include/lldb/ValueObject/DILEval.h +++ b/lldb/include/lldb/ValueObject/DILEval.h @@ -78,6 +78,22 @@ class Interpreter : Visitor { std::shared_ptr<ExecutionContextScope> ctx, const IntegerLiteralNode &literal); + /// A helper function for VerifyCastType (below). This performs + /// arithmetic-specific checks. It should only be called if the target_type + /// is a scalar type. + llvm::Expected<CastKind> VerifyArithmeticCast(CompilerType source_type, + CompilerType target_type, + int location); + + /// As a preparation for type casting, compare the requested 'target' type + /// of the cast with the type of the operand to be cast. If the cast is + /// allowed, return the appropriate CastKind for the cast; otherwise return + /// an error. + llvm::Expected<CastKind> VerifyCastType(lldb::ValueObjectSP operand, + CompilerType source_type, + CompilerType target_type, + int location); + // Used by the interpreter to create objects, perform casts, etc. lldb::TargetSP m_target; llvm::StringRef m_expr; diff --git a/lldb/source/ValueObject/DILEval.cpp b/lldb/source/ValueObject/DILEval.cpp index 445c949b581b6..575dfae850c19 100644 --- a/lldb/source/ValueObject/DILEval.cpp +++ b/lldb/source/ValueObject/DILEval.cpp @@ -21,6 +21,28 @@ namespace lldb_private::dil { +lldb::ValueObjectSP +GetDynamicOrSyntheticValue(lldb::ValueObjectSP value_sp, + lldb::DynamicValueType use_dynamic, + bool use_synthetic) { + if (!value_sp) + return nullptr; + + if (use_dynamic != lldb::eNoDynamicValues) { + lldb::ValueObjectSP dynamic_sp = value_sp->GetDynamicValue(use_dynamic); + if (dynamic_sp) + value_sp = dynamic_sp; + } + + if (use_synthetic) { + lldb::ValueObjectSP synthetic_sp = value_sp->GetSyntheticValue(); + if (synthetic_sp) + value_sp = synthetic_sp; + } + + return value_sp; +} + static llvm::Expected<lldb::TypeSystemSP> GetTypeSystemFromCU(std::shared_ptr<ExecutionContextScope> ctx) { auto stack_frame = ctx->CalculateStackFrame(); @@ -42,13 +64,14 @@ static CompilerType GetBasicType(lldb::TypeSystemSP type_system, return CompilerType(); } -static lldb::ValueObjectSP -ArrayToPointerConversion(ValueObject &valobj, ExecutionContextScope &ctx) { +static lldb::ValueObjectSP ArrayToPointerConversion(ValueObject &valobj, + ExecutionContextScope &ctx, + llvm::StringRef name) { uint64_t addr = valobj.GetLoadAddress(); ExecutionContext exe_ctx; ctx.CalculateExecutionContext(exe_ctx); return ValueObject::CreateValueObjectFromAddress( - "result", addr, exe_ctx, + name, addr, exe_ctx, valobj.GetCompilerType().GetArrayElementType(&ctx).GetPointerType(), /* do_deref */ false); } @@ -100,7 +123,7 @@ Interpreter::UnaryConversion(lldb::ValueObjectSP valobj, uint32_t location) { } if (in_type.IsArrayType()) - valobj = ArrayToPointerConversion(*valobj, *m_exe_ctx_scope); + valobj = ArrayToPointerConversion(*valobj, *m_exe_ctx_scope, "result"); if (valobj->GetCompilerType().IsInteger() || valobj->GetCompilerType().IsUnscopedEnumerationType()) { @@ -783,16 +806,179 @@ Interpreter::Visit(const BooleanLiteralNode &node) { return ValueObject::CreateValueObjectFromBool(m_target, value, "result"); } +llvm::Expected<CastKind> +Interpreter::VerifyArithmeticCast(CompilerType source_type, + CompilerType target_type, int location) { + if (source_type.IsPointerType() || source_type.IsNullPtrType()) { + // Cast from pointer to float/double is not allowed. + if (target_type.IsFloat()) { + std::string errMsg = llvm::formatv("Cast from {0} to {1} is not allowed", + source_type.TypeDescription(), + target_type.TypeDescription()); + return llvm::make_error<DILDiagnosticError>( + m_expr, std::move(errMsg), location, + source_type.TypeDescription().length()); + } + + // Casting from pointer to bool is always valid. + if (target_type.IsBoolean()) + return CastKind::eArithmetic; + + // Otherwise check if the result type is at least as big as the pointer + // size. + uint64_t type_byte_size = 0; + uint64_t rhs_type_byte_size = 0; + if (auto temp = target_type.GetByteSize(m_exe_ctx_scope.get())) { + type_byte_size = *temp; + } else { + std::string errMsg = llvm::formatv("unable to get byte size for type {0}", + target_type.TypeDescription()); + return llvm::make_error<DILDiagnosticError>( + m_expr, std::move(errMsg), location, + target_type.TypeDescription().length()); + } + + if (auto temp = source_type.GetByteSize(m_exe_ctx_scope.get())) { + rhs_type_byte_size = *temp; + } else { + std::string errMsg = llvm::formatv("unable to get byte size for type {0}", + source_type.TypeDescription()); + return llvm::make_error<DILDiagnosticError>( + m_expr, std::move(errMsg), location, + source_type.TypeDescription().length()); + } + + if (type_byte_size < rhs_type_byte_size) { + std::string errMsg = llvm::formatv( + "cast from pointer to smaller type {0} loses information", + target_type.TypeDescription()); + return llvm::make_error<DILDiagnosticError>( + m_expr, std::move(errMsg), location, + source_type.TypeDescription().length()); + } + } else if (!source_type.IsScalarType() && !source_type.IsEnumerationType()) { + // Otherwise accept only arithmetic types and enums. + std::string errMsg = llvm::formatv("cannot convert {0} to {1}", + source_type.TypeDescription(), + target_type.TypeDescription()); + + return llvm::make_error<DILDiagnosticError>( + m_expr, std::move(errMsg), location, + source_type.TypeDescription().length()); + } + return CastKind::eArithmetic; +} + +llvm::Expected<CastKind> +Interpreter::VerifyCastType(lldb::ValueObjectSP operand, + CompilerType source_type, CompilerType target_type, + int location) { + + if (target_type.IsScalarType()) + return VerifyArithmeticCast(source_type, target_type, location); + + if (target_type.IsEnumerationType()) { + // Cast to enum type. + if (!source_type.IsScalarType() && !source_type.IsEnumerationType()) { + std::string errMsg = llvm::formatv("Cast from {0} to {1} is not allowed", + source_type.TypeDescription(), + target_type.TypeDescription()); + + return llvm::make_error<DILDiagnosticError>( + m_expr, std::move(errMsg), location, + source_type.TypeDescription().length()); + } + return CastKind::eEnumeration; + } + + if (target_type.IsPointerType()) { + if (!source_type.IsInteger() && !source_type.IsEnumerationType() && + !source_type.IsArrayType() && !source_type.IsPointerType() && + !source_type.IsNullPtrType()) { + std::string errMsg = llvm::formatv( + "cannot cast from type {0} to pointer type {1}", + source_type.TypeDescription(), target_type.TypeDescription()); + + return llvm::make_error<DILDiagnosticError>( + m_expr, std::move(errMsg), location, + source_type.TypeDescription().length()); + } + return CastKind::ePointer; + } + + // Unsupported cast. + std::string errMsg = llvm::formatv( + "casting of {0} to {1} is not implemented yet", + source_type.TypeDescription(), target_type.TypeDescription()); + return llvm::make_error<DILDiagnosticError>( + m_expr, std::move(errMsg), location, + source_type.TypeDescription().length()); +} + llvm::Expected<lldb::ValueObjectSP> Interpreter::Visit(const CastNode &node) { auto operand_or_err = Evaluate(node.GetOperand()); + if (!operand_or_err) return operand_or_err; lldb::ValueObjectSP operand = *operand_or_err; - // Don't actually do the cast for now -- that code will be added later. - // For now just return an error message. - return llvm::make_error<DILDiagnosticError>( - m_expr, "Type casting is not supported here.", node.GetLocation()); + CompilerType op_type = operand->GetCompilerType(); + CompilerType target_type = node.GetType(); + + if (op_type.IsReferenceType()) + op_type = op_type.GetNonReferenceType(); + if (target_type.IsScalarType() && op_type.IsArrayType()) { + operand = ArrayToPointerConversion(*operand, *m_exe_ctx_scope, + operand->GetName().GetStringRef()); + op_type = operand->GetCompilerType(); + } + auto type_or_err = + VerifyCastType(operand, op_type, target_type, node.GetLocation()); + if (!type_or_err) + return type_or_err.takeError(); + + CastKind cast_kind = *type_or_err; + if (operand->GetCompilerType().IsReferenceType()) { + Status error; + operand = operand->Dereference(error); + if (error.Fail()) + return llvm::make_error<DILDiagnosticError>(m_expr, error.AsCString(), + node.GetLocation()); + } + + switch (cast_kind) { + case CastKind::eEnumeration: { + if (op_type.IsFloat() || op_type.IsInteger() || op_type.IsEnumerationType()) + return operand->CastToEnumType(target_type); + break; + } + case CastKind::eArithmetic: { + if (op_type.IsPointerType() || op_type.IsNullPtrType() || + op_type.IsScalarType() || op_type.IsEnumerationType()) + return operand->CastToBasicType(target_type); + break; + } + case CastKind::ePointer: { + uint64_t addr = op_type.IsArrayType() + ? operand->GetLoadAddress() + : (op_type.IsSigned() ? operand->GetValueAsSigned(0) + : operand->GetValueAsUnsigned(0)); + llvm::StringRef name = "result"; + ExecutionContext exe_ctx(m_target.get(), false); + return ValueObject::CreateValueObjectFromAddress(name, addr, exe_ctx, + target_type, + /* do_deref */ false); + } + case CastKind::eNone: { + return lldb::ValueObjectSP(); + } + } // switch + + std::string errMsg = + llvm::formatv("unable to cast from '{0}' to '{1}'", + op_type.TypeDescription(), target_type.TypeDescription()); + return llvm::make_error<DILDiagnosticError>(m_expr, std::move(errMsg), + node.GetLocation()); } } // namespace lldb_private::dil diff --git a/lldb/test/API/commands/frame/var-dil/basics/LocalVars/TestFrameVarDILLocalVars.py b/lldb/test/API/commands/frame/var-dil/basics/LocalVars/TestFrameVarDILLocalVars.py index 0f6618fe47984..85899caaa7433 100644 --- a/lldb/test/API/commands/frame/var-dil/basics/LocalVars/TestFrameVarDILLocalVars.py +++ b/lldb/test/API/commands/frame/var-dil/basics/LocalVars/TestFrameVarDILLocalVars.py @@ -28,4 +28,5 @@ def test_frame_var(self): self.expect_var_path("a", value="1") self.expect_var_path("b", value="2") self.expect_var_path("c", value="'\\xfd'") + self.expect_var_path("(int)c", value="-3") self.expect_var_path("s", value="4") diff --git a/lldb/test/API/commands/frame/var-dil/expr/Casts/Makefile b/lldb/test/API/commands/frame/var-dil/expr/Casts/Makefile new file mode 100644 index 0000000000000..99998b20bcb05 --- /dev/null +++ b/lldb/test/API/commands/frame/var-dil/expr/Casts/Makefile @@ -0,0 +1,3 @@ +CXX_SOURCES := main.cpp + +include Makefile.rules diff --git a/lldb/test/API/commands/frame/var-dil/expr/Casts/TestFrameVarDILCast.py b/lldb/test/API/commands/frame/var-dil/expr/Casts/TestFrameVarDILCast.py new file mode 100644 index 0000000000000..b48cae488e1a2 --- /dev/null +++ b/lldb/test/API/commands/frame/var-dil/expr/Casts/TestFrameVarDILCast.py @@ -0,0 +1,228 @@ +""" +Make sure 'frame var' using DIL parser/evaultor works for C-Style casts. +""" + +import lldb +from lldbsuite.test.lldbtest import * +from lldbsuite.test.decorators import * +from lldbsuite.test import lldbutil + +import os +import shutil +import time + + +class TestFrameVarDILCast(TestBase): + def test_type_cast(self): + self.build() + (target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint( + self, "Set a breakpoint here", lldb.SBFileSpec("main.cpp") + ) + + self.runCmd("settings set target.experimental.use-DIL true") + + # TestCastBUiltins + + self.expect_var_path("(int)1", value="1", type="int") + self.expect_var_path("(long long)1", value="1", type="long long") + self.expect_var_path("(unsigned long)1", value="1", type="unsigned long") + self.expect_var_path("(char*)1", value="0x0000000000000001", type="char *") + self.expect_var_path( + "(long long**)1", value="0x0000000000000001", type="long long **" + ) + + self.expect( + "frame variable '(long&*)1'", + error=True, + substrs=[ + "'type name' declared as a pointer to a reference of type 'long &'" + ], + ) + + self.expect( + "frame variable '(long& &)1'", + error=True, + substrs=["type name declared as a reference to a reference"], + ) + + self.expect( + "frame variable '(long 1)1'", + error=True, + substrs=["expected 'r_paren', got: <'1' (integer_constant)>"], + ) + + # TestCastBasicType + + # Test with integer literals. + self.expect_var_path("(char)1", type="char", value="'\\x01'") + self.expect_var_path("(long long)1", type="long long", value="1") + self.expect_var_path("(short)65534", type="short", value="-2") + self.expect_var_path( + "(unsigned short)100000", type="unsigned short", value="34464" + ) + self.expect_var_path("(int)false", type="int", value="0") + self.expect_var_path("(int)true", type="int", value="1") + self.expect_var_path("(float)1", type="float", value="1") + self.expect_var_path("(float)1.1", type="float", value="1.10000002") + self.expect_var_path("(float)1.1f", type="float", value="1.10000002") + self.expect_var_path("(float)false", type="float", value="0") + self.expect_var_path("(float)true", type="float", value="1") + self.expect_var_path("(double)1", type="double", value="1") + self.expect_var_path("(double)1.1", type="double", value="1.1000000000000001") + self.expect_var_path("(double)1.1f", type="double", value="1.1000000238418579") + self.expect_var_path("(double)false", type="double", value="0") + self.expect_var_path("(double)true", type="double", value="1") + self.expect_var_path("(int)1.1", type="int", value="1") + self.expect_var_path("(int)1.1f", type="int", value="1") + self.expect_var_path("(long)1.1", type="long", value="1") + self.expect_var_path("(bool)0", type="bool", value="false") + self.expect_var_path("(bool)0.0", type="bool", value="false") + self.expect_var_path("(bool)0.0f", type="bool", value="false") + self.expect_var_path("(bool)3", type="bool", value="true") + + self.expect( + "frame variable '&(int)1'", + error=True, + substrs=["'result' doesn't have a valid address"], + ) + + # Test with variables. + self.expect_var_path("(char)a", type="char", value="'\\x01'") + self.expect_var_path("(unsigned char)na", type="unsigned char", value="'\\xff'") + self.expect_var_path("(short)na", type="short", value="-1") + self.expect_var_path("(long long)a", type="long long", value="1") + self.expect_var_path("(float)a", type="float", value="1") + self.expect_var_path("(float)f", type="float", value="1.10000002") + self.expect_var_path("(double)f", type="double", value="1.1000000238418579") + self.expect_var_path("(int)f", type="int", value="1") + self.expect_var_path("(long)f", type="long", value="1") + self.expect_var_path("(bool)finf", type="bool", value="true") + self.expect_var_path("(bool)fnan", type="bool", value="true") + self.expect_var_path("(bool)fsnan", type="bool", value="true") + self.expect_var_path("(bool)fmax", type="bool", value="true") + self.expect_var_path("(bool)fdenorm", type="bool", value="true") + self.expect( + "frame variable '(int)ns_foo_'", + error=True, + substrs=["cannot convert 'ns::Foo' to 'int'"], + ) + + self.expect_var_path("(int)myint_", type="int", value="1") + self.expect_var_path("(int)ns_myint_", type="int", value="2") + self.expect_var_path("(long long)myint_", type="long long", value="1") + self.expect_var_path("(long long)ns_myint_", type="long long", value="2") + + # Test with pointers and arrays. + self.expect_var_path("(long long)ap", type="long long") + self.expect_var_path("(unsigned long long)vp", type="unsigned long long") + self.expect_var_path("(long long)arr", type="long long") + self.expect_var_path("(bool)ap", type="bool", value="true") + self.expect_var_path("(bool)(int*)0x00000000", type="bool", value="false") + self.expect_var_path("(bool)std_nullptr_t", value="false") + self.expect_var_path("(bool)arr", type="bool", value="true") + self.expect( + "frame variable '(char)ap'", + error=True, + substrs=["cast from pointer to smaller type 'char' loses information"], + ) + Is32Bit = False + if self.target().GetAddressByteSize() == 4: + Is32Bit = True + + if Is32Bit: + self.expect_var_path("(int)arr", type="int") + else: + self.expect( + "frame variable '(int)arr'", + error=True, + substrs=["cast from pointer to smaller type 'int' loses information"], + ) + + self.expect( + "frame variable '(float)ap'", + error=True, + substrs=["Cast from 'int *' to 'float' is not allowed"], + ) + self.expect( + "frame variable '(float)arr'", + error=True, + substrs=["Cast from 'int *' to 'float' is not allowed"], + ) + + # TestCastPointer + self.expect_var_path("(void*)&a", type="void *") + self.expect_var_path("(void*)ap", type="void *") + self.expect_var_path("(long long*)vp", type="long long *") + self.expect_var_path("(short int*)vp", type="short *") + self.expect_var_path("(unsigned long long*)vp", type="unsigned long long *") + self.expect_var_path("(unsigned short int*)vp", type="unsigned short *") + + if Is32Bit: + self.expect_var_path("(void*)0", type="void *", value="0x00000000") + self.expect_var_path("(void*)1", type="void *", value="0x00000001") + self.expect_var_path("(void*)a", type="void *", value="0x00000001") + self.expect_var_path("(void*)na", type="void *", value="0xffffffff") + else: + self.expect_var_path("(void*)0", type="void *", value="0x0000000000000000") + self.expect_var_path("(void*)1", type="void *", value="0x0000000000000001") + self.expect_var_path("(void*)a", type="void *", value="0x0000000000000001") + self.expect_var_path("(void*)na", type="void *", value="0xffffffffffffffff") + + self.expect( + "frame variable '(char*) 1.0'", + error=True, + substrs=["cannot cast from type 'double' to pointer type 'char *'"], + ) + + self.expect_var_path("*(int*)(void*)ap", type="int", value="1") + + self.expect( + "frame variable '(int& &)ap'", + error=True, + substrs=["type name declared as a reference to a reference"], + ) + self.expect( + "frame variable '(int&*)ap'", + error=True, + substrs=[ + "'type name' declared as a pointer to a reference of type 'int &'" + ], + ) + + if Is32Bit: + self.expect_var_path("(void *)0", type="void *", value="0x00000000") + else: + self.expect_var_path("(void *)0", type="void *", value="0x0000000000000000") + + self.expect( + "frame variable '(int)std_nullptr_t'", + error=True, + substrs=["cast from pointer to smaller type 'int' loses information"], + ) + + if Is32Bit: + self.expect_var_path( + "(void*)std_nullptr_t", type="void *", value="0x00000000" + ) + self.expect_var_path( + "(char*)std_nullptr_t", type="char *", value="0x00000000" + ) + else: + self.expect_var_path( + "(void*)std_nullptr_t", type="void *", value="0x0000000000000000" + ) + self.expect_var_path( + "(char*)std_nullptr_t", type="char *", value="0x0000000000000000" + ) + + # TestCastArray + self.expect_var_path("(int*)arr_1d", type="int *") + self.expect_var_path("(char*)arr_1d", type="char *") + self.expect_var_path("((char*)arr_1d)[0]", type="char", value="'\\x01'") + self.expect_var_path("((char*)arr_1d)[1]", type="char", value="'\\0'") + + # 2D arrays. + self.expect_var_path("(int*)arr_2d", type="int *") + self.expect_var_path("((int*)arr_2d)[1]", type="int", value="2") + self.expect_var_path("((int*)arr_2d)[2]", type="int", value="3") + self.expect_var_path("((int*)arr_2d[1])[1]", type="int", value="5") diff --git a/lldb/test/API/commands/frame/var-dil/expr/Casts/main.cpp b/lldb/test/API/commands/frame/var-dil/expr/Casts/main.cpp new file mode 100644 index 0000000000000..3977283f54cc6 --- /dev/null +++ b/lldb/test/API/commands/frame/var-dil/expr/Casts/main.cpp @@ -0,0 +1,46 @@ +// Type Casting, main.cpp + +#include <limits> + +namespace ns { + +typedef int myint; + +class Foo {}; + +} // namespace ns + +int main(int argc, char **argv) { + int a = 1; + int *ap = &a; + void *vp = &a; + int arr[2] = {1, 2}; + + int na = -1; + float f = 1.1; + + typedef int myint; + std::nullptr_t std_nullptr_t = nullptr; + bool found_it = false; + if (std_nullptr_t) { + found_it = true; + } else { + found_it = (bool)0; + } + + myint myint_ = 1; + ns::myint ns_myint_ = 2; + ns::Foo ns_foo_; + ns::Foo *ns_foo_ptr_ = &ns_foo_; + + float finf = std::numeric_limits<float>::infinity(); + float fnan = std::numeric_limits<float>::quiet_NaN(); + float fsnan = std::numeric_limits<float>::signaling_NaN(); + float fmax = std::numeric_limits<float>::max(); + float fdenorm = std::numeric_limits<float>::denorm_min(); + + int arr_1d[] = {1, 2, 3, 4}; + int arr_2d[2][3] = {{1, 2, 3}, {4, 5, 6}}; + + return 0; // Set a breakpoint here +} _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
