diff options
author | arkady-e1ppa <arkady-e1ppa@yandex-team.com> | 2024-05-30 21:48:06 +0300 |
---|---|---|
committer | arkady-e1ppa <arkady-e1ppa@yandex-team.com> | 2024-05-30 21:57:42 +0300 |
commit | d6adb2d70464c9eb08b2b24076be43c44d5a8b74 (patch) | |
tree | fca4b31583b00c5d3a17d114a9923b67e72745ed /library/cpp/yt/string/format_analyser.h | |
parent | b5530daab1ea3757321436470f86ed65f30408c9 (diff) | |
download | ydb-d6adb2d70464c9eb08b2b24076be43c44d5a8b74.tar.gz |
YT-21868: Static analysis of format string in logging
Added static analysis to format of YT_LOG_XXX macro's. We expect you to write format string as first or the second argument and follow the rules as if you are writing arguments for `NYT::Format`, which match those of printf: https://en.cppreference.com/w/cpp/io/c/fprintf plus few extra flags like 'v'. At the moment analyser checks if flags sequences is
1. Correctly terminated
2. Only contains specifiers valid for a given argument (if we are parsing nth argument of type T, then T must have all specifiers from its list of Conversion or Flag specifiers.
(2) Also means that the number of flag sequences must match the number of arguments supplied to format.
You can specialize `TFormatArg<T>` which is used to determine allowed Conversion and Flag specifiers to customise rules of static analysis. E.g. you can introduce new flags to the mix which you promise to parse in the related FormatValue function.
If you feel like this produces to much overhead in terms of compile time, you are free to use macro YT_DISABLE_FORMAT_STATIC_ANALYSIS to turn the entire thing into a no-op. We have measured compile time to be affected by roughly 3-5% in a log intensive files.
ae6def509474e8a42027bb4ed84ac040509b7c85
Diffstat (limited to 'library/cpp/yt/string/format_analyser.h')
-rw-r--r-- | library/cpp/yt/string/format_analyser.h | 102 |
1 files changed, 102 insertions, 0 deletions
diff --git a/library/cpp/yt/string/format_analyser.h b/library/cpp/yt/string/format_analyser.h new file mode 100644 index 0000000000..4afe3f1ca8 --- /dev/null +++ b/library/cpp/yt/string/format_analyser.h @@ -0,0 +1,102 @@ +#pragma once + +#include <array> +#include <string_view> + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +namespace NDetail { + +struct TFormatAnalyser +{ +public: + template <class... TArgs> + static consteval void ValidateFormat(std::string_view fmt); + +private: + // Non-constexpr function call will terminate compilation. + // Purposefully undefined and non-constexpr/consteval + static void CrashCompilerNotEnoughArguments(std::string_view msg); + static void CrashCompilerTooManyArguments(std::string_view msg); + static void CrashCompilerWrongTermination(std::string_view msg); + static void CrashCompilerMissingTermination(std::string_view msg); + static void CrashCompilerWrongFlagSpecifier(std::string_view msg); + + struct TSpecifiers + { + std::string_view Conversion; + std::string_view Flags; + }; + template <class TArg> + static consteval auto GetSpecifiers(); + + static constexpr char IntroductorySymbol = '%'; +}; + +} // namespace NDetail + +//////////////////////////////////////////////////////////////////////////////// + +// Base used for flag checks for each type independently. +// Use it for overrides. +struct TFormatArgBase +{ +public: + // TODO(arkady-e1ppa): Consider more strict formatting rules. + static constexpr std::array ConversionSpecifiers = { + 'v', '1', 'c', 's', 'd', 'i', 'o', + 'x', 'X', 'u', 'f', 'F', 'e', 'E', + 'a', 'A', 'g', 'G', 'n', 'p' + }; + + static constexpr std::array FlagSpecifiers = { + '-', '+', ' ', '#', '0', + '1', '2', '3', '4', '5', + '6', '7', '8', '9', + '*', '.', 'h', 'l', 'j', + 'z', 't', 'L', 'q', 'Q' + }; + + template <class T> + static constexpr bool IsSpecifierList = requires (T t) { + [] <size_t N> (std::array<char, N>) { } (t); + }; + + // Hot = |true| adds specifiers to the beggining + // of a new array. + template <bool Hot, size_t N, std::array<char, N> List, class TFrom = TFormatArgBase> + static consteval auto ExtendConversion(); + + template <bool Hot, size_t N, std::array<char, N> List, class TFrom = TFormatArgBase> + static consteval auto ExtendFlags(); + +private: + template <bool Hot, size_t N, std::array<char, N> List, auto* From> + static consteval auto AppendArrays(); +}; + +//////////////////////////////////////////////////////////////////////////////// + +template <class T> +struct TFormatArg + : public TFormatArgBase +{ }; + +// Semantic requirement: +// Said field must be constexpr. +template <class T> +concept CFormattable = requires { + TFormatArg<T>::ConversionSpecifiers; + requires TFormatArgBase::IsSpecifierList<decltype(TFormatArg<T>::ConversionSpecifiers)>; + + TFormatArg<T>::FlagSpecifiers; + requires TFormatArgBase::IsSpecifierList<decltype(TFormatArg<T>::FlagSpecifiers)>; +}; + +} // namespace NYT + +#define FORMAT_ANALYSER_INL_H_ +#include "format_analyser-inl.h" +#undef FORMAT_ANALYSER_INL_H_ |