Consolidate and improve the handling of built-in feature-like macros

Summary:
The parsing logic has been separated out from the macro implementation logic, leading to a number of improvements:

* Gracefully handle unexpected/invalid tokens, too few, too many and nested parameters
* Provide consistent behaviour between all built-in feature-like macros
* Simplify the implementation of macro logic
* Fix __is_identifier to correctly return '0' for non-identifiers

Reviewers: doug.gregor, rsmith

Subscribers: rsmith, cfe-commits

Differential Revision: http://reviews.llvm.org/D17149

git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@265381 91177308-0d34-0410-b5e6-96231b3b80d8
This commit is contained in:
Andy Gibbs 2016-04-05 08:36:47 +00:00
parent a6a2b89b5f
commit 548ed00533
6 changed files with 259 additions and 170 deletions

View File

@ -401,6 +401,7 @@ def err_pp_expected_rparen : Error<"expected ')' in preprocessor expression">;
def err_pp_expected_eol : Error<
"expected end of line in preprocessor expression">;
def err_pp_expected_after : Error<"missing %1 after %0">;
def err_pp_nested_paren : Error<"nested parentheses not permitted in %0">;
def err_pp_colon_without_question : Error<"':' without preceding '?'">;
def err_pp_division_by_zero : Error<
"division by zero in preprocessor expression">;

View File

@ -1053,9 +1053,8 @@ static void ComputeDATE_TIME(SourceLocation &DATELoc, SourceLocation &TIMELoc,
/// HasFeature - Return true if we recognize and implement the feature
/// specified by the identifier as a standard language feature.
static bool HasFeature(const Preprocessor &PP, const IdentifierInfo *II) {
static bool HasFeature(const Preprocessor &PP, StringRef Feature) {
const LangOptions &LangOpts = PP.getLangOpts();
StringRef Feature = II->getName();
// Normalize the feature name, __foo__ becomes foo.
if (Feature.startswith("__") && Feature.endswith("__") && Feature.size() >= 4)
@ -1229,8 +1228,8 @@ static bool HasFeature(const Preprocessor &PP, const IdentifierInfo *II) {
/// HasExtension - Return true if we recognize and implement the feature
/// specified by the identifier, either as an extension or a standard language
/// feature.
static bool HasExtension(const Preprocessor &PP, const IdentifierInfo *II) {
if (HasFeature(PP, II))
static bool HasExtension(const Preprocessor &PP, StringRef Extension) {
if (HasFeature(PP, Extension))
return true;
// If the use of an extension results in an error diagnostic, extensions are
@ -1240,7 +1239,6 @@ static bool HasExtension(const Preprocessor &PP, const IdentifierInfo *II) {
return false;
const LangOptions &LangOpts = PP.getLangOpts();
StringRef Extension = II->getName();
// Normalize the extension name, __foo__ becomes foo.
if (Extension.startswith("__") && Extension.endswith("__") &&
@ -1424,48 +1422,120 @@ static bool EvaluateHasIncludeNext(Token &Tok,
return EvaluateHasIncludeCommon(Tok, II, PP, Lookup, LookupFromFile);
}
/// \brief Process __building_module(identifier) expression.
/// \returns true if we are building the named module, false otherwise.
static bool EvaluateBuildingModule(Token &Tok,
IdentifierInfo *II, Preprocessor &PP) {
// Get '('.
PP.LexNonComment(Tok);
// Ensure we have a '('.
/// \brief Process single-argument builtin feature-like macros that return
/// integer values.
static void EvaluateFeatureLikeBuiltinMacro(llvm::raw_svector_ostream& OS,
Token &Tok, IdentifierInfo *II,
Preprocessor &PP,
llvm::function_ref<
int(Token &Tok,
bool &HasLexedNextTok)> Op) {
// Parse the initial '('.
PP.LexUnexpandedToken(Tok);
if (Tok.isNot(tok::l_paren)) {
PP.Diag(Tok.getLocation(), diag::err_pp_expected_after) << II
<< tok::l_paren;
return false;
// Provide a dummy '0' value on output stream to elide further errors.
if (!Tok.isOneOf(tok::eof, tok::eod)) {
OS << 0;
Tok.setKind(tok::numeric_constant);
}
return;
}
// Save '(' location for possible missing ')' message.
unsigned ParenDepth = 1;
SourceLocation LParenLoc = Tok.getLocation();
llvm::Optional<int> Result;
// Get the module name.
PP.LexNonComment(Tok);
Token ResultTok;
bool SuppressDiagnostic = false;
while (true) {
// Parse next token.
PP.LexUnexpandedToken(Tok);
// Ensure that we have an identifier.
if (Tok.isNot(tok::identifier)) {
PP.Diag(Tok.getLocation(), diag::err_expected_id_building_module);
return false;
already_lexed:
switch (Tok.getKind()) {
case tok::eof:
case tok::eod:
// Don't provide even a dummy value if the eod or eof marker is
// reached. Simply provide a diagnostic.
PP.Diag(Tok.getLocation(), diag::err_unterm_macro_invoc);
return;
case tok::comma:
if (!SuppressDiagnostic) {
PP.Diag(Tok.getLocation(), diag::err_too_many_args_in_macro_invoc);
SuppressDiagnostic = true;
}
continue;
case tok::l_paren:
++ParenDepth;
if (Result.hasValue())
break;
if (!SuppressDiagnostic) {
PP.Diag(Tok.getLocation(), diag::err_pp_nested_paren) << II;
SuppressDiagnostic = true;
}
continue;
case tok::r_paren:
if (--ParenDepth > 0)
continue;
// The last ')' has been reached; return the value if one found or
// a diagnostic and a dummy value.
if (Result.hasValue())
OS << Result.getValue();
else {
OS << 0;
if (!SuppressDiagnostic)
PP.Diag(Tok.getLocation(), diag::err_too_few_args_in_macro_invoc);
}
Tok.setKind(tok::numeric_constant);
return;
default: {
// Parse the macro argument, if one not found so far.
if (Result.hasValue())
break;
bool HasLexedNextToken = false;
Result = Op(Tok, HasLexedNextToken);
ResultTok = Tok;
if (HasLexedNextToken)
goto already_lexed;
continue;
}
}
// Diagnose missing ')'.
if (!SuppressDiagnostic) {
if (auto Diag = PP.Diag(Tok.getLocation(), diag::err_pp_expected_after)) {
if (IdentifierInfo *LastII = ResultTok.getIdentifierInfo())
Diag << LastII;
else
Diag << ResultTok.getKind();
Diag << tok::r_paren << ResultTok.getLocation();
}
PP.Diag(LParenLoc, diag::note_matching) << tok::l_paren;
SuppressDiagnostic = true;
}
}
}
bool Result =
PP.getLangOpts().CompilingModule &&
Tok.getIdentifierInfo()->getName() == PP.getLangOpts().CurrentModule;
/// \brief Helper function to return the IdentifierInfo structure of a Token
/// or generate a diagnostic if none available.
static IdentifierInfo *ExpectFeatureIdentifierInfo(Token &Tok,
Preprocessor &PP,
signed DiagID) {
IdentifierInfo *II;
if (!Tok.isAnnotation() && (II = Tok.getIdentifierInfo()))
return II;
// Get ')'.
PP.LexNonComment(Tok);
// Ensure we have a trailing ).
if (Tok.isNot(tok::r_paren)) {
PP.Diag(Tok.getLocation(), diag::err_pp_expected_after) << II
<< tok::r_paren;
PP.Diag(LParenLoc, diag::note_matching) << tok::l_paren;
return false;
}
return Result;
PP.Diag(Tok.getLocation(), DiagID);
return nullptr;
}
/// ExpandBuiltinMacro - If an identifier token is read that is to be expanded
@ -1601,84 +1671,81 @@ void Preprocessor::ExpandBuiltinMacro(Token &Tok) {
// __COUNTER__ expands to a simple numeric value.
OS << CounterValue++;
Tok.setKind(tok::numeric_constant);
} else if (II == Ident__has_feature ||
II == Ident__has_extension ||
II == Ident__has_builtin ||
II == Ident__is_identifier ||
II == Ident__has_attribute ||
II == Ident__has_declspec ||
II == Ident__has_cpp_attribute) {
// The argument to these builtins should be a parenthesized identifier.
SourceLocation StartLoc = Tok.getLocation();
bool IsValid = false;
IdentifierInfo *FeatureII = nullptr;
IdentifierInfo *ScopeII = nullptr;
// Read the '('.
LexUnexpandedToken(Tok);
if (Tok.is(tok::l_paren)) {
// Read the identifier
LexUnexpandedToken(Tok);
if ((FeatureII = Tok.getIdentifierInfo())) {
// If we're checking __has_cpp_attribute, it is possible to receive a
// scope token. Read the "::", if it's available.
LexUnexpandedToken(Tok);
bool IsScopeValid = true;
if (II == Ident__has_cpp_attribute && Tok.is(tok::coloncolon)) {
LexUnexpandedToken(Tok);
// The first thing we read was not the feature, it was the scope.
ScopeII = FeatureII;
if ((FeatureII = Tok.getIdentifierInfo()))
LexUnexpandedToken(Tok);
else
IsScopeValid = false;
} else if (II == Ident__has_feature) {
EvaluateFeatureLikeBuiltinMacro(OS, Tok, II, *this,
[this](Token &Tok, bool &HasLexedNextToken) -> int {
IdentifierInfo *II = ExpectFeatureIdentifierInfo(Tok, *this,
diag::err_feature_check_malformed);
return II && HasFeature(*this, II->getName());
});
} else if (II == Ident__has_extension) {
EvaluateFeatureLikeBuiltinMacro(OS, Tok, II, *this,
[this](Token &Tok, bool &HasLexedNextToken) -> int {
IdentifierInfo *II = ExpectFeatureIdentifierInfo(Tok, *this,
diag::err_feature_check_malformed);
return II && HasExtension(*this, II->getName());
});
} else if (II == Ident__has_builtin) {
EvaluateFeatureLikeBuiltinMacro(OS, Tok, II, *this,
[this](Token &Tok, bool &HasLexedNextToken) -> int {
IdentifierInfo *II = ExpectFeatureIdentifierInfo(Tok, *this,
diag::err_feature_check_malformed);
if (!II)
return false;
else if (II->getBuiltinID() != 0)
return true;
else {
const LangOptions &LangOpts = getLangOpts();
return llvm::StringSwitch<bool>(II->getName())
.Case("__make_integer_seq", LangOpts.CPlusPlus)
.Default(false);
}
// Read the closing paren.
if (IsScopeValid && Tok.is(tok::r_paren))
IsValid = true;
}
// Eat tokens until ')'.
while (Tok.isNot(tok::r_paren) && Tok.isNot(tok::eod) &&
Tok.isNot(tok::eof))
});
} else if (II == Ident__is_identifier) {
EvaluateFeatureLikeBuiltinMacro(OS, Tok, II, *this,
[](Token &Tok, bool &HasLexedNextToken) -> int {
return Tok.is(tok::identifier);
});
} else if (II == Ident__has_attribute) {
EvaluateFeatureLikeBuiltinMacro(OS, Tok, II, *this,
[this](Token &Tok, bool &HasLexedNextToken) -> int {
IdentifierInfo *II = ExpectFeatureIdentifierInfo(Tok, *this,
diag::err_feature_check_malformed);
return II ? hasAttribute(AttrSyntax::GNU, nullptr, II,
getTargetInfo(), getLangOpts()) : 0;
});
} else if (II == Ident__has_declspec) {
EvaluateFeatureLikeBuiltinMacro(OS, Tok, II, *this,
[this](Token &Tok, bool &HasLexedNextToken) -> int {
IdentifierInfo *II = ExpectFeatureIdentifierInfo(Tok, *this,
diag::err_feature_check_malformed);
return II ? hasAttribute(AttrSyntax::Declspec, nullptr, II,
getTargetInfo(), getLangOpts()) : 0;
});
} else if (II == Ident__has_cpp_attribute) {
EvaluateFeatureLikeBuiltinMacro(OS, Tok, II, *this,
[this](Token &Tok, bool &HasLexedNextToken) -> int {
IdentifierInfo *ScopeII = nullptr;
IdentifierInfo *II = ExpectFeatureIdentifierInfo(Tok, *this,
diag::err_feature_check_malformed);
if (!II)
return false;
// It is possible to receive a scope token. Read the "::", if it is
// available, and the subsequent identifier.
LexUnexpandedToken(Tok);
}
if (Tok.isNot(tok::coloncolon))
HasLexedNextToken = true;
else {
ScopeII = II;
LexUnexpandedToken(Tok);
II = ExpectFeatureIdentifierInfo(Tok, *this,
diag::err_feature_check_malformed);
}
int Value = 0;
if (!IsValid)
Diag(StartLoc, diag::err_feature_check_malformed);
else if (II == Ident__is_identifier)
Value = FeatureII->getTokenID() == tok::identifier;
else if (II == Ident__has_builtin) {
// Check for a builtin is trivial.
if (FeatureII->getBuiltinID() != 0) {
Value = true;
} else {
StringRef Feature = FeatureII->getName();
Value = llvm::StringSwitch<bool>(Feature)
.Case("__make_integer_seq", getLangOpts().CPlusPlus)
.Default(false);
}
} else if (II == Ident__has_attribute)
Value = hasAttribute(AttrSyntax::GNU, nullptr, FeatureII,
getTargetInfo(), getLangOpts());
else if (II == Ident__has_cpp_attribute)
Value = hasAttribute(AttrSyntax::CXX, ScopeII, FeatureII,
getTargetInfo(), getLangOpts());
else if (II == Ident__has_declspec)
Value = hasAttribute(AttrSyntax::Declspec, nullptr, FeatureII,
getTargetInfo(), getLangOpts());
else if (II == Ident__has_extension)
Value = HasExtension(*this, FeatureII);
else {
assert(II == Ident__has_feature && "Must be feature check");
Value = HasFeature(*this, FeatureII);
}
if (!IsValid)
return;
OS << Value;
Tok.setKind(tok::numeric_constant);
return II ? hasAttribute(AttrSyntax::CXX, ScopeII, II,
getTargetInfo(), getLangOpts()) : 0;
});
} else if (II == Ident__has_include ||
II == Ident__has_include_next) {
// The argument to these two builtins should be a parenthesized
@ -1696,64 +1763,44 @@ void Preprocessor::ExpandBuiltinMacro(Token &Tok) {
Tok.setKind(tok::numeric_constant);
} else if (II == Ident__has_warning) {
// The argument should be a parenthesized string literal.
// The argument to these builtins should be a parenthesized identifier.
SourceLocation StartLoc = Tok.getLocation();
bool IsValid = false;
bool Value = false;
// Read the '('.
LexUnexpandedToken(Tok);
do {
if (Tok.isNot(tok::l_paren)) {
Diag(StartLoc, diag::err_warning_check_malformed);
break;
}
EvaluateFeatureLikeBuiltinMacro(OS, Tok, II, *this,
[this](Token &Tok, bool &HasLexedNextToken) -> int {
std::string WarningName;
SourceLocation StrStartLoc = Tok.getLocation();
LexUnexpandedToken(Tok);
std::string WarningName;
SourceLocation StrStartLoc = Tok.getLocation();
if (!FinishLexStringLiteral(Tok, WarningName, "'__has_warning'",
/*MacroExpansion=*/false)) {
// Eat tokens until ')'.
while (Tok.isNot(tok::r_paren) && Tok.isNot(tok::eod) &&
Tok.isNot(tok::eof))
LexUnexpandedToken(Tok);
break;
}
HasLexedNextToken = Tok.is(tok::string_literal);
if (!FinishLexStringLiteral(Tok, WarningName, "'__has_warning'",
/*MacroExpansion=*/false))
return false;
// Is the end a ')'?
if (!(IsValid = Tok.is(tok::r_paren))) {
Diag(StartLoc, diag::err_warning_check_malformed);
break;
}
// FIXME: Should we accept "-R..." flags here, or should that be
// handled by a separate __has_remark?
if (WarningName.size() < 3 || WarningName[0] != '-' ||
WarningName[1] != 'W') {
Diag(StrStartLoc, diag::warn_has_warning_invalid_option);
return false;
}
// FIXME: Should we accept "-R..." flags here, or should that be handled
// by a separate __has_remark?
if (WarningName.size() < 3 || WarningName[0] != '-' ||
WarningName[1] != 'W') {
Diag(StrStartLoc, diag::warn_has_warning_invalid_option);
break;
}
// Finally, check if the warning flags maps to a diagnostic group.
// We construct a SmallVector here to talk to getDiagnosticIDs().
// Although we don't use the result, this isn't a hot path, and not
// worth special casing.
SmallVector<diag::kind, 10> Diags;
Value = !getDiagnostics().getDiagnosticIDs()->
getDiagnosticsInGroup(diag::Flavor::WarningOrError,
WarningName.substr(2), Diags);
} while (false);
if (!IsValid)
return;
OS << (int)Value;
Tok.setKind(tok::numeric_constant);
// Finally, check if the warning flags maps to a diagnostic group.
// We construct a SmallVector here to talk to getDiagnosticIDs().
// Although we don't use the result, this isn't a hot path, and not
// worth special casing.
SmallVector<diag::kind, 10> Diags;
return !getDiagnostics().getDiagnosticIDs()->
getDiagnosticsInGroup(diag::Flavor::WarningOrError,
WarningName.substr(2), Diags);
});
} else if (II == Ident__building_module) {
// The argument to this builtin should be an identifier. The
// builtin evaluates to 1 when that identifier names the module we are
// currently building.
OS << (int)EvaluateBuildingModule(Tok, II, *this);
Tok.setKind(tok::numeric_constant);
EvaluateFeatureLikeBuiltinMacro(OS, Tok, II, *this,
[this](Token &Tok, bool &HasLexedNextToken) -> int {
IdentifierInfo *II = ExpectFeatureIdentifierInfo(Tok, *this,
diag::err_expected_id_building_module);
return getLangOpts().CompilingModule && II &&
(II->getName() == getLangOpts().CurrentModule);
});
} else if (II == Ident__MODULE__) {
// The current module as an identifier.
OS << getLangOpts().CurrentModule;

View File

@ -55,8 +55,50 @@
#endif
#ifdef VERIFY
// expected-error@+2 {{builtin feature check macro requires a parenthesized identifier}}
// expected-error@+1 {{expected value in expression}}
// expected-error@+1 {{builtin feature check macro requires a parenthesized identifier}}
#if __has_feature('x')
#endif
// The following are not identifiers:
_Static_assert(!__is_identifier("string"), "oops");
_Static_assert(!__is_identifier('c'), "oops");
_Static_assert(!__is_identifier(123), "oops");
_Static_assert(!__is_identifier(int), "oops");
// The following are:
_Static_assert(__is_identifier(abc /* comment */), "oops");
_Static_assert(__is_identifier /* comment */ (xyz), "oops");
// expected-error@+1 {{too few arguments}}
#if __is_identifier()
#endif
// expected-error@+1 {{too many arguments}}
#if __is_identifier(,())
#endif
// expected-error@+1 {{missing ')' after 'abc'}}
#if __is_identifier(abc xyz) // expected-note {{to match this '('}}
#endif
// expected-error@+1 {{missing ')' after 'abc'}}
#if __is_identifier(abc()) // expected-note {{to match this '('}}
#endif
// expected-error@+1 {{missing ')' after '.'}}
#if __is_identifier(.abc) // expected-note {{to match this '('}}
#endif
// expected-error@+1 {{nested parentheses not permitted in '__is_identifier'}}
#if __is_identifier((abc))
#endif
// expected-error@+1 {{missing '(' after '__is_identifier'}} expected-error@+1 {{expected value}}
#if __is_identifier
#endif
// expected-error@+1 {{unterminated}} expected-error@+1 {{expected value}}
#if __is_identifier(
#endif
#endif

View File

@ -1,5 +1,5 @@
// RUN: %clang_cc1 -verify %s
// These must be the last lines in this test.
// expected-error@+1{{expected string literal}} expected-error@+1 2{{expected}}
// expected-error@+1{{unterminated}} expected-error@+1 2{{expected}}
int i = __has_warning(

View File

@ -1,5 +1,5 @@
// RUN: %clang_cc1 -verify %s
// These must be the last lines in this test.
// expected-error@+1{{expected string literal}} expected-error@+1{{expected}}
// expected-error@+1{{too few arguments}}
int i = __has_warning();

View File

@ -12,7 +12,7 @@
#endif
// expected-error@+2 {{expected string literal in '__has_warning'}}
// expected-error@+1 {{expected value in expression}}
// expected-error@+1 {{missing ')'}} expected-note@+1 {{match}}
#if __has_warning(-Wfoo)
#endif
@ -22,8 +22,7 @@
#warning Not a valid warning flag
#endif
// expected-error@+2 {{builtin warning check macro requires a parenthesized string}}
// expected-error@+1 {{invalid token}}
// expected-error@+1 {{missing '(' after '__has_warning'}}
#if __has_warning "not valid"
#endif
@ -33,7 +32,7 @@
#define MY_ALIAS "-Wparentheses"
// expected-error@+1 2{{expected}}
// expected-error@+1 {{expected}}
#if __has_warning(MY_ALIAS)
#error Alias expansion not allowed
#endif