perf: use fast_float to parse floating-point numbers (#3092)
This commit is contained in:
parent
ebd1a0b7bf
commit
2293f4336a
|
@ -43,3 +43,6 @@
|
||||||
[submodule "third-party/fmt"]
|
[submodule "third-party/fmt"]
|
||||||
path = third-party/fmt
|
path = third-party/fmt
|
||||||
url = https://github.com/transmission/fmt.git
|
url = https://github.com/transmission/fmt.git
|
||||||
|
[submodule "third-party/fast_float"]
|
||||||
|
path = third-party/fast_float
|
||||||
|
url = https://github.com/transmission/fast_float
|
||||||
|
|
|
@ -154,6 +154,7 @@ find_package(Fmt)
|
||||||
add_definitions(-DFMT_HEADER_ONLY -DFMT_EXCEPTIONS=0)
|
add_definitions(-DFMT_HEADER_ONLY -DFMT_EXCEPTIONS=0)
|
||||||
include_directories(SYSTEM ${LIBFMT_INCLUDE_DIRS})
|
include_directories(SYSTEM ${LIBFMT_INCLUDE_DIRS})
|
||||||
|
|
||||||
|
find_package(FastFloat)
|
||||||
find_package(UtfCpp)
|
find_package(UtfCpp)
|
||||||
find_package(Threads)
|
find_package(Threads)
|
||||||
find_package(PkgConfig QUIET)
|
find_package(PkgConfig QUIET)
|
||||||
|
@ -587,8 +588,7 @@ set(NEEDED_FUNCTIONS
|
||||||
sendfile64
|
sendfile64
|
||||||
statvfs
|
statvfs
|
||||||
strlcpy
|
strlcpy
|
||||||
syslog
|
syslog)
|
||||||
uselocale)
|
|
||||||
|
|
||||||
foreach(F ${NEEDED_FUNCTIONS})
|
foreach(F ${NEEDED_FUNCTIONS})
|
||||||
tr_make_id("${F}" F_ID)
|
tr_make_id("${F}" F_ID)
|
||||||
|
|
|
@ -3591,11 +3591,12 @@
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"third-party/arc4/src",
|
"third-party/arc4/src",
|
||||||
"third-party/dht",
|
"third-party/dht",
|
||||||
|
"third-party/fast_float/include",
|
||||||
"third-party/libb64/include",
|
"third-party/libb64/include",
|
||||||
"third-party/libutp",
|
|
||||||
"third-party/utfcpp/source",
|
|
||||||
"third-party/libdeflate",
|
"third-party/libdeflate",
|
||||||
"third-party/libpsl/include",
|
"third-party/libpsl/include",
|
||||||
|
"third-party/libutp",
|
||||||
|
"third-party/utfcpp/source",
|
||||||
);
|
);
|
||||||
OTHER_CFLAGS = (
|
OTHER_CFLAGS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
|
@ -3805,11 +3806,12 @@
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"third-party/arc4/src",
|
"third-party/arc4/src",
|
||||||
"third-party/dht",
|
"third-party/dht",
|
||||||
|
"third-party/fast_float/include",
|
||||||
"third-party/libb64/include",
|
"third-party/libb64/include",
|
||||||
"third-party/libutp",
|
|
||||||
"third-party/utfcpp/source",
|
|
||||||
"third-party/libdeflate",
|
"third-party/libdeflate",
|
||||||
"third-party/libpsl/include",
|
"third-party/libpsl/include",
|
||||||
|
"third-party/libutp",
|
||||||
|
"third-party/utfcpp/source",
|
||||||
);
|
);
|
||||||
OTHER_CFLAGS = (
|
OTHER_CFLAGS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
|
@ -4091,11 +4093,12 @@
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"third-party/arc4/src",
|
"third-party/arc4/src",
|
||||||
"third-party/dht",
|
"third-party/dht",
|
||||||
|
"third-party/fast_float/include",
|
||||||
"third-party/libb64/include",
|
"third-party/libb64/include",
|
||||||
"third-party/libutp",
|
|
||||||
"third-party/utfcpp/source",
|
|
||||||
"third-party/libdeflate",
|
"third-party/libdeflate",
|
||||||
"third-party/libpsl/include",
|
"third-party/libpsl/include",
|
||||||
|
"third-party/libutp",
|
||||||
|
"third-party/utfcpp/source",
|
||||||
);
|
);
|
||||||
OTHER_CFLAGS = (
|
OTHER_CFLAGS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
set(FAST_FLOAT_INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/third-party/fast_float/include)
|
|
@ -262,6 +262,7 @@ include_directories(
|
||||||
SYSTEM
|
SYSTEM
|
||||||
${UTFCPP_INCLUDE_DIRS}
|
${UTFCPP_INCLUDE_DIRS}
|
||||||
${DEFLATE_INCLUDE_DIRS}
|
${DEFLATE_INCLUDE_DIRS}
|
||||||
|
${FAST_FLOAT_INCLUDE_DIRS}
|
||||||
${CRYPTO_INCLUDE_DIRS}
|
${CRYPTO_INCLUDE_DIRS}
|
||||||
${CURL_INCLUDE_DIRS}
|
${CURL_INCLUDE_DIRS}
|
||||||
${EVENT2_INCLUDE_DIRS}
|
${EVENT2_INCLUDE_DIRS}
|
||||||
|
|
|
@ -25,6 +25,10 @@
|
||||||
#include <ws2tcpip.h> /* WSAStartup() */
|
#include <ws2tcpip.h> /* WSAStartup() */
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
#include <sys/stat.h> // mode_t
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef HAVE_ICONV
|
#ifdef HAVE_ICONV
|
||||||
#include <iconv.h>
|
#include <iconv.h>
|
||||||
#endif
|
#endif
|
||||||
|
@ -37,6 +41,8 @@
|
||||||
|
|
||||||
#include <fmt/format.h>
|
#include <fmt/format.h>
|
||||||
|
|
||||||
|
#include <fast_float/fast_float.h>
|
||||||
|
|
||||||
#include "transmission.h"
|
#include "transmission.h"
|
||||||
|
|
||||||
#include "error-types.h"
|
#include "error-types.h"
|
||||||
|
@ -1355,3 +1361,80 @@ std::string_view tr_get_mime_type_for_filename(std::string_view filename)
|
||||||
auto constexpr Fallback = "application/octet-stream"sv;
|
auto constexpr Fallback = "application/octet-stream"sv;
|
||||||
return Fallback;
|
return Fallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// parseNum()
|
||||||
|
|
||||||
|
#if defined(__GNUC__) && !__has_include(<charconv>)
|
||||||
|
|
||||||
|
#include <iomanip> // std::setbase
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
template<typename T, std::enable_if_t<std::is_integral<T>::value, bool> = true>
|
||||||
|
[[nodiscard]] std::optional<T> tr_parseNum(std::string_view& sv, int base)
|
||||||
|
{
|
||||||
|
auto val = T{};
|
||||||
|
auto const str = std::string(std::data(sv), std::min(std::size(sv), size_t{ 64 }));
|
||||||
|
auto sstream = std::stringstream{ str };
|
||||||
|
auto const oldpos = sstream.tellg();
|
||||||
|
/* The base parameter only works for bases 8, 10 and 16.
|
||||||
|
All other bases will be converted to 0 which activates the
|
||||||
|
prefix based parsing and therefore decimal in our usual cases.
|
||||||
|
This differs from the from_chars solution below. */
|
||||||
|
sstream >> std::setbase(base) >> val;
|
||||||
|
auto const newpos = sstream.tellg();
|
||||||
|
if ((newpos == oldpos) || (sstream.fail() && !sstream.eof()))
|
||||||
|
{
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
sv.remove_prefix(sstream.eof() ? std::size(sv) : newpos - oldpos);
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
#else // #if defined(__GNUC__) && !__has_include(<charconv>)
|
||||||
|
|
||||||
|
#include <charconv> // std::from_chars()
|
||||||
|
|
||||||
|
template<typename T, std::enable_if_t<std::is_integral<T>::value, bool>>
|
||||||
|
[[nodiscard]] std::optional<T> tr_parseNum(std::string_view& sv, int base)
|
||||||
|
{
|
||||||
|
auto val = T{};
|
||||||
|
auto const* const begin_ch = std::data(sv);
|
||||||
|
auto const* const end_ch = begin_ch + std::size(sv);
|
||||||
|
/* The base parameter works for any base from 2 to 36 (inclusive).
|
||||||
|
This is different from the behaviour of the stringstream
|
||||||
|
based solution above. */
|
||||||
|
auto const result = std::from_chars(begin_ch, end_ch, val, base);
|
||||||
|
if (result.ec != std::errc{})
|
||||||
|
{
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
sv.remove_prefix(result.ptr - std::data(sv));
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // #if defined(__GNUC__) && !__has_include(<charconv>)
|
||||||
|
|
||||||
|
template std::optional<int64_t> tr_parseNum(std::string_view& sv, int base);
|
||||||
|
template std::optional<int> tr_parseNum(std::string_view& sv, int base);
|
||||||
|
template std::optional<size_t> tr_parseNum(std::string_view& sv, int base);
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
template std::optional<mode_t> tr_parseNum(std::string_view& sv, int base);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
template<typename T, std::enable_if_t<std::is_floating_point<T>::value, bool>>
|
||||||
|
[[nodiscard]] std::optional<T> tr_parseNum(std::string_view& sv)
|
||||||
|
{
|
||||||
|
auto const* const begin_ch = std::data(sv);
|
||||||
|
auto const* const end_ch = begin_ch + std::size(sv);
|
||||||
|
auto val = T{};
|
||||||
|
auto const result = fast_float::from_chars(begin_ch, end_ch, val);
|
||||||
|
if (result.ec != std::errc{})
|
||||||
|
{
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
sv.remove_prefix(result.ptr - std::data(sv));
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
template std::optional<double> tr_parseNum(std::string_view& sv);
|
||||||
|
|
|
@ -115,55 +115,11 @@ uint64_t tr_time_msec();
|
||||||
/** @brief sleep the specified number of milliseconds */
|
/** @brief sleep the specified number of milliseconds */
|
||||||
void tr_wait_msec(long int delay_milliseconds);
|
void tr_wait_msec(long int delay_milliseconds);
|
||||||
|
|
||||||
#if defined(__GNUC__) && !__has_include(<charconv>)
|
template<typename T, std::enable_if_t<std::is_integral<T>::value, bool> = true>
|
||||||
|
[[nodiscard]] std::optional<T> tr_parseNum(std::string_view& sv, int base = 10);
|
||||||
|
|
||||||
#include <iomanip> // std::setbase
|
template<typename T, std::enable_if_t<std::is_floating_point<T>::value, bool> = true>
|
||||||
#include <sstream>
|
[[nodiscard]] std::optional<T> tr_parseNum(std::string_view& sv);
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
[[nodiscard]] std::optional<T> tr_parseNum(std::string_view& sv, int base = 10)
|
|
||||||
{
|
|
||||||
auto val = T{};
|
|
||||||
auto const str = std::string(std::data(sv), std::min(std::size(sv), size_t{ 64 }));
|
|
||||||
auto sstream = std::stringstream{ str };
|
|
||||||
auto const oldpos = sstream.tellg();
|
|
||||||
/* The base parameter only works for bases 8, 10 and 16.
|
|
||||||
All other bases will be converted to 0 which activates the
|
|
||||||
prefix based parsing and therefore decimal in our usual cases.
|
|
||||||
This differs from the from_chars solution below. */
|
|
||||||
sstream >> std::setbase(base) >> val;
|
|
||||||
auto const newpos = sstream.tellg();
|
|
||||||
if ((newpos == oldpos) || (sstream.fail() && !sstream.eof()))
|
|
||||||
{
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
sv.remove_prefix(sstream.eof() ? std::size(sv) : newpos - oldpos);
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
|
|
||||||
#else // #if defined(__GNUC__) && !__has_include(<charconv>)
|
|
||||||
|
|
||||||
#include <charconv> // std::from_chars()
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
[[nodiscard]] std::optional<T> tr_parseNum(std::string_view& sv, int base = 10)
|
|
||||||
{
|
|
||||||
auto val = T{};
|
|
||||||
auto const* const begin_ch = std::data(sv);
|
|
||||||
auto const* const end_ch = begin_ch + std::size(sv);
|
|
||||||
/* The base parameter works for any base from 2 to 36 (inclusive).
|
|
||||||
This is different from the behaviour of the stringstream
|
|
||||||
based solution above. */
|
|
||||||
auto const result = std::from_chars(begin_ch, end_ch, val, base);
|
|
||||||
if (result.ec != std::errc{})
|
|
||||||
{
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
sv.remove_prefix(result.ptr - std::data(sv));
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // #if defined(__GNUC__) && !__has_include(<charconv>)
|
|
||||||
|
|
||||||
bool tr_utf8_validate(std::string_view sv, char const** endptr);
|
bool tr_utf8_validate(std::string_view sv, char const** endptr);
|
||||||
|
|
||||||
|
|
|
@ -324,9 +324,9 @@ static void action_callback_POP(
|
||||||
{
|
{
|
||||||
if ((state->special_flags & JSONSL_SPECIALf_NUMNOINT) != 0)
|
if ((state->special_flags & JSONSL_SPECIALf_NUMNOINT) != 0)
|
||||||
{
|
{
|
||||||
char const* begin = jsn->base + state->pos_begin;
|
auto sv = std::string_view{ jsn->base + state->pos_begin, jsn->pos - state->pos_begin };
|
||||||
data->has_content = true;
|
auto const val = tr_parseNum<double>(sv);
|
||||||
tr_variantInitReal(get_node(jsn), strtod(begin, nullptr));
|
tr_variantInitReal(get_node(jsn), val ? *val : double{});
|
||||||
}
|
}
|
||||||
else if ((state->special_flags & JSONSL_SPECIALf_NUMERIC) != 0)
|
else if ((state->special_flags & JSONSL_SPECIALf_NUMERIC) != 0)
|
||||||
{
|
{
|
||||||
|
|
|
@ -3,18 +3,8 @@
|
||||||
// or any future license endorsed by Mnemosyne LLC.
|
// or any future license endorsed by Mnemosyne LLC.
|
||||||
// License text can be found in the licenses/ folder.
|
// License text can be found in the licenses/ folder.
|
||||||
|
|
||||||
#if defined(HAVE_USELOCALE) && (!defined(_XOPEN_SOURCE) || _XOPEN_SOURCE < 700)
|
|
||||||
#undef _XOPEN_SOURCE
|
|
||||||
#define XOPEN_SOURCE 700 // NOLINT
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if defined(HAVE_USELOCALE) && !defined(_GNU_SOURCE)
|
|
||||||
#define _GNU_SOURCE
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <algorithm> // std::sort
|
#include <algorithm> // std::sort
|
||||||
#include <cerrno>
|
#include <cerrno>
|
||||||
#include <cstdlib> /* strtod() */
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <stack>
|
#include <stack>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
@ -25,12 +15,6 @@
|
||||||
#include <share.h>
|
#include <share.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <clocale> /* setlocale() */
|
|
||||||
|
|
||||||
#if defined(HAVE_USELOCALE) && defined(HAVE_XLOCALE_H)
|
|
||||||
#include <xlocale.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <event2/buffer.h>
|
#include <event2/buffer.h>
|
||||||
|
|
||||||
#include <fmt/core.h>
|
#include <fmt/core.h>
|
||||||
|
@ -48,70 +32,8 @@
|
||||||
#include "variant-common.h"
|
#include "variant-common.h"
|
||||||
#include "variant.h"
|
#include "variant.h"
|
||||||
|
|
||||||
/* don't use newlocale/uselocale on old versions of uClibc because they're buggy.
|
|
||||||
* https://trac.transmissionbt.com/ticket/6006 */
|
|
||||||
#if defined(__UCLIBC__) && !TR_UCLIBC_CHECK_VERSION(0, 9, 34)
|
|
||||||
#undef HAVE_USELOCALE
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/**
|
|
||||||
***
|
|
||||||
**/
|
|
||||||
|
|
||||||
using namespace std::literals;
|
using namespace std::literals;
|
||||||
|
|
||||||
struct locale_context
|
|
||||||
{
|
|
||||||
#ifdef HAVE_USELOCALE
|
|
||||||
locale_t new_locale;
|
|
||||||
locale_t old_locale;
|
|
||||||
#else
|
|
||||||
#if defined(HAVE__CONFIGTHREADLOCALE) && defined(_ENABLE_PER_THREAD_LOCALE)
|
|
||||||
int old_thread_config;
|
|
||||||
#endif
|
|
||||||
int category;
|
|
||||||
char old_locale[128];
|
|
||||||
#endif
|
|
||||||
};
|
|
||||||
|
|
||||||
static void use_numeric_locale(struct locale_context* context, char const* locale_name)
|
|
||||||
{
|
|
||||||
#ifdef HAVE_USELOCALE
|
|
||||||
|
|
||||||
context->new_locale = newlocale(LC_NUMERIC_MASK, locale_name, nullptr);
|
|
||||||
context->old_locale = uselocale(context->new_locale);
|
|
||||||
|
|
||||||
#else
|
|
||||||
|
|
||||||
#if defined(HAVE__CONFIGTHREADLOCALE) && defined(_ENABLE_PER_THREAD_LOCALE)
|
|
||||||
context->old_thread_config = _configthreadlocale(_ENABLE_PER_THREAD_LOCALE);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
context->category = LC_NUMERIC;
|
|
||||||
tr_strlcpy(context->old_locale, setlocale(context->category, nullptr), sizeof(context->old_locale));
|
|
||||||
setlocale(context->category, locale_name);
|
|
||||||
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
static void restore_locale(struct locale_context* context)
|
|
||||||
{
|
|
||||||
#ifdef HAVE_USELOCALE
|
|
||||||
|
|
||||||
uselocale(context->old_locale);
|
|
||||||
freelocale(context->new_locale);
|
|
||||||
|
|
||||||
#else
|
|
||||||
|
|
||||||
setlocale(context->category, context->old_locale);
|
|
||||||
|
|
||||||
#if defined(HAVE__CONFIGTHREADLOCALE) && defined(_ENABLE_PER_THREAD_LOCALE)
|
|
||||||
_configthreadlocale(context->old_thread_config);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
/***
|
/***
|
||||||
****
|
****
|
||||||
***/
|
***/
|
||||||
|
@ -392,17 +314,13 @@ bool tr_variantGetReal(tr_variant const* v, double* setme)
|
||||||
|
|
||||||
if (!success && tr_variantIsString(v))
|
if (!success && tr_variantIsString(v))
|
||||||
{
|
{
|
||||||
/* the json spec requires a '.' decimal point regardless of locale */
|
if (auto sv = std::string_view{}; tr_variantGetStrView(v, &sv))
|
||||||
struct locale_context locale_ctx;
|
|
||||||
use_numeric_locale(&locale_ctx, "C");
|
|
||||||
char* endptr = nullptr;
|
|
||||||
double const d = strtod(getStr(v), &endptr);
|
|
||||||
restore_locale(&locale_ctx);
|
|
||||||
|
|
||||||
if (getStr(v) != endptr && *endptr == '\0')
|
|
||||||
{
|
{
|
||||||
*setme = d;
|
if (auto d = tr_parseNum<double>(sv); d)
|
||||||
success = true;
|
{
|
||||||
|
*setme = *d;
|
||||||
|
success = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1173,12 +1091,8 @@ void tr_variantMergeDicts(tr_variant* target, tr_variant const* source)
|
||||||
|
|
||||||
struct evbuffer* tr_variantToBuf(tr_variant const* v, tr_variant_fmt fmt)
|
struct evbuffer* tr_variantToBuf(tr_variant const* v, tr_variant_fmt fmt)
|
||||||
{
|
{
|
||||||
struct locale_context locale_ctx;
|
|
||||||
struct evbuffer* buf = evbuffer_new();
|
struct evbuffer* buf = evbuffer_new();
|
||||||
|
|
||||||
/* parse with LC_NUMERIC="C" to ensure a "." decimal separator */
|
|
||||||
use_numeric_locale(&locale_ctx, "C");
|
|
||||||
|
|
||||||
evbuffer_expand(buf, 4096); /* alloc a little memory to start off with */
|
evbuffer_expand(buf, 4096); /* alloc a little memory to start off with */
|
||||||
|
|
||||||
switch (fmt)
|
switch (fmt)
|
||||||
|
@ -1196,8 +1110,6 @@ struct evbuffer* tr_variantToBuf(tr_variant const* v, tr_variant_fmt fmt)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* restore the previous locale */
|
|
||||||
restore_locale(&locale_ctx);
|
|
||||||
return buf;
|
return buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1242,10 +1154,6 @@ bool tr_variantFromBuf(tr_variant* setme, int opts, std::string_view buf, char c
|
||||||
// supported formats: benc, json
|
// supported formats: benc, json
|
||||||
TR_ASSERT((opts & (TR_VARIANT_PARSE_BENC | TR_VARIANT_PARSE_JSON)) != 0);
|
TR_ASSERT((opts & (TR_VARIANT_PARSE_BENC | TR_VARIANT_PARSE_JSON)) != 0);
|
||||||
|
|
||||||
// parse with LC_NUMERIC="C" to ensure a "." decimal separator
|
|
||||||
auto locale_ctx = locale_context{};
|
|
||||||
use_numeric_locale(&locale_ctx, "C");
|
|
||||||
|
|
||||||
*setme = {};
|
*setme = {};
|
||||||
|
|
||||||
auto const success = ((opts & TR_VARIANT_PARSE_BENC) != 0) ? tr_variantParseBenc(*setme, opts, buf, setme_end, error) :
|
auto const success = ((opts & TR_VARIANT_PARSE_BENC) != 0) ? tr_variantParseBenc(*setme, opts, buf, setme_end, error) :
|
||||||
|
@ -1256,9 +1164,6 @@ bool tr_variantFromBuf(tr_variant* setme, int opts, std::string_view buf, char c
|
||||||
tr_variantFree(setme);
|
tr_variantFree(setme);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* restore the previous locale */
|
|
||||||
restore_locale(&locale_ctx);
|
|
||||||
|
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit f2082bf747eabc0873f2fdceb05f9451931b96dc
|
Loading…
Reference in New Issue