> On Jul 23, 2025, at 10:12, Siddhesh Poyarekar <siddh...@gotplt.org> wrote:
> 
> On 2025-07-23 10:00, Qing Zhao wrote:
>>> I can't see how this could happen, do you have an example test case?
>> The example used in my previous writeup show this:
>> https://gcc.gnu.org/pipermail/gcc-patches/2025-July/689663.html
>> f->p = malloc (size);
>> ***** With the approach B: the IL for the above is:
>>  tmp1 = f->p;
>>  tmp2 = &f->n;
>>  tmp3 = .ACCESS_WITH_SIZE (tmp1, tmp2, ...);
>>  tmp4 = malloc (size);
>>  tmp3 = tmp4;
>> the above IL will be expanded to the following when .ACCESS_WITH_SIZE is 
>> expanded
>> to its first argument:
>>  tmp1 = f->p;
>>  tmp2 = &f->n;
>>  tmp3 = tmp1;
>>  tmp4 = malloc (size);
>>  tmp3 = tmp4;
>> the final optimized IL will be:
>>  tmp3 = f->p;
>>  tmp3 = malloc (size);
>> From the above final IL, you can clearly see the undefined behavior 
>> introduced by tmp1 = f->p to the program.
> 
> Is that from v8 of the patch, where .ACCESS_WITH_SIZE is emitted at 
> definition of f->p?
Yes, that’s for V8 in my local workspace (not sent to this alias) based on our 
previous 
discussion to resolve PR120929, and turned out to have this issue.

>  I think that needs to move back to the original idea, i.e. emit at object 
> reference, not definition.

We always generate a call to .ACCESS_WITH_SIZE for every f->p whatever it’s a 
reference 
or a definition in C FE parser. (This is the case for FAM)

For pointer with counted by,  Yes, if we can determine whether a f->p is an 
object reference or 
an object definition, and ONLY emitting .ACCESS_WITH_SIZE for the f->p when 
it’s a object reference, 
then the approach that passes the VALUE of f->p to .ACCESS_WITH_SIZE is safe. 

However, I have two questions for this: 

Question 1: Can we make sure this in C FE?  (Determine whether a f->p is an 
object reference of an object definition)

I am not very sure, but I think that we cannot do it in C FE.
The reason is, the analysis for -Wuninitialized is done in the middle end with
data flow information available. Even in middle end with data flow, the analysis
is not 100% accurate, then how can we make sure this in C FE?   

Question 2: Can we move the code generation to the call to .ACCESS_WITH_SIZE in 
middle-end?

No.
The reason is, bound sanitizer needs information from .ACCESS_WITH_SIZE, and the
bound sanitizer instrumentation is done in the tree lowering pass in C FE. As
a result, the call to .ACCESS_WITH_SIZE need to be generated in C FE before the
tree lowering pass.

If we cannot decide whether a f->p is written or read in C FE, we cannot use 
this approach.

Is this reasonable?

So, my major questions at this moment is:  (I am really not sure and cannot 
decide on this myself)

Can we decide whether a f->p is a reference or a definition in C FE?  (I have 
asked this question multiple times previously…)

A definition of f->p might look like the following:

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

Int *g;

void setup (int **ptr, int count)
{
  *ptr = __builtin_malloc (sizeof (int) * count);
   g = *ptr;
};

int main ()
{
  f = __builtin_malloc (sizeof (struct S));
  setup (&f->p, 10);
}

In the above code, how should we decide whether the f->p  is a definition or a 
reference? 
Then, should we emit .ACCESS_WITH_SIZE for it or not? 


>  It's wrong, not just for the above reason of potentially undefined 
> behaviour, but also because it defeats the point of the .ACCESS_WITH_SIZE 
> call.  The intent of the call is to bind the reference of f->p with f->n so 
> that the reference of f->p does not get reordered w.r.t definition of f->n.  
> For example if you have:
> 
>  f->p = malloc (sz);
>  f->n = sz;
>  __builtin_dynamic_object_size (f->p, 0);
> 
> putting the .ACCESS_WITH_SIZE directly above, i.e.:
> 
>  f->p = malloc (sz);
>  f->n = sz;
>  .ACCESS_WITH_SIZE (f->p, &f->n, ...);
>  __builtin_dynamic_object_size (f->p, 0);
> 
> ensures that f->n does not get reordered below the 
> __builtin_dynamic_object_size call.  If you emit .ACCESS_WITH_SIZE at the 
> definition point you'd have:
> 
>  f->p = malloc (sz);
>  .ACCESS_WITH_SIZE (f->p, &f->n, ...);
>  f->n = sz;
>  __builtin_dynamic_object_size (f->p, 0);
> 
> or worse (AFAICT from the IL you shared above):
> 
>  f->p = malloc (sz);
>  .ACCESS_WITH_SIZE (f->p, &f->n, ...);
>  f->n = sz;
>  __builtin_dynamic_object_size (f->p, 0);
> 

We  generate calls to .ACCESS_WITH_SIZE for both “f->p” in the above code 
without distinguish whether the f->p
is a reference or a definition, i.e:

 tmp1 = f->p;
 tmp2 = &f->n;
 tmp3 = .ACCESS_WITH_SIZE (tmp1, tmp2, …);     //first ACCESS_WITH_SIZE for the 
definition;
 tmp4 = malloc (size);
 tmp3 = tmp4;
  f->n = sz;

  tmp5 = f->p;
  tmp6 = &f->n;
  tmp7 = .ACCESS_WITH_SIZE (tmp5, tmp6, …);   //second ACCESS_WITH_SIZE for the 
reference;
  __builtin_dynamic_object_size (tmp7, 0);


The first ACCESS_WITH_SIZE for the definition introduced the undefined behavior 
to the program. So, I only list it in the writeup.
If we can determine that the f->p is written to or read by in C FE, then we can 
avoid generating the first ACCESS_WITH_SIZE
In the above.  Then the UB issue will be resolved.

However, as I mentioned in the above, we need to  determine whether a “f->p” is 
a definition or a reference in C FE.  
And this seems cannot be done in C FE.

Therefore, it’s not safe in general to pass the VALUE of f->p to 
.ACCESS_WITH_SIZE.

Hope this is clear. 

Qing

> because of which the compiler is then free to reorder the f->n definition 
> below the __builtin_dynamic_object_size call.
> 
> Thanks,
> Sid

Reply via email to