[clang] diagnose_as_builtin attribute for Fortify diagnosing like builtins.

Differential Revision: https://reviews.llvm.org/D112024
This commit is contained in:
Michael Benfield 2021-09-14 18:36:58 +00:00
parent fd5e493874
commit bc5f2d12ca
8 changed files with 319 additions and 11 deletions

View File

@ -3865,6 +3865,14 @@ def ReleaseHandle : InheritableParamAttr {
let Documentation = [ReleaseHandleDocs]; let Documentation = [ReleaseHandleDocs];
} }
def DiagnoseAsBuiltin : InheritableAttr {
let Spellings = [Clang<"diagnose_as_builtin">];
let Args = [DeclArgument<Function, "Function">,
VariadicUnsignedArgument<"ArgIndices">];
let Subjects = SubjectList<[Function]>;
let Documentation = [DiagnoseAsBuiltinDocs];
}
def Builtin : InheritableAttr { def Builtin : InheritableAttr {
let Spellings = []; let Spellings = [];
let Args = [UnsignedArgument<"ID">]; let Args = [UnsignedArgument<"ID">];

View File

@ -5984,6 +5984,50 @@ attribute requires a string literal argument to identify the handle being releas
}]; }];
} }
def DiagnoseAsBuiltinDocs : Documentation {
let Category = DocCatFunction;
let Content = [{
The ``diagnose_as_builtin` attribute indicates that Fortify diagnostics are to
be applied to the declared function as if it were the function specified by the
attribute. The builtin function whose diagnostics are to be mimicked should be
given. In addition, the order in which arguments should be applied must also
be given.
For example, the attribute can be used as follows.
.. code-block:: c
__attribute__((diagnose_as_builtin(__builtin_memset, 3, 2, 1)))
void *mymemset(int n, int c, void *s) {
// ...
}
This indicates that calls to ``mymemset`` should be diagnosed as if they were
calls to ``__builtin_memset``. The arguments ``3, 2, 1`` indicate by index the
order in which arguments of ``mymemset`` should be applied to
``__builtin_memset``. The third argument should be applied first, then the
second, and then the first. Thus (when Fortify warnings are enabled) the call
``mymemset(n, c, s)`` will diagnose overflows as if it were the call
``__builtin_memset(s, c, n)``.
For variadic functions, the variadic arguments must come in the same order as
they would to the builtin function, after all normal arguments. For instance,
to diagnose a new function as if it were `sscanf`, we can use the attribute as
follows.
.. code-block:: c
__attribute__((diagnose_as_builtin(sscanf, 1, 2)))
int mysscanf(const char *str, const char *format, ...) {
// ...
}
Then the call `mysscanf("abc def", "%4s %4s", buf1, buf2)` will be diagnosed as
if it were the call `sscanf("abc def", "%4s %4s", buf1, buf2)`.
This attribute cannot be applied to non-static member functions.
}];
}
def ArmSveVectorBitsDocs : Documentation { def ArmSveVectorBitsDocs : Documentation {
let Category = DocCatType; let Category = DocCatType;
let Content = [{ let Content = [{

View File

@ -2956,6 +2956,17 @@ def err_attribute_invalid_argument : Error<
def err_attribute_wrong_number_arguments : Error< def err_attribute_wrong_number_arguments : Error<
"%0 attribute %plural{0:takes no arguments|1:takes one argument|" "%0 attribute %plural{0:takes no arguments|1:takes one argument|"
":requires exactly %1 arguments}1">; ":requires exactly %1 arguments}1">;
def err_attribute_wrong_number_arguments_for : Error <
"%0 attribute references function %1, which %plural{0:takes no arguments|1:takes one argument|"
":takes exactly %2 arguments}2">;
def err_attribute_bounds_for_function : Error<
"%0 attribute references parameter %1, but the function %2 has only %3 parameters">;
def err_attribute_no_member_function : Error<
"%0 attribute cannot be applied to non-static member functions">;
def err_attribute_parameter_types : Error<
"%0 attribute parameter types do not match: parameter %1 of function %2 has type %3, "
"but parameter %4 of function %5 has type %6">;
def err_attribute_too_many_arguments : Error< def err_attribute_too_many_arguments : Error<
"%0 attribute takes no more than %1 argument%s1">; "%0 attribute takes no more than %1 argument%s1">;
def err_attribute_too_few_arguments : Error< def err_attribute_too_few_arguments : Error<
@ -3013,7 +3024,7 @@ def err_attribute_sizeless_type : Error<
"%0 attribute cannot be applied to sizeless type %1">; "%0 attribute cannot be applied to sizeless type %1">;
def err_attribute_argument_n_type : Error< def err_attribute_argument_n_type : Error<
"%0 attribute requires parameter %1 to be %select{int or bool|an integer " "%0 attribute requires parameter %1 to be %select{int or bool|an integer "
"constant|a string|an identifier|a constant expression}2">; "constant|a string|an identifier|a constant expression|a builtin function}2">;
def err_attribute_argument_type : Error< def err_attribute_argument_type : Error<
"%0 attribute requires %select{int or bool|an integer " "%0 attribute requires %select{int or bool|an integer "
"constant|a string|an identifier}1">; "constant|a string|an identifier}1">;

View File

@ -1097,6 +1097,7 @@ enum AttributeArgumentNType {
AANT_ArgumentString, AANT_ArgumentString,
AANT_ArgumentIdentifier, AANT_ArgumentIdentifier,
AANT_ArgumentConstantExpr, AANT_ArgumentConstantExpr,
AANT_ArgumentBuiltinFunction,
}; };
/// These constants match the enumerated choices of /// These constants match the enumerated choices of

View File

@ -446,14 +446,14 @@ public:
break; break;
} }
auto OptionalFW = FS.getFieldWidth(); analyze_format_string::OptionalAmount FW = FS.getFieldWidth();
if (OptionalFW.getHowSpecified() != if (FW.getHowSpecified() !=
analyze_format_string::OptionalAmount::HowSpecified::Constant) analyze_format_string::OptionalAmount::HowSpecified::Constant)
return true; return true;
unsigned SourceSize = OptionalFW.getConstantAmount() + NulByte; unsigned SourceSize = FW.getConstantAmount() + NulByte;
auto DestSizeAPS = ComputeSizeArgument(FS.getArgIndex()); Optional<llvm::APSInt> DestSizeAPS = ComputeSizeArgument(FS.getArgIndex());
if (!DestSizeAPS) if (!DestSizeAPS)
return true; return true;
@ -652,20 +652,53 @@ void Sema::checkFortifiedBuiltinMemoryFunction(FunctionDecl *FD,
isConstantEvaluated()) isConstantEvaluated())
return; return;
unsigned BuiltinID = FD->getBuiltinID(/*ConsiderWrappers=*/true); bool UseDABAttr = false;
const FunctionDecl *UseDecl = FD;
const auto *DABAttr = FD->getAttr<DiagnoseAsBuiltinAttr>();
if (DABAttr) {
UseDecl = DABAttr->getFunction();
assert(UseDecl && "Missing FunctionDecl in DiagnoseAsBuiltin attribute!");
UseDABAttr = true;
}
unsigned BuiltinID = UseDecl->getBuiltinID(/*ConsiderWrappers=*/true);
if (!BuiltinID) if (!BuiltinID)
return; return;
const TargetInfo &TI = getASTContext().getTargetInfo(); const TargetInfo &TI = getASTContext().getTargetInfo();
unsigned SizeTypeWidth = TI.getTypeWidth(TI.getSizeType()); unsigned SizeTypeWidth = TI.getTypeWidth(TI.getSizeType());
auto TranslateIndex = [&](unsigned Index) -> Optional<unsigned> {
// If we refer to a diagnose_as_builtin attribute, we need to change the
// argument index to refer to the arguments of the called function. Unless
// the index is out of bounds, which presumably means it's a variadic
// function.
if (!UseDABAttr)
return Index;
unsigned DABIndices = DABAttr->argIndices_size();
unsigned NewIndex = Index < DABIndices
? DABAttr->argIndices_begin()[Index]
: Index - DABIndices + FD->getNumParams();
if (NewIndex >= TheCall->getNumArgs())
return llvm::None;
return NewIndex;
};
auto ComputeExplicitObjectSizeArgument = auto ComputeExplicitObjectSizeArgument =
[&](unsigned Index) -> Optional<llvm::APSInt> { [&](unsigned Index) -> Optional<llvm::APSInt> {
Optional<unsigned> IndexOptional = TranslateIndex(Index);
if (!IndexOptional)
return llvm::None;
unsigned NewIndex = IndexOptional.getValue();
Expr::EvalResult Result; Expr::EvalResult Result;
Expr *SizeArg = TheCall->getArg(Index); Expr *SizeArg = TheCall->getArg(NewIndex);
if (!SizeArg->EvaluateAsInt(Result, getASTContext())) if (!SizeArg->EvaluateAsInt(Result, getASTContext()))
return llvm::None; return llvm::None;
return Result.Val.getInt(); llvm::APSInt Integer = Result.Val.getInt();
Integer.setIsUnsigned(true);
return Integer;
}; };
auto ComputeSizeArgument = [&](unsigned Index) -> Optional<llvm::APSInt> { auto ComputeSizeArgument = [&](unsigned Index) -> Optional<llvm::APSInt> {
@ -680,7 +713,12 @@ void Sema::checkFortifiedBuiltinMemoryFunction(FunctionDecl *FD,
BOSType = POS->getType(); BOSType = POS->getType();
} }
const Expr *ObjArg = TheCall->getArg(Index); Optional<unsigned> IndexOptional = TranslateIndex(Index);
if (!IndexOptional)
return llvm::None;
unsigned NewIndex = IndexOptional.getValue();
const Expr *ObjArg = TheCall->getArg(NewIndex);
uint64_t Result; uint64_t Result;
if (!ObjArg->tryEvaluateObjectSize(Result, getASTContext(), BOSType)) if (!ObjArg->tryEvaluateObjectSize(Result, getASTContext(), BOSType))
return llvm::None; return llvm::None;
@ -690,7 +728,12 @@ void Sema::checkFortifiedBuiltinMemoryFunction(FunctionDecl *FD,
}; };
auto ComputeStrLenArgument = [&](unsigned Index) -> Optional<llvm::APSInt> { auto ComputeStrLenArgument = [&](unsigned Index) -> Optional<llvm::APSInt> {
Expr *ObjArg = TheCall->getArg(Index); Optional<unsigned> IndexOptional = TranslateIndex(Index);
if (!IndexOptional)
return llvm::None;
unsigned NewIndex = IndexOptional.getValue();
const Expr *ObjArg = TheCall->getArg(NewIndex);
uint64_t Result; uint64_t Result;
if (!ObjArg->tryEvaluateStrLen(Result, getASTContext())) if (!ObjArg->tryEvaluateStrLen(Result, getASTContext()))
return llvm::None; return llvm::None;
@ -898,7 +941,8 @@ void Sema::checkFortifiedBuiltinMemoryFunction(FunctionDecl *FD,
} }
if (!SourceSize || !DestinationSize || if (!SourceSize || !DestinationSize ||
SourceSize.getValue().ule(DestinationSize.getValue())) llvm::APSInt::compareValues(SourceSize.getValue(),
DestinationSize.getValue()) <= 0)
return; return;
StringRef FunctionName = GetFunctionName(); StringRef FunctionName = GetFunctionName();

View File

@ -1001,6 +1001,84 @@ public:
}; };
} }
static void handleDiagnoseAsBuiltinAttr(Sema &S, Decl *D,
const ParsedAttr &AL) {
const auto *DeclFD = cast<FunctionDecl>(D);
if (const auto *MethodDecl = dyn_cast<CXXMethodDecl>(DeclFD))
if (!MethodDecl->isStatic()) {
S.Diag(AL.getLoc(), diag::err_attribute_no_member_function) << AL;
return;
}
auto DiagnoseType = [&](unsigned Index, AttributeArgumentNType T) {
SourceLocation Loc = [&]() {
auto Union = AL.getArg(Index - 1);
if (Union.is<Expr *>())
return Union.get<Expr *>()->getBeginLoc();
return Union.get<IdentifierLoc *>()->Loc;
}();
S.Diag(Loc, diag::err_attribute_argument_n_type) << AL << Index << T;
};
FunctionDecl *AttrFD = [&]() -> FunctionDecl * {
if (!AL.isArgExpr(0))
return nullptr;
auto *F = dyn_cast_or_null<DeclRefExpr>(AL.getArgAsExpr(0));
if (!F)
return nullptr;
return dyn_cast_or_null<FunctionDecl>(F->getFoundDecl());
}();
if (!AttrFD || !AttrFD->getBuiltinID(true)) {
DiagnoseType(1, AANT_ArgumentBuiltinFunction);
return;
}
if (AttrFD->getNumParams() != AL.getNumArgs() - 1) {
S.Diag(AL.getLoc(), diag::err_attribute_wrong_number_arguments_for)
<< AL << AttrFD << AttrFD->getNumParams();
return;
}
SmallVector<unsigned, 8> Indices;
for (unsigned I = 1; I < AL.getNumArgs(); ++I) {
if (!AL.isArgExpr(I)) {
DiagnoseType(I + 1, AANT_ArgumentIntegerConstant);
return;
}
const Expr *IndexExpr = AL.getArgAsExpr(I);
uint32_t Index;
if (!checkUInt32Argument(S, AL, IndexExpr, Index, I + 1, false))
return;
if (Index > DeclFD->getNumParams()) {
S.Diag(AL.getLoc(), diag::err_attribute_bounds_for_function)
<< AL << Index << DeclFD << DeclFD->getNumParams();
return;
}
QualType T1 = AttrFD->getParamDecl(I - 1)->getType();
QualType T2 = DeclFD->getParamDecl(Index - 1)->getType();
if (T1.getCanonicalType().getUnqualifiedType() !=
T2.getCanonicalType().getUnqualifiedType()) {
S.Diag(IndexExpr->getBeginLoc(), diag::err_attribute_parameter_types)
<< AL << Index << DeclFD << T2 << I << AttrFD << T1;
return;
}
Indices.push_back(Index - 1);
}
D->addAttr(::new (S.Context) DiagnoseAsBuiltinAttr(
S.Context, AL, AttrFD, Indices.data(), Indices.size()));
}
static void handleDiagnoseIfAttr(Sema &S, Decl *D, const ParsedAttr &AL) { static void handleDiagnoseIfAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
S.Diag(AL.getLoc(), diag::ext_clang_diagnose_if); S.Diag(AL.getLoc(), diag::ext_clang_diagnose_if);
@ -8159,6 +8237,9 @@ static void ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D,
case ParsedAttr::AT_DiagnoseIf: case ParsedAttr::AT_DiagnoseIf:
handleDiagnoseIfAttr(S, D, AL); handleDiagnoseIfAttr(S, D, AL);
break; break;
case ParsedAttr::AT_DiagnoseAsBuiltin:
handleDiagnoseAsBuiltinAttr(S, D, AL);
break;
case ParsedAttr::AT_NoBuiltin: case ParsedAttr::AT_NoBuiltin:
handleNoBuiltinAttr(S, D, AL); handleNoBuiltinAttr(S, D, AL);
break; break;

View File

@ -57,6 +57,7 @@
// CHECK-NEXT: DLLExport (SubjectMatchRule_function, SubjectMatchRule_variable, SubjectMatchRule_record, SubjectMatchRule_objc_interface) // CHECK-NEXT: DLLExport (SubjectMatchRule_function, SubjectMatchRule_variable, SubjectMatchRule_record, SubjectMatchRule_objc_interface)
// CHECK-NEXT: DLLImport (SubjectMatchRule_function, SubjectMatchRule_variable, SubjectMatchRule_record, SubjectMatchRule_objc_interface) // CHECK-NEXT: DLLImport (SubjectMatchRule_function, SubjectMatchRule_variable, SubjectMatchRule_record, SubjectMatchRule_objc_interface)
// CHECK-NEXT: Destructor (SubjectMatchRule_function) // CHECK-NEXT: Destructor (SubjectMatchRule_function)
// CHECK-NEXT: DiagnoseAsBuiltin (SubjectMatchRule_function)
// CHECK-NEXT: DisableSanitizerInstrumentation (SubjectMatchRule_function, SubjectMatchRule_objc_method, SubjectMatchRule_variable_is_global) // CHECK-NEXT: DisableSanitizerInstrumentation (SubjectMatchRule_function, SubjectMatchRule_objc_method, SubjectMatchRule_variable_is_global)
// CHECK-NEXT: DisableTailCalls (SubjectMatchRule_function, SubjectMatchRule_objc_method) // CHECK-NEXT: DisableTailCalls (SubjectMatchRule_function, SubjectMatchRule_objc_method)
// CHECK-NEXT: EnableIf (SubjectMatchRule_function) // CHECK-NEXT: EnableIf (SubjectMatchRule_function)

View File

@ -0,0 +1,118 @@
// RUN: %clang_cc1 -Wfortify-source -triple x86_64-apple-macosx10.14.0 %s -verify
// RUN: %clang_cc1 -Wfortify-source -xc++ -triple x86_64-apple-macosx10.14.0 %s -verify
typedef unsigned long size_t;
__attribute__((diagnose_as_builtin(__builtin_memcpy, 3, 1, 2))) int x; // expected-warning {{'diagnose_as_builtin' attribute only applies to functions}}
void *test_memcpy(const void *src, size_t c, void *dst) __attribute__((diagnose_as_builtin(__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}}
}
void failure_function_doesnt_exist() __attribute__((diagnose_as_builtin(__function_that_doesnt_exist))) {} // expected-error {{use of undeclared identifier '__function_that_doesnt_exist'}}
void not_a_builtin() {}
void failure_not_a_builtin() __attribute__((diagnose_as_builtin(not_a_builtin))) {} // expected-error {{'diagnose_as_builtin' attribute requires parameter 1 to be a builtin function}}
void failure_too_many_parameters(void *dst, const void *src, size_t count, size_t nothing) __attribute__((diagnose_as_builtin(__builtin_memcpy, 1, 2, 3, 4))) {} // expected-error {{'diagnose_as_builtin' attribute references function '__builtin_memcpy', which takes exactly 3 arguments}}
void failure_too_few_parameters(void *dst, const void *src) __attribute__((diagnose_as_builtin(__builtin_memcpy, 1, 2))) {} // expected-error{{'diagnose_as_builtin' attribute references function '__builtin_memcpy', which takes exactly 3 arguments}}
void failure_not_an_integer(void *dst, const void *src, size_t count) __attribute__((diagnose_as_builtin(__builtin_memcpy, "abc", 2, 3))) {} // expected-error{{'diagnose_as_builtin' attribute requires parameter 2 to be an integer constant}}
void failure_not_a_builtin2() __attribute__((diagnose_as_builtin("abc"))) {} // expected-error{{'diagnose_as_builtin' attribute requires parameter 1 to be a builtin function}}
void failure_parameter_index_bounds(void *dst, const void *src) __attribute__((diagnose_as_builtin(__builtin_memcpy, 1, 2, 3))) {} // expected-error{{'diagnose_as_builtin' attribute references parameter 3, but the function 'failure_parameter_index_bounds' has only 2 parameters}}
void failure_parameter_types(double dst, const void *src, size_t count) __attribute__((diagnose_as_builtin(__builtin_memcpy, 1, 2, 3))) {} // expected-error{{'diagnose_as_builtin' attribute parameter types do not match: parameter 1 of function 'failure_parameter_types' has type 'double', but parameter 1 of function '__builtin_memcpy' has type 'void *'}}
void to_redeclare(void *dst, const void *src, size_t count);
void use_to_redeclare() {
char src[10];
char dst[9];
// We shouldn't get an error yet.
to_redeclare(dst, src, 10);
}
void to_redeclare(void *dst, const void *src, size_t count) __attribute((diagnose_as_builtin(__builtin_memcpy, 1, 2, 3)));
void error_to_redeclare() {
char src[10];
char dst[9];
// Now we get an error.
to_redeclare(dst, src, 10); // expected-warning {{'memcpy' will always overflow; destination buffer has size 9, but size argument is 10}}
}
// Make sure this works even with extra qualifiers and the pass_object_size attribute.
void *memcpy2(void *const dst __attribute__((pass_object_size(0))), const void *src, size_t copy_amount) __attribute((diagnose_as_builtin(__builtin_memcpy, 1, 2, 3)));
void call_memcpy2() {
char buf1[10];
char buf2[11];
memcpy2(buf1, buf2, 11); // expected-warning {{'memcpy' will always overflow; destination buffer has size 10, but size argument is 11}}
}
// We require the types to be identical, modulo canonicalization and qualifiers.
// Maybe this could be relaxed if it proves too restrictive.
void failure_type(void *dest, char val, size_t n) __attribute__((diagnose_as_builtin(__builtin_memset, 1, 2, 3))) {} // expected-error {{'diagnose_as_builtin' attribute parameter types do not match: parameter 2 of function 'failure_type' has type 'char', but parameter 2 of function '__builtin_memset' has type 'int'}}
#ifdef __cplusplus
extern "C" {
#endif
extern int sscanf(const char *input, const char *format, ...);
#ifdef __cplusplus
}
#endif
// Variadics should work.
int mysscanf(const char *str, const char *format, ...) __attribute__((diagnose_as_builtin(sscanf, 1, 2)));
void warn_mysccanf() {
char buf[10];
mysscanf("", "%10s", buf); // expected-warning{{'sscanf' may overflow; destination buffer in argument 3 has size 10, but the corresponding specifier may require size 11}}
}
#ifdef __cplusplus
template <class T>
void some_templated_function(int x) {
return;
}
void failure_template(int x) __attribute__((diagnose_as_builtin(some_templated_function, 1))) {} // expected-error{{'diagnose_as_builtin' attribute requires parameter 1 to be a builtin function}}
void failure_template_instantiation(int x) __attribute__((diagnose_as_builtin(some_templated_function<int>, 1))) {} // expected-error{{'diagnose_as_builtin' attribute requires parameter 1 to be a builtin function}}
void overloaded_function(unsigned);
void overloaded_function(int);
void failure_overloaded_function(int) __attribute__((diagnose_as_builtin(overloaded_function, 1))) {} // expected-error{{'diagnose_as_builtin' attribute requires parameter 1 to be a builtin function}}
struct S {
__attribute__((diagnose_as_builtin(__builtin_memset))) void f(); // expected-error{{'diagnose_as_builtin' attribute cannot be applied to non-static member functions}}
__attribute__((diagnose_as_builtin(__builtin_memcpy, 3, 1, 2))) static void *test_memcpy(const void *src, size_t c, void *dst) {
return __builtin_memcpy(dst, src, c);
}
};
void call_static_test_memcpy() {
char bufferA[10];
char bufferB[11];
S::test_memcpy(bufferB, 10, bufferA);
S::test_memcpy(bufferB, 11, bufferA); // expected-warning {{'memcpy' will always overflow; destination buffer has size 10, but size argument is 11}}
}
#endif