|
@@ -26,6 +26,8 @@
|
|
|
|
|
|
#pragma once
|
|
|
|
|
|
+#include <AK/CheckedFormatString.h>
|
|
|
+
|
|
|
#include <AK/AllOf.h>
|
|
|
#include <AK/AnyOf.h>
|
|
|
#include <AK/Array.h>
|
|
@@ -37,161 +39,6 @@
|
|
|
# include <stdio.h>
|
|
|
#endif
|
|
|
|
|
|
-#ifndef DBGLN_NO_COMPILETIME_FORMAT_CHECK
|
|
|
-// Note: Clang 12 adds support for CTAD, but still fails to build the dbgln() checks, so they're disabled altogether for now.
|
|
|
-// See https://oss-fuzz-build-logs.storage.googleapis.com/log-79750138-f41e-4f39-8812-7c536f1d2e35.txt, for example.
|
|
|
-# if defined(__clang__)
|
|
|
-# define DBGLN_NO_COMPILETIME_FORMAT_CHECK
|
|
|
-# endif
|
|
|
-#endif
|
|
|
-
|
|
|
-#ifndef DBGLN_NO_COMPILETIME_FORMAT_CHECK
|
|
|
-namespace {
|
|
|
-
|
|
|
-template<size_t N>
|
|
|
-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)
|
|
|
-{
|
|
|
- struct {
|
|
|
- size_t index_value { 0 };
|
|
|
- bool saw_explicit_index { false };
|
|
|
- } state;
|
|
|
- for (size_t i = specifier_start_index; i < specifier_end_index; ++i) {
|
|
|
- auto c = fmt[i];
|
|
|
- if (c > '9' || c < '0')
|
|
|
- break;
|
|
|
-
|
|
|
- state.index_value *= 10;
|
|
|
- state.index_value += c - '0';
|
|
|
- state.saw_explicit_index = true;
|
|
|
- }
|
|
|
-
|
|
|
- if (!state.saw_explicit_index)
|
|
|
- return next_implicit_argument_index++;
|
|
|
-
|
|
|
- return state.index_value;
|
|
|
-}
|
|
|
-
|
|
|
-// FIXME: We should rather parse these format strings at compile-time if possible.
|
|
|
-template<size_t N>
|
|
|
-consteval auto count_fmt_params(const char (&fmt)[N])
|
|
|
-{
|
|
|
- struct {
|
|
|
- // FIXME: Switch to variable-sized storage whenever we can come up with one :)
|
|
|
- Array<size_t, 128> used_arguments { 0 };
|
|
|
- size_t total_used_argument_count { 0 };
|
|
|
- size_t next_implicit_argument_index { 0 };
|
|
|
- bool has_explicit_argument_references { false };
|
|
|
-
|
|
|
- size_t unclosed_braces { 0 };
|
|
|
- size_t extra_closed_braces { 0 };
|
|
|
-
|
|
|
- Array<size_t, 4> last_format_specifier_start { 0 };
|
|
|
- size_t total_used_last_format_specifier_start_count { 0 };
|
|
|
-
|
|
|
- StringLiteral<128> internal_error { { 0 } };
|
|
|
- } result;
|
|
|
-
|
|
|
- for (size_t i = 0; i < N; ++i) {
|
|
|
- auto ch = fmt[i];
|
|
|
- switch (ch) {
|
|
|
- case '{':
|
|
|
- if (i + 1 < N && fmt[i + 1] == '{') {
|
|
|
- ++i;
|
|
|
- continue;
|
|
|
- }
|
|
|
-
|
|
|
- // Note: There's no compile-time throw, so we have to abuse a compile-time string to store errors.
|
|
|
- if (result.total_used_last_format_specifier_start_count >= result.last_format_specifier_start.size() - 1)
|
|
|
- result.internal_error = "Format-String Checker internal error: Format specifier nested too deep";
|
|
|
-
|
|
|
- result.last_format_specifier_start[result.total_used_last_format_specifier_start_count++] = i + 1;
|
|
|
-
|
|
|
- ++result.unclosed_braces;
|
|
|
- break;
|
|
|
- case '}':
|
|
|
- if (i + 1 < N && fmt[i + 1] == '}') {
|
|
|
- ++i;
|
|
|
- continue;
|
|
|
- }
|
|
|
- if (result.unclosed_braces) {
|
|
|
- --result.unclosed_braces;
|
|
|
-
|
|
|
- if (result.total_used_last_format_specifier_start_count == 0)
|
|
|
- result.internal_error = "Format-String Checker internal error: Expected location information";
|
|
|
-
|
|
|
- const auto specifier_start_index = result.last_format_specifier_start[--result.total_used_last_format_specifier_start_count];
|
|
|
-
|
|
|
- if (result.total_used_argument_count >= result.used_arguments.size())
|
|
|
- result.internal_error = "Format-String Checker internal error: Too many format arguments in format string";
|
|
|
-
|
|
|
- auto used_argument_index = extract_used_argument_index<N>(fmt, specifier_start_index, i, result.next_implicit_argument_index);
|
|
|
- if (used_argument_index + 1 != result.next_implicit_argument_index)
|
|
|
- result.has_explicit_argument_references = true;
|
|
|
- result.used_arguments[result.total_used_argument_count++] = used_argument_index;
|
|
|
-
|
|
|
- } else {
|
|
|
- ++result.extra_closed_braces;
|
|
|
- }
|
|
|
- break;
|
|
|
- default:
|
|
|
- continue;
|
|
|
- }
|
|
|
- }
|
|
|
- return result;
|
|
|
-}
|
|
|
-}
|
|
|
-
|
|
|
-template<size_t N, StringLiteral<N> fmt, size_t param_count, auto check = count_fmt_params<N>(fmt.data)>
|
|
|
-constexpr bool check_format_parameter_consistency()
|
|
|
-{
|
|
|
- static_assert(check.internal_error.data[0] == 0, "Some internal error occured, try looking at the check function type for the error");
|
|
|
- static_assert(check.unclosed_braces == 0, "Extra unclosed braces in format string");
|
|
|
- static_assert(check.extra_closed_braces == 0, "Extra closing braces in format string");
|
|
|
-
|
|
|
- {
|
|
|
- constexpr auto begin = check.used_arguments.begin();
|
|
|
- constexpr auto end = check.used_arguments.begin() + check.total_used_argument_count;
|
|
|
- constexpr auto has_all_referenced_arguments = !AK::any_of(begin, end, [](auto& entry) { return entry >= param_count; });
|
|
|
- static_assert(has_all_referenced_arguments, "Format string references nonexistent parameter");
|
|
|
- }
|
|
|
-
|
|
|
- if constexpr (!check.has_explicit_argument_references)
|
|
|
- static_assert(check.total_used_argument_count == param_count, "Format string does not reference all passed parameters");
|
|
|
-
|
|
|
- // Ensure that no passed parameter is ignored or otherwise not referenced in the format
|
|
|
- // As this check is generally pretty expensive, try to avoid it where it cannot fail.
|
|
|
- // We will only do this check if the format string has explicit argument refs
|
|
|
- // otherwise, the check above covers this check too, as implicit refs
|
|
|
- // monotonically increase, and cannot have 'gaps'.
|
|
|
- if constexpr (check.has_explicit_argument_references) {
|
|
|
- constexpr auto all_parameters = iota_array<size_t, param_count>(0);
|
|
|
- auto contains = [](auto begin, auto end, auto entry) {
|
|
|
- for (; begin != end; begin++) {
|
|
|
- if (*begin == entry)
|
|
|
- return true;
|
|
|
- }
|
|
|
-
|
|
|
- return false;
|
|
|
- };
|
|
|
- constexpr auto references_all_arguments = AK::all_of(
|
|
|
- all_parameters.begin(),
|
|
|
- all_parameters.end(),
|
|
|
- [&](auto& entry) {
|
|
|
- return contains(
|
|
|
- check.used_arguments.begin(),
|
|
|
- check.used_arguments.begin() + check.total_used_argument_count,
|
|
|
- entry);
|
|
|
- });
|
|
|
- static_assert(references_all_arguments, "Format string does not reference all passed parameters");
|
|
|
- }
|
|
|
-
|
|
|
- return true;
|
|
|
-}
|
|
|
-
|
|
|
-template<auto fmt, auto param_count>
|
|
|
-concept ConsistentFormatParameters = check_format_parameter_consistency<fmt.size, fmt, param_count>();
|
|
|
-#endif
|
|
|
-
|
|
|
namespace AK {
|
|
|
|
|
|
class TypeErasedFormatParams;
|
|
@@ -523,48 +370,38 @@ void vformat(const LogStream& stream, StringView fmtstr, TypeErasedFormatParams)
|
|
|
void vout(FILE*, StringView fmtstr, TypeErasedFormatParams, bool newline = false);
|
|
|
|
|
|
template<typename... Parameters>
|
|
|
-void out(FILE* file, StringView fmtstr, const Parameters&... parameters) { vout(file, fmtstr, VariadicFormatParams { parameters... }); }
|
|
|
-template<typename... Parameters>
|
|
|
-void outln(FILE* file, StringView fmtstr, const Parameters&... parameters) { vout(file, fmtstr, VariadicFormatParams { parameters... }, true); }
|
|
|
+void out(FILE* file, CheckedFormatString<Parameters...>&& fmtstr, const Parameters&... parameters) { vout(file, fmtstr.view(), VariadicFormatParams { parameters... }); }
|
|
|
+
|
|
|
template<typename... Parameters>
|
|
|
-void outln(FILE* file, const char* fmtstr, const Parameters&... parameters) { vout(file, fmtstr, VariadicFormatParams { parameters... }, true); }
|
|
|
+void outln(FILE* file, CheckedFormatString<Parameters...>&& fmtstr, const Parameters&... parameters) { vout(file, fmtstr.view(), VariadicFormatParams { parameters... }, true); }
|
|
|
+
|
|
|
inline void outln(FILE* file) { fputc('\n', file); }
|
|
|
|
|
|
template<typename... Parameters>
|
|
|
-void out(StringView fmtstr, const Parameters&... parameters) { out(stdout, fmtstr, parameters...); }
|
|
|
-template<typename... Parameters>
|
|
|
-void outln(StringView fmtstr, const Parameters&... parameters) { outln(stdout, fmtstr, parameters...); }
|
|
|
+void out(CheckedFormatString<Parameters...>&& fmtstr, const Parameters&... parameters) { out(stdout, move(fmtstr), parameters...); }
|
|
|
+
|
|
|
template<typename... Parameters>
|
|
|
-void outln(const char* fmtstr, const Parameters&... parameters) { outln(stdout, fmtstr, parameters...); }
|
|
|
+void outln(CheckedFormatString<Parameters...>&& fmtstr, const Parameters&... parameters) { outln(stdout, move(fmtstr), parameters...); }
|
|
|
+
|
|
|
inline void outln() { outln(stdout); }
|
|
|
|
|
|
template<typename... Parameters>
|
|
|
-void warn(StringView fmtstr, const Parameters&... parameters) { out(stderr, fmtstr, parameters...); }
|
|
|
-template<typename... Parameters>
|
|
|
-void warnln(StringView fmtstr, const Parameters&... parameters) { outln(stderr, fmtstr, parameters...); }
|
|
|
+void warn(CheckedFormatString<Parameters...>&& fmtstr, const Parameters&... parameters) { out(stderr, move(fmtstr), parameters...); }
|
|
|
+
|
|
|
template<typename... Parameters>
|
|
|
-void warnln(const char* fmtstr, const Parameters&... parameters) { outln(stderr, fmtstr, parameters...); }
|
|
|
+void warnln(CheckedFormatString<Parameters...>&& fmtstr, const Parameters&... parameters) { outln(stderr, move(fmtstr), parameters...); }
|
|
|
+
|
|
|
inline void warnln() { outln(stderr); }
|
|
|
#endif
|
|
|
|
|
|
void vdbgln(StringView fmtstr, TypeErasedFormatParams);
|
|
|
|
|
|
-#ifndef DBGLN_NO_COMPILETIME_FORMAT_CHECK
|
|
|
-template<StringLiteral fmt, bool enabled = true, typename... Parameters>
|
|
|
-void dbgln(const Parameters&... parameters) requires ConsistentFormatParameters<fmt, sizeof...(Parameters)>
|
|
|
-{
|
|
|
- dbgln<enabled>(StringView { fmt.data }, parameters...);
|
|
|
-}
|
|
|
-#endif
|
|
|
-
|
|
|
template<bool enabled = true, typename... Parameters>
|
|
|
-void dbgln(StringView fmtstr, const Parameters&... parameters)
|
|
|
+void dbgln(CheckedFormatString<Parameters...>&& fmtstr, const Parameters&... parameters)
|
|
|
{
|
|
|
if constexpr (enabled)
|
|
|
- vdbgln(fmtstr, VariadicFormatParams { parameters... });
|
|
|
+ vdbgln(fmtstr.view(), VariadicFormatParams { parameters... });
|
|
|
}
|
|
|
-template<bool enabled = true, typename... Parameters>
|
|
|
-void dbgln(const char* fmtstr, const Parameters&... parameters) { dbgln<enabled>(StringView { fmtstr }, parameters...); }
|
|
|
|
|
|
template<bool enabled = true>
|
|
|
void dbgln() { dbgln<enabled>(""); }
|
|
@@ -575,10 +412,10 @@ void set_debug_enabled(bool);
|
|
|
void vdmesgln(StringView fmtstr, TypeErasedFormatParams);
|
|
|
|
|
|
template<typename... Parameters>
|
|
|
-void dmesgln(StringView fmtstr, const Parameters&... parameters) { vdmesgln(fmtstr, VariadicFormatParams { parameters... }); }
|
|
|
-template<typename... Parameters>
|
|
|
-void dmesgln(const char* fmtstr, const Parameters&... parameters) { vdmesgln(fmtstr, VariadicFormatParams { parameters... }); }
|
|
|
-inline void dmesgln() { dmesgln(""); }
|
|
|
+void dmesgln(CheckedFormatString<Parameters...>&& fmt, const Parameters&... parameters)
|
|
|
+{
|
|
|
+ vdmesgln(fmt.view(), VariadicFormatParams { parameters... });
|
|
|
+}
|
|
|
#endif
|
|
|
|
|
|
template<typename T, typename = void>
|
|
@@ -647,13 +484,8 @@ using AK::warnln;
|
|
|
|
|
|
using AK::dbgln;
|
|
|
|
|
|
+using AK::CheckedFormatString;
|
|
|
using AK::FormatIfSupported;
|
|
|
using AK::FormatString;
|
|
|
|
|
|
-#ifdef DBGLN_NO_COMPILETIME_FORMAT_CHECK
|
|
|
-# define dbgln(fmt, ...) dbgln(fmt, ##__VA_ARGS__)
|
|
|
-# define dbgln_if(flag, fmt, ...) dbgln<flag>(fmt, ##__VA_ARGS__)
|
|
|
-#else
|
|
|
-# define dbgln(fmt, ...) dbgln<fmt>(__VA_ARGS__)
|
|
|
-# define dbgln_if(flag, fmt, ...) dbgln<fmt, flag>(__VA_ARGS__)
|
|
|
-#endif
|
|
|
+#define dbgln_if(flag, fmt, ...) dbgln<flag>(fmt, ##__VA_ARGS__)
|