Skip to content

Commit 0e00a95

Browse files
committed
Add new warning for compound punctuation tokens that are split across macro expansions or split by whitespace.
For example: #define FOO(x) (x) FOO({}); ... forms a statement-expression after macro expansion. This warning applies to '({' and '})' delimiting statement-expressions, '[[' and ']]' delimiting attributes, and '::*' introducing a pointer-to-member. The warning for forming these compound tokens across macro expansions (or across files!) is enabled by default; the warning for whitespace within the tokens is not, but is included in -Wall. Differential Revision: https://reviews.llvm.org/D86751
1 parent aab9038 commit 0e00a95

File tree

11 files changed

+146
-12
lines changed

11 files changed

+146
-12
lines changed

clang/include/clang/Basic/DiagnosticGroups.td

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@ def GNUCompoundLiteralInitializer : DiagGroup<"gnu-compound-literal-initializer"
4545
def BitFieldConstantConversion : DiagGroup<"bitfield-constant-conversion">;
4646
def BitFieldEnumConversion : DiagGroup<"bitfield-enum-conversion">;
4747
def BitFieldWidth : DiagGroup<"bitfield-width">;
48+
def CompoundTokenSplitByMacro : DiagGroup<"compound-token-split-by-macro">;
49+
def CompoundTokenSplitBySpace : DiagGroup<"compound-token-split-by-space">;
50+
def CompoundTokenSplit : DiagGroup<"compound-token-split",
51+
[CompoundTokenSplitByMacro,
52+
CompoundTokenSplitBySpace]>;
4853
def CoroutineMissingUnhandledException :
4954
DiagGroup<"coroutine-missing-unhandled-exception">;
5055
def Coroutine : DiagGroup<"coroutine", [CoroutineMissingUnhandledException]>;
@@ -943,7 +948,8 @@ def Consumed : DiagGroup<"consumed">;
943948
// Note that putting warnings in -Wall will not disable them by default. If a
944949
// warning should be active _only_ when -Wall is passed in, mark it as
945950
// DefaultIgnore in addition to putting it here.
946-
def All : DiagGroup<"all", [Most, Parentheses, Switch, SwitchBool, MisleadingIndentation]>;
951+
def All : DiagGroup<"all", [Most, Parentheses, Switch, SwitchBool,
952+
MisleadingIndentation, CompoundTokenSplit]>;
947953

948954
// Warnings that should be in clang-cl /w4.
949955
def : DiagGroup<"CL4", [All, Extra]>;

clang/include/clang/Basic/DiagnosticParseKinds.td

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,23 @@ def warn_misleading_indentation : Warning<
6262
def note_previous_statement : Note<
6363
"previous statement is here">;
6464

65+
def subst_compound_token_kind : TextSubstitution<
66+
"%select{%1 and |}0%2 tokens "
67+
"%select{introducing statement expression|terminating statement expression|"
68+
"introducing attribute|terminating attribute|"
69+
"forming pointer to member type}3">;
70+
def warn_compound_token_split_by_macro : Warning<
71+
"%sub{subst_compound_token_kind}0,1,2,3 appear in different "
72+
"macro expansion contexts">, InGroup<CompoundTokenSplitByMacro>;
73+
def warn_compound_token_split_by_file : Warning<
74+
"%sub{subst_compound_token_kind}0,1,2,3 appear in different source files">,
75+
InGroup<CompoundTokenSplit>;
76+
def note_compound_token_split_second_token_here : Note<
77+
"%select{|second }0%1 token is here">;
78+
def warn_compound_token_split_by_whitespace : Warning<
79+
"%sub{subst_compound_token_kind}0,1,2,3 are separated by whitespace">,
80+
InGroup<CompoundTokenSplitBySpace>, DefaultIgnore;
81+
6582
def ext_thread_before : Extension<"'__thread' before '%0'">;
6683
def ext_keyword_as_ident : ExtWarn<
6784
"keyword '%0' will be made available as an identifier "

clang/include/clang/Parse/Parser.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1045,6 +1045,25 @@ class Parser : public CodeCompletionHandler {
10451045
/// was successful.
10461046
bool expectIdentifier();
10471047

1048+
/// Kinds of compound pseudo-tokens formed by a sequence of two real tokens.
1049+
enum class CompoundToken {
1050+
/// A '(' '{' beginning a statement-expression.
1051+
StmtExprBegin,
1052+
/// A '}' ')' ending a statement-expression.
1053+
StmtExprEnd,
1054+
/// A '[' '[' beginning a C++11 or C2x attribute.
1055+
AttrBegin,
1056+
/// A ']' ']' ending a C++11 or C2x attribute.
1057+
AttrEnd,
1058+
/// A '::' '*' forming a C++ pointer-to-member declaration.
1059+
MemberPtr,
1060+
};
1061+
1062+
/// Check that a compound operator was written in a "sensible" way, and warn
1063+
/// if not.
1064+
void checkCompoundToken(SourceLocation FirstTokLoc,
1065+
tok::TokenKind FirstTokKind, CompoundToken Op);
1066+
10481067
public:
10491068
//===--------------------------------------------------------------------===//
10501069
// Scope manipulation

clang/lib/Headers/altivec.h

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3324,23 +3324,19 @@ static __inline__ void __attribute__((__always_inline__)) vec_dssall(void) {
33243324

33253325
/* vec_dst */
33263326
#define vec_dst(__PTR, __CW, __STR) \
3327-
__extension__( \
3328-
{ __builtin_altivec_dst((const void *)(__PTR), (__CW), (__STR)); })
3327+
__builtin_altivec_dst((const void *)(__PTR), (__CW), (__STR))
33293328

33303329
/* vec_dstst */
33313330
#define vec_dstst(__PTR, __CW, __STR) \
3332-
__extension__( \
3333-
{ __builtin_altivec_dstst((const void *)(__PTR), (__CW), (__STR)); })
3331+
__builtin_altivec_dstst((const void *)(__PTR), (__CW), (__STR))
33343332

33353333
/* vec_dststt */
33363334
#define vec_dststt(__PTR, __CW, __STR) \
3337-
__extension__( \
3338-
{ __builtin_altivec_dststt((const void *)(__PTR), (__CW), (__STR)); })
3335+
__builtin_altivec_dststt((const void *)(__PTR), (__CW), (__STR))
33393336

33403337
/* vec_dstt */
33413338
#define vec_dstt(__PTR, __CW, __STR) \
3342-
__extension__( \
3343-
{ __builtin_altivec_dstt((const void *)(__PTR), (__CW), (__STR)); })
3339+
__builtin_altivec_dstt((const void *)(__PTR), (__CW), (__STR))
33443340

33453341
/* vec_eqv */
33463342

clang/lib/Parse/ParseDecl.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5591,6 +5591,11 @@ void Parser::ParseDeclaratorInternal(Declarator &D,
55915591
return;
55925592
}
55935593

5594+
if (SS.isValid()) {
5595+
checkCompoundToken(SS.getEndLoc(), tok::coloncolon,
5596+
CompoundToken::MemberPtr);
5597+
}
5598+
55945599
SourceLocation StarLoc = ConsumeToken();
55955600
D.SetRangeEnd(StarLoc);
55965601
DeclSpec DS(AttrFactory);

clang/lib/Parse/ParseDeclCXX.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4142,9 +4142,11 @@ void Parser::ParseCXX11AttributeSpecifier(ParsedAttributes &attrs,
41424142
assert(Tok.is(tok::l_square) && NextToken().is(tok::l_square) &&
41434143
"Not a double square bracket attribute list");
41444144

4145-
Diag(Tok.getLocation(), diag::warn_cxx98_compat_attribute);
4145+
SourceLocation OpenLoc = Tok.getLocation();
4146+
Diag(OpenLoc, diag::warn_cxx98_compat_attribute);
41464147

41474148
ConsumeBracket();
4149+
checkCompoundToken(OpenLoc, tok::l_square, CompoundToken::AttrBegin);
41484150
ConsumeBracket();
41494151

41504152
SourceLocation CommonScopeLoc;
@@ -4227,8 +4229,11 @@ void Parser::ParseCXX11AttributeSpecifier(ParsedAttributes &attrs,
42274229
<< AttrName;
42284230
}
42294231

4232+
SourceLocation CloseLoc = Tok.getLocation();
42304233
if (ExpectAndConsume(tok::r_square))
42314234
SkipUntil(tok::r_square);
4235+
else if (Tok.is(tok::r_square))
4236+
checkCompoundToken(CloseLoc, tok::r_square, CompoundToken::AttrEnd);
42324237
if (endLoc)
42334238
*endLoc = Tok.getLocation();
42344239
if (ExpectAndConsume(tok::r_square))

clang/lib/Parse/ParseExpr.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2840,6 +2840,8 @@ Parser::ParseParenExpression(ParenParseOption &ExprType, bool stopIfCastExpr,
28402840
if (ExprType >= CompoundStmt && Tok.is(tok::l_brace)) {
28412841
Diag(Tok, diag::ext_gnu_statement_expr);
28422842

2843+
checkCompoundToken(OpenLoc, tok::l_paren, CompoundToken::StmtExprBegin);
2844+
28432845
if (!getCurScope()->getFnParent() && !getCurScope()->getBlockParent()) {
28442846
Result = ExprError(Diag(OpenLoc, diag::err_stmtexpr_file_scope));
28452847
} else {

clang/lib/Parse/ParseStmt.cpp

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1135,9 +1135,17 @@ StmtResult Parser::ParseCompoundStatementBody(bool isStmtExpr) {
11351135
SourceLocation CloseLoc = Tok.getLocation();
11361136

11371137
// We broke out of the while loop because we found a '}' or EOF.
1138-
if (!T.consumeClose())
1138+
if (!T.consumeClose()) {
1139+
// If this is the '})' of a statement expression, check that it's written
1140+
// in a sensible way.
1141+
if (isStmtExpr && Tok.is(tok::r_paren))
1142+
checkCompoundToken(CloseLoc, tok::r_brace, CompoundToken::StmtExprEnd);
1143+
} else {
11391144
// Recover by creating a compound statement with what we parsed so far,
1140-
// instead of dropping everything and returning StmtError();
1145+
// instead of dropping everything and returning StmtError().
1146+
}
1147+
1148+
if (T.getCloseLocation().isValid())
11411149
CloseLoc = T.getCloseLocation();
11421150

11431151
return Actions.ActOnCompoundStmt(T.getOpenLocation(), CloseLoc,

clang/lib/Parse/Parser.cpp

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,39 @@ bool Parser::expectIdentifier() {
227227
return true;
228228
}
229229

230+
void Parser::checkCompoundToken(SourceLocation FirstTokLoc,
231+
tok::TokenKind FirstTokKind, CompoundToken Op) {
232+
if (FirstTokLoc.isInvalid())
233+
return;
234+
SourceLocation SecondTokLoc = Tok.getLocation();
235+
236+
// We expect both tokens to come from the same FileID.
237+
FileID FirstID = PP.getSourceManager().getFileID(FirstTokLoc);
238+
FileID SecondID = PP.getSourceManager().getFileID(SecondTokLoc);
239+
if (FirstID != SecondID) {
240+
Diag(FirstTokLoc, (FirstTokLoc.isMacroID() || SecondTokLoc.isMacroID())
241+
? diag::warn_compound_token_split_by_macro
242+
: diag::warn_compound_token_split_by_file)
243+
<< (FirstTokKind == Tok.getKind()) << FirstTokKind << Tok.getKind()
244+
<< static_cast<int>(Op) << SourceRange(FirstTokLoc);
245+
Diag(SecondTokLoc, diag::note_compound_token_split_second_token_here)
246+
<< (FirstTokKind == Tok.getKind()) << Tok.getKind()
247+
<< SourceRange(SecondTokLoc);
248+
return;
249+
}
250+
251+
// We expect the tokens to abut.
252+
if (Tok.hasLeadingSpace()) {
253+
SourceLocation SpaceLoc = PP.getLocForEndOfToken(FirstTokLoc);
254+
if (SpaceLoc.isInvalid())
255+
SpaceLoc = FirstTokLoc;
256+
Diag(SpaceLoc, diag::warn_compound_token_split_by_whitespace)
257+
<< (FirstTokKind == Tok.getKind()) << FirstTokKind << Tok.getKind()
258+
<< static_cast<int>(Op) << SourceRange(FirstTokLoc, SecondTokLoc);
259+
return;
260+
}
261+
}
262+
230263
//===----------------------------------------------------------------------===//
231264
// Error recovery.
232265
//===----------------------------------------------------------------------===//

clang/test/Misc/warning-wall.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,9 @@ CHECK-NEXT: -Wdangling-else
9494
CHECK-NEXT: -Wswitch
9595
CHECK-NEXT: -Wswitch-bool
9696
CHECK-NEXT: -Wmisleading-indentation
97+
CHECK-NEXT: -Wcompound-token-split
98+
CHECK-NEXT: -Wcompound-token-split-by-macro
99+
CHECK-NEXT: -Wcompound-token-split-by-space
97100

98101

99102
CHECK-NOT:-W
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// RUN: %clang_cc1 %s -verify
2+
// RUN: %clang_cc1 %s -verify=expected,space -Wcompound-token-split
3+
// RUN: %clang_cc1 %s -verify=expected,space -Wall
4+
5+
#ifdef LSQUARE
6+
[ // expected-note {{second '[' token is here}}
7+
#else
8+
9+
#define VAR(type, name, init) type name = (init)
10+
11+
void f() {
12+
VAR(int, x, {}); // #1
13+
// expected-warning@#1 {{'(' and '{' tokens introducing statement expression appear in different macro expansion contexts}}
14+
// expected-note-re@#1 {{{{^}}'{' token is here}}
15+
//
16+
// FIXME: It would be nice to suppress this when we already warned about the opening '({'.
17+
// expected-warning@#1 {{'}' and ')' tokens terminating statement expression appear in different macro expansion contexts}}
18+
// expected-note-re@#1 {{{{^}}')' token is here}}
19+
//
20+
// expected-error@#1 {{cannot initialize a variable of type 'int' with an rvalue of type 'void'}}
21+
}
22+
23+
#define RPAREN )
24+
25+
int f2() {
26+
int n = ({ 1; }RPAREN; // expected-warning {{'}' and ')' tokens terminating statement expression appear in different macro expansion contexts}} expected-note {{')' token is here}}
27+
return n;
28+
}
29+
30+
[ // expected-warning-re {{{{^}}'[' tokens introducing attribute appear in different source files}}
31+
#define LSQUARE
32+
#include __FILE__
33+
noreturn ]] void g();
34+
35+
[[noreturn] ] void h(); // space-warning-re {{{{^}}']' tokens terminating attribute are separated by whitespace}}
36+
37+
struct X {};
38+
int X:: *p; // space-warning {{'::' and '*' tokens forming pointer to member type are separated by whitespace}}
39+
40+
#endif

0 commit comments

Comments
 (0)