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

-Wenum-constexpr-conversion should be a hard error, not a downgradable error #59036

Closed
carlosgalvezp opened this issue Nov 16, 2022 · 27 comments · Fixed by #102364
Closed

-Wenum-constexpr-conversion should be a hard error, not a downgradable error #59036

carlosgalvezp opened this issue Nov 16, 2022 · 27 comments · Fixed by #102364
Assignees
Labels
clang:diagnostics New/improved warning or error message in Clang, but not in clang-tidy or static analyzer enhancement Improving things as opposed to bug fixing, e.g. new or missing feature

Comments

@carlosgalvezp
Copy link
Contributor

The -Wenum-constexpr-conversion warning was created to account for the fact that casting integers to enums outside of the valid range of the enum is UB in C++17. Constant expressions invoking UB lead to an ill-formed program.

The current behavior of Clang is that it does warn and it does error, but the message is nevertheless a warning that the user can easily suppress via -Wno-enum-constexpr-conversion.

Since the program is ill-formed, I don't think this should be a warning that can be disabled. Just like one can't disable the warning in this ill-formed code:

constexpr int x[10]{};
constexpr int y = x[10];

Therefore I propose to make the diagnostic a hard error that cannot be disabled. Additionally, this "hard error" behavior should only be active in C++17 (it's currently active in for any standard).

The warning could be repurposed to be a general warning that one can enable, and is active not only in constexpr expressions, but in any expression that involves casting an integer to an enum.

@carlosgalvezp carlosgalvezp added enhancement Improving things as opposed to bug fixing, e.g. new or missing feature clang:diagnostics New/improved warning or error message in Clang, but not in clang-tidy or static analyzer labels Nov 16, 2022
@thesamesam
Copy link
Member

thesamesam commented Nov 16, 2022

https://clang.llvm.org/docs/ReleaseNotes.html#potentially-breaking-changes (for Clang 16) says it's an error by default but is currently downgradable (until Clang 17). So I guess this bug is to track remembering to do so in Clang 17.

See also #50055.

@thesamesam thesamesam changed the title -Wenum-constexpr-conversion should be a hard error, not a warning -Wenum-constexpr-conversion should be a hard error, not a downgradable error Nov 16, 2022
@carlosgalvezp
Copy link
Contributor Author

Nice, I wasn't aware of that, thanks! Yes that makes a lot of sense, thanks for pointing out. Let's keep this bug as a reminder. I will open a new ticket for having a warning that works also on non-constexpr contexts.

@tbaederr
Copy link
Contributor

Did this happen?

@tbaederr tbaederr added this to the LLVM 17.0.0 Release milestone Apr 15, 2023
kdesysadmin pushed a commit to KDE/kdevelop that referenced this issue Jul 7, 2023
This fixes the following Clang 16 compilation error:
kdevelop/plugins/clang/duchain/cursorkindtraits.h:217:7: error: integer value -1 is outside the valid range of values [0, 255] for the enumeration type 'CommonIntegralTypes' [-Wenum-constexpr-conversion]
    : static_cast<IntegralType::CommonIntegralTypes>(-1);

Quote from llvm/llvm-project#59036 :
    The -Wenum-constexpr-conversion warning was created to account for
    the fact that casting integers to enums outside of the valid range
    of the enum is UB in C++17. Constant expressions invoking UB lead to
    an ill-formed program.

BUG: 471995
FIXED-IN: 5.12.230800
carlosgalvezp pushed a commit to carlosgalvezp/llvm-project that referenced this issue Sep 22, 2023
Casting an int into an enum is undefined behavior if the int is
outside of the range of the enum. UB is not allowed in constant
expressions, therefore the compiler must produce a hard error
in that case.

However, until now, the compiler produced a warning that could be
suppressed. It should instead be a hard error, since the program
is ill-formed in that case, as per the C++ Standard.

This patch turns the warning into an error. Additionally, references
to the old warning are removed since they are now meaningless.

Fixes llvm#59036
@carlosgalvezp
Copy link
Contributor Author

@carlosgalvezp
Copy link
Contributor Author

@AaronBallman @dwblaikie We are now on Clang 19, do you think it's the right time to pull the trigger and turn this into a non-downgradable error? It has been a warning also in system headers since Clang 18.

@dwblaikie
Copy link
Collaborator

Looks like binutils is still suppressing this warning: https://sourceware.org/git/?p=binutils-gdb.git;a=blob;f=include/diagnostics.h;h=8cc2b493d2c02ba7dbc5879207746107ad7143a0;hb=refs/heads/master#l81

As is openmp in the LLVM project itself (

append_if(OPENMP_HAVE_WENUM_CONSTEXPR_CONVERSION_FLAG "-Wno-enum-constexpr-conversion" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
)

And I also found this magic_enum project disabling it too: https://github.com/Neargye/magic_enum/blob/b4ada55c8cea21f3c73c5a58cebd368a227465a1/include/magic_enum/magic_enum.hpp#L71

At least for the openmp usage and probably the binutils usage, it'd be nice to see if some progress can be made removing those warning suppressions.

@AaronBallman
Copy link
Collaborator

I expect that so long as we leave it a downgradable diagnostic, people will continue to not fix their code because that's easier or because they're unaware of the issue. The OpenMP find is a wonderful example; it was disabled two years ago "to buy ourselves more time" and no attempts have been made to actually fix the issue since then: 9ff0cc7

That said, Clang 19 might be a bit too soon given that we enabled it in system headers in Clang 18, which isn't yet released. I think Clang 20/21 might be somewhat better because that gives folks another year to discover the problems with their uses, but at some point, I think we really do need to force the issue otherwise the can will be kicked down the road forever.

@carlosgalvezp
Copy link
Contributor Author

FYI I'm bringing this issue to the binutils project.
https://sourceware.org/bugzilla/show_bug.cgi?id=31331

I agree with Aaron, it's way easier to just suppress the warning and call it a compiler bug than actually do some research and understand the subtle issues when doing these tricks with enums. I think that the more we delay it, the more projects will suppress the warning as they bump their clang installation, and so the harder it will be for us to introduce it.

That said, I'm not opposed to waiting another release or two.

carlosgalvezp pushed a commit to carlosgalvezp/llvm-project that referenced this issue Feb 9, 2024
This effectively reverts commit 9ff0cc7.
For some reason "git revert" lead to "no changes" after fixing
conflicts, so a clean revert was not possible.

The original issue (llvm#57022) is no longer reproducible even with this
patch, so we can remove the suppression.

This is in line with our goal to make -Wenum-constexpr-conversion
a non-downgradeable error, see llvm#59036.
carlosgalvezp added a commit that referenced this issue Feb 11, 2024
This effectively reverts commit
9ff0cc7. For some reason "git revert"
lead to "no changes" after fixing conflicts, so a clean revert was not
possible.

The original issue (#57022) is no longer reproducible even with this
patch, so we can remove the suppression.

This is in line with our goal to make -Wenum-constexpr-conversion a
non-downgradeable error, see #59036.

Co-authored-by: Carlos Gálvez <carlos.galvez@zenseact.com>
@RJVB
Copy link

RJVB commented Apr 26, 2024

The -Wenum-constexpr-conversion warning was created to account for the fact that casting integers to enums outside of the valid range of the enum is UB in C++17.

Quick question: it is thus not UB in earlier C++ dialects (or C)? I've just come across this "werror" being triggered building Phonon code as gnu++11with clang-16 .

@carlosgalvezp
Copy link
Contributor Author

Technically speaking it's unspecified behavior until C++17, after that it's UB.

@RJVB
Copy link

RJVB commented Apr 27, 2024 via email

@RJVB
Copy link

RJVB commented Apr 27, 2024 via email

@carlosgalvezp
Copy link
Contributor Author

carlosgalvezp commented Apr 27, 2024

And that's better or worse how? :)

Worse, because with UB "anything can happen", there's no restrictions. With Unspecified Behavior, only a defined set of behaviors is allowed (but which one is used is not specified in the Standard).

why it would be either from an implementation point of view.

People writing the Standard should probably answer that :) Some things are kept for historical reasons/backwards compatibility, even though they might make little sense today.

a standard int IIRC

That's true in C, but not in C++, where there's quite a few cases depending on whether the enum is a C-style enum, scoped/unscoped, with explicit underlying type, etc.

Seeing that it made sense that this "error" had never caused any runtime problems...

It's often the case with UB that the program "just works", but it can break anytime in the future when refactoring code, enabling optimizations, updating the compiler, etc.

clang shouldn't raise an error about it until C++17, technically speaking of course?

Good question, I'm not sure actually. The Standard requires that it's an error from C++17, but nothing stops an implementation to make it an error earlier than that. Maybe @AaronBallman @dwblaikie know better?

@RJVB
Copy link

RJVB commented Apr 27, 2024 via email

@carlosgalvezp
Copy link
Contributor Author

And that hasn't evolved more or less slowly from "true in C" (at some point C++ was just a preprocessor)?

C++ is a different language than C, with different standards. Sure, there is a lot of backwards compatibility with C, but there are slight differences.

I guess "unspecified" doesn't mean that there can't be a sensical thing to do...

Absolutely, but there may be multiple sensical things to do and different implementations don't necessarily need to agree.

But one that can be overridden, in that case? Standards are probably akin to legislation, where most things are either explicitly forbidden or implicitly allowed?

Currently the warning can be suppressed in order to give time to people to update their code, before making it a hard error. In that sense Clang is currently not complying with the Standard (but the goal is to make it be).

Standards describe mostly what is allowed, and often clarify that "otherwise, the behavior is undefined".

@RJVB
Copy link

RJVB commented Apr 28, 2024 via email

@carlosgalvezp
Copy link
Contributor Author

carlosgalvezp commented Apr 29, 2024

I suppose that should be technically possible. What's the argument for keeping it since C++11 instead of C++17 @AaronBallman @dwblaikie @shafik ?

@AaronBallman
Copy link
Collaborator

I suppose that should be technically possible. What's the argument for keeping it since C++11 instead of C++17 @AaronBallman @dwblaikie @shafik ?

This was a change as a result of a defect report and we typically apply defect reports as though the original specification was always worded with the fix applied.

@carlosgalvezp
Copy link
Contributor Author

Thanks for the explanation! I guess you meant this one?
https://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1766

In cppreference it even says it applies from C++98 onwards.

@RJVB does this answer your question?

@AaronBallman
Copy link
Collaborator

Thanks for the explanation! I guess you meant this one? https://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1766

It's sort of a combination of both. 1766 made it UB for out-of-range value conversion, 2338 reduced the restriction but still kept some parts of it UB, which impacts constexpr behavior.

@RJVB
Copy link

RJVB commented May 7, 2024 via email

@AaronBallman
Copy link
Collaborator

The purpose of the committee adopting a change as a defect report is to fix bugs with features of the language or library. While that can and does change behavior previously promised by the standard, the intent is that the previous standard was wrong and the changes are fixing an issue.

Whether that's a good idea or not is a matter for debate, but it is a practice that WG21 has had for over a decade and is something the committee strongly encourages implementations to do so that the language remains coherent. We are not required to implement a defect report in all language modes in terms of meeting conformance requirements for a standard, but it is a tradeoff. Implementing DRs the way the committee requests often helps migration between standards modes, but at the expense of hurting migration between compiler versions, unfortunately.

In this case, we've signaled our intent to turn this into a hard error in a future release of Clang. If there's compelling rationale for why we should not turn it into a hard error, it's something we'll certainly consider when deciding when or if to follow through on that intent.

Case in point: the "faulty" Phonon code that led me here could be "fixed" by just adding : unsigned to the enum declarations. Seeing that it made sense that this "error" had never caused any runtime problems...

C and C++ differ in their treatment of enumerations. In C before C23, the enumerators were of type int and the underlying type of the enumeration was either char, a signed integer type, or an unsigned integer type (implementation-defined). A lot of implementations had extensions to these rules, and those extensions were largely ratified for C23. However, in all versions of C++, an enumeration's underlying type was the smallest power-of-two integer bit-field that could represent all the values of the enumeration (up to a max of unsigned int). So: enum E { zero, one, two, three }; in C would be able to hold values that can fit in an int while in C++ it would be able to hold values up to unsigned int : 2. It's not possible to say "this makes sense in C so it should be fine in C++ as well" in all cases, and this is one such case. Adding an explicit underlying type to the enum declaration in C++ forces the underlying type to be large enough to hold any value that fits within that type, that's why it fixes the incorrect code. The code may not have caused runtime problems for you, but it was still undefined behavior and not something you could rely on working correctly across compilers or compiler upgrades. It sounds like this diagnostic caught a real issue that would have otherwise remained latent in the code base and could have caused a hard-to-debug issue if an optimizer started taking more advantage of the C++ semantics for enumerations.

@RJVB
Copy link

RJVB commented May 7, 2024 via email

@carlosgalvezp
Copy link
Contributor Author

Fyi I have now put up a patch for review to the binutils project:
https://sourceware.org/pipermail/gdb-patches/2024-May/209513.html

@carlosgalvezp
Copy link
Contributor Author

Hi!

We are now at Clang 20. Would it be now a good time to turn the warning into a hard error?

I'm pinging the GDB community but I get no reviews of the above patch. Unfortunately there's not much more I can do. Hopefully if we turn the warning into an error it will put some pressure into fixing the code with the available patch.

What do you think @AaronBallman @dwblaikie @shafik ?

@AaronBallman
Copy link
Collaborator

We are now at Clang 20. Would it be now a good time to turn the warning into a hard error?

I think it's worth a shot, yes.

I'm pinging the GDB community but I get no reviews of the above patch. Unfortunately there's not much more I can do. Hopefully if we turn the warning into an error it will put some pressure into fixing the code with the available patch.

Thank you for keeping on top of that!

@carlosgalvezp
Copy link
Contributor Author

Thanks! I can prepare a patch. For consistency, I think it would be nice to show the error like this:

<source>:2:15: error: constexpr variable 'a' must be initialized by a constant expression
    2 | constexpr int a  = x[15];
      |               ^    ~~~~~
<source>:2:20: note: cannot refer to element 15 of array of 10 elements in a constant expression
    2 | constexpr int a  = x[15];
      |                    ^

I.e. first print the generic error, and follow-up with the note (which is the current diagnostic as a warning). This clarifies that the hard error is only in the context of a constant expression.

carlosgalvezp added a commit to carlosgalvezp/llvm-project that referenced this issue Aug 7, 2024
The warning has been active for a few releases now, first only in
user code, later in system headers, and finally as an error by
default.

Therefore, we believe it is now time to transition into a hard error,
as required by the C++ Standard. The main affected C++ projects
have by now fixed the error, or there's a pending patch for review
that does it.

Fixes llvm#59036
@EugeneZelenko EugeneZelenko removed the awaiting-review Has pending Phabricator review label Aug 14, 2024
bwendling pushed a commit to bwendling/llvm-project that referenced this issue Aug 15, 2024
The warning has been active for a few releases now, first only in user
code, later in system headers, and finally as an error by default.

Therefore, we believe it is now time to transition into a hard error, as
required by the C++ Standard. The main affected C++ projects have by now
fixed the error, or there's a pending patch for review that does it.

Fixes llvm#59036
hubot pushed a commit to v8/v8 that referenced this issue Aug 28, 2024
It is UB to cast integers to enums outside of the valid range
of the enum.

Clang will turn `-Wenum-constexpr-conversion` into a hard error:
llvm/llvm-project#59036

This CL changes BitField::kMax to be the unsigned representation
integer type.

And all checks "Enum::kA <= BitField::kMax" to
"BitField::is_valid(Enum::kA)"

Change-Id: I2b0094c72b16fdde3bb2a90901f83e5a6e7d8176
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/5816333
Commit-Queue: Victor Gomes <victorgomes@chromium.org>
Reviewed-by: Igor Sheludko <ishell@chromium.org>
Auto-Submit: Victor Gomes <victorgomes@chromium.org>
Cr-Commit-Position: refs/heads/main@{#95845}
kris-jusiak added a commit to qlibs/reflect that referenced this issue Aug 29, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:diagnostics New/improved warning or error message in Clang, but not in clang-tidy or static analyzer enhancement Improving things as opposed to bug fixing, e.g. new or missing feature
Projects
None yet
9 participants