tvl-depot/absl/strings/internal/str_format/checker.h

325 lines
11 KiB
C
Raw Normal View History

#ifndef ABSL_STRINGS_INTERNAL_STR_FORMAT_CHECKER_H_
#define ABSL_STRINGS_INTERNAL_STR_FORMAT_CHECKER_H_
Export of internal Abseil changes -- 693f81830b9f9cc8b24a1f38492b8dfcdd1d0e24 by Abseil Team <absl-team@google.com>: Check that absl::int128 works as a std::chrono::duration::rep. In particular, validate that ... std::chrono::time_point<std::chrono::system_clock, std::chrono::duration<absl::int128, std::atto>> is a superset (range and resolution) of absl::Time. PiperOrigin-RevId: 283370280 -- df6073b686bd44223c6f9070fcceec918c728871 by Gennadiy Rozental <rogeeff@google.com>: Changes thread annotations to use DataGuard() function instead of a specific Mutex. Remove unused declaration of InvokeCallback. PiperOrigin-RevId: 283361188 -- b49eb2dd2ee1a0b4c8a7bb1a94e368b81ce5f861 by Abseil Team <absl-team@google.com>: Rewrite GetNominalCPUFrequency to use advapi32 instead of shlwapi Using shlwapi.dll means that gdi32.dll is loaded which then makes process destruction more expensive, which is unacceptable for some uses. There may be other places that pull in gdi32.dll - this just fixes the one. PiperOrigin-RevId: 282960698 -- b5508afec5099a0fdbb55e39a7cd2993259ed860 by Abseil Team <absl-team@google.com>: Small typo fix in comments: initiazliation -> initialization PiperOrigin-RevId: 282891800 -- 4319cc419584e91ee74f6ae1a32d88a412fc5c01 by Abseil Team <absl-team@google.com>: Update c_find_first_of() comment to remove the mention of an ordered container. PiperOrigin-RevId: 282836540 -- 5fcabc0a834dff39a505d5a5fc5403ddeb96028e by Derek Mauro <dmauro@google.com>: Fix NaCl build, where format checking is broken PiperOrigin-RevId: 282826202 -- aaf9ad3274c056a2f68e9b8ccada45c9802e2f1e by Derek Mauro <dmauro@google.com>: Fix more -Wundef warnings PiperOrigin-RevId: 282799820 -- 1fb06150a70ffe98bf4b2d42b2a39d083bf44f8c by Derek Mauro <dmauro@google.com>: Release support for additional platforms PiperOrigin-RevId: 282793384 -- fa947fc28624a316fa872d7045b3838b88a0d69b by Derek Mauro <dmauro@google.com>: Cleanup inconsistent usage of __has_attribute PiperOrigin-RevId: 282793296 -- 990030ad282263d6303c83b780a55fdec8e90d43 by Gennadiy Rozental <rogeeff@google.com>: Eliminate the pointer in absl::Flag, which points to n space where we were storing flag's default value. We also eliminate additional (now unnecessary) allocation for flag's default value. Instead we'll initialize the flags value directly from the value specified in ABSL_FLAG. If the default value is updated via the call to SetCommandLineOptionWithMode we are replacing pointer to initialization routine to pointer to new default value. PiperOrigin-RevId: 282637616 GitOrigin-RevId: 693f81830b9f9cc8b24a1f38492b8dfcdd1d0e24 Change-Id: I6f2edd8ef844de09aa2c182a7ca3133a22364792
2019-12-02 19:41:53 +01:00
#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
Export of internal Abseil changes -- 693f81830b9f9cc8b24a1f38492b8dfcdd1d0e24 by Abseil Team <absl-team@google.com>: Check that absl::int128 works as a std::chrono::duration::rep. In particular, validate that ... std::chrono::time_point<std::chrono::system_clock, std::chrono::duration<absl::int128, std::atto>> is a superset (range and resolution) of absl::Time. PiperOrigin-RevId: 283370280 -- df6073b686bd44223c6f9070fcceec918c728871 by Gennadiy Rozental <rogeeff@google.com>: Changes thread annotations to use DataGuard() function instead of a specific Mutex. Remove unused declaration of InvokeCallback. PiperOrigin-RevId: 283361188 -- b49eb2dd2ee1a0b4c8a7bb1a94e368b81ce5f861 by Abseil Team <absl-team@google.com>: Rewrite GetNominalCPUFrequency to use advapi32 instead of shlwapi Using shlwapi.dll means that gdi32.dll is loaded which then makes process destruction more expensive, which is unacceptable for some uses. There may be other places that pull in gdi32.dll - this just fixes the one. PiperOrigin-RevId: 282960698 -- b5508afec5099a0fdbb55e39a7cd2993259ed860 by Abseil Team <absl-team@google.com>: Small typo fix in comments: initiazliation -> initialization PiperOrigin-RevId: 282891800 -- 4319cc419584e91ee74f6ae1a32d88a412fc5c01 by Abseil Team <absl-team@google.com>: Update c_find_first_of() comment to remove the mention of an ordered container. PiperOrigin-RevId: 282836540 -- 5fcabc0a834dff39a505d5a5fc5403ddeb96028e by Derek Mauro <dmauro@google.com>: Fix NaCl build, where format checking is broken PiperOrigin-RevId: 282826202 -- aaf9ad3274c056a2f68e9b8ccada45c9802e2f1e by Derek Mauro <dmauro@google.com>: Fix more -Wundef warnings PiperOrigin-RevId: 282799820 -- 1fb06150a70ffe98bf4b2d42b2a39d083bf44f8c by Derek Mauro <dmauro@google.com>: Release support for additional platforms PiperOrigin-RevId: 282793384 -- fa947fc28624a316fa872d7045b3838b88a0d69b by Derek Mauro <dmauro@google.com>: Cleanup inconsistent usage of __has_attribute PiperOrigin-RevId: 282793296 -- 990030ad282263d6303c83b780a55fdec8e90d43 by Gennadiy Rozental <rogeeff@google.com>: Eliminate the pointer in absl::Flag, which points to n space where we were storing flag's default value. We also eliminate additional (now unnecessary) allocation for flag's default value. Instead we'll initialize the flags value directly from the value specified in ABSL_FLAG. If the default value is updated via the call to SetCommandLineOptionWithMode we are replacing pointer to initialization routine to pointer to new default value. PiperOrigin-RevId: 282637616 GitOrigin-RevId: 693f81830b9f9cc8b24a1f38492b8dfcdd1d0e24 Change-Id: I6f2edd8ef844de09aa2c182a7ca3133a22364792
2019-12-02 19:41:53 +01:00
#if ABSL_HAVE_ATTRIBUTE(enable_if) && !defined(__native_client__)
#define ABSL_INTERNAL_ENABLE_FORMAT_CHECKER 1
Export of internal Abseil changes -- 693f81830b9f9cc8b24a1f38492b8dfcdd1d0e24 by Abseil Team <absl-team@google.com>: Check that absl::int128 works as a std::chrono::duration::rep. In particular, validate that ... std::chrono::time_point<std::chrono::system_clock, std::chrono::duration<absl::int128, std::atto>> is a superset (range and resolution) of absl::Time. PiperOrigin-RevId: 283370280 -- df6073b686bd44223c6f9070fcceec918c728871 by Gennadiy Rozental <rogeeff@google.com>: Changes thread annotations to use DataGuard() function instead of a specific Mutex. Remove unused declaration of InvokeCallback. PiperOrigin-RevId: 283361188 -- b49eb2dd2ee1a0b4c8a7bb1a94e368b81ce5f861 by Abseil Team <absl-team@google.com>: Rewrite GetNominalCPUFrequency to use advapi32 instead of shlwapi Using shlwapi.dll means that gdi32.dll is loaded which then makes process destruction more expensive, which is unacceptable for some uses. There may be other places that pull in gdi32.dll - this just fixes the one. PiperOrigin-RevId: 282960698 -- b5508afec5099a0fdbb55e39a7cd2993259ed860 by Abseil Team <absl-team@google.com>: Small typo fix in comments: initiazliation -> initialization PiperOrigin-RevId: 282891800 -- 4319cc419584e91ee74f6ae1a32d88a412fc5c01 by Abseil Team <absl-team@google.com>: Update c_find_first_of() comment to remove the mention of an ordered container. PiperOrigin-RevId: 282836540 -- 5fcabc0a834dff39a505d5a5fc5403ddeb96028e by Derek Mauro <dmauro@google.com>: Fix NaCl build, where format checking is broken PiperOrigin-RevId: 282826202 -- aaf9ad3274c056a2f68e9b8ccada45c9802e2f1e by Derek Mauro <dmauro@google.com>: Fix more -Wundef warnings PiperOrigin-RevId: 282799820 -- 1fb06150a70ffe98bf4b2d42b2a39d083bf44f8c by Derek Mauro <dmauro@google.com>: Release support for additional platforms PiperOrigin-RevId: 282793384 -- fa947fc28624a316fa872d7045b3838b88a0d69b by Derek Mauro <dmauro@google.com>: Cleanup inconsistent usage of __has_attribute PiperOrigin-RevId: 282793296 -- 990030ad282263d6303c83b780a55fdec8e90d43 by Gennadiy Rozental <rogeeff@google.com>: Eliminate the pointer in absl::Flag, which points to n space where we were storing flag's default value. We also eliminate additional (now unnecessary) allocation for flag's default value. Instead we'll initialize the flags value directly from the value specified in ABSL_FLAG. If the default value is updated via the call to SetCommandLineOptionWithMode we are replacing pointer to initialization routine to pointer to new default value. PiperOrigin-RevId: 282637616 GitOrigin-RevId: 693f81830b9f9cc8b24a1f38492b8dfcdd1d0e24 Change-Id: I6f2edd8ef844de09aa2c182a7ca3133a22364792
2019-12-02 19:41:53 +01:00
#endif // ABSL_HAVE_ATTRIBUTE(enable_if) && !defined(__native_client__)
#endif // ABSL_INTERNAL_ENABLE_FORMAT_CHECKER
namespace absl {
namespace str_format_internal {
constexpr bool AllOf() { return true; }
template <typename... T>
constexpr bool AllOf(bool b, T... t) {
return b && AllOf(t...);
}
template <typename Arg>
constexpr Conv ArgumentToConv() {
return decltype(str_format_internal::FormatConvertImpl(
std::declval<const Arg&>(), std::declval<const ConversionSpec&>(),
std::declval<FormatSinkImpl*>()))::kConv;
}
Export of internal Abseil changes -- 693f81830b9f9cc8b24a1f38492b8dfcdd1d0e24 by Abseil Team <absl-team@google.com>: Check that absl::int128 works as a std::chrono::duration::rep. In particular, validate that ... std::chrono::time_point<std::chrono::system_clock, std::chrono::duration<absl::int128, std::atto>> is a superset (range and resolution) of absl::Time. PiperOrigin-RevId: 283370280 -- df6073b686bd44223c6f9070fcceec918c728871 by Gennadiy Rozental <rogeeff@google.com>: Changes thread annotations to use DataGuard() function instead of a specific Mutex. Remove unused declaration of InvokeCallback. PiperOrigin-RevId: 283361188 -- b49eb2dd2ee1a0b4c8a7bb1a94e368b81ce5f861 by Abseil Team <absl-team@google.com>: Rewrite GetNominalCPUFrequency to use advapi32 instead of shlwapi Using shlwapi.dll means that gdi32.dll is loaded which then makes process destruction more expensive, which is unacceptable for some uses. There may be other places that pull in gdi32.dll - this just fixes the one. PiperOrigin-RevId: 282960698 -- b5508afec5099a0fdbb55e39a7cd2993259ed860 by Abseil Team <absl-team@google.com>: Small typo fix in comments: initiazliation -> initialization PiperOrigin-RevId: 282891800 -- 4319cc419584e91ee74f6ae1a32d88a412fc5c01 by Abseil Team <absl-team@google.com>: Update c_find_first_of() comment to remove the mention of an ordered container. PiperOrigin-RevId: 282836540 -- 5fcabc0a834dff39a505d5a5fc5403ddeb96028e by Derek Mauro <dmauro@google.com>: Fix NaCl build, where format checking is broken PiperOrigin-RevId: 282826202 -- aaf9ad3274c056a2f68e9b8ccada45c9802e2f1e by Derek Mauro <dmauro@google.com>: Fix more -Wundef warnings PiperOrigin-RevId: 282799820 -- 1fb06150a70ffe98bf4b2d42b2a39d083bf44f8c by Derek Mauro <dmauro@google.com>: Release support for additional platforms PiperOrigin-RevId: 282793384 -- fa947fc28624a316fa872d7045b3838b88a0d69b by Derek Mauro <dmauro@google.com>: Cleanup inconsistent usage of __has_attribute PiperOrigin-RevId: 282793296 -- 990030ad282263d6303c83b780a55fdec8e90d43 by Gennadiy Rozental <rogeeff@google.com>: Eliminate the pointer in absl::Flag, which points to n space where we were storing flag's default value. We also eliminate additional (now unnecessary) allocation for flag's default value. Instead we'll initialize the flags value directly from the value specified in ABSL_FLAG. If the default value is updated via the call to SetCommandLineOptionWithMode we are replacing pointer to initialization routine to pointer to new default value. PiperOrigin-RevId: 282637616 GitOrigin-RevId: 693f81830b9f9cc8b24a1f38492b8dfcdd1d0e24 Change-Id: I6f2edd8ef844de09aa2c182a7ca3133a22364792
2019-12-02 19:41:53 +01:00
#ifdef ABSL_INTERNAL_ENABLE_FORMAT_CHECKER
constexpr bool ContainsChar(const char* chars, char c) {
return *chars == c || (*chars && ContainsChar(chars + 1, c));
}
// A constexpr compatible list of Convs.
struct ConvList {
const Conv* array;
int count;
// We do the bound check here to avoid having to do it on the callers.
// Returning an empty Conv has the same effect as short circuiting because it
// will never match any conversion.
constexpr Conv operator[](int i) const {
return i < count ? array[i] : Conv{};
}
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.
Conv 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 <Conv... 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_internal
} // namespace absl
#endif // ABSL_STRINGS_INTERNAL_STR_FORMAT_CHECKER_H_