--- /dev/null
+/* Test the attribute counted_by and its usage in
+__builtin_dynamic_object_size: what's the correct behavior when the
+allocation size mismatched with the value of counted_by attribute?
+We should always use the latest value that is hold by the counted_by
+field. */
+/* { dg-do run } */
+/* { dg-options "-O -fstrict-flex-arrays=3" } */
+
+#include "builtin-object-size-common.h"
+
+struct annotated {
+ size_t foo;
+ char others;
+ char array[] __attribute__((counted_by (foo)));
+};
+
+#define noinline __attribute__((__noinline__))
+#define SIZE_BUMP 10
+#define MAX(a, b) ((a) > (b) ? (a) : (b))
+
+/* In general, Due to type casting, the type for the pointee of a pointer
+ does not say anything about the object it points to,
+ So, __builtin_object_size can not directly use the type of the pointee
+ to decide the size of the object the pointer points to.
+
+ There are only two reliable ways:
+ A. observed allocations (call to the allocation functions in the routine)
+ B. observed accesses (read or write access to the location of the
+ pointer points to)
+
+ That provide information about the type/existence of an object at
+ the corresponding address.
+
+ For A, we use the "alloc_size" attribute for the corresponding allocation
+ functions to determine the object size;
+ (We treat counted_by attribute the same as the "alloc_size" attribute)
+
+ For B, we use the SIZE info of the TYPE attached to the corresponding access.
+
+ The only other way in C which ensures that a pointer actually points
+ to an object of the correct type is 'static':
+
+ void foo(struct P *p[static 1]);
+
+ See https://gcc.gnu.org/pipermail/gcc-patches/2023-July/624814.html
+ for more details. */
+
+/* In the following function, malloc allocated more space than the value
+ of counted_by attribute. Then what's the correct behavior we expect
+ the __builtin_dynamic_object_size should have for each of the cases? */
+
+static struct annotated * noinline alloc_buf_more (size_t index)
+{
+ struct annotated *p;
+ size_t allocated_size
+ = MAX (sizeof (struct annotated),
+ (__builtin_offsetof (struct annotated, array[0])
+ + (index + SIZE_BUMP) * sizeof (char)));
+ p = (struct annotated *) malloc (allocated_size);
+
+ p->foo = index;
+
+ /* When checking the observed access p->array, we have info on both
+ observered allocation and observed access,
+ A.1 from observed allocation:
+ allocated_size - offsetof (struct annotated, array[0])
+
+ A.2 from the counted-by attribute:
+ p->foo * sizeof (char)
+
+ We always use the latest value that is hold by the counted-by field.
+ */
+
+ EXPECT(__builtin_dynamic_object_size(p->array, 0),
+ (p->foo) * sizeof(char));
+
+ EXPECT(__builtin_dynamic_object_size(p->array, 1),
+ (p->foo) * sizeof(char));
+
+ EXPECT(__builtin_dynamic_object_size(p->array, 2),
+ (p->foo) * sizeof(char));
+
+ EXPECT(__builtin_dynamic_object_size(p->array, 3),
+ (p->foo) * sizeof(char));
+
+ /* When checking the pointer p, we only have info on the observed allocation.
+ So, the object size info can only been obtained from the call to malloc.
+ For both MAXIMUM and MINIMUM: A = (index + SIZE_BUMP) * sizeof (char) */
+ EXPECT(__builtin_dynamic_object_size(p, 0), allocated_size);
+ EXPECT(__builtin_dynamic_object_size(p, 1), allocated_size);
+ EXPECT(__builtin_dynamic_object_size(p, 2), allocated_size);
+ EXPECT(__builtin_dynamic_object_size(p, 3), allocated_size);
+ return p;
+}
+
+/* In the following function, malloc allocated less space than the value
+ of counted_by attribute. Then what's the correct behavior we expect
+ the __builtin_dynamic_object_size should have for each of the cases?
+ NOTE: this is an user error, GCC should issue warnings for such case.
+ This is a seperate issue we should address later. */
+
+static struct annotated * noinline alloc_buf_less (size_t index)
+{
+ struct annotated *p;
+ size_t allocated_size
+ = MAX (sizeof (struct annotated),
+ (__builtin_offsetof (struct annotated, array[0])
+ + (index) * sizeof (char)));
+ p = (struct annotated *) malloc (allocated_size);
+
+ p->foo = index + SIZE_BUMP;
+
+ /* When checking the observed access p->array, we have info on both
+ observered allocation and observed access,
+ A.1 from observed allocation:
+ allocated_size - offsetof (struct annotated, array[0])
+ A.2 from the counted-by attribute:
+ p->foo * sizeof (char)
+
+ We always use the latest value that is hold by the counted-by field.
+ */
+
+ EXPECT(__builtin_dynamic_object_size(p->array, 0),
+ (p->foo) * sizeof(char));
+
+ EXPECT(__builtin_dynamic_object_size(p->array, 1),
+ (p->foo) * sizeof(char));
+
+ EXPECT(__builtin_dynamic_object_size(p->array, 2),
+ (p->foo) * sizeof(char));
+
+ EXPECT(__builtin_dynamic_object_size(p->array, 3),
+ (p->foo) * sizeof(char));
+
+ /* When checking the pointer p, we only have info on the observed
+ allocation. So, the object size info can only been obtained from
+ the call to malloc. */
+ EXPECT(__builtin_dynamic_object_size(p, 0), allocated_size);
+ EXPECT(__builtin_dynamic_object_size(p, 1), allocated_size);
+ EXPECT(__builtin_dynamic_object_size(p, 2), allocated_size);
+ EXPECT(__builtin_dynamic_object_size(p, 3), allocated_size);
+ return p;
+}
+
+int main ()
+{
+ struct annotated *p, *q;
+ p = alloc_buf_more (10);
+ q = alloc_buf_less (10);
+
+ /* When checking the access p->array, we only have info on the counted-by
+ value. */
+ EXPECT(__builtin_dynamic_object_size(p->array, 0), p->foo * sizeof(char));
+ EXPECT(__builtin_dynamic_object_size(p->array, 1), p->foo * sizeof(char));
+ EXPECT(__builtin_dynamic_object_size(p->array, 2), p->foo * sizeof(char));
+ EXPECT(__builtin_dynamic_object_size(p->array, 3), p->foo * sizeof(char));
+ /* When checking the pointer p, we have no observed allocation nor observed
+ access, therefore, we cannot determine the size info here. */
+ EXPECT(__builtin_dynamic_object_size(p, 0), -1);
+ EXPECT(__builtin_dynamic_object_size(p, 1), -1);
+ EXPECT(__builtin_dynamic_object_size(p, 2), 0);
+ EXPECT(__builtin_dynamic_object_size(p, 3), 0);
+
+ /* When checking the access p->array, we only have info on the counted-by
+ value. */
+ EXPECT(__builtin_dynamic_object_size(q->array, 0), q->foo * sizeof(char));
+ EXPECT(__builtin_dynamic_object_size(q->array, 1), q->foo * sizeof(char));
+ EXPECT(__builtin_dynamic_object_size(q->array, 2), q->foo * sizeof(char));
+ EXPECT(__builtin_dynamic_object_size(q->array, 3), q->foo * sizeof(char));
+ /* When checking the pointer p, we have no observed allocation nor observed
+ access, therefore, we cannot determine the size info here. */
+ EXPECT(__builtin_dynamic_object_size(q, 0), -1);
+ EXPECT(__builtin_dynamic_object_size(q, 1), -1);
+ EXPECT(__builtin_dynamic_object_size(q, 2), 0);
+ EXPECT(__builtin_dynamic_object_size(q, 3), 0);
+
+ DONE ();
+}
#include "attribs.h"
#include "builtins.h"
#include "gimplify-me.h"
+#include "gimplify.h"
struct object_size_info
{
static bool addr_object_size (struct object_size_info *,
const_tree, int, tree *, tree *t = NULL);
static tree alloc_object_size (const gcall *, int);
+static tree access_with_size_object_size (const gcall *, int);
static tree pass_through_call (const gcall *);
static void collect_object_sizes_for (struct object_size_info *, tree);
static void expr_object_size (struct object_size_info *, tree, tree);
return false;
}
+/* Compute __builtin_object_size for a CALL to .ACCESS_WITH_SIZE,
+ OBJECT_SIZE_TYPE is the second argument from __builtin_object_size.
+ The 2nd, 3rd, and the 4th parameters of the call determine the size of
+ the CALL:
+
+ 2nd argument REF_TO_SIZE: The reference to the size of the object,
+ 3rd argument CLASS_OF_SIZE: The size referenced by the REF_TO_SIZE represents
+ 0: the number of bytes;
+ 1: the number of the elements of the object type;
+ 4th argument TYPE_OF_SIZE: A constant 0 with its TYPE being the same as the TYPE
+ of the object referenced by REF_TO_SIZE
+
+ The size of the element can be retrived from the result type of the call,
+ which is the pointer to the array type. */
+static tree
+access_with_size_object_size (const gcall *call, int object_size_type)
+{
+ /* If not for dynamic object size, return. */
+ if ((object_size_type & OST_DYNAMIC) == 0)
+ return size_unknown (object_size_type);
+
+ gcc_assert (gimple_call_internal_p (call, IFN_ACCESS_WITH_SIZE));
+ /* Result type is a pointer type to the original flexible array type. */
+ tree result_type = gimple_call_return_type (call);
+ gcc_assert (POINTER_TYPE_P (result_type));
+ tree element_size = TYPE_SIZE_UNIT (TREE_TYPE (TREE_TYPE (result_type)));
+ tree ref_to_size = gimple_call_arg (call, 1);
+ unsigned int class_of_size = TREE_INT_CST_LOW (gimple_call_arg (call, 2));
+ tree type = TREE_TYPE (gimple_call_arg (call, 3));
+
+ tree size = fold_build2 (MEM_REF, type, ref_to_size,
+ build_int_cst (ptr_type_node, 0));
+
+ /* If size is negative value, treat it as zero. */
+ if (!TYPE_UNSIGNED (type))
+ {
+ tree cond_expr = fold_build2 (LT_EXPR, boolean_type_node,
+ unshare_expr (size), build_zero_cst (type));
+ size = fold_build3 (COND_EXPR, integer_type_node, cond_expr,
+ build_zero_cst (type), size);
+ }
+
+ if (class_of_size == 1)
+ size = size_binop (MULT_EXPR,
+ fold_convert (sizetype, size),
+ fold_convert (sizetype, element_size));
+ else
+ size = fold_convert (sizetype, size);
+
+ if (!todo)
+ todo = TODO_update_ssa_only_virtuals;
+
+ return size;
+}
/* Compute __builtin_object_size for CALL, which is a GIMPLE_CALL.
Handles calls to functions declared with attribute alloc_size.
bool is_strdup = gimple_call_builtin_p (call, BUILT_IN_STRDUP);
bool is_strndup = gimple_call_builtin_p (call, BUILT_IN_STRNDUP);
+ bool is_access_with_size
+ = gimple_call_internal_p (call, IFN_ACCESS_WITH_SIZE);
if (is_strdup || is_strndup)
bytes = strdup_object_size (call, object_size_type, is_strndup);
+ else if (is_access_with_size)
+ bytes = access_with_size_object_size (call, object_size_type);
else
bytes = alloc_object_size (call, object_size_type);