CheckedFormatString.h 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. /*
  2. * Copyright (c) 2021, the SerenityOS developers.
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #pragma once
  7. #include <AK/AllOf.h>
  8. #include <AK/AnyOf.h>
  9. #include <AK/Array.h>
  10. #include <AK/StdLibExtras.h>
  11. #include <AK/StringView.h>
  12. #ifdef ENABLE_COMPILETIME_FORMAT_CHECK
  13. // FIXME: Seems like clang doesn't like calling 'consteval' functions inside 'consteval' functions quite the same way as GCC does,
  14. // it seems to entirely forget that it accepted that parameters to a 'consteval' function to begin with.
  15. # if defined(__clang__) || defined(__CLION_IDE_)
  16. # undef ENABLE_COMPILETIME_FORMAT_CHECK
  17. # endif
  18. #endif
  19. #ifdef ENABLE_COMPILETIME_FORMAT_CHECK
  20. namespace AK::Format::Detail {
  21. // We have to define a local "purely constexpr" Array that doesn't lead back to us (via e.g. VERIFY)
  22. template<typename T, size_t Size>
  23. struct Array {
  24. constexpr static size_t size() { return Size; }
  25. constexpr const T& operator[](size_t index) const { return __data[index]; }
  26. constexpr T& operator[](size_t index) { return __data[index]; }
  27. using ConstIterator = SimpleIterator<const Array, const T>;
  28. using Iterator = SimpleIterator<Array, T>;
  29. constexpr ConstIterator begin() const { return ConstIterator::begin(*this); }
  30. constexpr Iterator begin() { return Iterator::begin(*this); }
  31. constexpr ConstIterator end() const { return ConstIterator::end(*this); }
  32. constexpr Iterator end() { return Iterator::end(*this); }
  33. T __data[Size];
  34. };
  35. template<typename... Args>
  36. void compiletime_fail(Args...);
  37. template<size_t N>
  38. consteval auto extract_used_argument_index(const char (&fmt)[N], size_t specifier_start_index, size_t specifier_end_index, size_t& next_implicit_argument_index)
  39. {
  40. struct {
  41. size_t index_value { 0 };
  42. bool saw_explicit_index { false };
  43. } state;
  44. for (size_t i = specifier_start_index; i < specifier_end_index; ++i) {
  45. auto c = fmt[i];
  46. if (c > '9' || c < '0')
  47. break;
  48. state.index_value *= 10;
  49. state.index_value += c - '0';
  50. state.saw_explicit_index = true;
  51. }
  52. if (!state.saw_explicit_index)
  53. return next_implicit_argument_index++;
  54. return state.index_value;
  55. }
  56. // FIXME: We should rather parse these format strings at compile-time if possible.
  57. template<size_t N>
  58. consteval auto count_fmt_params(const char (&fmt)[N])
  59. {
  60. struct {
  61. // FIXME: Switch to variable-sized storage whenever we can come up with one :)
  62. Array<size_t, 128> used_arguments { 0 };
  63. size_t total_used_argument_count { 0 };
  64. size_t next_implicit_argument_index { 0 };
  65. bool has_explicit_argument_references { false };
  66. size_t unclosed_braces { 0 };
  67. size_t extra_closed_braces { 0 };
  68. size_t nesting_level { 0 };
  69. Array<size_t, 4> last_format_specifier_start { 0 };
  70. size_t total_used_last_format_specifier_start_count { 0 };
  71. } result;
  72. for (size_t i = 0; i < N; ++i) {
  73. auto ch = fmt[i];
  74. switch (ch) {
  75. case '{':
  76. if (i + 1 < N && fmt[i + 1] == '{') {
  77. ++i;
  78. continue;
  79. }
  80. // Note: There's no compile-time throw, so we have to abuse a compile-time string to store errors.
  81. if (result.total_used_last_format_specifier_start_count >= result.last_format_specifier_start.size() - 1)
  82. compiletime_fail("Format-String Checker internal error: Format specifier nested too deep");
  83. result.last_format_specifier_start[result.total_used_last_format_specifier_start_count++] = i + 1;
  84. ++result.unclosed_braces;
  85. ++result.nesting_level;
  86. break;
  87. case '}':
  88. if (result.nesting_level == 0) {
  89. if (i + 1 < N && fmt[i + 1] == '}') {
  90. ++i;
  91. continue;
  92. }
  93. }
  94. if (result.unclosed_braces) {
  95. --result.nesting_level;
  96. --result.unclosed_braces;
  97. if (result.total_used_last_format_specifier_start_count == 0)
  98. compiletime_fail("Format-String Checker internal error: Expected location information");
  99. const auto specifier_start_index = result.last_format_specifier_start[--result.total_used_last_format_specifier_start_count];
  100. if (result.total_used_argument_count >= result.used_arguments.size())
  101. compiletime_fail("Format-String Checker internal error: Too many format arguments in format string");
  102. auto used_argument_index = extract_used_argument_index<N>(fmt, specifier_start_index, i, result.next_implicit_argument_index);
  103. if (used_argument_index + 1 != result.next_implicit_argument_index)
  104. result.has_explicit_argument_references = true;
  105. result.used_arguments[result.total_used_argument_count++] = used_argument_index;
  106. } else {
  107. ++result.extra_closed_braces;
  108. }
  109. break;
  110. default:
  111. continue;
  112. }
  113. }
  114. return result;
  115. }
  116. }
  117. #endif
  118. namespace AK::Format::Detail {
  119. template<typename... Args>
  120. struct CheckedFormatString {
  121. template<size_t N>
  122. consteval CheckedFormatString(const char (&fmt)[N])
  123. : m_string { fmt }
  124. {
  125. #ifdef ENABLE_COMPILETIME_FORMAT_CHECK
  126. check_format_parameter_consistency<N, sizeof...(Args)>(fmt);
  127. #endif
  128. }
  129. template<typename T>
  130. CheckedFormatString(const T& unchecked_fmt) requires(requires(T t) { StringView { t }; })
  131. : m_string(unchecked_fmt)
  132. {
  133. }
  134. auto view() const { return m_string; }
  135. private:
  136. #ifdef ENABLE_COMPILETIME_FORMAT_CHECK
  137. template<size_t N, size_t param_count>
  138. consteval static bool check_format_parameter_consistency(const char (&fmt)[N])
  139. {
  140. auto check = count_fmt_params<N>(fmt);
  141. if (check.unclosed_braces != 0)
  142. compiletime_fail("Extra unclosed braces in format string");
  143. if (check.extra_closed_braces != 0)
  144. compiletime_fail("Extra closing braces in format string");
  145. {
  146. auto begin = check.used_arguments.begin();
  147. auto end = check.used_arguments.begin() + check.total_used_argument_count;
  148. auto has_all_referenced_arguments = !AK::any_of(begin, end, [](auto& entry) { return entry >= param_count; });
  149. if (!has_all_referenced_arguments)
  150. compiletime_fail("Format string references nonexistent parameter");
  151. }
  152. if (!check.has_explicit_argument_references && check.total_used_argument_count != param_count)
  153. compiletime_fail("Format string does not reference all passed parameters");
  154. // Ensure that no passed parameter is ignored or otherwise not referenced in the format
  155. // As this check is generally pretty expensive, try to avoid it where it cannot fail.
  156. // We will only do this check if the format string has explicit argument refs
  157. // otherwise, the check above covers this check too, as implicit refs
  158. // monotonically increase, and cannot have 'gaps'.
  159. if (check.has_explicit_argument_references) {
  160. auto all_parameters = iota_array<size_t, param_count>(0);
  161. constexpr auto contains = [](auto begin, auto end, auto entry) {
  162. for (; begin != end; begin++) {
  163. if (*begin == entry)
  164. return true;
  165. }
  166. return false;
  167. };
  168. auto references_all_arguments = AK::all_of(
  169. all_parameters,
  170. [&](auto& entry) {
  171. return contains(
  172. check.used_arguments.begin(),
  173. check.used_arguments.begin() + check.total_used_argument_count,
  174. entry);
  175. });
  176. if (!references_all_arguments)
  177. compiletime_fail("Format string does not reference all passed parameters");
  178. }
  179. return true;
  180. }
  181. #endif
  182. StringView m_string;
  183. };
  184. }
  185. namespace AK {
  186. template<typename... Args>
  187. using CheckedFormatString = Format::Detail::CheckedFormatString<IdentityType<Args>...>;
  188. }