> 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