diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -3818,6 +3818,14 @@ let Documentation = [ReleaseHandleDocs]; } +def DiagnoseAs : InheritableAttr { + let Spellings = [Clang<"diagnose_as">]; + let Args = [ExprArgument<"Function">, + VariadicUnsignedArgument<"ArgIndices">]; + let LateParsed = 1; + let Documentation = [DiagnoseAsDocs]; +} + def Builtin : InheritableAttr { let Spellings = []; let Args = [UnsignedArgument<"ID">]; diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td --- a/clang/include/clang/Basic/AttrDocs.td +++ b/clang/include/clang/Basic/AttrDocs.td @@ -5928,6 +5928,28 @@ }]; } +def DiagnoseAsDocs : Documentation { + let Category = DocCatFunction; + let Content = [{ +The ``diagnose_as` attribute specifies that Fortify diagnostics are to be +applied to the called function as if it were the function specified by the +attribute. The function whose diagnostics to mimic as well as the correct order +of arguments must be specified. + +For example, the attribute can be used as follow. + + .. code-block:: c + + __attribute__((diagnose_as (__builtin_memset, 3, 2, 1))) + void* mymemset(int n, int c, void* const s) { + return __builtin_memset(s, c, n); + } + +Then (when Fortify warnings are enabled) the call ``mymemset(n, c, s)`` will +diagnose overflows as if it were the call ``memset(s, c, n)``; +}]; +} + def ArmSveVectorBitsDocs : Documentation { let Category = DocCatType; let Content = [{ 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 @@ -594,20 +594,51 @@ isConstantEvaluated()) return; - unsigned BuiltinID = FD->getBuiltinID(/*ConsiderWrappers=*/true); + bool UseDAIAttr = false; + FunctionDecl *UseDecl = FD; + + auto DAIAttr = FD->getAttr(); + if (DAIAttr) { + DeclRefExpr *F = dyn_cast_or_null(DAIAttr->getFunction()); + if (!F) + return; + FunctionDecl *AttrDecl = dyn_cast_or_null(F->getFoundDecl()); + if (!AttrDecl) + return; + UseDecl = AttrDecl; + UseDAIAttr = true; + } + + unsigned BuiltinID = UseDecl->getBuiltinID(/*ConsiderWrappers=*/true); + if (!BuiltinID) return; const TargetInfo &TI = getASTContext().getTargetInfo(); unsigned SizeTypeWidth = TI.getTypeWidth(TI.getSizeType()); + auto TranslateIndex = [&](unsigned Index) -> Optional { + if (UseDAIAttr) { + if (Index >= DAIAttr->argIndices_size()) + return llvm::None; + return DAIAttr->argIndices_begin()[Index]; + } + return Index; + }; + auto ComputeExplicitObjectSizeArgument = [&](unsigned Index) -> Optional { + auto IndexOptional = TranslateIndex(Index); + if (!IndexOptional) + return llvm::None; + unsigned NewIndex = IndexOptional.getValue(); Expr::EvalResult Result; - Expr *SizeArg = TheCall->getArg(Index); + Expr *SizeArg = TheCall->getArg(NewIndex); if (!SizeArg->EvaluateAsInt(Result, getASTContext())) return llvm::None; - return Result.Val.getInt(); + auto Integer = Result.Val.getInt(); + Integer.setIsUnsigned(true); + return Integer; }; auto ComputeSizeArgument = [&](unsigned Index) -> Optional { @@ -616,10 +647,15 @@ // type 0. int BOSType = 0; if (const auto *POS = - FD->getParamDecl(Index)->getAttr()) + UseDecl->getParamDecl(Index)->getAttr()) BOSType = POS->getType(); - const Expr *ObjArg = TheCall->getArg(Index); + auto IndexOptional = TranslateIndex(Index); + if (!IndexOptional) + return llvm::None; + unsigned NewIndex = IndexOptional.getValue(); + + const Expr *ObjArg = TheCall->getArg(NewIndex); uint64_t Result; if (!ObjArg->tryEvaluateObjectSize(Result, getASTContext(), BOSType)) return llvm::None; @@ -629,7 +665,12 @@ }; auto ComputeStrLenArgument = [&](unsigned Index) -> Optional { - Expr *ObjArg = TheCall->getArg(Index); + auto IndexOptional = TranslateIndex(Index); + if (!IndexOptional) + return llvm::None; + unsigned NewIndex = IndexOptional.getValue(); + + Expr *ObjArg = TheCall->getArg(NewIndex); uint64_t Result; if (!ObjArg->tryEvaluateStrLen(Result, getASTContext())) return llvm::None; @@ -768,7 +809,8 @@ } if (!SourceSize || !DestinationSize || - SourceSize.getValue().ule(DestinationSize.getValue())) + llvm::APSInt::compareValues(SourceSize.getValue(), + DestinationSize.getValue()) <= 0) return; StringRef FunctionName = getASTContext().BuiltinInfo.getName(BuiltinID); diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -1001,6 +1001,38 @@ }; } +static void handleDiagnoseAsAttr(Sema &S, Decl *D, const ParsedAttr &AL) { + SmallVector Indices; + for (unsigned I = 1; I < AL.getNumArgs(); ++I) { + if (!AL.isArgExpr(I)) { + S.Diag(AL.getLoc(), diag::err_attribute_argument_type) + << AL << AANT_ArgumentIntegerConstant; + return; + } + + Expr *IndexExpr = AL.getArgAsExpr(I); + uint32_t Index; + + // If the expression is not parseable as a uint32_t we have a problem. + if (!checkUInt32Argument(S, AL, IndexExpr, Index, UINT_MAX, false)) { + S.Diag(AL.getLoc(), diag::err_attribute_argument_out_of_bounds) + << AL << I << IndexExpr->getSourceRange(); + return; + } + + Indices.push_back(Index - 1); + } + + if (!AL.isArgExpr(0)) { + S.Diag(AL.getLoc(), diag::err_attribute_argument_type) + << AL << AANT_ArgumentConstantExpr; + return; + } + + D->addAttr(::new (S.Context) DiagnoseAsAttr(S.Context, AL, AL.getArgAsExpr(0), + Indices.data(), Indices.size())); +} + static void handleDiagnoseIfAttr(Sema &S, Decl *D, const ParsedAttr &AL) { S.Diag(AL.getLoc(), diag::ext_clang_diagnose_if); @@ -8029,6 +8061,9 @@ case ParsedAttr::AT_DiagnoseIf: handleDiagnoseIfAttr(S, D, AL); break; + case ParsedAttr::AT_DiagnoseAs: + handleDiagnoseAsAttr(S, D, AL); + break; case ParsedAttr::AT_NoBuiltin: handleNoBuiltinAttr(S, D, AL); break; diff --git a/clang/test/Sema/warn-fortify-source.c b/clang/test/Sema/warn-fortify-source.c --- a/clang/test/Sema/warn-fortify-source.c +++ b/clang/test/Sema/warn-fortify-source.c @@ -181,6 +181,17 @@ sprintf(buf, "5%.1e", 9.f); // expected-warning {{'sprintf' will always overflow; destination buffer has size 6, but format string expands to at least 8}} } +void *test_memcpy(const void *src, size_t c, void *dst) __attribute__((diagnose_as(__builtin_memcpy, 3, 1, 2))) { + return __builtin_memcpy(dst, src, c); +} + +void call_test_memcpy() { + char bufferA[10]; + char bufferB[11]; + test_memcpy(bufferB, 10, bufferA); + test_memcpy(bufferB, 11, bufferA); // expected-warning {{'memcpy' will always overflow; destination buffer has size 10, but size argument is 11}} +} + #ifdef __cplusplus template struct S { void mf() const {