Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Specialized TfStringify for integral types to prevent locale dependent writes #3222

Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions pxr/base/tf/stringUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include <type_traits>
#include <vector>
#include <memory>
#include <charconv>

#include "pxrDoubleConversion/double-conversion.h"
#include "pxrDoubleConversion/utils.h"
Expand Down Expand Up @@ -912,6 +913,72 @@ TfStringify(std::string const& s)
return s;
}

template <typename T>
std::string
_TfStringifyIntegralImpl(const T value)
{
// plus one because for signed values, digits10 will give one less
// than what is actually needed in cases where the amount of characters
// could represent an overflow
constexpr size_t maxSize = std::numeric_limits<T>::digits10 + 1 +
(std::numeric_limits<T>::is_signed ? 1 : 0);

std::string result(maxSize, '\0');
auto [ptr, ec] = std::to_chars(result.data(), result.data() + maxSize, value);
TF_DEV_AXIOM(ec == std::errc());
result.resize(std::distance(result.data(), ptr));

return result;
}

std::string
TfStringify(short val)
{
return _TfStringifyIntegralImpl<short>(val);
}

std::string
TfStringify(unsigned short val)
{
return _TfStringifyIntegralImpl<unsigned short>(val);
}

std::string
TfStringify(int val)
{
return _TfStringifyIntegralImpl<int>(val);
}

std::string
TfStringify(unsigned int val)
{
return _TfStringifyIntegralImpl<unsigned int>(val);
}

std::string
TfStringify(long val)
{
return _TfStringifyIntegralImpl<long>(val);
}

std::string
TfStringify(unsigned long val)
{
return _TfStringifyIntegralImpl<unsigned long>(val);
}

std::string
TfStringify(long long val)
{
return _TfStringifyIntegralImpl<long long>(val);
}

std::string
TfStringify(unsigned long long val)
{
return _TfStringifyIntegralImpl<unsigned long long>(val);
}

static
const
pxr_double_conversion::DoubleToStringConverter&
Expand Down
16 changes: 16 additions & 0 deletions pxr/base/tf/stringUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -572,6 +572,22 @@ TF_API std::string TfStringify(std::string const&);
TF_API std::string TfStringify(float);
/// \overload
TF_API std::string TfStringify(double);
/// \overload
TF_API std::string TfStringify(short);
/// \overload
TF_API std::string TfStringify(unsigned short);
/// \overload
TF_API std::string TfStringify(int);
/// \overload
TF_API std::string TfStringify(unsigned int);
/// \overload
TF_API std::string TfStringify(long);
/// \overload
TF_API std::string TfStringify(unsigned long);
/// \overload
TF_API std::string TfStringify(long long);
/// \overload
TF_API std::string TfStringify(unsigned long long);
erslavin marked this conversation as resolved.
Show resolved Hide resolved

/// Writes the string representation of \c d to \c buffer of length \c len.
/// If \c emitTrailingZero is true, the string representation will end with .0
Expand Down
38 changes: 38 additions & 0 deletions pxr/base/tf/testenv/stringUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include <vector>
#include <sstream>
#include <stdio.h>
#include <locale>

using namespace std;
PXR_NAMESPACE_USING_DIRECTIVE
Expand Down Expand Up @@ -264,6 +265,14 @@ DoPrintfStr(const char *fmt, ...)
return ret;
}

template <typename T>
bool
_RoundtripStringifyLimits()
{
return (TfUnstringify<T>(TfStringify(std::numeric_limits<T>::min())) == std::numeric_limits<T>::min()) &&
(TfUnstringify<T>(TfStringify(std::numeric_limits<T>::max())) == std::numeric_limits<T>::max());
erslavin marked this conversation as resolved.
Show resolved Hide resolved
}

static bool
TestStrings()
{
Expand Down Expand Up @@ -386,6 +395,35 @@ TestStrings()
TF_AXIOM(TfUnstringify<char>("a") == 'a');
TF_AXIOM(TfStringify("string") == "string");
TF_AXIOM(TfUnstringify<string>("string") == "string");
TF_AXIOM(TfStringify(1000) == "1000");

// make sure we can represent the min and max of each type
TF_AXIOM(_RoundtripStringifyLimits<short>());
TF_AXIOM(_RoundtripStringifyLimits<int>());
TF_AXIOM(_RoundtripStringifyLimits<long>());
TF_AXIOM(_RoundtripStringifyLimits<long long>());
TF_AXIOM(_RoundtripStringifyLimits<unsigned short>());
TF_AXIOM(_RoundtripStringifyLimits<unsigned int>());
TF_AXIOM(_RoundtripStringifyLimits<unsigned long>());
TF_AXIOM(_RoundtripStringifyLimits<unsigned long long>());

// verify that TfStringify is agnostic to locale for
// numerical values
std::locale originalLocale;
std::locale::global(std::locale(""));
try
{
TF_AXIOM(TfStringify(1000.56) == "1000.56");
TF_AXIOM(TfStringify(1000) == "1000");
std::locale::global(originalLocale);
erslavin marked this conversation as resolved.
Show resolved Hide resolved
}
catch(...)
{
std::locale::global(originalLocale);
throw;
}
TF_AXIOM(TfStringify(1000) == "1000");
TF_AXIOM(TfStringify(1000.56) == "1000.56");

bool unstringRet = true;
TfUnstringify<int>("this ain't no int", &unstringRet);
Expand Down