• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 #ifndef ABSL_STRINGS_INTERNAL_STR_FORMAT_CHECKER_H_
2 #define ABSL_STRINGS_INTERNAL_STR_FORMAT_CHECKER_H_
3 
4 #include "absl/base/attributes.h"
5 #include "absl/strings/internal/str_format/arg.h"
6 #include "absl/strings/internal/str_format/extension.h"
7 
8 // Compile time check support for entry points.
9 
10 #ifndef ABSL_INTERNAL_ENABLE_FORMAT_CHECKER
11 #if ABSL_HAVE_ATTRIBUTE(enable_if) && !defined(__native_client__)
12 #define ABSL_INTERNAL_ENABLE_FORMAT_CHECKER 1
13 #endif  // ABSL_HAVE_ATTRIBUTE(enable_if) && !defined(__native_client__)
14 #endif  // ABSL_INTERNAL_ENABLE_FORMAT_CHECKER
15 
16 namespace absl {
17 ABSL_NAMESPACE_BEGIN
18 namespace str_format_internal {
19 
AllOf()20 constexpr bool AllOf() { return true; }
21 
22 template <typename... T>
AllOf(bool b,T...t)23 constexpr bool AllOf(bool b, T... t) {
24   return b && AllOf(t...);
25 }
26 
27 template <typename Arg>
ArgumentToConv()28 constexpr 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 #ifdef ABSL_INTERNAL_ENABLE_FORMAT_CHECKER
35 
ContainsChar(const char * chars,char c)36 constexpr 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.
41 struct 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 
without_frontConvList52   constexpr ConvList without_front() const {
53     return count != 0 ? ConvList{array + 1, count - 1} : *this;
54   }
55 };
56 
57 template <size_t count>
58 struct ConvListT {
59   // Make sure the array has size > 0.
60   Conv list[count ? count : 1];
61 };
62 
GetChar(string_view str,size_t index)63 constexpr char GetChar(string_view str, size_t index) {
64   return index < str.size() ? str[index] : char{};
65 }
66 
67 constexpr 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 
ConsumeAnyOf(string_view format,const char * chars)72 constexpr 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 
IsDigit(char c)78 constexpr 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.
82 struct 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.
ConsumePositionalDollarInteger88   constexpr Integer ConsumePositionalDollar() const {
89     return GetChar(format, 0) == '$' ? Integer{ConsumeFront(format), value}
90                                      : Integer{format, 0};
91   }
92 };
93 
94 constexpr 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 '$'.
ParsePositional(string_view format)103 constexpr 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.
109 class ConvParser {
SetFormat(string_view format)110   constexpr ConvParser SetFormat(string_view format) const {
111     return ConvParser(format, args_, error_, arg_position_, is_positional_);
112   }
113 
SetArgs(ConvList args)114   constexpr ConvParser SetArgs(ConvList args) const {
115     return ConvParser(format_, args, error_, arg_position_, is_positional_);
116   }
117 
SetError(bool error)118   constexpr ConvParser SetError(bool error) const {
119     return ConvParser(format_, args_, error_ || error, arg_position_,
120                       is_positional_);
121   }
122 
SetArgPosition(int arg_position)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`.
ConsumeNextArg(char 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.
VerifyPositional(Integer i,char conv)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_`.
ParseArgPosition(Integer arg)141   constexpr ConvParser ParseArgPosition(Integer arg) const {
142     return SetFormat(arg.format).SetArgPosition(arg.value);
143   }
144 
145   // Consume the flags.
ParseFlags()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.
ParseWidth()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.
ParsePrecision()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.
ParseLength()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.
ParseConversion()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 
ConvParser(string_view format,ConvList args,bool error,int arg_position,bool is_positional)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:
ConvParser(string_view format,ConvList args,bool is_positional)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.
Run()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 
format()223   constexpr string_view format() const { return format_; }
args()224   constexpr ConvList args() const { return args_; }
error()225   constexpr bool error() const { return error_; }
is_positional()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().
244 class FormatParser {
FoundPercent(string_view format)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 %%.
ConsumeNonPercent(string_view format)267   static constexpr string_view ConsumeNonPercent(string_view format) {
268     return FoundPercent(format)
269                ? format
270                : ConsumeNonPercent(ConsumeNonPercentInner(format));
271   }
272 
IsPositional(string_view format)273   static constexpr bool IsPositional(string_view format) {
274     return IsDigit(GetChar(format, 0)) ? IsPositional(ConsumeFront(format))
275                                        : GetChar(format, 0) == '$';
276   }
277 
RunImpl(bool is_positional)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 
ValidateArg(ConvParser conv)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:
FormatParser(string_view format,ConvList args)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.
Run()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 
313 template <Conv... C>
ValidFormatImpl(string_view format)314 constexpr 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 ABSL_NAMESPACE_END
324 }  // namespace absl
325 
326 #endif  // ABSL_STRINGS_INTERNAL_STR_FORMAT_CHECKER_H_
327