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_
#include <vector>
#include "format.h"
FMT_BEGIN_NAMESPACE
@ -109,6 +110,8 @@ class format_preparation_handler : public internal::error_handler {
parts_.push_back(part(string_view_metadata(offset, size)));
}
FMT_CONSTEXPR void on_text_end() {}
FMT_CONSTEXPR void on_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;
}
FMT_CONSTEXPR void on_text_end() {}
FMT_CONSTEXPR void on_arg_id() { ++num_parts; }
FMT_CONSTEXPR void on_arg_id(unsigned) { ++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
// 2.5x faster than the naive one-pass implementation on big format strings.
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);
}
write(begin, p);
++p;
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;
}
handler.on_text_end(); // Handle empty format strings.
}
template <typename T, typename ParseContext>
@ -2510,17 +2513,31 @@ class format_string_checker {
explicit FMT_CONSTEXPR format_string_checker(
basic_string_view<Char> format_str, ErrorHandler eh)
: arg_id_((std::numeric_limits<unsigned>::max)()),
max_arg_id_((std::numeric_limits<unsigned>::min)()),
context_(format_str, eh),
parse_funcs_{&parse_format_specs<Args, parse_context_type>...} {}
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() {
arg_id_ = context_.next_arg_id();
max_arg_id_ = ((arg_id_ > max_arg_id_) ? arg_id_ : max_arg_id_);
check_arg_id();
}
FMT_CONSTEXPR void on_arg_id(int 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);
check_arg_id();
}
@ -2551,6 +2568,7 @@ class format_string_checker {
using parse_func = const Char* (*)(parse_context_type&);
unsigned arg_id_;
unsigned max_arg_id_;
parse_context_type context_;
parse_func parse_funcs_[num_args > 0 ? num_args : 1];
};
@ -3164,6 +3182,8 @@ struct format_handler : internal::error_handler {
context.advance_to(out);
}
void on_text_end() {}
void get_arg(int id) { arg = internal::get_arg(context, 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.
#include <stdint.h>
#include <cctype>
#include <cfloat>
#include <climits>
@ -34,12 +35,12 @@
using std::size_t;
using fmt::basic_memory_buffer;
using fmt::internal::basic_writer;
using fmt::format;
using fmt::format_error;
using fmt::memory_buffer;
using fmt::string_view;
using fmt::wmemory_buffer;
using fmt::internal::basic_writer;
using testing::Return;
using testing::StrictMock;
@ -673,9 +674,7 @@ TEST(WriterTest, WriteString) {
CHECK_WRITE_WCHAR("abc");
}
TEST(WriterTest, WriteWideString) {
CHECK_WRITE_WCHAR(L"abc");
}
TEST(WriterTest, WriteWideString) { CHECK_WRITE_WCHAR(L"abc"); }
TEST(FormatToTest, FormatWithoutArgs) {
std::string s;
@ -2295,6 +2294,8 @@ TEST(FormatTest, ConstexprSpecsChecker) {
struct test_format_string_handler {
FMT_CONSTEXPR void on_text(const char*, const char*) {}
FMT_CONSTEXPR void on_text_end() {}
FMT_CONSTEXPR void on_arg_id() {}
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);
}
void on_text_end() {}
void on_arg_id() { on_arg_id(next_arg_id_++); }
void on_arg_id(int id) {
if (id >= args_.size) on_error("argument index out of range");