Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 94 additions & 45 deletions simplecpp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -733,6 +733,41 @@ static std::string stringifyToken(const std::string &str)
return ret;
}

/**
* The result of the # operator: the tokens as a string literal (C 6.10.3.2).
* The tokens are separated by a space where there was whitespace between them.
* @param tokens the argument of #, not macro expanded
* @param loc location for the warning
* @param macroName the macro that is expanded, for the warning
* @param outputList if not null, a warning is added when a final backslash is dropped
* @return the string literal, including the quotes
*/
static std::string stringify(const simplecpp::TokenList &tokens, const simplecpp::Location &loc, const std::string &macroName, simplecpp::OutputList *outputList)
{
std::string str;
for (const simplecpp::Token *tok = tokens.cfront(), *next; tok; tok = next) {
next = tok->next;
str += stringifyToken(tok->str());
if (next && tok->whitespaceahead)
str += ' ';
}
// An odd number of trailing backslashes would escape the closing quote and
// yield an unterminated string literal (undefined behavior, C 6.10.3.2p2).
// Drop the final backslash, as gcc and clang do.
std::size_t trailing = 0;
while (trailing < str.size() && str[str.size() - 1U - trailing] == '\\')
++trailing;
if (trailing % 2U == 1U) {
str.pop_back();
if (outputList) {
outputList->emplace_back(simplecpp::Output::PORTABILITY_STRINGIFY, loc,
"Invalid string literal when expanding \'" + macroName + "\', ignoring final \'\\\'. "
"Undefined behavior according to C standard chapter 6.10.3.2, paragraph 2.");
}
}
return '\"' + str + '\"';
}

void simplecpp::TokenList::lineDirective(unsigned int fileIndex_, unsigned int line, Location &location)
{
if (fileIndex_ != location.fileIndex || line >= location.line) {
Expand Down Expand Up @@ -2634,28 +2669,7 @@ namespace simplecpp {
TokenList tokenListHash(files);
const MacroMap macros2; // temporarily bypass macro expansion
tok = expandToken(tokenListHash, loc, tok->next, macros2, expandedmacros, parametertokens, outputList);
std::string str;
for (const Token *hashtok = tokenListHash.cfront(), *next; hashtok; hashtok = next) {
next = hashtok->next;
str += stringifyToken(hashtok->str());
if (next && hashtok->whitespaceahead)
str += ' ';
}
// An odd number of trailing backslashes would escape the closing quote and
// yield an unterminated string literal (undefined behavior, C 6.10.3.2p2).
// Drop the final backslash, as gcc and clang do.
std::size_t trailing = 0;
while (trailing < str.size() && str[str.size() - 1U - trailing] == '\\')
++trailing;
if (trailing % 2U == 1U) {
str.pop_back();
if (outputList) {
outputList->emplace_back(Output::PORTABILITY_STRINGIFY, loc,
"Invalid string literal when expanding \'" + name() + "\', ignoring final \'\\\'. "
"Undefined behavior according to C standard chapter 6.10.3.2, paragraph 2.");
}
}
output.push_back(newMacroToken('\"' + str + '\"', loc, isReplaced(expandedmacros)));
output.push_back(newMacroToken(stringify(tokenListHash, loc, name(), outputList), loc, isReplaced(expandedmacros)));
return tok;
}

Expand All @@ -2679,20 +2693,9 @@ namespace simplecpp {
throw invalidHashHash::unexpectedNewline(tok->location, name());

const bool canBeConcatenatedStringOrChar = isStringLiteral(A->str()) || isCharLiteral(A->str());
// A is a punctuator; the result is checked when B has been expanded
const bool punctuatorA = (!A->name && !A->number && !A->str().empty() && !canBeConcatenatedStringOrChar);

const Token * const B = tok->next->next;
if (!punctuatorA) {
if (!B->name && !B->number && B->op && !B->isOneOf("#="))
throw invalidHashHash::unexpectedToken(tok->location, name(), B);
if (B->op == '=')
throw invalidHashHash::cannotCombine(tok->location, name(), A, B);
}

// Superficial check; more in-depth would in theory be possible _after_ expandArg
if (canBeConcatenatedStringOrChar && (B->number || !B->name))
throw invalidHashHash::cannotCombine(tok->location, name(), A, B);
checkHashHashOperands(tok->location, name(), A, B);

TokenList tokensB(files);
const Token *nextTok = B->next;
Expand Down Expand Up @@ -2721,24 +2724,16 @@ namespace simplecpp {
strAB = ",";
}
else {
if (punctuatorA && !isValidPunctuatorPaste(A->str() + tokensB.cfront()->str()))
throw invalidHashHash::cannotCombine(tok->location, name(), A, tokensB.cfront());
checkPunctuatorPaste(tok->location, name(), A, tokensB.cfront());
strAB = A->str() + tokensB.cfront()->str();
tokensB.deleteToken(tokensB.front());
}
} else {
if (punctuatorA && !isValidPunctuatorPaste(A->str() + B->str()))
throw invalidHashHash::cannotCombine(tok->location, name(), A, B);
checkPunctuatorPaste(tok->location, name(), A, B);
strAB = A->str() + B->str();
}

// producing universal character is undefined behavior
if (A->previous && A->previous->str() == "\\") {
if (strAB[0] == 'u' && strAB.size() == 5)
throw invalidHashHash::universalCharacterUB(tok->location, name(), A, strAB);
if (strAB[0] == 'U' && strAB.size() == 9)
throw invalidHashHash::universalCharacterUB(tok->location, name(), A, strAB);
}
checkUniversalCharacterPaste(tok->location, name(), A, strAB);

if (varargs && tokensB.empty() && tok->previous->str() == ",") {
output.deleteToken(A);
Expand Down Expand Up @@ -2780,6 +2775,60 @@ namespace simplecpp {
return nextTok;
}

/** A is a punctuator, not a name, number or literal. Pasting it is checked after B is expanded. */
static bool isPunctuatorOperand(const Token *A) {
return !A->name && !A->number && !A->str().empty() && !isStringLiteral(A->str()) && !isCharLiteral(A->str());
}

/**
* Check the operands of A ## B before B is expanded.
* @param loc the location of the ##
* @param macroName the macro that is expanded
* @param A the token before ##, already expanded
* @param B the token after ## in the macro definition
* @throws invalidHashHash if A and B can not be pasted
*/
static void checkHashHashOperands(const Location &loc, const std::string &macroName, const Token *A, const Token *B) {
if (!isPunctuatorOperand(A)) {
if (!B->name && !B->number && B->op && !B->isOneOf("#="))
throw invalidHashHash::unexpectedToken(loc, macroName, B);
if (B->op == '=')
throw invalidHashHash::cannotCombine(loc, macroName, A, B);
}

// Superficial check; more in-depth would in theory be possible _after_ expandArg
if ((isStringLiteral(A->str()) || isCharLiteral(A->str())) && (B->number || !B->name))
throw invalidHashHash::cannotCombine(loc, macroName, A, B);
}

/**
* When A is a punctuator, check that pasting A and B gives a valid token.
* @param loc the location of the ##
* @param macroName the macro that is expanded
* @param A the token before ##
* @param B the first token after ##, after expansion
* @throws invalidHashHash if the result is not a valid token
*/
static void checkPunctuatorPaste(const Location &loc, const std::string &macroName, const Token *A, const Token *B) {
if (isPunctuatorOperand(A) && !isValidPunctuatorPaste(A->str() + B->str()))
throw invalidHashHash::cannotCombine(loc, macroName, A, B);
}

/**
* Producing a universal character name with ## is undefined behavior.
* @param loc the location of the ##
* @param macroName the macro that is expanded
* @param A the token before ##
* @param strAB the pasted text
* @throws invalidHashHash if A follows a backslash and strAB is a universal character name
*/
static void checkUniversalCharacterPaste(const Location &loc, const std::string &macroName, const Token *A, const std::string &strAB) {
if (!A->previous || A->previous->str() != "\\")
return;
if ((strAB[0] == 'u' && strAB.size() == 5) || (strAB[0] == 'U' && strAB.size() == 9))
throw invalidHashHash::universalCharacterUB(loc, macroName, A, strAB);
}

static bool isReplaced(const std::set<std::string> &expandedmacros) {
// return true if size > 1
auto it = expandedmacros.cbegin();
Expand Down
Loading