diff --git a/lldb/include/lldb/ValueObject/DILAST.h b/lldb/include/lldb/ValueObject/DILAST.h index 9fda0c798ec4e..3a18172852b5e 100644 --- a/lldb/include/lldb/ValueObject/DILAST.h +++ b/lldb/include/lldb/ValueObject/DILAST.h @@ -46,6 +46,13 @@ enum class CastKind { eNone, ///< Type promotion casting }; +/// Promotions allowed for type casts in DIL. +enum CastPromoKind { + eArithmetic, + ePointer, + eNone, +}; + /// Forward declaration, for use in DIL AST nodes. Definition is at the very /// end of this file. class Visitor; diff --git a/lldb/include/lldb/ValueObject/DILEval.h b/lldb/include/lldb/ValueObject/DILEval.h index 2db45a7c37314..8df12ce450db7 100644 --- a/lldb/include/lldb/ValueObject/DILEval.h +++ b/lldb/include/lldb/ValueObject/DILEval.h @@ -71,6 +71,11 @@ class Interpreter : Visitor { std::shared_ptr ctx, const IntegerLiteralNode *literal); + llvm::Expected + VerifyCastType(lldb::ValueObjectSP &operand, CompilerType &op_type, + CompilerType target_type, CastPromoKind &promo_kind, + CastKind &cast_kind, 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 dc0d93d242739..28175b3f6428e 100644 --- a/lldb/source/ValueObject/DILEval.cpp +++ b/lldb/source/ValueObject/DILEval.cpp @@ -21,6 +21,42 @@ namespace lldb_private::dil { +lldb::ValueObjectSP +GetDynamicOrSyntheticValue(lldb::ValueObjectSP in_valobj_sp, + lldb::DynamicValueType use_dynamic, + bool use_synthetic) { + Status error; + if (!in_valobj_sp) { + error = Status("invalid value object"); + return in_valobj_sp; + } + lldb::ValueObjectSP value_sp = in_valobj_sp; + Target *target = value_sp->GetTargetSP().get(); + // If this ValueObject holds an error, then it is valuable for that. + if (value_sp->GetError().Fail()) + return value_sp; + + if (!target) + return lldb::ValueObjectSP(); + + 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; + } + + if (!value_sp) + error = Status("invalid value object"); + + return value_sp; +} + static llvm::Expected GetTypeSystemFromCU(std::shared_ptr ctx) { auto stack_frame = ctx->CalculateStackFrame(); @@ -740,16 +776,236 @@ Interpreter::Visit(const BooleanLiteralNode *node) { return ValueObject::CreateValueObjectFromBool(m_target, value, "result"); } +llvm::Expected +Interpreter::VerifyCastType(lldb::ValueObjectSP &operand, CompilerType &op_type, + CompilerType target_type, CastPromoKind &promo_kind, + CastKind &cast_kind, int location) { + + promo_kind = CastPromoKind::eNone; + if (op_type.IsReferenceType()) + op_type = op_type.GetNonReferenceType(); + if (target_type.IsScalarType()) { + if (op_type.IsArrayType()) { + // Do array-to-pointer conversion. + CompilerType deref_type = + op_type.IsReferenceType() ? op_type.GetNonReferenceType() : op_type; + CompilerType result_type = + deref_type.GetArrayElementType(nullptr).GetPointerType(); + uint64_t addr = operand->GetLoadAddress(); + llvm::StringRef name = operand->GetName().GetStringRef(); + operand = ValueObject::CreateValueObjectFromAddress( + name, addr, m_exe_ctx_scope, result_type, /*do_deref=*/false); + op_type = result_type; + } + + if (op_type.IsPointerType() || op_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", op_type.TypeDescription(), + target_type.TypeDescription()); + return llvm::make_error( + m_expr, std::move(errMsg), location, + op_type.TypeDescription().length()); + } + // Casting pointer to bool is valid. 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.value(); + type_byte_size = *temp; + if (auto temp = op_type.GetByteSize(m_exe_ctx_scope.get())) + // rhs_type_byte_size = temp.value(); + rhs_type_byte_size = *temp; + if (!target_type.IsBoolean() && 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( + m_expr, std::move(errMsg), location, + op_type.TypeDescription().length()); + } + } else if (!op_type.IsScalarType() && !op_type.IsEnumerationType()) { + // Otherwise accept only arithmetic types and enums. + std::string errMsg = llvm::formatv( + "cannot convert {0} to {1} without a conversion operator", + op_type.TypeDescription(), target_type.TypeDescription()); + + return llvm::make_error( + m_expr, std::move(errMsg), location, + op_type.TypeDescription().length()); + } + promo_kind = CastPromoKind::eArithmetic; + } else if (target_type.IsEnumerationType()) { + // Cast to enum type. + if (!op_type.IsScalarType() && !op_type.IsEnumerationType()) { + std::string errMsg = llvm::formatv("Cast from {0} to {1} is not allowed", + op_type.TypeDescription(), + target_type.TypeDescription()); + + return llvm::make_error( + m_expr, std::move(errMsg), location, + op_type.TypeDescription().length()); + } + cast_kind = CastKind::eEnumeration; + + } else if (target_type.IsPointerType()) { + if (!op_type.IsInteger() && !op_type.IsEnumerationType() && + !op_type.IsArrayType() && !op_type.IsPointerType() && + !op_type.IsNullPtrType()) { + std::string errMsg = llvm::formatv( + "cannot cast from type {0} to pointer type {1}", + op_type.TypeDescription(), target_type.TypeDescription()); + + return llvm::make_error( + m_expr, std::move(errMsg), location, + op_type.TypeDescription().length()); + } + promo_kind = CastPromoKind::ePointer; + + } else if (target_type.IsNullPtrType()) { + // Cast to nullptr type. + bool is_signed; + if (!target_type.IsNullPtrType() && + (!operand->IsIntegerType(is_signed) || + (is_signed && operand->GetValueAsSigned(0) != 0) || + (!is_signed && operand->GetValueAsUnsigned(0) != 0))) { + std::string errMsg = llvm::formatv("Cast from {0} to {1} is not allowed", + op_type.TypeDescription(), + target_type.TypeDescription()); + + return llvm::make_error( + m_expr, std::move(errMsg), location, + op_type.TypeDescription().length()); + } + cast_kind = CastKind::eNullptr; + + } else if (target_type.IsReferenceType()) { + // Cast to a reference type. + cast_kind = CastKind::eReference; + } else { + // Unsupported cast. + std::string errMsg = + llvm::formatv("casting of {0} to {1} is not implemented yet", + op_type.TypeDescription(), target_type.TypeDescription()); + return llvm::make_error( + m_expr, std::move(errMsg), location, + op_type.TypeDescription().length()); + } + + return target_type; +} + llvm::Expected 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( - m_expr, "Type casting is not supported here.", node->GetLocation()); + CompilerType op_type = operand->GetCompilerType(); + CastKind cast_kind = CastKind::eNone; + CastPromoKind promo_kind = CastPromoKind::eNone; + + auto type_or_err = VerifyCastType(operand, op_type, node->GetType(), + promo_kind, cast_kind, node->GetLocation()); + if (!type_or_err) + return type_or_err.takeError(); + + CompilerType target_type = *type_or_err; + if (op_type.IsReferenceType()) { + Status error; + operand = operand->Dereference(error); + if (error.Fail()) + return llvm::make_error(m_expr, error.AsCString(), + node->GetLocation()); + } + + switch (cast_kind) { + case CastKind::eEnumeration: { + if (!target_type.IsEnumerationType()) { + std::string errMsg = "invalid ast: target type should be an enumeration."; + return llvm::make_error(m_expr, std::move(errMsg), + node->GetLocation()); + } + if (op_type.IsFloat()) + return operand->CastToEnumType(target_type); + + if (op_type.IsInteger() || op_type.IsEnumerationType()) + return operand->CastToEnumType(target_type); + + std::string errMsg = + "invalid ast: operand is not convertible to enumeration type"; + return llvm::make_error(m_expr, std::move(errMsg), + node->GetLocation()); + // return error.ToError(); + } + case CastKind::eNullptr: { + if (target_type.GetCanonicalType().GetBasicTypeEnumeration() != + lldb::eBasicTypeNullPtr) { + std::string errMsg = "invalid ast: target type should be a nullptr_t."; + return llvm::make_error(m_expr, std::move(errMsg), + node->GetLocation()); + } + return ValueObject::CreateValueObjectFromNullptr(m_target, target_type, + "result"); + } + case CastKind::eReference: { + lldb::ValueObjectSP operand_sp( + GetDynamicOrSyntheticValue(operand, m_use_dynamic, m_use_synthetic)); + return lldb::ValueObjectSP( + operand_sp->Cast(target_type.GetNonReferenceType())); + } + case CastKind::eNone: { + switch (promo_kind) { + case CastPromoKind::eArithmetic: { + if (target_type.GetCanonicalType().GetBasicTypeEnumeration() == + lldb::eBasicTypeInvalid) { + std::string errMsg = "invalid ast: target type should be a basic type."; + return llvm::make_error(m_expr, std::move(errMsg), + node->GetLocation()); + } + // Pick an appropriate cast. + if (op_type.IsPointerType() || op_type.IsNullPtrType()) { + return operand->CastToBasicType(target_type); + } + if (op_type.IsScalarType()) { + return operand->CastToBasicType(target_type); + } + if (op_type.IsEnumerationType()) { + return operand->CastToBasicType(target_type); + } + std::string errMsg = + "invalid ast: operand is not convertible to arithmetic type"; + return llvm::make_error(m_expr, std::move(errMsg), + node->GetLocation()); + } + case CastPromoKind::ePointer: { + if (!target_type.IsPointerType()) { + std::string errMsg = "invalid ast: target type should be a pointer"; + return llvm::make_error(m_expr, std::move(errMsg), + node->GetLocation()); + } + 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 CastPromoKind::eNone: { + return lldb::ValueObjectSP(); + } + } // switch promo_kind + } // case CastKind::eNone + } // switch cast_kind + std::string errMsg = "invalid ast: unexpected c-style cast kind"; + return llvm::make_error(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..0165eb73f3073 --- /dev/null +++ b/lldb/test/API/commands/frame/var-dil/expr/Casts/Makefile @@ -0,0 +1,6 @@ +CXX_SOURCES := main.cpp +#CXXFLAGS_EXTRAS := -std=c++14 + +USE_LIBSTDCPP := 1 + +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..2e358ed923732 --- /dev/null +++ b/lldb/test/API/commands/frame/var-dil/expr/Casts/TestFrameVarDILCast.py @@ -0,0 +1,213 @@ +""" +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' without a conversion operator"], + ) + + 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)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("frame variable '(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_var_path("(int*&)ap", type="int *") + + 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") + + # 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") + + # TestCastReference + self.expect_var_path("(int&)arr_1d[0]", type="int", value="1") + self.expect_var_path("(int&)arr_1d[1]", type="int", value="2") + self.expect_var_path("&(int&)arr_1d", type="int *") 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..8ecf4d548afe6 --- /dev/null +++ b/lldb/test/API/commands/frame/var-dil/expr/Casts/main.cpp @@ -0,0 +1,52 @@ +// Type Casting, main.cpp + +#include +#include +#include +#include +#include +#include +#include + +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::infinity(); + float fnan = std::numeric_limits::quiet_NaN(); + float fsnan = std::numeric_limits::signaling_NaN(); + float fmax = std::numeric_limits::max(); + float fdenorm = std::numeric_limits::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 +}