[Lldb-commits] [lldb] [lldb] Recognize flag forms in DWARFFormValue's constant accessors (PR #229738)

Yao Qi via lldb-commits lldb-commits at lists.llvm.org
Wed Oct 7 05:21:17 PDT 2026


https://github.com/qiyao created https://github.com/llvm/llvm-project/pull/229738

LLDB crashes on parsing a `DW_TAG_LLVM_ptrauth_type` with
`DW_AT_LLVM_ptrauth_address_discriminated`, here is the crash
inside lldb,

```
thread #1, stop reason = libc++: .../usr/include/c++/v1/optional:1150:libc++ Hardening assertion this->has_value() failed: optional operator*
called on a disengaged value

frame #2: llvm::DWARFTypePrinter<...>::appendUnqualifiedNameAfter(...)::'lambda'(...)::operator()(Attr=DW_AT_LLVM_ptrauth_address_discriminated) const at DWARFTypePrinter.h:375
   374        if (auto Form = D.find(Attr))
-> 375          return *Form->getAsUnsignedConstant();

(lldb) frame variable Form
(std::optional<DWARFFormValue>) Form =  Has Value=true  {
  Value = { m_form = DW_FORM_flag_present, m_value = (uval = 1, ...) }
}
```

The dereference that actually traps is one level deeper:
`Form->getAsUnsignedConstant()` itself returns `std::nullopt`, because
`DWARFFormValue::getAsUnsignedConstant()` and `getAsSignedConstant()`
only accept forms where `IsDataForm()` is true, and `IsDataForm()`
lists `sdata`, `udata`, and `data1` through `data8`, but not
`DW_FORM_flag` or `DW_FORM_flag_present`.

DWARF form values are grouped into classes, and `constant` and `flag`
are two separate classes. LLVM's own `DWARFFormValue` (in
`llvm/lib/DebugInfo/DWARF`) keeps that distinction: its
`getAsUnsignedConstant()` and `getAsSignedConstant()` check
`isFormClass(FC_Constant) || isFormClass(FC_Flag)`. LLDB's
`DWARFFormValue`'s two accessors check only `IsDataForm()`,that is
the divergence this crash exposes.

Add a new `IsFlagForm()`, parallel to the existing `IsBlockForm()`, and
have `getAsUnsignedConstant()` and `getAsSignedConstant()` accept
either `IsDataForm()` or `IsFlagForm()`, matching LLVM's own
`FC_Constant`/`FC_Flag` check. `DWARFFormValue::ExtractValue()` already
stores a flag's 0/1 value in `m_value.uval` the same way it stores any
other data form's value, so there is nothing to lose by accepting it
here.

Add a regression test building a class template whose argument type is
an address-discriminated `DW_TAG_LLVM_ptrauth_type`, then calling
`GetDIEClassTemplateParams`.


>From c47c907415c385c0a5f56aa3fc0b1f68e2e60b13 Mon Sep 17 00:00:00 2001
From: Yao Qi <yao_qi at apple.com>
Date: Fri, 2 Oct 2026 16:52:26 +0100
Subject: [PATCH] [lldb] Recognize flag forms in DWARFFormValue's constant
 accessors

LLDB crashes on parsing a `DW_TAG_LLVM_ptrauth_type` with
`DW_AT_LLVM_ptrauth_address_discriminated`, here is the crash
inside lldb,

```
thread #1, stop reason = libc++: .../usr/include/c++/v1/optional:1150:libc++ Hardening assertion this->has_value() failed: optional operator*
called on a disengaged value

frame #2: llvm::DWARFTypePrinter<...>::appendUnqualifiedNameAfter(...)::'lambda'(...)::operator()(Attr=DW_AT_LLVM_ptrauth_address_discriminated) const at DWARFTypePrinter.h:375
   374        if (auto Form = D.find(Attr))
-> 375          return *Form->getAsUnsignedConstant();

(lldb) frame variable Form
(std::optional<DWARFFormValue>) Form =  Has Value=true  {
  Value = { m_form = DW_FORM_flag_present, m_value = (uval = 1, ...) }
}
```

The dereference that actually traps is one level deeper:
`Form->getAsUnsignedConstant()` itself returns `std::nullopt`, because
`DWARFFormValue::getAsUnsignedConstant()` and `getAsSignedConstant()`
only accept forms where `IsDataForm()` is true, and `IsDataForm()`
lists `sdata`, `udata`, and `data1` through `data8`, but not
`DW_FORM_flag` or `DW_FORM_flag_present`.

DWARF form values are grouped into classes, and `constant` and `flag`
are two separate classes. LLVM's own `DWARFFormValue` (in
`llvm/lib/DebugInfo/DWARF`) keeps that distinction: its
`getAsUnsignedConstant()` and `getAsSignedConstant()` check
`isFormClass(FC_Constant) || isFormClass(FC_Flag)`. LLDB's
`DWARFFormValue`'s two accessors check only `IsDataForm()`,that is
the divergence this crash exposes.

Add a new `IsFlagForm()`, parallel to the existing `IsBlockForm()`, and
have `getAsUnsignedConstant()` and `getAsSignedConstant()` accept
either `IsDataForm()` or `IsFlagForm()`, matching LLVM's own
`FC_Constant`/`FC_Flag` check. `DWARFFormValue::ExtractValue()` already
stores a flag's 0/1 value in `m_value.uval` the same way it stores any
other data form's value, so there is nothing to lose by accepting it
here.

Add a regression test building a class template whose argument type is
an address-discriminated `DW_TAG_LLVM_ptrauth_type`, then calling
`GetDIEClassTemplateParams`.
---
 .../SymbolFile/DWARF/DWARFFormValue.cpp       | 16 +++-
 .../Plugins/SymbolFile/DWARF/DWARFFormValue.h |  1 +
 .../DWARF/DWARFASTParserClangTests.cpp        | 95 +++++++++++++++++++
 3 files changed, 110 insertions(+), 2 deletions(-)

diff --git a/lldb/source/Plugins/SymbolFile/DWARF/DWARFFormValue.cpp b/lldb/source/Plugins/SymbolFile/DWARF/DWARFFormValue.cpp
index c599da27f4701..e0d51a525e7d8 100644
--- a/lldb/source/Plugins/SymbolFile/DWARF/DWARFFormValue.cpp
+++ b/lldb/source/Plugins/SymbolFile/DWARF/DWARFFormValue.cpp
@@ -584,13 +584,14 @@ uint64_t DWARFFormValue::Reference(dw_offset_t base_offset) const {
 }
 
 std::optional<uint64_t> DWARFFormValue::getAsUnsignedConstant() const {
-  if ((!IsDataForm(m_form)) || m_form == llvm::dwarf::DW_FORM_sdata)
+  if ((!IsDataForm(m_form) && !IsFlagForm(m_form)) ||
+      m_form == llvm::dwarf::DW_FORM_sdata)
     return std::nullopt;
   return m_value.uval;
 }
 
 std::optional<int64_t> DWARFFormValue::getAsSignedConstant() const {
-  if ((!IsDataForm(m_form)) ||
+  if ((!IsDataForm(m_form) && !IsFlagForm(m_form)) ||
       (m_form == llvm::dwarf::DW_FORM_udata &&
        uint64_t(std::numeric_limits<int64_t>::max()) < m_value.uval))
     return std::nullopt;
@@ -640,6 +641,17 @@ bool DWARFFormValue::IsDataForm(const dw_form_t form) {
   llvm_unreachable("All cases handled above!");
 }
 
+bool DWARFFormValue::IsFlagForm(const dw_form_t form) {
+  switch (form) {
+  case DW_FORM_flag:
+  case DW_FORM_flag_present:
+    return true;
+  default:
+    return false;
+  }
+  llvm_unreachable("All cases handled above!");
+}
+
 bool DWARFFormValue::FormIsSupported(dw_form_t form) {
   switch (form) {
     case DW_FORM_addr:
diff --git a/lldb/source/Plugins/SymbolFile/DWARF/DWARFFormValue.h b/lldb/source/Plugins/SymbolFile/DWARF/DWARFFormValue.h
index 613948f2f3c9b..def06d59c86fe 100644
--- a/lldb/source/Plugins/SymbolFile/DWARF/DWARFFormValue.h
+++ b/lldb/source/Plugins/SymbolFile/DWARF/DWARFFormValue.h
@@ -72,6 +72,7 @@ class DWARFFormValue {
                         lldb::offset_t *offset_ptr, const DWARFUnit *unit);
   static bool IsBlockForm(const dw_form_t form);
   static bool IsDataForm(const dw_form_t form);
+  static bool IsFlagForm(const dw_form_t form);
   static int Compare(const DWARFFormValue &a, const DWARFFormValue &b);
   void Clear();
   static bool FormIsSupported(dw_form_t form);
diff --git a/lldb/unittests/SymbolFile/DWARF/DWARFASTParserClangTests.cpp b/lldb/unittests/SymbolFile/DWARF/DWARFASTParserClangTests.cpp
index 4d4dc57816694..729b47c7d08d0 100644
--- a/lldb/unittests/SymbolFile/DWARF/DWARFASTParserClangTests.cpp
+++ b/lldb/unittests/SymbolFile/DWARF/DWARFASTParserClangTests.cpp
@@ -451,6 +451,101 @@ TEST_F(DWARFASTParserClangTests, TestPtrAuthParsing) {
   ASSERT_EQ(compiler_type.GetPtrAuthDiscriminator(), 42U);
 }
 
+TEST_F(DWARFASTParserClangTests, TestPtrAuthAddressDiscriminatedTemplateParam) {
+  const char *yamldata = R"(
+--- !ELF
+FileHeader:
+  Class:   ELFCLASS64
+  Data:    ELFDATA2LSB
+  Type:    ET_EXEC
+  Machine: EM_AARCH64
+DWARF:
+  debug_str:
+    - Foo
+  debug_abbrev:
+    - ID:              0
+      Table:
+        - Code:            0x01
+          Tag:             DW_TAG_compile_unit
+          Children:        DW_CHILDREN_yes
+          Attributes:
+            - Attribute:       DW_AT_language
+              Form:            DW_FORM_data2
+        - Code:            0x02
+          Tag:             DW_TAG_structure_type
+          Children:        DW_CHILDREN_yes
+          Attributes:
+            - Attribute:       DW_AT_name
+              Form:            DW_FORM_strp
+        - Code:            0x03
+          Tag:             DW_TAG_template_type_parameter
+          Children:        DW_CHILDREN_no
+          Attributes:
+            - Attribute:       DW_AT_type
+              Form:            DW_FORM_ref4
+        - Code:            0x04
+          Tag:             DW_TAG_LLVM_ptrauth_type
+          Children:        DW_CHILDREN_no
+          Attributes:
+            - Attribute:       DW_AT_type
+              Form:            DW_FORM_ref4
+            - Attribute:       DW_AT_LLVM_ptrauth_key
+              Form:            DW_FORM_data1
+            - Attribute:       DW_AT_LLVM_ptrauth_address_discriminated
+              Form:            DW_FORM_flag_present
+        - Code:            0x05
+          Tag:             DW_TAG_pointer_type
+          Children:        DW_CHILDREN_no
+
+  debug_info:
+    - Version:         5
+      UnitType:        DW_UT_compile
+      AddrSize:        8
+      Entries:
+# 0x0c: DW_TAG_compile_unit
+        - AbbrCode:        0x01
+          Values:
+            - Value:           0x04 # DW_LANG_C_plus_plus
+
+# 0x0f:   DW_TAG_structure_type
+#           DW_AT_name [DW_FORM_strp] (\"Foo\")
+        - AbbrCode:        0x02
+          Values:
+            - Value:           0x00
+
+# 0x14:     DW_TAG_template_type_parameter
+#             DW_AT_type [DW_FORM_ref4] (0x0000001a)
+        - AbbrCode:        0x03
+          Values:
+            - Value:           0x1a
+
+        - AbbrCode:        0x00 # end of children of structure_type
+
+# 0x1a: DW_TAG_LLVM_ptrauth_type
+#         DW_AT_type [DW_FORM_ref4] (0x00000020)
+#         DW_AT_LLVM_ptrauth_key [DW_FORM_data1] (0x04)
+#         DW_AT_LLVM_ptrauth_address_discriminated [DW_FORM_flag_present] (true)
+        - AbbrCode:        0x04
+          Values:
+            - Value:           0x20
+            - Value:           0x04
+
+# 0x20: DW_TAG_pointer_type
+        - AbbrCode:        0x05
+
+        - AbbrCode:        0x00 # end of children of compile_unit
+...
+)";
+  DWARFASTParserClangYAMLTester tester(yamldata);
+
+  DWARFDIE cu_die = tester.GetCUDIE();
+  DWARFDIE struct_die = cu_die.GetFirstChild();
+  ASSERT_EQ(struct_die.Tag(), DW_TAG_structure_type);
+
+  EXPECT_EQ(tester.GetParser().GetDIEClassTemplateParams(struct_die),
+            "<void *__ptrauth(4, 1, 0x00)>");
+}
+
 struct ExtractIntFromFormValueTest : public testing::Test {
   SubsystemRAII<FileSystem, HostInfo> subsystems;
   clang_utils::TypeSystemClangHolder holder;



More information about the lldb-commits mailing list