diff --git a/simplecpp.cpp b/simplecpp.cpp index 2f26ddcd..91af52ea 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -179,14 +179,31 @@ static bool isValidPunctuatorPaste(const std::string &s) return isPunctuator(s) || (s.size() >= 2 && s[0] == '.' && std::isdigit(static_cast(s[1]))); } +/* + * When an operation can't be evaluated (division by zero), its result is an + * error value. The error is only reported if the error value is used, so an + * operand that is not evaluated, as in "0 && (1/0)", does not cause an error. + */ +static const std::string ERROR_VALUE("#error:"); + +static bool isErrorValue(const simplecpp::Token *tok) +{ + return tok && tok->str().compare(0, ERROR_VALUE.size(), ERROR_VALUE) == 0; +} + +static bool isValue(const simplecpp::Token *tok) +{ + return tok && (tok->number || isErrorValue(tok)); +} + static bool isAlternativeBinaryOp(const simplecpp::Token *tok, const std::string &alt) { return (tok->name && tok->str() == alt && tok->previous && tok->next && - (tok->previous->number || tok->previous->name || tok->previous->op == ')') && - (tok->next->number || tok->next->name || tok->next->op == '(')); + (isValue(tok->previous) || tok->previous->name || tok->previous->op == ')') && + (isValue(tok->next) || tok->next->name || tok->next->op == '(')); } static bool isAlternativeUnaryOp(const simplecpp::Token *tok, const std::string &alt) @@ -1072,6 +1089,23 @@ void simplecpp::TokenList::readfile(Stream &stream, const std::string &filename, combineOperators(); } +/** + * If one operand of the binary operator tok is an error value then + * "lhs op rhs" is replaced by that error value. + * @return the error value token, or nullptr + */ +static simplecpp::Token *foldErrorValue(simplecpp::TokenList &tokens, simplecpp::Token *tok) +{ + simplecpp::Token * const lhs = tok->previous; + if (!isErrorValue(lhs) && !isErrorValue(tok->next)) + return nullptr; + if (!isErrorValue(lhs)) + lhs->setstr(tok->next->str()); + tokens.deleteToken(tok->next); + tokens.deleteToken(tok); + return lhs; +} + void simplecpp::TokenList::constFold() { while (cfront()) { @@ -1105,6 +1139,11 @@ void simplecpp::TokenList::constFold() deleteToken(tok->previous); deleteToken(tok->next); } + + for (const Token *tok = cfront(); tok; tok = tok->next) { + if (isErrorValue(tok)) + throw std::overflow_error(tok->str().substr(ERROR_VALUE.size())); + } } static bool isFloatSuffix(const simplecpp::Token *tok) @@ -1262,17 +1301,27 @@ void simplecpp::TokenList::constFoldUnaryNotPosNeg(simplecpp::Token *tok) else if (isAlternativeUnaryOp(tok, COMPL)) tok->op = '~'; - if (tok->op == '!' && tok->next && tok->next->number) { + if (tok->isOneOf("!~") && isErrorValue(tok->next)) { + tok->setstr(tok->next->str()); + deleteToken(tok->next); + } else if (tok->op == '!' && tok->next && tok->next->number) { tok->setstr(tok->next->str() == "0" ? "1" : "0"); deleteToken(tok->next); } else if (tok->op == '~' && tok->next && tok->next->number) { tok->setstr(toString(~stringToLL(tok->next->str()))); deleteToken(tok->next); } else { - if (tok->previous && (tok->previous->number || tok->previous->name)) + if (tok->previous && (isValue(tok->previous) || tok->previous->name)) continue; - if (!tok->next || !tok->next->number) + if (!isValue(tok->next)) continue; + if (isErrorValue(tok->next)) { + if (tok->isOneOf("+-")) { + tok->setstr(tok->next->str()); + deleteToken(tok->next); + } + continue; + } switch (tok->op) { case '+': tok->setstr(tok->next->str()); @@ -1290,32 +1339,33 @@ void simplecpp::TokenList::constFoldUnaryNotPosNeg(simplecpp::Token *tok) void simplecpp::TokenList::constFoldMulDivRem(Token *tok) { for (; tok && tok->op != ')'; tok = tok->next) { - if (!tok->previous || !tok->previous->number) + if (!isValue(tok->previous) || !isValue(tok->next)) + continue; + if (!tok->isOneOf("*/%")) continue; - if (!tok->next || !tok->next->number) + if (Token * const errorValue = foldErrorValue(*this, tok)) { + tok = errorValue; continue; + } - long long result; + std::string result; if (tok->op == '*') { - result = (stringToLL(tok->previous->str()) * stringToLL(tok->next->str())); - } - else if (tok->op == '/' || tok->op == '%') { + result = toString(stringToLL(tok->previous->str()) * stringToLL(tok->next->str())); + } else { const long long rhs = stringToLL(tok->next->str()); - if (rhs == 0) - throw std::overflow_error("division/modulo by zero"); const long long lhs = stringToLL(tok->previous->str()); - if (rhs == -1 && lhs == std::numeric_limits::min()) - throw std::overflow_error("division overflow"); - if (tok->op == '/') - result = (lhs / rhs); + if (rhs == 0) + result = ERROR_VALUE + "division/modulo by zero"; + else if (rhs == -1 && lhs == std::numeric_limits::min()) + result = ERROR_VALUE + "division overflow"; + else if (tok->op == '/') + result = toString(lhs / rhs); else - result = (lhs % rhs); - } else { - continue; + result = toString(lhs % rhs); } tok = tok->previous; - tok->setstr(toString(result)); + tok->setstr(result); deleteToken(tok->next); deleteToken(tok->next); } @@ -1324,10 +1374,14 @@ void simplecpp::TokenList::constFoldMulDivRem(Token *tok) void simplecpp::TokenList::constFoldAddSub(Token *tok) { for (; tok && tok->op != ')'; tok = tok->next) { - if (!tok->previous || !tok->previous->number) + if (!isValue(tok->previous) || !isValue(tok->next)) + continue; + if (!tok->isOneOf("+-")) continue; - if (!tok->next || !tok->next->number) + if (Token * const errorValue = foldErrorValue(*this, tok)) { + tok = errorValue; continue; + } long long result; if (tok->op == '+') @@ -1347,10 +1401,14 @@ void simplecpp::TokenList::constFoldAddSub(Token *tok) void simplecpp::TokenList::constFoldShift(Token *tok) { for (; tok && tok->op != ')'; tok = tok->next) { - if (!tok->previous || !tok->previous->number) + if (!isValue(tok->previous) || !isValue(tok->next)) + continue; + if (tok->str() != "<<" && tok->str() != ">>") continue; - if (!tok->next || !tok->next->number) + if (Token * const errorValue = foldErrorValue(*this, tok)) { + tok = errorValue; continue; + } long long result; if (tok->str() == "<<") @@ -1374,12 +1432,14 @@ void simplecpp::TokenList::constFoldComparison(Token *tok) if (isAlternativeBinaryOp(tok,NOTEQ)) tok->setstr("!="); - if (!tok->startsWithOneOf("<>=!")) + if (!tok->isOneOf("<>") && tok->str() != "<=" && tok->str() != ">=" && tok->str() != "==" && tok->str() != "!=") continue; - if (!tok->previous || !tok->previous->number) + if (!isValue(tok->previous) || !isValue(tok->next)) continue; - if (!tok->next || !tok->next->number) + if (Token * const errorValue = foldErrorValue(*this, tok)) { + tok = errorValue; continue; + } int result; if (tok->str() == "==") @@ -1419,10 +1479,12 @@ void simplecpp::TokenList::constFoldBitwise(Token *tok) for (tok = tok1; tok && tok->op != ')'; tok = tok->next) { if (tok->op != *op && !isAlternativeBinaryOp(tok, *alternativeOp)) continue; - if (!tok->previous || !tok->previous->number) + if (!isValue(tok->previous) || !isValue(tok->next)) continue; - if (!tok->next || !tok->next->number) + if (Token * const errorValue = foldErrorValue(*this, tok)) { + tok = errorValue; continue; + } long long result; if (*op == '&') result = (stringToLL(tok->previous->str()) & stringToLL(tok->next->str())); @@ -1455,19 +1517,24 @@ void simplecpp::TokenList::constFoldLogicalOp(Token *tok) for (Token *t = tok; t && t->op != ')'; t = t->next) { if (t->str() != op) continue; - if (!t->previous || !t->previous->number) - continue; - if (!t->next || !t->next->number) + if (!isValue(t->previous) || !isValue(t->next)) continue; - int result; - if (t->str() == "||") - result = (stringToLL(t->previous->str()) || stringToLL(t->next->str())); - else /*if (t->str() == "&&")*/ - result = (stringToLL(t->previous->str()) && stringToLL(t->next->str())); + // the right operand is not evaluated if the left operand decides the result + std::string result; + if (isErrorValue(t->previous)) + result = t->previous->str(); + else if (t->str() == "||" && stringToLL(t->previous->str()) != 0) + result = "1"; + else if (t->str() == "&&" && stringToLL(t->previous->str()) == 0) + result = "0"; + else if (isErrorValue(t->next)) + result = t->next->str(); + else + result = (stringToLL(t->next->str()) != 0) ? "1" : "0"; t = t->previous; - t->setstr(toString(result)); + t->setstr(result); deleteToken(t->next); deleteToken(t->next); } @@ -1476,30 +1543,42 @@ void simplecpp::TokenList::constFoldLogicalOp(Token *tok) void simplecpp::TokenList::constFoldQuestionOp(Token *&tok1) { - bool gotoTok1 = false; - // NOLINTNEXTLINE(misc-const-correctness) - technically correct but used to access non-const data - for (Token *tok = tok1; tok && tok->op != ')'; tok = gotoTok1 ? tok1 : tok->next) { - gotoTok1 = false; - if (tok->str() != "?") - continue; + // ?: is right-associative, "a ? b : c ? d : e" means "a ? b : (c ? d : e)", + // so the last ? is folded first + std::vector questionTokens; + for (const Token *t = tok1; t && t->op != ')'; t = t->next) { + if (t->str() == "?") + questionTokens.push_back(t); + } + while (!questionTokens.empty()) { + const Token * const tok = questionTokens.back(); + questionTokens.pop_back(); if (!tok->previous || !tok->next || !tok->next->next) throw std::runtime_error("invalid expression"); - if (!tok->previous->number) - continue; - if (tok->next->next->op != ':') - continue; Token * const condTok = tok->previous; Token * const trueTok = tok->next; Token * const falseTok = trueTok->next->next; if (!falseTok) throw std::runtime_error("invalid expression"); - if (condTok == tok1) - tok1 = (condTok->str() != "0" ? trueTok : falseTok); - deleteToken(condTok->next); // ? - deleteToken(trueTok->next); // : - deleteToken(condTok->str() == "0" ? trueTok : falseTok); - deleteToken(condTok); - gotoTok1 = true; + if (trueTok->next->op != ':') + return; + if (!isValue(condTok) || !isValue(trueTok) || !isValue(falseTok)) + return; + if (isErrorValue(condTok)) { + // "error ? x : y" => error + deleteToken(condTok->next); // ? + deleteToken(trueTok->next); // : + deleteToken(trueTok); + deleteToken(falseTok); + } else { + Token * const result = (condTok->str() != "0" ? trueTok : falseTok); + if (condTok == tok1) + tok1 = result; + deleteToken(condTok->next); // ? + deleteToken(trueTok->next); // : + deleteToken(result == falseTok ? trueTok : falseTok); // the operand that is not evaluated + deleteToken(condTok); + } } } diff --git a/test.cpp b/test.cpp index fdfc9ead..bd0da227 100644 --- a/test.cpp +++ b/test.cpp @@ -620,6 +620,22 @@ static void constFold() ASSERT_EQUALS("1", testConstFold("010==8")); ASSERT_EQUALS("exception", testConstFold("!1 ? 2 :")); ASSERT_EQUALS("exception", testConstFold("?2:3")); + ASSERT_EQUALS("0", testConstFold("0 && 1/0")); + ASSERT_EQUALS("1", testConstFold("1 || 1/0")); + ASSERT_EQUALS("2", testConstFold("1 ? 2 : 1/0")); + ASSERT_EQUALS("exception", testConstFold("1 && 1/0")); + ASSERT_EQUALS("exception", testConstFold("1/0 && 0")); + ASSERT_EQUALS("exception", testConstFold("(1/0) ? 1 : 2")); + ASSERT_EQUALS("exception", testConstFold("-(1/0) + 1")); + // ?: is right-associative + ASSERT_EQUALS("1", testConstFold("1 ? 1 : 0 ? 2 : 3")); + ASSERT_EQUALS("3", testConstFold("0 ? 1 : 0 ? 2 : 3")); + ASSERT_EQUALS("2", testConstFold("1 ? 2 : 3 ? 4 : 5 ? 6 : 7")); + ASSERT_EQUALS("7", testConstFold("0 ? 2 : 0 ? 4 : 0 ? 6 : 7")); + ASSERT_EQUALS("6", testConstFold("1 ? 0 ? 5 : 6 : 7")); + ASSERT_EQUALS("2", testConstFold("(1 ? 1 : 0) ? 2 : 3")); + ASSERT_EQUALS("1", testConstFold("1 ? 1 : (1/0) ? 2 : 3")); + ASSERT_EQUALS("exception", testConstFold("0 ? 1 : (1/0) ? 2 : 3")); } #ifdef __CYGWIN__ @@ -2835,6 +2851,42 @@ static void ifdiv0() ASSERT_EQUALS("", preprocess(code)); } +static void ifShortCircuit() +{ + // operands that are not evaluated must not cause errors + const char * const exprs[] = { + "0 && (1/0)", + "!(1 || (1/0))", + "0 ? 1/0 : 0", + "1 ? 0 : 1/0", + "0 && 1 % 0 && 1", + "(1 || 1/0) && !(0 && 1/0) && 0", + "0 && (2 + (3 * (1/0)))", + "0 && -(1/0)", + "0 and (1/0)", + "!(1 or 1/0)", + "defined(X) and X/0", + "0 && 1 bitor (1/0)", + "!(1 || (1/0) bitand 1)", + "0 and 1 xor (1/0)" + }; + for (const char * const expr : exprs) { + const std::string code = std::string("#if ") + expr + "\n" + "1\n" + "#else\n" + "2\n" + "#endif\n"; + simplecpp::OutputList outputList; + ASSERT_EQUALS("\n\n\n2", preprocess(code.c_str(), &outputList)); + ASSERT_EQUALS("", toString(outputList)); + } + + // evaluated operands are still checked + simplecpp::OutputList outputList; + ASSERT_EQUALS("", preprocess("#if 1 && (1/0)\n#endif\n", &outputList)); + ASSERT_EQUALS("file0,1,syntax_error,failed to evaluate #if condition, division/modulo by zero\n", toString(outputList)); +} + static void ifTrueFalse() { const char code[] = "#if true && !false\n" @@ -5070,6 +5122,7 @@ static void runTests(int argc, char **argv, Input input) TEST_CASE(ifif); TEST_CASE(ifoverflow); TEST_CASE(ifdiv0); + TEST_CASE(ifShortCircuit); TEST_CASE(ifTrueFalse); TEST_CASE(ifalt); // using "and", "or", etc TEST_CASE(ifexpr);