Branch data Line data Source code
1 : : // Copyright (c) 2019-present The Bitcoin Core developers
2 : : // Distributed under the MIT software license, see the accompanying
3 : : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
4 : :
5 : : #ifndef BITCOIN_UTIL_STRING_H
6 : : #define BITCOIN_UTIL_STRING_H
7 : :
8 : : #include <algorithm>
9 : : #include <array>
10 : : #include <cstddef>
11 : : #include <cstdint>
12 : : #include <initializer_list>
13 : : #include <locale>
14 : : #include <optional>
15 : : #include <span>
16 : : #include <sstream>
17 : : #include <string>
18 : : #include <string_view>
19 : : #include <vector>
20 : :
21 : : #include <attributes.h>
22 : :
23 : : namespace util {
24 : : namespace detail {
25 : : template <unsigned num_params>
26 : 72 : constexpr static void CheckNumFormatSpecifiers(const char* str)
27 : : {
28 : 72 : unsigned count_normal{0}; // Number of "normal" specifiers, like %s
29 : 72 : unsigned count_pos{0}; // Max number in positional specifier, like %8$s
30 [ + + ]: 149 : for (auto it{str}; *it != '\0'; ++it) {
31 [ + + + + ]: 98 : if (*it != '%' || *++it == '%') continue; // Skip escaped %%
32 : :
33 : 270 : auto add_arg = [&] {
34 : 104 : unsigned maybe_num{0};
35 [ + + + + : 172 : while ('0' <= *it && *it <= '9') {
+ + + + +
+ + + ]
36 : 68 : maybe_num *= 10;
37 : 68 : maybe_num += *it - '0';
38 : 68 : ++it;
39 : : }
40 : :
41 [ + - + + : 104 : if (*it == '$') {
+ - + + +
+ + + ]
42 : 56 : ++it;
43 : : // Positional specifier, like %8$s
44 [ - + + + : 56 : if (maybe_num == 0) throw "Positional format specifier must have position of at least 1";
- + + + +
+ + + ]
45 [ + + + + : 89 : count_pos = std::max(count_pos, maybe_num);
+ + + + +
+ + - ]
46 : : } else {
47 : : // Non-positional specifier, like %s
48 : 48 : ++count_normal;
49 : : }
50 : : };
51 : :
52 : : // Increase argument count and consume positional specifier, if present.
53 : 70 : add_arg();
54 : :
55 : : // Consume flags.
56 [ + + + + : 80 : while (*it == '#' || *it == '0' || *it == '-' || *it == ' ' || *it == '+') ++it;
+ + + + +
+ ]
57 : :
58 : 242 : auto parse_size = [&] {
59 [ - + + + : 90 : if (*it == '*') {
+ - + + +
+ + - ]
60 : 34 : ++it;
61 : 34 : add_arg();
62 : : } else {
63 [ - - + + : 68 : while ('0' <= *it && *it <= '9') ++it;
- + + + +
+ - + ]
64 : : }
65 : : };
66 : :
67 : : // Consume dynamic or static width value.
68 : 66 : parse_size();
69 : :
70 : : // Consume dynamic or static precision value.
71 [ + + ]: 64 : if (*it == '.') {
72 : 24 : ++it;
73 : 24 : parse_size();
74 : : }
75 : :
76 [ + + ]: 62 : if (*it == '\0') throw "Format specifier incorrectly terminated by end of string";
77 : :
78 : : // Length and type in "[flags][width][.precision][length]type"
79 : : // is not checked. Parsing continues with the next '%'.
80 : : }
81 [ + + + + ]: 51 : if (count_normal && count_pos) throw "Format specifiers must be all positional or all non-positional!";
82 : 46 : unsigned count{count_normal | count_pos};
83 [ + + ]: 46 : if (num_params != count) throw "Format specifier count must match the argument count!";
84 : 40 : }
85 : : } // namespace detail
86 : :
87 : : /**
88 : : * @brief A wrapper for a compile-time partially validated format string
89 : : *
90 : : * This struct can be used to enforce partial compile-time validation of format
91 : : * strings, to reduce the likelihood of tinyformat throwing exceptions at
92 : : * run-time. Validation is partial to try and prevent the most common errors
93 : : * while avoiding re-implementing the entire parsing logic.
94 : : */
95 : : template <unsigned num_params>
96 : : struct ConstevalFormatString {
97 : : const char* const fmt;
98 : : consteval ConstevalFormatString(const char* str) : fmt{str} { detail::CheckNumFormatSpecifiers<num_params>(fmt); }
99 : : };
100 : :
101 : : void ReplaceAll(std::string& in_out, const std::string& search, const std::string& substitute);
102 : :
103 : : /** Split a string on any char found in separators, returning a vector.
104 : : *
105 : : * If sep does not occur in sp, a singleton with the entirety of sp is returned.
106 : : *
107 : : * @param[in] include_sep Whether to include the separator at the end of the left side of the splits.
108 : : *
109 : : * Note that this function does not care about braces, so splitting
110 : : * "foo(bar(1),2),3) on ',' will return {"foo(bar(1)", "2)", "3)"}.
111 : : *
112 : : * If include_sep == true, splitting "foo(bar(1),2),3) on ','
113 : : * will return:
114 : : * - foo(bar(1),
115 : : * - 2),
116 : : * - 3)
117 : : */
118 : : template <typename T = std::span<const char>>
119 : 28074 : std::vector<T> Split(const std::span<const char>& sp, std::string_view separators, bool include_sep = false)
120 : : {
121 : 28074 : std::vector<T> ret;
122 : 28074 : auto it = sp.begin();
123 : 28074 : auto start = it;
124 [ + + ]: 754209 : while (it != sp.end()) {
125 [ + + ]: 726135 : if (separators.find(*it) != std::string::npos) {
126 [ + + ]: 13679 : if (include_sep) {
127 [ + - ]: 62 : ret.emplace_back(start, it + 1);
128 : : } else {
129 [ + - ]: 13617 : ret.emplace_back(start, it);
130 : : }
131 : 13679 : start = it + 1;
132 : : }
133 : 726135 : ++it;
134 : : }
135 [ + - ]: 28074 : ret.emplace_back(start, it);
136 : 28074 : return ret;
137 : 0 : }
138 : :
139 : : /** Split a string on every instance of sep, returning a vector.
140 : : *
141 : : * If sep does not occur in sp, a singleton with the entirety of sp is returned.
142 : : *
143 : : * Note that this function does not care about braces, so splitting
144 : : * "foo(bar(1),2),3) on ',' will return {"foo(bar(1)", "2)", "3)"}.
145 : : */
146 : : template <typename T = std::span<const char>>
147 : 25068 : std::vector<T> Split(const std::span<const char>& sp, char sep, bool include_sep = false)
148 : : {
149 : 25068 : return Split<T>(sp, std::string_view{&sep, 1}, include_sep);
150 : : }
151 : :
152 : 21819 : [[nodiscard]] inline std::vector<std::string> SplitString(std::string_view str, char sep)
153 : : {
154 [ # # # # : 21819 : return Split<std::string>(str, sep);
# # # # #
# ][ + - #
# # # # #
# # ]
[ # # # # ]
[ - - - -
+ - + - ]
155 : : }
156 : :
157 : 2765 : [[nodiscard]] inline std::vector<std::string> SplitString(std::string_view str, std::string_view separators)
158 : : {
159 [ + - ][ + - : 2765 : return Split<std::string>(str, separators);
+ - + - +
- + - + -
+ - ]
160 : : }
161 : :
162 : 663328 : [[nodiscard]] inline std::string_view TrimStringView(std::string_view str, std::string_view pattern = " \f\n\r\t\v")
163 : : {
164 : 663328 : std::string::size_type front = str.find_first_not_of(pattern);
165 [ + + ]: 663328 : if (front == std::string::npos) {
166 : 37 : return {};
167 : : }
168 : 663291 : std::string::size_type end = str.find_last_not_of(pattern);
169 : 663291 : return str.substr(front, end - front + 1);
170 : : }
171 : :
172 : 371601 : [[nodiscard]] inline std::string TrimString(std::string_view str, std::string_view pattern = " \f\n\r\t\v")
173 : : {
174 : 371601 : return std::string(TrimStringView(str, pattern));
175 : : }
176 : :
177 : 1905 : [[nodiscard]] inline std::string_view RemoveSuffixView(std::string_view str, std::string_view suffix)
178 : : {
179 [ + + ]: 1905 : if (str.ends_with(suffix)) {
180 : 1010 : return str.substr(0, str.size() - suffix.size());
181 : : }
182 : 895 : return str;
183 : : }
184 : :
185 : 364484 : [[nodiscard]] inline std::string_view RemovePrefixView(std::string_view str, std::string_view prefix)
186 : : {
187 [ + + ]: 364484 : if (str.starts_with(prefix)) {
188 : 364457 : return str.substr(prefix.size());
189 : : }
190 : 27 : return str;
191 : : }
192 : :
193 : 11 : [[nodiscard]] inline std::string RemovePrefix(std::string_view str, std::string_view prefix)
194 : : {
195 : 11 : return std::string(RemovePrefixView(str, prefix));
196 : : }
197 : :
198 : : /**
199 : : * Join all container items. Typically used to concatenate strings but accepts
200 : : * containers with elements of any type.
201 : : *
202 : : * @param container The items to join
203 : : * @param separator The separator
204 : : * @param unary_op Apply this operator to each item
205 : : */
206 : : template <typename C, typename S, typename UnaryOp>
207 : : // NOLINTNEXTLINE(misc-no-recursion)
208 : 18097 : auto Join(const C& container, const S& separator, UnaryOp unary_op)
209 : : {
210 : 18097 : decltype(unary_op(*container.begin())) ret;
211 : 18097 : bool first{true};
212 [ + + ]: 101322 : for (const auto& item : container) {
213 [ + + ]: 126256 : if (!first) ret += separator;
[ + + + + ]
214 [ + + ]: 136695 : ret += unary_op(item);
[ + - - + ]
215 : 83225 : first = false;
216 : : }
217 : 18097 : return ret;
218 : 0 : }
219 : :
220 : : template <typename C, typename S>
221 : 13840 : auto Join(const C& container, const S& separator)
222 : : {
223 [ - + - + ]: 73350 : return Join(container, separator, [](const auto& i) { return i; });
[ - - ]
224 : : }
225 : :
226 : : /**
227 : : * Create an unordered multi-line list of items.
228 : : */
229 : 80 : inline std::string MakeUnorderedList(const std::vector<std::string>& items)
230 : : {
231 [ + - + - ]: 560 : return Join(items, "\n", [](const std::string& item) { return "- " + item; });
232 : : }
233 : :
234 : : /**
235 : : * Check if a string does not contain any embedded NUL (\0) characters
236 : : */
237 : 44766 : [[nodiscard]] inline bool ContainsNoNUL(std::string_view str) noexcept
238 : : {
239 [ + + + + : 703147 : for (auto c : str) {
+ + + + +
+ ]
[ + + + + ]
[ + + ]
240 [ + + + - : 658403 : if (c == 0) return false;
+ + + + +
- ]
[ + + + + ]
[ + + ]
241 : : }
242 : : return true;
243 : : }
244 : :
245 : : /**
246 : : * Locale-independent version of std::to_string
247 : : */
248 : : template <typename T>
249 : 224959 : std::string ToString(const T& t)
250 : : {
251 : 224959 : std::ostringstream oss;
252 [ + - + - ]: 224959 : oss.imbue(std::locale::classic());
253 [ + - + - ]: 224959 : oss << t;
[ # # ]
254 : 224959 : return oss.str();
255 : 224959 : }
256 : :
257 : : /**
258 : : * Check whether a container begins with the given prefix.
259 : : */
260 : : template <typename T1, size_t PREFIX_LEN>
261 [ + + ]: 720392 : [[nodiscard]] inline bool HasPrefix(const T1& obj,
262 : : const std::array<uint8_t, PREFIX_LEN>& prefix)
263 : : {
264 [ + - + + ]: 1438840 : return obj.size() >= PREFIX_LEN &&
265 [ + + ]: 720392 : std::equal(std::begin(prefix), std::end(prefix), std::begin(obj));
266 : : }
267 : :
268 : : struct LineReader {
269 : : const std::span<const std::byte>::iterator start;
270 : : const std::span<const std::byte>::iterator end;
271 : : const size_t max_line_length;
272 : : std::span<const std::byte>::iterator it;
273 : :
274 : : explicit LineReader(std::span<const std::byte> buffer, size_t max_line_length);
275 : 49 : explicit LineReader(std::string_view str, size_t max_line_length) : LineReader{std::as_bytes(std::span{str}), max_line_length} {}
276 : :
277 : : /**
278 : : * Returns a string from current iterator position up to (but not including) next \n
279 : : * and advances iterator to the character following the \n on success.
280 : : * Will not return a line longer than max_line_length.
281 : : * @returns the next string from the buffer.
282 : : * std::nullopt if end of buffer is reached without finding a \n.
283 : : * @throws a std::runtime_error if max_line_length + 1 bytes are read without finding \n.
284 : : */
285 : : std::optional<std::string_view> ReadLine() LIFETIMEBOUND;
286 : :
287 : : /**
288 : : * Returns string from current iterator position of specified length
289 : : * if possible and advances iterator on success.
290 : : * May exceed max_line_length but will not read past end of buffer.
291 : : * @param[in] len The number of bytes to read from the buffer
292 : : * @returns a string of the expected length.
293 : : * @throws a std::runtime_error if there is not enough data in the buffer.
294 : : */
295 : : std::string_view ReadLength(size_t len) LIFETIMEBOUND;
296 : :
297 : : /**
298 : : * Returns remaining size of bytes in buffer
299 : : */
300 : : size_t Remaining() const;
301 : :
302 : : /**
303 : : * Returns number of bytes already read from buffer
304 : : */
305 : : size_t Consumed() const;
306 : : };
307 : : } // namespace util
308 : :
309 : : #endif // BITCOIN_UTIL_STRING_H
|