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][C23] constexpr initialized with compound literal incorrectly allows non-null pointers #121694

Open
Cydox opened this issue Jan 5, 2025 · 4 comments
Labels
c23 clang:frontend Language frontend issues, e.g. anything involving "Sema"

Comments

@Cydox
Copy link
Contributor

Cydox commented Jan 5, 2025

@Fznamznon

Compiler Explorer: https://godbolt.org/z/oTvq738fa

typedef struct {
	int *a;
} foo;

int b = 42;

constexpr foo bar1 = (foo){.a = &b}; // compiles without warning (even with -Wpedantic)
constexpr foo bar2 = {.a = &b};      // error: constexpr pointer initializer is not null

Initializing with a compound literal is a GNU extension [1]:

"As a GNU extension, GCC allows initialization of objects with static storage duration by compound literals (which is not possible in ISO C99 because the initializer is not a constant). It is handled as if the object were initialized only with the brace-enclosed list if the types of the compound literal and the object match. The elements of the compound literal must be constant. If the object being initialized has array type of unknown size, the size is determined by the size of the compound literal."

But it should be treated like the variable was initialized with an initializer list. So the same constraints should be enforced in the constexpr case. Meaning no [2]:

  • Pointers (except that null pointers can be constexpr),
  • Variably modified types,
  • Atomic types,
  • volatile types,
  • restrict pointers.

The behavior of the generated code appears to be correct and what you'd expect though (in the limited testing I've done).

gcc behaves very similarly, however it does provide a warning with -Wpedantic for the bar1 initialization (bar2 correctly errors).

The problem appears to be that initialization with compound literal bypasses this check:

Expr::EvalResult ER;
if (Entity.getType()->getAs<PointerType>() &&
CurInit.get()->EvaluateAsRValue(ER, S.Context) &&
!ER.Val.isNullPointer()) {
S.Diag(Kind.getLocation(), diag::err_c23_constexpr_pointer_not_null);

and only hits this one:
if (HasConstInit) {
// FIXME: Consider replacing the initializer with a ConstantExpr.
} else if (var->isConstexpr()) {
SourceLocation DiagLoc = var->getLocation();
// If the note doesn't add any useful information other than a source
// location, fold it into the primary diagnostic.
if (Notes.size() == 1 && Notes[0].second.getDiagID() ==
diag::note_invalid_subexpr_in_const_expr) {
DiagLoc = Notes[0].first;
Notes.clear();
}
Diag(DiagLoc, diag::err_constexpr_var_requires_const_init)
<< var << Init->getSourceRange();

[1] https://gcc.gnu.org/onlinedocs/gcc/Compound-Literals.html
[2] https://en.cppreference.com/w/c/language/constexpr

@llvmbot llvmbot added the clang Clang issues not falling into any other category label Jan 5, 2025
@cor3ntin cor3ntin added c23 clang:frontend Language frontend issues, e.g. anything involving "Sema" labels Jan 5, 2025
@llvmbot
Copy link
Member

llvmbot commented Jan 5, 2025

@llvm/issue-subscribers-clang-frontend

Author: Jan Hendrik Farr (Cydox)

@Fznamznon

Compiler Explorer: https://godbolt.org/z/oTvq738fa

typedef struct {
	int *a;
} foo;

int b = 42;

constexpr foo bar1 = (foo){.a = &amp;b}; // compiles without warning (even with -Wpedantic)
constexpr foo bar2 = {.a = &amp;b};      // error: constexpr pointer initializer is not null

Initializing with a compound literal is a GNU extension [1]:
> "As a GNU extension, GCC allows initialization of objects with static storage duration by compound literals (which is not possible in ISO C99 because the initializer is not a constant). It is handled as if the object were initialized only with the brace-enclosed list if the types of the compound literal and the object match. The elements of the compound literal must be constant. If the object being initialized has array type of unknown size, the size is determined by the size of the compound literal."

But it should be treated like the variable was initialized with an initializer list. So the same constraints should be enforced in the constexpr case. Meaning no [2]:

  • Pointers (except that null pointers can be constexpr),
  • Variably modified types,
  • Atomic types,
  • volatile types,
  • restrict pointers.

The behavior of the generated code appears to be correct and what you'd expect though (in the limited testing I've done).

gcc behaves very similarly, however it does provide a warning with -Wpedantic for the bar1 initialization (bar2 correctly errors).

The problem appears to be that initialization with compound literal bypasses this check:

Expr::EvalResult ER;
if (Entity.getType()->getAs<PointerType>() &&
CurInit.get()->EvaluateAsRValue(ER, S.Context) &&
!ER.Val.isNullPointer()) {
S.Diag(Kind.getLocation(), diag::err_c23_constexpr_pointer_not_null);

and only hits this one:
if (HasConstInit) {
// FIXME: Consider replacing the initializer with a ConstantExpr.
} else if (var->isConstexpr()) {
SourceLocation DiagLoc = var->getLocation();
// If the note doesn't add any useful information other than a source
// location, fold it into the primary diagnostic.
if (Notes.size() == 1 && Notes[0].second.getDiagID() ==
diag::note_invalid_subexpr_in_const_expr) {
DiagLoc = Notes[0].first;
Notes.clear();
}
Diag(DiagLoc, diag::err_constexpr_var_requires_const_init)
<< var << Init->getSourceRange();

[1] https://gcc.gnu.org/onlinedocs/gcc/Compound-Literals.html
[2] https://en.cppreference.com/w/c/language/constexpr

@EugeneZelenko EugeneZelenko removed the clang Clang issues not falling into any other category label Jan 5, 2025
@Fznamznon
Copy link
Contributor

I see the point, I'm not quite sure though what is the best output of clang should be. Applying [1] to [2] probably requires to emit a hard error, however I'm not sure that emitting an error for gcc extension whereas gcc itself doesn't emit an error is a good idea.
cc @AaronBallman

@AaronBallman
Copy link
Collaborator

GCC accepts this code as a GNU extension, with a pedantic warning:

constexpr foo bar1 = (foo){.a = &b};
static_assert(bar1.a == &b); // Assertion passes

and rejects this code as being a constraint violation:

constexpr foo bar1 = (constexpr foo){.a = &b};

which feels a bit inconsistent to me, at least at first glance. @pinskia do you happen to know if these are intentionally handled differently in GCC? My inclination would be that both should work as a GNU extension but maybe I'm missing something?

@pinskia
Copy link

pinskia commented Jan 7, 2025

GCC accepts this code as a GNU extension, with a pedantic warning:

constexpr foo bar1 = (foo){.a = &b};
static_assert(bar1.a == &b); // Assertion passes

and rejects this code as being a constraint violation:

constexpr foo bar1 = (constexpr foo){.a = &b};

which feels a bit inconsistent to me, at least at first glance. @pinskia do you happen to know if these are intentionally handled differently in GCC? My inclination would be that both should work as a GNU extension but maybe I'm missing something?

Yes, there seems be to an order issue inside GCC, the check for non-null ptr for constexpr is handled before the "unwrapping": of the compound literal. Let me file a bug. I think GCC should reject constexpr foo bar1 = (foo){.a = &b}; case for the same reason as without the compound literal.

Filed https://gcc.gnu.org/bugzilla/show_bug.cgi?id=118335 .

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
c23 clang:frontend Language frontend issues, e.g. anything involving "Sema"
Projects
None yet
Development

No branches or pull requests

7 participants