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 <bernd.edlin...@hotmail.de> * 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 <bernd.edlin...@hotmail.de> * internal.h (maybe_print_line): Change signature. c-family: 2017-10-06 Bernd Edlinger <bernd.edlin...@hotmail.de> * 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 <bernd.edlin...@hotmail.de> * 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 <bernd.edlin...@hotmail.de> * 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 <bernd.edlin...@hotmail.de> * 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 *,