From f417583f319bd60d1e32cdf9d0242e42f86101bf Mon Sep 17 00:00:00 2001 From: YingChi Long Date: Wed, 3 Aug 2022 10:07:31 +0800 Subject: [PATCH] [clang] format string checking for conpile-time evaluated str literal This patch enhances clang's ability to check compile-time determinable string literals as format strings, and can give FixIt hints at literals (unlike gcc). Issue https://github.com/llvm/llvm-project/issues/55805 mentiond two compile-time string cases. And this patch partially fixes one. ``` constexpr const char* foo() { return "%s %d"; } int main() { printf(foo(), "abc", "def"); return 0; } ``` This patch enables clang check format string for this: ``` :4:24: warning: format specifies type 'int' but the argument has type 'const char *' [-Wformat] printf(foo(), "abc", "def"); ~~~~~ ^~~~~ :2:42: note: format string is defined here constexpr const char *foo() { return "%s %d"; } ^~ %s 1 warning generated. ``` Reviewed By: aaron.ballman Signed-off-by: YingChi Long Differential Revision: https://reviews.llvm.org/D130906 --- clang/docs/ReleaseNotes.rst | 2 ++ clang/lib/Sema/SemaChecking.cpp | 23 +++++++++++++++++- clang/test/SemaCXX/format-strings.cpp | 45 +++++++++++++++++++++++++++++++++++ 3 files changed, 69 insertions(+), 1 deletion(-) diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 878effe..85d360e 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -67,6 +67,8 @@ Improvements to Clang's diagnostics 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 index 6b60312..6428be1 100644 --- a/clang/lib/Sema/SemaChecking.cpp +++ b/clang/lib/Sema/SemaChecking.cpp @@ -8473,6 +8473,9 @@ static void CheckFormatString( 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 @@ tryAgain: } } } - + 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 @@ tryAgain: } } +// 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 index a0dcf01..2378eea 100644 --- a/clang/test/SemaCXX/format-strings.cpp +++ b/clang/test/SemaCXX/format-strings.cpp @@ -163,3 +163,48 @@ void f() { 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 -- 2.7.4