Skip to content

[lldb] Print empty enums as if they were unrecognised normal enums #97553

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

Merged
merged 1 commit into from
Jul 3, 2024

Conversation

DavidSpickett
Copy link
Collaborator

Fixes #97514

Given this example:

enum E {};

int main()
{
    E x = E(0);
    E y = E(1);
    E z = E(2);
    return 0;
}

lldb used to print nothing for x, but 0x1 for y and 0x2 for z.

At first this seemed like the 0 case needed fixing but the real issue here is that en enum with no enumerators was being detected as a "bitfield like enum".

Which is an enum where all enumerators are a single bit value, or the sum of previous single bit values.

For these we do not print anything for a value of 0, as we assume it must be the remainder after we've printed the other bits that were set (I think this is also unfortunate, but I'm not addressing that here).

Clearly an enum with no enumerators cannot be being used as a bitfield, so check that up front and print it as if it's a normal enum where we didn't match any of the enumerators. This means you now get:

(lldb) p x
(E) 0
(lldb) p y
(E) 1
(lldb) p z
(E) 2

Which is a change to decimal from hex, but I think it's overall more consistent. Printing hex here was never a concious decision.

@llvmbot
Copy link
Member

llvmbot commented Jul 3, 2024

@llvm/pr-subscribers-lldb

Author: David Spickett (DavidSpickett)

Changes

Fixes #97514

Given this example:

enum E {};

int main()
{
    E x = E(0);
    E y = E(1);
    E z = E(2);
    return 0;
}

lldb used to print nothing for x, but 0x1 for y and 0x2 for z.

At first this seemed like the 0 case needed fixing but the real issue here is that en enum with no enumerators was being detected as a "bitfield like enum".

Which is an enum where all enumerators are a single bit value, or the sum of previous single bit values.

For these we do not print anything for a value of 0, as we assume it must be the remainder after we've printed the other bits that were set (I think this is also unfortunate, but I'm not addressing that here).

Clearly an enum with no enumerators cannot be being used as a bitfield, so check that up front and print it as if it's a normal enum where we didn't match any of the enumerators. This means you now get:

(lldb) p x
(E) 0
(lldb) p y
(E) 1
(lldb) p z
(E) 2

Which is a change to decimal from hex, but I think it's overall more consistent. Printing hex here was never a concious decision.


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

2 Files Affected:

  • (modified) lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp (+18-13)
  • (modified) lldb/unittests/ValueObject/DumpValueObjectOptionsTests.cpp (+22-6)
diff --git a/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp b/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp
index 093d27a92d718..960c9f6acde57 100644
--- a/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp
+++ b/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp
@@ -8649,19 +8649,24 @@ static bool DumpEnumValue(const clang::QualType &qual_type, Stream &s,
   // At the same time, we're applying a heuristic to determine whether we want
   // to print this enum as a bitfield. We're likely dealing with a bitfield if
   // every enumerator is either a one bit value or a superset of the previous
-  // enumerators. Also 0 doesn't make sense when the enumerators are used as
-  // flags.
-  for (auto *enumerator : enum_decl->enumerators()) {
-    uint64_t val = enumerator->getInitVal().getSExtValue();
-    val = llvm::SignExtend64(val, 8*byte_size);
-    if (llvm::popcount(val) != 1 && (val & ~covered_bits) != 0)
-      can_be_bitfield = false;
-    covered_bits |= val;
-    ++num_enumerators;
-    if (val == enum_svalue) {
-      // Found an exact match, that's all we need to do.
-      s.PutCString(enumerator->getNameAsString());
-      return true;
+  // enumerators. Also an enumerator of 0 doesn't make sense when the
+  // enumerators are used as flags.
+  clang::EnumDecl::enumerator_range enumerators = enum_decl->enumerators();
+  if (enumerators.empty())
+    can_be_bitfield = false;
+  else {
+    for (auto *enumerator : enum_decl->enumerators()) {
+      uint64_t val = enumerator->getInitVal().getSExtValue();
+      val = llvm::SignExtend64(val, 8 * byte_size);
+      if (llvm::popcount(val) != 1 && (val & ~covered_bits) != 0)
+        can_be_bitfield = false;
+      covered_bits |= val;
+      ++num_enumerators;
+      if (val == enum_svalue) {
+        // Found an exact match, that's all we need to do.
+        s.PutCString(enumerator->getNameAsString());
+        return true;
+      }
     }
   }
 
diff --git a/lldb/unittests/ValueObject/DumpValueObjectOptionsTests.cpp b/lldb/unittests/ValueObject/DumpValueObjectOptionsTests.cpp
index 6cb982d7f5980..767f19872f858 100644
--- a/lldb/unittests/ValueObject/DumpValueObjectOptionsTests.cpp
+++ b/lldb/unittests/ValueObject/DumpValueObjectOptionsTests.cpp
@@ -71,12 +71,13 @@ class ValueObjectMockProcessTest : public ::testing::Test {
   }
 
   CompilerType
-  MakeEnumType(const std::vector<std::pair<const char *, int>> enumerators) {
-    CompilerType uint_type = m_type_system->GetBuiltinTypeForEncodingAndBitSize(
-        lldb::eEncodingUint, 32);
+  MakeEnumType(const std::vector<std::pair<const char *, int>> enumerators,
+               bool is_signed) {
+    CompilerType int_type = m_type_system->GetBuiltinTypeForEncodingAndBitSize(
+        is_signed ? lldb::eEncodingSint : lldb::eEncodingUint, 32);
     CompilerType enum_type = m_type_system->CreateEnumerationType(
         "TestEnum", m_type_system->GetTranslationUnitDecl(),
-        OptionalClangModuleID(), Declaration(), uint_type, false);
+        OptionalClangModuleID(), Declaration(), int_type, false);
 
     m_type_system->StartTagDeclarationDefinition(enum_type);
     Declaration decl;
@@ -123,12 +124,27 @@ class ValueObjectMockProcessTest : public ::testing::Test {
   lldb::ProcessSP m_process_sp;
 };
 
+TEST_F(ValueObjectMockProcessTest, EmptyEnum) {
+  // All values of an empty enum should be shown as plain numbers.
+  TestDumpValueObject(MakeEnumType({}, false),
+                      {{0, {}, "(TestEnum) test_var = 0\n"},
+                       {1, {}, "(TestEnum) test_var = 1\n"},
+                       {2, {}, "(TestEnum) test_var = 2\n"}});
+
+  TestDumpValueObject(MakeEnumType({}, true),
+                      {{-2, {}, "(TestEnum) test_var = -2\n"},
+                       {-1, {}, "(TestEnum) test_var = -1\n"},
+                       {0, {}, "(TestEnum) test_var = 0\n"},
+                       {1, {}, "(TestEnum) test_var = 1\n"},
+                       {2, {}, "(TestEnum) test_var = 2\n"}});
+}
+
 TEST_F(ValueObjectMockProcessTest, Enum) {
   // This is not a bitfield-like enum, so values are printed as decimal by
   // default. Also we only show the enumerator name if the value is an
   // exact match.
   TestDumpValueObject(
-      MakeEnumType({{"test_2", 2}, {"test_3", 3}}),
+      MakeEnumType({{"test_2", 2}, {"test_3", 3}}, false),
       {{0, {}, "(TestEnum) test_var = 0\n"},
        {1, {}, "(TestEnum) test_var = 1\n"},
        {2, {}, "(TestEnum) test_var = test_2\n"},
@@ -152,7 +168,7 @@ TEST_F(ValueObjectMockProcessTest, BitFieldLikeEnum) {
   // as hex, a value of 0 shows nothing, and values with no exact enumerator are
   // shown as combinations of the other values.
   TestDumpValueObject(
-      MakeEnumType({{"test_2", 2}, {"test_4", 4}}),
+      MakeEnumType({{"test_2", 2}, {"test_4", 4}}, false),
       {
           {0, {}, "(TestEnum) test_var =\n"},
           {1, {}, "(TestEnum) test_var = 0x1\n"},

@DavidSpickett DavidSpickett requested a review from clayborg July 3, 2024 10:36
// Found an exact match, that's all we need to do.
s.PutCString(enumerator->getNameAsString());
return true;
// enumerators. Also an enumerator of 0 doesn't make sense when the
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I clarified this to "an enumerator of 0 doesn't make sense", which is my understanding of it. As opposed to "a value of 0 doesn't make sense".

Copy link
Member

@Michael137 Michael137 left a comment

Choose a reason for hiding this comment

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

makes sense

Fixes llvm#97514

Given this example:
```
enum E {};

int main()
{
    E x = E(0);
    E y = E(1);
    E z = E(2);
    return 0;
}
```
lldb used to print nothing for `x`, but `0x1` for `y` and `0x2` for
`z`.

At first this seemed like the 0 case needed fixing but the real issue
here is that en enum with no enumerators was being detected as a
"bitfield like enum".

Which is an enum where all enumerators are a single bit value,
or the sum of previous single bit values.

For these we do not print anything for a value of 0, as we assume
it must be the remainder after we've printed the other bits that
were set (I think this is also unfortunate, but I'm not addressing that
here).

Clearly an enum with no enumerators cannot be being used as a bitfield,
so check that up front and print it as if it's a normal enum where we didn't
match any of the enumerators. This means you now get:
```
(lldb) p x
(E) 0
(lldb) p y
(E) 1
(lldb) p z
(E) 2
```

Which is a change to decimal from hex, but I think it's overall more consistent.
Printing hex here was never a concious decision.
@DavidSpickett DavidSpickett merged commit 41fddc4 into llvm:main Jul 3, 2024
4 of 5 checks passed
@DavidSpickett DavidSpickett deleted the lldb-emptyenum branch July 3, 2024 13:47
lravenclaw pushed a commit to lravenclaw/llvm-project that referenced this pull request Jul 3, 2024
…lvm#97553)

Fixes llvm#97514

Given this example:
```
enum E {};

int main()
{
    E x = E(0);
    E y = E(1);
    E z = E(2);
    return 0;
}
```
lldb used to print nothing for `x`, but `0x1` for `y` and `0x2` for `z`.

At first this seemed like the 0 case needed fixing but the real issue
here is that en enum with no enumerators was being detected as a
"bitfield like enum".

Which is an enum where all enumerators are a single bit value, or the
sum of previous single bit values.

For these we do not print anything for a value of 0, as we assume it
must be the remainder after we've printed the other bits that were set
(I think this is also unfortunate, but I'm not addressing that here).

Clearly an enum with no enumerators cannot be being used as a bitfield,
so check that up front and print it as if it's a normal enum where we
didn't match any of the enumerators. This means you now get:
```
(lldb) p x
(E) 0
(lldb) p y
(E) 1
(lldb) p z
(E) 2
```

Which is a change to decimal from hex, but I think it's overall more
consistent. Printing hex here was never a concious decision.
@mstorsjo
Copy link
Member

mstorsjo commented Jul 3, 2024

This causes build errors with GCC (11):

../../lldb/unittests/ValueObject/DumpValueObjectOptionsTests.cpp: In member function ‘virtual void ValueObjectMockProcessTest_EmptyEnum_Test::TestBody()’:
../../lldb/unittests/ValueObject/DumpValueObjectOptionsTests.cpp:134:22: error: narrowing conversion of ‘-2’ from ‘int’ to ‘unsigned int’ [-Wnarrowing]
  134 |   TestDumpValueObject(MakeEnumType({}, true),
      |   ~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~
  135 |                       {{-2, {}, "(TestEnum) test_var = -2\n"},
      |                       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  136 |                        {-1, {}, "(TestEnum) test_var = -1\n"},
      |                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  137 |                        {0, {}, "(TestEnum) test_var = 0\n"},
      |                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  138 |                        {1, {}, "(TestEnum) test_var = 1\n"},
      |                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  139 |                        {2, {}, "(TestEnum) test_var = 2\n"}});

@Prabhuk
Copy link
Contributor

Prabhuk commented Jul 3, 2024

We are seeing the same failure in LLDB tests. Can we please revert this change?

DavidSpickett added a commit that referenced this pull request Jul 4, 2024
…enums (#97553)"

This reverts commit 41fddc4.

Due to build errors with gcc passing signed ints to unsigned ints.
DavidSpickett added a commit that referenced this pull request Jul 4, 2024
…enums (#97553)"

This reverts commit 927def4.

I've refactored the tests so that we're explicit about whether the
enum is signed or not. Which means we use the proper types
throughout.
kbluck pushed a commit to kbluck/llvm-project that referenced this pull request Jul 6, 2024
…lvm#97553)

Fixes llvm#97514

Given this example:
```
enum E {};

int main()
{
    E x = E(0);
    E y = E(1);
    E z = E(2);
    return 0;
}
```
lldb used to print nothing for `x`, but `0x1` for `y` and `0x2` for `z`.

At first this seemed like the 0 case needed fixing but the real issue
here is that en enum with no enumerators was being detected as a
"bitfield like enum".

Which is an enum where all enumerators are a single bit value, or the
sum of previous single bit values.

For these we do not print anything for a value of 0, as we assume it
must be the remainder after we've printed the other bits that were set
(I think this is also unfortunate, but I'm not addressing that here).

Clearly an enum with no enumerators cannot be being used as a bitfield,
so check that up front and print it as if it's a normal enum where we
didn't match any of the enumerators. This means you now get:
```
(lldb) p x
(E) 0
(lldb) p y
(E) 1
(lldb) p z
(E) 2
```

Which is a change to decimal from hex, but I think it's overall more
consistent. Printing hex here was never a concious decision.
kbluck pushed a commit to kbluck/llvm-project that referenced this pull request Jul 6, 2024
…enums (llvm#97553)"

This reverts commit 41fddc4.

Due to build errors with gcc passing signed ints to unsigned ints.
kbluck pushed a commit to kbluck/llvm-project that referenced this pull request Jul 6, 2024
…enums (llvm#97553)"

This reverts commit 927def4.

I've refactored the tests so that we're explicit about whether the
enum is signed or not. Which means we use the proper types
throughout.
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.

[lldb] Unnamed zero enum is printed as empty string rather than 0x0
5 participants