From 73ad9b5c0aa3c8510c73c6c3c44ec4be0487ac9a Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 19 Feb 2020 11:11:36 +0100 Subject: [PATCH] common: avoid locale dependent strtod(3) Replace `json_to_double()` (which uses `strtod(3)`) with our own floating-point parsing function `json_to_millionths()` that specifically expects to receive such a number that can fit in a 64 bit integer after being multiplied by 1 million. The main piece of the code in this patch comes from https://github.com/ElementsProject/lightning/pull/3535#discussion_r381041419 Changelog-None --- common/json.c | 42 ++++++++++++++++++++++++++++--- common/json.h | 9 +++++-- common/json_tok.c | 7 ++---- common/test/run-json.c | 57 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 104 insertions(+), 11 deletions(-) diff --git a/common/json.c b/common/json.c index 4be49f04b..5fb509b3d 100644 --- a/common/json.c +++ b/common/json.c @@ -13,8 +13,10 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -97,13 +99,45 @@ bool json_to_s64(const char *buffer, const jsmntok_t *tok, s64 *num) return true; } -bool json_to_double(const char *buffer, const jsmntok_t *tok, double *num) +bool json_to_millionths(const char *buffer, const jsmntok_t *tok, + u64 *millionths) { - char *end; + int decimal_places = -1; + bool has_digits = 0; - *num = strtod(buffer + tok->start, &end); - if (end != buffer + tok->end) + *millionths = 0; + for (int i = tok->start; i < tok->end; i++) { + if (isdigit(buffer[i])) { + has_digits = true; + /* Ignore too much precision */ + if (decimal_places >= 0 && ++decimal_places > 6) + continue; + if (mul_overflows_u64(*millionths, 10)) + return false; + *millionths *= 10; + if (add_overflows_u64(*millionths, buffer[i] - '0')) + return false; + *millionths += buffer[i] - '0'; + } else if (buffer[i] == '.') { + if (decimal_places != -1) + return false; + decimal_places = 0; + } else + return false; + } + + if (!has_digits) return false; + + if (decimal_places == -1) + decimal_places = 0; + + while (decimal_places < 6) { + if (mul_overflows_u64(*millionths, 10)) + return false; + *millionths *= 10; + decimal_places++; + } return true; } diff --git a/common/json.h b/common/json.h index 551d1b198..f92a634d9 100644 --- a/common/json.h +++ b/common/json.h @@ -69,8 +69,13 @@ bool json_to_u32(const char *buffer, const jsmntok_t *tok, bool json_to_u16(const char *buffer, const jsmntok_t *tok, uint16_t *num); -/* Extract double from this (must be a number literal) */ -bool json_to_double(const char *buffer, const jsmntok_t *tok, double *num); +/* + * Extract a non-negative (either 0 or positive) floating-point number from this + * (must be a number literal), multiply it by 1 million and return it as an + * integer. Any fraction smaller than 0.000001 is ignored. + */ +bool json_to_millionths(const char *buffer, const jsmntok_t *tok, + u64 *millionths); /* Extract signed integer from this (may be a string, or a number literal) */ bool json_to_int(const char *buffer, const jsmntok_t *tok, int *num); diff --git a/common/json_tok.c b/common/json_tok.c index 730842d84..a08de9e0e 100644 --- a/common/json_tok.c +++ b/common/json_tok.c @@ -39,12 +39,9 @@ struct command_result *param_millionths(struct command *cmd, const char *name, const char *buffer, const jsmntok_t *tok, uint64_t **num) { - double d; - if (json_to_double(buffer, tok, &d) && d >= 0.0) { - *num = tal(cmd, uint64_t); - **num = (uint64_t)(d * 1000000); + *num = tal(cmd, uint64_t); + if (json_to_millionths(buffer, tok, *num)) return NULL; - } return command_fail( cmd, JSONRPC2_INVALID_PARAMS, diff --git a/common/test/run-json.c b/common/test/run-json.c index ed24dfd96..04e583e7d 100644 --- a/common/test/run-json.c +++ b/common/test/run-json.c @@ -46,6 +46,62 @@ static int test_json_tok_bitcoin_amount(void) return 0; } +static void do_json_tok_millionths(const char *val, bool ok, uint64_t expected) +{ + uint64_t amount; + jsmntok_t tok; + + tok.start = 0; + tok.end = strlen(val); + + assert(json_to_millionths(val, &tok, &amount) == ok); + if (ok) + assert(amount == expected); +} + +static int test_json_tok_millionths(void) +{ + do_json_tok_millionths("", false, 0); + do_json_tok_millionths("0..0", false, 0); + do_json_tok_millionths("0.0.", false, 0); + do_json_tok_millionths(".", false, 0); + do_json_tok_millionths("..", false, 0); + + do_json_tok_millionths("0", true, 0); + do_json_tok_millionths(".0", true, 0); + do_json_tok_millionths("0.", true, 0); + do_json_tok_millionths("100", true, 100 * 1000000); + do_json_tok_millionths("100.0", true, 100 * 1000000); + do_json_tok_millionths("100.", true, 100 * 1000000); + do_json_tok_millionths("100.000001", true, 100 * 1000000 + 1); + do_json_tok_millionths("100.0000001", true, 100 * 1000000); + do_json_tok_millionths(".000009", true, 9); + do_json_tok_millionths(".0000099", true, 9); + do_json_tok_millionths("18446744073709.551615", true, + 18446744073709551615ULL); + do_json_tok_millionths("18446744073709.551616", false, 0); + do_json_tok_millionths("18446744073709.551625", false, 0); + do_json_tok_millionths("18446744073709.551715", false, 0); + do_json_tok_millionths("18446744073709.552615", false, 0); + do_json_tok_millionths("18446744073709.561615", false, 0); + do_json_tok_millionths("18446744073709.651615", false, 0); + do_json_tok_millionths("18446744073710.551615", false, 0); + do_json_tok_millionths("18446744073809.551615", false, 0); + do_json_tok_millionths("18446744074709.551615", false, 0); + do_json_tok_millionths("18446744083709.551615", false, 0); + do_json_tok_millionths("18446744173709.551615", false, 0); + do_json_tok_millionths("18446745073709.551615", false, 0); + do_json_tok_millionths("18446754073709.551615", false, 0); + do_json_tok_millionths("18446844073709.551615", false, 0); + do_json_tok_millionths("18447744073709.551615", false, 0); + do_json_tok_millionths("18456744073709.551615", false, 0); + do_json_tok_millionths("18546744073709.551615", false, 0); + do_json_tok_millionths("19446744073709.551615", false, 0); + do_json_tok_millionths("28446744073709.551615", false, 0); + + return 0; +} + static void test_json_tok_size(void) { const jsmntok_t *toks; @@ -190,6 +246,7 @@ int main(void) test_json_tok_size(); test_json_tok_bitcoin_amount(); + test_json_tok_millionths(); test_json_delve(); assert(!taken_any()); take_cleanup();