aboutsummaryrefslogtreecommitdiffstats
path: root/library/cpp/yt/logging/static_analysis-inl.h
blob: d4ec5343bc14ea4983a84762d7441a1ca9d12676 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
#ifndef STATIC_ANALYSIS_INL_H_
#error "Direct inclusion of this file is not allowed, include static_analysis.h"
// For the sake of sane code completion.
#include "static_analysis.h"
#endif

#include <library/cpp/yt/misc/preprocessor.h>

#include <library/cpp/yt/string/format_analyser.h>

#include <string_view>
#include <variant> // monostate

namespace NYT::NLogging::NDetail {

////////////////////////////////////////////////////////////////////////////////

// Tag for dispatching proper TFormatArg specialization.
template <class T>
struct TLoggerFormatArg
{ };

////////////////////////////////////////////////////////////////////////////////

// Stateless constexpr way of capturing arg types
// without invoking any ctors. With the help of macros
// can turn non-constexpr argument pack of arguments
// into constexpr pack of types.
template <class... TArgs>
struct TLoggerFormatArgs
{ };

// Used for macro conversion. Purposefully undefined.
template <class... TArgs>
TLoggerFormatArgs<TArgs...> AsFormatArgs(TArgs&&...);

////////////////////////////////////////////////////////////////////////////////

template <bool First, bool Second>
struct TAnalyserDispatcher
{
    template <class... TArgs>
    static consteval void Do(std::string_view, std::string_view, TLoggerFormatArgs<TArgs...>)
    {
        // Give up :(
        // We can't crash here, because, for example, YT_LOG_ERROR(error) exists
        // and we can't really check if error is actually TError or something else here.
        // and probably shouldn't bother trying.
    }
};

template <bool Second>
struct TAnalyserDispatcher<true, Second>
{
    template <class TFirst, class... TArgs>
    static consteval void Do(std::string_view str, std::string_view, TLoggerFormatArgs<TFirst, TArgs...>)
    {
        // Remove outer \"'s generated by PP_STRINGIZE.
        auto stripped = std::string_view(std::begin(str) + 1, std::size(str) - 2);
        ::NYT::NDetail::TFormatAnalyser::ValidateFormat<TLoggerFormatArg<TArgs>...>(stripped);
    }
};

template <>
struct TAnalyserDispatcher<false, true>
{
    template <class TFirst, class TSecond, class... TArgs>
    static consteval void Do(std::string_view, std::string_view str, TLoggerFormatArgs<TFirst, TSecond, TArgs...>)
    {
        // Remove outer \"'s generated by PP_STRINGIZE.
        auto stripped = std::string_view(std::begin(str) + 1, std::size(str) - 2);
        ::NYT::NDetail::TFormatAnalyser::ValidateFormat<TLoggerFormatArg<TArgs>...>(stripped);
    }
};

////////////////////////////////////////////////////////////////////////////////

// This value is never read since homogenization works for unevaluated expressions.
inline constexpr auto InvalidToken = std::monostate{};

////////////////////////////////////////////////////////////////////////////////

#define PP_VA_PICK_1_IMPL(N, ...) N
#define PP_VA_PICK_2_IMPL(_1, N, ...) N

////////////////////////////////////////////////////////////////////////////////

//! Parameter pack parsing.

#define STATIC_ANALYSIS_CAPTURE_TYPES(...) \
    decltype(::NYT::NLogging::NDetail::AsFormatArgs(__VA_ARGS__)){}

#define STATIC_ANALYSIS_FIRST_TOKEN(...) \
    PP_STRINGIZE( \
        PP_VA_PICK_1_IMPL(__VA_ARGS__ __VA_OPT__(,) ::NYT::NLogging::NDetail::InvalidToken))

#define STATIC_ANALYSIS_SECOND_TOKEN(...) \
    PP_STRINGIZE(\
        PP_VA_PICK_2_IMPL( \
            __VA_ARGS__ __VA_OPT__(,) \
            ::NYT::NLogging::NDetail::InvalidToken, \
            ::NYT::NLogging::NDetail::InvalidToken))

#define STATIC_ANALYSIS_FIRST_TOKEN_COND(...) \
    STATIC_ANALYSIS_FIRST_TOKEN(__VA_ARGS__)[0] == '\"'

#define STATIC_ANALYSIS_SECOND_TOKEN_COND(...) \
    STATIC_ANALYSIS_SECOND_TOKEN(__VA_ARGS__)[0] == '\"'

#undef STATIC_ANALYSIS_CHECK_LOG_FORMAT
#define STATIC_ANALYSIS_CHECK_LOG_FORMAT(...) \
    ::NYT \
    ::NLogging \
    ::NDetail \
    ::TAnalyserDispatcher< \
        STATIC_ANALYSIS_FIRST_TOKEN_COND(__VA_ARGS__), \
        STATIC_ANALYSIS_SECOND_TOKEN_COND(__VA_ARGS__) \
    >::Do( \
        STATIC_ANALYSIS_FIRST_TOKEN(__VA_ARGS__), \
        STATIC_ANALYSIS_SECOND_TOKEN(__VA_ARGS__), \
        STATIC_ANALYSIS_CAPTURE_TYPES(__VA_ARGS__))

////////////////////////////////////////////////////////////////////////////////

} // namespace NYT::NLogging::NDetail

template <class T>
struct NYT::TFormatArg<NYT::NLogging::NDetail::TLoggerFormatArg<T>>
    : public NYT::TFormatArgBase
{
    // We mix in '\"' and ' ' which is an artifact of logging stringize.
    // We want to support YT_LOG_XXX("Value: %" PRIu64, 42)
    // for plantform independent prints of numbers.
    // String below may be converted to a token:
    // "\"Value: %\" \"u\""
    // Thus adding a \" \" sequence.
    static constexpr auto FlagSpecifiers
        = TFormatArgBase::ExtendFlags</*Hot*/ false, 2, std::array{'\"', ' '}, /*TFrom*/ NYT::TFormatArg<T>>();
};