| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333 | // Copyright 2020 The Abseil Authors.//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at////      https://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License.#ifndef ABSL_STRINGS_INTERNAL_STR_FORMAT_CHECKER_H_#define ABSL_STRINGS_INTERNAL_STR_FORMAT_CHECKER_H_#include "absl/base/attributes.h"#include "absl/strings/internal/str_format/arg.h"#include "absl/strings/internal/str_format/extension.h"// Compile time check support for entry points.#ifndef ABSL_INTERNAL_ENABLE_FORMAT_CHECKER#if ABSL_HAVE_ATTRIBUTE(enable_if) && !defined(__native_client__)#define ABSL_INTERNAL_ENABLE_FORMAT_CHECKER 1#endif  // ABSL_HAVE_ATTRIBUTE(enable_if) && !defined(__native_client__)#endif  // ABSL_INTERNAL_ENABLE_FORMAT_CHECKERnamespace absl {ABSL_NAMESPACE_BEGINnamespace str_format_internal {constexpr bool AllOf() { return true; }template <typename... T>constexpr bool AllOf(bool b, T... t) {  return b && AllOf(t...);}#ifdef ABSL_INTERNAL_ENABLE_FORMAT_CHECKERconstexpr bool ContainsChar(const char* chars, char c) {  return *chars == c || (*chars && ContainsChar(chars + 1, c));}// A constexpr compatible list of Convs.struct ConvList {  const FormatConversionCharSet* array;  int count;  // We do the bound check here to avoid having to do it on the callers.  // Returning an empty FormatConversionCharSet has the same effect as  // short circuiting because it will never match any conversion.  constexpr FormatConversionCharSet operator[](int i) const {    return i < count ? array[i] : FormatConversionCharSet{};  }  constexpr ConvList without_front() const {    return count != 0 ? ConvList{array + 1, count - 1} : *this;  }};template <size_t count>struct ConvListT {  // Make sure the array has size > 0.  FormatConversionCharSet list[count ? count : 1];};constexpr char GetChar(string_view str, size_t index) {  return index < str.size() ? str[index] : char{};}constexpr string_view ConsumeFront(string_view str, size_t len = 1) {  return len <= str.size() ? string_view(str.data() + len, str.size() - len)                           : string_view();}constexpr string_view ConsumeAnyOf(string_view format, const char* chars) {  return ContainsChar(chars, GetChar(format, 0))             ? ConsumeAnyOf(ConsumeFront(format), chars)             : format;}constexpr bool IsDigit(char c) { return c >= '0' && c <= '9'; }// Helper class for the ParseDigits function.// It encapsulates the two return values we need there.struct Integer {  string_view format;  int value;  // If the next character is a '$', consume it.  // Otherwise, make `this` an invalid positional argument.  constexpr Integer ConsumePositionalDollar() const {    return GetChar(format, 0) == '$' ? Integer{ConsumeFront(format), value}                                     : Integer{format, 0};  }};constexpr Integer ParseDigits(string_view format, int value = 0) {  return IsDigit(GetChar(format, 0))             ? ParseDigits(ConsumeFront(format),                           10 * value + GetChar(format, 0) - '0')             : Integer{format, value};}// Parse digits for a positional argument.// The parsing also consumes the '$'.constexpr Integer ParsePositional(string_view format) {  return ParseDigits(format).ConsumePositionalDollar();}// Parses a single conversion specifier.// See ConvParser::Run() for post conditions.class ConvParser {  constexpr ConvParser SetFormat(string_view format) const {    return ConvParser(format, args_, error_, arg_position_, is_positional_);  }  constexpr ConvParser SetArgs(ConvList args) const {    return ConvParser(format_, args, error_, arg_position_, is_positional_);  }  constexpr ConvParser SetError(bool error) const {    return ConvParser(format_, args_, error_ || error, arg_position_,                      is_positional_);  }  constexpr ConvParser SetArgPosition(int arg_position) const {    return ConvParser(format_, args_, error_, arg_position, is_positional_);  }  // Consumes the next arg and verifies that it matches `conv`.  // `error_` is set if there is no next arg or if it doesn't match `conv`.  constexpr ConvParser ConsumeNextArg(char conv) const {    return SetArgs(args_.without_front()).SetError(!Contains(args_[0], conv));  }  // Verify that positional argument `i.value` matches `conv`.  // `error_` is set if `i.value` is not a valid argument or if it doesn't  // match.  constexpr ConvParser VerifyPositional(Integer i, char conv) const {    return SetFormat(i.format).SetError(!Contains(args_[i.value - 1], conv));  }  // Parse the position of the arg and store it in `arg_position_`.  constexpr ConvParser ParseArgPosition(Integer arg) const {    return SetFormat(arg.format).SetArgPosition(arg.value);  }  // Consume the flags.  constexpr ConvParser ParseFlags() const {    return SetFormat(ConsumeAnyOf(format_, "-+ #0"));  }  // Consume the width.  // If it is '*', we verify that it matches `args_`. `error_` is set if it  // doesn't match.  constexpr ConvParser ParseWidth() const {    return IsDigit(GetChar(format_, 0))               ? SetFormat(ParseDigits(format_).format)               : GetChar(format_, 0) == '*'                     ? is_positional_                           ? VerifyPositional(                                 ParsePositional(ConsumeFront(format_)), '*')                           : SetFormat(ConsumeFront(format_))                                 .ConsumeNextArg('*')                     : *this;  }  // Consume the precision.  // If it is '*', we verify that it matches `args_`. `error_` is set if it  // doesn't match.  constexpr ConvParser ParsePrecision() const {    return GetChar(format_, 0) != '.'               ? *this               : GetChar(format_, 1) == '*'                     ? is_positional_                           ? VerifyPositional(                                 ParsePositional(ConsumeFront(format_, 2)), '*')                           : SetFormat(ConsumeFront(format_, 2))                                 .ConsumeNextArg('*')                     : SetFormat(ParseDigits(ConsumeFront(format_)).format);  }  // Consume the length characters.  constexpr ConvParser ParseLength() const {    return SetFormat(ConsumeAnyOf(format_, "lLhjztq"));  }  // Consume the conversion character and verify that it matches `args_`.  // `error_` is set if it doesn't match.  constexpr ConvParser ParseConversion() const {    return is_positional_               ? VerifyPositional({ConsumeFront(format_), arg_position_},                                  GetChar(format_, 0))               : ConsumeNextArg(GetChar(format_, 0))                     .SetFormat(ConsumeFront(format_));  }  constexpr ConvParser(string_view format, ConvList args, bool error,                       int arg_position, bool is_positional)      : format_(format),        args_(args),        error_(error),        arg_position_(arg_position),        is_positional_(is_positional) {} public:  constexpr ConvParser(string_view format, ConvList args, bool is_positional)      : format_(format),        args_(args),        error_(false),        arg_position_(0),        is_positional_(is_positional) {}  // Consume the whole conversion specifier.  // `format()` will be set to the character after the conversion character.  // `error()` will be set if any of the arguments do not match.  constexpr ConvParser Run() const {    return (is_positional_ ? ParseArgPosition(ParsePositional(format_)) : *this)        .ParseFlags()        .ParseWidth()        .ParsePrecision()        .ParseLength()        .ParseConversion();  }  constexpr string_view format() const { return format_; }  constexpr ConvList args() const { return args_; }  constexpr bool error() const { return error_; }  constexpr bool is_positional() const { return is_positional_; } private:  string_view format_;  // Current list of arguments. If we are not in positional mode we will consume  // from the front.  ConvList args_;  bool error_;  // Holds the argument position of the conversion character, if we are in  // positional mode. Otherwise, it is unspecified.  int arg_position_;  // Whether we are in positional mode.  // It changes the behavior of '*' and where to find the converted argument.  bool is_positional_;};// Parses a whole format expression.// See FormatParser::Run().class FormatParser {  static constexpr bool FoundPercent(string_view format) {    return format.empty() ||           (GetChar(format, 0) == '%' && GetChar(format, 1) != '%');  }  // We use an inner function to increase the recursion limit.  // The inner function consumes up to `limit` characters on every run.  // This increases the limit from 512 to ~512*limit.  static constexpr string_view ConsumeNonPercentInner(string_view format,                                                      int limit = 20) {    return FoundPercent(format) || !limit               ? format               : ConsumeNonPercentInner(                     ConsumeFront(format, GetChar(format, 0) == '%' &&                                                  GetChar(format, 1) == '%'                                              ? 2                                              : 1),                     limit - 1);  }  // Consume characters until the next conversion spec %.  // It skips %%.  static constexpr string_view ConsumeNonPercent(string_view format) {    return FoundPercent(format)               ? format               : ConsumeNonPercent(ConsumeNonPercentInner(format));  }  static constexpr bool IsPositional(string_view format) {    return IsDigit(GetChar(format, 0)) ? IsPositional(ConsumeFront(format))                                       : GetChar(format, 0) == '$';  }  constexpr bool RunImpl(bool is_positional) const {    // In non-positional mode we require all arguments to be consumed.    // In positional mode just reaching the end of the format without errors is    // enough.    return (format_.empty() && (is_positional || args_.count == 0)) ||           (!format_.empty() &&            ValidateArg(                ConvParser(ConsumeFront(format_), args_, is_positional).Run()));  }  constexpr bool ValidateArg(ConvParser conv) const {    return !conv.error() && FormatParser(conv.format(), conv.args())                                .RunImpl(conv.is_positional());  } public:  constexpr FormatParser(string_view format, ConvList args)      : format_(ConsumeNonPercent(format)), args_(args) {}  // Runs the parser for `format` and `args`.  // It verifies that the format is valid and that all conversion specifiers  // match the arguments passed.  // In non-positional mode it also verfies that all arguments are consumed.  constexpr bool Run() const {    return RunImpl(!format_.empty() && IsPositional(ConsumeFront(format_)));  } private:  string_view format_;  // Current list of arguments.  // If we are not in positional mode we will consume from the front and will  // have to be empty in the end.  ConvList args_;};template <FormatConversionCharSet... C>constexpr bool ValidFormatImpl(string_view format) {  return FormatParser(format,                      {ConvListT<sizeof...(C)>{{C...}}.list, sizeof...(C)})      .Run();}#endif  // ABSL_INTERNAL_ENABLE_FORMAT_CHECKER}  // namespace str_format_internalABSL_NAMESPACE_END}  // namespace absl#endif  // ABSL_STRINGS_INTERNAL_STR_FORMAT_CHECKER_H_
 |