Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP][lldb][DWARFASTParserClang] Eagerly search definitions for Objective-C classes #119860

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

Michael137
Copy link
Member

This patch essentially reverts the definition DIE delay changes (in https://github.com/llvm/llvm-project/pull/98361/files) for Objective-C.

The problem we've been seeing is as follows:

  1. An Objetive-C class extension is represented in DWARF as:
DW_TAG_structure_type
  DW_AT_declaration (true)
  DW_AT_name ("ExtendedClass")

  DW_TAG_subprogram
    DW_AT_name ("extensionMethod")
    ...

I.e., it is a forward declaration of the extended class, but that forward declaration has children methods.

  1. When we set a breakpoint in one of those methods we parse the subprogram DIE and try to create an ObjCMethodDecl for it (and attach it to the context).

  2. When parsing the subprogram DIE, we first try to complete the DeclContext. Because ExtendedClass is only a forward declaration and we don't try to fetch the definition DIE eagerly anymore, LLDB has no idea that we're dealing with an Objective-C type. So it goes ahead and constructs it as a CXXRecordDecl. This confuses DWARFASTParserClang::ParseObjCMethod because it expects the context to be an clang::ObjCObjectOrInterfaceType. So it bails and we end up crashing because we try to attach a FunctionDecl to an incomplete CXXRecordDecl (which wasn't even forcefully completed).

Since there's no good way to tell whether a forward declaration is an Objective-C type, the only solution I can really see here is to eagerly fetch the definition for Objective-C types.

…tive-C classes

This patch essentially reverts the definition DIE delay changes (in https://github.com/llvm/llvm-project/pull/98361/files) for Objective-C.

The problem we've been seeing is as follows:
1. An Objetive-C class extension is represented in DWARF as:
```
DW_TAG_structure_type
  DW_AT_declaration (true)
  DW_AT_name ("ExtendedClass")

  DW_TAG_subprogram
    DW_AT_name ("extensionMethod")
    ...
```
I.e., it is a forward declaration of the extended class, but that
forward declaration has children methods.

2. When we set a breakpoint in one of those methods we parse the
subprogram DIE and try to create an `ObjCMethodDecl` for it (and
attach it to the context).

3. When parsing the subprogram DIE, we first try to complete the
DeclContext. Because `ExtendedClass` is only a forward declaration and we don't
try to fetch the definition DIE eagerly anymore, LLDB has no idea that
we're dealing with an Objective-C type. So it goes ahead and constructs
it as a `CXXRecordDecl`. This confuses `DWARFASTParserClang::ParseObjCMethod`
because it expects the context to be an `clang::ObjCObjectOrInterfaceType`.
So it bails and we end up crashing because we try to attach a
`FunctionDecl` to an incomplete `CXXRecordDecl` (which wasn't even
forcefully completed).

Since there's no good way to tell whether a forward declaration is an
Objective-C type, the only solution I can really see here is to eagerly
fetch the definition for Objective-C types.
@Michael137 Michael137 requested a review from labath December 13, 2024 11:30
@llvmbot llvmbot added the lldb label Dec 13, 2024
@Michael137 Michael137 force-pushed the lldb/gmodules-dsym-objc-crash branch from 1096f65 to 4b1eb84 Compare December 13, 2024 11:30
@llvmbot
Copy link
Member

llvmbot commented Dec 13, 2024

@llvm/pr-subscribers-lldb

Author: Michael Buch (Michael137)

Changes

This patch essentially reverts the definition DIE delay changes (in https://github.com/llvm/llvm-project/pull/98361/files) for Objective-C.

The problem we've been seeing is as follows:

  1. An Objetive-C class extension is represented in DWARF as:
DW_TAG_structure_type
  DW_AT_declaration (true)
  DW_AT_name ("ExtendedClass")

  DW_TAG_subprogram
    DW_AT_name ("extensionMethod")
    ...

I.e., it is a forward declaration of the extended class, but that forward declaration has children methods.

  1. When we set a breakpoint in one of those methods we parse the subprogram DIE and try to create an ObjCMethodDecl for it (and attach it to the context).

  2. When parsing the subprogram DIE, we first try to complete the DeclContext. Because ExtendedClass is only a forward declaration and we don't try to fetch the definition DIE eagerly anymore, LLDB has no idea that we're dealing with an Objective-C type. So it goes ahead and constructs it as a CXXRecordDecl. This confuses DWARFASTParserClang::ParseObjCMethod because it expects the context to be an clang::ObjCObjectOrInterfaceType. So it bails and we end up crashing because we try to attach a FunctionDecl to an incomplete CXXRecordDecl (which wasn't even forcefully completed).

Since there's no good way to tell whether a forward declaration is an Objective-C type, the only solution I can really see here is to eagerly fetch the definition for Objective-C types.


Full diff: https://github.com/llvm/llvm-project/pull/119860.diff

2 Files Affected:

  • (modified) lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp (+73-30)
  • (added) lldb/test/Shell/SymbolFile/DWARF/objc-gmodules-class-extension.test (+33)
diff --git a/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp b/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp
index 37c1132c1c9f9a..131b2e6931f9f2 100644
--- a/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp
+++ b/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp
@@ -1671,43 +1671,84 @@ DWARFASTParserClang::ParseStructureLikeDIE(const SymbolContext &sc,
     attrs.is_forward_declaration = true;
   }
 
+  DWARFDIE def_die;
+  if (attrs.is_forward_declaration && cu_language == eLanguageTypeObjC) {
+    def_die = dwarf->FindDefinitionDIE(die);
+
+    if (!def_die) {
+      SymbolFileDWARFDebugMap *debug_map_symfile = dwarf->GetDebugMapSymfile();
+      if (debug_map_symfile) {
+        // We weren't able to find a full declaration in this DWARF,
+        // see if we have a declaration anywhere else...
+        def_die = debug_map_symfile->FindDefinitionDIE(die);
+      }
+    }
+
+    if (log) {
+      dwarf->GetObjectFile()->GetModule()->LogMessage(
+          log,
+          "SymbolFileDWARF({0:p}) - {1:x16}}: {2} ({3}) type \"{4}\" is a "
+          "forward declaration, complete DIE is {5}",
+          static_cast<void *>(this), die.GetID(), DW_TAG_value_to_name(tag),
+          tag, attrs.name.GetCString(),
+          def_die ? llvm::utohexstr(def_die.GetID()) : "not found");
+    }
+
+    if (def_die) {
+      if (auto [it, inserted] = dwarf->GetDIEToType().try_emplace(
+              def_die.GetDIE(), DIE_IS_BEING_PARSED);
+          !inserted) {
+        if (it->getSecond() == nullptr ||
+            it->getSecond() == DIE_IS_BEING_PARSED)
+          return nullptr;
+        return it->getSecond()->shared_from_this();
+      }
+      attrs = ParsedDWARFTypeAttributes(def_die);
+    }
+  }
+
+  if (!def_die)
+    def_die = die;
+
   if (attrs.name) {
-    GetUniqueTypeNameAndDeclaration(die, cu_language, unique_typename,
+    GetUniqueTypeNameAndDeclaration(def_die, cu_language, unique_typename,
                                     unique_decl);
     if (log) {
       dwarf->GetObjectFile()->GetModule()->LogMessage(
           log, "SymbolFileDWARF({0:p}) - {1:x16}: {2} has unique name: {3} ",
-          static_cast<void *>(this), die.GetID(), DW_TAG_value_to_name(tag),
+          static_cast<void *>(this), def_die.GetID(), DW_TAG_value_to_name(tag),
           unique_typename.AsCString());
     }
     if (UniqueDWARFASTType *unique_ast_entry_type =
             dwarf->GetUniqueDWARFASTTypeMap().Find(
-                unique_typename, die, unique_decl, byte_size,
+                unique_typename, def_die, unique_decl, byte_size,
                 attrs.is_forward_declaration)) {
       if (TypeSP type_sp = unique_ast_entry_type->m_type_sp) {
-        dwarf->GetDIEToType()[die.GetDIE()] = type_sp.get();
+        dwarf->GetDIEToType()[def_die.GetDIE()] = type_sp.get();
         LinkDeclContextToDIE(
-            GetCachedClangDeclContextForDIE(unique_ast_entry_type->m_die), die);
+            GetCachedClangDeclContextForDIE(unique_ast_entry_type->m_die),
+            def_die);
         // If the DIE being parsed in this function is a definition and the
         // entry in the map is a declaration, then we need to update the entry
         // to point to the definition DIE.
         if (!attrs.is_forward_declaration &&
             unique_ast_entry_type->m_is_forward_declaration) {
-          unique_ast_entry_type->UpdateToDefDIE(die, unique_decl, byte_size);
+          unique_ast_entry_type->UpdateToDefDIE(def_die, unique_decl,
+                                                byte_size);
           clang_type = type_sp->GetForwardCompilerType();
 
           CompilerType compiler_type_no_qualifiers =
               ClangUtil::RemoveFastQualifiers(clang_type);
           dwarf->GetForwardDeclCompilerTypeToDIE().insert_or_assign(
               compiler_type_no_qualifiers.GetOpaqueQualType(),
-              *die.GetDIERef());
+              *def_die.GetDIERef());
         }
         return type_sp;
       }
     }
   }
 
-  DEBUG_PRINTF("0x%8.8" PRIx64 ": %s (\"%s\")\n", die.GetID(),
+  DEBUG_PRINTF("0x%8.8" PRIx64 ": %s (\"%s\")\n", def_die.GetID(),
                DW_TAG_value_to_name(tag), type_name_cstr);
 
   int tag_decl_kind = -1;
@@ -1726,14 +1767,14 @@ DWARFASTParserClang::ParseStructureLikeDIE(const SymbolContext &sc,
   if ((attrs.class_language == eLanguageTypeObjC ||
        attrs.class_language == eLanguageTypeObjC_plus_plus) &&
       !attrs.is_complete_objc_class &&
-      die.Supports_DW_AT_APPLE_objc_complete_type()) {
+      def_die.Supports_DW_AT_APPLE_objc_complete_type()) {
     // We have a valid eSymbolTypeObjCClass class symbol whose name
     // matches the current objective C class that we are trying to find
     // and this DIE isn't the complete definition (we checked
     // is_complete_objc_class above and know it is false), so the real
     // definition is in here somewhere
     TypeSP type_sp =
-        dwarf->FindCompleteObjCDefinitionTypeForDIE(die, attrs.name, true);
+        dwarf->FindCompleteObjCDefinitionTypeForDIE(def_die, attrs.name, true);
 
     if (!type_sp) {
       SymbolFileDWARFDebugMap *debug_map_symfile = dwarf->GetDebugMapSymfile();
@@ -1741,7 +1782,7 @@ DWARFASTParserClang::ParseStructureLikeDIE(const SymbolContext &sc,
         // We weren't able to find a full declaration in this DWARF,
         // see if we have a declaration anywhere else...
         type_sp = debug_map_symfile->FindCompleteObjCDefinitionTypeForDIE(
-            die, attrs.name, true);
+            def_die, attrs.name, true);
       }
     }
 
@@ -1751,8 +1792,9 @@ DWARFASTParserClang::ParseStructureLikeDIE(const SymbolContext &sc,
             log,
             "SymbolFileDWARF({0:p}) - {1:x16}: {2} ({3}) type \"{4}\" is an "
             "incomplete objc type, complete type is {5:x8}",
-            static_cast<void *>(this), die.GetID(), DW_TAG_value_to_name(tag),
-            tag, attrs.name.GetCString(), type_sp->GetID());
+            static_cast<void *>(this), def_die.GetID(),
+            DW_TAG_value_to_name(tag), tag, attrs.name.GetCString(),
+            type_sp->GetID());
       }
       return type_sp;
     }
@@ -1761,7 +1803,7 @@ DWARFASTParserClang::ParseStructureLikeDIE(const SymbolContext &sc,
   if (attrs.is_forward_declaration) {
     // See if the type comes from a Clang module and if so, track down
     // that type.
-    TypeSP type_sp = ParseTypeFromClangModule(sc, die, log);
+    TypeSP type_sp = ParseTypeFromClangModule(sc, def_die, log);
     if (type_sp)
       return type_sp;
   }
@@ -1769,10 +1811,10 @@ DWARFASTParserClang::ParseStructureLikeDIE(const SymbolContext &sc,
   assert(tag_decl_kind != -1);
   UNUSED_IF_ASSERT_DISABLED(tag_decl_kind);
   clang::DeclContext *containing_decl_ctx =
-      GetClangDeclContextContainingDIE(die, nullptr);
+      GetClangDeclContextContainingDIE(def_die, nullptr);
 
   PrepareContextToReceiveMembers(m_ast, GetClangASTImporter(),
-                                 containing_decl_ctx, die,
+                                 containing_decl_ctx, def_die,
                                  attrs.name.GetCString());
 
   if (attrs.accessibility == eAccessNone && containing_decl_ctx) {
@@ -1785,31 +1827,32 @@ DWARFASTParserClang::ParseStructureLikeDIE(const SymbolContext &sc,
   }
 
   ClangASTMetadata metadata;
-  metadata.SetUserID(die.GetID());
-  metadata.SetIsDynamicCXXType(dwarf->ClassOrStructIsVirtual(die));
+  metadata.SetUserID(def_die.GetID());
+  metadata.SetIsDynamicCXXType(dwarf->ClassOrStructIsVirtual(def_die));
 
   TypeSystemClang::TemplateParameterInfos template_param_infos;
-  if (ParseTemplateParameterInfos(die, template_param_infos)) {
+  if (ParseTemplateParameterInfos(def_die, template_param_infos)) {
     clang::ClassTemplateDecl *class_template_decl =
         m_ast.ParseClassTemplateDecl(
-            containing_decl_ctx, GetOwningClangModule(die), attrs.accessibility,
-            attrs.name.GetCString(), tag_decl_kind, template_param_infos);
+            containing_decl_ctx, GetOwningClangModule(def_die),
+            attrs.accessibility, attrs.name.GetCString(), tag_decl_kind,
+            template_param_infos);
     if (!class_template_decl) {
       if (log) {
         dwarf->GetObjectFile()->GetModule()->LogMessage(
             log,
             "SymbolFileDWARF({0:p}) - {1:x16}: {2} ({3}) type \"{4}\" "
             "clang::ClassTemplateDecl failed to return a decl.",
-            static_cast<void *>(this), die.GetID(), DW_TAG_value_to_name(tag),
-            tag, attrs.name.GetCString());
+            static_cast<void *>(this), def_die.GetID(),
+            DW_TAG_value_to_name(tag), tag, attrs.name.GetCString());
       }
       return TypeSP();
     }
 
     clang::ClassTemplateSpecializationDecl *class_specialization_decl =
         m_ast.CreateClassTemplateSpecializationDecl(
-            containing_decl_ctx, GetOwningClangModule(die), class_template_decl,
-            tag_decl_kind, template_param_infos);
+            containing_decl_ctx, GetOwningClangModule(def_die),
+            class_template_decl, tag_decl_kind, template_param_infos);
     clang_type =
         m_ast.CreateClassTemplateSpecializationType(class_specialization_decl);
 
@@ -1819,13 +1862,13 @@ DWARFASTParserClang::ParseStructureLikeDIE(const SymbolContext &sc,
 
   if (!clang_type) {
     clang_type = m_ast.CreateRecordType(
-        containing_decl_ctx, GetOwningClangModule(die), attrs.accessibility,
+        containing_decl_ctx, GetOwningClangModule(def_die), attrs.accessibility,
         attrs.name.GetCString(), tag_decl_kind, attrs.class_language, metadata,
         attrs.exports_symbols);
   }
 
   TypeSP type_sp = dwarf->MakeType(
-      die.GetID(), attrs.name, attrs.byte_size, nullptr, LLDB_INVALID_UID,
+      def_die.GetID(), attrs.name, attrs.byte_size, nullptr, LLDB_INVALID_UID,
       Type::eEncodingIsUID, &attrs.decl, clang_type,
       Type::ResolveState::Forward,
       TypePayloadClang(OptionalClangModuleID(), attrs.is_complete_objc_class));
@@ -1835,7 +1878,7 @@ DWARFASTParserClang::ParseStructureLikeDIE(const SymbolContext &sc,
   // function prototypes.
   clang::DeclContext *type_decl_ctx =
       TypeSystemClang::GetDeclContextForType(clang_type);
-  LinkDeclContextToDIE(type_decl_ctx, die);
+  LinkDeclContextToDIE(type_decl_ctx, def_die);
 
   // UniqueDWARFASTType is large, so don't create a local variables on the
   // stack, put it on the heap. This function is often called recursively and
@@ -1846,7 +1889,7 @@ DWARFASTParserClang::ParseStructureLikeDIE(const SymbolContext &sc,
   // copies of the same type over and over in the ASTContext for our
   // module
   unique_ast_entry_up->m_type_sp = type_sp;
-  unique_ast_entry_up->m_die = die;
+  unique_ast_entry_up->m_die = def_die;
   unique_ast_entry_up->m_declaration = unique_decl;
   unique_ast_entry_up->m_byte_size = byte_size;
   unique_ast_entry_up->m_is_forward_declaration = attrs.is_forward_declaration;
@@ -1862,7 +1905,7 @@ DWARFASTParserClang::ParseStructureLikeDIE(const SymbolContext &sc,
       dwarf->GetForwardDeclCompilerTypeToDIE()
           .try_emplace(
               ClangUtil::RemoveFastQualifiers(clang_type).GetOpaqueQualType(),
-              *die.GetDIERef())
+              *def_die.GetDIERef())
           .second;
   assert(inserted && "Type already in the forward declaration map!");
   (void)inserted;
diff --git a/lldb/test/Shell/SymbolFile/DWARF/objc-gmodules-class-extension.test b/lldb/test/Shell/SymbolFile/DWARF/objc-gmodules-class-extension.test
new file mode 100644
index 00000000000000..c78eb30c3889ec
--- /dev/null
+++ b/lldb/test/Shell/SymbolFile/DWARF/objc-gmodules-class-extension.test
@@ -0,0 +1,33 @@
+# REQUIRES: system-darwin
+
+# Test that we can set a breakpoint in a method of a class extension.
+# This requires us to parse the method into an AST type, and the context
+# too (which in DWARF is just a forward declaration).
+#
+# RUN: split-file %s %t
+# RUN: %clangxx_host %t/lib.m  -c -g -gmodules -fmodules -o %t/lib.o
+# RUN: %clangxx_host %t/main.m -g -gmodules -fmodules %t/lib.o -o %t/a.out -framework Foundation
+#
+# RUN: %lldb %t/a.out -o "breakpoint set -f lib.m -l 6" -o exit | FileCheck %s
+
+# CHECK: (lldb) breakpoint set -f lib.m -l 6
+# CHECK: Breakpoint 1: where = a.out`-[NSObject(Foo) func]
+
+#--- main.m
+int main() {
+  return 0;
+}
+
+#--- lib.m
+#import <Foundation/Foundation.h>
+
+@implementation NSObject (Foo)
+- (NSError *)func {
+    NSLog(@"Hello, World!");
+    return 0;
+}
+@end
+
+NSObject * func() {
+  return 0;
+}

@Michael137
Copy link
Member Author

Marked WIP for now because I think the test-case can be reduced even further, and we can probably extract some of this patch into helper functions. But wanted to hear from @labath and @ZequanWu whether any of this could be dangerous.

@@ -1671,43 +1671,84 @@ DWARFASTParserClang::ParseStructureLikeDIE(const SymbolContext &sc,
attrs.is_forward_declaration = true;
}

DWARFDIE def_die;
if (attrs.is_forward_declaration && cu_language == eLanguageTypeObjC) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is pretty much copied from ParseEnum

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You'd also need to check for eLanguageTypeObjCPlusPlus here.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would also be helpful to add a comment explaining why this block exists.

if (attrs.name) {
GetUniqueTypeNameAndDeclaration(die, cu_language, unique_typename,
GetUniqueTypeNameAndDeclaration(def_die, cu_language, unique_typename,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find the name def_die misleading here. IIUC, it may be a def die if the block above was run and successful?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1, for non-objc/objc++ cases, the parameter die could be a def die or decl die. Maybe move def_die into the above block and update the parameter die to def_die if def_die is valid.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only catch there is that then die needs to be a value parameter, but I think that's a fine tradeoff. I contemplated doing that in #96484 (which is where the def_die name comes from, I think), and I didn't do it only because knew (well, I thought I knew) that code was going to be temporary).

@ZequanWu
Copy link
Contributor

Since there's no good way to tell whether a forward declaration is an Objective-C type, the only solution I can really see here is to eagerly fetch the definition for Objective-C types.

That makes sense.

@labath
Copy link
Collaborator

labath commented Dec 16, 2024

Unfortunate, but I guess there's nothing we can do to avoid it.

Michael137 added a commit to Michael137/llvm-project that referenced this pull request Dec 16, 2024
…ions

In Objective-C, forward declarations are currently represented as:
```
DW_TAG_structure_type
  DW_AT_name                ("Foo")
  DW_AT_declaration         (true)
  DW_AT_APPLE_runtime_class (DW_LANG_ObjC)
```
However, when compiling with `-gmodules`, when a class definition
is turned into a forward declaration within a `DW_TAG_module`, the
DIE for the forward declaration looks as follows:
```
DW_TAG_structure_type
  DW_AT_name                ("Foo")
  DW_AT_declaration         (true)
```

Note the absence of `DW_AT_APPLE_runtime_class`. With recent changes in
LLDB, not being able to differentiate between C++ and Objective-C
forward declarations has become problematic (see attached test-case
and explanation in llvm#119860).
Michael137 added a commit to Michael137/llvm-project that referenced this pull request Dec 16, 2024
…ions

In Objective-C, forward declarations are currently represented as:
```
DW_TAG_structure_type
  DW_AT_name                ("Foo")
  DW_AT_declaration         (true)
  DW_AT_APPLE_runtime_class (DW_LANG_ObjC)
```
However, when compiling with `-gmodules`, when a class definition
is turned into a forward declaration within a `DW_TAG_module`, the
DIE for the forward declaration looks as follows:
```
DW_TAG_structure_type
  DW_AT_name                ("Foo")
  DW_AT_declaration         (true)
```

Note the absence of `DW_AT_APPLE_runtime_class`. With recent changes in
LLDB, not being able to differentiate between C++ and Objective-C
forward declarations has become problematic (see attached test-case
and explanation in llvm#119860).
@Michael137
Copy link
Member Author

On second thought, this is more a bug in the -gmodules debug-info generation than it is in LLDB. The changes to how we fetch the definitions for structures just exposed it. Put up a patch so -gmodules doesn't drop the DW_AT_APPLE_runtime_class from the forward declarations that it creates: #120154, which resolves this issue. It doesn't require any changes to LLDB (it seems like we don't need the ObjCObjectOrInterfaceType to have a definition in order to attach methods to it).

Though we might still need to do this eager definition fetching for users who can't recompile their objective-c codebases with newer compilers. Also, I'm not entirely sure this patch here is correct yet. I think the way it's currently written doesn't handle the case where we call ParseStructureLikeDIE with a forward declaration, and then later on with a definition DIE. For C++ structures we handle that by updating the UniqueTypeMap with the definition DIE, etc. We'll probably have to do what we do for enums and also put the definition DIE into the DIEToType map.

Michael137 added a commit to swiftlang/llvm-project that referenced this pull request Dec 17, 2024
…ions

In Objective-C, forward declarations are currently represented as:
```
DW_TAG_structure_type
  DW_AT_name                ("Foo")
  DW_AT_declaration         (true)
  DW_AT_APPLE_runtime_class (DW_LANG_ObjC)
```
However, when compiling with `-gmodules`, when a class definition
is turned into a forward declaration within a `DW_TAG_module`, the
DIE for the forward declaration looks as follows:
```
DW_TAG_structure_type
  DW_AT_name                ("Foo")
  DW_AT_declaration         (true)
```

Note the absence of `DW_AT_APPLE_runtime_class`. With recent changes in
LLDB, not being able to differentiate between C++ and Objective-C
forward declarations has become problematic (see attached test-case
and explanation in llvm#119860).

(cherry picked from commit 7c7fd60)
Michael137 added a commit to Michael137/llvm-project that referenced this pull request Dec 17, 2024
…ions

In Objective-C, forward declarations are currently represented as:
```
DW_TAG_structure_type
  DW_AT_name                ("Foo")
  DW_AT_declaration         (true)
  DW_AT_APPLE_runtime_class (DW_LANG_ObjC)
```
However, when compiling with `-gmodules`, when a class definition
is turned into a forward declaration within a `DW_TAG_module`, the
DIE for the forward declaration looks as follows:
```
DW_TAG_structure_type
  DW_AT_name                ("Foo")
  DW_AT_declaration         (true)
```

Note the absence of `DW_AT_APPLE_runtime_class`. With recent changes in
LLDB, not being able to differentiate between C++ and Objective-C
forward declarations has become problematic (see attached test-case
and explanation in llvm#119860).
Michael137 added a commit that referenced this pull request Dec 17, 2024
…ions (#120154)

In Objective-C, forward declarations are currently represented as:
```
DW_TAG_structure_type
  DW_AT_name                ("Foo")
  DW_AT_declaration         (true)
  DW_AT_APPLE_runtime_class (DW_LANG_ObjC)
```
However, when compiling with `-gmodules`, when a class definition is
turned into a forward declaration within a `DW_TAG_module`, the DIE for
the forward declaration looks as follows:
```
DW_TAG_structure_type
  DW_AT_name                ("Foo")
  DW_AT_declaration         (true)
```

Note the absence of `DW_AT_APPLE_runtime_class`. With recent changes in
LLDB, not being able to differentiate between C++ and Objective-C
forward declarations has become problematic (see attached test-case and
explanation in #119860).
@ZequanWu
Copy link
Contributor

I think the way it's currently written doesn't handle the case where we call ParseStructureLikeDIE with a forward declaration, and then later on with a definition DIE. For C++ structures we handle that by updating the UniqueTypeMap with the definition DIE, etc. We'll probably have to do what we do for enums and also put the definition DIE into the DIEToType map.

In UniqueTypeMap, C++ is only special in that the unique name is constructed by walking all the way up in the DIE tree to get its fully qualified name. If we can do something similar when constructing the unique names for objc/objc++, that map should be reusable.

Actually, there's already a block to eagerly find the complete type when !is_complete_objc_class for objc/objc++:

if ((attrs.class_language == eLanguageTypeObjC ||
attrs.class_language == eLanguageTypeObjC_plus_plus) &&
!attrs.is_complete_objc_class) {
. How is it different from the change in this PR? Is is_complete_objc_class the same as !is_forward_declaration?

@Michael137
Copy link
Member Author

I think the way it's currently written doesn't handle the case where we call ParseStructureLikeDIE with a forward declaration, and then later on with a definition DIE. For C++ structures we handle that by updating the UniqueTypeMap with the definition DIE, etc. We'll probably have to do what we do for enums and also put the definition DIE into the DIEToType map.

In UniqueTypeMap, C++ is only special in that the unique name is constructed by walking all the way up in the DIE tree to get its fully qualified name. If we can do something similar when constructing the unique names for objc/objc++, that map should be reusable.

Actually, there's already a block to eagerly find the complete type when !is_complete_objc_class for objc/objc++:

if ((attrs.class_language == eLanguageTypeObjC ||
attrs.class_language == eLanguageTypeObjC_plus_plus) &&
!attrs.is_complete_objc_class) {

. How is it different from the change in this PR? Is is_complete_objc_class the same as !is_forward_declaration?

Yea that part of the ObjC support is particularly confusing. I added some context here: #120279

It previously had pretty much no coverage (and for some reason frame var doesn't need it). Any class that has is_complete_objc_class is definitely !is_forward_declaration, but the lack of is_complete_objc_class doesn't mean it's a forward declaration. There's 3 states of an objective-c type: (1) forward declaration as in other C-languages, (2) interface declarations (which aren't forward declarations in the C and C++ sense), and (3) interface implementations. If is_complete_objc_class == true it indicates (3). If !is_complete_objc_class && !is_forward_declaration then we're dealing with (2). Otherwise, is_forward_declaration is (1) (which never has is_complete_objc_class set).

@ZequanWu
Copy link
Contributor

I think the way it's currently written doesn't handle the case where we call ParseStructureLikeDIE with a forward declaration, and then later on with a definition DIE. For C++ structures we handle that by updating the UniqueTypeMap with the definition DIE, etc. We'll probably have to do what we do for enums and also put the definition DIE into the DIEToType map.

In UniqueTypeMap, C++ is only special in that the unique name is constructed by walking all the way up in the DIE tree to get its fully qualified name. If we can do something similar when constructing the unique names for objc/objc++, that map should be reusable.
Actually, there's already a block to eagerly find the complete type when !is_complete_objc_class for objc/objc++:

if ((attrs.class_language == eLanguageTypeObjC ||
attrs.class_language == eLanguageTypeObjC_plus_plus) &&
!attrs.is_complete_objc_class) {

. How is it different from the change in this PR? Is is_complete_objc_class the same as !is_forward_declaration?

Yea that part of the ObjC support is particularly confusing. I added some context here: #120279

It previously had pretty much no coverage (and for some reason frame var doesn't need it). Any class that has is_complete_objc_class is definitely !is_forward_declaration, but the lack of is_complete_objc_class doesn't mean it's a forward declaration. There's 3 states of an objective-c type: (1) forward declaration as in other C-languages, (2) interface declarations (which aren't forward declarations in the C and C++ sense), and (3) interface implementations. If is_complete_objc_class == true it indicates (3). If !is_complete_objc_class && !is_forward_declaration then we're dealing with (2). Otherwise, is_forward_declaration is (1) (which never has is_complete_objc_class set).

IIUC for (1) and (2), that block eagerly finds the definition die and resolve it in FindCompleteObjCDefinitionTypeForDIE via GetCompleteObjCClass which recursively call into ParseStructureLikeDIE with the def die. But this PR also eagerly just finds the definition die for (1) in FindDefinitionDIE via GetFullyQualifiedType and then create a lldb type from the definition die and clang ast type, and I guess that's all you need. I have 2 questions: 1. I doubt if GetFullyQualifiedType is the same as GetCompleteObjCClass in terms of finding the right def die for objc. 2. #96484 doesn't remove the recursive calls to ParseStructureLikeDIE when FindCompleteObjCDefinitionTypeForDIE is called. We need to refactor FindCompleteObjCDefinitionTypeForDIE to split the work of finding definition die and resolving the definition die, so it's consistent with c/c++.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants