[clang] [clang] Catch missing format attributes (PR #105479)
Aaron Puchert via cfe-commits
cfe-commits at lists.llvm.org
Sat Jun 7 16:05:37 PDT 2025
================
@@ -5918,6 +5918,181 @@ static void handlePreferredTypeAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
D->addAttr(::new (S.Context) PreferredTypeAttr(S.Context, AL, ParmTSI));
}
+// Diagnosing missing format attributes is implemented in two steps:
+// 1. Detect missing format attributes while checking function calls.
+// 2. Emit diagnostic in part that processes function body.
+// For this purpose it is created vector that stores information about format
+// attributes. There are no two format attributes with same arguments in a
+// vector. Vector could contains attributes that only store information about
+// format type (format string and first to check argument are set to -1).
+namespace {
+std::vector<FormatAttr *> MissingAttributes;
+} // end anonymous namespace
+
+// This function is called only if function call is not inside template body.
+// TODO: Add call for function calls inside template body.
+// Detects and stores missing format attributes in a vector.
+void Sema::DetectMissingFormatAttributes(const FunctionDecl *Callee,
+ ArrayRef<const Expr *> Args,
+ SourceLocation Loc) {
+ assert(Callee);
+
+ // If there is no caller, exit.
+ const FunctionDecl *Caller = getCurFunctionDecl();
+ if (!getCurFunctionDecl())
+ return;
+
+ // Check if callee function is a format function.
+ // If it is, check if caller function misses format attributes.
+
+ if (!Callee->hasAttr<FormatAttr>())
+ return;
+
+ // va_list is not intended to be passed to variadic function.
+ if (Callee->isVariadic())
+ return;
+
+ // Check if va_list is passed to callee function.
+ // If va_list is not passed, return.
+ bool hasVaList = false;
+ for (const auto *Param : Callee->parameters()) {
+ if (Param->getOriginalType().getCanonicalType() ==
+ getASTContext().getBuiltinVaListType().getCanonicalType()) {
+ hasVaList = true;
+ break;
+ }
+ }
+ if (!hasVaList)
+ return;
+
+ unsigned int FormatArgumentIndexOffset =
+ checkIfMethodHasImplicitObjectParameter(Callee) ? 2 : 1;
+
+ // If callee function is format function and format arguments are not
+ // relevant to emit diagnostic, save only information about format type
+ // (format index and first-to-check argument index are set to -1).
+ // Information about format type is later used to determine if there are
+ // more than one format type found.
+
+ unsigned int NumArgs = Args.size();
+ // Check if function has format attribute with forwarded format string.
+ IdentifierInfo *AttrType;
+ const ParmVarDecl *FormatStringArg;
+ if (!llvm::any_of(
+ Callee->specific_attrs<FormatAttr>(), [&](const FormatAttr *Attr) {
+ AttrType = Attr->getType();
+
+ int OffsetFormatIndex =
+ Attr->getFormatIdx() - FormatArgumentIndexOffset;
+ if (OffsetFormatIndex < 0 || (unsigned)OffsetFormatIndex >= NumArgs)
+ return false;
+
+ if (const auto *FormatArgExpr = dyn_cast<DeclRefExpr>(
+ Args[OffsetFormatIndex]->IgnoreParenCasts()))
+ if (FormatStringArg = dyn_cast_or_null<ParmVarDecl>(
+ FormatArgExpr->getReferencedDeclOfCallee()))
+ return true;
+ return false;
+ })) {
+ MissingAttributes.push_back(
+ FormatAttr::CreateImplicit(getASTContext(), AttrType, -1, -1));
+ return;
+ }
+
+ unsigned ArgumentIndexOffset =
+ checkIfMethodHasImplicitObjectParameter(Caller) ? 2 : 1;
+
+ unsigned NumOfCallerFunctionParams = Caller->getNumParams();
+
+ // Compare caller and callee function format attribute arguments (archetype
+ // and format string). If they don't match, caller misses format attribute.
+ if (llvm::any_of(
+ Caller->specific_attrs<FormatAttr>(), [&](const FormatAttr *Attr) {
+ if (Attr->getType() != AttrType)
+ return false;
+ int OffsetFormatIndex = Attr->getFormatIdx() - ArgumentIndexOffset;
+
+ if (OffsetFormatIndex < 0 ||
+ (unsigned)OffsetFormatIndex >= NumOfCallerFunctionParams)
+ return false;
+
+ if (Caller->parameters()[OffsetFormatIndex] != FormatStringArg)
+ return false;
+
+ return true;
+ })) {
+ MissingAttributes.push_back(
+ FormatAttr::CreateImplicit(getASTContext(), AttrType, -1, -1));
+ return;
+ }
+
+ // Get format string index
+ int FormatStringIndex =
+ FormatStringArg->getFunctionScopeIndex() + ArgumentIndexOffset;
+
+ // Get first argument index
+ int FirstToCheck = Caller->isVariadic()
+ ? (NumOfCallerFunctionParams + ArgumentIndexOffset)
+ : 0;
+
+ // Do not add duplicate in a vector of missing format attributes.
+ if (!llvm::any_of(MissingAttributes, [&](const FormatAttr *Attr) {
+ return Attr->getType() == AttrType &&
+ Attr->getFormatIdx() == FormatStringIndex &&
+ Attr->getFirstArg() == FirstToCheck;
+ }))
+ MissingAttributes.push_back(FormatAttr::CreateImplicit(
+ getASTContext(), AttrType, FormatStringIndex, FirstToCheck, Loc));
----------------
aaronpuchert wrote:
I think we should just not bother doing this and emit the warning right here.
https://github.com/llvm/llvm-project/pull/105479
More information about the cfe-commits
mailing list