================ @@ -6331,26 +6321,79 @@ static StringLiteralCheckType checkFormatStringExpr( } } - return SLCT_NotALiteral; + // try to constant-evaluate the string + break; } default: + // try to constant-evaluate the string + break; + } + + const StringLiteral *FakeLiteral = nullptr; + switch (constEvalStringAsLiteral(S, E, FakeLiteral)) { + case SLCER_NotEvaluated: return SLCT_NotALiteral; + + case SLCER_NotNullTerminated: + S.Diag(Args[format_idx]->getBeginLoc(), + diag::warn_printf_format_string_not_null_terminated) + << Args[format_idx]->getSourceRange(); + if (!InFunctionCall) + S.Diag(E->getBeginLoc(), diag::note_format_string_defined); + // Stop checking, as this might just mean we're missing a chunk of the + // format string and there would be other spurious format issues. + return SLCT_UncheckedLiteral; + + case SLCER_Evaluated: + InFunctionCall = false; + E = FakeLiteral; + goto 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) { +static StringLiteralConstEvalResult +constEvalStringAsLiteral(Sema &S, const Expr *E, const StringLiteral *&SL) { + // As a last resort, try to constant-evaluate the format string. If it + // evaluates to a string literal in the first place, we can point to that + // string literal in source and use that. Expr::EvalResult Result; - if (E->EvaluateAsRValue(Result, Context) && Result.Val.isLValue()) { + if (E->EvaluateAsRValue(Result, S.Context) && Result.Val.isLValue()) { const auto *LVE = Result.Val.getLValueBase().dyn_cast<const Expr *>(); - if (isa_and_nonnull<StringLiteral>(LVE)) - return LVE; + if (auto *BaseSL = dyn_cast_or_null<StringLiteral>(LVE)) { + SL = BaseSL; + return SLCER_Evaluated; + } } - return nullptr; + + // Otherwise, try to evaluate the expression as a string constant. + std::string FormatString; + if (!E->tryEvaluateString(S.Context, FormatString)) { + return FormatString.empty() ? SLCER_NotEvaluated : SLCER_NotNullTerminated; + } + + std::unique_ptr<llvm::MemoryBuffer> MemBuf; + { + llvm::SmallString<80> EscapedString; + { + llvm::raw_svector_ostream OS(EscapedString); + OS << '"'; + OS.write_escaped(FormatString); ---------------- apple-fcloutier wrote:
I tried with `-fixit` and had the same result. For clang-tidy, I tried this: ``` % cat /tmp/test.c __attribute__((format(printf, 1, 2))) int printf(const char *, ...); int main() { const char buf[] = {'"', '%', 's', '"', 0}; printf(buf, 123); printf("%s", 123); } % bin/clang-tidy --fix /tmp/test.c .... 2 warnings generated. /tmp/test.c:6:17: warning: format specifies type 'char *' but the argument has type 'int' [clang-diagnostic-format] 6 | printf(buf, 123); | ~~~ ^~~ note: format string resolved to a constant string /tmp/test.c:7:18: warning: format specifies type 'char *' but the argument has type 'int' [clang-diagnostic-format] 7 | printf("%s", 123); | ~~ ^~~ | %d /tmp/test.c:7:13: note: FIX-IT applied suggested code changes 7 | printf("%s", 123); | ^ clang-tidy applied 1 of 1 suggested fixes. ``` In words: it fixes the `printf("%s", 123)` line to use `%d` and leaves alone the other printf alone without throwing a fuss (claiming "clang-tidy applied 1 of 1 suggested fixes"). Clang-tidy shows the "format string resolved to a constant string" note but not the scratch space contents. It's not ideal, but it's quite reasonable IMO. https://github.com/llvm/llvm-project/pull/135864 _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits