Check for overflow when parsing argument index. Improve error handling. Fix overload issues. More tests.
This commit is contained in:
parent
5f3ed207da
commit
63539c03b0
29
format.cc
29
format.cc
@ -6,16 +6,22 @@
|
|||||||
#include "format.h"
|
#include "format.h"
|
||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
#include <climits>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
using std::size_t;
|
using std::size_t;
|
||||||
|
|
||||||
static void CheckClosingBrace(const char *s) {
|
// Throws Exception(message) if s contains '}' and FormatError reporting
|
||||||
|
// unmatched '{' otherwise. The idea is that unmatched '{' should override
|
||||||
|
// other errors.
|
||||||
|
template <typename Exception>
|
||||||
|
static void Throw(const char *s, const char *message) {
|
||||||
while (*s && *s != '}')
|
while (*s && *s != '}')
|
||||||
++s;
|
++s;
|
||||||
if (!*s)
|
if (!*s)
|
||||||
throw fmt::FormatError("unmatched '{' in format");
|
throw fmt::FormatError("unmatched '{' in format");
|
||||||
|
throw Exception(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
@ -54,18 +60,17 @@ void fmt::Formatter::Format() {
|
|||||||
// Parse argument index.
|
// Parse argument index.
|
||||||
unsigned arg_index = 0;
|
unsigned arg_index = 0;
|
||||||
if ('0' <= *s && *s <= '9') {
|
if ('0' <= *s && *s <= '9') {
|
||||||
// TODO: check overflow
|
|
||||||
do {
|
do {
|
||||||
arg_index = arg_index * 10 + (*s++ - '0');
|
unsigned index = arg_index * 10 + (*s++ - '0');
|
||||||
|
if (index < arg_index) // Check if index wrapped around.
|
||||||
|
Throw<FormatError>(s, "argument index is too big"); // TODO: test
|
||||||
|
arg_index = index;
|
||||||
} while ('0' <= *s && *s <= '9');
|
} while ('0' <= *s && *s <= '9');
|
||||||
} else {
|
} else {
|
||||||
CheckClosingBrace(s);
|
Throw<FormatError>(s, "missing argument index in format string");
|
||||||
throw FormatError("missing argument index in format string");
|
|
||||||
}
|
|
||||||
if (arg_index >= args_.size()) {
|
|
||||||
CheckClosingBrace(s);
|
|
||||||
throw std::out_of_range("argument index is out of range in format");
|
|
||||||
}
|
}
|
||||||
|
if (arg_index >= args_.size())
|
||||||
|
Throw<std::out_of_range>(s, "argument index is out of range in format");
|
||||||
|
|
||||||
char arg_format[8]; // longest format: %+0*.*ld
|
char arg_format[8]; // longest format: %+0*.*ld
|
||||||
char *arg_format_ptr = arg_format;
|
char *arg_format_ptr = arg_format;
|
||||||
@ -86,6 +91,7 @@ void fmt::Formatter::Format() {
|
|||||||
*arg_format_ptr++ = '*';
|
*arg_format_ptr++ = '*';
|
||||||
width = 0;
|
width = 0;
|
||||||
do {
|
do {
|
||||||
|
// TODO: check overflow
|
||||||
width = width * 10 + (*s++ - '0');
|
width = width * 10 + (*s++ - '0');
|
||||||
} while ('0' <= *s && *s <= '9');
|
} while ('0' <= *s && *s <= '9');
|
||||||
}
|
}
|
||||||
@ -98,6 +104,7 @@ void fmt::Formatter::Format() {
|
|||||||
precision = 0;
|
precision = 0;
|
||||||
if ('0' <= *s && *s <= '9') {
|
if ('0' <= *s && *s <= '9') {
|
||||||
do {
|
do {
|
||||||
|
// TODO: check overflow
|
||||||
precision = precision * 10 + (*s++ - '0');
|
precision = precision * 10 + (*s++ - '0');
|
||||||
} while ('0' <= *s && *s <= '9');
|
} while ('0' <= *s && *s <= '9');
|
||||||
} else {
|
} else {
|
||||||
@ -180,6 +187,9 @@ void fmt::Formatter::Format() {
|
|||||||
*arg_format_ptr = '\0';
|
*arg_format_ptr = '\0';
|
||||||
FormatArg(arg_format, arg.pointer_value, width, precision);
|
FormatArg(arg_format, arg.pointer_value, width, precision);
|
||||||
break;
|
break;
|
||||||
|
case OTHER:
|
||||||
|
(this->*arg.format)(arg.other_value);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
assert(false);
|
assert(false);
|
||||||
break;
|
break;
|
||||||
@ -192,4 +202,3 @@ fmt::ArgFormatter::~ArgFormatter() {
|
|||||||
if (!formatter_) return;
|
if (!formatter_) return;
|
||||||
FinishFormatting();
|
FinishFormatting();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
53
format.h
53
format.h
@ -31,7 +31,8 @@ class Formatter {
|
|||||||
std::vector<char> buffer_; // Output buffer.
|
std::vector<char> buffer_; // Output buffer.
|
||||||
|
|
||||||
enum Type {
|
enum Type {
|
||||||
CHAR, INT, UINT, LONG, ULONG, DOUBLE, LONG_DOUBLE, STRING, WSTRING, POINTER
|
CHAR, INT, UINT, LONG, ULONG, DOUBLE, LONG_DOUBLE,
|
||||||
|
STRING, WSTRING, POINTER, OTHER
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Arg {
|
struct Arg {
|
||||||
@ -46,6 +47,10 @@ class Formatter {
|
|||||||
const char *string_value;
|
const char *string_value;
|
||||||
const wchar_t *wstring_value;
|
const wchar_t *wstring_value;
|
||||||
const void *pointer_value;
|
const void *pointer_value;
|
||||||
|
struct {
|
||||||
|
const void *other_value;
|
||||||
|
void (Formatter::*format)(const void *value);
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
explicit Arg(char value) : type(CHAR), int_value(value) {}
|
explicit Arg(char value) : type(CHAR), int_value(value) {}
|
||||||
@ -59,6 +64,8 @@ class Formatter {
|
|||||||
explicit Arg(const char *value) : type(STRING), string_value(value) {}
|
explicit Arg(const char *value) : type(STRING), string_value(value) {}
|
||||||
explicit Arg(const wchar_t *value) : type(WSTRING), wstring_value(value) {}
|
explicit Arg(const wchar_t *value) : type(WSTRING), wstring_value(value) {}
|
||||||
explicit Arg(const void *value) : type(POINTER), pointer_value(value) {}
|
explicit Arg(const void *value) : type(POINTER), pointer_value(value) {}
|
||||||
|
explicit Arg(const void *value, void (Formatter::*format)(const void *))
|
||||||
|
: type(OTHER), format(format) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
std::vector<Arg> args_;
|
std::vector<Arg> args_;
|
||||||
@ -76,6 +83,12 @@ class Formatter {
|
|||||||
template <typename T>
|
template <typename T>
|
||||||
void FormatArg(const char *format, const T &arg, int width, int precision);
|
void FormatArg(const char *format, const T &arg, int width, int precision);
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void FormatOtherArg(const void *value) {
|
||||||
|
const T &typed_value = *static_cast<const T*>(value);
|
||||||
|
// TODO: format value
|
||||||
|
}
|
||||||
|
|
||||||
void Format();
|
void Format();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@ -88,8 +101,20 @@ class Formatter {
|
|||||||
|
|
||||||
const char *c_str() const { return &buffer_[0]; }
|
const char *c_str() const { return &buffer_[0]; }
|
||||||
std::size_t size() const { return buffer_.size() - 1; }
|
std::size_t size() const { return buffer_.size() - 1; }
|
||||||
|
|
||||||
|
void Swap(Formatter &f) {
|
||||||
|
buffer_.swap(f.buffer_);
|
||||||
|
args_.swap(f.args_);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct AddPtrConst { typedef T Value; };
|
||||||
|
|
||||||
|
// Convert "T*" into "const T*".
|
||||||
|
template <typename T>
|
||||||
|
struct AddPtrConst<T*> { typedef const T* Value; };
|
||||||
|
|
||||||
class ArgFormatter {
|
class ArgFormatter {
|
||||||
private:
|
private:
|
||||||
friend class Formatter;
|
friend class Formatter;
|
||||||
@ -177,6 +202,15 @@ class ArgFormatter {
|
|||||||
// arbitrary pointers. If you want to output a pointer cast it to void*.
|
// arbitrary pointers. If you want to output a pointer cast it to void*.
|
||||||
template <typename T>
|
template <typename T>
|
||||||
ArgFormatter &operator<<(const T *value);
|
ArgFormatter &operator<<(const T *value);
|
||||||
|
|
||||||
|
// If T is a pointer type, say "U*", AddPtrConst<T>::Value will be
|
||||||
|
// "const U*". This additional const ensures that operator<<(const void *)
|
||||||
|
// and not this method is called both for "const void*" and "void*".
|
||||||
|
template <typename T>
|
||||||
|
ArgFormatter &operator<<(const typename AddPtrConst<T>::Value &value) {
|
||||||
|
formatter_->Add(Formatter::Arg(&value, &Formatter::FormatOtherArg<T>));
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename Callback>
|
template <typename Callback>
|
||||||
@ -203,24 +237,29 @@ Formatter::FormatWithCallback(const char *format) {
|
|||||||
return ArgFormatterWithCallback<Callback>(*this);
|
return ArgFormatterWithCallback<Callback>(*this);
|
||||||
}
|
}
|
||||||
|
|
||||||
class Format : public ArgFormatter {
|
class FullFormat : public ArgFormatter {
|
||||||
private:
|
private:
|
||||||
Formatter formatter_;
|
mutable Formatter formatter_;
|
||||||
|
|
||||||
// Do not implement.
|
// Do not implement.
|
||||||
Format(const Format&);
|
FullFormat& operator=(const FullFormat&);
|
||||||
Format& operator=(const Format&);
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit Format(const char *format) : ArgFormatter(formatter_) {
|
explicit FullFormat(const char *format) : ArgFormatter(formatter_) {
|
||||||
ArgFormatter::operator=(formatter_(format));
|
ArgFormatter::operator=(formatter_(format));
|
||||||
}
|
}
|
||||||
|
|
||||||
~Format() {
|
FullFormat(const FullFormat& other) : ArgFormatter(other) {
|
||||||
|
formatter_.Swap(other.formatter_);
|
||||||
|
}
|
||||||
|
|
||||||
|
~FullFormat() {
|
||||||
FinishFormatting();
|
FinishFormatting();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
inline FullFormat Format(const char *format) { return FullFormat(format); }
|
||||||
|
|
||||||
class Print : public ArgFormatter {
|
class Print : public ArgFormatter {
|
||||||
private:
|
private:
|
||||||
Formatter formatter_;
|
Formatter formatter_;
|
||||||
|
@ -68,14 +68,31 @@ TEST(FormatterTest, FormatArgs) {
|
|||||||
EXPECT_EQ("abracadabra", str(Format("{0}{1}{0}") << "abra" << "cad"));
|
EXPECT_EQ("abracadabra", str(Format("{0}{1}{0}") << "abra" << "cad"));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(FormatterTest, InvalidFormat) {
|
TEST(FormatterTest, FormatErrors) {
|
||||||
//Format("{");
|
//Format("{");
|
||||||
|
EXPECT_THROW_MSG(Format("{"), FormatError, "unmatched '{' in format");
|
||||||
EXPECT_THROW_MSG(Format("{}"), FormatError,
|
EXPECT_THROW_MSG(Format("{}"), FormatError,
|
||||||
"missing argument index in format string");
|
"missing argument index in format string");
|
||||||
EXPECT_THROW_MSG(Format("{"), FormatError, "unmatched '{' in format");
|
|
||||||
EXPECT_THROW_MSG(Format("{0"), FormatError, "unmatched '{' in format");
|
EXPECT_THROW_MSG(Format("{0"), FormatError, "unmatched '{' in format");
|
||||||
EXPECT_THROW_MSG(Format("{0}"), std::out_of_range,
|
EXPECT_THROW_MSG(Format("{0}"), std::out_of_range,
|
||||||
"argument index is out of range in format");
|
"argument index is out of range in format");
|
||||||
|
char format[256];
|
||||||
|
std::sprintf(format, "{%u", UINT_MAX);
|
||||||
|
EXPECT_THROW_MSG(Format(format), FormatError, "unmatched '{' in format");
|
||||||
|
std::sprintf(format, "{%u}", UINT_MAX);
|
||||||
|
EXPECT_THROW_MSG(Format(format), std::out_of_range,
|
||||||
|
"argument index is out of range in format");
|
||||||
|
if (ULONG_MAX > UINT_MAX) {
|
||||||
|
std::sprintf(format, "{%lu", UINT_MAX + 1l);
|
||||||
|
EXPECT_THROW_MSG(Format(format), FormatError, "unmatched '{' in format");
|
||||||
|
std::sprintf(format, "{%lu}", UINT_MAX + 1l);
|
||||||
|
EXPECT_THROW_MSG(Format(format), FormatError, "argument index is too big");
|
||||||
|
} else {
|
||||||
|
std::sprintf(format, "{%u0", UINT_MAX);
|
||||||
|
EXPECT_THROW_MSG(Format(format), FormatError, "unmatched '{' in format");
|
||||||
|
std::sprintf(format, "{%u0}", UINT_MAX);
|
||||||
|
EXPECT_THROW_MSG(Format(format), FormatError, "argument index is too big");
|
||||||
|
}
|
||||||
// TODO
|
// TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user