llvm / llvm-project

The LLVM Project is a collection of modular and reusable compiler and toolchain technologies.
http://llvm.org
Other
29.27k stars 12.1k forks source link

[Clang][C23] Possible bug in initialization of constexpr variable #115845

Open chestnykh opened 1 week ago

chestnykh commented 1 week ago

Consider the following code

void f(){}

const int x = 4;

constexpr auto a = x - x; //clang: error: constexpr variable 'a' must be initialized by a constant expression
constexpr auto b = &f - &f; // clang accepts

but trunk gcc rejects both constexpr variables with the same diagnostic

error: ‘constexpr’ integer initializer is not an integer constant expression

So i guess that clang should also reject the initialization of the 2nd constexpr variable

llvmbot commented 1 week ago

@llvm/issue-subscribers-clang-frontend

Author: Dmitry Chestnykh (chestnykh)

Consider the following code ``` void f(){} const int x = 4; constexpr auto a = x - x; //clang: error: constexpr variable 'a' must be initialized by a constant expression constexpr auto b = &f - &f; // clang accepts ``` but trunk gcc rejects both constexpr variables with the same diagnostic ``` error: ‘constexpr’ integer initializer is not an integer constant expression ``` So i guess that clang should also reject the initialization of the 2nd constexpr variable
chestnykh commented 1 week ago

CC: @AaronBallman @Fznamznon

frederick-vs-ja commented 1 week ago

&f - &f is constraint-violating ("ill-formed") in C, and Clang conformingly rejects it with -pedantic-errors (Godbolt link).

Fznamznon commented 1 week ago

Per 6.6 p10

An arithmetic constant expression shall have arithmetic type and shall only have operands that are integer literals, floating literals, named or compound literal constants of arithmetic type, character literals, sizeof expressions whose results are integer constant expressions, and alignof expressions.

AFAIK pointer type is not an arithmetic type, so I guess this is not valid. It is a funny thing that clang accepts it as "GNU extension" whereas gcc rejects it. @AaronBallman , would it be better to always reject for constexpr variables?

AaronBallman commented 1 week ago

We have latitude to extend what is a valid constant expression (C23 6.6p14: An implementation may accept other forms of constant expressions; however, it is implementation-defined whether they are an integer constant expression.)

The GNU extension is on arithmetic with function pointer types (that's UB in C, see C23 6.5.7p2), so Clang is fine to support that. (C23 6.6p11 points out that an address constant can be a pointer to a function designator, so because Clang allows addition and subtraction on non-object types, that function pointer type is still an address constant, and thus is fine to use in an arithmetic constant expression, which is valid for use in an initializer.)

The behavior with const int is both correct per spec and perhaps a bit suspect in Clang thanks to extensions.

C23 6.7.2p6: If an object or subobject declared with storage-class specifier constexpr has pointer, integer, or arithmetic type, any explicit initializer value for it shall be null, an integer constant expression, or an arithmetic constant expression, respectively. ...

So given:

const int x = 4;
constexpr auto a = x - x;

we deduce the type of a as being int and thus the object has integer type, and thus requires the initializer to be an integer constant expression. Clang and GCC both reject because use of const int does not produce a valid integer constant expression (C23 6.6p8). This is why you get a VLA warning in code like this: https://godbolt.org/z/P1nYveWKs. However, if we change the example slightly, then GCC accepts... and Clang continues to reject:

const int x = 4;
constexpr float a = x - x;

Now a has an arithmetic type rather than an integer type, and so the initializer is required to be an arithmetic constant expression. It seems GCC allows const int in an arithmetic constant expression but Clang does not: https://godbolt.org/z/nfTKW4r51

So I think Clang's behavior here is correct, but we may have a GCC compatibility bug regarding what's considered a valid arithmetic constant expression. GCC may have the behavior they have because extending an integer constant expression can be awkward thanks to VLAs, but extending an arithmetic constant expression only impacts what's a valid object initializer.

frederick-vs-ja commented 1 week ago

(that's UB in C, see C23 6.5.7p2)

Oh, IIUC that's not UB, because the paragraph is under Constraints.

AaronBallman commented 1 week ago

(that's UB in C, see C23 6.5.7p2)

Oh, IIUC that's not UB, because the paragraph is under Constraints.

Ah sorry, yes, that's a constraint violation not pure UB. The end result is the same either way -- we extend the language and issue a pedantic diagnostic as a result.