Skip to content

Commit

Permalink
[C23] Complete support for WG14 N2508 (#71398)
Browse files Browse the repository at this point in the history
In Clang 16, we implemented the ability to add a label at the end of a
compound statement. These changes complete the implementation by
allowing a label to be followed by a declaration in C.

Note, this seems to have fixed an issue with some OpenMP stand-alone
directives not being properly diagnosed as per:
https://www.openmp.org/spec-html/5.1/openmpsu19.html#x34-330002.1.3
(The same requirement exists in OpenMP 5.2 as well.)
  • Loading branch information
AaronBallman committed Nov 20, 2023
1 parent 37fa327 commit 8bd06d5
Show file tree
Hide file tree
Showing 17 changed files with 151 additions and 68 deletions.
23 changes: 23 additions & 0 deletions clang/docs/ReleaseNotes.rst
Expand Up @@ -209,6 +209,12 @@ C23 Feature Support
- Clang now supports ``<stdckdint.h>`` which defines several macros for performing
checked integer arithmetic. It is also exposed in pre-C23 modes.

- Completed the implementation of
`N2508 <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2508.pdf>`_. We
previously implemented allowing a label at the end of a compound statement,
and now we've implemented allowing a label to be followed by a declaration
instead of a statement.

Non-comprehensive list of changes in this release
-------------------------------------------------

Expand Down Expand Up @@ -566,6 +572,23 @@ Bug Fixes in This Version
Fixes (`#67687 <https://github.com/llvm/llvm-project/issues/67687>`_)
- Fix crash from constexpr evaluator evaluating uninitialized arrays as rvalue.
Fixes (`#67317 <https://github.com/llvm/llvm-project/issues/67317>`_)
- Clang now properly diagnoses use of stand-alone OpenMP directives after a
label (including ``case`` or ``default`` labels).

Before:

.. code-block:: c++

label:
#pragma omp barrier // ok

After:

.. code-block:: c++

label:
#pragma omp barrier // error: '#pragma omp barrier' cannot be an immediate substatement

- Fixed an issue that a benign assertion might hit when instantiating a pack expansion
inside a lambda. (`#61460 <https://github.com/llvm/llvm-project/issues/61460>`_)

Expand Down
6 changes: 6 additions & 0 deletions clang/include/clang/Basic/DiagnosticParseKinds.td
Expand Up @@ -299,6 +299,12 @@ def note_missing_selector_name : Note<
def note_force_empty_selector_name : Note<
"or insert whitespace before ':' to use %0 as parameter name "
"and have an empty entry in the selector">;
def ext_c_label_followed_by_declaration : ExtWarn<
"label followed by a declaration is a C23 extension">,
InGroup<C23>;
def warn_c23_compat_label_followed_by_declaration : Warning<
"label followed by a declaration is incompatible with C standards before "
"C23">, InGroup<CPre23Compat>, DefaultIgnore;
def ext_c_label_end_of_compound_statement : ExtWarn<
"label at end of compound statement is a C23 extension">,
InGroup<C23>;
Expand Down
9 changes: 3 additions & 6 deletions clang/include/clang/Parse/Parser.h
Expand Up @@ -415,18 +415,15 @@ class Parser : public CodeCompletionHandler {

/// Flags describing a context in which we're parsing a statement.
enum class ParsedStmtContext {
/// This context permits declarations in language modes where declarations
/// are not statements.
AllowDeclarationsInC = 0x1,
/// This context permits standalone OpenMP directives.
AllowStandaloneOpenMPDirectives = 0x2,
AllowStandaloneOpenMPDirectives = 0x1,
/// This context is at the top level of a GNU statement expression.
InStmtExpr = 0x4,
InStmtExpr = 0x2,

/// The context of a regular substatement.
SubStmt = 0,
/// The context of a compound-statement.
Compound = AllowDeclarationsInC | AllowStandaloneOpenMPDirectives,
Compound = AllowStandaloneOpenMPDirectives,

LLVM_MARK_AS_BITMASK_ENUM(InStmtExpr)
};
Expand Down
4 changes: 2 additions & 2 deletions clang/lib/Parse/ParseOpenMP.cpp
Expand Up @@ -2670,7 +2670,7 @@ StmtResult Parser::ParseOpenMPDeclarativeOrExecutableDirective(
}
case OMPD_threadprivate: {
// FIXME: Should this be permitted in C++?
if ((StmtCtx & ParsedStmtContext::AllowDeclarationsInC) ==
if ((StmtCtx & ParsedStmtContext::AllowStandaloneOpenMPDirectives) ==
ParsedStmtContext()) {
Diag(Tok, diag::err_omp_immediate_directive)
<< getOpenMPDirectiveName(DKind) << 0;
Expand All @@ -2689,7 +2689,7 @@ StmtResult Parser::ParseOpenMPDeclarativeOrExecutableDirective(
}
case OMPD_allocate: {
// FIXME: Should this be permitted in C++?
if ((StmtCtx & ParsedStmtContext::AllowDeclarationsInC) ==
if ((StmtCtx & ParsedStmtContext::AllowStandaloneOpenMPDirectives) ==
ParsedStmtContext()) {
Diag(Tok, diag::err_omp_immediate_directive)
<< getOpenMPDirectiveName(DKind) << 0;
Expand Down
42 changes: 29 additions & 13 deletions clang/lib/Parse/ParseStmt.cpp
Expand Up @@ -235,10 +235,7 @@ StmtResult Parser::ParseStatementOrDeclarationAfterAttributes(
auto IsStmtAttr = [](ParsedAttr &Attr) { return Attr.isStmtAttr(); };
bool AllAttrsAreStmtAttrs = llvm::all_of(CXX11Attrs, IsStmtAttr) &&
llvm::all_of(GNUAttrs, IsStmtAttr);
if ((getLangOpts().CPlusPlus || getLangOpts().MicrosoftExt ||
(StmtCtx & ParsedStmtContext::AllowDeclarationsInC) !=
ParsedStmtContext()) &&
((GNUAttributeLoc.isValid() && !(HaveAttrs && AllAttrsAreStmtAttrs)) ||
if (((GNUAttributeLoc.isValid() && !(HaveAttrs && AllAttrsAreStmtAttrs)) ||
isDeclarationStatement())) {
SourceLocation DeclStart = Tok.getLocation(), DeclEnd;
DeclGroupPtrTy Decl;
Expand Down Expand Up @@ -701,6 +698,18 @@ StmtResult Parser::ParseSEHLeaveStatement() {
return Actions.ActOnSEHLeaveStmt(LeaveLoc, getCurScope());
}

static void DiagnoseLabelFollowedByDecl(Parser &P, const Stmt *SubStmt) {
// When in C mode (but not Microsoft extensions mode), diagnose use of a
// label that is followed by a declaration rather than a statement.
if (!P.getLangOpts().CPlusPlus && !P.getLangOpts().MicrosoftExt &&
isa<DeclStmt>(SubStmt)) {
P.Diag(SubStmt->getBeginLoc(),
P.getLangOpts().C23
? diag::warn_c23_compat_label_followed_by_declaration
: diag::ext_c_label_followed_by_declaration);
}
}

/// ParseLabeledStatement - We have an identifier and a ':' after it.
///
/// label:
Expand All @@ -715,9 +724,10 @@ StmtResult Parser::ParseLabeledStatement(ParsedAttributes &Attrs,
assert(Tok.is(tok::identifier) && Tok.getIdentifierInfo() &&
"Not an identifier!");

// The substatement is always a 'statement', not a 'declaration', but is
// otherwise in the same context as the labeled-statement.
StmtCtx &= ~ParsedStmtContext::AllowDeclarationsInC;
// [OpenMP 5.1] 2.1.3: A stand-alone directive may not be used in place of a
// substatement in a selection statement, in place of the loop body in an
// iteration statement, or in place of the statement that follows a label.
StmtCtx &= ~ParsedStmtContext::AllowStandaloneOpenMPDirectives;

Token IdentTok = Tok; // Save the whole token.
ConsumeToken(); // eat the identifier.
Expand Down Expand Up @@ -766,6 +776,8 @@ StmtResult Parser::ParseLabeledStatement(ParsedAttributes &Attrs,
if (SubStmt.isInvalid())
SubStmt = Actions.ActOnNullStmt(ColonLoc);

DiagnoseLabelFollowedByDecl(*this, SubStmt.get());

LabelDecl *LD = Actions.LookupOrCreateLabel(IdentTok.getIdentifierInfo(),
IdentTok.getLocation());
Actions.ProcessDeclAttributeList(Actions.CurScope, LD, Attrs);
Expand All @@ -784,9 +796,10 @@ StmtResult Parser::ParseCaseStatement(ParsedStmtContext StmtCtx,
bool MissingCase, ExprResult Expr) {
assert((MissingCase || Tok.is(tok::kw_case)) && "Not a case stmt!");

// The substatement is always a 'statement', not a 'declaration', but is
// otherwise in the same context as the labeled-statement.
StmtCtx &= ~ParsedStmtContext::AllowDeclarationsInC;
// [OpenMP 5.1] 2.1.3: A stand-alone directive may not be used in place of a
// substatement in a selection statement, in place of the loop body in an
// iteration statement, or in place of the statement that follows a label.
StmtCtx &= ~ParsedStmtContext::AllowStandaloneOpenMPDirectives;

// It is very common for code to contain many case statements recursively
// nested, as in (but usually without indentation):
Expand Down Expand Up @@ -912,6 +925,7 @@ StmtResult Parser::ParseCaseStatement(ParsedStmtContext StmtCtx,
// Broken sub-stmt shouldn't prevent forming the case statement properly.
if (SubStmt.isInvalid())
SubStmt = Actions.ActOnNullStmt(SourceLocation());
DiagnoseLabelFollowedByDecl(*this, SubStmt.get());
Actions.ActOnCaseStmtBody(DeepestParsedCaseStmt, SubStmt.get());
}

Expand All @@ -927,9 +941,10 @@ StmtResult Parser::ParseCaseStatement(ParsedStmtContext StmtCtx,
StmtResult Parser::ParseDefaultStatement(ParsedStmtContext StmtCtx) {
assert(Tok.is(tok::kw_default) && "Not a default stmt!");

// The substatement is always a 'statement', not a 'declaration', but is
// otherwise in the same context as the labeled-statement.
StmtCtx &= ~ParsedStmtContext::AllowDeclarationsInC;
// [OpenMP 5.1] 2.1.3: A stand-alone directive may not be used in place of a
// substatement in a selection statement, in place of the loop body in an
// iteration statement, or in place of the statement that follows a label.
StmtCtx &= ~ParsedStmtContext::AllowStandaloneOpenMPDirectives;

SourceLocation DefaultLoc = ConsumeToken(); // eat the 'default'.

Expand Down Expand Up @@ -963,6 +978,7 @@ StmtResult Parser::ParseDefaultStatement(ParsedStmtContext StmtCtx) {
if (SubStmt.isInvalid())
SubStmt = Actions.ActOnNullStmt(ColonLoc);

DiagnoseLabelFollowedByDecl(*this, SubStmt.get());
return Actions.ActOnDefaultStmt(DefaultLoc, ColonLoc,
SubStmt.get(), getCurScope());
}
Expand Down
39 changes: 33 additions & 6 deletions clang/test/C/C2x/n2508.c
@@ -1,23 +1,50 @@
// RUN: %clang_cc1 -verify -std=c2x %s
// RUN: %clang_cc1 -verify -std=c23 %s
// RUN: %clang_cc1 -verify=pedantic -std=c11 -pedantic %s
// RUN: %clang_cc1 -verify=compat -std=c23 -Wpre-c23-compat %s

// expected-no-diagnostics

/* WG14 N2508: yes
* Free positioning of labels inside compound statements
*/
void test() {
void test(void) {
{
inner:
}
} /* pedantic-warning {{label at end of compound statement is a C23 extension}}
compat-warning {{label at end of compound statement is incompatible with C standards before C23}}
*/

switch (1) {
case 1:
}
} /* pedantic-warning {{label at end of compound statement is a C23 extension}}
compat-warning {{label at end of compound statement is incompatible with C standards before C23}}
*/

{
multiple: labels: on: a: line:
}
} /* pedantic-warning {{label at end of compound statement is a C23 extension}}
compat-warning {{label at end of compound statement is incompatible with C standards before C23}}
*/

final:
}
} /* pedantic-warning {{label at end of compound statement is a C23 extension}}
compat-warning {{label at end of compound statement is incompatible with C standards before C23}}
*/

void test_labels(void) {
label:
int i = 0; /* pedantic-warning {{label followed by a declaration is a C23 extension}}
compat-warning {{label followed by a declaration is incompatible with C standards before C23}}
*/

switch (i) {
case 1:
_Static_assert(1, ""); /* pedantic-warning {{label followed by a declaration is a C23 extension}}
compat-warning {{label followed by a declaration is incompatible with C standards before C23}}
*/
default:
_Static_assert(1, ""); /* pedantic-warning {{label followed by a declaration is a C23 extension}}
compat-warning {{label followed by a declaration is incompatible with C standards before C23}}
*/
}
}
24 changes: 16 additions & 8 deletions clang/test/OpenMP/barrier_ast_print.cpp
Expand Up @@ -17,13 +17,13 @@ T tmain(T argc) {
static T a;
#pragma omp barrier
switch (argc) {
case 0:
case 0: {
#pragma omp barrier
break;
default:
} break;
default: {
#pragma omp barrier
#pragma omp barrier
break;
} break;
}
return a + argc;
}
Expand All @@ -35,11 +35,15 @@ T tmain(T argc) {
// CHECK-NEXT: #pragma omp barrier
// CHECK-NEXT: switch (argc) {
// CHECK-NEXT: case 0:
// CHECK-NEXT: {
// CHECK-NEXT: #pragma omp barrier
// CHECK-NEXT: }
// CHECK-NEXT: break;
// CHECK-NEXT: default:
// CHECK-NEXT: {
// CHECK-NEXT: #pragma omp barrier
// CHECK-NEXT: #pragma omp barrier
// CHECK-NEXT: }
// CHECK-NEXT: break;
// CHECK-NEXT: }

Expand All @@ -49,21 +53,25 @@ int main(int argc, char **argv) {
#pragma omp barrier
// CHECK-NEXT: #pragma omp barrier
switch (argc) {
case 0:
case 0: {
#pragma omp barrier
#pragma omp barrier
break;
default:
} break;
default: {
#pragma omp barrier
break;
} break;
}
// CHECK-NEXT: switch (argc) {
// CHECK-NEXT: case 0:
// CHECK-NEXT: {
// CHECK-NEXT: #pragma omp barrier
// CHECK-NEXT: #pragma omp barrier
// CHECK-NEXT: }
// CHECK-NEXT: break;
// CHECK-NEXT: default:
// CHECK-NEXT: {
// CHECK-NEXT: #pragma omp barrier
// CHECK-NEXT: }
// CHECK-NEXT: break;
// CHECK-NEXT: }
return tmain(argc) + tmain(argv[0][0]) + a;
Expand Down
8 changes: 4 additions & 4 deletions clang/test/OpenMP/barrier_messages.cpp
Expand Up @@ -38,7 +38,7 @@ T tmain(T argc) {
switch (argc) {
#pragma omp barrier
case 1:
#pragma omp barrier
#pragma omp barrier // expected-error {{'#pragma omp barrier' cannot be an immediate substatement}}
break;
default: {
#pragma omp barrier
Expand All @@ -50,7 +50,7 @@ T tmain(T argc) {
#pragma omp barrier
}
label:
#pragma omp barrier
#pragma omp barrier // expected-error {{'#pragma omp barrier' cannot be an immediate substatement}}
label1 : {
#pragma omp barrier
}
Expand Down Expand Up @@ -92,7 +92,7 @@ int main(int argc, char **argv) {
switch (argc) {
#pragma omp barrier
case 1:
#pragma omp barrier
#pragma omp barrier // expected-error {{'#pragma omp barrier' cannot be an immediate substatement}}
break;
default: {
#pragma omp barrier
Expand All @@ -104,7 +104,7 @@ int main(int argc, char **argv) {
#pragma omp barrier
}
label:
#pragma omp barrier
#pragma omp barrier // expected-error {{'#pragma omp barrier' cannot be an immediate substatement}}
label1 : {
#pragma omp barrier
}
Expand Down
6 changes: 4 additions & 2 deletions clang/test/OpenMP/cancel_messages.cpp
Expand Up @@ -71,7 +71,8 @@ int main(int argc, char **argv) {
switch (argc) {
#pragma omp cancel taskgroup // expected-error {{orphaned 'omp cancel' directives are prohibited; perhaps you forget to enclose the directive into a region?}}
case 1:
#pragma omp cancel parallel // expected-error {{orphaned 'omp cancel' directives are prohibited; perhaps you forget to enclose the directive into a region?}}
#pragma omp cancel parallel // expected-error {{orphaned 'omp cancel' directives are prohibited; perhaps you forget to enclose the directive into a region?}} \
expected-error {{'#pragma omp cancel' cannot be an immediate substatement}}
break;
default: {
#pragma omp cancel sections // expected-error {{orphaned 'omp cancel' directives are prohibited; perhaps you forget to enclose the directive into a region?}}
Expand All @@ -83,7 +84,8 @@ int main(int argc, char **argv) {
#pragma omp cancel taskgroup // expected-error {{orphaned 'omp cancel' directives are prohibited; perhaps you forget to enclose the directive into a region?}}
}
label:
#pragma omp cancel parallel // expected-error {{orphaned 'omp cancel' directives are prohibited; perhaps you forget to enclose the directive into a region?}}
#pragma omp cancel parallel // expected-error {{orphaned 'omp cancel' directives are prohibited; perhaps you forget to enclose the directive into a region?}} \
expected-error {{'#pragma omp cancel' cannot be an immediate substatement}}
label1 : {
#pragma omp cancel sections // expected-error {{orphaned 'omp cancel' directives are prohibited; perhaps you forget to enclose the directive into a region?}}
}
Expand Down
6 changes: 4 additions & 2 deletions clang/test/OpenMP/cancellation_point_messages.cpp
Expand Up @@ -71,7 +71,8 @@ int main(int argc, char **argv) {
switch (argc) {
#pragma omp cancellation point taskgroup // expected-error {{orphaned 'omp cancellation point' directives are prohibited; perhaps you forget to enclose the directive into a region?}}
case 1:
#pragma omp cancellation point parallel // expected-error {{orphaned 'omp cancellation point' directives are prohibited; perhaps you forget to enclose the directive into a region?}}
#pragma omp cancellation point parallel // expected-error {{orphaned 'omp cancellation point' directives are prohibited; perhaps you forget to enclose the directive into a region?}} \
expected-error {{'#pragma omp cancellation point' cannot be an immediate substatement}}
break;
default: {
#pragma omp cancellation point sections // expected-error {{orphaned 'omp cancellation point' directives are prohibited; perhaps you forget to enclose the directive into a region?}}
Expand All @@ -83,7 +84,8 @@ int main(int argc, char **argv) {
#pragma omp cancellation point taskgroup // expected-error {{orphaned 'omp cancellation point' directives are prohibited; perhaps you forget to enclose the directive into a region?}}
}
label:
#pragma omp cancellation point parallel // expected-error {{orphaned 'omp cancellation point' directives are prohibited; perhaps you forget to enclose the directive into a region?}}
#pragma omp cancellation point parallel // expected-error {{orphaned 'omp cancellation point' directives are prohibited; perhaps you forget to enclose the directive into a region?}} \
expected-error {{'#pragma omp cancellation point' cannot be an immediate substatement}}
label1 : {
#pragma omp cancellation point sections // expected-error {{orphaned 'omp cancellation point' directives are prohibited; perhaps you forget to enclose the directive into a region?}}
}
Expand Down

0 comments on commit 8bd06d5

Please sign in to comment.