diff options
author | Mark de Wever <koraq@xs4all.nl> | 2023-06-04 21:32:10 +0200 |
---|---|---|
committer | Mark de Wever <koraq@xs4all.nl> | 2023-07-18 21:01:52 +0200 |
commit | a0ffeccc707679d50cd8d4fba0e6f20a3450d474 (patch) | |
tree | 348b608b5c940dc1f5fa7d4d401a43604cab473e /libcxx/include/__format | |
parent | 7cc6b80d9a97602bd8f3bc5333cd26020a3726da (diff) | |
download | llvm-a0ffeccc707679d50cd8d4fba0e6f20a3450d474.zip llvm-a0ffeccc707679d50cd8d4fba0e6f20a3450d474.tar.gz llvm-a0ffeccc707679d50cd8d4fba0e6f20a3450d474.tar.bz2 |
[libc++][format] Improves run-time diagnostics.
After parsing a std-format-spec it's validated, depending on the type used some
format options are not allowed. This improves the error messages in the
exceptions thrown upon failure.
Depends on D155364
Reviewed By: #libc, ldionne
Differential Revision: https://reviews.llvm.org/D155366
Diffstat (limited to 'libcxx/include/__format')
-rw-r--r-- | libcxx/include/__format/formatter_bool.h | 2 | ||||
-rw-r--r-- | libcxx/include/__format/formatter_char.h | 2 | ||||
-rw-r--r-- | libcxx/include/__format/formatter_floating_point.h | 2 | ||||
-rw-r--r-- | libcxx/include/__format/formatter_integer.h | 2 | ||||
-rw-r--r-- | libcxx/include/__format/formatter_pointer.h | 2 | ||||
-rw-r--r-- | libcxx/include/__format/parser_std_format_spec.h | 152 |
6 files changed, 130 insertions, 32 deletions
diff --git a/libcxx/include/__format/formatter_bool.h b/libcxx/include/__format/formatter_bool.h index ad50f04..3c8ae95f 100644 --- a/libcxx/include/__format/formatter_bool.h +++ b/libcxx/include/__format/formatter_bool.h @@ -39,7 +39,7 @@ public: template <class _ParseContext> _LIBCPP_HIDE_FROM_ABI constexpr typename _ParseContext::iterator parse(_ParseContext& __ctx) { typename _ParseContext::iterator __result = __parser_.__parse(__ctx, __format_spec::__fields_integral); - __format_spec::__process_parsed_bool(__parser_); + __format_spec::__process_parsed_bool(__parser_, "a bool"); return __result; } diff --git a/libcxx/include/__format/formatter_char.h b/libcxx/include/__format/formatter_char.h index 8b1b357..d6e61e8 100644 --- a/libcxx/include/__format/formatter_char.h +++ b/libcxx/include/__format/formatter_char.h @@ -37,7 +37,7 @@ public: template <class _ParseContext> _LIBCPP_HIDE_FROM_ABI constexpr typename _ParseContext::iterator parse(_ParseContext& __ctx) { typename _ParseContext::iterator __result = __parser_.__parse(__ctx, __format_spec::__fields_integral); - __format_spec::__process_parsed_char(__parser_); + __format_spec::__process_parsed_char(__parser_, "a character"); return __result; } diff --git a/libcxx/include/__format/formatter_floating_point.h b/libcxx/include/__format/formatter_floating_point.h index e01d7b8..fbb8aa9 100644 --- a/libcxx/include/__format/formatter_floating_point.h +++ b/libcxx/include/__format/formatter_floating_point.h @@ -762,7 +762,7 @@ public: template <class _ParseContext> _LIBCPP_HIDE_FROM_ABI constexpr typename _ParseContext::iterator parse(_ParseContext& __ctx) { typename _ParseContext::iterator __result = __parser_.__parse(__ctx, __format_spec::__fields_floating_point); - __format_spec::__process_parsed_floating_point(__parser_); + __format_spec::__process_parsed_floating_point(__parser_, "a floating-point"); return __result; } diff --git a/libcxx/include/__format/formatter_integer.h b/libcxx/include/__format/formatter_integer.h index dca3cb3..5590bff 100644 --- a/libcxx/include/__format/formatter_integer.h +++ b/libcxx/include/__format/formatter_integer.h @@ -37,7 +37,7 @@ public: template <class _ParseContext> _LIBCPP_HIDE_FROM_ABI constexpr typename _ParseContext::iterator parse(_ParseContext& __ctx) { typename _ParseContext::iterator __result = __parser_.__parse(__ctx, __format_spec::__fields_integral); - __format_spec::__process_parsed_integer(__parser_); + __format_spec::__process_parsed_integer(__parser_, "an integer"); return __result; } diff --git a/libcxx/include/__format/formatter_pointer.h b/libcxx/include/__format/formatter_pointer.h index 18aaee1..a221261 100644 --- a/libcxx/include/__format/formatter_pointer.h +++ b/libcxx/include/__format/formatter_pointer.h @@ -35,7 +35,7 @@ public: template <class _ParseContext> _LIBCPP_HIDE_FROM_ABI constexpr typename _ParseContext::iterator parse(_ParseContext& __ctx) { typename _ParseContext::iterator __result = __parser_.__parse(__ctx, __format_spec::__fields_pointer); - __format_spec::__process_display_type_pointer(__parser_.__type_); + __format_spec::__process_display_type_pointer(__parser_.__type_, "a pointer"); return __result; } diff --git a/libcxx/include/__format/parser_std_format_spec.h b/libcxx/include/__format/parser_std_format_spec.h index 29cce4c..5147b5e 100644 --- a/libcxx/include/__format/parser_std_format_spec.h +++ b/libcxx/include/__format/parser_std_format_spec.h @@ -36,6 +36,7 @@ #include <__type_traits/is_trivially_copyable.h> #include <__variant/monostate.h> #include <cstdint> +#include <string> #include <string_view> #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) @@ -51,6 +52,17 @@ _LIBCPP_BEGIN_NAMESPACE_STD namespace __format_spec { +_LIBCPP_NORETURN _LIBCPP_HIDE_FROM_ABI inline void +__throw_invalid_option_format_error(const char* __id, const char* __option) { + std::__throw_format_error( + (string("The format specifier for ") + __id + " does not allow the " + __option + " option").c_str()); +} + +_LIBCPP_NORETURN _LIBCPP_HIDE_FROM_ABI inline void __throw_invalid_type_format_error(const char* __id) { + std::__throw_format_error( + (string("The type option contains an invalid value for ") + __id + " formatting argument").c_str()); +} + template <contiguous_iterator _Iterator, class _ParseContext> _LIBCPP_HIDE_FROM_ABI constexpr __format::__parse_number_result<_Iterator> __parse_arg_id(_Iterator __begin, _Iterator __end, _ParseContext& __ctx) { @@ -186,7 +198,7 @@ enum class _LIBCPP_ENUM_VIS __sign : uint8_t { }; enum class _LIBCPP_ENUM_VIS __type : uint8_t { - __default, + __default = 0, __string, __binary_lower_case, __binary_upper_case, @@ -208,6 +220,25 @@ enum class _LIBCPP_ENUM_VIS __type : uint8_t { __debug }; +_LIBCPP_HIDE_FROM_ABI inline constexpr uint32_t __create_type_mask(__type __t) { + uint32_t __shift = static_cast<uint32_t>(__t); + if (__shift == 0) + return 1; + + if (__shift > 31) + std::__throw_format_error("The type does not fit in the mask"); + + return 1 << __shift; +} + +inline constexpr uint32_t __type_mask_integer = + __create_type_mask(__type::__binary_lower_case) | // + __create_type_mask(__type::__binary_upper_case) | // + __create_type_mask(__type::__decimal) | // + __create_type_mask(__type::__octal) | // + __create_type_mask(__type::__hexadecimal_lower_case) | // + __create_type_mask(__type::__hexadecimal_upper_case); + struct __std { __alignment __alignment_ : 3; __sign __sign_ : 2; @@ -383,11 +414,86 @@ public: return __begin; if (__begin != __end && *__begin != _CharT('}')) - std::__throw_format_error("The format-spec should consume the input or end with a '}'"); + std::__throw_format_error("The format specifier should consume the input or end with a '}'"); return __begin; } + // Validates the selected the parsed data. + // + // The valid fields in the parser may depend on the display type + // selected. But the type is the last optional field, so by the time + // it's known an option can't be used, it already has been parsed. + // This does the validation again. + // + // For example an integral may have a sign, zero-padding, or alternate + // form when the type option is not 'c'. So the generic approach is: + // + // typename _ParseContext::iterator __result = __parser_.__parse(__ctx, __format_spec::__fields_integral); + // if (__parser.__type_ == __format_spec::__type::__char) { + // __parser.__validate((__format_spec::__fields_bool, "an integer"); + // ... // more char adjustments + // } else { + // ... // validate an integral type. + // } + // + // For some types all valid options need a second validation run, like + // boolean types. + // + // Depending on whether the validation is done at compile-time or + // run-time the error differs + // - run-time the exception is thrown and contains the type of field + // being validated. + // - at compile-time the line with `std::__throw_format_error` is shown + // in the output. In that case it's important for the error to be on one + // line. + // Note future versions of C++ may allow better compile-time error + // reporting. + _LIBCPP_HIDE_FROM_ABI constexpr void + __validate(__fields __fields, const char* __id, uint32_t __type_mask = -1) const { + if (!__fields.__sign_ && __sign_ != __sign::__default) { + if (std::is_constant_evaluated()) + std::__throw_format_error("The format specifier does not allow the sign option"); + else + __format_spec::__throw_invalid_option_format_error(__id, "sign"); + } + + if (!__fields.__alternate_form_ && __alternate_form_) { + if (std::is_constant_evaluated()) + std::__throw_format_error("The format specifier does not allow the alternate form option"); + else + __format_spec::__throw_invalid_option_format_error(__id, "alternate form"); + } + + if (!__fields.__zero_padding_ && __alignment_ == __alignment::__zero_padding) { + if (std::is_constant_evaluated()) + std::__throw_format_error("The format specifier does not allow the zero-padding option"); + else + __format_spec::__throw_invalid_option_format_error(__id, "zero-padding"); + } + + if (!__fields.__precision_ && __precision_ != -1) { // Works both when the precision has a value or an arg-id. + if (std::is_constant_evaluated()) + std::__throw_format_error("The format specifier does not allow the precision option"); + else + __format_spec::__throw_invalid_option_format_error(__id, "precision"); + } + + if (!__fields.__locale_specific_form_ && __locale_specific_form_) { + if (std::is_constant_evaluated()) + std::__throw_format_error("The format specifier does not allow the locale-specific form option"); + else + __format_spec::__throw_invalid_option_format_error(__id, "locale-specific form"); + } + + if ((__create_type_mask(__type_) & __type_mask) == 0) { + if (std::is_constant_evaluated()) + std::__throw_format_error("The format specifier uses an invalid value for the type option"); + else + __format_spec::__throw_invalid_type_format_error(__id); + } + } + /// \returns the `__parsed_specifications` with the resolved dynamic sizes.. _LIBCPP_HIDE_FROM_ABI __parsed_specifications<_CharT> __get_parsed_std_specifications(auto& __ctx) const { @@ -788,31 +894,23 @@ _LIBCPP_HIDE_FROM_ABI constexpr void __process_display_type_string(__format_spec } template <class _CharT> -_LIBCPP_HIDE_FROM_ABI constexpr void __process_display_type_bool_string(__parser<_CharT>& __parser) { - if (__parser.__sign_ != __sign::__default) - std::__throw_format_error("A sign field isn't allowed in this format-spec"); - - if (__parser.__alternate_form_) - std::__throw_format_error("An alternate form field isn't allowed in this format-spec"); - - if (__parser.__alignment_ == __alignment::__zero_padding) - std::__throw_format_error("A zero-padding field isn't allowed in this format-spec"); - +_LIBCPP_HIDE_FROM_ABI constexpr void __process_display_type_bool_string(__parser<_CharT>& __parser, const char* __id) { + __parser.__validate(__format_spec::__fields_bool, __id); if (__parser.__alignment_ == __alignment::__default) __parser.__alignment_ = __alignment::__left; } template <class _CharT> -_LIBCPP_HIDE_FROM_ABI constexpr void __process_display_type_char(__parser<_CharT>& __parser) { - __format_spec::__process_display_type_bool_string(__parser); +_LIBCPP_HIDE_FROM_ABI constexpr void __process_display_type_char(__parser<_CharT>& __parser, const char* __id) { + __format_spec::__process_display_type_bool_string(__parser, __id); } template <class _CharT> -_LIBCPP_HIDE_FROM_ABI constexpr void __process_parsed_bool(__parser<_CharT>& __parser) { +_LIBCPP_HIDE_FROM_ABI constexpr void __process_parsed_bool(__parser<_CharT>& __parser, const char* __id) { switch (__parser.__type_) { case __format_spec::__type::__default: case __format_spec::__type::__string: - __format_spec::__process_display_type_bool_string(__parser); + __format_spec::__process_display_type_bool_string(__parser, __id); break; case __format_spec::__type::__binary_lower_case: @@ -824,17 +922,17 @@ _LIBCPP_HIDE_FROM_ABI constexpr void __process_parsed_bool(__parser<_CharT>& __p break; default: - std::__throw_format_error("The format-spec type has a type not supported for a bool argument"); + __format_spec::__throw_invalid_type_format_error(__id); } } template <class _CharT> -_LIBCPP_HIDE_FROM_ABI constexpr void __process_parsed_char(__parser<_CharT>& __parser) { +_LIBCPP_HIDE_FROM_ABI constexpr void __process_parsed_char(__parser<_CharT>& __parser, const char* __id) { switch (__parser.__type_) { case __format_spec::__type::__default: case __format_spec::__type::__char: case __format_spec::__type::__debug: - __format_spec::__process_display_type_char(__parser); + __format_spec::__process_display_type_char(__parser, __id); break; case __format_spec::__type::__binary_lower_case: @@ -846,12 +944,12 @@ _LIBCPP_HIDE_FROM_ABI constexpr void __process_parsed_char(__parser<_CharT>& __p break; default: - std::__throw_format_error("The format-spec type has a type not supported for a char argument"); + __format_spec::__throw_invalid_type_format_error(__id); } } template <class _CharT> -_LIBCPP_HIDE_FROM_ABI constexpr void __process_parsed_integer(__parser<_CharT>& __parser) { +_LIBCPP_HIDE_FROM_ABI constexpr void __process_parsed_integer(__parser<_CharT>& __parser, const char* __id) { switch (__parser.__type_) { case __format_spec::__type::__default: case __format_spec::__type::__binary_lower_case: @@ -863,16 +961,16 @@ _LIBCPP_HIDE_FROM_ABI constexpr void __process_parsed_integer(__parser<_CharT>& break; case __format_spec::__type::__char: - __format_spec::__process_display_type_char(__parser); + __format_spec::__process_display_type_char(__parser, __id); break; default: - std::__throw_format_error("The format-spec type has a type not supported for an integer argument"); + __format_spec::__throw_invalid_type_format_error(__id); } } template <class _CharT> -_LIBCPP_HIDE_FROM_ABI constexpr void __process_parsed_floating_point(__parser<_CharT>& __parser) { +_LIBCPP_HIDE_FROM_ABI constexpr void __process_parsed_floating_point(__parser<_CharT>& __parser, const char* __id) { switch (__parser.__type_) { case __format_spec::__type::__default: case __format_spec::__type::__hexfloat_lower_case: @@ -891,11 +989,11 @@ _LIBCPP_HIDE_FROM_ABI constexpr void __process_parsed_floating_point(__parser<_C break; default: - std::__throw_format_error("The format-spec type has a type not supported for a floating-point argument"); + __format_spec::__throw_invalid_type_format_error(__id); } } -_LIBCPP_HIDE_FROM_ABI constexpr void __process_display_type_pointer(__format_spec::__type __type) { +_LIBCPP_HIDE_FROM_ABI constexpr void __process_display_type_pointer(__format_spec::__type __type, const char* __id) { switch (__type) { case __format_spec::__type::__default: case __format_spec::__type::__pointer_lower_case: @@ -903,7 +1001,7 @@ _LIBCPP_HIDE_FROM_ABI constexpr void __process_display_type_pointer(__format_spe break; default: - std::__throw_format_error("The format-spec type has a type not supported for a pointer argument"); + __format_spec::__throw_invalid_type_format_error(__id); } } |