inclyc created this revision. Herald added a project: All. inclyc edited the summary of this revision. inclyc updated this revision to Diff 449023. inclyc added a comment. inclyc added reviewers: mizvekov, rsmith, aaron.ballman. inclyc added a project: clang. inclyc added a subscriber: clang. inclyc published this revision for review. Herald added a subscriber: cfe-commits.
add newline 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: <source>:4:24: warning: format specifies type 'int' but the argument has type 'const char *' [-Wformat] printf(foo(), "abc", "def"); ~~~~~ ^~~~~ <source>:2:42: note: format string is defined here constexpr const char *foo() { return "%s %d"; } ^~ %s 1 warning generated. Signed-off-by: YingChi Long <m...@inclyc.cn> Repository: rG LLVM Github Monorepo https://reviews.llvm.org/D130906 Files: clang/lib/Sema/SemaChecking.cpp clang/test/Sema/format-strings-scanf.c clang/test/Sema/format-strings.c clang/test/SemaCXX/format-strings.cpp
Index: clang/test/SemaCXX/format-strings.cpp =================================================================== --- clang/test/SemaCXX/format-strings.cpp +++ 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 Index: clang/test/Sema/format-strings.c =================================================================== --- clang/test/Sema/format-strings.c +++ clang/test/Sema/format-strings.c @@ -105,7 +105,8 @@ // expected-note@-1{{treat the string as an argument to avoid this}} printf("yes" ?: "no %d", 1); // expected-warning{{data argument not used by format string}} printf(0 ? "yes %s" : "no %d", 1); // no-warning - printf(0 ? "yes %d" : "no %s", 1); // expected-warning{{format specifies type 'char *'}} + printf(0 ? "yes %d" : "no %s", 1); // expected-warning{{format specifies type 'char *'}} \ + // expected-note{{format string is defined here}} printf(0 ? "yes" : "no %d", 1); // no-warning printf(0 ? "yes %d" : "no", 1); // expected-warning{{data argument not used by format string}} @@ -259,8 +260,7 @@ printf(s2); // no-warning printf(s3); // expected-warning{{not a string literal}} // expected-note@-1{{treat the string as an argument to avoid this}} - printf(s4); // expected-warning{{not a string literal}} - // expected-note@-1{{treat the string as an argument to avoid this}} + printf(s4); printf(s5); // expected-warning{{not a string literal}} // expected-note@-1{{treat the string as an argument to avoid this}} } @@ -621,7 +621,8 @@ // Make sure that the "format string is defined here" note is not emitted // when the original string is within the argument expression. - printf(1 ? "yes %d" : "no %d"); // expected-warning{{more '%' conversions than data arguments}} + printf(1 ? "yes %d" : "no %d"); // expected-warning{{more '%' conversions than data arguments}} \ + // expected-note {{format string is defined here}} const char kFormat17[] = "%hu"; // expected-note{{format string is defined here}}} printf(kFormat17, (int[]){0}); // expected-warning{{format specifies type 'unsigned short' but the argument}} Index: clang/test/Sema/format-strings-scanf.c =================================================================== --- clang/test/Sema/format-strings-scanf.c +++ clang/test/Sema/format-strings-scanf.c @@ -232,9 +232,11 @@ void check_conditional_literal(char *s, int *i) { scanf(0 ? "%s" : "%d", i); // no warning - scanf(1 ? "%s" : "%d", i); // expected-warning{{format specifies type 'char *'}} + scanf(1 ? "%s" : "%d", i); // expected-warning{{format specifies type 'char *'}} \ + // expected-note{{format string is defined here}} scanf(0 ? "%d %d" : "%d", i); // no warning - scanf(1 ? "%d %d" : "%d", i); // expected-warning{{more '%' conversions than data arguments}} + scanf(1 ? "%d %d" : "%d", i); // expected-warning{{more '%' conversions than data arguments}} \ + // expected-note{{format string is defined here}} scanf(0 ? "%d %d" : "%d", i, s); // expected-warning{{data argument not used}} scanf(1 ? "%d %s" : "%d", i, s); // no warning scanf(i ? "%d %s" : "%d", i, s); // no warning Index: clang/lib/Sema/SemaChecking.cpp =================================================================== --- clang/lib/Sema/SemaChecking.cpp +++ clang/lib/Sema/SemaChecking.cpp @@ -8501,6 +8501,17 @@ // dependent. Ideally if the format string cannot be null then // it should have a 'nonnull' attribute in the function prototype. return SLCT_UncheckedLiteral; + Expr::EvalResult Result; + if (E->EvaluateAsRValue(Result, S.Context)) { + if (Result.Val.isLValue()) { + auto *LVE = Result.Val.getLValueBase().dyn_cast<const Expr *>(); + if (LVE && LVE->getStmtClass() == Stmt::StringLiteralClass) { + return checkFormatStringExpr( + S, LVE, Args, APK, format_idx, firstDataArg, Type, CallType, + /*InFunctionCall*/ false, CheckedVarArgs, UncoveredArg, Offset); + } + } + } switch (E->getStmtClass()) { case Stmt::BinaryConditionalOperatorClass:
_______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits