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:
parent
9e2490be4c
commit
c970d4bc21
@ -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; }
|
||||||
|
|||||||
@ -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()); }
|
||||||
|
|||||||
@ -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) {}
|
||||||
|
|||||||
@ -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");
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user