diff --git a/format.cc b/format.cc index 78b54d65..38ef44bb 100644 --- a/format.cc +++ b/format.cc @@ -28,6 +28,16 @@ void ReportError(const char *s, const std::string &message) { throw fmt::FormatError(*s ? message : std::string("unmatched '{' in format")); } +void ReportUnknownType(char code, const char *type) { + if (std::isprint(code)) { + throw fmt::FormatError( + str(fmt::Format("unknown format code '{0}' for {1}") << code << type)); + } + throw fmt::FormatError( + str(fmt::Format("unknown format code '\\x{0:02x}' for {1}") + << static_cast(code) << type)); +} + // Parses an unsigned integer advancing s to the end of the parsed input. // This function assumes that the first character of s is a digit. unsigned ParseUInt(const char *&s) { @@ -141,8 +151,8 @@ void fmt::Formatter::FormatInt(T value, unsigned flags, int width, char type) { break; } default: - throw FormatError( - str(fmt::Format("unknown format code '{0}' for integer") << type)); + ReportUnknownType(type, "integer"); + break; } if (sign) { if ((flags & ZERO_FLAG) != 0) @@ -163,8 +173,8 @@ void fmt::Formatter::FormatDouble( case 'e': case 'E': case 'f': case 'F': case 'g': case 'G': break; default: - throw FormatError( - str(fmt::Format("unknown format code '{0}' for double") << type)); + ReportUnknownType(type, "double"); + break; } // Build format string. @@ -306,13 +316,16 @@ void fmt::Formatter::Format() { FormatDouble(arg.long_double_value, flags, width, precision, type); break; case CHAR: - // TODO: check if type is 'c' or none + if (type && type != 'c') + ReportUnknownType(type, "char"); buffer_.reserve(std::max(width, 1)); buffer_.push_back(arg.int_value); if (width > 1) buffer_.resize(buffer_.size() + width - 1, ' '); break; case STRING: { + if (type && type != 's') + ReportUnknownType(type, "string"); const char *str = arg.string_value; size_t size = arg.size; if (size == 0 && *str) @@ -324,12 +337,14 @@ void fmt::Formatter::Format() { break; } case POINTER: - // TODO: don't allow type specifiers other than 'p' + if (type && type != 'p') + ReportUnknownType(type, "pointer"); FormatInt(reinterpret_cast( arg.pointer_value), HEX_PREFIX_FLAG, width, 'x'); break; case CUSTOM: - // TODO: check if type is 's' or none + if (type) + ReportUnknownType(type, "object"); (this->*arg.format)(arg.custom_value, width); break; default: diff --git a/format_test.cc b/format_test.cc index 6004ffc6..f173c776 100644 --- a/format_test.cc +++ b/format_test.cc @@ -42,7 +42,7 @@ using fmt::FormatError; } else if (actual_message != message) {\ gtest_msg.value = \ "Expected: " #statement " throws an exception of type " \ - #expected_exception " with message \"" message "\"."; \ + #expected_exception "."; \ goto GTEST_CONCAT_TOKEN_(gtest_label_testthrow_, __LINE__); \ } \ } else \ @@ -258,19 +258,27 @@ TEST(FormatterTest, Precision) { FormatError, "precision specifier requires floating-point argument"); } +template +void CheckUnknownTypes( + const T &value, const char *types, const char *type_name) { + char format[256], message[256]; + const char *special = ".0123456789}"; + for (int c = CHAR_MIN; c <= CHAR_MAX; ++c) { + if (std::strchr(types, c) || std::strchr(special, c) || !c) continue; + sprintf(format, "{0:1%c}", c, type_name); + if (std::isprint(c)) + sprintf(message, "unknown format code '%c' for %s", c, type_name); + else + sprintf(message, "unknown format code '\\x%02x' for %s", c, type_name); + EXPECT_THROW_MSG(Format(format) << value, FormatError, message) + << format << " " << message; + } +} + TEST(FormatterTest, FormatInt) { EXPECT_THROW_MSG(Format("{0:v") << 42, FormatError, "unmatched '{' in format"); - EXPECT_THROW_MSG(Format("{0:v}") << 42, - FormatError, "unknown format code 'v' for integer"); - EXPECT_THROW_MSG(Format("{0:c}") << 42, - FormatError, "unknown format code 'c' for integer"); - EXPECT_THROW_MSG(Format("{0:e}") << 42, - FormatError, "unknown format code 'e' for integer"); - EXPECT_THROW_MSG(Format("{0:f}") << 42, - FormatError, "unknown format code 'f' for integer"); - EXPECT_THROW_MSG(Format("{0:g}") << 42, - FormatError, "unknown format code 'g' for integer"); + CheckUnknownTypes(42, "doxX", "integer"); } TEST(FormatterTest, FormatDec) { @@ -342,14 +350,7 @@ TEST(FormatterTest, FormatOct) { } TEST(FormatterTest, FormatDouble) { - EXPECT_THROW_MSG(Format("{0:c}") << 1.2, - FormatError, "unknown format code 'c' for double"); - EXPECT_THROW_MSG(Format("{0:d}") << 1.2, - FormatError, "unknown format code 'd' for double"); - EXPECT_THROW_MSG(Format("{0:o}") << 1.2, - FormatError, "unknown format code 'o' for double"); - EXPECT_THROW_MSG(Format("{0:x}") << 1.2, - FormatError, "unknown format code 'x' for double"); + CheckUnknownTypes(1.2, "eEfFgG", "double"); EXPECT_EQ("0", str(Format("{0:}") << 0.0)); EXPECT_EQ("0.000000", str(Format("{0:f}") << 0.0)); EXPECT_EQ("392.65", str(Format("{0:}") << 392.65)); @@ -376,17 +377,28 @@ TEST(FormatterTest, FormatLongDouble) { } TEST(FormatterTest, FormatChar) { - EXPECT_EQ("a*b", str(Format("{0}{1}{2}") << 'a' << '*' << 'b')); + CheckUnknownTypes('a', "c", "char"); + EXPECT_EQ("a", str(Format("{0}") << 'a')); + EXPECT_EQ("z", str(Format("{0:c}") << 'z')); +} + +TEST(FormatterTest, FormatCString) { + CheckUnknownTypes("test", "s", "string"); + EXPECT_EQ("test", str(Format("{0}") << "test")); + EXPECT_EQ("test", str(Format("{0:s}") << "test")); +} + +TEST(FormatterTest, FormatPointer) { + CheckUnknownTypes(reinterpret_cast(0x1234), "p", "pointer"); + EXPECT_EQ("0x0", str(Format("{0}") << reinterpret_cast(0))); + EXPECT_EQ("0x1234", str(Format("{0}") << reinterpret_cast(0x1234))); + EXPECT_EQ("0x1234", str(Format("{0:p}") << reinterpret_cast(0x1234))); } TEST(FormatterTest, FormatString) { EXPECT_EQ("test", str(Format("{0}") << std::string("test"))); } -TEST(FormatterTest, FormatPointer) { - EXPECT_EQ("0x0", str(Format("{0}") << reinterpret_cast(0))); -} - class Date { int year_, month_, day_; public: @@ -398,10 +410,12 @@ class Date { } }; -TEST(FormatterTest, FormatCustomArg) { +TEST(FormatterTest, FormatCustom) { EXPECT_EQ("a string", str(Format("{0}") << TestString("a string"))); std::string s = str(fmt::Format("The date is {0}") << Date(2012, 12, 9)); EXPECT_EQ("The date is 2012-12-9", s); + Date date(2012, 12, 9); + CheckUnknownTypes(date, "", "object"); } TEST(FormatterTest, FormatStringFromSpeedTest) {