UDocumentation UE5.7 10.02.2026 (Source)
API documentation for Unreal Engine 5.7
FormatStringSan.h
Go to the documentation of this file.
1// Copyright Epic Games, Inc. All Rights Reserved.
2#pragma once
3
4#include <type_traits>
5#include "CoreTypes.h"
9#include "Traits/IsCharType.h"
11#include "Traits/IsTString.h"
13
14#define UE_CHECK_FORMAT_STRING(Fmt, ...) \
15 do { \
16 namespace UCFS = ::UE::Core::Private::FormatStringSan; \
17 using UCFS_FChecker = decltype(UCFS::GetFmtArgCheckerType(Fmt, ##__VA_ARGS__)); \
18 constexpr UCFS::FResult UCFS_Result = UCFS_FChecker::Check(false, 0, Fmt); \
19 (UCFS::AssertFormatStatus<UCFS_Result.Status, UCFS::TAtArgPos<UCFS_Result.ArgPos>>()); \
20 } while(false);
21
22#define UE_CHECK_FORMAT_STRING_ERR(Err, Fmt, ...) \
23 (decltype(::UE::Core::Private::FormatStringSan::GetFmtArgCheckerType(Fmt, ##__VA_ARGS__))::Check(false, 0, Fmt).Status == Err)
24
25
26#if UE_VALIDATE_FORMAT_STRINGS
27 #define UE_VALIDATE_FORMAT_STRING UE_CHECK_FORMAT_STRING
28#else
29 #define UE_VALIDATE_FORMAT_STRING(Format, ...)
30#endif
31
33namespace UE::Core::Private
34{
35 namespace FormatStringSan
36 {
37 // Returns true when the type is a const char*, const TCHAR*, const char[] or const TCHAR[]
38 template<typename T>
39 inline constexpr bool bIsAConstString =
40 !(std::is_convertible_v<T, char*> || std::is_convertible_v<T, TCHAR*>)
41 && (std::is_convertible_v<T, const char*> || std::is_convertible_v<T, const TCHAR*>);
42
44 {
45 Ok,
46
47 #define X(Name, Desc) Name,
49 #undef X
50 };
51
52 template <EFormatStringSanStatus Err, typename T>
53 constexpr void AssertFormatStatus()
54 {
55 switch (Err)
56 {
58
59 #define X(Name, Desc) \
60 case EFormatStringSanStatus::Name: static_assert(Err != EFormatStringSanStatus::Name, Desc); break;
62 #undef X
63 }
64 }
65
66 template <int N>
67 struct TAtArgPos {};
68
74
75 template <typename T>
76 inline constexpr bool TIsFloatOrDouble_V = std::is_same_v<float, T> || std::is_same_v<double, T>;
77
78 template <typename T>
79 inline constexpr bool TIsIntegralEnum_V = std::is_enum_v<T> || TIsTEnumAsByte_V<T>;
80
81 template <typename CharType, typename...>
83
84 template <typename CharType, typename... Ts>
86
87 template <typename CharType>
88 constexpr bool CharIsDigit(CharType Char)
89 {
90 return Char >= (CharType)'0' && Char <= (CharType)'9';
91 }
92
93 template <typename CharType>
94 constexpr const CharType* SkipInteger(const CharType* Fmt)
95 {
96 while (CharIsDigit(*Fmt))
97 {
98 ++Fmt;
99 }
100 return Fmt;
101 }
102
103 template <typename CharType>
104 constexpr bool CharIsIntegerFormatSpecifier(CharType Char)
105 {
106 switch (Char)
107 {
108 case (CharType)'i': case (CharType)'d': case (CharType)'u': case (CharType)'X': case (CharType)'x':
109 return true;
110 default:
111 return false;
112 }
113 }
114
115 template <typename CharType, typename Arg, typename... Args>
116 struct TCheckFormatString<CharType, Arg, Args...>
117 {
118 static constexpr FResult HandleDynamicLengthSpecifier(int CurArgPos, const CharType* Fmt)
119 {
120 if constexpr (!(std::is_integral_v<Arg> || TIsIntegralEnum_V<Arg>))
121 {
122 return {EFormatStringSanStatus::DynamicLengthSpecNeedsIntegerArg, CurArgPos};
123 }
124 else
125 {
127 }
128 }
129
130 static constexpr FResult Check(bool bInsideFormatSpec, int CurArgPos, const CharType* Fmt)
131 {
132 using ArgPointeeType = std::remove_pointer_t<Arg>;
133
134 if (Fmt[0] == (CharType)'\0' && !bInsideFormatSpec)
135 {
136 return {EFormatStringSanStatus::NotEnoughSpecifiers, CurArgPos};
137 }
138
140 {
141 while (*Fmt != (CharType)'\0' && *Fmt != (CharType)'%')
142 {
143 ++Fmt;
144 }
145 if (*Fmt == (CharType)'%')
146 {
147 ++Fmt;
148 }
149 }
150
151 if (*Fmt == (CharType)'\0')
152 {
153 if (Fmt[-1] == (CharType)'%' || bInsideFormatSpec) // nb: Fmt[-1] is safe here because zero-length fmt strings are addressed above
154 {
155 return {EFormatStringSanStatus::IncompleteFormatSpecifierOrUnescapedPercent, CurArgPos};
156 }
157 else
158 {
159 return {EFormatStringSanStatus::Ok, 0};
160 }
161 }
162
163 while (*Fmt == (CharType)'+' || *Fmt == (CharType)'#' || *Fmt == (CharType)' ' || *Fmt == (CharType)'0')
164 {
165 ++Fmt;
166 }
167
168 if (*Fmt == (CharType)'-')
169 {
170 ++Fmt;
171 }
172 if (*Fmt == (CharType)'*')
173 {
174 return HandleDynamicLengthSpecifier(CurArgPos, Fmt);
175 }
176 else if (CharIsDigit(*Fmt))
177 {
179 }
180
181 if (*Fmt == (CharType)'.')
182 {
183 ++Fmt;
184 }
185 if (*Fmt == (CharType)'*')
186 {
187 return HandleDynamicLengthSpecifier(CurArgPos, Fmt);
188 }
189 else if (CharIsDigit(*Fmt))
190 {
192 }
193
194 if (Fmt[0] == (CharType)'l' && Fmt[1] == (CharType)'s')
195 {
196 if constexpr (TIsTString_V<Arg>)
197 {
198 return {EFormatStringSanStatus::LSNeedsDereferencedWideString, CurArgPos};
199 }
200 else if constexpr (TIsCharType_V<Arg>)
201 {
202 return {EFormatStringSanStatus::LSNeedsPtrButGotChar, CurArgPos};
203 }
204 else if constexpr (!std::is_pointer_v<Arg> || !TIsCharType_V<ArgPointeeType>)
205 {
206 return {EFormatStringSanStatus::LSNeedsWideCharPtrArg, CurArgPos};
207 }
208 else if constexpr (sizeof(ArgPointeeType) != sizeof(WIDECHAR))
209 {
210 return {(sizeof(CharType) == 1) ? EFormatStringSanStatus::LSNeedsWideCharPtrArgButGotNarrowOnNarrowString : EFormatStringSanStatus::LSNeedsWideCharPtrArgButGotNarrowOnWideString, CurArgPos};
211 }
212 else
213 {
215 }
216 }
217 else if (Fmt[0] == (CharType)'h' && Fmt[1] == (CharType)'s')
218 {
219 if constexpr (TIsTString_V<Arg>)
220 {
221 return {EFormatStringSanStatus::HSNeedsDereferencedNarrowString, CurArgPos};
222 }
223 else if constexpr (TIsCharType_V<Arg>)
224 {
225 return {EFormatStringSanStatus::HSNeedsPtrButGotChar, CurArgPos};
226 }
227 else if constexpr (!std::is_pointer_v<Arg> || !TIsCharType_V<ArgPointeeType>)
228 {
229 return {EFormatStringSanStatus::HSNeedsNarrowCharPtrArg, CurArgPos};
230 }
231 else if constexpr (sizeof(ArgPointeeType) != sizeof(ANSICHAR))
232 {
233 return {(sizeof(CharType) == 1) ? EFormatStringSanStatus::HSNeedsNarrowCharPtrArgButGotWideOnNarrowString : EFormatStringSanStatus::HSNeedsNarrowCharPtrArgButGotWideOnWideString, CurArgPos};
234 }
235 else
236 {
238 }
239 }
240 else if (Fmt[0] == (CharType)'s')
241 {
242 if constexpr (TIsTString_V<Arg>)
243 {
244 return {(sizeof(CharType) == 1) ? EFormatStringSanStatus::SNeedsDereferencedNarrowString : EFormatStringSanStatus::SNeedsDereferencedWideString, CurArgPos};
245 }
246 else if constexpr (TIsCharType_V<Arg>)
247 {
248 return {EFormatStringSanStatus::SNeedsPtrButGotChar, CurArgPos};
249 }
250 else if constexpr (!std::is_pointer_v<Arg> || !TIsCharType_V<ArgPointeeType>)
251 {
252 return {(sizeof(CharType) == 1) ? EFormatStringSanStatus::SNeedsNarrowCharPtrArg : EFormatStringSanStatus::SNeedsWideCharPtrArg, CurArgPos};
253 }
254 else if constexpr (sizeof(ArgPointeeType) != sizeof(CharType))
255 {
256 return {(sizeof(CharType) == 1) ? EFormatStringSanStatus::SNeedsNarrowCharPtrArgButGotWide : EFormatStringSanStatus::SNeedsWideCharPtrArgButGotNarrow, CurArgPos};
257 }
258 else
259 {
261 }
262 }
263 else if (Fmt[0] == (CharType)'S')
264 {
265 if constexpr (TIsTString_V<Arg>)
266 {
267 return {(sizeof(CharType) == 1) ? EFormatStringSanStatus::CapitalSNeedsDereferencedWideString : EFormatStringSanStatus::CapitalSNeedsDereferencedNarrowString, CurArgPos};
268 }
269 else if constexpr (TIsCharType_V<Arg>)
270 {
271 return {EFormatStringSanStatus::CapitalSNeedsPtrButGotChar, CurArgPos};
272 }
273 else if constexpr (!std::is_pointer_v<Arg> || !TIsCharType_V<ArgPointeeType>)
274 {
275 return {(sizeof(CharType) == 1) ? EFormatStringSanStatus::CapitalSNeedsWideCharPtrArg : EFormatStringSanStatus::CapitalSNeedsNarrowCharPtrArg, CurArgPos};
276 }
277 else if constexpr (sizeof(ArgPointeeType) == sizeof(CharType))
278 {
279 return {(sizeof(CharType) == 1) ? EFormatStringSanStatus::CapitalSNeedsWideCharPtrArgButGotNarrow : EFormatStringSanStatus::CapitalSNeedsNarrowCharPtrArgButGotWide, CurArgPos};
280 }
281 else
282 {
284 }
285 }
286
287 switch (Fmt[0])
288 {
289 case (CharType)'%':
291 case (CharType)'c':
292 if constexpr (!std::is_same_v<Arg, UTF8CHAR> && !(std::is_integral_v<Arg> && sizeof(Arg) <= sizeof(int)))
293 {
294 return {(sizeof(CharType) == 1) ? EFormatStringSanStatus::CNeedsCharArgOnNarrowString : EFormatStringSanStatus::CNeedsCharArgOnWideString, CurArgPos};
295 }
296 else
297 {
299 }
300 case (CharType)'d':
301 case (CharType)'i':
302 case (CharType)'X':
303 case (CharType)'x':
304 case (CharType)'u':
305 if constexpr (std::is_pointer_v<Arg> && TIsCharType_V<ArgPointeeType>)
306 {
307 return {EFormatStringSanStatus::DNeedsIntegerArg, CurArgPos};
308 }
309 else if constexpr (!(TIsIntegralEnum_V<Arg> || std::is_integral_v<Arg> || std::is_pointer_v<Arg>))
310 {
311 return {EFormatStringSanStatus::DNeedsIntegerArg, CurArgPos};
312 }
313 else
314 {
316 }
317 case (CharType)'z':
319 {
320 return {EFormatStringSanStatus::ZNeedsIntegerSpec, CurArgPos};
321 }
322 else if constexpr (!(TIsIntegralEnum_V<Arg> || std::is_integral_v<Arg>))
323 {
324 return {EFormatStringSanStatus::ZNeedsIntegerArg, CurArgPos};
325 }
326 else
327 {
329 }
330 case (CharType)'p':
331 if constexpr (!std::is_pointer_v<Arg>)
332 {
333 return {EFormatStringSanStatus::PNeedsPointerArg, CurArgPos};
334 }
335 else
336 {
338 }
339 case (CharType)'I':
340 if (!(Fmt[1] == (CharType)'6' && Fmt[2] == (CharType)'4'))
341 {
342 return {EFormatStringSanStatus::I64BadSpec, CurArgPos};
343 }
344 else if (!CharIsIntegerFormatSpecifier(Fmt[3]))
345 {
346 return {EFormatStringSanStatus::I64BadSpec, CurArgPos};
347 }
348 else if constexpr (!(TIsIntegralEnum_V<Arg> || std::is_integral_v<Arg>))
349 {
350 return {EFormatStringSanStatus::I64NeedsIntegerArg, CurArgPos};
351 }
352 else
353 {
355 }
356
357 case (CharType)'l':
359 {
360 if constexpr (!(TIsIntegralEnum_V<Arg> || std::is_integral_v<Arg>))
361 {
362 return {EFormatStringSanStatus::LNeedsIntegerArg, CurArgPos};
363 }
364 else
365 {
367 }
368 }
369 else if (Fmt[1] == (CharType)'f')
370 {
371 if constexpr (!TIsFloatOrDouble_V<Arg>)
372 {
373 return {EFormatStringSanStatus::FNeedsFloatOrDoubleArg, CurArgPos};
374 }
375 else
376 {
378 }
379 }
380 else if (Fmt[1] != (CharType)'l')
381 {
382 return {EFormatStringSanStatus::InvalidFormatSpec, CurArgPos};
383 }
384 else if (!CharIsIntegerFormatSpecifier(Fmt[2]))
385 {
386 return {EFormatStringSanStatus::LLNeedsIntegerSpec, CurArgPos};
387 }
388 else if constexpr (!(TIsIntegralEnum_V<Arg> || std::is_integral_v<Arg> || std::is_pointer_v<Arg>))
389 {
390 return {EFormatStringSanStatus::LLNeedsIntegerArg, CurArgPos};
391 }
392 else
393 {
395 }
396
397 case (CharType)'h':
399 {
400 if constexpr (!(TIsIntegralEnum_V<Arg> || std::is_integral_v<Arg>))
401 {
402 return {EFormatStringSanStatus::HNeedsIntegerArg, CurArgPos};
403 }
405 }
406 else if (Fmt[1] != (CharType)'h')
407 {
408 return {EFormatStringSanStatus::InvalidFormatSpec, CurArgPos};
409 }
410 else if (!CharIsIntegerFormatSpecifier(Fmt[2]))
411 {
412 return {EFormatStringSanStatus::HHNeedsIntegerSpec, CurArgPos};
413 }
414 else if constexpr (!(TIsIntegralEnum_V<Arg> || std::is_integral_v<Arg>))
415 {
416 return {EFormatStringSanStatus::HHNeedsIntegerArg, CurArgPos};
417 }
418 else
419 {
421 }
422
423 case (CharType)'f':
424 case (CharType)'e':
425 case (CharType)'g':
426 if constexpr (!TIsFloatOrDouble_V<Arg>)
427 {
428 return {EFormatStringSanStatus::FNeedsFloatOrDoubleArg, CurArgPos};
429 }
430 else
431 {
433 }
434
435 case (CharType)' ':
436 return {EFormatStringSanStatus::IncompleteFormatSpecifierOrUnescapedPercent, CurArgPos};
437
438 default:
439 return {EFormatStringSanStatus::InvalidFormatSpec, CurArgPos};
440 }
441 }
442 };
443
444 template <typename CharType>
445 struct TCheckFormatString<CharType>
446 {
447 static constexpr FResult Check(bool bInsideFormatSpec, int CurArgPos, const CharType* Fmt)
448 {
450 {
451 return {EFormatStringSanStatus::IncompleteFormatSpecifierOrUnescapedPercent, CurArgPos};
452 }
453
454 while (*Fmt)
455 {
456 if (Fmt[0] != (CharType)'%')
457 {
458 ++Fmt;
459 continue;
460 }
461
462 if (Fmt[1] == (CharType)'%')
463 {
464 Fmt += 2;
465 continue;
466 }
467
468 if (Fmt[1] == (CharType)'\0')
469 {
470 return {EFormatStringSanStatus::IncompleteFormatSpecifierOrUnescapedPercent, CurArgPos};
471 }
472
473 return {EFormatStringSanStatus::NotEnoughArguments, CurArgPos};
474 }
475
476 return {EFormatStringSanStatus::Ok, 0};
477 }
478 };
479
480 template <typename CharType, typename... ArgTypes>
482 {
483 const CharType* FormatString;
484
485#if UE_VALIDATE_FORMAT_STRINGS && defined(__cpp_consteval)
486 template <int32 N>
487 consteval TCheckedFormatStringPrivate(const CharType (&Fmt)[N])
489 {
491 FResult Result = Checker::Check(false, 0, Fmt);
492
493 switch (Result.Status)
494 {
495 case EFormatStringSanStatus::Ok: break;
496
497 #define X(Name, Desc) \
498 case EFormatStringSanStatus::Name: PRINTF_FORMAT_STRING_ERROR(Desc); break;
500 #undef X
501 }
502 }
503
504 void PRINTF_FORMAT_STRING_ERROR(const char*)
505 {
506 // this non-consteval function exists to trigger a compiler error when called from a
507 // consteval function
508 }
509
510#else
511
512 template <int32 N>
513 TCheckedFormatStringPrivate(const CharType(&Fmt)[N])
515 {
516 static_assert((TIsValidVariadicFunctionArg<ArgTypes>::Value && ...), "Invalid argument(s) passed to Printf");
517 }
518
519#endif // defined(__cpp_consteval)
520 };
521 } // namespace FormatStringSan
522} // namespace UE::Core::Private
523
524namespace UE::Core
525{
526 template <typename FmtType, typename... ArgTypes>
528}
FPlatformTypes::WIDECHAR WIDECHAR
A wide character. Normally a signed type.
Definition Platform.h:1133
FPlatformTypes::ANSICHAR ANSICHAR
An ANSI character. Normally a signed type.
Definition Platform.h:1131
UE_FORCEINLINE_HINT TSharedRef< CastToType, Mode > StaticCastSharedRef(TSharedRef< CastFromType, Mode > const &InSharedRef)
Definition SharedPointer.h:127
@ Char
Character type.
constexpr void AssertFormatStatus()
Definition FormatStringSan.h:53
constexpr bool TIsFloatOrDouble_V
Definition FormatStringSan.h:76
constexpr TCheckFormatString< CharType, std::decay_t< Ts >... > GetFmtArgCheckerType(const CharType *, Ts...)
constexpr bool CharIsIntegerFormatSpecifier(CharType Char)
Definition FormatStringSan.h:104
constexpr bool bIsAConstString
Definition FormatStringSan.h:39
EFormatStringSanStatus
Definition FormatStringSan.h:44
constexpr bool TIsIntegralEnum_V
Definition FormatStringSan.h:79
constexpr bool CharIsDigit(CharType Char)
Definition FormatStringSan.h:88
constexpr const CharType * SkipInteger(const CharType *Fmt)
Definition FormatStringSan.h:94
implementation
Definition PlayInEditorLoadingScope.h:8
Definition PlayInEditorLoadingScope.h:8
Definition IsValidVariadicFunctionArg.h:14
Definition FormatStringSan.h:70
EFormatStringSanStatus Status
Definition FormatStringSan.h:71
int ArgPos
Definition FormatStringSan.h:72
Definition FormatStringSan.h:67
static constexpr FResult HandleDynamicLengthSpecifier(int CurArgPos, const CharType *Fmt)
Definition FormatStringSan.h:118
static constexpr FResult Check(bool bInsideFormatSpec, int CurArgPos, const CharType *Fmt)
Definition FormatStringSan.h:130
static constexpr FResult Check(bool bInsideFormatSpec, int CurArgPos, const CharType *Fmt)
Definition FormatStringSan.h:447
TCheckedFormatStringPrivate(const CharType(&Fmt)[N])
Definition FormatStringSan.h:513
const CharType * FormatString
Definition FormatStringSan.h:483