Add compile-time check for too many arguments:

* Check only for automatic argument indexing.
* Tests not included as I am not sure how compile failures should be tested (I haven't seen any death test in the repo).

All the following lines fail to compile after applying this patch:
```
    fmt::print(FMT_STRING(""), 1); // too many
    fmt::print(FMT_STRING("too many\n"), 1);
    fmt::print(FMT_STRING("too many {}\n"), 1, 2);
    fmt::print(FMT_STRING("too many {}\n"), 1, 2, 3);
    fmt::print(FMT_STRING("too few {}-{}-{}bal\n"), 1, 2);
```

Tested on GCC and clang (https://wandbox.org/permlink/f7Pf9ERBskTWfFQx).
This commit is contained in:
Marek Kurdej 2019-08-30 15:38:37 +02:00
parent 9e2490be4c
commit c970d4bc21
4 changed files with 33 additions and 5 deletions

View File

@ -9,6 +9,7 @@
#define FMT_COMPILE_H_ #define FMT_COMPILE_H_
#include <vector> #include <vector>
#include "format.h" #include "format.h"
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
@ -109,6 +110,8 @@ class format_preparation_handler : public internal::error_handler {
parts_.push_back(part(string_view_metadata(offset, size))); parts_.push_back(part(string_view_metadata(offset, size)));
} }
FMT_CONSTEXPR void on_text_end() {}
FMT_CONSTEXPR void on_arg_id() { FMT_CONSTEXPR void on_arg_id() {
parts_.push_back(part(parse_context_.next_arg_id())); parts_.push_back(part(parse_context_.next_arg_id()));
} }
@ -267,6 +270,8 @@ template <typename Char> struct part_counter {
if (begin != end) ++num_parts; if (begin != end) ++num_parts;
} }
FMT_CONSTEXPR void on_text_end() {}
FMT_CONSTEXPR void on_arg_id() { ++num_parts; } FMT_CONSTEXPR void on_arg_id() { ++num_parts; }
FMT_CONSTEXPR void on_arg_id(unsigned) { ++num_parts; } FMT_CONSTEXPR void on_arg_id(unsigned) { ++num_parts; }
FMT_CONSTEXPR void on_arg_id(basic_string_view<Char>) { ++num_parts; } FMT_CONSTEXPR void on_arg_id(basic_string_view<Char>) { ++num_parts; }

View File

@ -2461,8 +2461,10 @@ FMT_CONSTEXPR void parse_format_string(basic_string_view<Char> format_str,
// Doing two passes with memchr (one for '{' and another for '}') is up to // Doing two passes with memchr (one for '{' and another for '}') is up to
// 2.5x faster than the naive one-pass implementation on big format strings. // 2.5x faster than the naive one-pass implementation on big format strings.
const Char* p = begin; const Char* p = begin;
if (*begin != '{' && !find<IS_CONSTEXPR>(begin, end, '{', p)) if (*begin != '{' && !find<IS_CONSTEXPR>(begin, end, '{', p)) {
handler.on_text_end();
return write(begin, end); return write(begin, end);
}
write(begin, p); write(begin, p);
++p; ++p;
if (p == end) return handler.on_error("invalid format string"); if (p == end) return handler.on_error("invalid format string");
@ -2486,6 +2488,7 @@ FMT_CONSTEXPR void parse_format_string(basic_string_view<Char> format_str,
} }
begin = p + 1; begin = p + 1;
} }
handler.on_text_end(); // Handle empty format strings.
} }
template <typename T, typename ParseContext> template <typename T, typename ParseContext>
@ -2510,17 +2513,31 @@ class format_string_checker {
explicit FMT_CONSTEXPR format_string_checker( explicit FMT_CONSTEXPR format_string_checker(
basic_string_view<Char> format_str, ErrorHandler eh) basic_string_view<Char> format_str, ErrorHandler eh)
: arg_id_((std::numeric_limits<unsigned>::max)()), : arg_id_((std::numeric_limits<unsigned>::max)()),
max_arg_id_((std::numeric_limits<unsigned>::min)()),
context_(format_str, eh), context_(format_str, eh),
parse_funcs_{&parse_format_specs<Args, parse_context_type>...} {} parse_funcs_{&parse_format_specs<Args, parse_context_type>...} {}
FMT_CONSTEXPR void on_text(const Char*, const Char*) {} FMT_CONSTEXPR void on_text(const Char*, const Char*) {}
FMT_CONSTEXPR void on_text_end() {
// Do not check when using manual indexing.
if (max_arg_id_ == (std::numeric_limits<unsigned>::max)()) return;
if (((arg_id_ == (std::numeric_limits<unsigned>::max)()) &&
(num_args > 0)) || // No argument used.
(num_args > max_arg_id_ + 1)) // More arguments given than used.
return on_error("too many arguments");
}
FMT_CONSTEXPR void on_arg_id() { FMT_CONSTEXPR void on_arg_id() {
arg_id_ = context_.next_arg_id(); arg_id_ = context_.next_arg_id();
max_arg_id_ = ((arg_id_ > max_arg_id_) ? arg_id_ : max_arg_id_);
check_arg_id(); check_arg_id();
} }
FMT_CONSTEXPR void on_arg_id(int id) { FMT_CONSTEXPR void on_arg_id(int id) {
arg_id_ = id; arg_id_ = id;
// Do not check for too many arguments when using manual argument indexing.
max_arg_id_ = (std::numeric_limits<unsigned>::max)();
context_.check_arg_id(id); context_.check_arg_id(id);
check_arg_id(); check_arg_id();
} }
@ -2551,6 +2568,7 @@ class format_string_checker {
using parse_func = const Char* (*)(parse_context_type&); using parse_func = const Char* (*)(parse_context_type&);
unsigned arg_id_; unsigned arg_id_;
unsigned max_arg_id_;
parse_context_type context_; parse_context_type context_;
parse_func parse_funcs_[num_args > 0 ? num_args : 1]; parse_func parse_funcs_[num_args > 0 ? num_args : 1];
}; };
@ -3164,6 +3182,8 @@ struct format_handler : internal::error_handler {
context.advance_to(out); context.advance_to(out);
} }
void on_text_end() {}
void get_arg(int id) { arg = internal::get_arg(context, id); } void get_arg(int id) { arg = internal::get_arg(context, id); }
void on_arg_id() { get_arg(parse_context.next_arg_id()); } void on_arg_id() { get_arg(parse_context.next_arg_id()); }

View File

@ -6,6 +6,7 @@
// For the license information refer to format.h. // For the license information refer to format.h.
#include <stdint.h> #include <stdint.h>
#include <cctype> #include <cctype>
#include <cfloat> #include <cfloat>
#include <climits> #include <climits>
@ -34,12 +35,12 @@
using std::size_t; using std::size_t;
using fmt::basic_memory_buffer; using fmt::basic_memory_buffer;
using fmt::internal::basic_writer;
using fmt::format; using fmt::format;
using fmt::format_error; using fmt::format_error;
using fmt::memory_buffer; using fmt::memory_buffer;
using fmt::string_view; using fmt::string_view;
using fmt::wmemory_buffer; using fmt::wmemory_buffer;
using fmt::internal::basic_writer;
using testing::Return; using testing::Return;
using testing::StrictMock; using testing::StrictMock;
@ -673,9 +674,7 @@ TEST(WriterTest, WriteString) {
CHECK_WRITE_WCHAR("abc"); CHECK_WRITE_WCHAR("abc");
} }
TEST(WriterTest, WriteWideString) { TEST(WriterTest, WriteWideString) { CHECK_WRITE_WCHAR(L"abc"); }
CHECK_WRITE_WCHAR(L"abc");
}
TEST(FormatToTest, FormatWithoutArgs) { TEST(FormatToTest, FormatWithoutArgs) {
std::string s; std::string s;
@ -2295,6 +2294,8 @@ TEST(FormatTest, ConstexprSpecsChecker) {
struct test_format_string_handler { struct test_format_string_handler {
FMT_CONSTEXPR void on_text(const char*, const char*) {} FMT_CONSTEXPR void on_text(const char*, const char*) {}
FMT_CONSTEXPR void on_text_end() {}
FMT_CONSTEXPR void on_arg_id() {} FMT_CONSTEXPR void on_arg_id() {}
template <typename T> FMT_CONSTEXPR void on_arg_id(T) {} template <typename T> FMT_CONSTEXPR void on_arg_id(T) {}

View File

@ -168,6 +168,8 @@ struct scan_handler : error_handler {
scan_ctx_.advance_to(it + size); scan_ctx_.advance_to(it + size);
} }
void on_text_end() {}
void on_arg_id() { on_arg_id(next_arg_id_++); } void on_arg_id() { on_arg_id(next_arg_id_++); }
void on_arg_id(int id) { void on_arg_id(int id) {
if (id >= args_.size) on_error("argument index out of range"); if (id >= args_.size) on_error("argument index out of range");