llvm-project/clang/test/Sema/attr-counted-by-or-null-last-field.c
Dan Liew 59eafd1bdc
[BoundsSafety][Sema] Allow counted_by and counted_by_or_null on pointers where the pointee type is incomplete but potentially completable (#106321)
Previously using the `counted_by` or `counted_by_or_null` attribute on a
pointer with an incomplete pointee type was forbidden. Unfortunately
this prevented a situation like the following from being allowed.

Header file:

```
struct EltTy; // Incomplete type
struct Buffer {
  size_t count;
  struct EltTy* __counted_by(count) buffer; // error before this patch
};
```

Implementation file:

```
struct EltTy {
  // definition
};

void allocBuffer(struct Buffer* b) {
  b->buffer = malloc(sizeof(EltTy)* b->count);
}
```

To allow code like the above but still enforce that the pointee
type size is known in locations where `-fbounds-safety` needs to
emit bounds checks the following scheme is used.

* For incomplete pointee types that can never be completed (e.g. `void`)
  these are treated as error where the attribute is written (just like
  before this patch).
* For incomplete pointee types that might be completable later on
  (struct, union, and enum forward declarations)
  in the translation unit, writing the attribute on the incomplete
  pointee type is allowed on the FieldDecl declaration but "uses" of the
  declared pointer are forbidden if at the point of "use" the pointee
  type is still incomplete.

For this patch a "use" of a FieldDecl covers:

* Explicit and Implicit initialization (note see **Tentative Definition
  Initialization** for an exception to this)
* Assignment
* Conversion to an lvalue (e.g. for use in an expression)

In the swift lang fork of Clang the `counted_by` and
`counted_by_or_null` attribute are allowed in many more contexts. That
isn't the case for upstream Clang so the "use" checks for the attribute
on VarDecl, ParamVarDecl, and function return type have been omitted
from this patch because they can't be tested. However, the
`BoundsSafetyCheckAssignmentToCountAttrPtrWithIncompletePointeeTy` and
`BoundsSafetyCheckUseOfCountAttrPtrWithIncompletePointeeTy` functions
retain the ability to emit diagnostics for these other contexts to avoid
unnecessary divergence between upstream Clang and Apple's internal fork.
Support for checking "uses" will be upstreamed when upstream Clang
allows the `counted_by` and `counted_by_or_null` attribute in additional
contexts.

This patch has a few limitations:

** 1. Tentative Defition Initialization **

This patch currently allows something like:

```
struct IncompleteTy;
struct Buffer {
  int count;
  struct IncompleteTy* __counted_by(count) buf;
};

// Tentative definition
struct Buffer GlobalBuf;
```

The Tentative definition in this example becomes an actual definition
whose initialization **should be checked** but it currently isn't.
Addressing this problem will be done in a subseqent patch.

** 2. When the incomplete pointee type is a typedef diagnostics are slightly misleading **

For this situation:

```

struct IncompleteTy;
typedef struct IncompleteTy Incomplete_t;

struct Buffer {
  int count;
  struct IncompleteTy* __counted_by(count) buf;
};

void use(struct Buffer b) {
  b.buf = 0x0;
}
```

This code emits `note: forward declaration of 'Incomplete_t' (aka
'struct IncompleteTy')` but the location is on the `struct
IncompleteTy;` forward declaration.  This is misleading because
`Incomplete_t` isn't actually forward declared there (instead the
underlying type is). This could be resolved by additional diagnostics
that walk the chain of typedefs and explain each step of the walk.
However, that would be very verbose and didn't seem like a direction
worth pursuing.

rdar://133600117
2025-04-18 15:26:04 -07:00

145 lines
4.6 KiB
C

// RUN: %clang_cc1 -fsyntax-only -verify=expected,immediate %s
// RUN: %clang_cc1 -fsyntax-only -fexperimental-late-parse-attributes -verify=expected,late %s
#define __counted_by_or_null(f) __attribute__((counted_by_or_null(f)))
// This has been adapted from clang/test/Sema/attr-counted-by-vla.c, but with VLAs replaced with pointers
struct bar;
struct not_found {
int count;
struct bar *ptr __counted_by_or_null(bork); // expected-error {{use of undeclared identifier 'bork'}}
};
struct no_found_count_not_in_substruct {
unsigned long flags;
unsigned char count; // expected-note {{'count' declared here}}
struct A {
int dummy;
int * ptr __counted_by_or_null(count); // expected-error {{'counted_by_or_null' field 'count' isn't within the same struct as the annotated pointer}}
} a;
};
struct not_found_count_not_in_unnamed_substruct {
unsigned char count; // expected-note {{'count' declared here}}
struct {
int dummy;
int * ptr __counted_by_or_null(count); // expected-error {{'counted_by_or_null' field 'count' isn't within the same struct as the annotated pointer}}
} a;
};
struct not_found_count_not_in_unnamed_substruct_2 {
struct {
unsigned char count; // expected-note {{'count' declared here}}
};
struct {
int dummy;
int * ptr __counted_by_or_null(count); // expected-error {{'counted_by_or_null' field 'count' isn't within the same struct as the annotated pointer}}
} a;
};
struct not_found_count_in_other_unnamed_substruct {
struct {
unsigned char count;
} a1;
struct {
int dummy;
int * ptr __counted_by_or_null(count); // expected-error {{use of undeclared identifier 'count'}}
};
};
struct not_found_count_in_other_substruct {
struct _a1 {
unsigned char count;
} a1;
struct {
int dummy;
int * ptr __counted_by_or_null(count); // expected-error {{use of undeclared identifier 'count'}}
};
};
struct not_found_count_in_other_substruct_2 {
struct _a2 {
unsigned char count;
} a2;
int * ptr __counted_by_or_null(count); // expected-error {{use of undeclared identifier 'count'}}
};
struct not_found_suggest {
int bork;
struct bar **ptr __counted_by_or_null(blork); // expected-error {{use of undeclared identifier 'blork'}}
};
int global; // expected-note {{'global' declared here}}
struct found_outside_of_struct {
int bork;
struct bar ** ptr __counted_by_or_null(global); // expected-error {{field 'global' in 'counted_by_or_null' not inside structure}}
};
struct self_referrential {
int bork;
// immediate-error@+2{{use of undeclared identifier 'self'}}
// late-error@+1{{'counted_by_or_null' only applies to pointers; did you mean to use 'counted_by'?}}
struct bar *self[] __counted_by_or_null(self);
};
struct non_int_count {
double dbl_count;
struct bar ** ptr __counted_by_or_null(dbl_count); // expected-error {{'counted_by_or_null' requires a non-boolean integer type argument}}
};
struct array_of_ints_count {
int integers[2];
struct bar ** ptr __counted_by_or_null(integers); // expected-error {{'counted_by_or_null' requires a non-boolean integer type argument}}
};
struct not_a_c99_fam {
int count;
struct bar *non_c99_fam[0] __counted_by_or_null(count); // expected-error {{'counted_by_or_null' only applies to pointers; did you mean to use 'counted_by'?}}
};
struct annotated_with_anon_struct {
unsigned long flags;
struct {
unsigned char count;
int * ptr __counted_by_or_null(crount); // expected-error {{use of undeclared identifier 'crount'}}
};
};
//==============================================================================
// __counted_by_or_null on a struct ptr with element type that has unknown count
//==============================================================================
struct count_unknown;
struct on_member_ptr_incomplete_ty_ty_pos {
int count;
struct count_unknown * ptr __counted_by_or_null(count); // ok
};
struct on_member_ptr_incomplete_const_ty_ty_pos {
int count;
const struct count_unknown * ptr __counted_by_or_null(count); // ok
};
struct on_member_ptr_void_ty_ty_pos {
int count;
void * ptr __counted_by_or_null(count); // expected-error {{'counted_by_or_null' cannot be applied to a pointer with pointee of unknown size because 'void' is an incomplete type}}
};
typedef void(fn_ty)(int);
struct on_member_ptr_fn_ptr_ty {
int count;
fn_ty* * ptr __counted_by_or_null(count);
};
struct on_member_ptr_fn_ty {
int count;
fn_ty * ptr __counted_by_or_null(count); // expected-error {{'counted_by_or_null' cannot be applied to a pointer with pointee of unknown size because 'fn_ty' (aka 'void (int)') is a function type}}
};