Skip to content

Commit

Permalink
Replace kstream::to_string() - not just int, fix portability
Browse files Browse the repository at this point in the history
See #8 (comment)

Fix #28,
close #39
  • Loading branch information
generalmimon committed Apr 2, 2022
1 parent aaa2a3f commit 5ee4e19
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 18 deletions.
3 changes: 3 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ set (HEADERS
kaitai/kaitaistream.h
kaitai/kaitaistruct.h
kaitai/exceptions.h
# required by public header kaitai/kaitaistream.h, so I guess it must be included as well
# (see https://stackoverflow.com/a/61152762)
kaitai/detail/make_unsigned.h
)

set (SOURCES
Expand Down
19 changes: 19 additions & 0 deletions kaitai/detail/make_unsigned.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#ifndef KAITAI_DETAIL_MAKE_UNSIGNED_H
#define KAITAI_DETAIL_MAKE_UNSIGNED_H

namespace kaitai {
// poor man's implementation of `std::make_unsigned` in pre-C++11 versions
// (https://stackoverflow.com/a/27577556)
//
// Don't use this externally. This is an internal implementation helper
// and may be removed at any time.
template<typename T> struct make_unsigned { typedef T type; };
template<> struct make_unsigned<char> { typedef unsigned char type; };
template<> struct make_unsigned<signed char> { typedef unsigned char type; };
template<> struct make_unsigned<short> { typedef unsigned short type; };
template<> struct make_unsigned<int> { typedef unsigned int type; };
template<> struct make_unsigned<long> { typedef unsigned long type; };
// `long long` is available only since C++11
}

#endif
2 changes: 1 addition & 1 deletion kaitai/exceptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ class undecided_endianness_error: public kstruct_error {
class validation_failed_error: public kstruct_error {
public:
validation_failed_error(const std::string what, kstream* io, const std::string src_path):
kstruct_error("at pos " + kstream::to_string(static_cast<int>(io->pos())) + ": validation failed: " + what, src_path),
kstruct_error("at pos " + kstream::to_string(io->pos()) + ": validation failed: " + what, src_path),
m_io(io)
{
}
Expand Down
16 changes: 0 additions & 16 deletions kaitai/kaitaistream.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -566,22 +566,6 @@ int kaitai::kstream::mod(int a, int b) {
return r;
}

#include <stdio.h>
std::string kaitai::kstream::to_string(int val) {
// if int is 32 bits, "-2147483648" is the longest string representation
// => 11 chars + zero => 12 chars
// if int is 64 bits, "-9223372036854775808" is the longest
// => 20 chars + zero => 21 chars
char buf[25];
int got_len = snprintf(buf, sizeof(buf), "%d", val);

// should never happen, but check nonetheless
if (got_len > sizeof(buf))
throw std::invalid_argument("to_string: integer is longer than string buffer");

return std::string(buf);
}

#include <algorithm>
std::string kaitai::kstream::reverse(std::string val) {
std::reverse(val.begin(), val.end());
Expand Down
63 changes: 62 additions & 1 deletion kaitai/kaitaistream.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@
#include <sstream>
#include <stdint.h>
#include <sys/types.h>
#include <algorithm>
#include <limits>

// https://stackoverflow.com/a/40512515
#if __cplusplus >= 201103L || (defined(_MSC_VER) && _MSC_VER >= 1900)
#define KAITAI_STREAM_H_CPP11_SUPPORT
#else
#include <kaitai/detail/make_unsigned.h>
#endif

namespace kaitai {

Expand Down Expand Up @@ -224,7 +233,34 @@ class kstream {
* Should be used in place of std::to_string() (which is available only
* since C++11) in older C++ implementations.
*/
static std::string to_string(int val);
template<typename I>
#ifdef KAITAI_STREAM_H_CPP11_SUPPORT
// https://stackoverflow.com/a/27913885
typename std::enable_if<
std::is_integral<I>::value,
std::string
>::type
#else
std::string
#endif
static to_string(I val) {
#ifdef KAITAI_STREAM_H_CPP11_SUPPORT
typedef typename std::make_unsigned<I>::type U;
#else
typedef typename make_unsigned<I>::type U;
#endif
// in theory, `digits10 + 3` would be enough (minus sign + leading digit
// + null terminator), but let's add a little more to be safe
char buf[std::numeric_limits<I>::digits10 + 5];
if (val < 0) {
buf[0] = '-';
// get absolute value without undefined behavior (https://stackoverflow.com/a/12231604)
unsigned_to_decimal<U>(-static_cast<U>(val), &buf[1]);
} else {
unsigned_to_decimal<U>(val, buf);
}
return std::string(buf);
}

/**
* Reverses given string `val`, so that the first character becomes the
Expand Down Expand Up @@ -258,6 +294,31 @@ class kstream {
void init();
void exceptions_enable() const;

template<typename U>
#ifdef KAITAI_STREAM_H_CPP11_SUPPORT
typename std::enable_if<
std::is_integral<U>::value && std::is_unsigned<U>::value,
void
>::type
#else
void
#endif
static inline unsigned_to_decimal(U number, char *buffer) {
// Implementation from https://ideone.com/nrQfA8 by Alf P. Steinbach
// (see https://www.zverovich.net/2013/09/07/integer-to-string-conversion-in-cplusplus.html#comment-1033931478)
if (number == 0) {
*buffer++ = '0';
} else {
char *p_first = buffer;
while (number != 0) {
*buffer++ = static_cast<char>('0' + number % 10);
number /= 10;
}
std::reverse(p_first, buffer);
}
*buffer = '\0';
}

static const int ZLIB_BUF_SIZE = 128 * 1024;
};

Expand Down

0 comments on commit 5ee4e19

Please sign in to comment.