Hi!
I think I have now something useful, it has a few more heuristics
added, to reduce the number of false-positives so that it
is able to find real bugs, for instance in openssl it triggers
at a function cast which has already a TODO on it.
The heuristics are:
- handle void (*)(void) as a wild-card function type.
- ignore volatile, const qualifiers on parameters/return.
- handle any pointers as equivalent.
- handle integral types, enums, and booleans of same precision
and signedness as equivalent.
- stop parameter validation at the first "...".
I cannot convince myself to handle uintptr_t and pointers as
equivalent. It is possible that targets like m68k have
an ABI where uintptr_t and void* are different as return values
but are identical as parameter values.
IMHO adding an exception for uintptr_t vs. pointer as parameters
could easily prevent detection of real bugs. Even if it is safe
for all targets.
However it happens: Examples are the libiberty splay-tree functions,
and also one single place in linux, where an argument of type
"long int" is used to call a timer function with a pointer
argument. Note, linux does not enable -Wextra.
Patch was bootstrapped and reg-tested on x86_64-pc-linux-gnu.
Is it OK for trunk?
Thanks
Bernd.
gcc:
2017-10-06 Bernd Edlinger <[email protected]>
* doc/invoke.texi: Document -Wcast-function-type.
* recog.h (stored_funcptr): Change signature.
* tree-dump.c (dump_node): Avoid warning.
* typed-splay-tree.h (typed_splay_tree): Avoid warning.
libcpp:
2017-10-06 Bernd Edlinger <[email protected]>
* internal.h (maybe_print_line): Change signature.
c-family:
2017-10-06 Bernd Edlinger <[email protected]>
* c.opt (Wcast-function-type): New warning option.
* c-lex.c (get_fileinfo): Avoid warning.
* c-ppoutput.c (scan_translation_unit_directives_only): Remove cast.
c:
2017-10-06 Bernd Edlinger <[email protected]>
* c-typeck.c (c_abi_equiv_type_p, c_safe_function_type_cast_p): New.
(build_c_cast): Implement -Wcast_function_type.
cp:
2017-10-06 Bernd Edlinger <[email protected]>
* decl2.c (start_static_storage_duration_function): Avoid warning.
* typeck.c (cxx_abi_equiv_type_p, cxx_safe_function_type_cast_p): New.
(build_reinterpret_cast_1): Implement -Wcast_function_type.
testsuite:
2017-10-06 Bernd Edlinger <[email protected]>
* c-c++-common/Wcast-function-type.c: New test.
Index: gcc/c/c-typeck.c
===================================================================
--- gcc/c/c-typeck.c (revision 253493)
+++ gcc/c/c-typeck.c (working copy)
@@ -5474,6 +5474,52 @@ handle_warn_cast_qual (location_t loc, tree type,
while (TREE_CODE (in_type) == POINTER_TYPE);
}
+/* Heuristic check if two parameter types can be considered ABI-equivalent. */
+
+static bool
+c_abi_equiv_type_p (tree t1, tree t2)
+{
+ t1 = TYPE_MAIN_VARIANT (t1);
+ t2 = TYPE_MAIN_VARIANT (t2);
+
+ if (TREE_CODE (t1) == POINTER_TYPE
+ && TREE_CODE (t2) == POINTER_TYPE)
+ return true;
+
+ if (INTEGRAL_TYPE_P (t1)
+ && INTEGRAL_TYPE_P (t2)
+ && TYPE_PRECISION (t1) == TYPE_PRECISION (t2)
+ && TYPE_UNSIGNED (t1) == TYPE_UNSIGNED (t2))
+ return true;
+
+ return comptypes (t1, t2);
+}
+
+/* Check if a type cast between two function types can be considered safe. */
+
+static bool
+c_safe_function_type_cast_p (tree t1, tree t2)
+{
+ if (TREE_TYPE (t1) == void_type_node &&
+ TYPE_ARG_TYPES (t1) == void_list_node)
+ return true;
+
+ if (TREE_TYPE (t2) == void_type_node &&
+ TYPE_ARG_TYPES (t2) == void_list_node)
+ return true;
+
+ if (!c_abi_equiv_type_p (TREE_TYPE (t1), TREE_TYPE (t2)))
+ return false;
+
+ for (t1 = TYPE_ARG_TYPES (t1), t2 = TYPE_ARG_TYPES (t2);
+ t1 && t2;
+ t1 = TREE_CHAIN (t1), t2 = TREE_CHAIN (t2))
+ if (!c_abi_equiv_type_p (TREE_VALUE (t1), TREE_VALUE (t2)))
+ return false;
+
+ return true;
+}
+
/* Build an expression representing a cast to type TYPE of expression EXPR.
LOC is the location of the cast-- typically the open paren of the cast. */
@@ -5667,6 +5713,16 @@ build_c_cast (location_t loc, tree type, tree expr
pedwarn (loc, OPT_Wpedantic, "ISO C forbids "
"conversion of object pointer to function pointer type");
+ if (TREE_CODE (type) == POINTER_TYPE
+ && TREE_CODE (otype) == POINTER_TYPE
+ && TREE_CODE (TREE_TYPE (type)) == FUNCTION_TYPE
+ && TREE_CODE (TREE_TYPE (otype)) == FUNCTION_TYPE
+ && !c_safe_function_type_cast_p (TREE_TYPE (type),
+ TREE_TYPE (otype)))
+ warning_at (loc, OPT_Wcast_function_type,
+ "cast between incompatible function types"
+ " from %qT to %qT", otype, type);
+
ovalue = value;
value = convert (type, value);
Index: gcc/c-family/c-lex.c
===================================================================
--- gcc/c-family/c-lex.c (revision 253493)
+++ gcc/c-family/c-lex.c (working copy)
@@ -101,9 +101,11 @@ get_fileinfo (const char *name)
struct c_fileinfo *fi;
if (!file_info_tree)
- file_info_tree = splay_tree_new ((splay_tree_compare_fn) strcmp,
+ file_info_tree = splay_tree_new ((splay_tree_compare_fn)
+ (void (*) (void)) strcmp,
0,
- (splay_tree_delete_value_fn) free);
+ (splay_tree_delete_value_fn)
+ (void (*) (void)) free);
n = splay_tree_lookup (file_info_tree, (splay_tree_key) name);
if (n)
Index: gcc/c-family/c-ppoutput.c
===================================================================
--- gcc/c-family/c-ppoutput.c (revision 253493)
+++ gcc/c-family/c-ppoutput.c (working copy)
@@ -299,7 +299,7 @@ scan_translation_unit_directives_only (cpp_reader
struct _cpp_dir_only_callbacks cb;
cb.print_lines = print_lines_directives_only;
- cb.maybe_print_line = (void (*) (source_location)) maybe_print_line;
+ cb.maybe_print_line = maybe_print_line;
_cpp_preprocess_dir_only (pfile, &cb);
}
Index: gcc/c-family/c.opt
===================================================================
--- gcc/c-family/c.opt (revision 253493)
+++ gcc/c-family/c.opt (working copy)
@@ -384,6 +384,10 @@ Wc++17-compat
C++ ObjC++ Var(warn_cxx17_compat) Warning LangEnabledBy(C++ ObjC++,Wall)
Warn about C++ constructs whose meaning differs between ISO C++ 2014 and ISO C++ 2017.
+Wcast-function-type
+C ObjC C++ ObjC++ Var(warn_cast_function_type) Warning EnabledBy(Wextra)
+Warn about casts between incompatible function types.
+
Wcast-qual
C ObjC C++ ObjC++ Var(warn_cast_qual) Warning
Warn about casts which discard qualifiers.
Index: gcc/cp/decl2.c
===================================================================
--- gcc/cp/decl2.c (revision 253493)
+++ gcc/cp/decl2.c (working copy)
@@ -3484,7 +3484,8 @@ start_static_storage_duration_function (unsigned c
priority_info_map = splay_tree_new (splay_tree_compare_ints,
/*delete_key_fn=*/0,
/*delete_value_fn=*/
- (splay_tree_delete_value_fn) &free);
+ (splay_tree_delete_value_fn)
+ (void (*) (void)) free);
/* We always need to generate functions for the
DEFAULT_INIT_PRIORITY so enter it now. That way when we walk
Index: gcc/cp/typeck.c
===================================================================
--- gcc/cp/typeck.c (revision 253493)
+++ gcc/cp/typeck.c (working copy)
@@ -1173,6 +1173,52 @@ comp_template_parms_position (tree t1, tree t2)
return true;
}
+/* Heuristic check if two parameter types can be considered ABI-equivalent. */
+
+static bool
+cxx_abi_equiv_type_p (tree t1, tree t2)
+{
+ t1 = TYPE_MAIN_VARIANT (t1);
+ t2 = TYPE_MAIN_VARIANT (t2);
+
+ if (TREE_CODE (t1) == POINTER_TYPE
+ && TREE_CODE (t2) == POINTER_TYPE)
+ return true;
+
+ if (INTEGRAL_TYPE_P (t1)
+ && INTEGRAL_TYPE_P (t2)
+ && TYPE_PRECISION (t1) == TYPE_PRECISION (t2)
+ && TYPE_UNSIGNED (t1) == TYPE_UNSIGNED (t2))
+ return true;
+
+ return same_type_p (t1, t2);
+}
+
+/* Check if a type cast between two function types can be considered safe. */
+
+static bool
+cxx_safe_function_type_cast_p (tree t1, tree t2)
+{
+ if (TREE_TYPE (t1) == void_type_node &&
+ TYPE_ARG_TYPES (t1) == void_list_node)
+ return true;
+
+ if (TREE_TYPE (t2) == void_type_node &&
+ TYPE_ARG_TYPES (t2) == void_list_node)
+ return true;
+
+ if (!cxx_abi_equiv_type_p (TREE_TYPE (t1), TREE_TYPE (t2)))
+ return false;
+
+ for (t1 = TYPE_ARG_TYPES (t1), t2 = TYPE_ARG_TYPES (t2);
+ t1 && t2;
+ t1 = TREE_CHAIN (t1), t2 = TREE_CHAIN (t2))
+ if (!cxx_abi_equiv_type_p (TREE_VALUE (t1), TREE_VALUE (t2)))
+ return false;
+
+ return true;
+}
+
/* Subroutine in comptypes. */
static bool
@@ -7263,7 +7309,19 @@ build_reinterpret_cast_1 (tree type, tree expr, bo
return rvalue (expr);
else if ((TYPE_PTRFN_P (type) && TYPE_PTRFN_P (intype))
|| (TYPE_PTRMEMFUNC_P (type) && TYPE_PTRMEMFUNC_P (intype)))
- return build_nop (type, expr);
+ {
+ if ((complain & tf_warning)
+ && TREE_CODE (type) == POINTER_TYPE
+ && TREE_CODE (intype) == POINTER_TYPE
+ && TREE_CODE (TREE_TYPE (type)) == FUNCTION_TYPE
+ && TREE_CODE (TREE_TYPE (intype)) == FUNCTION_TYPE
+ && !cxx_safe_function_type_cast_p (TREE_TYPE (type),
+ TREE_TYPE (intype)))
+ warning (OPT_Wcast_function_type,
+ "cast between incompatible function types"
+ " from %qH to %qI", intype, type);
+ return build_nop (type, expr);
+ }
else if ((TYPE_PTRDATAMEM_P (type) && TYPE_PTRDATAMEM_P (intype))
|| (TYPE_PTROBV_P (type) && TYPE_PTROBV_P (intype)))
{
Index: gcc/doc/invoke.texi
===================================================================
--- gcc/doc/invoke.texi (revision 253493)
+++ gcc/doc/invoke.texi (working copy)
@@ -267,7 +267,7 @@ Objective-C and Objective-C++ Dialects}.
-Wno-builtin-declaration-mismatch @gol
-Wno-builtin-macro-redefined -Wc90-c99-compat -Wc99-c11-compat @gol
-Wc++-compat -Wc++11-compat -Wc++14-compat @gol
--Wcast-align -Wcast-align=strict -Wcast-qual @gol
+-Wcast-align -Wcast-align=strict -Wcast-function-type -Wcast-qual @gol
-Wchar-subscripts -Wchkp -Wcatch-value -Wcatch-value=@var{n} @gol
-Wclobbered -Wcomment -Wconditionally-supported @gol
-Wconversion -Wcoverage-mismatch -Wno-cpp -Wdangling-else -Wdate-time @gol
@@ -3899,6 +3899,7 @@ This enables some extra warning flags that are not
name is still supported, but the newer name is more descriptive.)
@gccoptlist{-Wclobbered @gol
+-Wcast-function-type @gol
-Wempty-body @gol
-Wignored-qualifiers @gol
-Wimplicit-fallthrough=3 @gol
@@ -5959,6 +5960,21 @@ Warn whenever a pointer is cast such that the requ
target is increased. For example, warn if a @code{char *} is cast to
an @code{int *} regardless of the target machine.
+@item -Wcast-function-type
+@opindex Wcast-function-type
+@opindex Wno-cast-function-type
+Warn when a function pointer is cast to an incompatible function pointer.
+When one of the function types uses variable arguments like this
+@code{int f(...);}, then only the return value and the parameters before
+the @code{...} are checked, otherwise the return value and all parametes
+are checked for binary compatibility. Any parameter of pointer-type
+matches any other pointer-type. Any benign differences in integral types
+are ignored, like @code{int} vs. @code{long} on ILP32 targets. Likewise
+@code{const} and @code{volatile} qualifiers are ignored. The function
+type @code{void (*) (void);} is special and matches everything, which can
+be used to suppress this warning.
+This warning is enabled by @option{-Wextra}.
+
@item -Wwrite-strings
@opindex Wwrite-strings
@opindex Wno-write-strings
Index: gcc/recog.h
===================================================================
--- gcc/recog.h (revision 253493)
+++ gcc/recog.h (working copy)
@@ -294,7 +294,7 @@ struct insn_gen_fn
typedef rtx_insn * (*f15) (rtx, rtx, rtx, rtx, rtx, rtx, rtx, rtx, rtx, rtx, rtx, rtx, rtx, rtx, rtx);
typedef rtx_insn * (*f16) (rtx, rtx, rtx, rtx, rtx, rtx, rtx, rtx, rtx, rtx, rtx, rtx, rtx, rtx, rtx, rtx);
- typedef f0 stored_funcptr;
+ typedef void (*stored_funcptr) (void);
rtx_insn * operator () (void) const { return ((f0)func) (); }
rtx_insn * operator () (rtx a0) const { return ((f1)func) (a0); }
Index: gcc/testsuite/c-c++-common/Wcast-function-type.c
===================================================================
--- gcc/testsuite/c-c++-common/Wcast-function-type.c (revision 0)
+++ gcc/testsuite/c-c++-common/Wcast-function-type.c (working copy)
@@ -0,0 +1,31 @@
+/* { dg-do compile } */
+/* { dg-options "-Wcast-function-type" } */
+
+int f(long);
+
+typedef int (f1)(long);
+typedef int (f2)(void*);
+#ifdef __cplusplus
+typedef int (f3)(...);
+typedef void (f4)(...);
+#else
+typedef int (f3)();
+typedef void (f4)();
+#endif
+typedef void (f5)(void);
+
+f1 *a;
+f2 *b;
+f3 *c;
+f4 *d;
+f5 *e;
+
+void
+foo (void)
+{
+ a = (f1 *) f; /* { dg-bogus "incompatible function types" } */
+ b = (f2 *) f; /* { dg-warning "incompatible function types" } */
+ c = (f3 *) f; /* { dg-bogus "incompatible function types" } */
+ d = (f4 *) f; /* { dg-warning "incompatible function types" } */
+ e = (f5 *) f; /* { dg-bogus "incompatible function types" } */
+}
Index: gcc/tree-dump.c
===================================================================
--- gcc/tree-dump.c (revision 253493)
+++ gcc/tree-dump.c (working copy)
@@ -735,7 +735,8 @@ dump_node (const_tree t, dump_flags_t flags, FILE
di.flags = flags;
di.node = t;
di.nodes = splay_tree_new (splay_tree_compare_pointers, 0,
- (splay_tree_delete_value_fn) &free);
+ (splay_tree_delete_value_fn)
+ (void (*) (void)) free);
/* Queue up the first node. */
queue (&di, t, DUMP_NONE);
Index: gcc/typed-splay-tree.h
===================================================================
--- gcc/typed-splay-tree.h (revision 253493)
+++ gcc/typed-splay-tree.h (working copy)
@@ -75,9 +75,12 @@ inline typed_splay_tree<KEY_TYPE, VALUE_TYPE>::
delete_key_fn delete_key_fn,
delete_value_fn delete_value_fn)
{
- m_inner = splay_tree_new ((splay_tree_compare_fn)compare_fn,
- (splay_tree_delete_key_fn)delete_key_fn,
- (splay_tree_delete_value_fn)delete_value_fn);
+ m_inner = splay_tree_new ((splay_tree_compare_fn)
+ (void (*) (void)) compare_fn,
+ (splay_tree_delete_key_fn)
+ (void (*) (void)) delete_key_fn,
+ (splay_tree_delete_value_fn)
+ (void (*) (void)) delete_value_fn);
}
/* Destructor for typed_splay_tree <K, V>. */
Index: libcpp/internal.h
===================================================================
--- libcpp/internal.h (revision 253493)
+++ libcpp/internal.h (working copy)
@@ -708,7 +708,7 @@ struct _cpp_dir_only_callbacks
{
/* Called to print a block of lines. */
void (*print_lines) (int, const void *, size_t);
- void (*maybe_print_line) (source_location);
+ bool (*maybe_print_line) (source_location);
};
extern void _cpp_preprocess_dir_only (cpp_reader *,