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