From 321020d2248e8ea0c42369b8a2e02de98c150b83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Sat, 7 Nov 2026 12:19:56 +0100 Subject: [PATCH] Fix #727 (#if: two unary operators in a row silently evaluate to false) Unary operators were folded from left to right in one pass, so in "!-0" the ! was skipped because its operand was not folded yet. Fold them from right to left. Negating a negative value no longer gives an invalid number such as "--1". Co-Authored-By: Claude Opus 5.5 --- simplecpp.cpp | 16 +++++++++++++--- test.cpp | 30 ++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index 60f879cc..c9d762fa 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -1291,9 +1291,16 @@ void simplecpp::TokenList::combineOperators() static const std::string COMPL("compl"); static const std::string NOT("not"); -void simplecpp::TokenList::constFoldUnaryNotPosNeg(simplecpp::Token *tok) +void simplecpp::TokenList::constFoldUnaryNotPosNeg(simplecpp::Token *tok1) { - for (; tok && tok->op != ')'; tok = tok->next) { + if (!tok1 || tok1->op == ')') + return; + Token *last = tok1; + while (last->next && last->next->op != ')') + last = last->next; + + // unary operators are right-associative, "!-0" means "!(-0)", so they are folded from right to left + for (Token *tok = last; tok; tok = (tok == tok1) ? nullptr : tok->previous) { // "not" might be ! if (isAlternativeUnaryOp(tok, NOT)) tok->op = '!'; @@ -1328,7 +1335,10 @@ void simplecpp::TokenList::constFoldUnaryNotPosNeg(simplecpp::Token *tok) deleteToken(tok->next); break; case '-': - tok->setstr(tok->op + tok->next->str()); + if (tok->next->str()[0] == '-') + tok->setstr(tok->next->str().substr(1)); + else + tok->setstr(tok->op + tok->next->str()); deleteToken(tok->next); break; } diff --git a/test.cpp b/test.cpp index 1296c790..00a4a136 100644 --- a/test.cpp +++ b/test.cpp @@ -2913,6 +2913,35 @@ static void ifZeroCondition() ASSERT_EQUALS("file0,1,syntax_error,failed to evaluate #if condition, division/modulo by zero\n", toString(outputList)); } +static void ifUnaryOperators() +{ + // unary operators in a row + const char * const exprs[] = { + "!!1", + "!-0", + "!+0", + "!~-1", + "- -1", + "-!0", + "-~0", + "~!0", + "+-1 == -1", + "1 && !-0", + "!-X", + "-(-1) == 1", + "- - -1 == -1", + "1 - -1 == 2" + }; + for (const char * const expr : exprs) { + const std::string code = std::string("#if ") + expr + "\n" + "1\n" + "#else\n" + "2\n" + "#endif\n"; + ASSERT_EQUALS("\n1", preprocess(code.c_str())); + } +} + static void ifTrueFalse() { const char code[] = "#if true && !false\n" @@ -5150,6 +5179,7 @@ static void runTests(int argc, char **argv, Input input) TEST_CASE(ifdiv0); TEST_CASE(ifShortCircuit); TEST_CASE(ifZeroCondition); + TEST_CASE(ifUnaryOperators); TEST_CASE(ifTrueFalse); TEST_CASE(ifalt); // using "and", "or", etc TEST_CASE(ifexpr);