Skip to content

refactor: Enable -Wswitch in exhaustive switch'es, Enable -Wcovered-switch-default#34684

Open
maflcko wants to merge 2 commits intobitcoin:masterfrom
maflcko:2602-w-switch
Open

refactor: Enable -Wswitch in exhaustive switch'es, Enable -Wcovered-switch-default#34684
maflcko wants to merge 2 commits intobitcoin:masterfrom
maflcko:2602-w-switch

Conversation

@maflcko
Copy link
Member

@maflcko maflcko commented Feb 26, 2026

The compiler flag -Wswitch is enabled. However, it can not fire when a default: case exists. Fix that by removing the default case where a switch is already handling all cases exhaustively.

Also, enable -Wcovered-switch-default to catch those cases at compile time in the future.

Also, apply the comment according to the dev notes.

Can be reviewed via --ignore-all-space

@DrahtBot
Copy link
Contributor

DrahtBot commented Feb 26, 2026

The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

Reviews

See the guideline for information on the review process.

Type Reviewers
ACK stickies-v
Concept NACK purpleKarrot
Stale ACK willcl-ark, l0rinc

If your review is incorrectly listed, please copy-paste <!--meta-tag:bot-skip--> into the comment that the bot should ignore.

Conflicts

Reviewers, this pull request conflicts with the following ones:

  • #34075 (fees: Introduce Mempool Based Fee Estimation to reduce overestimation by ismaelsadeeq)
  • #31507 (build: Use clang-cl to build on Windows natively by hebasto)
  • #28802 (ArgsManager: support command-specific options by ajtowns)

If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

Copy link
Contributor

@l0rinc l0rinc left a comment

Choose a reason for hiding this comment

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

ACK 6666ff4

I had a similar attempt last week, had a few more cases covered (pun intended), but haven't finished, so it's likely not all correct - see if there's anything you can include here: https://github.com/l0rinc/bitcoin/pull/119/changes

@maflcko
Copy link
Member Author

maflcko commented Feb 27, 2026

I had a similar attempt last week, had a few more cases covered (pun intended), but haven't finished, so it's likely not all correct - see if there's anything you can include here: https://github.com/l0rinc/bitcoin/pull/119/changes

thx, taken. Except for the cases where the switch was not exhaustive. I think it is fine to have default cases that serve as a fallback, when the fallback is harmless. Also, I haven't reviewed nor taken the GUI changes.

@l0rinc
Copy link
Contributor

l0rinc commented Feb 27, 2026

ACK fa6aa9e

@willcl-ark
Copy link
Member

Excuse my almost-certainly-smooth-brained question, but, the PR title says "enable -Wswitch", though that flag is not in the changeset.

Am I correct in inferring that, -Wall enables this by proxy, and then by removing the default case, this check is acually used/useful?

@maflcko maflcko changed the title refactor: Enable -Wswitch in exhaustive switch refactor: Enable -Wswitch in exhaustive switch'es Feb 27, 2026
@maflcko
Copy link
Member Author

maflcko commented Feb 27, 2026

Sorry, I forgot to write the motivation in the description. Fixed now

Copy link
Member

@willcl-ark willcl-ark left a comment

Choose a reason for hiding this comment

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

ACK fa6aa9e

This looks correct to me. I found other switch cases with default remaining, but none that qualify for a refactor-only PR, as they are non-exhaustive, non-enum, or othery types.

This appears to fix all exhaustive switches where removal of the default case is only a refactor.

The addition of the CHECK_NONFATAL in wallet/rpc/spend.cpp is needed to avoid -Wreturn-type error on gcc, as per https://gcc.gnu.org/wiki/VerboseDiagnostics#enum_switch (reading that, I wonder why clang doesn't warn here too).

@maflcko maflcko changed the title refactor: Enable -Wswitch in exhaustive switch'es refactor: Enable -Wswitch in exhaustive switch'es, Enable -Wcovered-switch-default Feb 28, 2026
@maflcko maflcko added this to the 32.0 milestone Feb 28, 2026
@maflcko maflcko removed this from the 32.0 milestone Feb 28, 2026
@DrahtBot
Copy link
Contributor

🚧 At least one of the CI tasks failed.
Task Windows native, VS: https://github.com/bitcoin/bitcoin/actions/runs/22524615324/job/65254621781
LLM reason (✨ experimental): test_bitcoin-qt failed due to an assertion failure in sendcoinsdialog.cpp (UI test assertion).

Hints

Try to run the tests locally, according to the documentation. However, a CI failure may still
happen due to a number of reasons, for example:

  • Possibly due to a silent merge conflict (the changes in this pull request being
    incompatible with the current code in the target branch). If so, make sure to rebase on the latest
    commit of the target branch.

  • A sanitizer issue, which can only be found by compiling with the sanitizer and running the
    affected test.

  • An intermittent issue.

Leave a comment here, if you need help tracking down a confusing failure.

Copy link
Member

@willcl-ark willcl-ark left a comment

Choose a reason for hiding this comment

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

reACK fa84cd0

In addition to the second commit, I also checked this time that my version of gcc includes -Wswitch in -Wall, as I didn't do that previously:

[nix-shell:~/src/core/worktrees/pr-34684]$ gcc --version
gcc (GCC) 15.2.0

[nix-shell:~/src/core/worktrees/pr-34684]$ gcc -Wall -Q --help=warnings | grep enabled | grep switch
  -Wswitch                              [enabled]
  -Wswitch-bool                         [enabled]
  -Wswitch-outside-range                [enabled]
  -Wswitch-unreachable                  [enabled]

The new second commit provides warnings if anyone tries to add a redundant default case to a covered switch, preventing regressions the other way. Notably this is a clang-only warning, not supported by gcc:

[nix-shell:~/src/core/worktrees/pr-34684]$ gcc -Wcovered-switch-default -x c -c /dev/null -o /dev/null
gcc: error: unrecognized command-line option ‘-Wcovered-switch-default’; did you mean ‘-Wno-switch-default’?

...but try_append_cxx_flags handles this correctly.

Wrapping RPCHelpMan in a lambda and returning makes more sense ergonomically.

@DrahtBot DrahtBot requested a review from l0rinc March 3, 2026 10:59
Copy link
Contributor

@l0rinc l0rinc left a comment

Choose a reason for hiding this comment

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

I'm mostly fine with the change, I think we should adjust the remaining texts and would prefer using standard loggers instead of std::cout and have a more descriptive NONFATAL_UNREACHABLE instead of the hacky assert(false);

@DrahtBot DrahtBot requested a review from l0rinc March 3, 2026 14:06
@purpleKarrot
Copy link
Contributor

NACK

I consider "no default case, so the compiler can warn about missing cases" a compiler bug. Why can't the compiler warn about missing cases when there is a default?

In my view, there should be two guidelines:

  1. All switch statements should have a default.
  2. Switch statements over enums (unless they are marked as flag_enum) should cover all cases.

With GCC, those two guidelines can be enforced with -Wswitch-default and -Wswitch-enum.
Clang does not support those and instead provides a -Wcovered-switch-default, which I consider harmful. It shows a lack of understanding how enums work in C and C++.

In this code:

enum color: int { red, blue };

color is defined as a strong type with the same value range as int, with the two named constants red and blue. Repeat that: color has the same value range as int! A switch over red and blue is by no means "covered" or "exhausted".

@willcl-ark
Copy link
Member

I consider "no default case, so the compiler can warn about missing cases" a compiler bug. Why can't the compiler warn about missing cases when there is a default?

Hmmm, these are fair comments, and I think I find myself agreeing with them... Two things stand out to me:

We're pretty much a gcc project (currently, for our release builds at least), and as noted gcc supports -Wswitch-enum which warns on missing cases even with a default: present. So the "can't have both" tradeoff that motivates removing default: doesn't actually apply to our release compiler.

The other concern is that without a default:, the runtime safety net we use by convention (assert after the switch) relies on the dev remembering to add it, or reviewers catching it being absent. Nothing warns if they forget. With -Wswitch-enum + -Wswitch-default, the default: (really ~ default: assert(false)) is structurally part of the switch and enforced by the compiler.

This PR's clang warnings enforce a convention (no default: in exhaustive switches). GCC's pair would enforce both exhaustiveness and runtime safety. That's quite compelling to me.

Changing our developer-notes.md and all switches is going to be a much larger change though, and perhaps not worth it?

@maflcko
Copy link
Member Author

maflcko commented Mar 3, 2026

I consider "no default case, so the compiler can warn about missing cases" a compiler bug. Why can't the compiler warn about missing cases when there is a default?

I don't think this is about compilers, it is more about existing code using the default: as a catch for the remaining, unlisted cases. Enforcing -Wswitch-enum is certainly possible. It makes the code more verbose, which is probably the reason it hasn't been done in the past.

Enabling -Wswitch-enum seems questionable, glancing at the output:

./src/script/interpreter.cpp:930:29: warning: 107 enumeration values not explicitly handled in switch: 'OP_0', 'OP_PUSHDATA1', 'OP_PUSHDATA2'... [-Wswitch-enum]
  930 |                     switch (opcode)
      |                             ^~~~~~
./src/script/interpreter.cpp:965:29: warning: 100 enumeration values not explicitly handled in switch: 'OP_0', 'OP_PUSHDATA1', 'OP_PUSHDATA2'... [-Wswitch-enum]
  965 |                     switch (opcode)
      |                             ^~~~~~
./src/script/interpreter.cpp:484:21: warning: 26 enumeration values not explicitly handled in switch: 'OP_0', 'OP_PUSHDATA1', 'OP_PUSHDATA2'... [-Wswitch-enum]
  484 |             switch (opcode)
      |                     ^~~~~~

My preference would be to discuss and enable that in a separate pull request, if there is need for it.

To clarify, there are two different patterns used in this codebase:

bool IsApple(Fruit f) {
 switch (f) {
  case apple:  return 1;
  default: return 0; // all other fruits (or invalid values)
 }
}

and

int Calories(Fruit f) {
 switch (f) {
  case apple:  return 95;
  case banana: return 105;
 } // no default case, so the compiler can warn about missing cases
 assert(false); // Any other value of Fruit would be a logic error or data corruption -> terminate
}

Enabling -Wswitch-enum would disallow the first pattern.

Personally, I think it is perfectly fine (and desirable) to have both patterns at the disposal, and have a way to document which pattern is used, so that compilers and code readers can understand and enforce each pattern.

Edit: about enum .. {..} __attribute__((flag_enum));: I think enforcing a default for those could make sense. It can even be part of -Wswitch, but I think no compiler has this implemented yet.

Copy link
Contributor

@stickies-v stickies-v left a comment

Choose a reason for hiding this comment

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

Concept ACK.

I find reserving the default case strictly for switches that are not exhaustive (on the named enumerators) to be natural and idiomatic. It is unfortunate that we have to rely on a convention-enforced safety net (assert/NONFATAL_UNREACHABLE) to handle non-enumerator values, but requiring exhaustive case listing in every enum switch seems too costly a measure given the current state of compiler tooling.

Copy link
Contributor

@l0rinc l0rinc left a comment

Choose a reason for hiding this comment

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

ACK fa3ec4a

I can understand the objection and kind of lean toward adding all values of every enum everywhere (we can just add 100 to a single line if the noise is the concern, but I agree with the author that it should be decided in a follow-up).
I'm personally fine with this change and will let others decide.

Copy link
Contributor

@stickies-v stickies-v left a comment

Choose a reason for hiding this comment

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

ACK fa3ec4a

@luke-jr
Copy link
Member

luke-jr commented Mar 6, 2026

Maybe #ifndef them so the default is omitted only on debug builds?

@maflcko
Copy link
Member Author

maflcko commented Mar 10, 2026

Maybe #ifndef them so the default is omitted only on debug builds?

Not sure what the benefit would be. Also, the net benefit is already possible today (and even enforced for RPC code):

Example from above:

int Calories(Fruit f) {
 switch (f) {
  case apple:  return 95;
  case banana: return 105;
 } // no default case, so the compiler can warn about missing cases
 assert(false); // Any other value of Fruit would be a logic error or data corruption -> terminate
}

Example for a release-only fallback:

int Calories(Fruit f) {
 switch (f) {
  case apple:  return 95;
  case banana: return 105;
 } // no default case, so the compiler can warn about missing cases
 Assume(false); // Any other value of Fruit would be a logic error or data corruption
 return DEFAULT_CALORIES;
}

Example for an RPC-style non-fatal check:

int Calories(Fruit f) {
 switch (f) {
  case apple:  return 95;
  case banana: return 105;
 } // no default case, so the compiler can warn about missing cases
 CHECK_NONFATAL(false); // Any other value of Fruit would be a logic error or data corruption -> throw
}

MarcoFalke and others added 2 commits March 13, 2026 09:02
Also, apply the comment according to the dev notes.

Also, modify the dev notes to give a lambda-wrapped example.

Can be reviewed via --ignore-all-space

Co-authored-by: Lőrinc <pap.lorinc@gmail.com>
The leveldb exclusion is required to avoid this warning in the subtree:

```
src/leveldb/util/status.cc:63:7: warning: default label in switch which covers all enumeration values [-Wcovered-switch-default]
   63 |       default:
      |       ^
1 warning generated.
```
@stickies-v
Copy link
Contributor

re-ACK fa4ec13, no changes except for addressing silent merge conflict from d339884

@DrahtBot DrahtBot requested a review from l0rinc March 13, 2026 08:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants