https://github.com/cmtice updated https://github.com/llvm/llvm-project/pull/175061
>From cf0161bf183944ab37c1e6952e5cd2e883b4d212 Mon Sep 17 00:00:00 2001 From: Caroline Tice <[email protected]> Date: Thu, 8 Jan 2026 11:45:30 -0800 Subject: [PATCH 1/3] [LLDB] Add type casting to DIL, part 3 of 3 This PR updates type parsing in DIL to recognize user-defined types (classes, namespaces, etc.), and allows this to be used in type casting. --- lldb/include/lldb/ValueObject/DILParser.h | 7 + lldb/source/ValueObject/DILEval.cpp | 14 +- lldb/source/ValueObject/DILParser.cpp | 176 +++++++++++++++++- .../var-dil/expr/Casts/TestFrameVarDILCast.py | 26 +++ .../frame/var-dil/expr/Casts/main.cpp | 20 ++ 5 files changed, 227 insertions(+), 16 deletions(-) diff --git a/lldb/include/lldb/ValueObject/DILParser.h b/lldb/include/lldb/ValueObject/DILParser.h index bd2fc373cd9b5..3a6261aaac5fc 100644 --- a/lldb/include/lldb/ValueObject/DILParser.h +++ b/lldb/include/lldb/ValueObject/DILParser.h @@ -11,6 +11,7 @@ #include "lldb/Host/common/DiagnosticsRendering.h" #include "lldb/Target/ExecutionContextScope.h" +#include "lldb/Target/StackFrame.h" #include "lldb/Utility/Status.h" #include "lldb/ValueObject/DILAST.h" #include "lldb/ValueObject/DILLexer.h" @@ -31,6 +32,9 @@ enum class ErrorCode : unsigned char { kUnknown, }; +llvm::Expected<lldb::TypeSystemSP> +GetTypeSystemFromCU(std::shared_ptr<StackFrame> ctx); + // The following is modeled on class OptionParseError. class DILDiagnosticError : public llvm::ErrorInfo<DILDiagnosticError, DiagnosticError> { @@ -103,6 +107,9 @@ class DILParser { ASTNodeUP ParseCastExpression(); std::optional<CompilerType> ParseBuiltinType(); std::optional<CompilerType> ParseTypeId(); + void ParseTypeSpecifierSeq(std::string &type_name); + bool ParseTypeSpecifier(std::string &user_type_name); + std::string ParseTypeName(); CompilerType ResolveTypeDeclarators(CompilerType type, const std::vector<Token> &ptr_operators); diff --git a/lldb/source/ValueObject/DILEval.cpp b/lldb/source/ValueObject/DILEval.cpp index 575dfae850c19..604c5da9d5aa7 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" @@ -43,19 +44,6 @@ GetDynamicOrSyntheticValue(lldb::ValueObjectSP value_sp, return value_sp; } -static llvm::Expected<lldb::TypeSystemSP> -GetTypeSystemFromCU(std::shared_ptr<ExecutionContextScope> ctx) { - auto stack_frame = ctx->CalculateStackFrame(); - if (!stack_frame) - return llvm::createStringError("no stack frame in this context"); - SymbolContext symbol_context = - stack_frame->GetSymbolContext(lldb::eSymbolContextCompUnit); - lldb::LanguageType language = symbol_context.comp_unit->GetLanguage(); - - symbol_context = stack_frame->GetSymbolContext(lldb::eSymbolContextModule); - return symbol_context.module_sp->GetTypeSystemForLanguage(language); -} - static CompilerType GetBasicType(lldb::TypeSystemSP type_system, lldb::BasicType basic_type) { if (type_system) diff --git a/lldb/source/ValueObject/DILParser.cpp b/lldb/source/ValueObject/DILParser.cpp index f3027a3d82fa2..de09dd5f612bc 100644 --- a/lldb/source/ValueObject/DILParser.cpp +++ b/lldb/source/ValueObject/DILParser.cpp @@ -44,6 +44,72 @@ DILDiagnosticError::DILDiagnosticError(llvm::StringRef expr, m_detail.rendered = std::move(rendered_msg); } +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); +} + +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); + } + } + } + } + + // 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 && type.IsValid()) + return type; + + if (type_name_ref.ends_with(name_ref)) + partial_matches.push_back(type); + } + + // 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, @@ -339,12 +405,32 @@ std::string DILParser::ParseNestedNameSpecifier() { // std::optional<CompilerType> DILParser::ParseTypeId() { CompilerType type; - // For now only allow builtin types -- will expand add to this later. auto maybe_builtin_type = ParseBuiltinType(); if (maybe_builtin_type) { type = *maybe_builtin_type; - } else - return {}; + } else { + // Check to see if we have a user-defined type here. + // First build up the user-defined type name. + std::string type_name; + ParseTypeSpecifierSeq(type_name); + + if (type_name.size() == 0) + return {}; + type = ResolveTypeByName(type_name, m_ctx_scope); + if (!type.IsValid()) + return {}; + + // Same-name identifiers should be preferred over typenames. + if (LookupIdentifier(type_name, m_ctx_scope, m_use_dynamic)) + // TODO: Make type accessible with 'class', 'struct' and 'union' keywords. + return {}; + + // Same-name identifiers should be preferred over typenames. + if (LookupGlobalIdentifier(type_name, m_ctx_scope, + m_ctx_scope->CalculateTarget(), m_use_dynamic)) + // TODO: Make type accessible with 'class', 'struct' and 'union' keywords + return {}; + } // // abstract_declarator: @@ -400,6 +486,90 @@ std::optional<CompilerType> DILParser::ParseBuiltinType() { return {}; } +// Parse a type_specifier_seq. +// +// type_specifier_seq: +// type_specifier [type_specifier_seq] +// +void DILParser::ParseTypeSpecifierSeq(std::string &type_name) { + while (true) { + bool type_specifier = ParseTypeSpecifier(type_name); + if (!type_specifier) { + break; + } + } +} + +// Parse a type_specifier. +// +// type_specifier: +// ["::"] [nested_name_specifier] type_name +// +// Returns TRUE if a type_specifier was successfully parsed at this location. +// +bool DILParser::ParseTypeSpecifier(std::string &user_type_name) { + // 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. + user_type_name = 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: 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 index b1f91e55353f3..c999b420f066e 100644 --- a/lldb/test/API/commands/frame/var-dil/expr/Casts/TestFrameVarDILCast.py +++ b/lldb/test/API/commands/frame/var-dil/expr/Casts/TestFrameVarDILCast.py @@ -117,10 +117,34 @@ def test_type_cast(self): substrs=["cannot convert 'ns::Foo' to 'int'"], ) + # 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") @@ -180,6 +204,8 @@ def test_type_cast(self): error=True, substrs=["cannot cast from type 'double' to pointer type 'char *'"], ) + 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_var_path("*(int*)(void*)ap", type="int", value="1") 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 index 3977283f54cc6..7c83f18b70a34 100644 --- a/lldb/test/API/commands/frame/var-dil/expr/Casts/main.cpp +++ b/lldb/test/API/commands/frame/var-dil/expr/Casts/main.cpp @@ -8,6 +8,14 @@ typedef int myint; class Foo {}; +namespace inner { + +using mydouble = double; + +class Foo {}; + +} // namespace inner + } // namespace ns int main(int argc, char **argv) { @@ -33,12 +41,24 @@ int main(int argc, char **argv) { 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(); + 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}}; >From 0a7b7467d3a1f73ccfddac1afae08585bd97fa5b Mon Sep 17 00:00:00 2001 From: Caroline Tice <[email protected]> Date: Thu, 15 Jan 2026 14:58:08 -0800 Subject: [PATCH 2/3] - Use std::optional<std::string> for return values in ParseTypeName and ParseTypeSpecifier. - Remove output parameter from ParseTypeSpecifier. - Other small reviewer requested edits. --- lldb/include/lldb/ValueObject/DILParser.h | 4 +- lldb/source/ValueObject/DILParser.cpp | 62 +++++++++++------------ 2 files changed, 32 insertions(+), 34 deletions(-) diff --git a/lldb/include/lldb/ValueObject/DILParser.h b/lldb/include/lldb/ValueObject/DILParser.h index 3a6261aaac5fc..2defcf7bf3793 100644 --- a/lldb/include/lldb/ValueObject/DILParser.h +++ b/lldb/include/lldb/ValueObject/DILParser.h @@ -108,8 +108,8 @@ class DILParser { std::optional<CompilerType> ParseBuiltinType(); std::optional<CompilerType> ParseTypeId(); void ParseTypeSpecifierSeq(std::string &type_name); - bool ParseTypeSpecifier(std::string &user_type_name); - std::string ParseTypeName(); + std::optional<std::string> ParseTypeSpecifier(); + std::optional<std::string> ParseTypeName(); CompilerType ResolveTypeDeclarators(CompilerType type, const std::vector<Token> &ptr_operators); diff --git a/lldb/source/ValueObject/DILParser.cpp b/lldb/source/ValueObject/DILParser.cpp index de09dd5f612bc..51e25fd732456 100644 --- a/lldb/source/ValueObject/DILParser.cpp +++ b/lldb/source/ValueObject/DILParser.cpp @@ -414,7 +414,7 @@ std::optional<CompilerType> DILParser::ParseTypeId() { std::string type_name; ParseTypeSpecifierSeq(type_name); - if (type_name.size() == 0) + if (type_name.empty()) return {}; type = ResolveTypeByName(type_name, m_ctx_scope); if (!type.IsValid()) @@ -493,10 +493,10 @@ std::optional<CompilerType> DILParser::ParseBuiltinType() { // void DILParser::ParseTypeSpecifierSeq(std::string &type_name) { while (true) { - bool type_specifier = ParseTypeSpecifier(type_name); - if (!type_specifier) { + std::optional<std::string> err_or_string = ParseTypeSpecifier(); + if (!err_or_string) break; - } + type_name = *err_or_string; } } @@ -507,38 +507,36 @@ void DILParser::ParseTypeSpecifierSeq(std::string &type_name) { // // Returns TRUE if a type_specifier was successfully parsed at this location. // -bool DILParser::ParseTypeSpecifier(std::string &user_type_name) { +std::optional<std::string> DILParser::ParseTypeSpecifier() { // 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. - user_type_name = llvm::formatv("{0}{1}{2}", global_scope ? "::" : "", - nested_name_specifier, type_name); - return true; - } + // Try parsing optional global scope operator. + bool global_scope = false; + if (CurToken().Is(Token::coloncolon)) { + global_scope = true; + m_dil_lexer.Advance(); } + // Try parsing optional nested_name_specifier. + auto nested_name_specifier = ParseNestedNameSpecifier(); + + // Try parsing required type_name. + auto type_name_or_err = ParseTypeName(); + if (!type_name_or_err) + return type_name_or_err; + std::string type_name = *type_name_or_err; + + // 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. + return llvm::formatv("{0}{1}{2}", global_scope ? "::" : "", + nested_name_specifier, type_name); + // No type_specifier was found here. - return false; + return {}; } // Parse a type_name. @@ -557,10 +555,10 @@ bool DILParser::ParseTypeSpecifier(std::string &user_type_name) { // typedef_name // identifier // -std::string DILParser::ParseTypeName() { +std::optional<std::string> DILParser::ParseTypeName() { // Typename always starts with an identifier. if (CurToken().IsNot(Token::identifier)) { - return ""; + return std::nullopt; } // Otherwise look for a class_name, enum_name or a typedef_name. >From 4b5a62bbd2e87b59c898868af979b8097148abce Mon Sep 17 00:00:00 2001 From: Caroline Tice <[email protected]> Date: Thu, 15 Jan 2026 15:15:42 -0800 Subject: [PATCH 3/3] Update type cast test to verify that if a user variable and a user-defined type have the same name, DIL prefers to interpret the name as the variable. --- .../var-dil/expr/Casts/TestFrameVarDILCast.py | 16 ++++++++++++++++ .../commands/frame/var-dil/expr/Casts/main.cpp | 8 ++++++++ 2 files changed, 24 insertions(+) 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 index c999b420f066e..8f7948ec82406 100644 --- a/lldb/test/API/commands/frame/var-dil/expr/Casts/TestFrameVarDILCast.py +++ b/lldb/test/API/commands/frame/var-dil/expr/Casts/TestFrameVarDILCast.py @@ -259,3 +259,19 @@ def test_type_cast(self): 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") + + # Test casting to user-defined type with same name as variable. + + self.expect_var_path("myStruct", type="myName") + self.expect_var_path("myName", type="int", value="37") + + # Here 'myName' is treated as a variable, not a type, so '(myName)' + # is parsed as a variable expression and 'InnerFoo' is unexpected, + # and a type cast is not attempted. + self.expect( + "frame variable '(myName)InnerFoo'", + error=True, + substrs=[ + "expected 'eof', got: <'InnerFoo' (identifier)>" + ], + ) 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 index 7c83f18b70a34..cdd4b8dfe72b2 100644 --- a/lldb/test/API/commands/frame/var-dil/expr/Casts/main.cpp +++ b/lldb/test/API/commands/frame/var-dil/expr/Casts/main.cpp @@ -62,5 +62,13 @@ int main(int argc, char **argv) { int arr_1d[] = {1, 2, 3, 4}; int arr_2d[2][3] = {{1, 2, 3}, {4, 5, 6}}; + struct myName { + int x; + int y; + }; + + struct myName myStruct = { 98, 99 }; + int myName = 37; + return 0; // Set a breakpoint here } _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
