Repository: bitcoin/bitcoin
File: src/util/string.h (C/C++)

Code before:
 * @brief A wrapper for a compile-time partially validated format string
 *
 * This struct can be used to enforce partial compile-time validation of format
 * strings, to reduce the likelihood of tinyformat throwing exceptions at
 * run-time. Validation is partial to try and prevent the most common errors
 * while avoiding re-implementing the entire parsing logic.
 */
template <unsigned num_params>
struct ConstevalFormatString {
    const char* const fmt;
    consteval ConstevalFormatString(const char* str) : fmt{str} { Detail_CheckNumFormatSpecifiers(fmt); }
    constexpr static void Detail_CheckNumFormatSpecifiers(const char* str)
    {
        unsigned count_normal{0}; // Number of "normal" specifiers, like %s
        unsigned count_pos{0};    // Max number in positional specifier, like %8$s
        for (auto it{str}; *it != '\0'; ++it) {
            if (*it == '%') {
                ++it;
            } else {
                continue;
            }

            if (*it == '%') {
                // Percent escape: %%
                continue;
            }

            auto add_arg = [&] {
                unsigned maybe_num{0};
                while ('0' <= *it && *it <= '9') {
                    maybe_num *= 10;
                    maybe_num += *it - '0';
                    ++it;
                }

                if (*it == '$') {
                    ++it;
                    // Positional specifier, like %8$s
                    if (maybe_num == 0) throw "Positional format specifier must have position of at least 1";
                    count_pos = std::max(count_pos, maybe_num);
                } else {
                    // Non-positional specifier, like %s
                    ++count_normal;
                }
            };

            // Increase argument count and consume positional specifier, if present.
            add_arg();

            // Consume flags.
            while (*it == '#' || *it == '0' || *it == '-' || *it == ' ' || *it == '+') ++it;

Review comment (security):
These are all here to check if we're inside a format string, but we don't have a `%%`, right?
Could we maybe simplify that to something like:
```suggestion
            if (*it != '%' || *(++it) == '%') continue; // Skip escaped %%
```
?

Code after:
 * @brief A wrapper for a compile-time partially validated format string
 *
 * This struct can be used to enforce partial compile-time validation of format
 * strings, to reduce the likelihood of tinyformat throwing exceptions at
 * run-time. Validation is partial to try and prevent the most common errors
 * while avoiding re-implementing the entire parsing logic.
 */
template <unsigned num_params>
struct ConstevalFormatString {
    const char* const fmt;
    consteval ConstevalFormatString(const char* str) : fmt{str} { Detail_CheckNumFormatSpecifiers(fmt); }
    constexpr static void Detail_CheckNumFormatSpecifiers(const char* str)
    {
        unsigned count_normal{0}; // Number of "normal" specifiers, like %s
        unsigned count_pos{0};    // Max number in positional specifier, like %8$s
        for (auto it{str}; *it != '\0'; ++it) {
            if (*it != '%' || *++it == '%') continue; // Skip escaped %%

            auto add_arg = [&] {
                unsigned maybe_num{0};
                while ('0' <= *it && *it <= '9') {
                    maybe_num *= 10;
                    maybe_num += *it - '0';
                    ++it;
                }

                if (*it == '$') {
                    ++it;
                    // Positional specifier, like %8$s
                    if (maybe_num == 0) throw "Positional format specifier must have position of at least 1";
                    count_pos = std::max(count_pos, maybe_num);
                } else {
                    // Non-positional specifier, like %s
                    ++count_normal;
                }
            };

            // Increase argument count and consume positional specifier, if present.
            add_arg();

            // Consume flags.
            while (*it == '#' || *it == '0' || *it == '-' || *it == ' ' || *it == '+') ++it;

            auto parse_size = [&] {
                if (*it == '*') {
                    ++it;
                    add_arg();
                } else {
                    while ('0' <= *it && *it <= '9') ++it;
                }
            };