ychen created this revision.
ychen added reviewers: aaron.ballman, rsmith.
ychen requested review of this revision.
Herald added a project: clang.
Herald added a subscriber: cfe-commits.
Warn when a function pointer is cast to an incompatible function
pointer. In a cast involving function types with a variable argument
list only the types of initial arguments that are provided are
considered. Any parameter of pointer-type matches any other
pointer-type. Any benign differences in integral types are ignored, like
int vs. long on ILP32 targets. Likewise type qualifiers are ignored. The
function type void (*) (void) is special and matches everything, which
can be used to suppress this warning. In a cast involving pointer to
member types this warning warns whenever the type cast is changing the
pointer to member type. This warning is enabled by -Wextra.
It is not part of -Wextra for now. I could add it if it is preferred.
Repository:
rG LLVM Github Monorepo
https://reviews.llvm.org/D97831
Files:
clang/docs/DiagnosticsReference.rst
clang/include/clang/Basic/DiagnosticGroups.td
clang/include/clang/Basic/DiagnosticSemaKinds.td
clang/lib/Sema/SemaCast.cpp
clang/test/Sema/warn-cast-function-type.c
clang/test/Sema/warn-cast-function-type.cpp
Index: clang/test/Sema/warn-cast-function-type.cpp
===================================================================
--- /dev/null
+++ 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<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;
+
+ mf p1 = (mf)&S::foo; /* expected-warning {{cast between incompatible function types from 'void (S::*)(int *)' to 'mf' (aka 'void (S::*)(int)')}} */
+}
Index: clang/test/Sema/warn-cast-function-type.c
===================================================================
--- /dev/null
+++ 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;
+}
Index: clang/lib/Sema/SemaCast.cpp
===================================================================
--- clang/lib/Sema/SemaCast.cpp
+++ 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 <set>
using namespace clang;
@@ -1035,6 +1036,105 @@
<< 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<std::pair<Decl *, Decl *>> 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();
+ if (Self.getLangOpts().CPlusPlus) {
+ SrcType = Self.Context.getCanonicalType(SrcType);
+ DestType = Self.Context.getCanonicalType(DestType);
+ }
+ const FunctionType *SrcFTy = nullptr;
+ const FunctionType *DstFTy = nullptr;
+ if (SrcType->isFunctionPointerType() && DestType->isFunctionPointerType()) {
+ SrcFTy = SrcType->castAs<PointerType>()
+ ->getPointeeType()
+ ->castAs<FunctionType>();
+ DstFTy = DestType->castAs<PointerType>()
+ ->getPointeeType()
+ ->castAs<FunctionType>();
+ } else if (SrcType->isMemberFunctionPointerType() &&
+ DestType->isMemberFunctionPointerType()) {
+ SrcFTy = SrcType->castAs<MemberPointerType>()
+ ->getPointeeType()
+ ->castAs<FunctionType>();
+ DstFTy = DestType->castAs<MemberPointerType>()
+ ->getPointeeType()
+ ->castAs<FunctionType>();
+ } else {
+ return true;
+ }
+ assert(SrcFTy && DstFTy);
+
+ auto IsVoidVoid = [](const FunctionType *T) {
+ if (!T->getReturnType()->isVoidType())
+ return false;
+ if (auto PT = T->getAs<FunctionProtoType>())
+ 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<FunctionProtoType>(SrcFTy);
+ auto DstFPTy = cast<FunctionProtoType>(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\<DestType\>(SrcExpr) is
/// valid.
/// Refer to C++ 5.2.10 for details. reinterpret_cast is typically used in code
@@ -1072,6 +1172,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 +2749,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 +3066,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);
Index: clang/include/clang/Basic/DiagnosticSemaKinds.td
===================================================================
--- clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ 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<BadFunctionCast>, DefaultIgnore;
+def warn_cast_function_type : Warning<
+ "cast between incompatible function types from %0 to %1">,
+ InGroup<CastFunctionType>, 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">;
Index: clang/include/clang/Basic/DiagnosticGroups.td
===================================================================
--- clang/include/clang/Basic/DiagnosticGroups.td
+++ 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">;
Index: clang/docs/DiagnosticsReference.rst
===================================================================
--- clang/docs/DiagnosticsReference.rst
+++ 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
----------------
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits