Skip to content

Commit 666e6e2

Browse files
laanwjPastaPastaPasta
authored andcommitted
Merge bitcoin#20406: util: Avoid invalid integer negation in FormatMoney and ValueFromAmount
1f05dbd util: Avoid invalid integer negation in ValueFromAmount: make ValueFromAmount(const CAmount& n) well-defined also when n is std::numeric_limits<CAmount>::min() (practicalswift) 7cc75c9 util: Avoid invalid integer negation in FormatMoney: make FormatMoney(const CAmount& n) well-defined also when n is std::numeric_limits<CAmount>::min() (practicalswift) Pull request description: Avoid invalid integer negation in `FormatMoney` and `ValueFromAmount`. Fixes bitcoin#20402. Before this patch: ``` $ CC=clang CXX=clang++ ./configure --with-sanitizers=undefined $ make -C src/ test/test_bitcoin $ src/test/test_bitcoin -t rpc_tests/rpc_format_monetary_values -t util_tests/util_FormatMoney core_write.cpp:21:29: runtime error: negation of -9223372036854775808 cannot be represented in type 'CAmount' (aka 'long'); cast to an unsigned type to negate this value to itself SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior core_write.cpp:21:29 in test/rpc_tests.cpp(186): error: in "rpc_tests/rpc_format_monetary_values": check ValueFromAmount(std::numeric_limits<CAmount>::min()).write() == "-92233720368.54775808" has failed [--92233720368.-54775808 != -92233720368.54775808] util/moneystr.cpp:16:34: runtime error: negation of -9223372036854775808 cannot be represented in type 'CAmount' (aka 'long'); cast to an unsigned type to negate this value to itself SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior util/moneystr.cpp:16:34 in test/util_tests.cpp(1188): error: in "util_tests/util_FormatMoney": check FormatMoney(std::numeric_limits<CAmount>::min()) == "-92233720368.54775808" has failed [--92233720368.-54775808 != -92233720368.54775808] ``` After this patch: ``` $ CC=clang CXX=clang++ ./configure --with-sanitizers=undefined $ make -C src/ test/test_bitcoin $ src/test/test_bitcoin -t rpc_tests/rpc_format_monetary_values -t util_tests/util_FormatMoney ``` ACKs for top commit: laanwj: re-ACK 1f05dbd Tree-SHA512: 5aaeb8e2178f1597921f53c12bdfc2f3d5993d10c41658dcd25943e54e8cc2116a411bc71d928f890b33bc0b3761a8ee4449b0532bce41125b6c60692808c8c3
1 parent 1a07e04 commit 666e6e2

File tree

9 files changed

+48
-29
lines changed

9 files changed

+48
-29
lines changed

src/core_io.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ std::vector<unsigned char> ParseHexUV(const UniValue& v, const std::string& strN
4141
int ParseSighashString(const UniValue& sighash);
4242

4343
// core_write.cpp
44-
UniValue ValueFromAmount(const CAmount& amount);
44+
UniValue ValueFromAmount(const CAmount amount);
4545
std::string FormatScript(const CScript& script);
4646
std::string EncodeHexTx(const CTransaction& tx);
4747
std::string SighashToStr(unsigned char sighash_type);

src/core_write.cpp

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include <univalue.h>
1616
#include <util/check.h>
1717
#include <util/strencodings.h>
18+
#include <util/system.h>
1819

1920
#include <addressindex.h>
2021
#include <spentindex.h>
@@ -26,14 +27,17 @@
2627
#include <evo/specialtx.h>
2728
#include <llmq/commitment.h>
2829

29-
UniValue ValueFromAmount(const CAmount& amount)
30+
UniValue ValueFromAmount(const CAmount amount)
3031
{
31-
bool sign = amount < 0;
32-
int64_t n_abs = (sign ? -amount : amount);
33-
int64_t quotient = n_abs / COIN;
34-
int64_t remainder = n_abs % COIN;
32+
static_assert(COIN > 1);
33+
int64_t quotient = amount / COIN;
34+
int64_t remainder = amount % COIN;
35+
if (amount < 0) {
36+
quotient = -quotient;
37+
remainder = -remainder;
38+
}
3539
return UniValue(UniValue::VNUM,
36-
strprintf("%s%d.%08d", sign ? "-" : "", quotient, remainder));
40+
strprintf("%s%d.%08d", amount < 0 ? "-" : "", quotient, remainder));
3741
}
3842

3943
std::string FormatScript(const CScript& script)

src/evo/cbtx.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ class CChainLocksHandler;
2424
}// namespace llmq
2525

2626
// Forward declaration from core_io to get rid of circular dependency
27-
UniValue ValueFromAmount(const CAmount& amount);
27+
UniValue ValueFromAmount(const CAmount amount);
2828

2929
// coinbase transaction
3030
class CCbTx

src/test/fuzz/integer.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,8 @@ FUZZ_TARGET_INIT(integer, initialize_integer)
7979
(void)DecompressAmount(u64);
8080
(void)FormatISO8601Date(i64);
8181
(void)FormatISO8601DateTime(i64);
82-
// FormatMoney(i) not defined when i == std::numeric_limits<int64_t>::min()
83-
if (i64 != std::numeric_limits<int64_t>::min()) {
82+
{
83+
8484
if (std::optional<CAmount> parsed = ParseMoney(FormatMoney(i64))) {
8585
assert(parsed.value() == i64);
8686
}
@@ -122,8 +122,8 @@ FUZZ_TARGET_INIT(integer, initialize_integer)
122122
(void)SipHashUint256Extra(u64, u64, u256, u32);
123123
(void)ToLower(ch);
124124
(void)ToUpper(ch);
125-
// ValueFromAmount(i) not defined when i == std::numeric_limits<int64_t>::min()
126-
if (i64 != std::numeric_limits<int64_t>::min()) {
125+
{
126+
127127
if (std::optional<CAmount> parsed = ParseMoney(ValueFromAmount(i64).getValStr())) {
128128
assert(parsed.value() == i64);
129129
}

src/test/fuzz/transaction.cpp

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -94,16 +94,7 @@ FUZZ_TARGET_INIT(transaction, initialize_transaction)
9494
(void)AreInputsStandard(tx, coins_view_cache);
9595

9696
UniValue u(UniValue::VOBJ);
97-
// ValueFromAmount(i) not defined when i == std::numeric_limits<int64_t>::min()
98-
bool skip_tx_to_univ = false;
99-
for (const CTxOut& txout : tx.vout) {
100-
if (txout.nValue == std::numeric_limits<int64_t>::min()) {
101-
skip_tx_to_univ = true;
102-
}
103-
}
104-
if (!skip_tx_to_univ) {
105-
TxToUniv(tx, /* hashBlock */ {}, u);
106-
static const uint256 u256_max(uint256S("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"));
107-
TxToUniv(tx, u256_max, u);
108-
}
97+
TxToUniv(tx, /* hashBlock */ {}, u);
98+
static const uint256 u256_max(uint256S("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"));
99+
TxToUniv(tx, u256_max, u);
109100
}

src/test/rpc_tests.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,16 @@ BOOST_AUTO_TEST_CASE(rpc_format_monetary_values)
232232
BOOST_CHECK_EQUAL(ValueFromAmount(COIN/1000000).write(), "0.00000100");
233233
BOOST_CHECK_EQUAL(ValueFromAmount(COIN/10000000).write(), "0.00000010");
234234
BOOST_CHECK_EQUAL(ValueFromAmount(COIN/100000000).write(), "0.00000001");
235+
236+
BOOST_CHECK_EQUAL(ValueFromAmount(std::numeric_limits<CAmount>::max()).write(), "92233720368.54775807");
237+
BOOST_CHECK_EQUAL(ValueFromAmount(std::numeric_limits<CAmount>::max() - 1).write(), "92233720368.54775806");
238+
BOOST_CHECK_EQUAL(ValueFromAmount(std::numeric_limits<CAmount>::max() - 2).write(), "92233720368.54775805");
239+
BOOST_CHECK_EQUAL(ValueFromAmount(std::numeric_limits<CAmount>::max() - 3).write(), "92233720368.54775804");
240+
// ...
241+
BOOST_CHECK_EQUAL(ValueFromAmount(std::numeric_limits<CAmount>::min() + 3).write(), "-92233720368.54775805");
242+
BOOST_CHECK_EQUAL(ValueFromAmount(std::numeric_limits<CAmount>::min() + 2).write(), "-92233720368.54775806");
243+
BOOST_CHECK_EQUAL(ValueFromAmount(std::numeric_limits<CAmount>::min() + 1).write(), "-92233720368.54775807");
244+
BOOST_CHECK_EQUAL(ValueFromAmount(std::numeric_limits<CAmount>::min()).write(), "-92233720368.54775808");
235245
}
236246

237247
static UniValue ValueFromString(const std::string &str)

src/test/util_tests.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1302,6 +1302,16 @@ BOOST_AUTO_TEST_CASE(util_FormatMoney)
13021302
BOOST_CHECK_EQUAL(FormatMoney(COIN/1000000), "0.000001");
13031303
BOOST_CHECK_EQUAL(FormatMoney(COIN/10000000), "0.0000001");
13041304
BOOST_CHECK_EQUAL(FormatMoney(COIN/100000000), "0.00000001");
1305+
1306+
BOOST_CHECK_EQUAL(FormatMoney(std::numeric_limits<CAmount>::max()), "92233720368.54775807");
1307+
BOOST_CHECK_EQUAL(FormatMoney(std::numeric_limits<CAmount>::max() - 1), "92233720368.54775806");
1308+
BOOST_CHECK_EQUAL(FormatMoney(std::numeric_limits<CAmount>::max() - 2), "92233720368.54775805");
1309+
BOOST_CHECK_EQUAL(FormatMoney(std::numeric_limits<CAmount>::max() - 3), "92233720368.54775804");
1310+
// ...
1311+
BOOST_CHECK_EQUAL(FormatMoney(std::numeric_limits<CAmount>::min() + 3), "-92233720368.54775805");
1312+
BOOST_CHECK_EQUAL(FormatMoney(std::numeric_limits<CAmount>::min() + 2), "-92233720368.54775806");
1313+
BOOST_CHECK_EQUAL(FormatMoney(std::numeric_limits<CAmount>::min() + 1), "-92233720368.54775807");
1314+
BOOST_CHECK_EQUAL(FormatMoney(std::numeric_limits<CAmount>::min()), "-92233720368.54775808");
13051315
}
13061316

13071317
BOOST_AUTO_TEST_CASE(util_ParseMoney)

src/util/moneystr.cpp

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,17 @@
1212

1313
#include <optional>
1414

15-
std::string FormatMoney(const CAmount& n)
15+
std::string FormatMoney(const CAmount n)
1616
{
1717
// Note: not using straight sprintf here because we do NOT want
1818
// localized number formatting.
19-
int64_t n_abs = (n > 0 ? n : -n);
20-
int64_t quotient = n_abs/COIN;
21-
int64_t remainder = n_abs%COIN;
19+
static_assert(COIN > 1);
20+
int64_t quotient = n / COIN;
21+
int64_t remainder = n % COIN;
22+
if (n < 0) {
23+
quotient = -quotient;
24+
remainder = -remainder;
25+
}
2226
std::string str = strprintf("%d.%08d", quotient, remainder);
2327

2428
// Right-trim excess zeros before the decimal point:

src/util/moneystr.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
/* Do not use these functions to represent or parse monetary amounts to or from
1919
* JSON but use AmountFromValue and ValueFromAmount for that.
2020
*/
21-
std::string FormatMoney(const CAmount& n);
21+
std::string FormatMoney(const CAmount n);
2222
/** Parse an amount denoted in full coins. E.g. "0.0034" supplied on the command line. **/
2323
std::optional<CAmount> ParseMoney(const std::string& str);
2424

0 commit comments

Comments
 (0)