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

[Clang][Sema] Fix type of enumerators in incomplete enumerations #84068

Merged
merged 1 commit into from
Mar 12, 2024

Conversation

Kupa-Martin
Copy link
Contributor

@Kupa-Martin Kupa-Martin commented Mar 5, 2024

Enumerators dont have the type of their enumeration before the closing brace. In these cases Expr::getEnumCoercedType() incorrectly returned the enumeration type.

Introduced in PR #81418
Fixes #84712

@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" labels Mar 5, 2024
@llvmbot
Copy link
Collaborator

llvmbot commented Mar 5, 2024

@llvm/pr-subscribers-clang

Author: None (Kupa-Martin)

Changes

Enumerators dont have the type of their enumeration before the closing brace. In these cases Expr::getEnumCoercedType() incorrectly returned the enumeration type.

Introduced in PR #81418


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

2 Files Affected:

  • (modified) clang/lib/AST/Expr.cpp (+6-3)
  • (modified) clang/test/Sema/warn-compare-enum-types-mismatch.c (+3-1)
diff --git a/clang/lib/AST/Expr.cpp b/clang/lib/AST/Expr.cpp
index b4de2155adcebd..9a28e6f750069b 100644
--- a/clang/lib/AST/Expr.cpp
+++ b/clang/lib/AST/Expr.cpp
@@ -264,10 +264,13 @@ namespace {
 }
 
 QualType Expr::getEnumCoercedType(const ASTContext &Ctx) const {
-  if (isa<EnumType>(this->getType()))
+  if (isa<EnumType>(this->getType())) {
     return this->getType();
-  else if (const auto *ECD = this->getEnumConstantDecl())
-    return Ctx.getTypeDeclType(cast<EnumDecl>(ECD->getDeclContext()));
+  } else if (const auto *ECD = this->getEnumConstantDecl()) {
+    const auto *ED = cast<EnumDecl>(ECD->getDeclContext());
+    if (ED->isCompleteDefinition())
+      return Ctx.getTypeDeclType(ED);
+  }
   return this->getType();
 }
 
diff --git a/clang/test/Sema/warn-compare-enum-types-mismatch.c b/clang/test/Sema/warn-compare-enum-types-mismatch.c
index 2b72aae16b977a..1094a6972e778d 100644
--- a/clang/test/Sema/warn-compare-enum-types-mismatch.c
+++ b/clang/test/Sema/warn-compare-enum-types-mismatch.c
@@ -6,7 +6,9 @@ typedef enum EnumA {
 } EnumA;
 
 enum EnumB {
-  B
+  B,
+  B1 = 1,
+  B2 = A == B1
 };
 
 enum {

@Kupa-Martin
Copy link
Contributor Author

@erichkeane I cant add the previous reviewers myself, could you do it for me please?

clang/lib/AST/Expr.cpp Outdated Show resolved Hide resolved
@Sirraide
Copy link
Contributor

Sirraide commented Mar 5, 2024

Also, this probably needs a release note.

@Kupa-Martin
Copy link
Contributor Author

Also, this probably needs a release note.

If you want I'll add one but this bug has been on main no longer than a week, so I didnt think it would be necessary.

@Sirraide
Copy link
Contributor

Sirraide commented Mar 5, 2024

Also, this probably needs a release note.

If you want I'll add one but this bug has been on main no longer than a week, so I didnt think it would be necessary.

I see. Yeah, I don’t think we really need one if the bug was introduced and fixed in the same version.

@erichkeane
Copy link
Collaborator

Also, this probably needs a release note.

If you want I'll add one but this bug has been on main no longer than a week, so I didnt think it would be necessary.

I see. Yeah, I don’t think we really need one if the bug was introduced and fixed in the same version.

Thats correct, a release note is only necessary in cases where the bug existed in a release.

Copy link
Collaborator

@shafik shafik left a comment

Choose a reason for hiding this comment

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

Should we also have a C++ test for this fix?

@Kupa-Martin
Copy link
Contributor Author

Should we also have a C++ test for this fix?

clang/test/Sema/warn-compare-enum-types-mismatch.c should cover both C and C++. Or do you mean some other kind of test?

Copy link
Collaborator

@AaronBallman AaronBallman left a comment

Choose a reason for hiding this comment

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

Hmm, the rules are different between C and C++, but I think we may be okay. In C++:

https://eel.is/c++draft/enum#dcl.enum-5.sentence-6
Following the closing brace of an enum-specifier, each enumerator has the type of its enumeration.

In C23 6.7.3.3p12:
During the processing of each enumeration constant in the enumerator list, the type of the enumeration constant shall be:

p15:
The enumeration member type for an enumerated type without fixed underlying type upon completion is:
— int if all the values of the enumeration are representable as an int; or,
— the enumerated type.140)

Footnote 140) The integer type selected during processing of the enumerator list (before completion) of the enumeration may not be the same as the compatible implementation-defined integer type selected for the completed enumeration.

So I believe:

typedef enum EnumA {
  A
} EnumA;

enum EnumB {
  B,
  B1 = 1,
  B2 = A == B1
};

is not an enum compare warning in C++ because B1 doesn't have an enumeration type due to the enumeration not being fully-defined, and is not an enum compare warning in C because A has type int (due to p15) and B1 has type int (due to p12).

I think we play a bit fast-and-loose with the way we treat enum constants in C in Clang, but that's outside of the scope of this patch. So LGTM aside from some tiny nits!

Comment on lines 267 to 273
if (isa<EnumType>(getType())) {
return getType();
} else if (const auto *ECD = getEnumConstantDecl()) {
const auto *ED = cast<EnumDecl>(ECD->getDeclContext());
if (ED->isCompleteDefinition())
return Ctx.getTypeDeclType(ED);
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
if (isa<EnumType>(getType())) {
return getType();
} else if (const auto *ECD = getEnumConstantDecl()) {
const auto *ED = cast<EnumDecl>(ECD->getDeclContext());
if (ED->isCompleteDefinition())
return Ctx.getTypeDeclType(ED);
}
if (isa<EnumType>(getType()))
return getType();
if (const auto *ECD = getEnumConstantDecl()) {
const auto *ED = cast<EnumDecl>(ECD->getDeclContext());
if (ED->isCompleteDefinition())
return Ctx.getTypeDeclType(ED);
}

No else after return via our coding standard, NFC

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

@@ -6,7 +6,9 @@ typedef enum EnumA {
} EnumA;

enum EnumB {
B
B,
Copy link
Collaborator

Choose a reason for hiding this comment

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

It might be good to capture some of what I wrote in the review summary as a comment here so it's clear why this is correct in both C and C++.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think what I was asking, was do we have an equivalent C++ test that verifies in a .cpp file that we also do not obtain a diagnostic for this.

@Kupa-Martin
Copy link
Contributor Author

not an enum compare warning in C++ because B1 doesn't have an enumeration type due to the enumeration not being fully-defined, and is not an enum compare warning in C because A has type int (due to p15) and B1 has type int (due to p12).

Yes, you are correct.

I think we play a bit fast-and-loose with the way we treat enum constants in C in Clang, but that's outside of the scope of this patch.

There are enough differences between the C23 and C++ standards to produce different diagnostics. I've made a godbolt snippet that tries to keep track of the type of different enumerators in C23 and C++; we warn when we shouldnt and we dont warn when we should. In any case, this patch didnt intend to cover C23.

@shafik
Copy link
Collaborator

shafik commented Mar 9, 2024

So I believe:

typedef enum EnumA {
  A
} EnumA;

enum EnumB {
  B,
  B1 = 1,
  B2 = A == B1
};

is not an enum compare warning in C++ because B1 doesn't have an enumeration type due to the enumeration not being fully-defined, and is not an enum compare warning in C because A has type int (due to p15) and B1 has type int (due to p12).

Right, so do we have a test for C++ that verifies that.

@Kupa-Martin
Copy link
Contributor Author

@shafik We dont have a dedicated cpp test for this. I can add one if you want, but clang/test/Sema/warn-compare-enum-types-mismatch.c runs clang both on C and C++ mode, so I didnt think it necessary.

@shafik
Copy link
Collaborator

shafik commented Mar 11, 2024

@shafik We dont have a dedicated cpp test for this. I can add one if you want, but clang/test/Sema/warn-compare-enum-types-mismatch.c runs clang both on C and C++ mode, so I didnt think it necessary.

I think we just a bug that demonstrates this issue: #84712

So if this fixes that as well then we should add tests for those cases too.

@Sirraide
Copy link
Contributor

Sirraide commented Mar 11, 2024

Regarding #84712: From what I can tell, the behaviour of cases such as

enum e1 { a };
enum e2 { b = a };

has changed between C++11 and C++14 (at least the wording is different starting with C++14; there may be some other section that states the same for C++11, but I couldn’t find one); specifically, C++11’s [dcl.enum]p5 states that:

If the underlying type is not fixed, the type of each enumerator is the type
of its initializing value:

  • If an initializer is specified for an enumerator, the initializing value has the same type as the expression
    and the constant-expression shall be an integral constant expression (5.19)

Whereas in C++14 (and later), that same section reads:

If the underlying type is not fixed, the type of each enumerator prior
to the closing brace is determined as follows:

  • If an initializer is specified for an enumerator, the constant-expression shall be an integral constant
    expression (5.20). If the expression has unscoped enumeration type, the enumerator has the underlying
    type of that enumeration type, otherwise it has the same type as the expression
    .

That is, not only is the type of b above not e2 before the closing brace, but it would seem that its type is supposed to be e1 before C++14, and whatever the underlying type of e1 is starting with C++14.

@Kupa-Martin
Copy link
Contributor Author

Kupa-Martin commented Mar 11, 2024

@shafik We dont have a dedicated cpp test for this. I can add one if you want, but clang/test/Sema/warn-compare-enum-types-mismatch.c runs clang both on C and C++ mode, so I didnt think it necessary.

I think we just a bug that demonstrates this issue: #84712

So if this fixes that as well then we should add tests for those cases too.

I've included a separate test for C++. If no further changes are needed, could someone please merge this for me? I dont have commit access.

Copy link
Collaborator

@AaronBallman AaronBallman left a comment

Choose a reason for hiding this comment

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

Aside from some formatting issues, this LGTM. Once you push up a fix for the formatting, I'll land. Thank you!

@@ -1,12 +1,19 @@
// RUN: %clang_cc1 -x c -fsyntax-only -verify -Wenum-compare -Wno-unused-comparison %s
// RUN: %clang_cc1 -x c++ -fsyntax-only -verify -Wenum-compare -Wno-unused-comparison %s

// In C enumerators (i.e enumeration constants) have type int (until C23). In order to support diagnostics such as
Copy link
Collaborator

Choose a reason for hiding this comment

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

There's trailing whitespace here and it goes over the 80-col limit; can you reformat?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

B
B,
B1 = 1,
// In C++ this comparison doesnt warn as enumerators dont have the type of their enumeration before the closing
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same issue here as above with trailing whitespace and 80 col.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

Enumerators dont have the type of their enumeration before the
closing brace. In these cases Expr::getEnumCoercedType()
incorrectly returned the enumeration type.

Introduced in PR llvm#81418
Fixes llvm#84712
@AaronBallman AaronBallman merged commit f8fab21 into llvm:main Mar 12, 2024
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[clang] Invalid rejection of enum arithmetic
6 participants