Skip to content

Commit

Permalink
Add new warning for compound punctuation tokens that are split across…
Browse files Browse the repository at this point in the history
… 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
  • Loading branch information
zygoloid committed Aug 28, 2020
1 parent aab9038 commit 0e00a95
Show file tree
Hide file tree
Showing 11 changed files with 146 additions and 12 deletions.
8 changes: 7 additions & 1 deletion clang/include/clang/Basic/DiagnosticGroups.td
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ def GNUCompoundLiteralInitializer : DiagGroup<"gnu-compound-literal-initializer"
def BitFieldConstantConversion : DiagGroup<"bitfield-constant-conversion">;
def BitFieldEnumConversion : DiagGroup<"bitfield-enum-conversion">;
def BitFieldWidth : DiagGroup<"bitfield-width">;
def CompoundTokenSplitByMacro : DiagGroup<"compound-token-split-by-macro">;
def CompoundTokenSplitBySpace : DiagGroup<"compound-token-split-by-space">;
def CompoundTokenSplit : DiagGroup<"compound-token-split",
[CompoundTokenSplitByMacro,
CompoundTokenSplitBySpace]>;
def CoroutineMissingUnhandledException :
DiagGroup<"coroutine-missing-unhandled-exception">;
def Coroutine : DiagGroup<"coroutine", [CoroutineMissingUnhandledException]>;
Expand Down Expand Up @@ -943,7 +948,8 @@ def Consumed : DiagGroup<"consumed">;
// Note that putting warnings in -Wall will not disable them by default. If a
// warning should be active _only_ when -Wall is passed in, mark it as
// DefaultIgnore in addition to putting it here.
def All : DiagGroup<"all", [Most, Parentheses, Switch, SwitchBool, MisleadingIndentation]>;
def All : DiagGroup<"all", [Most, Parentheses, Switch, SwitchBool,
MisleadingIndentation, CompoundTokenSplit]>;

// Warnings that should be in clang-cl /w4.
def : DiagGroup<"CL4", [All, Extra]>;
Expand Down
17 changes: 17 additions & 0 deletions clang/include/clang/Basic/DiagnosticParseKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,23 @@ def warn_misleading_indentation : Warning<
def note_previous_statement : Note<
"previous statement is here">;

def subst_compound_token_kind : TextSubstitution<
"%select{%1 and |}0%2 tokens "
"%select{introducing statement expression|terminating statement expression|"
"introducing attribute|terminating attribute|"
"forming pointer to member type}3">;
def warn_compound_token_split_by_macro : Warning<
"%sub{subst_compound_token_kind}0,1,2,3 appear in different "
"macro expansion contexts">, InGroup<CompoundTokenSplitByMacro>;
def warn_compound_token_split_by_file : Warning<
"%sub{subst_compound_token_kind}0,1,2,3 appear in different source files">,
InGroup<CompoundTokenSplit>;
def note_compound_token_split_second_token_here : Note<
"%select{|second }0%1 token is here">;
def warn_compound_token_split_by_whitespace : Warning<
"%sub{subst_compound_token_kind}0,1,2,3 are separated by whitespace">,
InGroup<CompoundTokenSplitBySpace>, DefaultIgnore;

def ext_thread_before : Extension<"'__thread' before '%0'">;
def ext_keyword_as_ident : ExtWarn<
"keyword '%0' will be made available as an identifier "
Expand Down
19 changes: 19 additions & 0 deletions clang/include/clang/Parse/Parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -1045,6 +1045,25 @@ class Parser : public CodeCompletionHandler {
/// was successful.
bool expectIdentifier();

/// Kinds of compound pseudo-tokens formed by a sequence of two real tokens.
enum class CompoundToken {
/// A '(' '{' beginning a statement-expression.
StmtExprBegin,
/// A '}' ')' ending a statement-expression.
StmtExprEnd,
/// A '[' '[' beginning a C++11 or C2x attribute.
AttrBegin,
/// A ']' ']' ending a C++11 or C2x attribute.
AttrEnd,
/// A '::' '*' forming a C++ pointer-to-member declaration.
MemberPtr,
};

/// Check that a compound operator was written in a "sensible" way, and warn
/// if not.
void checkCompoundToken(SourceLocation FirstTokLoc,
tok::TokenKind FirstTokKind, CompoundToken Op);

public:
//===--------------------------------------------------------------------===//
// Scope manipulation
Expand Down
12 changes: 4 additions & 8 deletions clang/lib/Headers/altivec.h
Original file line number Diff line number Diff line change
Expand Up @@ -3324,23 +3324,19 @@ static __inline__ void __attribute__((__always_inline__)) vec_dssall(void) {

/* vec_dst */
#define vec_dst(__PTR, __CW, __STR) \
__extension__( \
{ __builtin_altivec_dst((const void *)(__PTR), (__CW), (__STR)); })
__builtin_altivec_dst((const void *)(__PTR), (__CW), (__STR))

/* vec_dstst */
#define vec_dstst(__PTR, __CW, __STR) \
__extension__( \
{ __builtin_altivec_dstst((const void *)(__PTR), (__CW), (__STR)); })
__builtin_altivec_dstst((const void *)(__PTR), (__CW), (__STR))

/* vec_dststt */
#define vec_dststt(__PTR, __CW, __STR) \
__extension__( \
{ __builtin_altivec_dststt((const void *)(__PTR), (__CW), (__STR)); })
__builtin_altivec_dststt((const void *)(__PTR), (__CW), (__STR))

/* vec_dstt */
#define vec_dstt(__PTR, __CW, __STR) \
__extension__( \
{ __builtin_altivec_dstt((const void *)(__PTR), (__CW), (__STR)); })
__builtin_altivec_dstt((const void *)(__PTR), (__CW), (__STR))

/* vec_eqv */

Expand Down
5 changes: 5 additions & 0 deletions clang/lib/Parse/ParseDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5591,6 +5591,11 @@ void Parser::ParseDeclaratorInternal(Declarator &D,
return;
}

if (SS.isValid()) {
checkCompoundToken(SS.getEndLoc(), tok::coloncolon,
CompoundToken::MemberPtr);
}

SourceLocation StarLoc = ConsumeToken();
D.SetRangeEnd(StarLoc);
DeclSpec DS(AttrFactory);
Expand Down
7 changes: 6 additions & 1 deletion clang/lib/Parse/ParseDeclCXX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4142,9 +4142,11 @@ void Parser::ParseCXX11AttributeSpecifier(ParsedAttributes &attrs,
assert(Tok.is(tok::l_square) && NextToken().is(tok::l_square) &&
"Not a double square bracket attribute list");

Diag(Tok.getLocation(), diag::warn_cxx98_compat_attribute);
SourceLocation OpenLoc = Tok.getLocation();
Diag(OpenLoc, diag::warn_cxx98_compat_attribute);

ConsumeBracket();
checkCompoundToken(OpenLoc, tok::l_square, CompoundToken::AttrBegin);
ConsumeBracket();

SourceLocation CommonScopeLoc;
Expand Down Expand Up @@ -4227,8 +4229,11 @@ void Parser::ParseCXX11AttributeSpecifier(ParsedAttributes &attrs,
<< AttrName;
}

SourceLocation CloseLoc = Tok.getLocation();
if (ExpectAndConsume(tok::r_square))
SkipUntil(tok::r_square);
else if (Tok.is(tok::r_square))
checkCompoundToken(CloseLoc, tok::r_square, CompoundToken::AttrEnd);
if (endLoc)
*endLoc = Tok.getLocation();
if (ExpectAndConsume(tok::r_square))
Expand Down
2 changes: 2 additions & 0 deletions clang/lib/Parse/ParseExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2840,6 +2840,8 @@ Parser::ParseParenExpression(ParenParseOption &ExprType, bool stopIfCastExpr,
if (ExprType >= CompoundStmt && Tok.is(tok::l_brace)) {
Diag(Tok, diag::ext_gnu_statement_expr);

checkCompoundToken(OpenLoc, tok::l_paren, CompoundToken::StmtExprBegin);

if (!getCurScope()->getFnParent() && !getCurScope()->getBlockParent()) {
Result = ExprError(Diag(OpenLoc, diag::err_stmtexpr_file_scope));
} else {
Expand Down
12 changes: 10 additions & 2 deletions clang/lib/Parse/ParseStmt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1135,9 +1135,17 @@ StmtResult Parser::ParseCompoundStatementBody(bool isStmtExpr) {
SourceLocation CloseLoc = Tok.getLocation();

// We broke out of the while loop because we found a '}' or EOF.
if (!T.consumeClose())
if (!T.consumeClose()) {
// If this is the '})' of a statement expression, check that it's written
// in a sensible way.
if (isStmtExpr && Tok.is(tok::r_paren))
checkCompoundToken(CloseLoc, tok::r_brace, CompoundToken::StmtExprEnd);
} else {
// Recover by creating a compound statement with what we parsed so far,
// instead of dropping everything and returning StmtError();
// instead of dropping everything and returning StmtError().
}

if (T.getCloseLocation().isValid())
CloseLoc = T.getCloseLocation();

return Actions.ActOnCompoundStmt(T.getOpenLocation(), CloseLoc,
Expand Down
33 changes: 33 additions & 0 deletions clang/lib/Parse/Parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,39 @@ bool Parser::expectIdentifier() {
return true;
}

void Parser::checkCompoundToken(SourceLocation FirstTokLoc,
tok::TokenKind FirstTokKind, CompoundToken Op) {
if (FirstTokLoc.isInvalid())
return;
SourceLocation SecondTokLoc = Tok.getLocation();

// We expect both tokens to come from the same FileID.
FileID FirstID = PP.getSourceManager().getFileID(FirstTokLoc);
FileID SecondID = PP.getSourceManager().getFileID(SecondTokLoc);
if (FirstID != SecondID) {
Diag(FirstTokLoc, (FirstTokLoc.isMacroID() || SecondTokLoc.isMacroID())
? diag::warn_compound_token_split_by_macro
: diag::warn_compound_token_split_by_file)
<< (FirstTokKind == Tok.getKind()) << FirstTokKind << Tok.getKind()
<< static_cast<int>(Op) << SourceRange(FirstTokLoc);
Diag(SecondTokLoc, diag::note_compound_token_split_second_token_here)
<< (FirstTokKind == Tok.getKind()) << Tok.getKind()
<< SourceRange(SecondTokLoc);
return;
}

// We expect the tokens to abut.
if (Tok.hasLeadingSpace()) {
SourceLocation SpaceLoc = PP.getLocForEndOfToken(FirstTokLoc);
if (SpaceLoc.isInvalid())
SpaceLoc = FirstTokLoc;
Diag(SpaceLoc, diag::warn_compound_token_split_by_whitespace)
<< (FirstTokKind == Tok.getKind()) << FirstTokKind << Tok.getKind()
<< static_cast<int>(Op) << SourceRange(FirstTokLoc, SecondTokLoc);
return;
}
}

//===----------------------------------------------------------------------===//
// Error recovery.
//===----------------------------------------------------------------------===//
Expand Down
3 changes: 3 additions & 0 deletions clang/test/Misc/warning-wall.c
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@ CHECK-NEXT: -Wdangling-else
CHECK-NEXT: -Wswitch
CHECK-NEXT: -Wswitch-bool
CHECK-NEXT: -Wmisleading-indentation
CHECK-NEXT: -Wcompound-token-split
CHECK-NEXT: -Wcompound-token-split-by-macro
CHECK-NEXT: -Wcompound-token-split-by-space


CHECK-NOT:-W
40 changes: 40 additions & 0 deletions clang/test/Parser/compound-token-split.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// RUN: %clang_cc1 %s -verify
// RUN: %clang_cc1 %s -verify=expected,space -Wcompound-token-split
// RUN: %clang_cc1 %s -verify=expected,space -Wall

#ifdef LSQUARE
[ // expected-note {{second '[' token is here}}
#else

#define VAR(type, name, init) type name = (init)

void f() {
VAR(int, x, {}); // #1
// expected-warning@#1 {{'(' and '{' tokens introducing statement expression appear in different macro expansion contexts}}
// expected-note-re@#1 {{{{^}}'{' token is here}}
//
// FIXME: It would be nice to suppress this when we already warned about the opening '({'.
// expected-warning@#1 {{'}' and ')' tokens terminating statement expression appear in different macro expansion contexts}}
// expected-note-re@#1 {{{{^}}')' token is here}}
//
// expected-error@#1 {{cannot initialize a variable of type 'int' with an rvalue of type 'void'}}
}

#define RPAREN )

int f2() {
int n = ({ 1; }RPAREN; // expected-warning {{'}' and ')' tokens terminating statement expression appear in different macro expansion contexts}} expected-note {{')' token is here}}
return n;
}

[ // expected-warning-re {{{{^}}'[' tokens introducing attribute appear in different source files}}
#define LSQUARE
#include __FILE__
noreturn ]] void g();

[[noreturn] ] void h(); // space-warning-re {{{{^}}']' tokens terminating attribute are separated by whitespace}}

struct X {};
int X:: *p; // space-warning {{'::' and '*' tokens forming pointer to member type are separated by whitespace}}

#endif

0 comments on commit 0e00a95

Please sign in to comment.