Skip to content

Conversation

patrykstefanski
Copy link

@patrykstefanski patrykstefanski commented Sep 24, 2025

This change adds support for checking count-attributed assignment
groups. This commit adds the following checks:

  1. Standalone assignments to count-attributed objects (pointers/dependent
    counts) that are not directly inside of a compound statement. Our
    model rejects those and requires the user to simplify their code if
    necessary. For example:
      void foo(int *__counted_by(count) p, int count) {
        q = p = ...;
              ^ this is rejected
        n = count = ...;
                  ^ this is rejected
        // the following is fine:
        p = ...;
        count = ...;
      }
  2. Assignments to count-attributed objects that are implicitly
    immutable. For example, assigning to a dependent count that is used
    in an inout pointer is not allowed, since the update won't be visible
    on the call-site:
      void foo(int *__counted_by(count) *out_p, int count) {
        *out_p = ...;
        count = ...; // immutable
      }
  3. Missing and duplicated assignments:
      void foo(int *__counted_by(a + b) p, int a, int b) {
        p = ...;
        p = ...; // duplicated
        a = ...;
        // b missing
      }
  4. Count-attributed objects that are assigned and used in the same
    group. Allowing such assignments can cause the bounds-check to use
    the old dependent count, while updating the count to a new value. For
    example, the bounds-check in sp.first() uses the value of b
    before the later update, which can lead to OOB if b was less than
    42:
      void foo(int *__counted_by(a + b) p, int a, int b, std::span<int> sp) {
        p = sp.first(b + 42).data();
        b = 42; // b is assigned and used
        a = b;
      }
  5. Safe assignment patterns. This uses the infrastructure that is
    already available for count-attributed arguments, and checks for each
    assigned pointer in the group that the RHS has enough elements.

This analysis is hidden behind -fexperimental-bounds-safety-attributes
flag, so that we don't waste cycles traversing the AST when the
attributes are not enabled.

rdar://128160398
rdar://128161580

@patrykstefanski patrykstefanski self-assigned this Sep 24, 2025
@patrykstefanski patrykstefanski added the clang:bounds-safety Issue relating to the experimental -fbounds-safety feature in Clang label Sep 24, 2025
This change adds support for checking count-attributed assignment
groups. This commit adds the following checks:

1. Standalone assignments to count-attributed objects (pointers/dependent
   counts) that are not directly inside of a compound statement. Our
   model rejects those and requires the user to simplify their code if
   necessary. For example:
   ```
     void foo(int *__counted_by(count) p, int count) {
       q = p = ...;
             ^ this is rejected
       n = count = ...;
                 ^ this is rejected
       // the following is fine:
       p = ...;
       count = ...;
     }
   ```
2. Assignments to count-attributed objects that are implicitly
   immutable. For example, assigning to a dependent count that is used
   in an inout pointer is not allowed, since the update won't be visible
   on the call-site:
   ```
     void foo(int *__counted_by(count) *out_p, int count) {
       *out_p = ...;
       count = ...; // immutable
     }
   ```
3. Missing and duplicated assignments:
   ```
     void foo(int *__counted_by(a + b) p, int a, int b) {
       p = ...;
       p = ...; // duplicated
       a = ...;
       // b missing
     }
   ```
4. Count-attributed objects that are assigned and used in the same
   group. Allowing such assignments can cause the bounds-check to use
   the old dependent count, while updating the count to a new value. For
   example, the bounds-check in `sp.first()` uses the value of `b`
   before the later update, which can lead to OOB if `b` was less than
   42:
   ```
     void foo(int *__counted_by(a + b) p, int a, int b, std::span<int> sp) {
       p = sp.first(b + 42).data();
       b = 42; // b is assigned and used
       a = b;
     }
   ```
5. Safe assignment patterns. This uses the infrastructure that is
   already available for count-attributed arguments, and checks for each
   assigned pointer in the group that the RHS has enough elements.

This analysis is hidden behind `-fexperimental-bounds-safety-attributes`
flag, so that we don't waste cycles traversing the AST when the
attributes are not enabled.

rdar://128160398
rdar://128161580
@patrykstefanski patrykstefanski force-pushed the eng/pstefanski/PR-128160398 branch from 4239eee to 99b91ff Compare September 26, 2025 23:44
@patrykstefanski patrykstefanski changed the title [WIP][-Wunsafe-buffer-usage] Check __counted_by() assignments [-Wunsafe-buffer-usage] Check count-attributed assignment groups Sep 26, 2025
Copy link

@ziqingluo-90 ziqingluo-90 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

q = p = q;     // warn: assignment to bounds-attributed pointer 'p' must be inside of a bounds-attributed group in a compound statement

I feel like the diagnostic message may confuse users. What is a bounds-attributed group? How about just say

assignment to bounds-attributed pointer 'p' must be a simple statement 'p = RHS;' followed by a simple statement 'count = RHS;'. 

When the count expression is a constant, we hide followed by a simple statement 'count = RHS;'.

Copy link

@ziqingluo-90 ziqingluo-90 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just came up with some questions after reading the description.

Assignments to count-attributed objects that are implicitly
immutable. For example, assigning to a dependent count that is used
in an inout pointer is not allowed, since the update won't be visible
on the call-site:
void foo(int *__counted_by(count) *out_p, int count) {
*out_p = ...;
count = ...; // immutable
}

Will out_p = ... ; be warned about?

void foo(int *__counted_by(a + b) p, int a, int b) {
p = ...;
p = ...; // duplicated
a = ...;
// b missing
}

So if the pointer is updated, the count variables must also be explicitly updated regardless of whether their values change?

@patrykstefanski
Copy link
Author

I'll split this PR into multiple parts, so that reviewing is easier and we can have more focused discussion.

@patrykstefanski patrykstefanski changed the title [-Wunsafe-buffer-usage] Check count-attributed assignment groups [DO NOT MERGE][-Wunsafe-buffer-usage] Check count-attributed assignment groups Sep 29, 2025
@patrykstefanski
Copy link
Author

q = p = q;     // warn: assignment to bounds-attributed pointer 'p' must be inside of a bounds-attributed group in a compound statement

I feel like the diagnostic message may confuse users. What is a bounds-attributed group? How about just say

assignment to bounds-attributed pointer 'p' must be a simple statement 'p = RHS;' followed by a simple statement 'count = RHS;'. 

When the count expression is a constant, we hide followed by a simple statement 'count = RHS;'.

I agree that my initial diagnostic needs rework. I think your diagnostic is better. Though, does 'p' must be a simple statement 'p = RHS;' imply that { p = RHS; ... } (assignment directly in compound) is fine but q = p = RHS; is not? I feel like we should provide some info that the assignment cannot be nested.

@patrykstefanski
Copy link
Author

Just came up with some questions after reading the description.

Assignments to count-attributed objects that are implicitly
immutable. For example, assigning to a dependent count that is used
in an inout pointer is not allowed, since the update won't be visible
on the call-site:
void foo(int *__counted_by(count) *out_p, int count) {
*out_p = ...;
count = ...; // immutable
}

Will out_p = ... ; be warned about?

void foo(int *__counted_by(a + b) p, int a, int b) {
p = ...;
p = ...; // duplicated
a = ...;
// b missing
}

So if the pointer is updated, the count variables must also be explicitly updated regardless of whether their values change?

Yes, you have to update the count explicitly, unless the count is immutable (then you have to omit the assignment). Those are the rules of -fbounds-safety and I just ported them.

The reason behind this rule in -fbounds-safety is that, without it, it would often lead to runtime traps, when the user forgot to update the count. For example:

void foo(int *__counted_by(count) p, int count) {
  p = NULL; count = 0;

  // ...

  p = malloc(100 * sizeof(int));
  // count is 0, so the assignment succeeds

  // ...

  int x = p[42]; // oops, runtime trap, count is 0
}

However, this is not the case for our interop, since we always verify the assignment at compile-time, and in our case skipping the assignment would work just fine. Unsure if we should just port this rule from -fbounds-safety or diverge from it and have something nicer. Let's follow up in the future PR about this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:bounds-safety Issue relating to the experimental -fbounds-safety feature in Clang
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants