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