Fix type safety when using custom formatters (#394)

This commit is contained in:
Victor Zverovich 2016-10-07 08:37:06 -07:00
parent 506435bf71
commit dafbec7553
6 changed files with 143 additions and 116 deletions

View File

@ -403,72 +403,11 @@ FMT_FUNC void format_system_error(
fmt::format_error_code(out, error_code, message); // 'fmt::' is for bcc32.
}
template <typename Char>
void internal::ArgMap<Char>::init(const format_args &args) {
if (!map_.empty())
return;
typedef internal::NamedArg<Char> NamedArg;
const NamedArg *named_arg = 0;
bool use_values =
args.type(MAX_PACKED_ARGS - 1) == internal::Arg::NONE;
if (use_values) {
for (unsigned i = 0;/*nothing*/; ++i) {
internal::Arg::Type arg_type = args.type(i);
switch (arg_type) {
case internal::Arg::NONE:
return;
case internal::Arg::NAMED_ARG:
named_arg = static_cast<const NamedArg*>(args.values_[i].pointer);
map_.push_back(Pair(named_arg->name, *named_arg));
break;
default:
/*nothing*/;
}
}
return;
}
for (unsigned i = 0; i != MAX_PACKED_ARGS; ++i) {
internal::Arg::Type arg_type = args.type(i);
if (arg_type == internal::Arg::NAMED_ARG) {
named_arg = static_cast<const NamedArg*>(args.args_[i].pointer);
map_.push_back(Pair(named_arg->name, *named_arg));
}
}
for (unsigned i = MAX_PACKED_ARGS;/*nothing*/; ++i) {
switch (args.args_[i].type) {
case internal::Arg::NONE:
return;
case internal::Arg::NAMED_ARG:
named_arg = static_cast<const NamedArg*>(args.args_[i].pointer);
map_.push_back(Pair(named_arg->name, *named_arg));
break;
default:
/*nothing*/;
}
}
}
template <typename Char>
void internal::FixedBuffer<Char>::grow(std::size_t) {
FMT_THROW(std::runtime_error("buffer overflow"));
}
FMT_FUNC Arg internal::FormatterBase::do_get_arg(
unsigned arg_index, const char *&error) {
Arg arg = args_[arg_index];
switch (arg.type) {
case Arg::NONE:
error = "argument index out of range";
break;
case Arg::NAMED_ARG:
arg = *static_cast<const internal::Arg*>(arg.pointer);
break;
default:
/*nothing*/;
}
return arg;
}
FMT_FUNC void report_system_error(
int error_code, fmt::StringRef message) FMT_NOEXCEPT {
// 'fmt::' is for bcc32.
@ -505,7 +444,8 @@ template <typename Char>
void printf(BasicWriter<Char> &w, BasicCStringRef<Char> format,
format_args args);
FMT_FUNC int vfprintf(std::FILE *f, CStringRef format, format_args args) {
FMT_FUNC int vfprintf(std::FILE *f, CStringRef format,
basic_format_args<PrintfFormatter<char>> args) {
MemoryWriter w;
printf(w, format, args);
std::size_t size = w.size();

View File

@ -1429,7 +1429,7 @@ constexpr uint64_t make_type<void>() { return 0; }
enum { MAX_PACKED_ARGS = 16 };
} // namespace internal
template <typename ...Args>
template <typename Formatter, typename ...Args>
class format_arg_store {
private:
static const size_t NUM_ARGS = sizeof...(Args);
@ -1441,13 +1441,12 @@ class format_arg_store {
// If the arguments are not packed, add one more element to mark the end.
std::array<value_type, NUM_ARGS + (IS_PACKED ? 0 : 1)> data_;
template <typename Formatter, typename ...A>
friend format_arg_store<A...> make_format_args(const A & ... args);
template <typename ...A>
friend format_arg_store<Formatter, A...> make_format_args(const A & ... args);
public:
static const uint64_t TYPES = internal::make_type<Args..., void>();
template <typename Formatter>
format_arg_store(const Args &... args, Formatter *)
: data_{{internal::MakeValue<Formatter>(args)...}} {}
@ -1455,13 +1454,15 @@ class format_arg_store {
};
template <typename Formatter, typename ...Args>
inline format_arg_store<Args...> make_format_args(const Args & ... args) {
inline format_arg_store<Formatter, Args...>
make_format_args(const Args & ... args) {
Formatter *f = nullptr;
return format_arg_store<Args...>(args..., f);
return format_arg_store<Formatter, Args...>(args..., f);
}
/** Formatting arguments. */
class format_args {
template <typename Formatter>
class basic_format_args {
private:
// To reduce compiled code size per formatting function call, types of first
// MAX_PACKED_ARGS arguments are passed in the types_ field.
@ -1491,10 +1492,10 @@ class format_args {
public:
typedef unsigned size_type;
format_args() : types_(0) {}
basic_format_args() : types_(0) {}
template <typename... Args>
format_args(const format_arg_store<Args...> &store)
template <typename F, typename... Args>
basic_format_args(const format_arg_store<F, Args...> &store)
: types_(store.TYPES) {
set_data(store.data());
}
@ -1525,6 +1526,9 @@ class format_args {
}
};
typedef basic_format_args<BasicFormatter<char>> format_args;
typedef basic_format_args<BasicFormatter<wchar_t>> wformat_args;
#define FMT_DISPATCH(call) static_cast<Impl*>(this)->call
/**
@ -1919,7 +1923,8 @@ class ArgMap {
MapType map_;
public:
FMT_API void init(const format_args &args);
template <typename Formatter>
void init(const basic_format_args<Formatter> &args);
const internal::Arg* find(const fmt::BasicStringRef<Char> &name) const {
// The list is unsorted, so just return the first matching name.
@ -1932,6 +1937,52 @@ class ArgMap {
}
};
template <typename Char>
template <typename Formatter>
void ArgMap<Char>::init(const basic_format_args<Formatter> &args) {
if (!map_.empty())
return;
typedef internal::NamedArg<Char> NamedArg;
const NamedArg *named_arg = 0;
bool use_values =
args.type(MAX_PACKED_ARGS - 1) == internal::Arg::NONE;
if (use_values) {
for (unsigned i = 0;/*nothing*/; ++i) {
internal::Arg::Type arg_type = args.type(i);
switch (arg_type) {
case internal::Arg::NONE:
return;
case internal::Arg::NAMED_ARG:
named_arg = static_cast<const NamedArg*>(args.values_[i].pointer);
map_.push_back(Pair(named_arg->name, *named_arg));
break;
default:
/*nothing*/;
}
}
return;
}
for (unsigned i = 0; i != MAX_PACKED_ARGS; ++i) {
internal::Arg::Type arg_type = args.type(i);
if (arg_type == internal::Arg::NAMED_ARG) {
named_arg = static_cast<const NamedArg*>(args.args_[i].pointer);
map_.push_back(Pair(named_arg->name, *named_arg));
}
}
for (unsigned i = MAX_PACKED_ARGS;/*nothing*/; ++i) {
switch (args.args_[i].type) {
case internal::Arg::NONE:
return;
case internal::Arg::NAMED_ARG:
named_arg = static_cast<const NamedArg*>(args.args_[i].pointer);
map_.push_back(Pair(named_arg->name, *named_arg));
break;
default:
/*nothing*/;
}
}
}
template <typename Impl, typename Char>
class ArgFormatterBase : public ArgVisitor<Impl, void> {
private:
@ -2030,21 +2081,33 @@ class ArgFormatterBase : public ArgVisitor<Impl, void> {
}
};
template <typename Formatter>
class FormatterBase {
private:
format_args args_;
private:
basic_format_args<Formatter> args_;
int next_arg_index_;
// Returns the argument with specified index.
FMT_API Arg do_get_arg(unsigned arg_index, const char *&error);
Arg do_get_arg(unsigned arg_index, const char *&error) {
Arg arg = args_[arg_index];
switch (arg.type) {
case Arg::NONE:
error = "argument index out of range";
break;
case Arg::NAMED_ARG:
arg = *static_cast<const internal::Arg*>(arg.pointer);
break;
default:
/*nothing*/;
}
return arg;
}
protected:
const format_args &args() const { return args_; }
FormatterBase(basic_format_args<Formatter> args)
: args_(args), next_arg_index_(0) {}
explicit FormatterBase(const format_args &args) {
args_ = args;
next_arg_index_ = 0;
}
const basic_format_args<Formatter> &args() const { return args_; }
// Returns the next argument.
Arg next_arg(const char *&error) {
@ -2132,7 +2195,8 @@ class ArgFormatter : public BasicArgFormatter<ArgFormatter<Char>, Char> {
/** This template formats data and writes the output to a writer. */
template <typename CharType, typename ArgFormatter>
class BasicFormatter : private internal::FormatterBase {
class BasicFormatter :
private internal::FormatterBase<BasicFormatter<CharType, ArgFormatter>> {
public:
/** The character type for the output. */
typedef CharType Char;
@ -2143,7 +2207,8 @@ class BasicFormatter : private internal::FormatterBase {
FMT_DISALLOW_COPY_AND_ASSIGN(BasicFormatter);
using internal::FormatterBase::get_arg;
typedef internal::FormatterBase<BasicFormatter> Base;
using Base::get_arg;
// Checks if manual indexing is used and returns the argument with
// specified name.
@ -2163,8 +2228,8 @@ class BasicFormatter : private internal::FormatterBase {
appropriate lifetimes.
\endrst
*/
BasicFormatter(const format_args &args, BasicWriter<Char> &w)
: internal::FormatterBase(args), writer_(w) {}
BasicFormatter(basic_format_args<BasicFormatter> args, BasicWriter<Char> &w)
: Base(args), writer_(w) {}
/** Returns a reference to the writer associated with this formatter. */
BasicWriter<Char> &writer() { return writer_; }
@ -2404,7 +2469,8 @@ class BasicWriter {
return std::basic_string<Char>(&buffer_[0], buffer_.size());
}
void vwrite(BasicCStringRef<Char> format, format_args args) {
void vwrite(BasicCStringRef<Char> format,
basic_format_args<BasicFormatter<Char>> args) {
BasicFormatter<Char>(args, *this).format(format);
}
@ -3111,7 +3177,7 @@ inline std::string format(CStringRef format_str, const Args & ... args) {
return vformat(format_str, make_format_args<BasicFormatter<char>>(args...));
}
inline std::wstring vformat(WCStringRef format_str, format_args args) {
inline std::wstring vformat(WCStringRef format_str, wformat_args args) {
WMemoryWriter w;
w.vwrite(format_str, args);
return w.str();
@ -3348,8 +3414,8 @@ void check_sign(const Char *&s, const Arg &arg) {
template <typename Char, typename AF>
inline internal::Arg BasicFormatter<Char, AF>::get_arg(
BasicStringRef<Char> arg_name, const char *&error) {
if (check_no_auto_index(error)) {
map_.init(args());
if (this->check_no_auto_index(error)) {
map_.init(this->args());
const internal::Arg *arg = map_.find(arg_name);
if (arg)
return *arg;
@ -3362,7 +3428,7 @@ template <typename Char, typename AF>
inline internal::Arg BasicFormatter<Char, AF>::parse_arg_index(const Char *&s) {
const char *error = 0;
internal::Arg arg = *s < '0' || *s > '9' ?
next_arg(error) : get_arg(internal::parse_nonnegative_int(s), error);
this->next_arg(error) : get_arg(internal::parse_nonnegative_int(s), error);
if (error) {
FMT_THROW(format_error(
*s != '}' && *s != ':' ? "invalid format string" : error));
@ -3563,18 +3629,18 @@ void BasicFormatter<Char, AF>::format(BasicCStringRef<Char> format_str) {
Char c = *s++;
if (c != '{' && c != '}') continue;
if (*s == c) {
write(writer_, start, s);
this->write(writer_, start, s);
start = ++s;
continue;
}
if (c == '}')
FMT_THROW(format_error("unmatched '}' in format string"));
write(writer_, start, s - 1);
this->write(writer_, start, s - 1);
internal::Arg arg = internal::is_name_start(*s) ?
parse_arg_name(s) : parse_arg_index(s);
start = s = format(s, arg);
}
write(writer_, start, s);
this->write(writer_, start, s);
}
} // namespace fmt

View File

@ -262,7 +262,8 @@ class BasicPrintfArgFormatter : public internal::ArgFormatterBase<Impl, Char> {
/** Formats an argument of a custom (user-defined) type. */
void visit_custom(internal::Arg::CustomValue c) {
BasicFormatter<Char> formatter(format_args(), this->writer());
BasicFormatter<Char> formatter(basic_format_args<BasicFormatter<Char>>(),
this->writer());
const Char format_str[] = {'}', 0};
const Char *format = format_str;
c.format(&formatter, c.value, &format);
@ -281,10 +282,13 @@ class PrintfArgFormatter
/** This template formats data and writes the output to a writer. */
template <typename Char, typename ArgFormatter = PrintfArgFormatter<Char> >
class PrintfFormatter : private internal::FormatterBase {
class PrintfFormatter :
private internal::FormatterBase<PrintfFormatter<Char, ArgFormatter>> {
private:
BasicWriter<Char> &writer_;
typedef internal::FormatterBase<PrintfFormatter> Base;
void parse_flags(FormatSpec &spec, const Char *&s);
// Returns the argument with specified index or, if arg_index is equal
@ -304,8 +308,9 @@ class PrintfFormatter : private internal::FormatterBase {
appropriate lifetimes.
\endrst
*/
explicit PrintfFormatter(const format_args &args, BasicWriter<Char> &w)
: FormatterBase(args), writer_(w) {}
explicit PrintfFormatter(basic_format_args<PrintfFormatter> args,
BasicWriter<Char> &w)
: Base(args), writer_(w) {}
/** Formats stored arguments and writes the output to the writer. */
FMT_API void format(BasicCStringRef<Char> format_str);
@ -343,7 +348,7 @@ internal::Arg PrintfFormatter<Char, AF>::get_arg(const Char *s,
(void)s;
const char *error = 0;
internal::Arg arg = arg_index == std::numeric_limits<unsigned>::max() ?
next_arg(error) : FormatterBase::get_arg(arg_index - 1, error);
this->next_arg(error) : Base::get_arg(arg_index - 1, error);
if (error)
FMT_THROW(format_error(!*s ? "invalid format string" : error));
return arg;
@ -391,11 +396,11 @@ void PrintfFormatter<Char, AF>::format(BasicCStringRef<Char> format_str) {
Char c = *s++;
if (c != '%') continue;
if (*s == c) {
write(writer_, start, s);
this->write(writer_, start, s);
start = ++s;
continue;
}
write(writer_, start, s - 1);
this->write(writer_, start, s - 1);
FormatSpec spec;
spec.align_ = ALIGN_RIGHT;
@ -480,16 +485,17 @@ void PrintfFormatter<Char, AF>::format(BasicCStringRef<Char> format_str) {
// Format argument.
AF(writer_, spec).visit(arg);
}
write(writer_, start, s);
this->write(writer_, start, s);
}
template <typename Char>
void printf(BasicWriter<Char> &w, BasicCStringRef<Char> format,
format_args args) {
basic_format_args<PrintfFormatter<Char>> args) {
PrintfFormatter<Char>(args, w).format(format);
}
inline std::string vsprintf(CStringRef format, format_args args) {
inline std::string vsprintf(CStringRef format,
basic_format_args<PrintfFormatter<char>> args) {
MemoryWriter w;
printf(w, format, args);
return w.str();
@ -509,7 +515,8 @@ inline std::string sprintf(CStringRef format_str, const Args & ... args) {
return vsprintf(format_str, make_format_args<BasicFormatter<char>>(args...));
}
inline std::wstring vsprintf(WCStringRef format, format_args args) {
inline std::wstring vsprintf(WCStringRef format,
basic_format_args<PrintfFormatter<wchar_t>> args) {
WMemoryWriter w;
printf(w, format, args);
return w.str();
@ -521,7 +528,8 @@ inline std::wstring sprintf(WCStringRef format_str, const Args & ... args) {
return vsprintf(format_str, vargs);
}
FMT_API int vfprintf(std::FILE *f, CStringRef format, format_args args);
FMT_API int vfprintf(std::FILE *f, CStringRef format,
basic_format_args<PrintfFormatter<char>> args);
/**
\rst
@ -534,11 +542,12 @@ FMT_API int vfprintf(std::FILE *f, CStringRef format, format_args args);
*/
template <typename... Args>
inline int fprintf(std::FILE *f, CStringRef format_str, const Args & ... args) {
auto vargs = make_format_args<BasicFormatter<char>>(args...);
auto vargs = make_format_args<PrintfFormatter<char>>(args...);
return vfprintf(f, format_str, vargs);
}
inline int vprintf(CStringRef format, format_args args) {
inline int vprintf(CStringRef format,
basic_format_args<PrintfFormatter<char>> args) {
return vfprintf(stdout, format, args);
}
@ -556,7 +565,8 @@ inline int printf(CStringRef format_str, const Args & ... args) {
return vprintf(format_str, make_format_args<BasicFormatter<char>>(args...));
}
inline int vfprintf(std::ostream &os, CStringRef format_str, format_args args) {
inline int vfprintf(std::ostream &os, CStringRef format_str,
basic_format_args<PrintfFormatter<char>> args) {
MemoryWriter w;
printf(w, format_str, args);
internal::write(os, w);

View File

@ -45,10 +45,13 @@ class CustomPrintfArgFormatter :
}
};
std::string custom_vformat(const char *format_str, fmt::format_args args) {
typedef fmt::BasicFormatter<char, CustomArgFormatter> CustomFormatter;
std::string custom_vformat(const char *format_str,
fmt::basic_format_args<CustomFormatter> args) {
fmt::MemoryWriter writer;
// Pass custom argument formatter as a template arg to BasicFormatter.
fmt::BasicFormatter<char, CustomArgFormatter> formatter(args, writer);
CustomFormatter formatter(args, writer);
formatter.format(format_str);
return writer.str();
}
@ -59,9 +62,14 @@ std::string custom_format(const char *format_str, const Args & ... args) {
return custom_vformat(format_str, va);
}
std::string custom_vsprintf(const char* format_str, fmt::format_args args) {
typedef fmt::PrintfFormatter<char, CustomPrintfArgFormatter>
CustomPrintfFormatter;
std::string custom_vsprintf(
const char* format_str,
fmt::basic_format_args<CustomPrintfFormatter> args) {
fmt::MemoryWriter writer;
fmt::PrintfFormatter<char, CustomPrintfArgFormatter> formatter(args, writer);
CustomPrintfFormatter formatter(args, writer);
formatter.format(format_str);
return writer.str();
}

View File

@ -1633,16 +1633,19 @@ class MockArgFormatter :
MOCK_METHOD1(visit_int, void (int value));
};
void vcustom_format(const char *format_str, fmt::format_args args) {
typedef fmt::BasicFormatter<char, MockArgFormatter> CustomFormatter;
void custom_vformat(const char *format_str,
fmt::basic_format_args<CustomFormatter> args) {
fmt::MemoryWriter writer;
fmt::BasicFormatter<char, MockArgFormatter> formatter(args, writer);
CustomFormatter formatter(args, writer);
formatter.format(format_str);
}
template <typename... Args>
void custom_format(const char *format_str, const Args & ... args) {
auto va = fmt::make_format_args<fmt::BasicFormatter<char>>(args...);
return vcustom_format(format_str, va);
return custom_vformat(format_str, va);
}
TEST(FormatTest, CustomArgFormatter) {

View File

@ -67,7 +67,7 @@ struct TestArgFormatter : fmt::BasicArgFormatter<TestArgFormatter, char> {
TEST(OStreamTest, CustomArg) {
fmt::MemoryWriter writer;
typedef fmt::BasicFormatter<char, TestArgFormatter> Formatter;
Formatter formatter(fmt::format_args(), writer);
Formatter formatter(fmt::basic_format_args<Formatter>(), writer);
fmt::FormatSpec spec;
TestArgFormatter af(formatter, spec, "}");
af.visit(fmt::internal::MakeArg<Formatter>(TestEnum()));