[Apologies if I've missed some context in my reading since I'm coming back to this after a big break]

On 2025-07-22 12:33, Qing Zhao wrote:
Why it's wrong to pass the VALUE of the original pointer as the first argument 
to
the call to .ACCESS_WITH_SIZE

For a pointer field with counted_by attribute:

struct S {
   int n;
   int *p __attribute__((counted_by(n)));
} *f;

f->p

if we pass the VALUE of the original pointer f->p as the first argument,
    and also return the original pointer:
  .ACCESS_WITH_SIZE (f->p, &f->n,...)

the IL for the above is:
   tmp1 = f->p;
   tmp2 = &f->n;
   tmp3 = .ACCESS_WITH_SIZE (tmp1, tmp2, ...);

In the above, in order to generate a call to .ACCESS_WITH_SIZE for the pointer
reference f->p,  we have to add the new GIMPLE tmp1 = f->p to pass the value of
the pointer f->p to the call to .ACCESS_WITH_SIZE.
This new gimple reads the VALUE of the pointer f->p. It will introduce undefined
behavior if it is inserted in the program BEFORE the pointer f->p is 
initialized.

I can't see how this could happen, do you have an example test case?

Based on the above, my conclusion are:

1. It's not safe in general to pass the VALUE of the pointer f->p to the call to
    .ACCESS_WITH_SIZE.

Given that .ACCESS_WITH_SIZE is always generated to replace a reference for f->p, I don't see how it could generate any *new* potentially uninitialized access. In fact, I would argue that the indirection in the .ACCESS_WITH_SIZE implementation was incorrect and was masked by the fact that &f->p and f->p mean the same thing for arrays and this got exposed when the same concept was extended to pointers.

Looking at a simple example:

```
typedef __SIZE_TYPE__ size_t;

struct A
{
  int n;
  char c[] __attribute__ ((counted_by (n)));
};

extern void * unknown_alloc (size_t);
extern void do_something1 (const char *);
extern void do_something2 ();

size_t
foo (size_t sz)
{
  struct A *a = unknown_alloc (sz);
  a->n = sz;

  do_something1 (a->c);
  do_something2 ();

  return __builtin_dynamic_object_size (a->c, 0);
}
```

with -fdump-tree-original (which is what the frontend would have produced), we get:

```
;; Function foo (null)
;; enabled by -tree-original


{
  struct A * a = (struct A *) unknown_alloc (sz);

    struct A * a = (struct A *) unknown_alloc (sz);
  a->n = (int) sz;
do_something1 ((const char *) .ACCESS_WITH_SIZE ((char *) &a->c, &a->n, 1, 0, -1, 0B));
  do_something2 ();
return (size_t) __builtin_dynamic_object_size ((const void *) .ACCESS_WITH_SIZE ((char *) &a->c, &a->n, 1, 0, -1, 0B), 0);
}
```

We see that:

(1) the .ACCESS_WITH_SIZE is produced for each reference of a->c. this should not produce any new undefined behaviour; any new read of f->p will be exactly at the point of the read that the programmer intended and even if reordered, it won't be reordered above its initialization point.

(2) A pointer to an array is coerced to a char */void * and the .ACCESS_WITH_SIZE simply passes it through. This only happens to work for arrays because a->c and &a->c mean the same thing, but it breaks for pointers because they're different things now.

In the end, .ACCESS_WITH_SIZE is a nop, so it really shouldn't matter what is passed as long as it is effective at associating the FAM (or pointer) with its counted_by member, expressing their mutual data dependency.

Thanks,
Sid

Reply via email to