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 @@ -8502,6 +8502,17 @@ // it should have a 'nonnull' attribute in the function prototype. return SLCT_UncheckedLiteral; + // maybe this expression can be evaluated at compile-time + // checks if E can be evaluated to some StringLiteral and retry + Expr::EvalResult Result; + if (E->EvaluateAsRValue(Result, S.Context) && Result.Val.isLValue()) { + auto *LVE = Result.Val.getLValueBase().dyn_cast(); + 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: case Stmt::ConditionalOperatorClass: { diff --git a/clang/test/Sema/format-strings-scanf.c b/clang/test/Sema/format-strings-scanf.c --- a/clang/test/Sema/format-strings-scanf.c +++ b/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 diff --git a/clang/test/Sema/format-strings.c b/clang/test/Sema/format-strings.c --- a/clang/test/Sema/format-strings.c +++ b/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}} 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