Skip to content

Commit

Permalink
cras_cpp_common: string_utils: Handle possible error in vsnprintf. Ad…
Browse files Browse the repository at this point in the history
…ded printf-format attributes to cras::format() to enable compile-time checks of format strings.
  • Loading branch information
peci1 committed Nov 4, 2024
1 parent b08cc8d commit 749febf
Show file tree
Hide file tree
Showing 4 changed files with 31 additions and 9 deletions.
2 changes: 1 addition & 1 deletion cras_cpp_common/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ Release jobs Noetic
- `set_utils`: Provides `isSetIntersectionEmpty()` working on a pair of `std::set`s.
- `small_map`: Provides `SmallMap` and `SmallSet`, variants of `std::map` implemented using `std::list` which are append-only and lock-free for reading.
- `span`: Provides forward compatibility for [`std::span`](https://en.cppreference.com/w/cpp/container/span).
- `string_utils`: Provides many string manipulation helpers you've always dreamed of. Universal `to_string()` that converts almost anything to a sensible string. `startsWith()`/`endsWith()`, `replace()`, `contains()`, `split()`/`join()`, `format()` (like `sprintf()` but without hassle and on `std::string`), `stripLeading()`/`stripTrailing()`, `removePrefix()`/`removeSuffix()`, `parseFloat()`/`parseDouble()` (convert string to double independent of locale!), `parseInt32()` and friends (parse many textual representations to an integer, or with specified radix). `parseTime()` and `parseDuration()` to parse textual date/time strings to `ros::Time` and `ros::Duration`.
- `string_utils`: Provides many string manipulation helpers you've always dreamed of. Universal `to_string()` that converts almost anything to a sensible string. `startsWith()`/`endsWith()`, `replace()`, `contains()`, `split()`/`join()`, `format()` (like `sprintf()` but without hassle and on `std::string`), `stripLeading()`/`stripTrailing()`, `removePrefix()`/`removeSuffix()`, `parseFloat()`/`parseDouble()` (convert string to double independent of locale!), `parseInt32()` and friends (parse many textual representations to an integer, or with specified radix). `parseTime()` and `parseDuration()` to parse textual date/time strings to `ros::Time` and `ros::Duration`. `toValidRosName()` to convert any input string to a valid ROS graph resource name.
- `suppress_warnings`: Unified macros that suppress various compiler warnings for a piece of code.
- `test_utils`: Provide a hack that allows loading a locally-defined nodelet without the need to register it via package.xml.
- `tf2_utils`: `getRoll()`, `getPitch()`, `getYaw()`, `getRPY()` from a `tf2::Quaternion` or `geometry_msgs::Quaternion`!
Expand Down
17 changes: 13 additions & 4 deletions cras_cpp_common/include/cras_cpp_common/string_utils.hpp
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
#pragma once

// SPDX-License-Identifier: BSD-3-Clause
// SPDX-FileCopyrightText: Czech Technical University in Prague

/**
* \file
* \brief Utils for working with strings.
* \author Martin Pecka
* SPDX-License-Identifier: BSD-3-Clause
* SPDX-FileCopyrightText: Czech Technical University in Prague
*/

#include <array>
#include <cstdarg>
#include <cstring>
#include <functional>
#include <list>
#include <map>
#include <set>
#include <stdexcept>
#include <string>
#include <sstream>
#include <unordered_map>
Expand Down Expand Up @@ -221,6 +224,7 @@ ::std::string toLower(const ::std::string& str);
* \param[in] args Arguments of the format string.
* \return The formatted string.
*/
__attribute__((format(printf, 1, 0)))
inline ::std::string format(const char* format, ::va_list args)
{
constexpr size_t BUF_LEN = 1024u;
Expand All @@ -232,7 +236,11 @@ inline ::std::string format(const char* format, ::va_list args)
const auto len = ::vsnprintf(buf, BUF_LEN, format, args);

::std::string result;
if (len < BUF_LEN)
if (len < 0)
{
throw ::std::runtime_error(::std::string("Error formatting string '") + format + "': " + ::strerror(errno));
}
else if (len < BUF_LEN)
{
result = buf;
}
Expand All @@ -253,6 +261,7 @@ inline ::std::string format(const char* format, ::va_list args)
* \param[in] ... Arguments of the format string.
* \return The formatted string.
*/
__attribute__((format(printf, 1, 2)))
inline ::std::string format(const char* format, ...)
{
::va_list(args);
Expand Down Expand Up @@ -351,7 +360,7 @@ inline ::std::string to_string(const float& value)

inline ::std::string to_string(const long double& value)
{
return ::cras::format("%gL", value);
return ::cras::format("%Lg", value);
}

inline ::std::string to_string(const char* value)
Expand Down
4 changes: 2 additions & 2 deletions cras_cpp_common/src/string_utils/ros.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ template<> ros::Time parseTime(
{
auto paddedNsec = matches[7].str();
if (paddedNsec.length() < 9)
paddedNsec = cras::format("%s%0*d", paddedNsec.c_str(), 9 - paddedNsec.length(), 0);
paddedNsec = cras::format("%s%0*d", paddedNsec.c_str(), static_cast<int>(9 - paddedNsec.length()), 0);
else if (paddedNsec.length() > 9)
paddedNsec = paddedNsec.substr(0, 9); // We could correctly round here, but who cares about one ns?
fracNsec = cras::parseUInt32(paddedNsec, 10);
Expand Down Expand Up @@ -186,7 +186,7 @@ template<> ros::Duration parseDuration(const std::string& s)
{
auto paddedNsec = nsecString;
if (paddedNsec.length() < 9)
paddedNsec = cras::format("%s%0*d", paddedNsec.c_str(), 9 - paddedNsec.length(), 0);
paddedNsec = cras::format("%s%0*d", paddedNsec.c_str(), static_cast<int>(9 - paddedNsec.length()), 0);
else if (paddedNsec.length() > 9)
paddedNsec = paddedNsec.substr(0, 9); // We could correctly round here, but who cares about one ns?
fracNsec = cras::parseInt32(paddedNsec, 10);
Expand Down
17 changes: 15 additions & 2 deletions cras_cpp_common/test/test_string_utils.cpp
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
// SPDX-License-Identifier: BSD-3-Clause
// SPDX-FileCopyrightText: Czech Technical University in Prague

/**
* \file
* \brief Unit test for string_utils.hpp
* \author Martin Pecka
* SPDX-License-Identifier: BSD-3-Clause
* SPDX-FileCopyrightText: Czech Technical University in Prague
*/

#include "gtest/gtest.h"
Expand Down Expand Up @@ -580,6 +581,7 @@ TEST(StringUtils, ToLower) // NOLINT
// EXPECT_EQ("ěščřžýáíéďťňúů", cras::toLower("ĚŠČŘŽÝÁÍÉĎŤŇÚŮ")); // not yet working
}

__attribute__((__format__(__printf__, 2, 3)))
void test_va_format(const std::string& res, const char* format, ...)
{
va_list(args);
Expand All @@ -600,6 +602,17 @@ TEST(StringUtils, FormatVaList) // NOLINT

std::string longString(300000, '*'); // generates a string of length 300.000 asterisks
{SCOPED_TRACE("8"); test_va_format(longString, "%s", longString.c_str()); }

// Try to make vsnprintf fail with error. This is an attempt at passing it an invalid multibyte character.
// However, not all systems must have such character. That's why we need to first check whether the conversion
// of our chosen character WEOF is actually invalid on the running system.
{
TempLocale l(LC_ALL, "C");
std::mbstate_t state;
char tmp[MB_LEN_MAX];
if (wcrtomb(tmp, WEOF, &state) == static_cast<size_t>(-1))
EXPECT_THROW(cras::format("%lc", WEOF), std::runtime_error);
}
}

TEST(StringUtils, FormatCharPtr) // NOLINT
Expand Down

0 comments on commit 749febf

Please sign in to comment.