The current “catch-all” catch (const std::exception& e)
in CRPCTable::help
is problematic, because it could catch exceptions unrelated to passing the help string up.
Fix this by using a dedicated exception type.
The current “catch-all” catch (const std::exception& e)
in CRPCTable::help
is problematic, because it could catch exceptions unrelated to passing the help string up.
Fix this by using a dedicated exception type.
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.
For details see: https://corecheck.dev/bitcoin/bitcoin/pulls/32660.
See the guideline for information on the review process.
If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update.
Can be tested by adding a bug, like
0diff --git a/src/rpc/signmessage.cpp b/src/rpc/signmessage.cpp
1index 5597f8d237..d62893bf0c 100644
2--- a/src/rpc/signmessage.cpp
3+++ b/src/rpc/signmessage.cpp
4@@ -19,7 +19,7 @@ static RPCHelpMan verifymessage()
5 return RPCHelpMan{"verifymessage",
6 "Verify a signed message.",
7 {
8- {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The bitcoin address to use for the signature."},
9+ {"addres\ns", RPCArg::Type::STR, RPCArg::Optional::NO, "The bitcoin address to use for the signature."},
10 {"signature", RPCArg::Type::STR, RPCArg::Optional::NO, "The signature provided by the signer in base 64 encoding (see signmessage)."},
11 {"message", RPCArg::Type::STR, RPCArg::Optional::NO, "The message that was signed."},
12 },
On master, the bug is hidden. On this pull, it is more obvious. For example, ./test/functional/rpc_help.py
or the help
RPC fail.
66@@ -67,6 +67,10 @@ class FillableSigningProvider;
67 class CScript;
68 struct Sections;
69
70+struct HelpException : std::runtime_error {
71+ explicit HelpException(const std::string& msg) : std::runtime_error{msg} {}
72+};
nits:
final
0struct HelpException final : std::runtime_error
1{
2 explicit HelpException(const std::string& msg) : std::runtime_error{msg} {}
3};
88 jreq.params = UniValue();
89
90- for (const std::pair<std::string, const CRPCCommand*>& command : vCommands)
91- {
92- const CRPCCommand *pcmd = command.second;
93+ for (const auto& [_, pcmd] : vCommands) {
👍, alternatively, if you think it’s clearer:
0 for (const auto& pcmd : vCommands | std::views::values) {
It doesn’t seem like this is catching the issue in #29136 (comment)? I tried making a similar change to a RPC in this PR and the test still passed.
Are you sure? What are the exact steps to reproduce?
Locally such a bug passes for me on current master:
0$ git checkout bitcoin-core/master && git diff && cmakeb && ./bld-cmake/test/functional/rpc_help.py
1M src/rpc/signmessage.cpp
2HEAD is now at 88b22acc3d Merge bitcoin/bitcoin#32528: rpc: Round verificationprogress to 1 for a recent tip
3
4
5diff --git a/src/rpc/signmessage.cpp b/src/rpc/signmessage.cpp
6index 5597f8d237..18af6d110e 100644
7--- a/src/rpc/signmessage.cpp
8+++ b/src/rpc/signmessage.cpp
9@@ -17,7 +17,7 @@
10 static RPCHelpMan verifymessage()
11 {
12 return RPCHelpMan{"verifymessage",
13- "Verify a signed message.",
14+ "\nVerify a signed message.",
15 {
16 {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The bitcoin address to use for the signature."},
17 {"signature", RPCArg::Type::STR, RPCArg::Optional::NO, "The signature provided by the signer in base 64 encoding (see signmessage)."},
whereas on this pull, the same diff fails.
whereas on this pull, the same diff fails.
Work for me:
0./build/test/functional/rpc_help.py
12025-06-03T07:21:05.244000Z TestFramework (INFO): PRNG seed is: 8715551814237508387
22025-06-03T07:21:05.244000Z TestFramework (INFO): Initializing test directory /var/folders/sq/z88fhjzj0b19ftsd2_bjrmjm0000gn/T/bitcoin_func_test_ks0xf8o9
32025-06-03T07:21:06.291000Z TestFramework (ERROR): JSONRPC error
4Traceback (most recent call last):
5 File "/bitcoin/test/functional/test_framework/test_framework.py", line 189, in main
6 self.run_test()
7 ~~~~~~~~~~~~~^^
8 File "/bitcoin/./build/test/functional/rpc_help.py", line 53, in run_test
9 self.test_categories()
10 ~~~~~~~~~~~~~~~~~~~~^^
11 File "/bitcoin/./build/test/functional/rpc_help.py", line 102, in test_categories
12 titles = [line[3:-3] for line in node.help().splitlines() if line.startswith('==')]
13 ~~~~~~~~~^^
14 File "/bitcoin/test/functional/test_framework/coverage.py", line 50, in __call__
15 return_val = self.auth_service_proxy_instance.__call__(*args, **kwargs)
16 File "/bitcoin/test/functional/test_framework/authproxy.py", line 151, in __call__
17 raise JSONRPCException(response['error'], status)
18test_framework.authproxy.JSONRPCException: Internal bug detected: !m_description.starts_with('\n')
19../../src/rpc/util.cpp:796 (ToString)
20Bitcoin Core v29.99.0-6497cef9bdc8-dirty
21Please report this issue here: https://github.com/bitcoin/bitcoin/issues
22 (-1)
232025-06-03T07:21:06.343000Z TestFramework (INFO): Not stopping nodes as test failed. The dangling processes will be cleaned up later.
0./build/bin/bitcoin-cli help
1error code: -1
2error message:
3Internal bug detected: !m_description.starts_with('\n')
4../../src/rpc/util.cpp:796 (ToString)
5Bitcoin Core v29.99.0-6497cef9bdc8-dirty
6Please report this issue here: https://github.com/bitcoin/bitcoin/issues
644@@ -645,7 +645,7 @@ UniValue RPCHelpMan::HandleRequest(const JSONRPCRequest& request) const
645 * the user is asking for help information, and throw help when appropriate.
646 */
647 if (request.mode == JSONRPCRequest::GET_HELP || !IsValidNumArgs(request.params.size())) {
648- throw std::runtime_error(ToString());
649+ throw HelpException{ToString()};
NonFatalCheckError
exception is thrown from here that was earlier caught by the catch-all handler in server.cpp
. Now only HelpException
is caught and the rest, including NonFatalCheckError
, is bubbled up all the way to the RPC response. https://github.com/bitcoin/bitcoin/blob/e872a566f251c73908de8b6d243c94a6679c2eac/src/rpc/util.cpp#L795-L796
Nit: Maybe returning the result by throwing an exception is obvious to others but it wasn’t to me. Does it necessarily need to be called HelpException
? Calling it HelpResult
might make it more explicit or HelpResultException
(not my preference).
0diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp
1index d5c790e89b..bd99615bbf 100644
2--- a/src/rpc/server.cpp
3+++ b/src/rpc/server.cpp
4@@ -96,7 +96,7 @@ std::string CRPCTable::help(const std::string& strCommand, const JSONRPCRequest&
5 UniValue unused_result;
6 if (setDone.insert(pcmd->unique_id).second)
7 pcmd->actor(jreq, unused_result, /*last_handler=*/true);
8- } catch (const HelpException& e) {
9+ } catch (const HelpResult& e) {
10 std::string strHelp{e.what()};
11 if (strCommand == "")
12 {
13diff --git a/src/rpc/util.cpp b/src/rpc/util.cpp
14index 75b801f2bf..5da02b4df4 100644
15--- a/src/rpc/util.cpp
16+++ b/src/rpc/util.cpp
17@@ -645,7 +645,7 @@ UniValue RPCHelpMan::HandleRequest(const JSONRPCRequest& request) const
18 * the user is asking for help information, and throw help when appropriate.
19 */
20 if (request.mode == JSONRPCRequest::GET_HELP || !IsValidNumArgs(request.params.size())) {
21- throw HelpException{ToString()};
22+ throw HelpResult{ToString()};
23 }
24 UniValue arg_mismatch{UniValue::VOBJ};
25 for (size_t i{0}; i < m_args.size(); ++i) {
26diff --git a/src/rpc/util.h b/src/rpc/util.h
27index 0a86768814..1650d2da1f 100644
28--- a/src/rpc/util.h
29+++ b/src/rpc/util.h
30@@ -67,8 +67,8 @@ class FillableSigningProvider;
31 class CScript;
32 struct Sections;
33
34-struct HelpException : std::runtime_error {
35- explicit HelpException(const std::string& msg) : std::runtime_error{msg} {}
36+struct HelpResult : std::runtime_error {
37+ explicit HelpResult(const std::string& msg) : std::runtime_error{msg} {}
38 };
HelpResult
, if I have to re-touch for other reasons.
git range-diff bitcoin-core/master fa0cf42380 fa946520d2
.
ACK fa0cf42380dec17e73d38aa69dd70662a0bc344a
0➜ bitcoin git:(2506-rpc-help) ✗ git diff --color | cat
1diff --git a/src/rpc/signmessage.cpp b/src/rpc/signmessage.cpp
2index 5597f8d237..18af6d110e 100644
3--- a/src/rpc/signmessage.cpp
4+++ b/src/rpc/signmessage.cpp
5@@ -17,7 +17,7 @@
6 static RPCHelpMan verifymessage()
7 {
8 return RPCHelpMan{"verifymessage",
9- "Verify a signed message.",
10+ "\nVerify a signed message.",
11 {
12 {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The bitcoin address to use for the signature."},
13 {"signature", RPCArg::Type::STR, RPCArg::Optional::NO, "The signature provided by the signer in base 64 encoding (see signmessage)."},
0➜ bitcoin git:(2506-rpc-help) ✗ bitcoinclireg help verifymessage
1error code: -1
2error message:
3Internal bug detected: !m_description.starts_with('\n')
4rpc/util.cpp:796 (ToString)
5Bitcoin Core v29.99.0-fa0cf42380de-dirty
6Please report this issue here: https://github.com/bitcoin/bitcoin/issues
7
8➜ bitcoin git:(2506-rpc-help) ✗ bitcoinclireg help
9error code: -1
10error message:
11Internal bug detected: !m_description.starts_with('\n')
12rpc/util.cpp:796 (ToString)
13Bitcoin Core v29.99.0-fa0cf42380de-dirty
14Please report this issue here: https://github.com/bitcoin/bitcoin/issues
0➜ bitcoin git:(master) ✗ bitcoinclireg help verifymessage
1Internal bug detected: !m_description.starts_with('\n')
2rpc/util.cpp:796 (ToString)
3Bitcoin Core v29.99.0-4b1d48a6866b-dirty
4Please report this issue here: https://github.com/bitcoin/bitcoin/issues
5
6➜ bitcoin git:(master) ✗ bitcoinclireg help
7...
8...
9signmessagewithprivkey "privkey" "message"
10validateaddress "address"
11Internal bug detected: !m_description.starts_with('\n')
12
13== Wallet ==
14abandontransaction "txid"
The rpc_help
functional test also fails with the diff in the branch but not in master.
Are you sure? What are the exact steps to reproduce?
Nope.
Not sure what I did wrong yesterday, but tried again and it does seem to work.
I retested the scenario with "address\n;)"
, running
0cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug && ninja -C build && build/test/functional/test_runner.py rpc_help
and got:
0Internal bug detected: s.m_left.find('\n') == std::string::npos
on master it’s:
0rpc_help.py | ✓ Passed
👍
tested ACK fa946520d229ae45b30519bccc9eaa2c47b4a093 (edited)
Reapplied the tests from #32660 (comment) - passes without the changes, fails with the change as expected!
git fetch origin fa0cf42380dec17e73d38aa69dd70662a0bc344a fa946520d229ae45b30519bccc9eaa2c47b4a093 git range-diff master fa946520d229ae45b30519bccc9eaa2c47b4a093 fa0cf42380dec17e73d38aa69dd70662a0bc344a
01: fa97f9b78e ! 1: eeeec1579e rpc: Use type-safe HelpException
1@@ Metadata
2 Author: MarcoFalke <*~=`'#}+{/-|&$^_@721217.xyz>
3
4## Commit message ##
5- rpc: Use type-safe HelpException
6+ rpc: Use type-safe exception to pass RPC help
7
8## src/rpc/server.cpp ##
9@@ src/rpc/server.cpp: std::string CRPCTable::help(const std::string& strCommand, const JSONRPCRequest&
10@@ src/rpc/server.cpp: std::string CRPCTable::help(const std::string& strCommand, c
11 - {
12 - // Help text is returned in an exception
13 - std::string strHelp = std::string(e.what());
14-+ } catch (const HelpException& e) {
15++ } catch (const HelpResult& e) {
16 + std::string strHelp{e.what()};
17 if (strCommand == "")
18 {
19@@ src/rpc/util.cpp: UniValue RPCHelpMan::HandleRequest(const JSONRPCRequest& reque
20 */
21 if (request.mode == JSONRPCRequest::GET_HELP || !IsValidNumArgs(request.params.size())) {
22 - throw std::runtime_error(ToString());
23-+ throw HelpException{ToString()};
24++ throw HelpResult{ToString()};
25 }
26 UniValue arg_mismatch{UniValue::VOBJ};
27 for (size_t i{0}; i < m_args.size(); ++i) {
28@@ src/rpc/util.h: class FillableSigningProvider;
29 class CScript;
30 struct Sections;
31
32-+struct HelpException : std::runtime_error {
33-+ explicit HelpException(const std::string& msg) : std::runtime_error{msg} {}
34++struct HelpResult : std::runtime_error {
35++ explicit HelpResult(const std::string& msg) : std::runtime_error{msg} {}
36 +};
37 +
38 /**
392: fa0cf42380 = 2: fa946520d2 refactor: Use structured binding for-loop
re-ACK fa946520d229ae45b30519bccc9eaa2c47b4a093
0git range-diff fa0cf42...fa94652
Thanks for accepting the naming suggestion.