diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -67,6 +67,8 @@ enum without a fixed underlying type is set to a value outside the range of the enumeration's values. Fixes `Issue 50055: `_. +- Clang will now check compile-time determinable string literals as format strings. + This fixes `Issue 55805: `_. Non-comprehensive list of changes in this release ------------------------------------------------- diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp --- a/clang/lib/Sema/SemaChecking.cpp +++ b/clang/lib/Sema/SemaChecking.cpp @@ -8473,6 +8473,9 @@ llvm::SmallBitVector &CheckedVarArgs, UncoveredArgHandler &UncoveredArg, bool IgnoreStringsWithoutSpecifiers); +static const Expr *maybeConstEvalStringLiteral(ASTContext &Context, + const Expr *E); + // Determine if an expression is a string literal or constant string. // If this function returns false on the arguments to a function expecting a // format string, we will usually need to emit a warning. @@ -8713,7 +8716,11 @@ } } } - + if (const auto *SLE = maybeConstEvalStringLiteral(S.Context, E)) + return checkFormatStringExpr(S, SLE, Args, APK, format_idx, firstDataArg, + Type, CallType, /*InFunctionCall*/ false, + CheckedVarArgs, UncoveredArg, Offset, + IgnoreStringsWithoutSpecifiers); return SLCT_NotALiteral; } case Stmt::ObjCMessageExprClass: { @@ -8823,6 +8830,20 @@ } } +// If this expression can be evaluated at compile-time, +// check if the result is a StringLiteral and return it +// otherwise return nullptr +static const Expr *maybeConstEvalStringLiteral(ASTContext &Context, + const Expr *E) { + Expr::EvalResult Result; + if (E->EvaluateAsRValue(Result, Context) && Result.Val.isLValue()) { + const auto *LVE = Result.Val.getLValueBase().dyn_cast(); + if (isa_and_nonnull(LVE)) + return LVE; + } + return nullptr; +} + Sema::FormatStringType Sema::GetFormatStringType(const FormatAttr *Format) { return llvm::StringSwitch(Format->getType()->getName()) .Case("scanf", FST_Scanf) diff --git a/clang/test/SemaCXX/format-strings.cpp b/clang/test/SemaCXX/format-strings.cpp --- a/clang/test/SemaCXX/format-strings.cpp +++ b/clang/test/SemaCXX/format-strings.cpp @@ -163,3 +163,48 @@ t::func4("Hello %s"); // expected-warning {{more '%' conversions than data arguments}} } } +#if __cplusplus >= 201103L +namespace evaluated { + +constexpr const char *basic() { + return +"%s %d"; // expected-note {{format string is defined here}} +} + +constexpr const char *correct_fmt() { + return +"%d %d"; +} + +constexpr const char *string_linebreak() { + return +"%d %d" +"%d %s"; // expected-note {{format string is defined here}} +} + +/*non-constexpr*/ const char *not_literal() { + return +"%d %d" +"%d %s"; +} + +constexpr const char *inner_call() { + return "%d %s"; // expected-note {{format string is defined here}} +} + +constexpr const char *wrap_constexpr() { + return inner_call(); +} + + +void f() { + printf(basic(), 1, 2); // expected-warning {{format specifies type 'char *' but the argument has type 'int'}} + printf(correct_fmt(), 1, 2); + printf(string_linebreak(), 1, 2, 3, 4); // expected-warning {{format specifies type 'char *' but the argument has type 'int'}} + printf(not_literal(), 1, 2, 3, 4); // expected-warning {{format string is not a string literal}} + printf(wrap_constexpr(), 1, 2); // expected-warning {{format specifies type 'char *' but the argument has type 'int'}} +} + + +} +#endif