diff --git a/clang/docs/DiagnosticsReference.rst b/clang/docs/DiagnosticsReference.rst --- a/clang/docs/DiagnosticsReference.rst +++ b/clang/docs/DiagnosticsReference.rst @@ -851,6 +851,14 @@ |:warning:`warning:` |nbsp| :diagtext:`cast from function call of type` |nbsp| :placeholder:`A` |nbsp| :diagtext:`to non-matching type` |nbsp| :placeholder:`B`| +--------------------------------------------------------------------------------------------------------------------------------------------------------------+ +-Wcast-function-type +------------------- +**Diagnostic text:** + ++--------------------------------------------------------------------------------------------------------------------------------------------------------------+ +|:warning:`warning:` |nbsp| :diagtext:`cast between incompatible function types from` |nbsp| :placeholder:`A` |nbsp| :diagtext:`to` |nbsp| :placeholder:`B`| ++--------------------------------------------------------------------------------------------------------------------------------------------------------------+ + -Wbinary-literal ---------------- diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td --- a/clang/include/clang/Basic/DiagnosticGroups.td +++ b/clang/include/clang/Basic/DiagnosticGroups.td @@ -474,6 +474,7 @@ def SelTypeCast : DiagGroup<"cast-of-sel-type">; def FunctionDefInObjCContainer : DiagGroup<"function-def-in-objc-container">; def BadFunctionCast : DiagGroup<"bad-function-cast">; +def CastFunctionType : DiagGroup<"cast-function-type">; def ObjCPropertyImpl : DiagGroup<"objc-property-implementation">; def ObjCPropertyNoAttribute : DiagGroup<"objc-property-no-attribute">; def ObjCPropertyAssignOnObjectType : DiagGroup<"objc-property-assign-on-object-type">; diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -8364,6 +8364,9 @@ def warn_bad_function_cast : Warning< "cast from function call of type %0 to non-matching type %1">, InGroup, DefaultIgnore; +def warn_cast_function_type : Warning< + "cast between incompatible function types from %0 to %1">, + InGroup, DefaultIgnore; def err_cast_pointer_to_non_pointer_int : Error< "pointer cannot be cast to type %0">; def err_cast_to_bfloat16 : Error<"cannot type-cast to __bf16">; diff --git a/clang/lib/Sema/SemaCast.cpp b/clang/lib/Sema/SemaCast.cpp --- a/clang/lib/Sema/SemaCast.cpp +++ b/clang/lib/Sema/SemaCast.cpp @@ -13,8 +13,8 @@ // //===----------------------------------------------------------------------===// -#include "clang/Sema/SemaInternal.h" #include "clang/AST/ASTContext.h" +#include "clang/AST/ASTStructuralEquivalence.h" #include "clang/AST/CXXInheritance.h" #include "clang/AST/ExprCXX.h" #include "clang/AST/ExprObjC.h" @@ -23,6 +23,7 @@ #include "clang/Basic/TargetInfo.h" #include "clang/Lex/Preprocessor.h" #include "clang/Sema/Initialization.h" +#include "clang/Sema/SemaInternal.h" #include "llvm/ADT/SmallVector.h" #include using namespace clang; @@ -1035,6 +1036,101 @@ << FixItHint::CreateReplacement(BeginLoc, "static_cast"); } +static bool argTypeIsABIEquivalent(QualType SrcType, QualType DestType, + ASTContext &Context) { + if (SrcType->isPointerType() && DestType->isPointerType()) + return true; + + // Allow integral type mismatch if their size are equal. + if (SrcType->isIntegralType(Context) && DestType->isIntegralType(Context)) + if (Context.getTypeInfoInChars(SrcType).Width == + Context.getTypeInfoInChars(DestType).Width) + return true; + + llvm::DenseSet> NonEquivalentDecls; + StructuralEquivalenceContext Ctx( + Context, Context, NonEquivalentDecls, StructuralEquivalenceKind::Default, + false /*StrictTypeSpelling*/, false /*Complain*/, + false /*ErrorOnTagTypeMismatch*/); + return Ctx.IsEquivalent(SrcType, DestType); +} + +static bool checkCastFunctionType(Sema &Self, const ExprResult &SrcExpr, + QualType DestType) { + if (Self.Diags.isIgnored(diag::warn_cast_function_type, + SrcExpr.get()->getExprLoc())) + return true; + + QualType SrcType = SrcExpr.get()->getType(); + const FunctionType *SrcFTy = nullptr; + const FunctionType *DstFTy = nullptr; + if (SrcType->isFunctionPointerType() && DestType->isFunctionPointerType()) { + SrcFTy = SrcType->castAs() + ->getPointeeType() + ->castAs(); + DstFTy = DestType->castAs() + ->getPointeeType() + ->castAs(); + } else if (SrcType->isMemberFunctionPointerType() && + DestType->isMemberFunctionPointerType()) { + SrcFTy = SrcType->castAs() + ->getPointeeType() + ->castAs(); + DstFTy = DestType->castAs() + ->getPointeeType() + ->castAs(); + } else { + return true; + } + assert(SrcFTy && DstFTy); + + auto IsVoidVoid = [](const FunctionType *T) { + if (!T->getReturnType()->isVoidType()) + return false; + if (auto PT = T->getAs()) + return !PT->isVariadic() && PT->getNumParams() == 0; + return false; + }; + + // Skip if either function type is void(*)(void) + if (IsVoidVoid(SrcFTy) || IsVoidVoid(DstFTy)) + return true; + + // Check return type. + if (!argTypeIsABIEquivalent(SrcFTy->getReturnType(), DstFTy->getReturnType(), + Self.Context)) + return false; + + // Check if either has unspecified number of parameters + if (SrcFTy->isFunctionNoProtoType() || DstFTy->isFunctionNoProtoType()) + return true; + + // Check parameter types. + + auto SrcFPTy = cast(SrcFTy); + auto DstFPTy = cast(DstFTy); + + // In a cast involving function types with a variable argument list only the + // types of initial arguments that are provided are considered. + unsigned NumParams = SrcFPTy->getNumParams(); + unsigned DstNumParams = DstFPTy->getNumParams(); + if (NumParams > DstNumParams) { + if (!DstFPTy->isVariadic()) + return false; + NumParams = DstNumParams; + } else if (NumParams < DstNumParams) { + if (!SrcFPTy->isVariadic()) + return false; + } + + for (unsigned i = 0; i < NumParams; ++i) + if (!argTypeIsABIEquivalent(SrcFPTy->getParamType(i), + DstFPTy->getParamType(i), Self.Context)) + return false; + + return true; +} + /// CheckReinterpretCast - Check that a reinterpret_cast\(SrcExpr) is /// valid. /// Refer to C++ 5.2.10 for details. reinterpret_cast is typically used in code @@ -1072,6 +1168,10 @@ if (Self.getLangOpts().allowsNonTrivialObjCLifetimeQualifiers()) checkObjCConversion(Sema::CCK_OtherCast); DiagnoseReinterpretUpDownCast(Self, SrcExpr.get(), DestType, OpRange); + + if (!checkCastFunctionType(Self, SrcExpr, DestType)) + Self.Diag(OpRange.getBegin(), diag::warn_cast_function_type) + << SrcExpr.get()->getType() << DestType << OpRange; } else { SrcExpr = ExprError(); } @@ -2645,6 +2745,11 @@ if (isValidCast(tcr)) { if (Kind == CK_BitCast) checkCastAlign(); + + if (!checkCastFunctionType(Self, SrcExpr, DestType)) + Self.Diag(OpRange.getBegin(), diag::warn_cast_function_type) + << SrcExpr.get()->getType() << DestType << OpRange; + } else { SrcExpr = ExprError(); } @@ -2957,6 +3062,10 @@ } } + if (!checkCastFunctionType(Self, SrcExpr, DestType)) + Self.Diag(OpRange.getBegin(), diag::warn_cast_function_type) + << SrcType << DestType << OpRange; + DiagnoseCastOfObjCSEL(Self, SrcExpr, DestType); DiagnoseCallingConvCast(Self, SrcExpr, DestType, OpRange); DiagnoseBadFunctionCast(Self, SrcExpr, DestType); diff --git a/clang/test/Sema/warn-cast-function-type.c b/clang/test/Sema/warn-cast-function-type.c new file mode 100644 --- /dev/null +++ b/clang/test/Sema/warn-cast-function-type.c @@ -0,0 +1,29 @@ +// RUN: %clang_cc1 -x c %s -fsyntax-only -Wno-unused-value -Wcast-function-type -triple x86_64-- -verify + +int x(long); + +typedef int (f1)(long); +typedef int (f2)(void*); +typedef int (f3)(); +typedef void (f4)(); +typedef void (f5)(void); +typedef int (f6)(long, int); +typedef int (f7)(long,...); + +f1 *a; +f2 *b; +f3 *c; +f4 *d; +f5 *e; +f6 *f; +f7 *g; + +void foo(void) { + a = (f1 *)x; + b = (f2 *)x; /* expected-warning {{cast between incompatible function types from 'int (*)(long)' to 'f2 *' (aka 'int (*)(void *)')}} */ + c = (f3 *)x; + d = (f4 *)x; /* expected-warning {{cast between incompatible function types from 'int (*)(long)' to 'f4 *' (aka 'void (*)()')}} */ + e = (f5 *)x; + f = (f6 *)x; /* expected-warning {{cast between incompatible function types from 'int (*)(long)' to 'f6 *' (aka 'int (*)(long, int)')}} */ + g = (f7 *)x; +} diff --git a/clang/test/Sema/warn-cast-function-type.cpp b/clang/test/Sema/warn-cast-function-type.cpp new file mode 100644 --- /dev/null +++ b/clang/test/Sema/warn-cast-function-type.cpp @@ -0,0 +1,40 @@ +// RUN: %clang_cc1 -x c++ %s -fsyntax-only -Wno-unused-value -Wcast-function-type -triple x86_64-- -verify + +int x(long); + +typedef int (f1)(long); +typedef int (f2)(void*); +typedef int (f3)(...); +typedef void (f4)(...); +typedef void (f5)(void); +typedef int (f6)(long, int); +typedef int (f7)(long,...); + +f1 *a; +f2 *b; +f3 *c; +f4 *d; +f5 *e; +f6 *f; +f7 *g; + +struct S +{ + void foo (int*); + void bar (int); +}; + +typedef void (S::*mf)(int); + +void foo() { + a = (f1 *)x; + b = (f2 *)x; /* expected-warning {{cast between incompatible function types from 'int (*)(long)' to 'f2 *' (aka 'int (*)(void *)')}} */ + b = reinterpret_cast(x); /* expected-warning {{cast between incompatible function types from 'int (*)(long)' to 'f2 *' (aka 'int (*)(void *)')}} */ + c = (f3 *)x; + d = (f4 *)x; /* expected-warning {{cast between incompatible function types from 'int (*)(long)' to 'f4 *' (aka 'void (*)(...)')}} */ + e = (f5 *)x; + f = (f6 *)x; /* expected-warning {{cast between incompatible function types from 'int (*)(long)' to 'f6 *' (aka 'int (*)(long, int)')}} */ + g = (f7 *)x; + + mf p1 = (mf)&S::foo; /* expected-warning {{cast between incompatible function types from 'void (S::*)(int *)' to 'mf' (aka 'void (S::*)(int)')}} */ +}