Skip to content

Commit f29955a

Browse files
authored
[Clang][Sema] Allow counted_by on void* as GNU extension (#164737)
The counted_by attribute currently rejects void* members because void has no defined size. However, the sized_by attribute accepts void* since it explicitly measures bytes. As a GNU extension, void pointer arithmetic treats void as having size 1 byte, so counted_by on void* should behave identically to sized_by (treating the count as bytes). Allow counted_by on void* as a GNU extension. The implementation validates this only at declaration time in SemaBoundsSafety.cpp, emitting a -Wpointer-arith warning that the attribute is treated as a GNU extension equivalent to sized_by. Both use-site validation and code generation trust this earlier validation, avoiding redundant checks. In CodeGen, __builtin_dynamic_object_size now correctly handles counted_by on void* by treating any CountAttributedType with zero element size as having 1-byte elements, matching the GNU void pointer arithmetic semantics. Add tests validating both Sema diagnostics and CodeGen behavior (correct byte counts from __builtin_dynamic_object_size). Update existing counted_by tests to explicitly use -Wpointer-arith to preserve their original intent of rejecting void* in strict C mode.
1 parent c88e207 commit f29955a

10 files changed

+225
-25
lines changed

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8116,6 +8116,17 @@ def ext_gnu_ptr_func_arith : Extension<
81168116
"arithmetic on%select{ a|}0 pointer%select{|s}0 to%select{ the|}2 function "
81178117
"type%select{|s}2 %1%select{| and %3}2 is a GNU extension">,
81188118
InGroup<GNUPointerArith>;
8119+
def ext_gnu_counted_by_void_ptr
8120+
: Extension<
8121+
"'%select{counted_by|sized_by|counted_by_or_null|sized_by_or_null}0' "
8122+
"on a pointer to void is a GNU extension, treated as "
8123+
"'%select{sized_by|sized_by|sized_by_or_null|sized_by_or_null}0'">,
8124+
InGroup<GNUPointerArith>;
8125+
def note_gnu_counted_by_void_ptr_use_sized_by
8126+
: Note<"use "
8127+
"'%select{__sized_by|__sized_by|__sized_by_or_null|__sized_by_or_"
8128+
"null}0' "
8129+
"to suppress this warning">;
81198130
def err_readonly_message_assignment : Error<
81208131
"assigning to 'readonly' return result of an Objective-C message not allowed">;
81218132
def ext_c2y_increment_complex : Extension<

clang/lib/CodeGen/CGBuiltin.cpp

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1211,14 +1211,10 @@ llvm::Value *CodeGenFunction::emitCountedByPointerSize(
12111211
getContext().getTypeSizeInChars(ElementTy->getPointeeType());
12121212

12131213
if (ElementSize.isZero()) {
1214-
// This might be a __sized_by on a 'void *', which counts bytes, not
1215-
// elements.
1214+
// This might be a __sized_by (or __counted_by) on a
1215+
// 'void *', which counts bytes, not elements.
12161216
auto *CAT = ElementTy->getAs<CountAttributedType>();
1217-
if (!CAT || (CAT->getKind() != CountAttributedType::SizedBy &&
1218-
CAT->getKind() != CountAttributedType::SizedByOrNull))
1219-
// Okay, not sure what it is now.
1220-
// FIXME: Should this be an assert?
1221-
return std::optional<CharUnits>();
1217+
assert(CAT && "must have an CountAttributedType");
12221218

12231219
ElementSize = CharUnits::One();
12241220
}

clang/lib/Sema/SemaBoundsSafety.cpp

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -132,9 +132,23 @@ bool Sema::CheckCountedByAttrOnField(FieldDecl *FD, Expr *E, bool CountInBytes,
132132
// `BoundsSafetyCheckUseOfCountAttrPtr`
133133
//
134134
// * When the pointee type is always an incomplete type (e.g.
135-
// `void`) the attribute is disallowed by this method because we know the
136-
// type can never be completed so there's no reason to allow it.
137-
InvalidTypeKind = CountedByInvalidPointeeTypeKind::INCOMPLETE;
135+
// `void` in strict C mode) the attribute is disallowed by this method
136+
// because we know the type can never be completed so there's no reason
137+
// to allow it.
138+
//
139+
// Exception: void has an implicit size of 1 byte for pointer arithmetic
140+
// (following GNU convention). Therefore, counted_by on void* is allowed
141+
// and behaves equivalently to sized_by (treating the count as bytes).
142+
bool IsVoidPtr = PointeeTy->isVoidType();
143+
if (IsVoidPtr) {
144+
// Emit a warning that this is a GNU extension.
145+
Diag(FD->getBeginLoc(), diag::ext_gnu_counted_by_void_ptr) << Kind;
146+
Diag(FD->getBeginLoc(), diag::note_gnu_counted_by_void_ptr_use_sized_by)
147+
<< Kind;
148+
assert(InvalidTypeKind == CountedByInvalidPointeeTypeKind::VALID);
149+
} else {
150+
InvalidTypeKind = CountedByInvalidPointeeTypeKind::INCOMPLETE;
151+
}
138152
} else if (PointeeTy->isSizelessType()) {
139153
InvalidTypeKind = CountedByInvalidPointeeTypeKind::SIZELESS;
140154
} else if (PointeeTy->isFunctionType()) {
@@ -272,6 +286,9 @@ GetCountedByAttrOnIncompletePointee(QualType Ty, NamedDecl **ND) {
272286
if (!PointeeTy->isIncompleteType(ND))
273287
return {};
274288

289+
if (PointeeTy->isVoidType())
290+
return {};
291+
275292
return {CATy, PointeeTy};
276293
}
277294

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// RUN: %clang_cc1 -std=gnu11 -triple x86_64-unknown-linux-gnu -O2 -emit-llvm -o - %s | FileCheck %s
2+
3+
// Test that counted_by on void* in GNU mode treats void as having size 1 (byte count)
4+
5+
#define __counted_by(f) __attribute__((counted_by(f)))
6+
#define __sized_by(f) __attribute__((sized_by(f)))
7+
8+
struct with_counted_by_void {
9+
int count;
10+
void* buf __counted_by(count);
11+
};
12+
13+
struct with_sized_by_void {
14+
int size;
15+
void* buf __sized_by(size);
16+
};
17+
18+
struct with_counted_by_int {
19+
int count;
20+
int* buf __counted_by(count);
21+
};
22+
23+
// CHECK-LABEL: define dso_local {{.*}}@test_counted_by_void(
24+
// CHECK: %[[COUNT:.*]] = load i32, ptr %s
25+
// CHECK: %[[NARROW:.*]] = tail call i32 @llvm.smax.i32(i32 %[[COUNT]], i32 0)
26+
// CHECK: %[[ZEXT:.*]] = zext nneg i32 %[[NARROW]] to i64
27+
// CHECK: ret i64 %[[ZEXT]]
28+
//
29+
// Verify: counted_by on void* returns the count directly (count * 1 byte)
30+
long long test_counted_by_void(struct with_counted_by_void *s) {
31+
return __builtin_dynamic_object_size(s->buf, 0);
32+
}
33+
34+
// CHECK-LABEL: define dso_local {{.*}}@test_sized_by_void(
35+
// CHECK: %[[SIZE:.*]] = load i32, ptr %s
36+
// CHECK: %[[NARROW:.*]] = tail call i32 @llvm.smax.i32(i32 %[[SIZE]], i32 0)
37+
// CHECK: %[[ZEXT:.*]] = zext nneg i32 %[[NARROW]] to i64
38+
// CHECK: ret i64 %[[ZEXT]]
39+
//
40+
// Verify: sized_by on void* returns the size directly
41+
long long test_sized_by_void(struct with_sized_by_void *s) {
42+
return __builtin_dynamic_object_size(s->buf, 0);
43+
}
44+
45+
// CHECK-LABEL: define dso_local {{.*}}@test_counted_by_int(
46+
// CHECK: %[[COUNT:.*]] = load i32, ptr %s
47+
// CHECK: %[[SEXT:.*]] = sext i32 %[[COUNT]] to i64
48+
// CHECK: %[[SIZE:.*]] = shl nsw i64 %[[SEXT]], 2
49+
// CHECK: ret i64
50+
//
51+
// Verify: counted_by on int* returns count * sizeof(int) = count * 4
52+
long long test_counted_by_int(struct with_counted_by_int *s) {
53+
return __builtin_dynamic_object_size(s->buf, 0);
54+
}
55+
56+
// CHECK-LABEL: define dso_local ptr @test_void_ptr_arithmetic(
57+
// CHECK: %[[BUF:.*]] = load ptr, ptr
58+
// CHECK: %[[EXT:.*]] = sext i32 %offset to i64
59+
// CHECK: %[[PTR:.*]] = getelementptr inbounds i8, ptr %[[BUF]], i64 %[[EXT]]
60+
// CHECK: ret ptr %[[PTR]]
61+
//
62+
// Verify: pointer arithmetic on void* uses i8 (byte offsets), not i32 or other sizes
63+
void* test_void_ptr_arithmetic(struct with_counted_by_void *s, int offset) {
64+
return s->buf + offset; // GNU extension: void* arithmetic
65+
}

clang/test/Sema/attr-counted-by-late-parsed-struct-ptrs.c

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// RUN: %clang_cc1 -fexperimental-late-parse-attributes -fsyntax-only -verify %s
1+
// RUN: %clang_cc1 -fexperimental-late-parse-attributes -fsyntax-only -Wpointer-arith -verify %s
22

33
#define __counted_by(f) __attribute__((counted_by(f)))
44

@@ -29,7 +29,9 @@ struct on_member_pointer_const_incomplete_ty {
2929
};
3030

3131
struct on_member_pointer_void_ty {
32-
void* buf __counted_by(count); // expected-error{{'counted_by' cannot be applied to a pointer with pointee of unknown size because 'void' is an incomplete type}}
32+
// expected-warning@+2{{'counted_by' on a pointer to void is a GNU extension, treated as 'sized_by'}}
33+
// expected-note@+1{{use '__sized_by' to suppress this warning}}
34+
void* buf __counted_by(count);
3335
int count;
3436
};
3537

clang/test/Sema/attr-counted-by-or-null-last-field.c

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
// RUN: %clang_cc1 -fsyntax-only -verify=expected,immediate %s
2-
// RUN: %clang_cc1 -fsyntax-only -fexperimental-late-parse-attributes -verify=expected,late %s
1+
// RUN: %clang_cc1 -fsyntax-only -Wpointer-arith -verify=expected,immediate %s
2+
// RUN: %clang_cc1 -fsyntax-only -Wpointer-arith -fexperimental-late-parse-attributes -verify=expected,late %s
33

44
#define __counted_by_or_null(f) __attribute__((counted_by_or_null(f)))
55

@@ -128,7 +128,9 @@ struct on_member_ptr_incomplete_const_ty_ty_pos {
128128

129129
struct on_member_ptr_void_ty_ty_pos {
130130
int count;
131-
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}}
131+
// expected-warning@+2{{'counted_by_or_null' on a pointer to void is a GNU extension, treated as 'sized_by_or_null'}}
132+
// expected-note@+1{{use '__sized_by_or_null' to suppress this warning}}
133+
void * ptr __counted_by_or_null(count);
132134
};
133135

134136
typedef void(fn_ty)(int);

clang/test/Sema/attr-counted-by-or-null-late-parsed-struct-ptrs.c

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// RUN: %clang_cc1 -fexperimental-late-parse-attributes -fsyntax-only -verify %s
1+
// RUN: %clang_cc1 -fexperimental-late-parse-attributes -fsyntax-only -Wpointer-arith -verify %s
22

33
#define __counted_by_or_null(f) __attribute__((counted_by_or_null(f)))
44
#define __counted_by(f) __attribute__((counted_by(f)))
@@ -30,7 +30,9 @@ struct on_member_pointer_const_incomplete_ty {
3030
};
3131

3232
struct on_member_pointer_void_ty {
33-
void* buf __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}}
33+
// expected-warning@+2{{'counted_by_or_null' on a pointer to void is a GNU extension, treated as 'sized_by_or_null'}}
34+
// expected-note@+1{{use '__sized_by_or_null' to suppress this warning}}
35+
void* buf __counted_by_or_null(count);
3436
int count;
3537
};
3638

clang/test/Sema/attr-counted-by-or-null-struct-ptrs.c

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
// RUN: %clang_cc1 -fsyntax-only -verify %s
2-
// RUN: %clang_cc1 -fexperimental-late-parse-attributes -fsyntax-only -verify %s
1+
// RUN: %clang_cc1 -fsyntax-only -Wpointer-arith -verify %s
2+
// RUN: %clang_cc1 -fexperimental-late-parse-attributes -fsyntax-only -Wpointer-arith -verify %s
33

44
#define __counted_by_or_null(f) __attribute__((counted_by_or_null(f)))
55
#define __counted_by(f) __attribute__((counted_by(f)))
@@ -32,7 +32,8 @@ struct on_member_pointer_const_incomplete_ty {
3232

3333
struct on_member_pointer_void_ty {
3434
int count;
35-
// expected-error@+1{{'counted_by_or_null' cannot be applied to a pointer with pointee of unknown size because 'void' is an incomplete type}}
35+
// expected-warning@+2{{'counted_by_or_null' on a pointer to void is a GNU extension, treated as 'sized_by_or_null'}}
36+
// expected-note@+1{{use '__sized_by_or_null' to suppress this warning}}
3637
void* buf __counted_by_or_null(count);
3738
};
3839

@@ -124,7 +125,8 @@ struct on_member_pointer_const_incomplete_ty_ty_pos {
124125

125126
struct on_member_pointer_void_ty_ty_pos {
126127
int count;
127-
// expected-error@+1{{'counted_by_or_null' cannot be applied to a pointer with pointee of unknown size because 'void' is an incomplete type}}
128+
// expected-warning@+2{{'counted_by_or_null' on a pointer to void is a GNU extension, treated as 'sized_by_or_null'}}
129+
// expected-note@+1{{use '__sized_by_or_null' to suppress this warning}}
128130
void *__counted_by_or_null(count) buf;
129131
};
130132

clang/test/Sema/attr-counted-by-struct-ptrs.c

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
// RUN: %clang_cc1 -fsyntax-only -verify %s
2-
// RUN: %clang_cc1 -fsyntax-only -fexperimental-late-parse-attributes %s -verify
1+
// RUN: %clang_cc1 -fsyntax-only -Wpointer-arith -verify %s
2+
// RUN: %clang_cc1 -fsyntax-only -Wpointer-arith -fexperimental-late-parse-attributes %s -verify
33

44
#define __counted_by(f) __attribute__((counted_by(f)))
55

@@ -31,7 +31,8 @@ struct on_member_pointer_const_incomplete_ty {
3131

3232
struct on_member_pointer_void_ty {
3333
int count;
34-
// expected-error@+1{{'counted_by' cannot be applied to a pointer with pointee of unknown size because 'void' is an incomplete type}}
34+
// expected-warning@+2{{'counted_by' on a pointer to void is a GNU extension, treated as 'sized_by'}}
35+
// expected-note@+1{{use '__sized_by' to suppress this warning}}
3536
void* buf __counted_by(count);
3637
};
3738

@@ -123,7 +124,8 @@ struct on_member_pointer_const_incomplete_ty_ty_pos {
123124

124125
struct on_member_pointer_void_ty_ty_pos {
125126
int count;
126-
// expected-error@+1{{'counted_by' cannot be applied to a pointer with pointee of unknown size because 'void' is an incomplete type}}
127+
// expected-warning@+2{{'counted_by' on a pointer to void is a GNU extension, treated as 'sized_by'}}
128+
// expected-note@+1{{use '__sized_by' to suppress this warning}}
127129
void *__counted_by(count) buf;
128130
};
129131

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
// RUN: %clang_cc1 -fsyntax-only -verify=expected-nowarn %s
2+
// RUN: %clang_cc1 -Wpointer-arith -fsyntax-only -verify=expected-warn %s
3+
// RUN: %clang_cc1 -fexperimental-bounds-safety -fsyntax-only -verify=expected-bounds %s
4+
5+
// expected-nowarn-no-diagnostics
6+
// expected-bounds-no-diagnostics
7+
8+
#define NULL (void*)0
9+
#define __counted_by(f) __attribute__((counted_by(f)))
10+
#define __counted_by_or_null(f) __attribute__((counted_by_or_null(f)))
11+
#define __sized_by(f) __attribute__((sized_by(f)))
12+
13+
//==============================================================================
14+
// Test: counted_by on void* is allowed (warns with -Wpointer-arith)
15+
//==============================================================================
16+
17+
struct test_void_ptr_gnu {
18+
int count;
19+
// expected-warn-warning@+2{{'counted_by' on a pointer to void is a GNU extension, treated as 'sized_by'}}
20+
// expected-warn-note@+1{{use '__sized_by' to suppress this warning}}
21+
void* buf __counted_by(count);
22+
};
23+
24+
struct test_const_void_ptr_gnu {
25+
int count;
26+
// expected-warn-warning@+2{{'counted_by' on a pointer to void is a GNU extension, treated as 'sized_by'}}
27+
// expected-warn-note@+1{{use '__sized_by' to suppress this warning}}
28+
const void* buf __counted_by(count);
29+
};
30+
31+
struct test_volatile_void_ptr_gnu {
32+
int count;
33+
// expected-warn-warning@+2{{'counted_by' on a pointer to void is a GNU extension, treated as 'sized_by'}}
34+
// expected-warn-note@+1{{use '__sized_by' to suppress this warning}}
35+
volatile void* buf __counted_by(count);
36+
};
37+
38+
struct test_const_volatile_void_ptr_gnu {
39+
int count;
40+
// expected-warn-warning@+2{{'counted_by' on a pointer to void is a GNU extension, treated as 'sized_by'}}
41+
// expected-warn-note@+1{{use '__sized_by' to suppress this warning}}
42+
const volatile void* buf __counted_by(count);
43+
};
44+
45+
// Verify sized_by still works the same way (always allowed, no warning)
46+
struct test_sized_by_void_ptr {
47+
int size;
48+
void* buf __sized_by(size); // OK in both modes, no warning
49+
};
50+
51+
//==============================================================================
52+
// Test: counted_by_or_null on void* behaves the same
53+
//==============================================================================
54+
55+
struct test_void_ptr_or_null_gnu {
56+
int count;
57+
// expected-warn-warning@+2{{'counted_by_or_null' on a pointer to void is a GNU extension, treated as 'sized_by_or_null'}}
58+
// expected-warn-note@+1{{use '__sized_by_or_null' to suppress this warning}}
59+
void* buf __counted_by_or_null(count);
60+
};
61+
62+
struct test_const_void_ptr_or_null_gnu {
63+
int count;
64+
// expected-warn-warning@+2{{'counted_by_or_null' on a pointer to void is a GNU extension, treated as 'sized_by_or_null'}}
65+
// expected-warn-note@+1{{use '__sized_by_or_null' to suppress this warning}}
66+
const void* buf __counted_by_or_null(count);
67+
};
68+
69+
//==============================================================================
70+
// Test: Using void* __counted_by(...) pointers (not just declaring them)
71+
//==============================================================================
72+
73+
// Verify that void* __counted_by pointers can be used as rvalues, assigned to,
74+
// passed to functions, etc.
75+
76+
void* use_as_rvalue(struct test_void_ptr_gnu* t) {
77+
return t->buf;
78+
}
79+
80+
void assign_to_pointer(struct test_void_ptr_gnu* t) {
81+
t->buf = NULL;
82+
t->count = 0;
83+
}
84+
85+
extern void* my_allocator(unsigned long);
86+
87+
void assign_from_allocator(struct test_void_ptr_gnu* t) {
88+
t->buf = my_allocator(100);
89+
t->count = 100;
90+
}
91+
92+
void takes_void_ptr(void* p);
93+
94+
void pass_to_function(struct test_void_ptr_gnu* t) {
95+
takes_void_ptr(t->buf);
96+
}
97+
98+
void* pointer_arithmetic(struct test_void_ptr_gnu* t) {
99+
// expected-warn-warning@+1{{arithmetic on a pointer to void is a GNU extension}}
100+
return t->buf + 10;
101+
}

0 commit comments

Comments
 (0)