Skip to content

[clang] Fix implicit integer conversion for opaque enums declared in class templates #121039

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 6 commits into from
Jan 10, 2025

Conversation

thebrandre
Copy link
Contributor

@thebrandre thebrandre commented Dec 24, 2024

This commit fixes issues with enumeration types instantiated from an opaque-enum-declarations
(see [dcl.enum]) in class templates broke basic assumptions during parsing of arithmetic
expressions due to absent (NULL TYPE) promotion types of instances of EnumDecl.

To this end, we repeat the simple steps in Sema::ActOnTag to evaluate the promotion type
of a fixed enumeration based on its underlying type (see C++11 [conv.prom] p4).

Note that if, instead, a full enum-specifier (subsequent curly braces) is provided,
Sema::ActOnEnumBody is re-invoked on template instantiation anyway overriding the
promotion type and hiding the issue. This is analog to how enumerations declarations
outside of template declarations are handled.
Note that, in contrast to Sema::ActOnEnumBody, Sema::ActOnTag is not called again
for the instantiated enumeration type.

Fixes #117960.

Copy link

Thank you for submitting a Pull Request (PR) to the LLVM Project!

This PR will be automatically labeled and the relevant teams will be notified.

If you wish to, you can add reviewers by using the "Reviewers" section on this page.

If this is not working for you, it is probably because you do not have write permissions for the repository. In which case you can instead tag reviewers by name in a comment by using @ followed by their GitHub username.

If you have received no comments on your PR for a week, you can request a review by "ping"ing the PR by adding a comment “Ping”. The common courtesy "ping" rate is once a week. Please remember that you are asking for valuable time from other developers.

If you have further questions, they may be answered by the LLVM GitHub User Guide.

You can also ask questions in a comment on this PR, on the LLVM Discord or on the forums.

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

llvmbot commented Dec 24, 2024

@llvm/pr-subscribers-clang

Author: None (thebrandre)

Changes

This commit fixes implicit integer conversions for opaque-enum-declarations inside a class template with a specified enum-base (see [dcl.enum]).

Previously, the promotion type of the instantiated enum was set only in Sema::ActOnEnumBody, which is not called if there are no curly braces are after the enum-base. This fixes GitHub issue #117960.


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

2 Files Affected:

  • (modified) clang/lib/Sema/SemaTemplateInstantiateDecl.cpp (+3)
  • (added) clang/test/SemaCXX/enum-base-in-class-template.cpp (+66)
diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
index e058afe81da589..4d39fd409795b6 100644
--- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
@@ -1620,6 +1620,9 @@ Decl *TemplateDeclInstantiator::VisitEnumDecl(EnumDecl *D) {
   if (isDeclWithinFunction(D) ? D == Def : Def && !Enum->isScoped()) {
     SemaRef.CurrentInstantiationScope->InstantiatedLocal(D, Enum);
     InstantiateEnumDefinition(Enum, Def);
+  } else {
+    if (D->isFixed() && !Def)
+      Enum->setPromotionType(Enum->getIntegerType());
   }
 
   return Enum;
diff --git a/clang/test/SemaCXX/enum-base-in-class-template.cpp b/clang/test/SemaCXX/enum-base-in-class-template.cpp
new file mode 100644
index 00000000000000..012bef9785cbaf
--- /dev/null
+++ b/clang/test/SemaCXX/enum-base-in-class-template.cpp
@@ -0,0 +1,66 @@
+// RUN: %clang_cc1 -std=c++11 -fsyntax-only %s -verify
+// RUN: %clang_cc1 -std=c++20 -fsyntax-only %s -verify
+
+// This program causes clang 19 and earlier to crash because
+// EnumDecl::PromotionType has not been set on the instantiated enum.
+// See GitHub Issue #117960.
+namespace Issue117960 {
+template <typename T>
+struct A {
+  enum E : T;
+};
+
+int b = A<int>::E{} + 0;
+}
+
+
+namespace test {
+template <typename T1, typename T2>
+struct IsSame {
+  static constexpr bool check() { return false; }
+};
+
+template <typename T>
+struct IsSame<T, T> {
+  static constexpr bool check() { return true; }
+};
+}  // namespace test
+
+
+template <typename T>
+struct S1 {
+  enum E : T;
+};
+// checks if EnumDecl::PromotionType is set
+int X1 = S1<int>::E{} + 0;
+int Y1 = S1<unsigned>::E{} + 0;
+static_assert(test::IsSame<decltype(S1<int>::E{}+0), int>::check(), "");
+static_assert(test::IsSame<decltype(S1<unsigned>::E{}+0), unsigned>::check(), "");
+char Z1 = S1<unsigned>::E(-1) + 0; // expected-warning{{implicit conversion from 'unsigned int' to 'char'}}
+
+template <typename Traits>
+struct S2 {
+  enum E : typename Traits::IntegerType;
+};
+
+template <typename T>
+struct Traits {
+  typedef T IntegerType;
+};
+
+int X2 = S2<Traits<int>>::E{} + 0;
+int Y2 = S2<Traits<unsigned>>::E{} + 0;
+static_assert(test::IsSame<decltype(S2<Traits<int>>::E{}+0), int>::check(), "");
+static_assert(test::IsSame<decltype(S2<Traits<unsigned>>::E{}+0), unsigned>::check(), "");
+
+
+template <typename T>
+struct S3 {
+  enum E : unsigned;
+};
+
+int X3 = S3<float>::E{} + 0;
+
+// fails in clang 19 and earlier (see the discussion on GitHub Issue #117960):
+static_assert(test::IsSame<decltype(S3<float>::E{}+0), unsigned>::check(), "");
+

@cor3ntin
Copy link
Contributor

Thanks for the PR. Can you update the commit message to be more descriptive?

I'm not sure the fix is sufficient.
I think most of the checks done in ActOnEnumBody should be (re) performed on instantiation.

Maybe we should instead

  • Add a new BuildEnumBody function, move most (all?) the implementation of ActOnEnumBody to that
  • call BuildEnumBody from both ActOnEnumBody and RebuildEnumType

That would not only fix the crash but also the fact that we are seemingly missing a lot of diagnostics
https://godbolt.org/z/17dTW4dEe

@thebrandre
Copy link
Contributor Author

Thanks for the feedback @cor3ntin! 🙂

I will change the title of the PR and of the commit message to:

[clang] Fix implicit integer conversion for opaque enum declarations in class templates

adopting the terminology from [dcl.enum] if that isn't too verbose?

The other potential issues you mentioned should actually conform to the specification in [temp.spec.general] if I understand it correctly.
See here on Compiler Explorer: https://godbolt.org/z/GzYq9jzjo.
This behavior is also consistent with GCC.

My changes should not affect the behavior if ActOnEnumBody is called. Admittedly, I was very conservative when changing behavior because I am not yet very familiar with the codebase.
Do you think, we should still pursue the idea of refactoring as you described if the current behavior of Clang turns out to be correct in the situations you brought up? I'd be happy to do so. But I am not sure if it belongs into this PR? It definitely makes sense to add regression tests for this scenario if they don't already exist.

In any case, I'll try if I can find any other related scenarios where the fix isn't sufficient!

@thebrandre thebrandre changed the title [clang] Fix issue #117960 [clang] Fix implicit integer conversion for opaque enum declarations in class templates Dec 31, 2024
@thebrandre
Copy link
Contributor Author

I added regression tests for the scoped enumeration types to make sure that these are not affected.
And I also tried to improve the commit message and added comments with references to the relevant sections in the C++ standard.
But I still have to think about your refactoring/completeness suggestions.

@thebrandre thebrandre changed the title [clang] Fix implicit integer conversion for opaque enum declarations in class templates [clang] Fix implicit integer conversion for opaque enums declared in class templates Jan 5, 2025
@thebrandre
Copy link
Contributor Author

thebrandre commented Jan 5, 2025

@cor3ntin Good news! I figured it out on my own! And I now have a fix that I am confident in. 🙂
I analyzed the steps of the parser in a lot of similar cases, for which I also added quite a few regression tests.

The attributes have already been handled correctly previously. But to make sure of it, I also added tests.

Putting the few lines for the fix there, seems fairly consistent to me. It repeats the logic in Sema::ActOnTag and Sema::ActOnEnumBody (exact lines of code are linked). We would now repeat them a third time but this is unavoidable because Sema::ActOnEnumBody is called again during instantiation but Sema::ActOnTag is not.
Refactoring this is would be hard because it's not really specified what exactly the individual functions are supposed to do. And the repetition doesn't seem too bad to me.

As you suggested, I stepped through the code to check if anything else from Sema::ActOnTag might be missing. But this doesn't seem to be the case.
Some things seemed odd but non-relevant. For example for enum E : int {}; EnumDecl::NumPositiveBits is set to 1, but it stays 0 for the opaque declaration enum E : int;. I also noticed that I can trigger a case where a diagnostic gets reported twice. This, however, seems rather unrelated to me. I would rather open up a separate pull request for that.

Thanks again for your feedback @cor3ntin - it helped me to get a much cleaner solution and a better understanding of the issue even though my initial analysis might have been a little confusing ...
Well, I hope it's now ready for merging. 🙂

I rebased my branch on the current main and also "rebased away" my prior fix to make the diff more concise.

P.S.: Of course I also ran all the regression tests on my machine. And I didn't break any. 😉

@cor3ntin
Copy link
Contributor

cor3ntin commented Jan 5, 2025

Thanks for the PR. Can you update the commit message to be more descriptive?

I'm not sure the fix is sufficient.
I think most of the checks done in ActOnEnumBody should be (re) performed on instantiation.

Maybe we should instead

  • Add a new BuildEnumBody function, move most (all?) the implementation of ActOnEnumBody to that
  • call BuildEnumBody from both ActOnEnumBody and RebuildEnumType

That would not only fix the crash but also the fact that we are seemingly missing a lot of diagnostics
https://godbolt.org/z/17dTW4dEe

@erichkeane wdyt?

@thebrandre
Copy link
Contributor Author

@cor3ntin I hope there is no misunderstanding here. If so, I apologize,

I strongly agree with you that some refactoring should be applied because, as I mentioned, the diagnostics get duplicated in certain cases as shown here on Compiler Explorer: https://godbolt.org/z/473Eezhjn.
But I would prefer opening another pull request for it keeping it separate from the fix making it easier to cherry-pick.
My understanding is that all commits get squashed before being applied to main?

If there is still something wrong with the commit message, I would gladly give it another try if you could be more specific.

@erichkeane
Copy link
Collaborator

Thanks for the PR. Can you update the commit message to be more descriptive?
I'm not sure the fix is sufficient.
I think most of the checks done in ActOnEnumBody should be (re) performed on instantiation.
Maybe we should instead

  • Add a new BuildEnumBody function, move most (all?) the implementation of ActOnEnumBody to that
  • call BuildEnumBody from both ActOnEnumBody and RebuildEnumType

That would not only fix the crash but also the fact that we are seemingly missing a lot of diagnostics
https://godbolt.org/z/17dTW4dEe

@erichkeane wdyt?

I agree, it seems that the instantiation of the enum is missing quite a bit when it comes to diagnostics. We should definitely do some sort of analysis to ensure that we're getting the same diagnostics at both points.

That said, I am ok with doing this NOW, as long as the author promises to do the rest in a followup patch ASAP.

@thebrandre
Copy link
Contributor Author

@erichkeane I am not sure what you mean. As I pointed out earlier, the examples @cor3ntin brought up actually conform to the standard as in [temp.spec.general]:

The implicit instantiation of a class template specialization causes the implicit instantiation of the declarations, but not of the definitions of [...] scoped member enumerations, [...]

The issues are correctly diagnosed by clang if the struct is explicitly instantiated as demonstrated here in Compiler Explorer: https://godbolt.org/z/GzYq9jzjo.

Nevertheless, I investigated further and managed to discover some real bugs with diagnostics on template instantiation.
These affect not only enums but also nested classes. If you are interested, you can have a look: https://godbolt.org/z/vKG6vqWhs. I have already started working on these issues but so far they seem unrelated.

I plan to keep working on it but I can't promise much given the facts that I only have the weekends to spend time on it and my limited familiarity with the codebase (this would be my very first commit to LLVM). But, of course, I promise to try my best! 🙂

@erichkeane
Copy link
Collaborator

@erichkeane I am not sure what you mean. As I pointed out earlier, the examples @cor3ntin brought up actually conform to the standard as in [temp.spec.general]:

The implicit instantiation of a class template specialization causes the implicit instantiation of the declarations, but not of the definitions of [...] scoped member enumerations, [...]

The issues are correctly diagnosed by clang if the struct is explicitly instantiated as demonstrated here in Compiler Explorer: https://godbolt.org/z/GzYq9jzjo.

My quick look through the ActOnEnum and this function looked like it showed some misses. If Corentin is comfortable with our checking here, then I'm happy too.

Nevertheless, I investigated further and managed to discover some real bugs with diagnostics on template instantiation. These affect not only enums but also nested classes. If you are interested, you can have a look: https://godbolt.org/z/vKG6vqWhs. I have already started working on these issues but so far they seem unrelated.

Thats great, thanks for finding that and working on it! Those look like some pretty unfortunate misses.

I plan to keep working on it but I can't promise much given the facts that I only have the weekends to spend time on it and my limited familiarity with the codebase (this would be my very first commit to LLVM). But, of course, I promise to try my best! 🙂

Definitely appreciate it!

I'm happy when Corentin is here, I have no concerns.

@cor3ntin
Copy link
Contributor

cor3ntin commented Jan 6, 2025

We usually try to diagnose issues as soon as possible, so i still believe there are improvements to be made here. That being said, I'm happy to approve the PR as it is now.

That being said, it is missing a changelog entry in clang/docs/ReleaseNotes.rst, can you add one?

@cor3ntin
Copy link
Contributor

cor3ntin commented Jan 6, 2025

The issue number here seems wrong, can you update the description?
#121039 (comment) (#116525 is a completely unrelated issue)

thebrandre and others added 5 commits January 7, 2025 18:47
…ass templates

Repeat the steps in `Sema::ActOnTag` for the promotion type after type substitution of the
underlying type. This is required in the case of an *opaque-enum-declaration* (see [dcl.enum]).
If, instead, a full *enum-specifier* (subsequent curly braces) is provided, `Sema::ActOnEnumBody`
is re-invoked on template instantiation overriding the promotion type and hiding the issue.
Note that, in contrast to `Sema::ActOnEnumBody`, `Sema::ActOnTag` is *not* called again for the
instantiated enumeration type.

Fixes llvm#117960.
…n class templates

These tests are variants of the crash reported in GitHub issue llvm#117960,
in which an opaque-enum-declaration in a class template broke basic assumptions
during parsing of arithmetic expressions due to missing promotion types
of instances of EnumDecl.
@thebrandre
Copy link
Contributor Author

The issue number here seems wrong, can you update the description? #121039 (comment) (#116525 is a completely unrelated issue)

Thanks for pointing that out @cor3ntin. I messed that up in my last edit.

That being said, it is missing a changelog entry in clang/docs/ReleaseNotes.rst, can you add one?

Sure. You won't notice it but I literally spent half an hour thinking about this sentence. It's so difficult to be clear, technically correct, and concise at the same time.

We usually try to diagnose issues as soon as possible, so i still believe there are improvements to be made here.

I am still undecided here. As far as I understand the standard, this is valid code as long as the enum is not used. It says The implicit instantiation of a class template specialization causes [...] the implicit instantiation of the definitions of unscoped member enumerations [temp.inst]. This feature extensively used for member functions. For example std::vector::resize is not valid for types that are not default-constructible but you can still use std::vector as long as you don't use the bad member functions.
See here on Compiler Explorer https://godbolt.org/z/Mn3Y1Ka14.

Admittedly, I can't think of any sane use case for this feature on unscoped member enumerations ... but there are very creative people out there. 😉

Copy link
Contributor Author

@thebrandre thebrandre left a comment

Choose a reason for hiding this comment

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

I am not sure why the jumbled letters in "GH#117960" are still there and why I can't apply it (it says the change request is outdated). So I am going to fix that manually.

Copy link
Contributor

@cor3ntin cor3ntin left a comment

Choose a reason for hiding this comment

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

Thanks!
Do you need me to merge that for you?

Copy link
Contributor Author

@thebrandre thebrandre left a comment

Choose a reason for hiding this comment

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

And it's me again. I did review it before but didn't know I was supposed to explicitly mark each file. 🙈

@thebrandre
Copy link
Contributor Author

Thanks! Do you need me to merge that for you?

That would be great! 😊

@cor3ntin cor3ntin merged commit d6b6598 into llvm:main Jan 10, 2025
7 checks passed
Copy link

@thebrandre Congratulations on having your first Pull Request (PR) merged into the LLVM Project!

Your changes will be combined with recent changes from other authors, then tested by our build bots. If there is a problem with a build, you may receive a report in an email or a comment on this PR.

Please check whether problems have been caused by your change specifically, as the builds can include changes from many authors. It is not uncommon for your change to be included in a build that fails due to someone else's changes, or infrastructure issues.

How to do this, and the rest of the post-merge process, is covered in detail here.

If your change does cause a problem, it may be reverted, or you can revert it yourself. This is a normal part of LLVM development. You can fix your changes and open a new PR to merge them again.

If you don't get any reports, no action is required from you. Your changes are working as expected, well done!

@thebrandre thebrandre deleted the fix-issue-117960 branch January 11, 2025 07:51
BaiXilin pushed a commit to BaiXilin/llvm-fix-vnni-instr-types that referenced this pull request Jan 12, 2025
…class templates (llvm#121039)

This commit fixes issues with enumeration types instantiated from an
opaque-enum-declarations
(see [dcl.enum]) in class templates broke basic assumptions during
parsing of arithmetic
expressions due to absent (NULL TYPE) promotion types of instances of
EnumDecl.

To this end, we repeat the simple steps in `Sema::ActOnTag` to evaluate
the promotion type
of a fixed enumeration based on its underlying type (see C++11
[conv.prom] p4).

Note that if, instead, a full *enum-specifier* (subsequent curly braces)
is provided,
`Sema::ActOnEnumBody` is re-invoked on template instantiation anyway
overriding the
promotion type and hiding the issue. This is analog to how enumerations
declarations
outside of template declarations are handled.
Note that, in contrast to `Sema::ActOnEnumBody`, `Sema::ActOnTag` is
*not* called again
for the instantiated enumeration type.

Fixes llvm#117960.

---------

Co-authored-by: cor3ntin <[email protected]>
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.

Crashes When Compiling Template Enum with Function and Static Assertions
4 participants