1#ifndef ABSL_STRINGS_INTERNAL_STR_FORMAT_CHECKER_H_
2#define ABSL_STRINGS_INTERNAL_STR_FORMAT_CHECKER_H_
3
4#include "absl/strings/internal/str_format/arg.h"
5#include "absl/strings/internal/str_format/extension.h"
6
7// Compile time check support for entry points.
8
9#ifndef ABSL_INTERNAL_ENABLE_FORMAT_CHECKER
10#if defined(__clang__) && !defined(__native_client__)
11#if __has_attribute(enable_if)
12#define ABSL_INTERNAL_ENABLE_FORMAT_CHECKER 1
13#endif // __has_attribute(enable_if)
14#endif // defined(__clang__) && !defined(__native_client__)
15#endif // ABSL_INTERNAL_ENABLE_FORMAT_CHECKER
16
17namespace absl {
18namespace str_format_internal {
19
20constexpr bool AllOf() { return true; }
21
22template <typename... T>
23constexpr bool AllOf(bool b, T... t) {
24 return b && AllOf(t...);
25}
26
27template <typename Arg>
28constexpr Conv ArgumentToConv() {
29 return decltype(str_format_internal::FormatConvertImpl(
30 std::declval<const Arg&>(), std::declval<const ConversionSpec&>(),
31 std::declval<FormatSinkImpl*>()))::kConv;
32}
33
34#if ABSL_INTERNAL_ENABLE_FORMAT_CHECKER
35
36constexpr bool ContainsChar(const char* chars, char c) {
37 return *chars == c || (*chars && ContainsChar(chars + 1, c));
38}
39
40// A constexpr compatible list of Convs.
41struct ConvList {
42 const Conv* array;
43 int count;
44
45 // We do the bound check here to avoid having to do it on the callers.
46 // Returning an empty Conv has the same effect as short circuiting because it
47 // will never match any conversion.
48 constexpr Conv operator[](int i) const {
49 return i < count ? array[i] : Conv{};
50 }
51
52 constexpr ConvList without_front() const {
53 return count != 0 ? ConvList{array + 1, count - 1} : *this;
54 }
55};
56
57template <size_t count>
58struct ConvListT {
59 // Make sure the array has size > 0.
60 Conv list[count ? count : 1];
61};
62
63constexpr char GetChar(string_view str, size_t index) {
64 return index < str.size() ? str[index] : char{};
65}
66
67constexpr string_view ConsumeFront(string_view str, size_t len = 1) {
68 return len <= str.size() ? string_view(str.data() + len, str.size() - len)
69 : string_view();
70}
71
72constexpr string_view ConsumeAnyOf(string_view format, const char* chars) {
73 return ContainsChar(chars, GetChar(format, 0))
74 ? ConsumeAnyOf(ConsumeFront(format), chars)
75 : format;
76}
77
78constexpr bool IsDigit(char c) { return c >= '0' && c <= '9'; }
79
80// Helper class for the ParseDigits function.
81// It encapsulates the two return values we need there.
82struct Integer {
83 string_view format;
84 int value;
85
86 // If the next character is a '$', consume it.
87 // Otherwise, make `this` an invalid positional argument.
88 constexpr Integer ConsumePositionalDollar() const {
89 return GetChar(format, 0) == '$' ? Integer{ConsumeFront(format), value}
90 : Integer{format, 0};
91 }
92};
93
94constexpr Integer ParseDigits(string_view format, int value = 0) {
95 return IsDigit(GetChar(format, 0))
96 ? ParseDigits(ConsumeFront(format),
97 10 * value + GetChar(format, 0) - '0')
98 : Integer{format, value};
99}
100
101// Parse digits for a positional argument.
102// The parsing also consumes the '$'.
103constexpr Integer ParsePositional(string_view format) {
104 return ParseDigits(format).ConsumePositionalDollar();
105}
106
107// Parses a single conversion specifier.
108// See ConvParser::Run() for post conditions.
109class ConvParser {
110 constexpr ConvParser SetFormat(string_view format) const {
111 return ConvParser(format, args_, error_, arg_position_, is_positional_);
112 }
113
114 constexpr ConvParser SetArgs(ConvList args) const {
115 return ConvParser(format_, args, error_, arg_position_, is_positional_);
116 }
117
118 constexpr ConvParser SetError(bool error) const {
119 return ConvParser(format_, args_, error_ || error, arg_position_,
120 is_positional_);
121 }
122
123 constexpr ConvParser SetArgPosition(int arg_position) const {
124 return ConvParser(format_, args_, error_, arg_position, is_positional_);
125 }
126
127 // Consumes the next arg and verifies that it matches `conv`.
128 // `error_` is set if there is no next arg or if it doesn't match `conv`.
129 constexpr ConvParser ConsumeNextArg(char conv) const {
130 return SetArgs(args_.without_front()).SetError(!Contains(args_[0], conv));
131 }
132
133 // Verify that positional argument `i.value` matches `conv`.
134 // `error_` is set if `i.value` is not a valid argument or if it doesn't
135 // match.
136 constexpr ConvParser VerifyPositional(Integer i, char conv) const {
137 return SetFormat(i.format).SetError(!Contains(args_[i.value - 1], conv));
138 }
139
140 // Parse the position of the arg and store it in `arg_position_`.
141 constexpr ConvParser ParseArgPosition(Integer arg) const {
142 return SetFormat(arg.format).SetArgPosition(arg.value);
143 }
144
145 // Consume the flags.
146 constexpr ConvParser ParseFlags() const {
147 return SetFormat(ConsumeAnyOf(format_, "-+ #0"));
148 }
149
150 // Consume the width.
151 // If it is '*', we verify that it matches `args_`. `error_` is set if it
152 // doesn't match.
153 constexpr ConvParser ParseWidth() const {
154 return IsDigit(GetChar(format_, 0))
155 ? SetFormat(ParseDigits(format_).format)
156 : GetChar(format_, 0) == '*'
157 ? is_positional_
158 ? VerifyPositional(
159 ParsePositional(ConsumeFront(format_)), '*')
160 : SetFormat(ConsumeFront(format_))
161 .ConsumeNextArg('*')
162 : *this;
163 }
164
165 // Consume the precision.
166 // If it is '*', we verify that it matches `args_`. `error_` is set if it
167 // doesn't match.
168 constexpr ConvParser ParsePrecision() const {
169 return GetChar(format_, 0) != '.'
170 ? *this
171 : GetChar(format_, 1) == '*'
172 ? is_positional_
173 ? VerifyPositional(
174 ParsePositional(ConsumeFront(format_, 2)), '*')
175 : SetFormat(ConsumeFront(format_, 2))
176 .ConsumeNextArg('*')
177 : SetFormat(ParseDigits(ConsumeFront(format_)).format);
178 }
179
180 // Consume the length characters.
181 constexpr ConvParser ParseLength() const {
182 return SetFormat(ConsumeAnyOf(format_, "lLhjztq"));
183 }
184
185 // Consume the conversion character and verify that it matches `args_`.
186 // `error_` is set if it doesn't match.
187 constexpr ConvParser ParseConversion() const {
188 return is_positional_
189 ? VerifyPositional({ConsumeFront(format_), arg_position_},
190 GetChar(format_, 0))
191 : ConsumeNextArg(GetChar(format_, 0))
192 .SetFormat(ConsumeFront(format_));
193 }
194
195 constexpr ConvParser(string_view format, ConvList args, bool error,
196 int arg_position, bool is_positional)
197 : format_(format),
198 args_(args),
199 error_(error),
200 arg_position_(arg_position),
201 is_positional_(is_positional) {}
202
203 public:
204 constexpr ConvParser(string_view format, ConvList args, bool is_positional)
205 : format_(format),
206 args_(args),
207 error_(false),
208 arg_position_(0),
209 is_positional_(is_positional) {}
210
211 // Consume the whole conversion specifier.
212 // `format()` will be set to the character after the conversion character.
213 // `error()` will be set if any of the arguments do not match.
214 constexpr ConvParser Run() const {
215 return (is_positional_ ? ParseArgPosition(ParsePositional(format_)) : *this)
216 .ParseFlags()
217 .ParseWidth()
218 .ParsePrecision()
219 .ParseLength()
220 .ParseConversion();
221 }
222
223 constexpr string_view format() const { return format_; }
224 constexpr ConvList args() const { return args_; }
225 constexpr bool error() const { return error_; }
226 constexpr bool is_positional() const { return is_positional_; }
227
228 private:
229 string_view format_;
230 // Current list of arguments. If we are not in positional mode we will consume
231 // from the front.
232 ConvList args_;
233 bool error_;
234 // Holds the argument position of the conversion character, if we are in
235 // positional mode. Otherwise, it is unspecified.
236 int arg_position_;
237 // Whether we are in positional mode.
238 // It changes the behavior of '*' and where to find the converted argument.
239 bool is_positional_;
240};
241
242// Parses a whole format expression.
243// See FormatParser::Run().
244class FormatParser {
245 static constexpr bool FoundPercent(string_view format) {
246 return format.empty() ||
247 (GetChar(format, 0) == '%' && GetChar(format, 1) != '%');
248 }
249
250 // We use an inner function to increase the recursion limit.
251 // The inner function consumes up to `limit` characters on every run.
252 // This increases the limit from 512 to ~512*limit.
253 static constexpr string_view ConsumeNonPercentInner(string_view format,
254 int limit = 20) {
255 return FoundPercent(format) || !limit
256 ? format
257 : ConsumeNonPercentInner(
258 ConsumeFront(format, GetChar(format, 0) == '%' &&
259 GetChar(format, 1) == '%'
260 ? 2
261 : 1),
262 limit - 1);
263 }
264
265 // Consume characters until the next conversion spec %.
266 // It skips %%.
267 static constexpr string_view ConsumeNonPercent(string_view format) {
268 return FoundPercent(format)
269 ? format
270 : ConsumeNonPercent(ConsumeNonPercentInner(format));
271 }
272
273 static constexpr bool IsPositional(string_view format) {
274 return IsDigit(GetChar(format, 0)) ? IsPositional(ConsumeFront(format))
275 : GetChar(format, 0) == '$';
276 }
277
278 constexpr bool RunImpl(bool is_positional) const {
279 // In non-positional mode we require all arguments to be consumed.
280 // In positional mode just reaching the end of the format without errors is
281 // enough.
282 return (format_.empty() && (is_positional || args_.count == 0)) ||
283 (!format_.empty() &&
284 ValidateArg(
285 ConvParser(ConsumeFront(format_), args_, is_positional).Run()));
286 }
287
288 constexpr bool ValidateArg(ConvParser conv) const {
289 return !conv.error() && FormatParser(conv.format(), conv.args())
290 .RunImpl(conv.is_positional());
291 }
292
293 public:
294 constexpr FormatParser(string_view format, ConvList args)
295 : format_(ConsumeNonPercent(format)), args_(args) {}
296
297 // Runs the parser for `format` and `args`.
298 // It verifies that the format is valid and that all conversion specifiers
299 // match the arguments passed.
300 // In non-positional mode it also verfies that all arguments are consumed.
301 constexpr bool Run() const {
302 return RunImpl(!format_.empty() && IsPositional(ConsumeFront(format_)));
303 }
304
305 private:
306 string_view format_;
307 // Current list of arguments.
308 // If we are not in positional mode we will consume from the front and will
309 // have to be empty in the end.
310 ConvList args_;
311};
312
313template <Conv... C>
314constexpr bool ValidFormatImpl(string_view format) {
315 return FormatParser(format,
316 {ConvListT<sizeof...(C)>{{C...}}.list, sizeof...(C)})
317 .Run();
318}
319
320#endif // ABSL_INTERNAL_ENABLE_FORMAT_CHECKER
321
322} // namespace str_format_internal
323} // namespace absl
324
325#endif // ABSL_STRINGS_INTERNAL_STR_FORMAT_CHECKER_H_
326