https://github.com/cmtice created https://github.com/llvm/llvm-project/pull/159500
This adds basic c-style type casting to DIL. It handles basic built-in types: bool, char, short, int, long, double, float, and void. It also handles user-defined types (e.g. class names, typedefs, etc.). It does NOT handle C++ templates at the moment. Not sure if this is something we would want to add to DIL later or not. DIL will not be supporting C++-specific-style casts (static_cast<>, reinterpret_cast<>, or dynamic_cast<>). This PR adds quite a bit of type-parsing, which must happen in the parser, to resolve ambiguity ("is this thing I'm looking at a type cast or just an expression in parentheses?"). I'd be happy to break this up into smaller PRs if someone could suggest a reasonable way to break it up. >From bbacb825638d18f052dfeec253c765001a872229 Mon Sep 17 00:00:00 2001 From: Caroline Tice <cmt...@google.com> Date: Wed, 17 Sep 2025 20:21:52 -0700 Subject: [PATCH] [LLDB] Add type casting to DIL. This adds basic c-style type casting to DIL. It handles basic built-in types: bool, char, short, int, long, double, float, and void. It also handles user-defined types (e.g. class names, typedefs, etc.). It does NOT handle C++ templates at the moment. Not sure if this is something we would want to add to DIL later or not. DIL will not be supporting C++-specific-style casts (static_cast<>, reinterpret_cast<>, or dynamic_cast<>). This PR adds quite a bit of type-parsing, which must happen in the parser, to resolve ambiguity ("is this thing I'm looking at a type cast or just an expression in parentheses?"). I'd be happy to break this up into smaller PRs if someone could suggest a reasonable way to break it up. --- lldb/docs/dil-expr-lang.ebnf | 32 +- lldb/include/lldb/ValueObject/DILAST.h | 41 ++ lldb/include/lldb/ValueObject/DILEval.h | 7 + lldb/include/lldb/ValueObject/DILParser.h | 133 ++++ lldb/source/ValueObject/DILAST.cpp | 4 + lldb/source/ValueObject/DILEval.cpp | 285 +++++++- lldb/source/ValueObject/DILParser.cpp | 634 +++++++++++++++++- .../LocalVars/TestFrameVarDILLocalVars.py | 1 + .../frame/var-dil/expr/CStyleCast/Makefile | 6 + .../CStyleCast/TestFrameVarDILCStyleCast.py | 233 +++++++ .../frame/var-dil/expr/CStyleCast/main.cpp | 81 +++ 11 files changed, 1439 insertions(+), 18 deletions(-) create mode 100644 lldb/test/API/commands/frame/var-dil/expr/CStyleCast/Makefile create mode 100644 lldb/test/API/commands/frame/var-dil/expr/CStyleCast/TestFrameVarDILCStyleCast.py create mode 100644 lldb/test/API/commands/frame/var-dil/expr/CStyleCast/main.cpp diff --git a/lldb/docs/dil-expr-lang.ebnf b/lldb/docs/dil-expr-lang.ebnf index 67328939ba420..4a66734e75fec 100644 --- a/lldb/docs/dil-expr-lang.ebnf +++ b/lldb/docs/dil-expr-lang.ebnf @@ -3,10 +3,13 @@ (* This is currently a subset of the final DIL Language, matching the current DIL implementation. *) -expression = unary_expression ; +expression = cast_expression; + +cast_expression = unary_expression + | "(" type_id ")" cast_expression; unary_expression = postfix_expression - | unary_operator expression ; + | unary_operator cast_expression ; unary_operator = "*" | "&" ; @@ -41,6 +44,31 @@ nested_name_specifier = type_name "::" | namespace_name '::' | nested_name_specifier identifier "::" ; +type_id = type_specifier_seq [abstract_declarator] ; + +type_specifier_seq = type_specifier [type_specifier]; + +type_specifier = ["::"] [nested_name_specifier] type_name; + | "char" + | "bool" + | "short" + | "int" + | "long" + | "signed" + | "unsigned" + | "float" + | "double" + | "void" ; + +nested_name_specifier = type_name "::" + | namespace_name "::" + | nested_name_specifier identifier "::" ; + +abstract_declarator = ptr_operator [abstract_declarator] ; + +ptr_operator = "*" + | "&"; + type_name = class_name | enum_name | typedef_name; diff --git a/lldb/include/lldb/ValueObject/DILAST.h b/lldb/include/lldb/ValueObject/DILAST.h index 1d10755c46e39..bbaad28d55735 100644 --- a/lldb/include/lldb/ValueObject/DILAST.h +++ b/lldb/include/lldb/ValueObject/DILAST.h @@ -20,6 +20,7 @@ namespace lldb_private::dil { enum class NodeKind { eArraySubscriptNode, eBitExtractionNode, + eCStyleCastNode, eErrorNode, eFloatLiteralNode, eIdentifierNode, @@ -28,6 +29,21 @@ enum class NodeKind { eUnaryOpNode, }; +/// The C-Style casts allowed by DIL. +enum class CStyleCastKind { + eEnumeration, + eNullptr, + eReference, + eNone, +}; + +/// Promotions for C-Style casts in DIL. +enum class CastPromoKind { + eArithmetic, + ePointer, + eNone, +}; + /// The Unary operators recognized by DIL. enum class UnaryOpKind { AddrOf, // "&" @@ -226,6 +242,29 @@ class FloatLiteralNode : public ASTNode { llvm::APFloat m_value; }; +class CStyleCastNode : public ASTNode { +public: + CStyleCastNode(uint32_t location, CompilerType type, ASTNodeUP operand, + CStyleCastKind kind) + : ASTNode(location, NodeKind::eCStyleCastNode), m_type(type), + m_operand(std::move(operand)), m_cast_kind(kind) {} + + llvm::Expected<lldb::ValueObjectSP> Accept(Visitor *v) const override; + + CompilerType GetType() const { return m_type; } + ASTNode *GetOperand() const { return m_operand.get(); } + CStyleCastKind GetCastKind() const { return m_cast_kind; } + + static bool classof(const ASTNode *node) { + return node->GetKind() == NodeKind::eCStyleCastNode; + } + +private: + CompilerType m_type; + ASTNodeUP m_operand; + CStyleCastKind m_cast_kind; +}; + /// This class contains one Visit method for each specialized type of /// DIL AST node. The Visit methods are used to dispatch a DIL AST node to /// the correct function in the DIL expression evaluator for evaluating that @@ -247,6 +286,8 @@ class Visitor { Visit(const IntegerLiteralNode *node) = 0; virtual llvm::Expected<lldb::ValueObjectSP> Visit(const FloatLiteralNode *node) = 0; + virtual llvm::Expected<lldb::ValueObjectSP> + Visit(const CStyleCastNode *node) = 0; }; } // namespace lldb_private::dil diff --git a/lldb/include/lldb/ValueObject/DILEval.h b/lldb/include/lldb/ValueObject/DILEval.h index 5a48c2c989f4d..3d4ef021cbc6f 100644 --- a/lldb/include/lldb/ValueObject/DILEval.h +++ b/lldb/include/lldb/ValueObject/DILEval.h @@ -58,12 +58,19 @@ class Interpreter : Visitor { Visit(const IntegerLiteralNode *node) override; llvm::Expected<lldb::ValueObjectSP> Visit(const FloatLiteralNode *node) override; + llvm::Expected<lldb::ValueObjectSP> + Visit(const CStyleCastNode *node) override; llvm::Expected<CompilerType> PickIntegerType(lldb::TypeSystemSP type_system, std::shared_ptr<ExecutionContextScope> ctx, const IntegerLiteralNode *literal); + llvm::Expected<CompilerType> + VerifyCStyleCastType(lldb::ValueObjectSP &operand, CompilerType &op_type, + CompilerType target_type, CastPromoKind &promo_kind, + CStyleCastKind &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/include/lldb/ValueObject/DILParser.h b/lldb/include/lldb/ValueObject/DILParser.h index 90df109337dcf..081711d26439d 100644 --- a/lldb/include/lldb/ValueObject/DILParser.h +++ b/lldb/include/lldb/ValueObject/DILParser.h @@ -10,6 +10,7 @@ #define LLDB_VALUEOBJECT_DILPARSER_H #include "lldb/Target/ExecutionContextScope.h" +#include "lldb/Target/StackFrame.h" #include "lldb/Utility/DiagnosticsRendering.h" #include "lldb/Utility/Status.h" #include "lldb/ValueObject/DILAST.h" @@ -31,6 +32,9 @@ enum class ErrorCode : unsigned char { kUnknown, }; +llvm::Expected<lldb::TypeSystemSP> +DILGetTypeSystemFromCU(std::shared_ptr<StackFrame> ctx); + // The following is modeled on class OptionParseError. class DILDiagnosticError : public llvm::ErrorInfo<DILDiagnosticError, DiagnosticError> { @@ -55,6 +59,61 @@ class DILDiagnosticError std::string message() const override { return m_detail.rendered; } }; +/// TypeDeclaration builds information about the literal type definition as +/// type is being parsed. It doesn't perform semantic analysis for non-basic +/// types -- e.g. "char&&&" is a valid type declaration. +/// NOTE: CV qualifiers are ignored. +class TypeDeclaration { +public: + enum class TypeSpecifier { + kBool, + kChar, + kDouble, + kFloat, + kInt, + kLong, + kLongDouble, + kLongLong, + kShort, + kUnknown, + kVoid, + }; + + enum class SignSpecifier { + kUnknown, + kSigned, + kUnsigned, + }; + + bool IsEmpty() const { return !m_is_builtin && !m_is_user_type; } + + lldb::BasicType GetBasicType() const; + +public: + // Indicates user-defined typename (e.g. "MyClass", "MyTmpl<int>"). + std::string m_user_typename; + + // Basic type specifier ("void", "char", "intr", "float", "long long", etc.). + TypeSpecifier m_type_specifier = TypeSpecifier::kUnknown; + + // Signedness specifier ("signed", "unsigned"). + SignSpecifier m_sign_specifier = SignSpecifier::kUnknown; + + // Does the type declaration includes "int" specifier? + // This is different than `type_specifier_` and is used to detect "int" + // duplication for types that can be combined with "int" specifier (e.g. + // "short int", "long int"). + bool m_has_int_specifier = false; + + // Indicates whether there was an error during parsing. + bool m_has_error = false; + + // Indicates whether this declaration describes a builtin type. + bool m_is_builtin = false; + + // Indicates whether this declaration describes a user type. + bool m_is_user_type = false; +}; // class TypeDeclaration /// Pure recursive descent parser for C++ like expressions. /// EBNF grammar for the parser is described in lldb/docs/dil-expr-lang.ebnf @@ -100,10 +159,22 @@ class DILParser { ASTNodeUP ParseIntegerLiteral(); ASTNodeUP ParseFloatingPointLiteral(); + ASTNodeUP ParseCastExpression(); + std::optional<CompilerType> ParseTypeId(bool must_be_type_id = false); + void ParseTypeSpecifierSeq(TypeDeclaration *type_decl); + bool ParseTypeSpecifier(TypeDeclaration *type_decl); + std::string ParseTypeName(); + CompilerType ResolveTypeDeclarators(CompilerType type, + const std::vector<Token> &ptr_operators); + bool IsSimpleTypeSpecifierKeyword(Token token) const; + bool HandleSimpleTypeSpecifier(TypeDeclaration *type_decl); + void BailOut(const std::string &error, uint32_t loc, uint16_t err_len); void Expect(Token::Kind kind); + void ExpectOneOf(std::vector<Token::Kind> kinds_vec); + void TentativeParsingRollback(uint32_t saved_idx) { if (m_error) llvm::consumeError(std::move(m_error)); @@ -132,4 +203,66 @@ class DILParser { } // namespace lldb_private::dil +namespace llvm { +template <> +struct format_provider<lldb_private::dil::TypeDeclaration::TypeSpecifier> { + static void format(const lldb_private::dil::TypeDeclaration::TypeSpecifier &t, + raw_ostream &OS, llvm::StringRef Options) { + switch (t) { + case lldb_private::dil::TypeDeclaration::TypeSpecifier::kVoid: + OS << "void"; + break; + case lldb_private::dil::TypeDeclaration::TypeSpecifier::kBool: + OS << "bool"; + break; + case lldb_private::dil::TypeDeclaration::TypeSpecifier::kChar: + OS << "char"; + break; + case lldb_private::dil::TypeDeclaration::TypeSpecifier::kInt: + OS << "int"; + break; + case lldb_private::dil::TypeDeclaration::TypeSpecifier::kFloat: + OS << "float"; + break; + case lldb_private::dil::TypeDeclaration::TypeSpecifier::kShort: + OS << "short"; + break; + case lldb_private::dil::TypeDeclaration::TypeSpecifier::kLong: + OS << "long"; + break; + case lldb_private::dil::TypeDeclaration::TypeSpecifier::kLongLong: + OS << "long long"; + break; + case lldb_private::dil::TypeDeclaration::TypeSpecifier::kDouble: + OS << "double"; + break; + case lldb_private::dil::TypeDeclaration::TypeSpecifier::kLongDouble: + OS << "long double"; + break; + default: + OS << "invalid type specifier"; + break; + } + } +}; + +template <> +struct format_provider<lldb_private::dil::TypeDeclaration::SignSpecifier> { + static void format(const lldb_private::dil::TypeDeclaration::SignSpecifier &t, + raw_ostream &OS, llvm::StringRef Options) { + switch (t) { + case lldb_private::dil::TypeDeclaration::SignSpecifier::kSigned: + OS << "signed"; + break; + case lldb_private::dil::TypeDeclaration::SignSpecifier::kUnsigned: + OS << "unsigned"; + break; + default: + OS << "invalid sign specifier"; + break; + } + } +}; +} // namespace llvm + #endif // LLDB_VALUEOBJECT_DILPARSER_H diff --git a/lldb/source/ValueObject/DILAST.cpp b/lldb/source/ValueObject/DILAST.cpp index 70564663a62cd..f8afd9d7bd50a 100644 --- a/lldb/source/ValueObject/DILAST.cpp +++ b/lldb/source/ValueObject/DILAST.cpp @@ -46,4 +46,8 @@ llvm::Expected<lldb::ValueObjectSP> FloatLiteralNode::Accept(Visitor *v) const { return v->Visit(this); } +llvm::Expected<lldb::ValueObjectSP> CStyleCastNode::Accept(Visitor *v) const { + return v->Visit(this); +} + } // namespace lldb_private::dil diff --git a/lldb/source/ValueObject/DILEval.cpp b/lldb/source/ValueObject/DILEval.cpp index c6cf41ee9e9ee..05bcc25d2b9f2 100644 --- a/lldb/source/ValueObject/DILEval.cpp +++ b/lldb/source/ValueObject/DILEval.cpp @@ -13,6 +13,7 @@ #include "lldb/Symbol/VariableList.h" #include "lldb/Target/RegisterContext.h" #include "lldb/ValueObject/DILAST.h" +#include "lldb/ValueObject/DILParser.h" #include "lldb/ValueObject/ValueObject.h" #include "lldb/ValueObject/ValueObjectRegister.h" #include "lldb/ValueObject/ValueObjectVariable.h" @@ -21,6 +22,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 lldb::VariableSP DILFindVariable(ConstString name, VariableList &variable_list) { lldb::VariableSP exact_match; @@ -499,16 +536,6 @@ Interpreter::Visit(const BitFieldExtractionNode *node) { return child_valobj_sp; } -static llvm::Expected<lldb::TypeSystemSP> -GetTypeSystemFromCU(std::shared_ptr<StackFrame> ctx) { - SymbolContext symbol_context = - ctx->GetSymbolContext(lldb::eSymbolContextCompUnit); - lldb::LanguageType language = symbol_context.comp_unit->GetLanguage(); - - symbol_context = ctx->GetSymbolContext(lldb::eSymbolContextModule); - return symbol_context.module_sp->GetTypeSystemForLanguage(language); -} - static CompilerType GetBasicType(lldb::TypeSystemSP type_system, lldb::BasicType basic_type) { if (type_system) @@ -559,7 +586,7 @@ Interpreter::PickIntegerType(lldb::TypeSystemSP type_system, llvm::Expected<lldb::ValueObjectSP> Interpreter::Visit(const IntegerLiteralNode *node) { llvm::Expected<lldb::TypeSystemSP> type_system = - GetTypeSystemFromCU(m_exe_ctx_scope); + DILGetTypeSystemFromCU(m_exe_ctx_scope); if (!type_system) return type_system.takeError(); @@ -583,7 +610,7 @@ Interpreter::Visit(const IntegerLiteralNode *node) { llvm::Expected<lldb::ValueObjectSP> Interpreter::Visit(const FloatLiteralNode *node) { llvm::Expected<lldb::TypeSystemSP> type_system = - GetTypeSystemFromCU(m_exe_ctx_scope); + DILGetTypeSystemFromCU(m_exe_ctx_scope); if (!type_system) return type_system.takeError(); @@ -602,4 +629,238 @@ Interpreter::Visit(const FloatLiteralNode *node) { "result"); } +llvm::Expected<CompilerType> Interpreter::VerifyCStyleCastType( + lldb::ValueObjectSP &operand, CompilerType &op_type, + CompilerType target_type, CastPromoKind &promo_kind, + CStyleCastKind &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()) { + // C-style cast from pointer to float/double is not allowed. + if (target_type.IsFloat()) { + std::string errMsg = llvm::formatv( + "C-style cast from {0} to {1} is not allowed", + op_type.TypeDescription(), target_type.TypeDescription()); + return llvm::make_error<DILDiagnosticError>( + 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<DILDiagnosticError>( + 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<DILDiagnosticError>( + 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( + "C-style cast from {0} to {1} is not allowed", + op_type.TypeDescription(), target_type.TypeDescription()); + + return llvm::make_error<DILDiagnosticError>( + m_expr, std::move(errMsg), location, + op_type.TypeDescription().length()); + } + cast_kind = CStyleCastKind::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<DILDiagnosticError>( + 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( + "C-style cast from {0} to {1} is not allowed", + op_type.TypeDescription(), target_type.TypeDescription()); + + return llvm::make_error<DILDiagnosticError>( + m_expr, std::move(errMsg), location, + op_type.TypeDescription().length()); + } + cast_kind = CStyleCastKind::eNullptr; + + } else if (target_type.IsReferenceType()) { + // Cast to a reference type. + cast_kind = CStyleCastKind::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<DILDiagnosticError>( + m_expr, std::move(errMsg), location, + op_type.TypeDescription().length()); + } + + return target_type; +} + +llvm::Expected<lldb::ValueObjectSP> +Interpreter::Visit(const CStyleCastNode *node) { + auto operand_or_err = Evaluate(node->GetOperand()); + if (!operand_or_err) + return operand_or_err; + + lldb::ValueObjectSP operand = *operand_or_err; + CompilerType op_type = operand->GetCompilerType(); + CStyleCastKind cast_kind = CStyleCastKind::eNone; + CastPromoKind promo_kind = CastPromoKind::eNone; + + auto type_or_err = + VerifyCStyleCastType(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<DILDiagnosticError>(m_expr, error.AsCString(), + node->GetLocation()); + } + + switch (cast_kind) { + case CStyleCastKind::eEnumeration: { + if (!target_type.IsEnumerationType()) { + std::string errMsg = "invalid ast: target type should be an enumeration."; + return llvm::make_error<DILDiagnosticError>(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<DILDiagnosticError>(m_expr, std::move(errMsg), + node->GetLocation()); + // return error.ToError(); + } + case CStyleCastKind::eNullptr: { + if (target_type.GetCanonicalType().GetBasicTypeEnumeration() != + lldb::eBasicTypeNullPtr) { + std::string errMsg = "invalid ast: target type should be a nullptr_t."; + return llvm::make_error<DILDiagnosticError>(m_expr, std::move(errMsg), + node->GetLocation()); + } + return ValueObject::CreateValueObjectFromNullptr(m_target, target_type, + "result"); + } + case CStyleCastKind::eReference: { + lldb::ValueObjectSP operand_sp( + GetDynamicOrSyntheticValue(operand, m_use_dynamic, m_use_synthetic)); + return lldb::ValueObjectSP( + operand_sp->Cast(target_type.GetNonReferenceType())); + } + case CStyleCastKind::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<DILDiagnosticError>(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<DILDiagnosticError>(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<DILDiagnosticError>(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 CStyleCastKind::eNone + } // switch cast_kind + std::string errMsg = "invalid ast: unexpected c-style cast kind"; + return llvm::make_error<DILDiagnosticError>(m_expr, std::move(errMsg), + node->GetLocation()); +} + } // namespace lldb_private::dil diff --git a/lldb/source/ValueObject/DILParser.cpp b/lldb/source/ValueObject/DILParser.cpp index 8c4f7fdb25bea..578ee99053201 100644 --- a/lldb/source/ValueObject/DILParser.cpp +++ b/lldb/source/ValueObject/DILParser.cpp @@ -12,7 +12,9 @@ //===----------------------------------------------------------------------===// #include "lldb/ValueObject/DILParser.h" +#include "lldb/Symbol/CompileUnit.h" #include "lldb/Target/ExecutionContextScope.h" +#include "lldb/Target/LanguageRuntime.h" #include "lldb/Utility/DiagnosticsRendering.h" #include "lldb/ValueObject/DILAST.h" #include "lldb/ValueObject/DILEval.h" @@ -42,6 +44,83 @@ DILDiagnosticError::DILDiagnosticError(llvm::StringRef expr, m_detail.rendered = std::move(rendered_msg); } +llvm::Expected<lldb::TypeSystemSP> +DILGetTypeSystemFromCU(std::shared_ptr<StackFrame> ctx) { + SymbolContext symbol_context = + ctx->GetSymbolContext(lldb::eSymbolContextCompUnit); + lldb::LanguageType language = symbol_context.comp_unit->GetLanguage(); + + symbol_context = ctx->GetSymbolContext(lldb::eSymbolContextModule); + return symbol_context.module_sp->GetTypeSystemForLanguage(language); +} + +CompilerType +ResolveTypeByName(const std::string &name, + std::shared_ptr<ExecutionContextScope> ctx_scope) { + // Internally types don't have global scope qualifier in their names and + // LLDB doesn't support queries with it too. + llvm::StringRef name_ref(name); + + if (name_ref.starts_with("::")) + name_ref = name_ref.drop_front(2); + + std::vector<CompilerType> result_type_list; + lldb::TargetSP target_sp = ctx_scope->CalculateTarget(); + const char *type_name = name_ref.data(); + if (type_name && type_name[0] && target_sp) { + ModuleList &images = target_sp->GetImages(); + ConstString const_type_name(type_name); + TypeQuery query(type_name); + TypeResults results; + images.FindTypes(nullptr, query, results); + for (const lldb::TypeSP &type_sp : results.GetTypeMap().Types()) + if (type_sp) + result_type_list.push_back(type_sp->GetFullCompilerType()); + + if (auto process_sp = target_sp->GetProcessSP()) { + for (auto *runtime : process_sp->GetLanguageRuntimes()) { + if (auto *vendor = runtime->GetDeclVendor()) { + auto types = vendor->FindTypes(const_type_name, UINT32_MAX); + for (auto type : types) + result_type_list.push_back(type); + } + } + } + + if (result_type_list.empty()) { + for (auto type_system_sp : target_sp->GetScratchTypeSystems()) + if (auto compiler_type = + type_system_sp->GetBuiltinTypeByName(const_type_name)) + result_type_list.push_back(compiler_type); + } + } + + // We've found multiple types, try finding the "correct" one. + CompilerType full_match; + std::vector<CompilerType> partial_matches; + + for (uint32_t i = 0; i < result_type_list.size(); ++i) { + CompilerType type = result_type_list[i]; + llvm::StringRef type_name_ref = type.GetTypeName().GetStringRef(); + ; + + if (type_name_ref == name_ref) + full_match = type; + else if (type_name_ref.ends_with(name_ref)) + partial_matches.push_back(type); + } + + // Full match is always correct. + if (full_match.IsValid()) + return full_match; + + // If we have partial matches, pick a "random" one. + if (partial_matches.size() > 0) + return partial_matches.back(); + + return {}; +} + llvm::Expected<ASTNodeUP> DILParser::Parse(llvm::StringRef dil_input_expr, DILLexer lexer, std::shared_ptr<StackFrame> frame_sp, @@ -80,15 +159,64 @@ ASTNodeUP DILParser::Run() { // Parse an expression. // // expression: -// unary_expression +// cast_expression +// +ASTNodeUP DILParser::ParseExpression() { return ParseCastExpression(); } + +// Parse a cast_expression. // -ASTNodeUP DILParser::ParseExpression() { return ParseUnaryExpression(); } +// cast_expression: +// unary_expression +// "(" type_id ")" cast_expression + +ASTNodeUP DILParser::ParseCastExpression() { + // This can be a C-style cast, try parsing the contents as a type declaration. + if (CurToken().Is(Token::l_paren)) { + Token token = CurToken(); + uint32_t loc = token.GetLocation(); + + // Enable lexer backtracking, so that we can rollback in case it's not + // actually a type declaration. + + // Start tentative parsing (save token location/idx, for possible rollback). + uint32_t save_token_idx = m_dil_lexer.GetCurrentTokenIdx(); + + // Consume the token only after enabling the backtracking. + m_dil_lexer.Advance(); + + // Try parsing the type declaration. If the returned value is not valid, + // then we should rollback and try parsing the expression. + auto type_id = ParseTypeId(); + if (type_id) { + // Successfully parsed the type declaration. Commit the backtracked + // tokens and parse the cast_expression. + + if (!type_id.value().IsValid()) + return std::make_unique<ErrorNode>(); + + Expect(Token::r_paren); + m_dil_lexer.Advance(); + auto rhs = ParseCastExpression(); + + // return BuildCStyleCast(type_id.value(), std::move(rhs), + // token.GetLocation()); + return std::make_unique<CStyleCastNode>( + loc, type_id.value(), std::move(rhs), CStyleCastKind::eNone); + } + + // Failed to parse the contents of the parentheses as a type declaration. + // Rollback the lexer and try parsing it as unary_expression. + TentativeParsingRollback(save_token_idx); + } + + return ParseUnaryExpression(); +} // Parse an unary_expression. // // unary_expression: // postfix_expression -// unary_operator expression +// unary_operator cast_expression // // unary_operator: // "&" @@ -99,7 +227,7 @@ ASTNodeUP DILParser::ParseUnaryExpression() { Token token = CurToken(); uint32_t loc = token.GetLocation(); m_dil_lexer.Advance(); - auto rhs = ParseExpression(); + auto rhs = ParseCastExpression(); switch (token.GetKind()) { case Token::star: return std::make_unique<UnaryOpNode>(loc, UnaryOpKind::Deref, @@ -271,6 +399,236 @@ std::string DILParser::ParseNestedNameSpecifier() { } } +// Parse a type_id. +// +// type_id: +// type_specifier_seq [abstract_declarator] +std::optional<CompilerType> DILParser::ParseTypeId(bool must_be_type_id) { + uint32_t type_loc = CurToken().GetLocation(); + TypeDeclaration type_decl; + + // type_specifier_seq is required here, start with trying to parse it. + ParseTypeSpecifierSeq(&type_decl); + + if (type_decl.IsEmpty()) { + // TODO: Should we bail out if `must_be_type_id` is set? + return {}; + } + + if (type_decl.m_has_error) { + if (type_decl.m_is_builtin) { + return {}; + } + + assert(type_decl.m_is_user_type && "type_decl must be a user type"); + // Found something looking like a user type, but failed to parse it. + // Return invalid type if we expect to have a type here, otherwise nullopt. + if (must_be_type_id) { + return {}; + } + return {}; + } + + // Try to resolve the base type. + CompilerType type; + if (type_decl.m_is_builtin) { + llvm::Expected<lldb::TypeSystemSP> type_system = + DILGetTypeSystemFromCU(m_ctx_scope); + if (!type_system) + return {}; + // type = GetBasicType(m_ctx_scope, type_decl.GetBasicType()); + // type = DILGetBasicType(*type_system, type_decl.GetBasicType()); + type = (*type_system).get()->GetBasicTypeFromAST(type_decl.GetBasicType()); + assert(type.IsValid() && "cannot resolve basic type"); + + } else { + assert(type_decl.m_is_user_type && "type_decl must be a user type"); + type = ResolveTypeByName(type_decl.m_user_typename, m_ctx_scope); + if (!type.IsValid()) { + if (must_be_type_id) { + BailOut( + llvm::formatv("unknown type name '{0}'", type_decl.m_user_typename), + type_loc, CurToken().GetSpelling().length()); + return {}; + } + return {}; + } + + if (LookupIdentifier(type_decl.m_user_typename, m_ctx_scope, + m_use_dynamic)) { + // Same-name identifiers should be preferred over typenames. + // TODO: Make type accessible with 'class', 'struct' and 'union' keywords. + if (must_be_type_id) { + BailOut(llvm::formatv( + "must use '{0}' tag to refer to type '{1}' in this scope", + type.GetTypeTag(), type_decl.m_user_typename), + type_loc, CurToken().GetSpelling().length()); + return {}; + } + return {}; + } + + if (LookupGlobalIdentifier(type_decl.m_user_typename, m_ctx_scope, + m_ctx_scope->CalculateTarget(), m_use_dynamic)) { + // Same-name identifiers should be preferred over typenames. + // TODO: Make type accessible with 'class', 'struct' and 'union' keywords. + if (must_be_type_id) { + BailOut(llvm::formatv( + "must use '{0}' tag to refer to type '{1}' in this scope", + type.GetTypeTag(), type_decl.m_user_typename), + type_loc, CurToken().GetSpelling().length()); + return {}; + } + return {}; + } + } + + // + // abstract_declarator: + // ptr_operator [abstract_declarator] + // + std::vector<Token> ptr_operators; + while (CurToken().IsOneOf({Token::star, Token::amp})) { + Token tok = CurToken(); + ptr_operators.push_back(std::move(tok)); + m_dil_lexer.Advance(); + } + type = ResolveTypeDeclarators(type, ptr_operators); + + return type; +} + +// Parse a type_specifier_seq. +// +// type_specifier_seq: +// type_specifier [type_specifier_seq] +// +void DILParser::ParseTypeSpecifierSeq(TypeDeclaration *type_decl) { + while (true) { + bool type_specifier = ParseTypeSpecifier(type_decl); + if (!type_specifier) { + break; + } + } +} + +// Parse a type_specifier. +// +// type_specifier: +// simple_type_specifier +// cv_qualifier +// +// simple_type_specifier: +// ["::"] [nested_name_specifier] type_name +// "char" +// "bool" +// "integer" +// "float" +// "void" +// +// Returns TRUE if a type_specifier was successfully parsed at this location. +// +bool DILParser::ParseTypeSpecifier(TypeDeclaration *type_decl) { + if (IsSimpleTypeSpecifierKeyword(CurToken())) { + // User-defined typenames can't be combined with builtin keywords. + if (type_decl->m_is_user_type) { + BailOut("cannot combine with previous declaration specifier", + CurToken().GetLocation(), CurToken().GetSpelling().length()); + type_decl->m_has_error = true; + return false; + } + + // From now on this type declaration must describe a builtin type. + // TODO: Should this be allowed -- `unsigned myint`? + type_decl->m_is_builtin = true; + + if (!HandleSimpleTypeSpecifier(type_decl)) { + type_decl->m_has_error = true; + return false; + } + m_dil_lexer.Advance(); + return true; + } + + // The type_specifier must be a user-defined type. Try parsing a + // simple_type_specifier. + { + // Try parsing optional global scope operator. + bool global_scope = false; + if (CurToken().Is(Token::coloncolon)) { + global_scope = true; + m_dil_lexer.Advance(); + } + + uint32_t loc = CurToken().GetLocation(); + + // Try parsing optional nested_name_specifier. + auto nested_name_specifier = ParseNestedNameSpecifier(); + + // Try parsing required type_name. + auto type_name = ParseTypeName(); + + // If there is a type_name, then this is indeed a simple_type_specifier. + // Global and qualified (namespace/class) scopes can be empty, since they're + // optional. In this case type_name is type we're looking for. + if (!type_name.empty()) { + // User-defined typenames can't be combined with builtin keywords. + if (type_decl->m_is_builtin) { + BailOut("cannot combine with previous declaration specifier", loc, + CurToken().GetSpelling().length()); + type_decl->m_has_error = true; + return false; + } + // There should be only one user-defined typename. + if (type_decl->m_is_user_type) { + BailOut("two or more data types in declaration of 'type name'", loc, + CurToken().GetSpelling().length()); + type_decl->m_has_error = true; + return false; + } + + // Construct the fully qualified typename. + type_decl->m_is_user_type = true; + type_decl->m_user_typename = + llvm::formatv("{0}{1}{2}", global_scope ? "::" : "", + nested_name_specifier, type_name); + return true; + } + } + + // No type_specifier was found here. + return false; +} + +// Parse a type_name. +// +// type_name: +// class_name +// enum_name +// typedef_name +// +// class_name +// identifier +// +// enum_name +// identifier +// +// typedef_name +// identifier +// +std::string DILParser::ParseTypeName() { + // Typename always starts with an identifier. + if (CurToken().IsNot(Token::identifier)) { + return ""; + } + + // Otherwise look for a class_name, enum_name or a typedef_name. + std::string identifier = CurToken().GetSpelling(); + m_dil_lexer.Advance(); + + return identifier; +} + // Parse an id_expression. // // id_expression: @@ -336,6 +694,204 @@ std::string DILParser::ParseUnqualifiedId() { return identifier; } +CompilerType +DILParser::ResolveTypeDeclarators(CompilerType type, + const std::vector<Token> &ptr_operators) { + CompilerType bad_type; + // Resolve pointers/references. + for (Token tk : ptr_operators) { + uint32_t loc = tk.GetLocation(); + if (tk.GetKind() == Token::star) { + // Pointers to reference types are forbidden. + if (type.IsReferenceType()) { + BailOut(llvm::formatv("'type name' declared as a pointer to a " + "reference of type {0}", + type.TypeDescription()), + loc, CurToken().GetSpelling().length()); + return bad_type; + } + // Get pointer type for the base type: e.g. int* -> int**. + type = type.GetPointerType(); + + } else if (tk.GetKind() == Token::amp) { + // References to references are forbidden. + if (type.IsReferenceType()) { + BailOut("type name declared as a reference to a reference", loc, + CurToken().GetSpelling().length()); + return bad_type; + } + // Get reference type for the base type: e.g. int -> int&. + type = type.GetLValueReferenceType(); + } + } + + return type; +} + +bool DILParser::IsSimpleTypeSpecifierKeyword(Token token) const { + if (token.GetKind() != Token::identifier) + return false; + if (token.GetSpelling() == "bool" || token.GetSpelling() == "char" || + token.GetSpelling() == "int" || token.GetSpelling() == "float" || + token.GetSpelling() == "void" || token.GetSpelling() == "short" || + token.GetSpelling() == "long" || token.GetSpelling() == "signed" || + token.GetSpelling() == "unsigned" || token.GetSpelling() == "double") + return true; + return false; +} + +bool DILParser::HandleSimpleTypeSpecifier(TypeDeclaration *type_decl) { + using TypeSpecifier = TypeDeclaration::TypeSpecifier; + using SignSpecifier = TypeDeclaration::SignSpecifier; + + TypeSpecifier type_spec = type_decl->m_type_specifier; + uint32_t loc = CurToken().GetLocation(); + std::string kind = CurToken().GetSpelling(); + + // switch (kind) { + if (kind == "int") { + // case Token::kw_int: { + // "int" can have signedness and be combined with "short", "long" and + // "long long" (but not with another "int"). + if (type_decl->m_has_int_specifier) { + BailOut("cannot combine with previous 'int' declaration specifier", loc, + CurToken().GetSpelling().length()); + return false; + } + if (type_spec == TypeSpecifier::kShort || + type_spec == TypeSpecifier::kLong || + type_spec == TypeSpecifier::kLongLong) { + type_decl->m_has_int_specifier = true; + return true; + } else if (type_spec == TypeSpecifier::kUnknown) { + type_decl->m_type_specifier = TypeSpecifier::kInt; + type_decl->m_has_int_specifier = true; + return true; + } + BailOut(llvm::formatv( + "cannot combine with previous '{0}' declaration specifier", + type_spec), + loc, CurToken().GetSpelling().length()); + return false; + } + + if (kind == "long") { + // "long" can have signedness and be combined with "int" or "long" to + // form "long long". + if (type_spec == TypeSpecifier::kUnknown || + type_spec == TypeSpecifier::kInt) { + type_decl->m_type_specifier = TypeSpecifier::kLong; + return true; + } else if (type_spec == TypeSpecifier::kLong) { + type_decl->m_type_specifier = TypeSpecifier::kLongLong; + return true; + } else if (type_spec == TypeSpecifier::kDouble) { + type_decl->m_type_specifier = TypeSpecifier::kLongDouble; + return true; + } + BailOut(llvm::formatv( + "cannot combine with previous '{0}' declaration specifier", + type_spec), + loc, CurToken().GetSpelling().length()); + return false; + } + + if (kind == "short") { + // "short" can have signedness and be combined with "int". + if (type_spec == TypeSpecifier::kUnknown || + type_spec == TypeSpecifier::kInt) { + type_decl->m_type_specifier = TypeSpecifier::kShort; + return true; + } + BailOut(llvm::formatv( + "cannot combine with previous '{0}' declaration specifier", + type_spec), + loc, CurToken().GetSpelling().length()); + return false; + } + + if (kind == "char") { + // "char" can have signedness, but it cannot be combined with any other + // type specifier. + if (type_spec == TypeSpecifier::kUnknown) { + type_decl->m_type_specifier = TypeSpecifier::kChar; + return true; + } + BailOut(llvm::formatv( + "cannot combine with previous '{0}' declaration specifier", + type_spec), + loc, CurToken().GetSpelling().length()); + return false; + } + + if (kind == "double") { + // "double" can be combined with "long" to form "long double", but it + // cannot be combined with signedness specifier. + if (type_decl->m_sign_specifier != SignSpecifier::kUnknown) { + BailOut("'double' cannot be signed or unsigned", loc, + CurToken().GetSpelling().length()); + return false; + } + if (type_spec == TypeSpecifier::kUnknown) { + type_decl->m_type_specifier = TypeSpecifier::kDouble; + return true; + } else if (type_spec == TypeSpecifier::kLong) { + type_decl->m_type_specifier = TypeSpecifier::kLongDouble; + return true; + } + BailOut(llvm::formatv( + "cannot combine with previous '{0}' declaration specifier", + type_spec), + loc, CurToken().GetSpelling().length()); + return false; + } + + if (kind == "bool" || kind == "void" || kind == "float") { + // These types cannot have signedness or be combined with any other type + // specifiers. + if (type_decl->m_sign_specifier != SignSpecifier::kUnknown) { + BailOut(llvm::formatv("'{0}' cannot be signed or unsigned", kind), loc, + CurToken().GetSpelling().length()); + return false; + } + if (type_spec != TypeSpecifier::kUnknown) { + BailOut(llvm::formatv( + "cannot combine with previous '{0}' declaration specifier", + type_spec), + loc, CurToken().GetSpelling().length()); + } + if (kind == "bool") + type_decl->m_type_specifier = TypeSpecifier::kBool; + else if (kind == "void") + type_decl->m_type_specifier = TypeSpecifier::kVoid; + else if (kind == "float") + type_decl->m_type_specifier = TypeSpecifier::kFloat; + return true; + } + + if (kind == "signed" || kind == "unsigned") { + // "signed" and "unsigned" cannot be combined with another signedness + // specifier. + if (type_spec == TypeSpecifier::kVoid || + type_spec == TypeSpecifier::kBool || + type_spec == TypeSpecifier::kFloat || + type_spec == TypeSpecifier::kDouble || + type_spec == TypeSpecifier::kLongDouble) { + BailOut(llvm::formatv("'{0}' cannot be signed or unsigned", type_spec), + loc, CurToken().GetSpelling().length()); + return false; + } + + type_decl->m_sign_specifier = + (kind == "signed") ? SignSpecifier::kSigned : SignSpecifier::kUnsigned; + return true; + } + + BailOut(llvm::formatv("invalid simple type specifier kind"), loc, + CurToken().GetSpelling().length()); + return false; +} + void DILParser::BailOut(const std::string &error, uint32_t loc, uint16_t err_len) { if (m_error) @@ -444,4 +1000,74 @@ void DILParser::Expect(Token::Kind kind) { } } +void DILParser::ExpectOneOf(std::vector<Token::Kind> kinds_vec) { + if (!CurToken().IsOneOf(kinds_vec)) { + BailOut(llvm::formatv("expected any of ({0}), got: {1}", + llvm::iterator_range(kinds_vec), CurToken()), + CurToken().GetLocation(), CurToken().GetSpelling().length()); + } +} + +lldb::BasicType TypeDeclaration::GetBasicType() const { + if (!m_is_builtin) + return lldb::eBasicTypeInvalid; + + if (m_sign_specifier == SignSpecifier::kSigned && + m_type_specifier == TypeSpecifier::kChar) { + // "signed char" isn't the same as "char". + return lldb::eBasicTypeSignedChar; + } + + if (m_sign_specifier == SignSpecifier::kUnsigned) { + switch (m_type_specifier) { + // "unsigned" is "unsigned int" + case TypeSpecifier::kUnknown: + return lldb::eBasicTypeUnsignedInt; + case TypeSpecifier::kChar: + return lldb::eBasicTypeUnsignedChar; + case TypeSpecifier::kShort: + return lldb::eBasicTypeUnsignedShort; + case TypeSpecifier::kInt: + return lldb::eBasicTypeUnsignedInt; + case TypeSpecifier::kLong: + return lldb::eBasicTypeUnsignedLong; + case TypeSpecifier::kLongLong: + return lldb::eBasicTypeUnsignedLongLong; + default: + // assert(false && "unknown unsigned basic type"); + return lldb::eBasicTypeInvalid; + } + } + + switch (m_type_specifier) { + case TypeSpecifier::kUnknown: + // "signed" is "signed int" + if (m_sign_specifier != SignSpecifier::kSigned) + return lldb::eBasicTypeInvalid; + return lldb::eBasicTypeInt; + case TypeSpecifier::kVoid: + return lldb::eBasicTypeVoid; + case TypeSpecifier::kBool: + return lldb::eBasicTypeBool; + case TypeSpecifier::kChar: + return lldb::eBasicTypeChar; + case TypeSpecifier::kShort: + return lldb::eBasicTypeShort; + case TypeSpecifier::kInt: + return lldb::eBasicTypeInt; + case TypeSpecifier::kLong: + return lldb::eBasicTypeLong; + case TypeSpecifier::kLongLong: + return lldb::eBasicTypeLongLong; + case TypeSpecifier::kFloat: + return lldb::eBasicTypeFloat; + case TypeSpecifier::kDouble: + return lldb::eBasicTypeDouble; + case TypeSpecifier::kLongDouble: + return lldb::eBasicTypeLongDouble; + } + + return lldb::eBasicTypeInvalid; +} + } // 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/CStyleCast/Makefile b/lldb/test/API/commands/frame/var-dil/expr/CStyleCast/Makefile new file mode 100644 index 0000000000000..0165eb73f3073 --- /dev/null +++ b/lldb/test/API/commands/frame/var-dil/expr/CStyleCast/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/CStyleCast/TestFrameVarDILCStyleCast.py b/lldb/test/API/commands/frame/var-dil/expr/CStyleCast/TestFrameVarDILCStyleCast.py new file mode 100644 index 0000000000000..001e39d8a9137 --- /dev/null +++ b/lldb/test/API/commands/frame/var-dil/expr/CStyleCast/TestFrameVarDILCStyleCast.py @@ -0,0 +1,233 @@ +""" +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 TestFrameVarDILCStyleCast(TestBase): + NO_DEBUG_INFO_TESTCASE = True + + def expect_var_path(self, expr, compare_to_framevar=False, value=None, type=None): + value_dil = super().expect_var_path(expr, value=value, type=type) + if compare_to_framevar: + self.runCmd("settings set target.experimental.use-DIL false") + value_frv = super().expect_var_path(expr, value=value, type=type) + self.runCmd("settings set target.experimental.use-DIL true") + self.assertEqual(value_dil.GetValue(), value_frv.GetValue()) + + 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") + + # TestCStyleCastBUiltins + + 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)>"]) + + # TestCStyleCastBasicType + + # 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("(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("(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("(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"]) + + # Test with typedefs and namespaces. + self.expect_var_path("(myint)1", type="myint", value="1") + self.expect_var_path("(myint)1LL", type="myint", value="1") + self.expect_var_path("(ns::myint)1", type="ns::myint", value="1") + self.expect_var_path("(::ns::myint)1", type="ns::myint", value="1") + self.expect_var_path("(::ns::myint)myint_", type="ns::myint", value="1") + + 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") + self.expect_var_path("(::ns::myint)myint_", type="ns::myint", value="1") + + self.expect_var_path("(ns::inner::mydouble)1", type="ns::inner::mydouble", value="1") + self.expect_var_path("(::ns::inner::mydouble)1.2", + type="ns::inner::mydouble", value="1.2") + self.expect_var_path("(ns::inner::mydouble)myint_", + type="ns::inner::mydouble", value="1") + self.expect_var_path("(::ns::inner::mydouble)ns_inner_mydouble_", + type="ns::inner::mydouble", value="1.2") + self.expect_var_path("(myint)ns_inner_mydouble_", + type="myint", value="1") + + # 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'", error=True, + substrs=["cast from pointer to smaller type 'int' loses" + " information"]) + 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=["C-style cast from 'int *' to 'float' is not " + "allowed"]) + self.expect("frame variable '(float)arr'", error=True, + substrs=["C-style cast from 'int *' to 'float' is not " + "allowed"]) + + # TestCStyleCastPointer + 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_var_path("(ns::Foo*)ns_inner_foo_ptr_", type="ns::Foo *") + self.expect_var_path("(ns::inner::Foo*)ns_foo_ptr_", type="ns::inner::Foo *") + + 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") + + + # TestCStyleCastArray + + threads = lldbutil.continue_to_source_breakpoint( + self, process,"Set a breakpoint here", lldb.SBFileSpec("main.cpp") + ) + self.assertEqual( + len(threads), 1, "There should be a thread stopped at our breakpoint" + ) + + 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") + + # TestCStyleCastReference + + self.expect_var_path("((InnerFoo&)arr_1d[1]).a", type="int", value="2") + self.expect_var_path("((InnerFoo&)arr_1d[1]).b", type="int", value="3") + + 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/CStyleCast/main.cpp b/lldb/test/API/commands/frame/var-dil/expr/CStyleCast/main.cpp new file mode 100644 index 0000000000000..0fd841d95cdf6 --- /dev/null +++ b/lldb/test/API/commands/frame/var-dil/expr/CStyleCast/main.cpp @@ -0,0 +1,81 @@ +// CStyleCast, main.cpp + +#include <cstdarg> +#include <cstddef> +#include <cstdint> +#include <cstdlib> +#include <limits> +#include <memory> +#include <string> + +namespace ns { + +typedef int myint; + +class Foo {}; + +namespace inner { + +using mydouble = double; + +class Foo {}; + +} // namespace inner + +} // 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_; + + ns::inner::mydouble ns_inner_mydouble_ = 1.2; + ns::inner::Foo ns_inner_foo_; + ns::inner::Foo *ns_inner_foo_ptr_ = &ns_inner_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(); + + // TestCStyleCastBuiltins + // TestCStyleCastBasicType + // TestCStyleCastPointer + // TestCStyleCastNullptrType + if (false) { // Set a breakpoint here + } + + struct InnerFoo { + int a; + int b; + }; + + InnerFoo ifoo; + (void)ifoo; + + int arr_1d[] = {1, 2, 3, 4}; + int arr_2d[2][3] = {{1, 2, 3}, {4, 5, 6}}; + + // TestCStyleCastArray + // TestCStyleCastReference + return 0; // Set a breakpoint here +} _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits