diff mbox series

handle function pointers in __builtin_object_size (PR 88372)

Message ID fad8025b-b1f5-38c6-1c53-a00294ba0bf2@gmail.com
State New
Headers show
Series handle function pointers in __builtin_object_size (PR 88372) | expand

Commit Message

Martin Sebor Dec. 6, 2018, 8:21 p.m. UTC
Bug 88372 - alloc_size attribute is ignored on function pointers
points out that even though the alloc_size attribute is accepted
on function pointers it doesn't have any effect on Object Size
Checking.  The reporter, who is implementing the feature in Clang,
wants to know if by exposing it under the same name they won't be
causing incompatibilities with GCC.

I don't think it's intentional that GCC doesn't take advantage of
the attribute for Object Size Checking, and certainly not to detect
the same kinds of issues as with other allocation functions (such
as excessive or negative size arguments).  Rather, it's almost
certainly an oversight since GCC does make use of function pointer
attributes in other contexts (e.g., attributes alloc_align and
noreturn).

As an oversight, I think it's fair to consider it a bug rather
than a request for an enhancement.  Since not handling
the attribute in Object Size Checking has adverse security
implications, I also think this bug should be addressed in GCC
9.  With that, I submit the attached patch to resolve both
aspects of the problem.

Tested on x86_64-redhat-linux.

Martin

Comments

Jakub Jelinek Dec. 6, 2018, 9:26 p.m. UTC | #1
On Thu, Dec 06, 2018 at 01:21:58PM -0700, Martin Sebor wrote:
> Bug 88372 - alloc_size attribute is ignored on function pointers
> points out that even though the alloc_size attribute is accepted
> on function pointers it doesn't have any effect on Object Size
> Checking.  The reporter, who is implementing the feature in Clang,
> wants to know if by exposing it under the same name they won't be
> causing incompatibilities with GCC.
> 
> I don't think it's intentional that GCC doesn't take advantage of
> the attribute for Object Size Checking, and certainly not to detect
> the same kinds of issues as with other allocation functions (such
> as excessive or negative size arguments).  Rather, it's almost
> certainly an oversight since GCC does make use of function pointer
> attributes in other contexts (e.g., attributes alloc_align and
> noreturn).
> 
> As an oversight, I think it's fair to consider it a bug rather
> than a request for an enhancement.  Since not handling
> the attribute in Object Size Checking has adverse security
> implications, I also think this bug should be addressed in GCC
> 9.  With that, I submit the attached patch to resolve both
> aspects of the problem.

This is because alloc_object_size has been written before we had attributes
like alloc_size.  The only thing I'm unsure about is whether we should
prefer gimple_call_fntype or TREE_TYPE (gimple_call_fndecl ()) if it is a
direct call or if we should try to look for alloc_size attribute on both
of those if they are different types.  E.g. if somebody does

#include <stdlib.h>

typedef void *(*allocfn) (size_t);

static inline void *
foo (allocfn fn, size_t sz)
{
  return fn (sz);
}

static inline void *
bar (size_t sz)
{
  return foo (malloc, sz);
}

then I think this patch would no longer treat it as malloc.

As this is security relevant, I'd probably look for alloc_size
attribute in both gimple_call_fntype and, if gimple_call_fndecl is non-NULL,
its TREE_TYPE.

Otherwise, the patch looks reasonable to me.

	Jakub
Martin Sebor Dec. 6, 2018, 11:01 p.m. UTC | #2
On 12/6/18 2:26 PM, Jakub Jelinek wrote:
> On Thu, Dec 06, 2018 at 01:21:58PM -0700, Martin Sebor wrote:
>> Bug 88372 - alloc_size attribute is ignored on function pointers
>> points out that even though the alloc_size attribute is accepted
>> on function pointers it doesn't have any effect on Object Size
>> Checking.  The reporter, who is implementing the feature in Clang,
>> wants to know if by exposing it under the same name they won't be
>> causing incompatibilities with GCC.
>>
>> I don't think it's intentional that GCC doesn't take advantage of
>> the attribute for Object Size Checking, and certainly not to detect
>> the same kinds of issues as with other allocation functions (such
>> as excessive or negative size arguments).  Rather, it's almost
>> certainly an oversight since GCC does make use of function pointer
>> attributes in other contexts (e.g., attributes alloc_align and
>> noreturn).
>>
>> As an oversight, I think it's fair to consider it a bug rather
>> than a request for an enhancement.  Since not handling
>> the attribute in Object Size Checking has adverse security
>> implications, I also think this bug should be addressed in GCC
>> 9.  With that, I submit the attached patch to resolve both
>> aspects of the problem.
> 
> This is because alloc_object_size has been written before we had attributes
> like alloc_size.  The only thing I'm unsure about is whether we should
> prefer gimple_call_fntype or TREE_TYPE (gimple_call_fndecl ()) if it is a
> direct call or if we should try to look for alloc_size attribute on both
> of those if they are different types.  E.g. if somebody does
> 
> #include <stdlib.h>
> 
> typedef void *(*allocfn) (size_t);
> 
> static inline void *
> foo (allocfn fn, size_t sz)
> {
>    return fn (sz);
> }
> 
> static inline void *
> bar (size_t sz)
> {
>    return foo (malloc, sz);
> }
> 
> then I think this patch would no longer treat it as malloc.
> 
> As this is security relevant, I'd probably look for alloc_size
> attribute in both gimple_call_fntype and, if gimple_call_fndecl is non-NULL,
> its TREE_TYPE.

Thanks for the test case!  I wondered if using fntype would
always work but couldn't think of when it wouldn't.  I've
adjusted the function to use both and added the test case.

While thinking about this it occurred to me that alloc_size
is only documented as a function attribute but not one that
applies to pointers or types.  I added documentation for
these uses to the Common Type and Common Variable sections.

Martin

PS Other function attributes that also apply to types and
variables are only documented in the function section.  They
should also be mentioned in the other sections.  Which, if
done in the established style, will result in duplicating
a lot of text in three places.  I think that suggests that
we might want to think about structuring these sections of
the manual differently to avoid the duplication.
PR tree-optimization/88372 - alloc_size attribute is ignored on function pointers

gcc/ChangeLog:

	PR tree-optimization/88372
	* calls.c (maybe_warn_alloc_args_overflow): Handle function pointers.
	* tree-object-size.c (alloc_object_size): Same.  Simplify.
	* doc/extend.texi (Object Size Checking): Update.
	(Other Builtins): Add __builtin_object_size.
	(Common Type Attributes): Add alloc_size.
	(Common Variable Attributes): Ditto.

gcc/testsuite/ChangeLog:

	PR tree-optimization/88372
	* gcc.dg/Walloc-size-larger-than-18.c: New test.
	* gcc.dg/builtin-object-size-19.c: Same.

Index: gcc/calls.c
===================================================================
--- gcc/calls.c	(revision 266862)
+++ gcc/calls.c	(working copy)
@@ -1342,9 +1342,10 @@ get_size_range (tree exp, tree range[2], bool allo
 /* Diagnose a call EXP to function FN decorated with attribute alloc_size
    whose argument numbers given by IDX with values given by ARGS exceed
    the maximum object size or cause an unsigned oveflow (wrapping) when
-   multiplied.  When ARGS[0] is null the function does nothing.  ARGS[1]
-   may be null for functions like malloc, and non-null for those like
-   calloc that are decorated with a two-argument attribute alloc_size.  */
+   multiplied.  FN is null when EXP is a call via a function pointer.
+   When ARGS[0] is null the function does nothing.  ARGS[1] may be null
+   for functions like malloc, and non-null for those like calloc that
+   are decorated with a two-argument attribute alloc_size.  */
 
 void
 maybe_warn_alloc_args_overflow (tree fn, tree exp, tree args[2], int idx[2])
@@ -1357,6 +1358,8 @@ maybe_warn_alloc_args_overflow (tree fn, tree exp,
 
   location_t loc = EXPR_LOCATION (exp);
 
+  tree fntype = fn ? TREE_TYPE (fn) : TREE_TYPE (TREE_TYPE (exp));
+  built_in_function fncode = fn ? DECL_FUNCTION_CODE (fn) : BUILT_IN_NONE;
   bool warned = false;
 
   /* Validate each argument individually.  */
@@ -1382,11 +1385,11 @@ maybe_warn_alloc_args_overflow (tree fn, tree exp,
 		 friends.
 		 Also avoid issuing the warning for calls to function named
 		 "alloca".  */
-	      if ((DECL_FUNCTION_CODE (fn) == BUILT_IN_ALLOCA
+	      if ((fncode == BUILT_IN_ALLOCA
 		   && IDENTIFIER_LENGTH (DECL_NAME (fn)) != 6)
-		  || (DECL_FUNCTION_CODE (fn) != BUILT_IN_ALLOCA
+		  || (fncode != BUILT_IN_ALLOCA
 		      && !lookup_attribute ("returns_nonnull",
-					    TYPE_ATTRIBUTES (TREE_TYPE (fn)))))
+					    TYPE_ATTRIBUTES (fntype))))
 		warned = warning_at (loc, OPT_Walloc_zero,
 				     "%Kargument %i value is zero",
 				     exp, idx[i] + 1);
@@ -1398,6 +1401,7 @@ maybe_warn_alloc_args_overflow (tree fn, tree exp,
 		 size overflow.  There's no good way to detect C++98 here
 		 so avoid diagnosing these calls for all C++ modes.  */
 	      if (i == 0
+		  && fn
 		  && !args[1]
 		  && lang_GNU_CXX ()
 		  && DECL_IS_OPERATOR_NEW (fn)
@@ -1481,7 +1485,7 @@ maybe_warn_alloc_args_overflow (tree fn, tree exp,
 	}
     }
 
-  if (warned)
+  if (warned && fn)
     {
       location_t fnloc = DECL_SOURCE_LOCATION (fn);
 
@@ -1933,14 +1937,13 @@ initialize_argument_information (int num_actuals A
 
   bitmap_obstack_release (NULL);
 
-  /* Extract attribute alloc_size and if set, store the indices of
-     the corresponding arguments in ALLOC_IDX, and then the actual
-     argument(s) at those indices in ALLOC_ARGS.  */
+  /* Extract attribute alloc_size from the type of the called expression
+     (which could be a function or a function pointer) and if set, store
+     the indices of the corresponding arguments in ALLOC_IDX, and then
+     the actual argument(s) at those indices in ALLOC_ARGS.  */
   int alloc_idx[2] = { -1, -1 };
-  if (tree alloc_size
-      = (fndecl ? lookup_attribute ("alloc_size",
-				    TYPE_ATTRIBUTES (TREE_TYPE (fndecl)))
-	 : NULL_TREE))
+  if (tree alloc_size = lookup_attribute ("alloc_size",
+					  TYPE_ATTRIBUTES (fntype)))
     {
       tree args = TREE_VALUE (alloc_size);
       alloc_idx[0] = TREE_INT_CST_LOW (TREE_VALUE (args)) - 1;
Index: gcc/tree-object-size.c
===================================================================
--- gcc/tree-object-size.c	(revision 266862)
+++ gcc/tree-object-size.c	(working copy)
@@ -401,25 +401,28 @@ addr_object_size (struct object_size_info *osi, co
 
 
 /* Compute __builtin_object_size for CALL, which is a GIMPLE_CALL.
-   Handles various allocation calls.  OBJECT_SIZE_TYPE is the second
-   argument from __builtin_object_size.  If unknown, return
-   unknown[object_size_type].  */
+   Handles calls to functions declared with attribute alloc_size.
+   OBJECT_SIZE_TYPE is the second argument from __builtin_object_size.
+   If unknown, return unknown[object_size_type].  */
 
 static unsigned HOST_WIDE_INT
 alloc_object_size (const gcall *call, int object_size_type)
 {
-  tree callee, bytes = NULL_TREE;
-  tree alloc_size;
-  int arg1 = -1, arg2 = -1;
-
   gcc_assert (is_gimple_call (call));
 
-  callee = gimple_call_fndecl (call);
-  if (!callee)
+  tree calltype;
+  if (tree callfn = gimple_call_fndecl (call))
+    calltype = TREE_TYPE (callfn);
+  else
+    calltype = gimple_call_fntype (call);
+
+  if (!calltype)
     return unknown[object_size_type];
 
-  alloc_size = lookup_attribute ("alloc_size",
-				 TYPE_ATTRIBUTES (TREE_TYPE (callee)));
+  /* Set to positions of alloc_size arguments.  */
+  int arg1 = -1, arg2 = -1;
+  tree alloc_size = lookup_attribute ("alloc_size",
+				      TYPE_ATTRIBUTES (calltype));
   if (alloc_size && TREE_VALUE (alloc_size))
     {
       tree p = TREE_VALUE (alloc_size);
@@ -429,19 +432,6 @@ alloc_object_size (const gcall *call, int object_s
         arg2 = TREE_INT_CST_LOW (TREE_VALUE (TREE_CHAIN (p)))-1;
     }
 
-  if (DECL_BUILT_IN_CLASS (callee) == BUILT_IN_NORMAL)
-    switch (DECL_FUNCTION_CODE (callee))
-      {
-      case BUILT_IN_CALLOC:
-	arg2 = 1;
-	/* fall through */
-      case BUILT_IN_MALLOC:
-      CASE_BUILT_IN_ALLOCA:
-	arg1 = 0;
-      default:
-	break;
-      }
-
   if (arg1 < 0 || arg1 >= (int)gimple_call_num_args (call)
       || TREE_CODE (gimple_call_arg (call, arg1)) != INTEGER_CST
       || (arg2 >= 0
@@ -449,6 +439,7 @@ alloc_object_size (const gcall *call, int object_s
 	      || TREE_CODE (gimple_call_arg (call, arg2)) != INTEGER_CST)))
     return unknown[object_size_type];
 
+  tree bytes = NULL_TREE;
   if (arg2 >= 0)
     bytes = size_binop (MULT_EXPR,
 	fold_convert (sizetype, gimple_call_arg (call, arg1)),
Index: gcc/doc/extend.texi
===================================================================
--- gcc/doc/extend.texi	(revision 266862)
+++ gcc/doc/extend.texi	(working copy)
@@ -2506,7 +2506,7 @@ The function parameter(s) denoting the allocated s
 one or two integer arguments supplied to the attribute.  The allocated size
 is either the value of the single function argument specified or the product
 of the two function arguments specified.  Argument numbering starts at
-one.
+one for ordinary functions, and at two for C++ non-static member functions.
 
 For instance,
 
@@ -6282,6 +6282,34 @@ This warning can be disabled by @option{-Wno-if-no
 The @code{warn_if_not_aligned} attribute can also be used for types
 (@pxref{Common Type Attributes}.)
 
+@item alloc_size (@var{position})
+@itemx alloc_size (@var{position-1}, @var{position-2})
+@cindex @code{alloc_size} variable attribute
+The @code{alloc_size} variable attribute may be applied to the declaration
+of a pointer to a function that returns a pointer and takes at least one
+argument of an integer type.  It indicates that the returned pointer points
+to an object whose size is given by the function argument at @var{position-1},
+or by the product of the arguments at @var{position-1} and @var{position-2}.
+Meaningful sizes are positive values less than @code{PTRDIFF_MAX}.  Other
+sizes are disagnosed when detected.  GCC uses this information to improve
+the results of @code{__builtin_object_size}.
+
+For instance, the following declarations
+
+@smallexample
+typedef __attribute__ ((alloc_size (1, 2))) void*
+  (*calloc_ptr) (size_t, size_t);
+typedef __attribute__ ((alloc_size (1))) void*
+  (*malloc_ptr) (size_t);
+@end smallexample
+
+@noindent
+specify that @code{calloc_ptr} is a pointer of a function that, like
+the standard C function @code{calloc}, returns an object whose size
+is given by the product of arguments 1 and 2, and similarly, that
+@code{malloc_ptr}, like the standard C function @code{malloc},
+returns an object whose size is given by argument 1 to the function.
+
 @item cleanup (@var{cleanup_function})
 @cindex @code{cleanup} variable attribute
 The @code{cleanup} attribute runs a function when the variable goes
@@ -7263,6 +7291,34 @@ struct __attribute__ ((aligned (8))) foo
 
 This warning can be disabled by @option{-Wno-if-not-aligned}.
 
+@item alloc_size (@var{position})
+@itemx alloc_size (@var{position-1}, @var{position-2})
+@cindex @code{alloc_size} type attribute
+The @code{alloc_size} type attribute may be applied to the definition
+of a type of a function that returns a pointer and takes at least one
+argument of an integer type.  It indicates that the returned pointer
+points to an object whose size is given by the function argument at
+@var{position-1}, or by the product of the arguments at @var{position-1}
+and @var{position-2}.  Meaningful sizes are positive values less than
+@code{PTRDIFF_MAX}.  Other sizes are disagnosed when detected.  GCC uses
+this information to improve the results of @code{__builtin_object_size}.
+
+For instance, the following declarations
+
+@smallexample
+typedef __attribute__ ((alloc_size (1, 2))) void*
+  calloc_type (size_t, size_t);
+typedef __attribute__ ((alloc_size (1))) void*
+  malloc_type (size_t);
+@end smallexample
+
+@noindent
+specify that @code{calloc_type} is a type of a function that, like
+the standard C function @code{calloc}, returns an object whose size
+is given by the product of arguments 1 and 2, and that
+@code{malloc_type}, like the standard C function @code{malloc},
+returns an object whose size is given by argument 1 to the function.
+
 @item copy
 @itemx copy (@var{expression})
 @cindex @code{copy} type attribute
@@ -11124,7 +11180,10 @@ a limited extent, they can be used without optimiz
 @deftypefn {Built-in Function} {size_t} __builtin_object_size (const void * @var{ptr}, int @var{type})
 is a built-in construct that returns a constant number of bytes from
 @var{ptr} to the end of the object @var{ptr} pointer points to
-(if known at compile time).  @code{__builtin_object_size} never evaluates
+(if known at compile time).  To determine the sizes of dynamically allocated
+objects the function relies on the allocation functions called to obtain
+the storage to be declared with the @code{alloc_size} attribute (@xref{Common
+Function Attributes}).  @code{__builtin_object_size} never evaluates
 its arguments for side effects.  If there are any side effects in them, it
 returns @code{(size_t) -1} for @var{type} 0 or 1 and @code{(size_t) 0}
 for @var{type} 2 or 3.  If there are multiple objects @var{ptr} can
@@ -11249,6 +11308,7 @@ is called and the @var{flag} argument passed to it
 @findex __builtin_islessequal
 @findex __builtin_islessgreater
 @findex __builtin_isunordered
+@findex __builtin_object_size
 @findex __builtin_powi
 @findex __builtin_powif
 @findex __builtin_powil
@@ -12492,6 +12552,10 @@ is evaluated if it includes side effects but no ot
 and GCC does not issue a warning.
 @end deftypefn
 
+@deftypefn {Built-in Function}{size_t} __builtin_object_size (const void * @var{ptr}, int @var{type})
+Returns the size of an object pointed to by @var{ptr}.  @xref{Object Size Checking} for a detailed description of the function.
+@end deftypefn
+
 @deftypefn {Built-in Function} double __builtin_huge_val (void)
 Returns a positive infinity, if supported by the floating-point format,
 else @code{DBL_MAX}.  This function is suitable for implementing the
Index: gcc/testsuite/gcc.dg/Walloc-size-larger-than-18.c
===================================================================
--- gcc/testsuite/gcc.dg/Walloc-size-larger-than-18.c	(nonexistent)
+++ gcc/testsuite/gcc.dg/Walloc-size-larger-than-18.c	(working copy)
@@ -0,0 +1,93 @@
+/* PR tree-optimization/88372 - alloc_size attribute is ignored
+   on function pointers
+   Verify that calls via function pointers declared alloc_size
+   with zero or excessive size trigger either -Walloc-zero or
+   -Walloc-size-larger-than warnings.
+   { dg-do compile }
+   { dg-options "-O2 -Wall -Walloc-zero -ftrack-macro-expansion=0" } */
+
+#define ATTR(...) __attribute__ ((__VA_ARGS__))
+
+typedef __SIZE_TYPE__ size_t;
+
+
+void sink (void*);
+
+#define T(call) sink (call)
+
+ATTR (alloc_size (1)) void* (*ai1)(int, int);
+ATTR (alloc_size (2)) void* (*ai2)(int, int);
+ATTR (alloc_size (1, 2)) void* (*ai1_2)(int, int);
+
+ATTR (alloc_size (1)) void* (*asz1)(size_t, size_t);
+ATTR (alloc_size (2)) void* (*asz2)(size_t, size_t);
+ATTR (alloc_size (1, 2)) void* (*asz1_2)(size_t, size_t);
+
+
+void test_alloc_ptr_zero (void)
+{
+  T (asz1 (0, 0));      /* { dg-warning "argument 1 value is zero" } */
+  T (asz1 (0, 1));      /* { dg-warning "argument 1 value is zero" } */
+  T (asz1 (1, 0));
+  T (asz1 (1, 1));
+
+  T (asz2 (0, 0));      /* { dg-warning "argument 2 value is zero" } */
+  T (asz2 (0, 1));
+  T (asz2 (1, 0));      /* { dg-warning "argument 2 value is zero" } */
+  T (asz2 (1, 1));
+
+  T (asz1_2 (0, 0));    /* { dg-warning "argument \[12\] value is zero" } */
+  T (asz1_2 (1, 0));    /* { dg-warning "argument 2 value is zero" } */
+  T (asz1_2 (0, 1));    /* { dg-warning "argument 1 value is zero" } */
+  T (asz1_2 (1, 1));
+}
+
+
+void test_alloc_ptr_negative (int n)
+{
+  T (ai1 (-1, -1));     /* { dg-warning "argument 1 value .-1. is negative" } */
+  T (ai1 (-2,  1));     /* { dg-warning "argument 1 value .-2. is negative" } */
+  T (ai1 ( 1, -1));
+  T (ai1 ( 1,  1));
+
+  T (ai2 (-1, -3));     /* { dg-warning "argument 2 value .-3. is negative" } */
+  T (ai2 (-1,  1));
+  T (ai2 ( 1, -4));     /* { dg-warning "argument 2 value .-4. is negative" } */
+  T (ai2 ( 1,  1));
+
+  T (ai1_2 (-5, -6));   /* { dg-warning "argument \[12\] value .-\[56\]. is negative" } */
+  T (ai1_2 ( 1, -7));   /* { dg-warning "argument 2 value .-7. is negative" } */
+  T (ai1_2 (-8,  1));   /* { dg-warning "argument 1 value .-8. is negative" } */
+  T (ai1_2 ( 1,  1));
+
+  if (n > -1)
+    n = -1;
+
+  /* Also verify a simple range.  */
+  T (ai1_2 ( 1,  n));   /* { dg-warning "argument 2 range \\\[-\[0-9\]+, -1] is negative" } */
+  T (ai1_2 ( n,  1));   /* { dg-warning "argument 1 range \\\[-\[0-9\]+, -1] is negative" } */
+}
+
+void test_alloc_ptr_too_big (void)
+{
+  size_t x = (__SIZE_MAX__ >> 1) + 1;
+  size_t y = __SIZE_MAX__ / 5;
+
+  T (asz1 (x, x));     /* { dg-warning "argument 1 value .\[0-9\]+. exceeds" } */
+  T (asz1 (x, 1));     /* { dg-warning "argument 1 value .\[0-9\]+. exceeds" } */
+  T (asz1 (1, x));
+  T (asz1 (1, 1));
+
+  T (asz2 (x, x));     /* { dg-warning "argument 2 value .\[0-9\]+. exceeds" } */
+  T (asz2 (x, 1));
+  T (asz2 (1, x));     /* { dg-warning "argument 2 value .\[0-9\]+. exceeds" } */
+  T (asz2 (1, 1));
+
+  T (asz1_2 (x, x));   /* { dg-warning "argument \[12\] value .\[0-9\]+. exceeds" } */
+  T (asz1_2 (y, 3));   /* { dg-warning "product .\[0-9\]+ \\\* 3. of arguments 1 and 2 exceeds" } */
+  T (asz1_2 (y, y));   /* { dg-warning "product .\[0-9\]+ \\\* \[0-9\]+. of arguments 1 and 2 exceeds" } */
+  T (asz1_2 (1, x));   /* { dg-warning "argument 2 value .\[0-9\]+. exceeds" } */
+  T (asz1_2 (x, 1));   /* { dg-warning "argument 1 value .\[0-9\]+. exceeds" } */
+  T (asz1_2 (1, 1));
+
+}
Index: gcc/testsuite/gcc.dg/builtin-object-size-19.c
===================================================================
--- gcc/testsuite/gcc.dg/builtin-object-size-19.c	(nonexistent)
+++ gcc/testsuite/gcc.dg/builtin-object-size-19.c	(working copy)
@@ -0,0 +1,101 @@
+/* PR tree-optimization/88372 - alloc_size attribute is ignored
+   on function pointers { dg-do compile }
+   { dg-options "-O2 -fdump-tree-optimized" } */
+
+typedef __SIZE_TYPE__ size_t;
+
+#define ATTR(...) __attribute__ ((__VA_ARGS__))
+#define CONCAT(x, y) x ## y
+#define CAT(x, y) CONCAT (x, y)
+#define FAILNAME(name) CAT (call_ ## name ##_on_line_, __LINE__)
+
+#define FAIL(name) do {				\
+    extern void FAILNAME (name) (void);		\
+    FAILNAME (name)();				\
+  } while (0)
+
+/* Macro to emit a call to function named
+   call_in_true_branch_not_eliminated_on_line_NNN()
+   for each call that's expected to be eliminated.  The dg-final
+   scan-tree-dump-time directive at the bottom of the test verifies
+   that no such call appears in output.  */
+#define ELIM(expr)							\
+  if (!(expr)) FAIL (in_true_branch_not_eliminated); else (void)0
+
+void sink (void*);
+
+#define T(alloc, n) do {			\
+    void *p = alloc;				\
+    sink (p);					\
+    ELIM (n == __builtin_object_size (p, 0));	\
+    ELIM (n == __builtin_object_size (p, 1));	\
+    ELIM (n == __builtin_object_size (p, 2));	\
+    ELIM (n == __builtin_object_size (p, 3));	\
+  } while (0)
+
+
+ATTR (alloc_size (1)) void* (*alloc_1_x)(size_t, size_t);
+ATTR (alloc_size (2)) void* (*alloc_x_2)(size_t, size_t);
+
+/* Verify that things work when attribute alloc_size is applied
+   to a typedef that is then used to declared a pointer.  */
+typedef ATTR (alloc_size (1, 2)) void* (alloc_1_2_t)(size_t, size_t);
+
+void test_alloc_ptr (alloc_1_2_t *alloc_1_2)
+{
+  T (alloc_1_x (0, 0), 0);
+  T (alloc_1_x (1, 0), 1);
+  T (alloc_1_x (3, 0), 3);
+  T (alloc_1_x (9, 5), 9);
+
+  T (alloc_x_2 (0, 0), 0);
+  T (alloc_x_2 (1, 0), 0);
+  T (alloc_x_2 (0, 1), 1);
+  T (alloc_x_2 (9, 5), 5);
+
+  T (alloc_1_2 (0, 0), 0);
+  T (alloc_1_2 (1, 0), 0);
+  T (alloc_1_2 (0, 1), 0);
+  T (alloc_1_2 (9, 5), 45);
+}
+
+/* Verify that object size is detected even in indirect calls via
+   function pointers to built-in allocation functions, even without
+   explicit use of attribute alloc_size on the pointers.  */
+
+typedef void *(allocfn_1) (size_t);
+typedef void *(allocfn_1_2) (size_t, size_t);
+
+static inline void *
+call_alloc (allocfn_1 *fn1, allocfn_1_2 *fn2, size_t n1, size_t n2)
+{
+  return fn1 ? fn1 (n1) : fn2 (n1, n2);
+}
+
+static inline void *
+call_malloc (size_t n)
+{
+  return call_alloc (__builtin_malloc, 0, n, 0);
+}
+
+static inline void *
+call_calloc (size_t n1, size_t n2)
+{
+  return call_alloc (0, __builtin_calloc, n1, n2);
+}
+
+void test_builtin_ptr (void)
+{
+  T (call_malloc (0), 0);
+  T (call_malloc (1), 1);
+  T (call_malloc (9), 9);
+
+  T (call_calloc (0, 0), 0);
+  T (call_calloc (0, 1), 0);
+  T (call_calloc (1, 0), 0);
+  T (call_calloc (1, 1), 1);
+  T (call_calloc (1, 3), 3);
+  T (call_calloc (2, 3), 6);
+}
+
+/* { dg-final { scan-tree-dump-not "not_eliminated" "optimized" } } */
Richard Biener Dec. 7, 2018, 8:06 a.m. UTC | #3
On Thu, 6 Dec 2018, Martin Sebor wrote:

> On 12/6/18 2:26 PM, Jakub Jelinek wrote:
> > On Thu, Dec 06, 2018 at 01:21:58PM -0700, Martin Sebor wrote:
> > > Bug 88372 - alloc_size attribute is ignored on function pointers
> > > points out that even though the alloc_size attribute is accepted
> > > on function pointers it doesn't have any effect on Object Size
> > > Checking.  The reporter, who is implementing the feature in Clang,
> > > wants to know if by exposing it under the same name they won't be
> > > causing incompatibilities with GCC.
> > > 
> > > I don't think it's intentional that GCC doesn't take advantage of
> > > the attribute for Object Size Checking, and certainly not to detect
> > > the same kinds of issues as with other allocation functions (such
> > > as excessive or negative size arguments).  Rather, it's almost
> > > certainly an oversight since GCC does make use of function pointer
> > > attributes in other contexts (e.g., attributes alloc_align and
> > > noreturn).
> > > 
> > > As an oversight, I think it's fair to consider it a bug rather
> > > than a request for an enhancement.  Since not handling
> > > the attribute in Object Size Checking has adverse security
> > > implications, I also think this bug should be addressed in GCC
> > > 9.  With that, I submit the attached patch to resolve both
> > > aspects of the problem.
> > 
> > This is because alloc_object_size has been written before we had attributes
> > like alloc_size.  The only thing I'm unsure about is whether we should
> > prefer gimple_call_fntype or TREE_TYPE (gimple_call_fndecl ()) if it is a
> > direct call or if we should try to look for alloc_size attribute on both
> > of those if they are different types.  E.g. if somebody does
> > 
> > #include <stdlib.h>
> > 
> > typedef void *(*allocfn) (size_t);
> > 
> > static inline void *
> > foo (allocfn fn, size_t sz)
> > {
> >    return fn (sz);
> > }
> > 
> > static inline void *
> > bar (size_t sz)
> > {
> >    return foo (malloc, sz);
> > }
> > 
> > then I think this patch would no longer treat it as malloc.
> > 
> > As this is security relevant, I'd probably look for alloc_size
> > attribute in both gimple_call_fntype and, if gimple_call_fndecl is non-NULL,
> > its TREE_TYPE.
> 
> Thanks for the test case!  I wondered if using fntype would
> always work but couldn't think of when it wouldn't.  I've
> adjusted the function to use both and added the test case.
> 
> While thinking about this it occurred to me that alloc_size
> is only documented as a function attribute but not one that
> applies to pointers or types.  I added documentation for
> these uses to the Common Type and Common Variable sections.

Please always _only_ use gimple_call_fntype when the decl
isn't visible.  As elsewhere the type of the function
pointer doesn't have any semantic meaning (it could be a
wrong one).

Richard.

> Martin
> 
> PS Other function attributes that also apply to types and
> variables are only documented in the function section.  They
> should also be mentioned in the other sections.  Which, if
> done in the established style, will result in duplicating
> a lot of text in three places.  I think that suggests that
> we might want to think about structuring these sections of
> the manual differently to avoid the duplication.
>
Martin Sebor Dec. 8, 2018, 5:42 p.m. UTC | #4
On 12/7/18 1:06 AM, Richard Biener wrote:
> On Thu, 6 Dec 2018, Martin Sebor wrote:
> 
>> On 12/6/18 2:26 PM, Jakub Jelinek wrote:
>>> On Thu, Dec 06, 2018 at 01:21:58PM -0700, Martin Sebor wrote:
>>>> Bug 88372 - alloc_size attribute is ignored on function pointers
>>>> points out that even though the alloc_size attribute is accepted
>>>> on function pointers it doesn't have any effect on Object Size
>>>> Checking.  The reporter, who is implementing the feature in Clang,
>>>> wants to know if by exposing it under the same name they won't be
>>>> causing incompatibilities with GCC.
>>>>
>>>> I don't think it's intentional that GCC doesn't take advantage of
>>>> the attribute for Object Size Checking, and certainly not to detect
>>>> the same kinds of issues as with other allocation functions (such
>>>> as excessive or negative size arguments).  Rather, it's almost
>>>> certainly an oversight since GCC does make use of function pointer
>>>> attributes in other contexts (e.g., attributes alloc_align and
>>>> noreturn).
>>>>
>>>> As an oversight, I think it's fair to consider it a bug rather
>>>> than a request for an enhancement.  Since not handling
>>>> the attribute in Object Size Checking has adverse security
>>>> implications, I also think this bug should be addressed in GCC
>>>> 9.  With that, I submit the attached patch to resolve both
>>>> aspects of the problem.
>>>
>>> This is because alloc_object_size has been written before we had attributes
>>> like alloc_size.  The only thing I'm unsure about is whether we should
>>> prefer gimple_call_fntype or TREE_TYPE (gimple_call_fndecl ()) if it is a
>>> direct call or if we should try to look for alloc_size attribute on both
>>> of those if they are different types.  E.g. if somebody does
>>>
>>> #include <stdlib.h>
>>>
>>> typedef void *(*allocfn) (size_t);
>>>
>>> static inline void *
>>> foo (allocfn fn, size_t sz)
>>> {
>>>     return fn (sz);
>>> }
>>>
>>> static inline void *
>>> bar (size_t sz)
>>> {
>>>     return foo (malloc, sz);
>>> }
>>>
>>> then I think this patch would no longer treat it as malloc.
>>>
>>> As this is security relevant, I'd probably look for alloc_size
>>> attribute in both gimple_call_fntype and, if gimple_call_fndecl is non-NULL,
>>> its TREE_TYPE.
>>
>> Thanks for the test case!  I wondered if using fntype would
>> always work but couldn't think of when it wouldn't.  I've
>> adjusted the function to use both and added the test case.
>>
>> While thinking about this it occurred to me that alloc_size
>> is only documented as a function attribute but not one that
>> applies to pointers or types.  I added documentation for
>> these uses to the Common Type and Common Variable sections.
> 
> Please always _only_ use gimple_call_fntype when the decl
> isn't visible.  As elsewhere the type of the function
> pointer doesn't have any semantic meaning (it could be a
> wrong one).

I don't understand what you're asking me to do differently here:

-  callee = gimple_call_fndecl (call);
-  if (!callee)
+  tree calltype;
+  if (tree callfn = gimple_call_fndecl (call))
+    calltype = TREE_TYPE (callfn);
+  else
+    calltype = gimple_call_fntype (call);
                 ^^^^^^^^^^^^^^^^^^

Can you show the change you're looking for?  (The change I had
made originally was in response to this same request you made
in Bugzilla, which Jakub then suggested may not be robust enough.)

Btw., it should be straightforward to ask "give me the attribute
if this is a call to an alloc_size-kind of a function?" (or one
with whatever other attribute of interest).  Since it appears
to be anything but straightforward, we should consider providing
an API to make it so.  Maybe something like:

   tree gimple_call_attribute (gcall *, tree attribute);

Martin

> 
> Richard.
> 
>> Martin
>>
>> PS Other function attributes that also apply to types and
>> variables are only documented in the function section.  They
>> should also be mentioned in the other sections.  Which, if
>> done in the established style, will result in duplicating
>> a lot of text in three places.  I think that suggests that
>> we might want to think about structuring these sections of
>> the manual differently to avoid the duplication.
>>
>
Richard Biener Dec. 9, 2018, 9:39 a.m. UTC | #5
On December 8, 2018 6:42:48 PM GMT+01:00, Martin Sebor <msebor@gmail.com> wrote:
>On 12/7/18 1:06 AM, Richard Biener wrote:
>> On Thu, 6 Dec 2018, Martin Sebor wrote:
>> 
>>> On 12/6/18 2:26 PM, Jakub Jelinek wrote:
>>>> On Thu, Dec 06, 2018 at 01:21:58PM -0700, Martin Sebor wrote:
>>>>> Bug 88372 - alloc_size attribute is ignored on function pointers
>>>>> points out that even though the alloc_size attribute is accepted
>>>>> on function pointers it doesn't have any effect on Object Size
>>>>> Checking.  The reporter, who is implementing the feature in Clang,
>>>>> wants to know if by exposing it under the same name they won't be
>>>>> causing incompatibilities with GCC.
>>>>>
>>>>> I don't think it's intentional that GCC doesn't take advantage of
>>>>> the attribute for Object Size Checking, and certainly not to
>detect
>>>>> the same kinds of issues as with other allocation functions (such
>>>>> as excessive or negative size arguments).  Rather, it's almost
>>>>> certainly an oversight since GCC does make use of function pointer
>>>>> attributes in other contexts (e.g., attributes alloc_align and
>>>>> noreturn).
>>>>>
>>>>> As an oversight, I think it's fair to consider it a bug rather
>>>>> than a request for an enhancement.  Since not handling
>>>>> the attribute in Object Size Checking has adverse security
>>>>> implications, I also think this bug should be addressed in GCC
>>>>> 9.  With that, I submit the attached patch to resolve both
>>>>> aspects of the problem.
>>>>
>>>> This is because alloc_object_size has been written before we had
>attributes
>>>> like alloc_size.  The only thing I'm unsure about is whether we
>should
>>>> prefer gimple_call_fntype or TREE_TYPE (gimple_call_fndecl ()) if
>it is a
>>>> direct call or if we should try to look for alloc_size attribute on
>both
>>>> of those if they are different types.  E.g. if somebody does
>>>>
>>>> #include <stdlib.h>
>>>>
>>>> typedef void *(*allocfn) (size_t);
>>>>
>>>> static inline void *
>>>> foo (allocfn fn, size_t sz)
>>>> {
>>>>     return fn (sz);
>>>> }
>>>>
>>>> static inline void *
>>>> bar (size_t sz)
>>>> {
>>>>     return foo (malloc, sz);
>>>> }
>>>>
>>>> then I think this patch would no longer treat it as malloc.
>>>>
>>>> As this is security relevant, I'd probably look for alloc_size
>>>> attribute in both gimple_call_fntype and, if gimple_call_fndecl is
>non-NULL,
>>>> its TREE_TYPE.
>>>
>>> Thanks for the test case!  I wondered if using fntype would
>>> always work but couldn't think of when it wouldn't.  I've
>>> adjusted the function to use both and added the test case.
>>>
>>> While thinking about this it occurred to me that alloc_size
>>> is only documented as a function attribute but not one that
>>> applies to pointers or types.  I added documentation for
>>> these uses to the Common Type and Common Variable sections.
>> 
>> Please always _only_ use gimple_call_fntype when the decl
>> isn't visible.  As elsewhere the type of the function
>> pointer doesn't have any semantic meaning (it could be a
>> wrong one).
>
>I don't understand what you're asking me to do differently here:
>
>-  callee = gimple_call_fndecl (call);
>-  if (!callee)
>+  tree calltype;
>+  if (tree callfn = gimple_call_fndecl (call))
>+    calltype = TREE_TYPE (callfn);
>+  else
>+    calltype = gimple_call_fntype (call);
>                 ^^^^^^^^^^^^^^^^^^
>
>Can you show the change you're looking for?  (The change I had
>made originally was in response to this same request you made
>in Bugzilla, which Jakub then suggested may not be robust enough.)

I was replying to the discussion, not looking at the patch. The above snippet looks OK to me. 

>Btw., it should be straightforward to ask "give me the attribute
>if this is a call to an alloc_size-kind of a function?" (or one
>with whatever other attribute of interest).  Since it appears
>to be anything but straightforward, we should consider providing
>an API to make it so.  Maybe something like:
>
>   tree gimple_call_attribute (gcall *, tree attribute);

Maybe a char * instead of the tree argument to match lookup_attribute? But yes, sth like this looks indeed useful. 

Richard. 

>Martin
>
>> 
>> Richard.
>> 
>>> Martin
>>>
>>> PS Other function attributes that also apply to types and
>>> variables are only documented in the function section.  They
>>> should also be mentioned in the other sections.  Which, if
>>> done in the established style, will result in duplicating
>>> a lot of text in three places.  I think that suggests that
>>> we might want to think about structuring these sections of
>>> the manual differently to avoid the duplication.
>>>
>>
Jeff Law Dec. 14, 2018, 12:24 a.m. UTC | #6
On 12/6/18 4:01 PM, Martin Sebor wrote:
> On 12/6/18 2:26 PM, Jakub Jelinek wrote:
>> On Thu, Dec 06, 2018 at 01:21:58PM -0700, Martin Sebor wrote:
>>> Bug 88372 - alloc_size attribute is ignored on function pointers
>>> points out that even though the alloc_size attribute is accepted
>>> on function pointers it doesn't have any effect on Object Size
>>> Checking.  The reporter, who is implementing the feature in Clang,
>>> wants to know if by exposing it under the same name they won't be
>>> causing incompatibilities with GCC.
>>>
>>> I don't think it's intentional that GCC doesn't take advantage of
>>> the attribute for Object Size Checking, and certainly not to detect
>>> the same kinds of issues as with other allocation functions (such
>>> as excessive or negative size arguments).  Rather, it's almost
>>> certainly an oversight since GCC does make use of function pointer
>>> attributes in other contexts (e.g., attributes alloc_align and
>>> noreturn).
>>>
>>> As an oversight, I think it's fair to consider it a bug rather
>>> than a request for an enhancement.  Since not handling
>>> the attribute in Object Size Checking has adverse security
>>> implications, I also think this bug should be addressed in GCC
>>> 9.  With that, I submit the attached patch to resolve both
>>> aspects of the problem.
>>
>> This is because alloc_object_size has been written before we had attributes
>>
>> like alloc_size.  The only thing I'm unsure about is whether we should
>> prefer gimple_call_fntype or TREE_TYPE (gimple_call_fndecl ()) if it is a
>> direct call or if we should try to look for alloc_size attribute on both
>> of those if they are different types.  E.g. if somebody does
>>
>> #include <stdlib.h>
>>
>> typedef void *(*allocfn) (size_t);
>>
>> static inline void *
>> foo (allocfn fn, size_t sz)
>> {
>>    return fn (sz);
>> }
>>
>> static inline void *
>> bar (size_t sz)
>> {
>>    return foo (malloc, sz);
>> }
>>
>> then I think this patch would no longer treat it as malloc.
>>
>> As this is security relevant, I'd probably look for alloc_size
>> attribute in both gimple_call_fntype and, if gimple_call_fndecl is non-NULL,
>>
>> its TREE_TYPE.
> 
> Thanks for the test case!  I wondered if using fntype would
> always work but couldn't think of when it wouldn't.  I've
> adjusted the function to use both and added the test case.
> 
> While thinking about this it occurred to me that alloc_size
> is only documented as a function attribute but not one that
> applies to pointers or types.  I added documentation for
> these uses to the Common Type and Common Variable sections.
> 
> Martin
> 
> PS Other function attributes that also apply to types and
> variables are only documented in the function section.  They
> should also be mentioned in the other sections.  Which, if
> done in the established style, will result in duplicating
> a lot of text in three places.  I think that suggests that
> we might want to think about structuring these sections of
> the manual differently to avoid the duplication.
> 
> gcc-88372.diff
> 
> PR tree-optimization/88372 - alloc_size attribute is ignored on function pointers
> 
> gcc/ChangeLog:
> 
> 	PR tree-optimization/88372
> 	* calls.c (maybe_warn_alloc_args_overflow): Handle function pointers.
> 	* tree-object-size.c (alloc_object_size): Same.  Simplify.
> 	* doc/extend.texi (Object Size Checking): Update.
> 	(Other Builtins): Add __builtin_object_size.
> 	(Common Type Attributes): Add alloc_size.
> 	(Common Variable Attributes): Ditto.
> 
> gcc/testsuite/ChangeLog:
> 
> 	PR tree-optimization/88372
> 	* gcc.dg/Walloc-size-larger-than-18.c: New test.
> 	* gcc.dg/builtin-object-size-19.c: Same.
OK
jeff
diff mbox series

Patch

PR tree-optimization/88372 - alloc_size attribute is ignored on function pointers

gcc/ChangeLog:

	PR tree-optimization/88372
	* calls.c (maybe_warn_alloc_args_overflow): Handle function pointers.
	* tree-object-size.c (alloc_object_size): Same.  Simplify.
	* doc/extend.texi (Object Size Checking): Update.
	(Other Builtins): Add __builtin_object_size.

gcc/testsuite/ChangeLog:

	PR tree-optimization/88372
	* gcc.dg/Walloc-size-larger-than-18.c: New test.
	* gcc.dg/builtin-object-size-19.c: Same.

Index: gcc/calls.c
===================================================================
--- gcc/calls.c	(revision 266862)
+++ gcc/calls.c	(working copy)
@@ -1342,9 +1342,10 @@  get_size_range (tree exp, tree range[2], bool allo
 /* Diagnose a call EXP to function FN decorated with attribute alloc_size
    whose argument numbers given by IDX with values given by ARGS exceed
    the maximum object size or cause an unsigned oveflow (wrapping) when
-   multiplied.  When ARGS[0] is null the function does nothing.  ARGS[1]
-   may be null for functions like malloc, and non-null for those like
-   calloc that are decorated with a two-argument attribute alloc_size.  */
+   multiplied.  FN is null when EXP is a call via a function pointer.
+   When ARGS[0] is null the function does nothing.  ARGS[1] may be null
+   for functions like malloc, and non-null for those like calloc that
+   are decorated with a two-argument attribute alloc_size.  */
 
 void
 maybe_warn_alloc_args_overflow (tree fn, tree exp, tree args[2], int idx[2])
@@ -1357,6 +1358,8 @@  maybe_warn_alloc_args_overflow (tree fn, tree exp,
 
   location_t loc = EXPR_LOCATION (exp);
 
+  tree fntype = fn ? TREE_TYPE (fn) : TREE_TYPE (TREE_TYPE (exp));
+  built_in_function fncode = fn ? DECL_FUNCTION_CODE (fn) : BUILT_IN_NONE;
   bool warned = false;
 
   /* Validate each argument individually.  */
@@ -1382,11 +1385,11 @@  maybe_warn_alloc_args_overflow (tree fn, tree exp,
 		 friends.
 		 Also avoid issuing the warning for calls to function named
 		 "alloca".  */
-	      if ((DECL_FUNCTION_CODE (fn) == BUILT_IN_ALLOCA
+	      if ((fncode == BUILT_IN_ALLOCA
 		   && IDENTIFIER_LENGTH (DECL_NAME (fn)) != 6)
-		  || (DECL_FUNCTION_CODE (fn) != BUILT_IN_ALLOCA
+		  || (fncode != BUILT_IN_ALLOCA
 		      && !lookup_attribute ("returns_nonnull",
-					    TYPE_ATTRIBUTES (TREE_TYPE (fn)))))
+					    TYPE_ATTRIBUTES (fntype))))
 		warned = warning_at (loc, OPT_Walloc_zero,
 				     "%Kargument %i value is zero",
 				     exp, idx[i] + 1);
@@ -1398,6 +1401,7 @@  maybe_warn_alloc_args_overflow (tree fn, tree exp,
 		 size overflow.  There's no good way to detect C++98 here
 		 so avoid diagnosing these calls for all C++ modes.  */
 	      if (i == 0
+		  && fn
 		  && !args[1]
 		  && lang_GNU_CXX ()
 		  && DECL_IS_OPERATOR_NEW (fn)
@@ -1481,7 +1485,7 @@  maybe_warn_alloc_args_overflow (tree fn, tree exp,
 	}
     }
 
-  if (warned)
+  if (warned && fn)
     {
       location_t fnloc = DECL_SOURCE_LOCATION (fn);
 
@@ -1933,14 +1937,13 @@  initialize_argument_information (int num_actuals A
 
   bitmap_obstack_release (NULL);
 
-  /* Extract attribute alloc_size and if set, store the indices of
-     the corresponding arguments in ALLOC_IDX, and then the actual
-     argument(s) at those indices in ALLOC_ARGS.  */
+  /* Extract attribute alloc_size from the type of the called expression
+     (which could be a function or a function pointer) and if set, store
+     the indices of the corresponding arguments in ALLOC_IDX, and then
+     the actual argument(s) at those indices in ALLOC_ARGS.  */
   int alloc_idx[2] = { -1, -1 };
-  if (tree alloc_size
-      = (fndecl ? lookup_attribute ("alloc_size",
-				    TYPE_ATTRIBUTES (TREE_TYPE (fndecl)))
-	 : NULL_TREE))
+  if (tree alloc_size = lookup_attribute ("alloc_size",
+					  TYPE_ATTRIBUTES (fntype)))
     {
       tree args = TREE_VALUE (alloc_size);
       alloc_idx[0] = TREE_INT_CST_LOW (TREE_VALUE (args)) - 1;
Index: gcc/tree-object-size.c
===================================================================
--- gcc/tree-object-size.c	(revision 266862)
+++ gcc/tree-object-size.c	(working copy)
@@ -401,25 +401,23 @@  addr_object_size (struct object_size_info *osi, co
 
 
 /* Compute __builtin_object_size for CALL, which is a GIMPLE_CALL.
-   Handles various allocation calls.  OBJECT_SIZE_TYPE is the second
-   argument from __builtin_object_size.  If unknown, return
-   unknown[object_size_type].  */
+   Handles calls to functions declared with attribute alloc_size.
+   OBJECT_SIZE_TYPE is the second argument from __builtin_object_size.
+   If unknown, return unknown[object_size_type].  */
 
 static unsigned HOST_WIDE_INT
 alloc_object_size (const gcall *call, int object_size_type)
 {
-  tree callee, bytes = NULL_TREE;
-  tree alloc_size;
-  int arg1 = -1, arg2 = -1;
-
   gcc_assert (is_gimple_call (call));
 
-  callee = gimple_call_fndecl (call);
-  if (!callee)
+  tree calltype = gimple_call_fntype (call);
+  if (!calltype)
     return unknown[object_size_type];
 
-  alloc_size = lookup_attribute ("alloc_size",
-				 TYPE_ATTRIBUTES (TREE_TYPE (callee)));
+  /* Set to positions of alloc_size arguments.  */
+  int arg1 = -1, arg2 = -1;
+  tree alloc_size = lookup_attribute ("alloc_size",
+				      TYPE_ATTRIBUTES (calltype));
   if (alloc_size && TREE_VALUE (alloc_size))
     {
       tree p = TREE_VALUE (alloc_size);
@@ -429,19 +427,6 @@  alloc_object_size (const gcall *call, int object_s
         arg2 = TREE_INT_CST_LOW (TREE_VALUE (TREE_CHAIN (p)))-1;
     }
 
-  if (DECL_BUILT_IN_CLASS (callee) == BUILT_IN_NORMAL)
-    switch (DECL_FUNCTION_CODE (callee))
-      {
-      case BUILT_IN_CALLOC:
-	arg2 = 1;
-	/* fall through */
-      case BUILT_IN_MALLOC:
-      CASE_BUILT_IN_ALLOCA:
-	arg1 = 0;
-      default:
-	break;
-      }
-
   if (arg1 < 0 || arg1 >= (int)gimple_call_num_args (call)
       || TREE_CODE (gimple_call_arg (call, arg1)) != INTEGER_CST
       || (arg2 >= 0
@@ -449,6 +434,7 @@  alloc_object_size (const gcall *call, int object_s
 	      || TREE_CODE (gimple_call_arg (call, arg2)) != INTEGER_CST)))
     return unknown[object_size_type];
 
+  tree bytes = NULL_TREE;
   if (arg2 >= 0)
     bytes = size_binop (MULT_EXPR,
 	fold_convert (sizetype, gimple_call_arg (call, arg1)),
Index: gcc/doc/extend.texi
===================================================================
--- gcc/doc/extend.texi	(revision 266862)
+++ gcc/doc/extend.texi	(working copy)
@@ -11124,7 +11124,10 @@  a limited extent, they can be used without optimiz
 @deftypefn {Built-in Function} {size_t} __builtin_object_size (const void * @var{ptr}, int @var{type})
 is a built-in construct that returns a constant number of bytes from
 @var{ptr} to the end of the object @var{ptr} pointer points to
-(if known at compile time).  @code{__builtin_object_size} never evaluates
+(if known at compile time).  To determine the sizes of dynamically allocated
+objects the function relies on the allocation functions called to obtain
+the storage to be declared with the @code{alloc_size} attribute (@xref{Common
+Function Attributes}).  @code{__builtin_object_size} never evaluates
 its arguments for side effects.  If there are any side effects in them, it
 returns @code{(size_t) -1} for @var{type} 0 or 1 and @code{(size_t) 0}
 for @var{type} 2 or 3.  If there are multiple objects @var{ptr} can
@@ -11249,6 +11252,7 @@  is called and the @var{flag} argument passed to it
 @findex __builtin_islessequal
 @findex __builtin_islessgreater
 @findex __builtin_isunordered
+@findex __builtin_object_size
 @findex __builtin_powi
 @findex __builtin_powif
 @findex __builtin_powil
@@ -12492,6 +12496,10 @@  is evaluated if it includes side effects but no ot
 and GCC does not issue a warning.
 @end deftypefn
 
+@deftypefn {Built-in Function}{size_t} __builtin_object_size (const void * @var{ptr}, int @var{type})
+Returns the size of an object pointed to by @var{ptr}.  @xref{Object Size Checking} for a detailed description of the function.
+@end deftypefn
+
 @deftypefn {Built-in Function} double __builtin_huge_val (void)
 Returns a positive infinity, if supported by the floating-point format,
 else @code{DBL_MAX}.  This function is suitable for implementing the
Index: gcc/testsuite/gcc.dg/Walloc-size-larger-than-18.c
===================================================================
--- gcc/testsuite/gcc.dg/Walloc-size-larger-than-18.c	(nonexistent)
+++ gcc/testsuite/gcc.dg/Walloc-size-larger-than-18.c	(working copy)
@@ -0,0 +1,93 @@ 
+/* PR tree-optimization/88372 - alloc_size attribute is ignored
+   on function pointers
+   Verify that calls via function pointers declared alloc_size
+   with zero or excessive size trigger either -Walloc-zero or
+   -Walloc-size-larger-than warnings.
+   { dg-do compile }
+   { dg-options "-O2 -Wall -Walloc-zero -ftrack-macro-expansion=0" } */
+
+#define ATTR(...) __attribute__ ((__VA_ARGS__))
+
+typedef __SIZE_TYPE__ size_t;
+
+
+void sink (void*);
+
+#define T(call) sink (call)
+
+ATTR (alloc_size (1)) void* (*ai1)(int, int);
+ATTR (alloc_size (2)) void* (*ai2)(int, int);
+ATTR (alloc_size (1, 2)) void* (*ai1_2)(int, int);
+
+ATTR (alloc_size (1)) void* (*asz1)(size_t, size_t);
+ATTR (alloc_size (2)) void* (*asz2)(size_t, size_t);
+ATTR (alloc_size (1, 2)) void* (*asz1_2)(size_t, size_t);
+
+
+void test_alloc_ptr_zero (void)
+{
+  T (asz1 (0, 0));      /* { dg-warning "argument 1 value is zero" } */
+  T (asz1 (0, 1));      /* { dg-warning "argument 1 value is zero" } */
+  T (asz1 (1, 0));
+  T (asz1 (1, 1));
+
+  T (asz2 (0, 0));      /* { dg-warning "argument 2 value is zero" } */
+  T (asz2 (0, 1));
+  T (asz2 (1, 0));      /* { dg-warning "argument 2 value is zero" } */
+  T (asz2 (1, 1));
+
+  T (asz1_2 (0, 0));    /* { dg-warning "argument \[12\] value is zero" } */
+  T (asz1_2 (1, 0));    /* { dg-warning "argument 2 value is zero" } */
+  T (asz1_2 (0, 1));    /* { dg-warning "argument 1 value is zero" } */
+  T (asz1_2 (1, 1));
+}
+
+
+void test_alloc_ptr_negative (int n)
+{
+  T (ai1 (-1, -1));     /* { dg-warning "argument 1 value .-1. is negative" } */
+  T (ai1 (-2,  1));     /* { dg-warning "argument 1 value .-2. is negative" } */
+  T (ai1 ( 1, -1));
+  T (ai1 ( 1,  1));
+
+  T (ai2 (-1, -3));     /* { dg-warning "argument 2 value .-3. is negative" } */
+  T (ai2 (-1,  1));
+  T (ai2 ( 1, -4));     /* { dg-warning "argument 2 value .-4. is negative" } */
+  T (ai2 ( 1,  1));
+
+  T (ai1_2 (-5, -6));   /* { dg-warning "argument \[12\] value .-\[56\]. is negative" } */
+  T (ai1_2 ( 1, -7));   /* { dg-warning "argument 2 value .-7. is negative" } */
+  T (ai1_2 (-8,  1));   /* { dg-warning "argument 1 value .-8. is negative" } */
+  T (ai1_2 ( 1,  1));
+
+  if (n > -1)
+    n = -1;
+
+  /* Also verify a simple range.  */
+  T (ai1_2 ( 1,  n));   /* { dg-warning "argument 2 range \\\[-\[0-9\]+, -1] is negative" } */
+  T (ai1_2 ( n,  1));   /* { dg-warning "argument 1 range \\\[-\[0-9\]+, -1] is negative" } */
+}
+
+void test_alloc_ptr_too_big (void)
+{
+  size_t x = (__SIZE_MAX__ >> 1) + 1;
+  size_t y = __SIZE_MAX__ / 5;
+
+  T (asz1 (x, x));     /* { dg-warning "argument 1 value .\[0-9\]+. exceeds" } */
+  T (asz1 (x, 1));     /* { dg-warning "argument 1 value .\[0-9\]+. exceeds" } */
+  T (asz1 (1, x));
+  T (asz1 (1, 1));
+
+  T (asz2 (x, x));     /* { dg-warning "argument 2 value .\[0-9\]+. exceeds" } */
+  T (asz2 (x, 1));
+  T (asz2 (1, x));     /* { dg-warning "argument 2 value .\[0-9\]+. exceeds" } */
+  T (asz2 (1, 1));
+
+  T (asz1_2 (x, x));   /* { dg-warning "argument \[12\] value .\[0-9\]+. exceeds" } */
+  T (asz1_2 (y, 3));   /* { dg-warning "product .\[0-9\]+ \\\* 3. of arguments 1 and 2 exceeds" } */
+  T (asz1_2 (y, y));   /* { dg-warning "product .\[0-9\]+ \\\* \[0-9\]+. of arguments 1 and 2 exceeds" } */
+  T (asz1_2 (1, x));   /* { dg-warning "argument 2 value .\[0-9\]+. exceeds" } */
+  T (asz1_2 (x, 1));   /* { dg-warning "argument 1 value .\[0-9\]+. exceeds" } */
+  T (asz1_2 (1, 1));
+
+}
Index: gcc/testsuite/gcc.dg/builtin-object-size-19.c
===================================================================
--- gcc/testsuite/gcc.dg/builtin-object-size-19.c	(nonexistent)
+++ gcc/testsuite/gcc.dg/builtin-object-size-19.c	(working copy)
@@ -0,0 +1,58 @@ 
+/* PR tree-optimization/88372 - alloc_size attribute is ignored
+   on function pointers { dg-do compile }
+   { dg-options "-O2 -fdump-tree-optimized" } */
+
+#define ATTR(...) __attribute__ ((__VA_ARGS__))
+#define CONCAT(x, y) x ## y
+#define CAT(x, y) CONCAT (x, y)
+#define FAILNAME(name) CAT (call_ ## name ##_on_line_, __LINE__)
+
+#define FAIL(name) do {				\
+    extern void FAILNAME (name) (void);		\
+    FAILNAME (name)();				\
+  } while (0)
+
+/* Macro to emit a call to function named
+   call_in_true_branch_not_eliminated_on_line_NNN()
+   for each call that's expected to be eliminated.  The dg-final
+   scan-tree-dump-time directive at the bottom of the test verifies
+   that no such call appears in output.  */
+#define ELIM(expr)							\
+  if (!(expr)) FAIL (in_true_branch_not_eliminated); else (void)0
+
+void sink (void*);
+
+#define T1(alloc, n) do {			\
+    void *p = alloc;				\
+    sink (p);					\
+    ELIM (n == __builtin_object_size (p, 0));	\
+    ELIM (n == __builtin_object_size (p, 1));	\
+    ELIM (n == __builtin_object_size (p, 2));	\
+    ELIM (n == __builtin_object_size (p, 3));	\
+  } while (0)
+
+
+ATTR (alloc_size (1)) void* (*alloc_1)(int, int);
+ATTR (alloc_size (2)) void* (*alloc_2)(int, int);
+ATTR (alloc_size (1, 2)) void* (*alloc_1_2)(int, int);
+
+
+void test_alloc_ptr (void)
+{
+  T1 (alloc_1 (0, 0), 0);
+  T1 (alloc_1 (1, 0), 1);
+  T1 (alloc_1 (3, 0), 3);
+  T1 (alloc_1 (9, 5), 9);
+
+  T1 (alloc_2 (0, 0), 0);
+  T1 (alloc_2 (1, 0), 0);
+  T1 (alloc_2 (0, 1), 1);
+  T1 (alloc_2 (9, 5), 5);
+
+  T1 (alloc_1_2 (0, 0), 0);
+  T1 (alloc_1_2 (1, 0), 0);
+  T1 (alloc_1_2 (0, 1), 0);
+  T1 (alloc_1_2 (9, 5), 45);
+}
+
+/* { dg-final { scan-tree-dump-not "not_eliminated" "optimized" } } */