Skip to content

Fix #716 (#if: && and || are evaluated with the same precedence) - #717

Merged
danmar merged 2 commits into
masterfrom
fix-logical-precedence
Nov 6, 2026
Merged

danmar merged 2 commits into
masterfrom
fix-logical-precedence

Conversation

@danmar

@danmar danmar commented Nov 6, 2026

Copy link
Copy Markdown
Collaborator

'#if 1 || 1 && 0' was evaluated as '(1 || 1) && 0'. Fold && before ||.

'#if 1 || 1 && 0' was evaluated as '(1 || 1) && 0'. Fold && before ||.
@danmar
danmar force-pushed the fix-logical-precedence branch from 69897c5 to 9ff6576 Compare November 6, 2026 12:19

@dmcppcheck dmcppcheck left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI review

I found no new problems in this PR. && is now folded before ||, and when I compared #if expressions built only from numbers, parentheses, !, && and || against gcc, the results matched in every case. Among the expressions I fuzzed, there were no cases where base agreed with gcc and the PR did not.

One related bug is still there, but it was already in base and the PR does not touch it. simplifyName (simplecpp.cpp:2877-2890) replaces and/or with 0 when the next token is !, -, ~ or not, because isAlternativeBinaryOp only accepts a number, a name or ( after the operator. So #if 1 or 0 and !0 gives no output, while gcc keeps the line, and the alternative-token form of this PR's precedence case is still wrong there. It could be a follow-up.

The other differences from gcc that the fuzzing found also happen on base: chained </==, !!x | ..., and some nested ?: expressions.

The extra pass over the expression costs very little.

Testing

Build and test suites: make -j8 testrunner simplecpp CXXOPTS=-Werror built in pr/ without warnings. Results:

  • ./testrunner: 0 failures.
  • python3 run-tests.py: 176 tests, 0 failed.
  • python3 -m pytest integration_test.py: 128 passed.
  • ./selfcheck.sh: exit code 0.

I also built base/ to compare against.

Differential fuzzing: I generated random #if expressions (values 0/1/2, parentheses, !, &&, ||, ?:, and in some runs also == | + <), wrote one expression per file, and compared gcc -E -P with the pr and base simplecpp binaries:

  • Only &&, ||, ! and parentheses, 300 expressions: the PR had 0 mismatches, base had 22.
  • Adding ?:, 400 expressions: the PR had 13 mismatches, base had 28.
  • With == and | as well, 400 expressions: the PR had 9 mismatches, base had 12.
  • With == | + < as well, 300 expressions: the PR had 6 mismatches, base had 15.

In none of these runs was there a regression (a case where base matched gcc and the PR did not). The remaining mismatches also happen on base.

Bug that was already in base:

printf '#if 1 or 0 and !0\nY\n#endif\n' > a.cpp
/work/pr/simplecpp a.cpp     # prints nothing (also on base)
gcc -E -P a.cpp              # prints Y

The same happens with 0 and !0 or 1 and 0 and not 0 or 1. 0 and (!0) or 1 and 1 || 0 && !0 work. The cause is that simplifyName replaces and with 0 when ! follows it.


Automated review of 9ff6576 by Claude. The pull request was built and tested locally.

@danmar
danmar merged commit 9fedb7d into master Nov 6, 2026
47 checks passed
@danmar
danmar deleted the fix-logical-precedence branch November 6, 2026 12:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants