Add new diagnostic messages when too many arguments are presented to a

function-like macro.  Clang will attempt to correct the arguments by detecting
braced initializer lists:

1) If possible, suggest parentheses around arguments
containing braced lists which will give the proper number of arguments.
2) If a braced list is detected at the start of a macro argument, it cannot be
corrected by parentheses.  Instead, just point out the location of these
braced lists.

llvm-svn: 186971
This commit is contained in:
Richard Trieu 2013-07-23 18:01:49 +00:00
parent 8c4245455a
commit 79b45389c3
5 changed files with 385 additions and 18 deletions

View File

@ -990,6 +990,10 @@ public:
bool hasMaxRanges() const {
return NumRanges == DiagnosticsEngine::MaxRanges;
}
bool hasMaxFixItHints() const {
return NumFixits == DiagnosticsEngine::MaxFixItHints;
}
};
inline const DiagnosticBuilder &operator<<(const DiagnosticBuilder &DB,

View File

@ -460,6 +460,11 @@ def err_unterm_macro_invoc : Error<
"unterminated function-like macro invocation">;
def err_too_many_args_in_macro_invoc : Error<
"too many arguments provided to function-like macro invocation">;
def note_suggest_parens_for_macro : Note<
"parentheses are required around macro argument containing braced "
"initializer list">;
def note_init_list_at_beginning_of_macro_argument : Note<
"cannot use initializer list at the beginning of an macro argument">;
def err_too_few_args_in_macro_invoc : Error<
"too few arguments provided to function-like macro invocation">;
def err_pp_bad_paste : Error<

View File

@ -390,6 +390,136 @@ bool Preprocessor::HandleMacroExpandedIdentifier(Token &Identifier,
return false;
}
enum Bracket {
Brace,
Paren
};
/// CheckMatchedBrackets - Returns true if the braces and parentheses in the
/// token vector are properly nested.
static bool CheckMatchedBrackets(const SmallVectorImpl<Token> &Tokens) {
SmallVector<Bracket, 8> Brackets;
for (SmallVectorImpl<Token>::const_iterator I = Tokens.begin(),
E = Tokens.end();
I != E; ++I) {
if (I->is(tok::l_paren)) {
Brackets.push_back(Paren);
} else if (I->is(tok::r_paren)) {
if (Brackets.empty() || Brackets.back() == Brace)
return false;
Brackets.pop_back();
} else if (I->is(tok::l_brace)) {
Brackets.push_back(Brace);
} else if (I->is(tok::r_brace)) {
if (Brackets.empty() || Brackets.back() == Paren)
return false;
Brackets.pop_back();
}
}
if (!Brackets.empty())
return false;
return true;
}
/// GenerateNewArgTokens - Returns true if OldTokens can be converted to a new
/// vector of tokens in NewTokens. The new number of arguments will be placed
/// in NumArgs and the ranges which need to surrounded in parentheses will be
/// in ParenHints.
/// Returns false if the token stream cannot be changed. If this is because
/// of an initializer list starting a macro argument, the range of those
/// initializer lists will be place in InitLists.
static bool GenerateNewArgTokens(Preprocessor &PP,
SmallVectorImpl<Token> &OldTokens,
SmallVectorImpl<Token> &NewTokens,
unsigned &NumArgs,
SmallVectorImpl<SourceRange> &ParenHints,
SmallVectorImpl<SourceRange> &InitLists) {
if (!CheckMatchedBrackets(OldTokens))
return false;
// Once it is known that the brackets are matched, only a simple count of the
// braces is needed.
unsigned Braces = 0;
// First token of a new macro argument.
SmallVectorImpl<Token>::iterator ArgStartIterator = OldTokens.begin();
// First closing brace in a new macro argument. Used to generate
// SourceRanges for InitLists.
SmallVectorImpl<Token>::iterator ClosingBrace = OldTokens.end();
NumArgs = 0;
Token TempToken;
// Set to true when a macro separator token is found inside a braced list.
// If true, the fixed argument spans multiple old arguments and ParenHints
// will be updated.
bool FoundSeparatorToken = false;
for (SmallVectorImpl<Token>::iterator I = OldTokens.begin(),
E = OldTokens.end();
I != E; ++I) {
if (I->is(tok::l_brace)) {
++Braces;
} else if (I->is(tok::r_brace)) {
--Braces;
if (Braces == 0 && ClosingBrace == E && FoundSeparatorToken)
ClosingBrace = I;
} else if (I->is(tok::eof)) {
// EOF token is used to separate macro arguments
if (Braces != 0) {
// Assume comma separator is actually braced list separator and change
// it back to a comma.
FoundSeparatorToken = true;
I->setKind(tok::comma);
I->setLength(1);
} else { // Braces == 0
// Separator token still separates arguments.
++NumArgs;
// If the argument starts with a brace, it can't be fixed with
// parentheses. A different diagnostic will be given.
if (FoundSeparatorToken && ArgStartIterator->is(tok::l_brace)) {
InitLists.push_back(
SourceRange(ArgStartIterator->getLocation(),
PP.getLocForEndOfToken(ClosingBrace->getLocation())));
ClosingBrace = E;
}
// Add left paren
if (FoundSeparatorToken) {
TempToken.startToken();
TempToken.setKind(tok::l_paren);
TempToken.setLocation(ArgStartIterator->getLocation());
TempToken.setLength(0);
NewTokens.push_back(TempToken);
}
// Copy over argument tokens
NewTokens.insert(NewTokens.end(), ArgStartIterator, I);
// Add right paren and store the paren locations in ParenHints
if (FoundSeparatorToken) {
SourceLocation Loc = PP.getLocForEndOfToken((I - 1)->getLocation());
TempToken.startToken();
TempToken.setKind(tok::r_paren);
TempToken.setLocation(Loc);
TempToken.setLength(0);
NewTokens.push_back(TempToken);
ParenHints.push_back(SourceRange(ArgStartIterator->getLocation(),
Loc));
}
// Copy separator token
NewTokens.push_back(*I);
// Reset values
ArgStartIterator = I + 1;
FoundSeparatorToken = false;
}
}
}
return !ParenHints.empty() && InitLists.empty();
}
/// ReadFunctionLikeMacroArgs - After reading "MACRO" and knowing that the next
/// token is the '(' of the macro, this method is invoked to read all of the
/// actual arguments specified for the macro invocation. This returns null on
@ -415,6 +545,8 @@ MacroArgs *Preprocessor::ReadFunctionLikeMacroArgs(Token &MacroName,
SmallVector<Token, 64> ArgTokens;
bool ContainsCodeCompletionTok = false;
SourceLocation TooManyArgsLoc;
unsigned NumActuals = 0;
while (Tok.isNot(tok::r_paren)) {
if (ContainsCodeCompletionTok && (Tok.is(tok::eof) || Tok.is(tok::eod)))
@ -504,22 +636,15 @@ MacroArgs *Preprocessor::ReadFunctionLikeMacroArgs(Token &MacroName,
// If this is not a variadic macro, and too many args were specified, emit
// an error.
if (!isVariadic && NumFixedArgsLeft == 0) {
if (!isVariadic && NumFixedArgsLeft == 0 && TooManyArgsLoc.isInvalid()) {
if (ArgTokens.size() != ArgTokenStart)
ArgStartLoc = ArgTokens[ArgTokenStart].getLocation();
if (!ContainsCodeCompletionTok) {
// Emit the diagnostic at the macro name in case there is a missing ).
// Emitting it at the , could be far away from the macro name.
Diag(ArgStartLoc, diag::err_too_many_args_in_macro_invoc);
Diag(MI->getDefinitionLoc(), diag::note_macro_here)
<< MacroName.getIdentifierInfo();
return 0;
}
TooManyArgsLoc = ArgTokens[ArgTokenStart].getLocation();
else
TooManyArgsLoc = ArgStartLoc;
}
// Empty arguments are standard in C99 and C++0x, and are supported as an extension in
// other modes.
// Empty arguments are standard in C99 and C++0x, and are supported as an
// extension in other modes.
if (ArgTokens.size() == ArgTokenStart && !LangOpts.C99)
Diag(Tok, LangOpts.CPlusPlus11 ?
diag::warn_cxx98_compat_empty_fnmacro_arg :
@ -533,16 +658,66 @@ MacroArgs *Preprocessor::ReadFunctionLikeMacroArgs(Token &MacroName,
EOFTok.setLength(0);
ArgTokens.push_back(EOFTok);
++NumActuals;
if (!ContainsCodeCompletionTok || NumFixedArgsLeft != 0) {
assert(NumFixedArgsLeft != 0 && "Too many arguments parsed");
if (!ContainsCodeCompletionTok && NumFixedArgsLeft != 0)
--NumFixedArgsLeft;
}
}
// Okay, we either found the r_paren. Check to see if we parsed too few
// arguments.
unsigned MinArgsExpected = MI->getNumArgs();
// If this is not a variadic macro, and too many args were specified, emit
// an error.
if (!isVariadic && NumActuals > MinArgsExpected &&
!ContainsCodeCompletionTok) {
// Emit the diagnostic at the macro name in case there is a missing ).
// Emitting it at the , could be far away from the macro name.
Diag(TooManyArgsLoc, diag::err_too_many_args_in_macro_invoc);
Diag(MI->getDefinitionLoc(), diag::note_macro_here)
<< MacroName.getIdentifierInfo();
// Commas from braced initializer lists will be treated as argument
// separators inside macros. Attempt to correct for this with parentheses.
// TODO: See if this can be generalized to angle brackets for templates
// inside macro arguments.
SmallVector<Token, 64> FixedArgTokens;
unsigned FixedNumArgs = 0;
SmallVector<SourceRange, 4> ParenHints, InitLists;
if (!GenerateNewArgTokens(*this, ArgTokens, FixedArgTokens, FixedNumArgs,
ParenHints, InitLists)) {
if (!InitLists.empty()) {
DiagnosticBuilder DB =
Diag(MacroName,
diag::note_init_list_at_beginning_of_macro_argument);
for (SmallVector<SourceRange, 4>::iterator
Range = InitLists.begin(), RangeEnd = InitLists.end();
Range != RangeEnd; ++Range) {
if (DB.hasMaxRanges())
break;
DB << *Range;
}
}
return 0;
}
if (FixedNumArgs != MinArgsExpected)
return 0;
DiagnosticBuilder DB = Diag(MacroName, diag::note_suggest_parens_for_macro);
for (SmallVector<SourceRange, 4>::iterator
ParenLocation = ParenHints.begin(), ParenEnd = ParenHints.end();
ParenLocation != ParenEnd; ++ParenLocation) {
if (DB.hasMaxFixItHints())
break;
DB << FixItHint::CreateInsertion(ParenLocation->getBegin(), "(");
if (DB.hasMaxFixItHints())
break;
DB << FixItHint::CreateInsertion(ParenLocation->getEnd(), ")");
}
ArgTokens.swap(FixedArgTokens);
NumActuals = FixedNumArgs;
}
// See MacroArgs instance var for description of this.
bool isVarargsElided = false;

View File

@ -13,7 +13,8 @@ zero(1, 2, 3); /* expected-error {{too many arguments provided to function-li
one() /* ok */
one(a)
one(a,) /* expected-error {{too many arguments provided to function-like macro invocation}} */
one(a,) /* expected-error {{too many arguments provided to function-like macro invocation}} \
expected-warning {{empty macro arguments are a C99 feature}}*/
one(a, b) /* expected-error {{too many arguments provided to function-like macro invocation}} */
two() /* expected-error {{too few arguments provided to function-like macro invocation}} */
@ -25,7 +26,7 @@ two(
, /* expected-warning {{empty macro arguments are a C99 feature}} */
, /* expected-warning {{empty macro arguments are a C99 feature}} \
expected-error {{too many arguments provided to function-like macro invocation}} */
)
) /* expected-warning {{empty macro arguments are a C99 feature}} */
two(,) /* expected-warning 2 {{empty macro arguments are a C99 feature}} */

View File

@ -0,0 +1,182 @@
// RUN: %clang_cc1 -std=c++11 -fsyntax-only -verify %s
// RUN: %clang_cc1 -std=c++11 -fsyntax-only -fdiagnostics-parseable-fixits %s 2>&1 | FileCheck %s
namespace std {
template <class X>
class initializer_list {
public:
initializer_list();
};
}
class Foo {
public:
Foo();
Foo(std::initializer_list<int>);
bool operator==(const Foo);
Foo operator+(const Foo);
};
#define EQ(x,y) (void)(x == y) // expected-note 6{{defined here}}
void test_EQ() {
Foo F;
F = Foo{1,2};
EQ(F,F);
EQ(F,Foo());
EQ(F,Foo({1,2,3}));
EQ(Foo({1,2,3}),F);
EQ((Foo{1,2,3}),(Foo{1,2,3}));
EQ(F, F + F);
EQ(F, Foo({1,2,3}) + Foo({1,2,3}));
EQ(F, (Foo{1,2,3} + Foo{1,2,3}));
EQ(F,Foo{1,2,3});
// expected-error@-1 {{too many arguments provided}}
// expected-note@-2 {{parentheses are required}}
EQ(Foo{1,2,3},F);
// expected-error@-1 {{too many arguments provided}}
// expected-note@-2 {{parentheses are required}}
EQ(Foo{1,2,3},Foo{1,2,3});
// expected-error@-1 {{too many arguments provided}}
// expected-note@-2 {{parentheses are required}}
EQ(Foo{1,2,3} + Foo{1,2,3}, F);
// expected-error@-1 {{too many arguments provided}}
// expected-note@-2 {{parentheses are required}}
EQ(F, Foo({1,2,3}) + Foo{1,2,3});
// expected-error@-1 {{too many arguments provided}}
// expected-note@-2 {{parentheses are required}}
EQ(F, Foo{1,2,3} + Foo{1,2,3});
// expected-error@-1 {{too many arguments provided}}
// expected-note@-2 {{parentheses are required}}
}
// CHECK: fix-it:"{{.*}}macro_with_initializer_list.cpp":{33:8-33:8}:"("
// CHECK: fix-it:"{{.*}}macro_with_initializer_list.cpp":{33:18-33:18}:")"
// CHECK: fix-it:"{{.*}}macro_with_initializer_list.cpp":{36:6-36:6}:"("
// CHECK: fix-it:"{{.*}}macro_with_initializer_list.cpp":{36:16-36:16}:")"
// CHECK: fix-it:"{{.*}}macro_with_initializer_list.cpp":{39:6-39:6}:"("
// CHECK: fix-it:"{{.*}}macro_with_initializer_list.cpp":{39:16-39:16}:")"
// CHECK: fix-it:"{{.*}}macro_with_initializer_list.cpp":{39:17-39:17}:"("
// CHECK: fix-it:"{{.*}}macro_with_initializer_list.cpp":{39:27-39:27}:")"
// CHECK: fix-it:"{{.*}}macro_with_initializer_list.cpp":{43:6-43:6}:"("
// CHECK: fix-it:"{{.*}}macro_with_initializer_list.cpp":{43:29-43:29}:")"
// CHECK: fix-it:"{{.*}}macro_with_initializer_list.cpp":{46:9-46:9}:"("
// CHECK: fix-it:"{{.*}}macro_with_initializer_list.cpp":{46:34-46:34}:")"
// CHECK: fix-it:"{{.*}}macro_with_initializer_list.cpp":{49:9-49:9}:"("
// CHECK: fix-it:"{{.*}}macro_with_initializer_list.cpp":{49:32-49:32}:")"
#define NE(x,y) (void)(x != y) // expected-note 6{{defined here}}
// Operator != isn't defined. This tests that the corrected macro arguments
// are used in the macro expansion.
void test_NE() {
Foo F;
NE(F,F);
// expected-error@-1 {{invalid operands}}
NE(F,Foo());
// expected-error@-1 {{invalid operands}}
NE(F,Foo({1,2,3}));
// expected-error@-1 {{invalid operands}}
NE((Foo{1,2,3}),(Foo{1,2,3}));
// expected-error@-1 {{invalid operands}}
NE(F,Foo{1,2,3});
// expected-error@-1 {{too many arguments provided}}
// expected-note@-2 {{parentheses are required}}
// expected-error@-3 {{invalid operands}}
NE(Foo{1,2,3},F);
// expected-error@-1 {{too many arguments provided}}
// expected-note@-2 {{parentheses are required}}
// expected-error@-3 {{invalid operands}}
NE(Foo{1,2,3},Foo{1,2,3});
// expected-error@-1 {{too many arguments provided}}
// expected-note@-2 {{parentheses are required}}
// expected-error@-3 {{invalid operands}}
NE(Foo{1,2,3} + Foo{1,2,3}, F);
// expected-error@-1 {{too many arguments provided}}
// expected-note@-2 {{parentheses are required}}
// expected-error@-3 {{invalid operands}}
NE(F, Foo({1,2,3}) + Foo{1,2,3});
// expected-error@-1 {{too many arguments provided}}
// expected-note@-2 {{parentheses are required}}
// expected-error@-3 {{invalid operands}}
NE(F, Foo{1,2,3} + Foo{1,2,3});
// expected-error@-1 {{too many arguments provided}}
// expected-note@-2 {{parentheses are required}}
// expected-error@-3 {{invalid operands}}
}
// CHECK: fix-it:"{{.*}}macro_with_initializer_list.cpp":{89:8-89:8}:"("
// CHECK: fix-it:"{{.*}}macro_with_initializer_list.cpp":{89:18-89:18}:")"
// CHECK: fix-it:"{{.*}}macro_with_initializer_list.cpp":{93:6-93:6}:"("
// CHECK: fix-it:"{{.*}}macro_with_initializer_list.cpp":{93:16-93:16}:")"
// CHECK: fix-it:"{{.*}}macro_with_initializer_list.cpp":{97:6-97:6}:"("
// CHECK: fix-it:"{{.*}}macro_with_initializer_list.cpp":{97:16-97:16}:")"
// CHECK: fix-it:"{{.*}}macro_with_initializer_list.cpp":{97:17-97:17}:"("
// CHECK: fix-it:"{{.*}}macro_with_initializer_list.cpp":{97:27-97:27}:")"
// CHECK: fix-it:"{{.*}}macro_with_initializer_list.cpp":{102:6-102:6}:"("
// CHECK: fix-it:"{{.*}}macro_with_initializer_list.cpp":{102:29-102:29}:")"
// CHECK: fix-it:"{{.*}}macro_with_initializer_list.cpp":{106:9-106:9}:"("
// CHECK: fix-it:"{{.*}}macro_with_initializer_list.cpp":{106:34-106:34}:")"
// CHECK: fix-it:"{{.*}}macro_with_initializer_list.cpp":{110:9-110:9}:"("
// CHECK: fix-it:"{{.*}}macro_with_initializer_list.cpp":{110:32-110:32}:")"
#define INIT(var, init) Foo var = init; // expected-note 3{{defined here}}
// Can't use an initializer list as a macro argument. The commas in the list
// will be interpretted as argument separaters and adding parenthesis will
// make it no longer an initializer list.
void test() {
INIT(a, Foo());
INIT(b, Foo({1, 2, 3}));
INIT(c, Foo());
INIT(d, Foo{1, 2, 3});
// expected-error@-1 {{too many arguments provided}}
// expected-note@-2 {{parentheses are required}}
// Can't be fixed by parentheses.
INIT(e, {1, 2, 3});
// expected-error@-1 {{too many arguments provided}}
// expected-error@-2 {{use of undeclared identifier}}
// expected-note@-3 {{cannot use initializer list at the beginning of an macro argument}}
// Can't be fixed by parentheses.
INIT(e, {1, 2, 3} + {1, 2, 3});
// expected-error@-1 {{too many arguments provided}}
// expected-error@-2 {{use of undeclared identifier}}
// expected-note@-3 {{cannot use initializer list at the beginning of an macro argument}}
}
// CHECK: fix-it:"{{.*}}macro_with_initializer_list.cpp":{145:11-145:11}:"("
// CHECK: fix-it:"{{.*}}macro_with_initializer_list.cpp":{145:23-145:23}:")"
#define M(name,a,b,c,d,e,f,g,h,i,j,k,l) \
Foo name = a + b + c + d + e + f + g + h + i + j + k + l;
// expected-note@-2 2{{defined here}}
void test2() {
M(F1, Foo(), Foo(), Foo(), Foo(), Foo(), Foo(),
Foo(), Foo(), Foo(), Foo(), Foo(), Foo());
M(F2, Foo{1,2,3}, Foo{1,2,3}, Foo{1,2,3}, Foo{1,2,3}, Foo{1,2,3}, Foo{1,2,3},
Foo{1,2,3}, Foo{1,2,3}, Foo{1,2,3}, Foo{1,2,3}, Foo{1,2,3}, Foo{1,2,3});
// expected-error@-2 {{too many arguments provided}}
// expected-note@-3 {{parentheses are required}}
M(F3, {1,2,3}, {1,2,3}, {1,2,3}, {1,2,3}, {1,2,3}, {1,2,3},
{1,2,3}, {1,2,3}, {1,2,3}, {1,2,3}, {1,2,3}, {1,2,3});
// expected-error@-2 {{too many arguments provided}}
// expected-error@-3 {{use of undeclared identifier}}
// expected-note@-4 {{cannot use initializer list at the beginning of an macro argument}}
}