Skip to content

Commit

Permalink
Add compile-time checking of string_view and regex_matches sources
Browse files Browse the repository at this point in the history
  • Loading branch information
eliaskosunen committed Jan 15, 2024
1 parent 46dcc69 commit 43d3be8
Show file tree
Hide file tree
Showing 11 changed files with 169 additions and 52 deletions.
94 changes: 65 additions & 29 deletions include/scn/detail/format_string.h
Original file line number Diff line number Diff line change
Expand Up @@ -397,7 +397,14 @@ template <typename Str>
inline constexpr bool is_compile_string_v =
std::is_base_of_v<compile_string, Str>;

template <typename T, typename Ctx, typename ParseCtx>
template <typename Scanner, typename = void>
inline constexpr bool scanner_has_format_specs_member_v = false;
template <typename Scanner>
inline constexpr bool scanner_has_format_specs_member_v<
Scanner,
std::void_t<decltype(SCN_DECLVAL(Scanner&)._format_specs())>> = true;

template <typename T, typename Source, typename Ctx, typename ParseCtx>
constexpr typename ParseCtx::iterator parse_format_specs(ParseCtx& parse_ctx)
{
using char_type = typename ParseCtx::char_type;
Expand All @@ -406,25 +413,41 @@ constexpr typename ParseCtx::iterator parse_format_specs(ParseCtx& parse_ctx)
std::remove_reference_t<decltype(arg_mapper<char_type>().map(
SCN_DECLVAL(T&)))>,
T>;
auto s = scanner<mapped_type, char_type>{};
return s.parse(parse_ctx)
.transform_error([&](scan_error err) constexpr {
parse_ctx.on_error(err.msg());
return err;
})
.value_or(parse_ctx.end());
auto s = typename Ctx::template scanner_type<mapped_type>{};
auto it = s.parse(parse_ctx)
.transform_error([&](scan_error err) constexpr {
parse_ctx.on_error(err.msg());
return err;
})
.value_or(parse_ctx.end());
if constexpr (scanner_has_format_specs_member_v<decltype(s)>) {
auto& specs = s._format_specs();
if ((specs.type == presentation_type::regex ||
specs.type == presentation_type::regex_escaped) &&
!(ranges::range<Source> && ranges::contiguous_range<Source>)) {
// clang-format off
parse_ctx.on_error("Cannot read a regex from a non-contiguous "
"source");
// clang-format on
}
}
return it;
}

template <typename CharT, typename... Args>
template <typename CharT, typename Source, typename... Args>
class format_string_checker {
public:
using parse_context_type = compile_parse_context<CharT>;
static constexpr auto num_args = sizeof...(Args);

explicit constexpr format_string_checker(
std::basic_string_view<CharT> format_str)
: m_parse_context(format_str, num_args, m_types),
: m_parse_context(format_str,
num_args,
m_types,
type_identity<Source>{}),
m_parse_funcs{&parse_format_specs<Args,
Source,
basic_scan_context<CharT>,
parse_context_type>...},
m_types{arg_type_constant<Args, CharT>::value...}
Expand Down Expand Up @@ -473,12 +496,13 @@ class format_string_checker {

constexpr void on_replacement_field(size_t id, const CharT*)
{
m_parse_context.check_arg_can_be_read(id);
set_arg_as_read(id);

if (m_types[id] == arg_type::narrow_regex_matches_type ||
m_types[id] == arg_type::wide_regex_matches_type) {
// clang-format off
return on_error("Regular expression needs to specified "
return on_error("Regular expression needs to be specified "
"when reading regex_matches");
// clang-format on
}
Expand All @@ -488,6 +512,7 @@ class format_string_checker {
const CharT* begin,
const CharT*)
{
m_parse_context.check_arg_can_be_read(id);
set_arg_as_read(id);
m_parse_context.advance_to(begin);
return id < num_args ? m_parse_funcs[id](m_parse_context) : begin;
Expand Down Expand Up @@ -543,19 +568,19 @@ class format_string_checker {
bool m_visited_args[num_args > 0 ? num_args : 1] = {false};
};

template <typename... Args, typename Str>
template <typename Source, typename... Args, typename Str>
auto check_format_string(const Str&)
-> std::enable_if_t<!is_compile_string_v<Str>>
{
// TODO: SCN_ENFORE_COMPILE_STRING?
#if 0 // SCN_ENFORE_COMPILE_STRING
static_assert(dependent_false<Str>::value,
"SCN_ENFORCE_COMPILE_STRING requires all format "
"strings to use SCN_STRING.");
static_assert(dependent_false<Str>::value,
"SCN_ENFORCE_COMPILE_STRING requires all format "
"strings to use SCN_STRING.");
#endif
}

template <typename... Args, typename Str>
template <typename Source, typename... Args, typename Str>
auto check_format_string(Str format_str)
-> std::enable_if_t<is_compile_string_v<Str>>
{
Expand All @@ -566,7 +591,7 @@ auto check_format_string(Str format_str)
constexpr auto s = std::basic_string_view<char_type>{format_str};
SCN_GCC_POP

using checker = format_string_checker<char_type, remove_cvref_t<Args>...>;
using checker = format_string_checker<char_type, Source, Args...>;
constexpr bool invalid_format =
(parse_format_string<true>(s, checker(s)), true);
SCN_UNUSED(invalid_format);
Expand Down Expand Up @@ -606,41 +631,52 @@ constexpr std::basic_string_view<CharT> compile_string_to_view(
*
* \ingroup format-string
*/
template <typename CharT, typename... Args>
class basic_format_string {
template <typename CharT, typename Source, typename... Args>
class basic_scan_format_string {
public:
SCN_CLANG_PUSH
#if SCN_CLANG >= SCN_COMPILER(10, 0, 0)
SCN_CLANG_IGNORE("-Wc++20-compat") // false positive about consteval
#endif
template <
typename S,
typename = std::enable_if_t<
std::is_convertible_v<const S&, std::basic_string_view<CharT>>>>
SCN_CONSTEVAL basic_format_string(const S& s) : m_str(s)
std::enable_if_t<
std::is_convertible_v<const S&, std::basic_string_view<CharT>> &&
detail::is_not_self<S, basic_scan_format_string>>* = nullptr>
SCN_CONSTEVAL basic_scan_format_string(const S& s) : m_str(s)
{
#if SCN_HAS_CONSTEVAL
using checker =
detail::format_string_checker<CharT,
detail::remove_cvref_t<Args>...>;
using checker = detail::format_string_checker<CharT, Source, Args...>;
const auto e = detail::parse_format_string<true>(m_str, checker(s));
SCN_UNUSED(e);
#else
detail::check_format_string<Args...>(s);
detail::check_format_string<Source, Args...>(s);
#endif
}
SCN_CLANG_POP

basic_format_string(detail::basic_runtime_format_string<CharT> r)
template <
typename OtherSource,
std::enable_if_t<std::is_same_v<detail::remove_cvref_t<Source>,
detail::remove_cvref_t<OtherSource>> &&
ranges::borrowed_range<Source> ==
ranges::borrowed_range<OtherSource>>* = nullptr>
constexpr basic_scan_format_string(
const basic_scan_format_string<CharT, OtherSource, Args...>& other)
: m_str(other.get())
{
}

basic_scan_format_string(detail::basic_runtime_format_string<CharT> r)
: m_str(r.str)
{
}

operator std::basic_string_view<CharT>() const
constexpr operator std::basic_string_view<CharT>() const
{
return m_str;
}
std::basic_string_view<CharT> get() const
constexpr std::basic_string_view<CharT> get() const
{
return m_str;
}
Expand Down
46 changes: 45 additions & 1 deletion include/scn/detail/parse_context.h
Original file line number Diff line number Diff line change
Expand Up @@ -105,12 +105,21 @@ class compile_parse_context : public basic_scan_parse_context<CharT> {
using base = basic_scan_parse_context<CharT>;

public:
template <typename Source>
explicit constexpr compile_parse_context(
std::basic_string_view<CharT> format_str,
int num_args,
const arg_type* types,
type_identity<Source> source_tag,
int next_arg_id = 0)
: base(format_str, next_arg_id), m_num_args(num_args), m_types(types)
: base(format_str, next_arg_id),
m_num_args(num_args),
m_types(types),
m_is_contiguous(ranges::range<Source> &&
ranges::contiguous_range<Source>),
m_is_borrowed(
(ranges::range<Source> && ranges::borrowed_range<Source>) ||
std::is_same_v<detail::remove_cvref_t<Source>, std::FILE*>)
{
}

Expand Down Expand Up @@ -144,9 +153,44 @@ class compile_parse_context : public basic_scan_parse_context<CharT> {
}
using base::check_arg_id;

constexpr void check_arg_can_be_read(std::size_t id)
{
auto type = get_arg_type(id);

if ((type == arg_type::narrow_string_view_type ||
type == arg_type::wide_string_view_type) &&
!m_is_contiguous) {
// clang-format off
this->
on_error("Cannot read a string_view from a non-contiguous source");
// clang-format on
return;
}
if ((type == arg_type::narrow_string_view_type ||
type == arg_type::wide_string_view_type) &&
!m_is_borrowed) {
// clang-format off
this->
on_error("Cannot read a string_view from a non-borrowed source");
// clang-format on
return;
}

if ((type == arg_type::narrow_regex_matches_type ||
type == arg_type::wide_regex_matches_type) &&
!m_is_contiguous) {
// clang-format off
this->
on_error("Cannot read a regex_matches from a non-contiguous source");
// clang-format on
return;
}
}

private:
int m_num_args;
const arg_type* m_types;
bool m_is_contiguous, m_is_borrowed;

SCN_GCC_POP // -Wsign-conversion
};
Expand Down
14 changes: 8 additions & 6 deletions include/scn/detail/scan.h
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,8 @@ auto scan_impl(Source&& source,
template <typename... Args,
typename Source,
typename = std::enable_if_t<detail::is_file_or_narrow_range<Source>>>
SCN_NODISCARD auto scan(Source&& source, format_string<Args...> format)
SCN_NODISCARD auto scan(Source&& source,
scan_format_string<Source, Args...> format)
-> scan_result_type<Source, Args...>
{
return detail::scan_impl<char, Args...>(SCN_FWD(source), format, {});
Expand Down Expand Up @@ -138,7 +139,7 @@ template <typename... Args,
typename Source,
typename = std::enable_if_t<detail::is_file_or_narrow_range<Source>>>
SCN_NODISCARD auto scan(Source&& source,
format_string<Args...> format,
scan_format_string<Source, Args...> format,
std::tuple<Args...>&& default_args)
-> scan_result_type<Source, Args...>
{
Expand Down Expand Up @@ -194,7 +195,7 @@ template <typename... Args,
typename = std::void_t<decltype(Locale::classic())>>
SCN_NODISCARD auto scan(const Locale& loc,
Source&& source,
format_string<Args...> format)
scan_format_string<Source, Args...> format)
-> scan_result_type<Source, Args...>
{
return detail::scan_localized_impl<char, Args...>(loc, SCN_FWD(source),
Expand All @@ -213,7 +214,7 @@ template <typename... Args,
typename = std::void_t<decltype(Locale::classic())>>
SCN_NODISCARD auto scan(const Locale& loc,
Source&& source,
format_string<Args...> format,
scan_format_string<Source, Args...> format,
std::tuple<Args...>&& default_args)
-> scan_result_type<Source, Args...>
{
Expand Down Expand Up @@ -274,7 +275,7 @@ SCN_NODISCARD auto scan_value(Source&& source, T default_value)
* \ingroup scan
*/
template <typename... Args>
SCN_NODISCARD auto input(format_string<Args...> format)
SCN_NODISCARD auto input(scan_format_string<std::FILE*, Args...> format)
-> scan_result_type<std::FILE*, Args...>
{
auto args = make_scan_args<scan_context, Args...>();
Expand All @@ -291,7 +292,8 @@ SCN_NODISCARD auto input(format_string<Args...> format)
* \ingroup scan
*/
template <typename... Args>
SCN_NODISCARD auto prompt(const char* msg, format_string<Args...> format)
SCN_NODISCARD auto prompt(const char* msg,
scan_format_string<std::FILE*, Args...> format)
-> scan_result_type<std::FILE*, Args...>
{
std::printf("%s", msg);
Expand Down
4 changes: 4 additions & 0 deletions include/scn/detail/scanner.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ struct scanner<T,
return detail::scanner_scan_for_builtin_type(val, ctx, m_specs);
}

constexpr auto& _format_specs() {
return m_specs;
}

private:
detail::basic_format_specs<CharT> m_specs;
};
Expand Down
9 changes: 5 additions & 4 deletions include/scn/detail/xchar.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@ auto vscan_value(Range&& range, basic_scan_arg<wscan_context> arg)
template <typename... Args,
typename Source,
std::enable_if_t<detail::is_wide_range<Source>>* = nullptr>
SCN_NODISCARD auto scan(Source&& source, wformat_string<Args...> format)
SCN_NODISCARD auto scan(Source&& source,
wscan_format_string<Source, Args...> format)
-> scan_result_type<Source, Args...>
{
return detail::scan_impl<wchar_t, Args...>(SCN_FWD(source), format, {});
Expand All @@ -98,7 +99,7 @@ template <typename... Args,
typename Source,
std::enable_if_t<detail::is_wide_range<Source>>* = nullptr>
SCN_NODISCARD auto scan(Source&& source,
wformat_string<Args...> format,
wscan_format_string<Source, Args...> format,
std::tuple<Args...>&& args)
-> scan_result_type<Source, Args...>
{
Expand All @@ -118,7 +119,7 @@ template <typename... Args,
std::void_t<decltype(Locale::classic())>* = nullptr>
SCN_NODISCARD auto scan(const Locale& loc,
Source&& source,
wformat_string<Args...> format)
wscan_format_string<Source, Args...> format)
-> scan_result_type<Source, Args...>
{
return detail::scan_localized_impl<wchar_t, Args...>(loc, SCN_FWD(source),
Expand All @@ -137,7 +138,7 @@ template <typename... Args,
std::void_t<decltype(Locale::classic())>* = nullptr>
SCN_NODISCARD auto scan(const Locale& loc,
Source&& source,
wformat_string<Args...> format,
wscan_format_string<Source, Args...> format,
std::tuple<Args...>&& args)
-> scan_result_type<Source, Args...>
{
Expand Down
20 changes: 12 additions & 8 deletions include/scn/fwd.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@ class scan_error;

template <typename CharT>
struct basic_runtime_format_string;
template <typename CharT, typename... Args>
class basic_format_string;
template <typename CharT, typename Source, typename... Args>
class basic_scan_format_string;

namespace detail {
template <typename T>
Expand All @@ -79,12 +79,16 @@ template <typename T>
using type_identity_t = typename type_identity<T>::type;
} // namespace detail

template <typename... Args>
using format_string =
basic_format_string<char, detail::type_identity_t<Args>...>;
template <typename... Args>
using wformat_string =
basic_format_string<wchar_t, detail::type_identity_t<Args>...>;
template <typename Source, typename... Args>
using scan_format_string =
basic_scan_format_string<char,
detail::type_identity_t<Source>,
detail::type_identity_t<Args>...>;
template <typename Source, typename... Args>
using wscan_format_string =
basic_scan_format_string<wchar_t,
detail::type_identity_t<Source>,
detail::type_identity_t<Args>...>;

// detail/format_string_parser.h: empty

Expand Down
Loading

0 comments on commit 43d3be8

Please sign in to comment.