[clang] f29955a - [Clang][Sema] Allow counted_by on void* as GNU extension (#164737)
via cfe-commits
cfe-commits at lists.llvm.org
Thu Nov 6 19:10:40 PST 2025
Author: Kees Cook
Date: 2025-11-06T19:10:36-08:00
New Revision: f29955a594aedf5943d492a999b83e8c6b8fafae
URL: https://github.com/llvm/llvm-project/commit/f29955a594aedf5943d492a999b83e8c6b8fafae
DIFF: https://github.com/llvm/llvm-project/commit/f29955a594aedf5943d492a999b83e8c6b8fafae.diff
LOG: [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.
Added:
clang/test/CodeGen/attr-counted-by-void-ptr-gnu.c
clang/test/Sema/attr-counted-by-void-ptr-gnu.c
Modified:
clang/include/clang/Basic/DiagnosticSemaKinds.td
clang/lib/CodeGen/CGBuiltin.cpp
clang/lib/Sema/SemaBoundsSafety.cpp
clang/test/Sema/attr-counted-by-late-parsed-struct-ptrs.c
clang/test/Sema/attr-counted-by-or-null-last-field.c
clang/test/Sema/attr-counted-by-or-null-late-parsed-struct-ptrs.c
clang/test/Sema/attr-counted-by-or-null-struct-ptrs.c
clang/test/Sema/attr-counted-by-struct-ptrs.c
Removed:
################################################################################
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index f43707e513351..04f2e8d654fd5 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -8116,6 +8116,17 @@ def ext_gnu_ptr_func_arith : Extension<
"arithmetic on%select{ a|}0 pointer%select{|s}0 to%select{ the|}2 function "
"type%select{|s}2 %1%select{| and %3}2 is a GNU extension">,
InGroup<GNUPointerArith>;
+def ext_gnu_counted_by_void_ptr
+ : Extension<
+ "'%select{counted_by|sized_by|counted_by_or_null|sized_by_or_null}0' "
+ "on a pointer to void is a GNU extension, treated as "
+ "'%select{sized_by|sized_by|sized_by_or_null|sized_by_or_null}0'">,
+ InGroup<GNUPointerArith>;
+def note_gnu_counted_by_void_ptr_use_sized_by
+ : Note<"use "
+ "'%select{__sized_by|__sized_by|__sized_by_or_null|__sized_by_or_"
+ "null}0' "
+ "to suppress this warning">;
def err_readonly_message_assignment : Error<
"assigning to 'readonly' return result of an Objective-C message not allowed">;
def ext_c2y_increment_complex : Extension<
diff --git a/clang/lib/CodeGen/CGBuiltin.cpp b/clang/lib/CodeGen/CGBuiltin.cpp
index bbcee34b384c0..f3eee38bdbd5f 100644
--- a/clang/lib/CodeGen/CGBuiltin.cpp
+++ b/clang/lib/CodeGen/CGBuiltin.cpp
@@ -1211,14 +1211,10 @@ llvm::Value *CodeGenFunction::emitCountedByPointerSize(
getContext().getTypeSizeInChars(ElementTy->getPointeeType());
if (ElementSize.isZero()) {
- // This might be a __sized_by on a 'void *', which counts bytes, not
- // elements.
+ // This might be a __sized_by (or __counted_by) on a
+ // 'void *', which counts bytes, not elements.
auto *CAT = ElementTy->getAs<CountAttributedType>();
- if (!CAT || (CAT->getKind() != CountAttributedType::SizedBy &&
- CAT->getKind() != CountAttributedType::SizedByOrNull))
- // Okay, not sure what it is now.
- // FIXME: Should this be an assert?
- return std::optional<CharUnits>();
+ assert(CAT && "must have an CountAttributedType");
ElementSize = CharUnits::One();
}
diff --git a/clang/lib/Sema/SemaBoundsSafety.cpp b/clang/lib/Sema/SemaBoundsSafety.cpp
index 39ab13653f5fe..de9adf8ef5a1b 100644
--- a/clang/lib/Sema/SemaBoundsSafety.cpp
+++ b/clang/lib/Sema/SemaBoundsSafety.cpp
@@ -132,9 +132,23 @@ bool Sema::CheckCountedByAttrOnField(FieldDecl *FD, Expr *E, bool CountInBytes,
// `BoundsSafetyCheckUseOfCountAttrPtr`
//
// * When the pointee type is always an incomplete type (e.g.
- // `void`) the attribute is disallowed by this method because we know the
- // type can never be completed so there's no reason to allow it.
- InvalidTypeKind = CountedByInvalidPointeeTypeKind::INCOMPLETE;
+ // `void` in strict C mode) the attribute is disallowed by this method
+ // because we know the type can never be completed so there's no reason
+ // to allow it.
+ //
+ // Exception: void has an implicit size of 1 byte for pointer arithmetic
+ // (following GNU convention). Therefore, counted_by on void* is allowed
+ // and behaves equivalently to sized_by (treating the count as bytes).
+ bool IsVoidPtr = PointeeTy->isVoidType();
+ if (IsVoidPtr) {
+ // Emit a warning that this is a GNU extension.
+ Diag(FD->getBeginLoc(), diag::ext_gnu_counted_by_void_ptr) << Kind;
+ Diag(FD->getBeginLoc(), diag::note_gnu_counted_by_void_ptr_use_sized_by)
+ << Kind;
+ assert(InvalidTypeKind == CountedByInvalidPointeeTypeKind::VALID);
+ } else {
+ InvalidTypeKind = CountedByInvalidPointeeTypeKind::INCOMPLETE;
+ }
} else if (PointeeTy->isSizelessType()) {
InvalidTypeKind = CountedByInvalidPointeeTypeKind::SIZELESS;
} else if (PointeeTy->isFunctionType()) {
@@ -272,6 +286,9 @@ GetCountedByAttrOnIncompletePointee(QualType Ty, NamedDecl **ND) {
if (!PointeeTy->isIncompleteType(ND))
return {};
+ if (PointeeTy->isVoidType())
+ return {};
+
return {CATy, PointeeTy};
}
diff --git a/clang/test/CodeGen/attr-counted-by-void-ptr-gnu.c b/clang/test/CodeGen/attr-counted-by-void-ptr-gnu.c
new file mode 100644
index 0000000000000..e22aad306f60c
--- /dev/null
+++ b/clang/test/CodeGen/attr-counted-by-void-ptr-gnu.c
@@ -0,0 +1,65 @@
+// RUN: %clang_cc1 -std=gnu11 -triple x86_64-unknown-linux-gnu -O2 -emit-llvm -o - %s | FileCheck %s
+
+// Test that counted_by on void* in GNU mode treats void as having size 1 (byte count)
+
+#define __counted_by(f) __attribute__((counted_by(f)))
+#define __sized_by(f) __attribute__((sized_by(f)))
+
+struct with_counted_by_void {
+ int count;
+ void* buf __counted_by(count);
+};
+
+struct with_sized_by_void {
+ int size;
+ void* buf __sized_by(size);
+};
+
+struct with_counted_by_int {
+ int count;
+ int* buf __counted_by(count);
+};
+
+// CHECK-LABEL: define dso_local {{.*}}@test_counted_by_void(
+// CHECK: %[[COUNT:.*]] = load i32, ptr %s
+// CHECK: %[[NARROW:.*]] = tail call i32 @llvm.smax.i32(i32 %[[COUNT]], i32 0)
+// CHECK: %[[ZEXT:.*]] = zext nneg i32 %[[NARROW]] to i64
+// CHECK: ret i64 %[[ZEXT]]
+//
+// Verify: counted_by on void* returns the count directly (count * 1 byte)
+long long test_counted_by_void(struct with_counted_by_void *s) {
+ return __builtin_dynamic_object_size(s->buf, 0);
+}
+
+// CHECK-LABEL: define dso_local {{.*}}@test_sized_by_void(
+// CHECK: %[[SIZE:.*]] = load i32, ptr %s
+// CHECK: %[[NARROW:.*]] = tail call i32 @llvm.smax.i32(i32 %[[SIZE]], i32 0)
+// CHECK: %[[ZEXT:.*]] = zext nneg i32 %[[NARROW]] to i64
+// CHECK: ret i64 %[[ZEXT]]
+//
+// Verify: sized_by on void* returns the size directly
+long long test_sized_by_void(struct with_sized_by_void *s) {
+ return __builtin_dynamic_object_size(s->buf, 0);
+}
+
+// CHECK-LABEL: define dso_local {{.*}}@test_counted_by_int(
+// CHECK: %[[COUNT:.*]] = load i32, ptr %s
+// CHECK: %[[SEXT:.*]] = sext i32 %[[COUNT]] to i64
+// CHECK: %[[SIZE:.*]] = shl nsw i64 %[[SEXT]], 2
+// CHECK: ret i64
+//
+// Verify: counted_by on int* returns count * sizeof(int) = count * 4
+long long test_counted_by_int(struct with_counted_by_int *s) {
+ return __builtin_dynamic_object_size(s->buf, 0);
+}
+
+// CHECK-LABEL: define dso_local ptr @test_void_ptr_arithmetic(
+// CHECK: %[[BUF:.*]] = load ptr, ptr
+// CHECK: %[[EXT:.*]] = sext i32 %offset to i64
+// CHECK: %[[PTR:.*]] = getelementptr inbounds i8, ptr %[[BUF]], i64 %[[EXT]]
+// CHECK: ret ptr %[[PTR]]
+//
+// Verify: pointer arithmetic on void* uses i8 (byte offsets), not i32 or other sizes
+void* test_void_ptr_arithmetic(struct with_counted_by_void *s, int offset) {
+ return s->buf + offset; // GNU extension: void* arithmetic
+}
diff --git a/clang/test/Sema/attr-counted-by-late-parsed-struct-ptrs.c b/clang/test/Sema/attr-counted-by-late-parsed-struct-ptrs.c
index 8d4e0c510603a..443ccbbae66db 100644
--- a/clang/test/Sema/attr-counted-by-late-parsed-struct-ptrs.c
+++ b/clang/test/Sema/attr-counted-by-late-parsed-struct-ptrs.c
@@ -1,4 +1,4 @@
-// RUN: %clang_cc1 -fexperimental-late-parse-attributes -fsyntax-only -verify %s
+// RUN: %clang_cc1 -fexperimental-late-parse-attributes -fsyntax-only -Wpointer-arith -verify %s
#define __counted_by(f) __attribute__((counted_by(f)))
@@ -29,7 +29,9 @@ struct on_member_pointer_const_incomplete_ty {
};
struct on_member_pointer_void_ty {
- 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}}
+ // expected-warning at +2{{'counted_by' on a pointer to void is a GNU extension, treated as 'sized_by'}}
+ // expected-note at +1{{use '__sized_by' to suppress this warning}}
+ void* buf __counted_by(count);
int count;
};
diff --git a/clang/test/Sema/attr-counted-by-or-null-last-field.c b/clang/test/Sema/attr-counted-by-or-null-last-field.c
index 60a1f571b19e9..d0c50a733acef 100644
--- a/clang/test/Sema/attr-counted-by-or-null-last-field.c
+++ b/clang/test/Sema/attr-counted-by-or-null-last-field.c
@@ -1,5 +1,5 @@
-// RUN: %clang_cc1 -fsyntax-only -verify=expected,immediate %s
-// RUN: %clang_cc1 -fsyntax-only -fexperimental-late-parse-attributes -verify=expected,late %s
+// RUN: %clang_cc1 -fsyntax-only -Wpointer-arith -verify=expected,immediate %s
+// RUN: %clang_cc1 -fsyntax-only -Wpointer-arith -fexperimental-late-parse-attributes -verify=expected,late %s
#define __counted_by_or_null(f) __attribute__((counted_by_or_null(f)))
@@ -128,7 +128,9 @@ struct on_member_ptr_incomplete_const_ty_ty_pos {
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}}
+ // expected-warning at +2{{'counted_by_or_null' on a pointer to void is a GNU extension, treated as 'sized_by_or_null'}}
+ // expected-note at +1{{use '__sized_by_or_null' to suppress this warning}}
+ void * ptr __counted_by_or_null(count);
};
typedef void(fn_ty)(int);
diff --git a/clang/test/Sema/attr-counted-by-or-null-late-parsed-struct-ptrs.c b/clang/test/Sema/attr-counted-by-or-null-late-parsed-struct-ptrs.c
index 2150c81f9e9be..233b729f87ccd 100644
--- a/clang/test/Sema/attr-counted-by-or-null-late-parsed-struct-ptrs.c
+++ b/clang/test/Sema/attr-counted-by-or-null-late-parsed-struct-ptrs.c
@@ -1,4 +1,4 @@
-// RUN: %clang_cc1 -fexperimental-late-parse-attributes -fsyntax-only -verify %s
+// RUN: %clang_cc1 -fexperimental-late-parse-attributes -fsyntax-only -Wpointer-arith -verify %s
#define __counted_by_or_null(f) __attribute__((counted_by_or_null(f)))
#define __counted_by(f) __attribute__((counted_by(f)))
@@ -30,7 +30,9 @@ struct on_member_pointer_const_incomplete_ty {
};
struct on_member_pointer_void_ty {
- 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}}
+ // expected-warning at +2{{'counted_by_or_null' on a pointer to void is a GNU extension, treated as 'sized_by_or_null'}}
+ // expected-note at +1{{use '__sized_by_or_null' to suppress this warning}}
+ void* buf __counted_by_or_null(count);
int count;
};
diff --git a/clang/test/Sema/attr-counted-by-or-null-struct-ptrs.c b/clang/test/Sema/attr-counted-by-or-null-struct-ptrs.c
index 0bb09059c97f9..0fd739ca7d4c3 100644
--- a/clang/test/Sema/attr-counted-by-or-null-struct-ptrs.c
+++ b/clang/test/Sema/attr-counted-by-or-null-struct-ptrs.c
@@ -1,5 +1,5 @@
-// RUN: %clang_cc1 -fsyntax-only -verify %s
-// RUN: %clang_cc1 -fexperimental-late-parse-attributes -fsyntax-only -verify %s
+// RUN: %clang_cc1 -fsyntax-only -Wpointer-arith -verify %s
+// RUN: %clang_cc1 -fexperimental-late-parse-attributes -fsyntax-only -Wpointer-arith -verify %s
#define __counted_by_or_null(f) __attribute__((counted_by_or_null(f)))
#define __counted_by(f) __attribute__((counted_by(f)))
@@ -32,7 +32,8 @@ struct on_member_pointer_const_incomplete_ty {
struct on_member_pointer_void_ty {
int count;
- // expected-error at +1{{'counted_by_or_null' cannot be applied to a pointer with pointee of unknown size because 'void' is an incomplete type}}
+ // expected-warning at +2{{'counted_by_or_null' on a pointer to void is a GNU extension, treated as 'sized_by_or_null'}}
+ // expected-note at +1{{use '__sized_by_or_null' to suppress this warning}}
void* buf __counted_by_or_null(count);
};
@@ -124,7 +125,8 @@ struct on_member_pointer_const_incomplete_ty_ty_pos {
struct on_member_pointer_void_ty_ty_pos {
int count;
- // expected-error at +1{{'counted_by_or_null' cannot be applied to a pointer with pointee of unknown size because 'void' is an incomplete type}}
+ // expected-warning at +2{{'counted_by_or_null' on a pointer to void is a GNU extension, treated as 'sized_by_or_null'}}
+ // expected-note at +1{{use '__sized_by_or_null' to suppress this warning}}
void *__counted_by_or_null(count) buf;
};
diff --git a/clang/test/Sema/attr-counted-by-struct-ptrs.c b/clang/test/Sema/attr-counted-by-struct-ptrs.c
index c05d18262e2b7..a42f3895695a3 100644
--- a/clang/test/Sema/attr-counted-by-struct-ptrs.c
+++ b/clang/test/Sema/attr-counted-by-struct-ptrs.c
@@ -1,5 +1,5 @@
-// RUN: %clang_cc1 -fsyntax-only -verify %s
-// RUN: %clang_cc1 -fsyntax-only -fexperimental-late-parse-attributes %s -verify
+// RUN: %clang_cc1 -fsyntax-only -Wpointer-arith -verify %s
+// RUN: %clang_cc1 -fsyntax-only -Wpointer-arith -fexperimental-late-parse-attributes %s -verify
#define __counted_by(f) __attribute__((counted_by(f)))
@@ -31,7 +31,8 @@ struct on_member_pointer_const_incomplete_ty {
struct on_member_pointer_void_ty {
int count;
- // expected-error at +1{{'counted_by' cannot be applied to a pointer with pointee of unknown size because 'void' is an incomplete type}}
+ // expected-warning at +2{{'counted_by' on a pointer to void is a GNU extension, treated as 'sized_by'}}
+ // expected-note at +1{{use '__sized_by' to suppress this warning}}
void* buf __counted_by(count);
};
@@ -123,7 +124,8 @@ struct on_member_pointer_const_incomplete_ty_ty_pos {
struct on_member_pointer_void_ty_ty_pos {
int count;
- // expected-error at +1{{'counted_by' cannot be applied to a pointer with pointee of unknown size because 'void' is an incomplete type}}
+ // expected-warning at +2{{'counted_by' on a pointer to void is a GNU extension, treated as 'sized_by'}}
+ // expected-note at +1{{use '__sized_by' to suppress this warning}}
void *__counted_by(count) buf;
};
diff --git a/clang/test/Sema/attr-counted-by-void-ptr-gnu.c b/clang/test/Sema/attr-counted-by-void-ptr-gnu.c
new file mode 100644
index 0000000000000..c1ed5f84cf935
--- /dev/null
+++ b/clang/test/Sema/attr-counted-by-void-ptr-gnu.c
@@ -0,0 +1,101 @@
+// RUN: %clang_cc1 -fsyntax-only -verify=expected-nowarn %s
+// RUN: %clang_cc1 -Wpointer-arith -fsyntax-only -verify=expected-warn %s
+// RUN: %clang_cc1 -fexperimental-bounds-safety -fsyntax-only -verify=expected-bounds %s
+
+// expected-nowarn-no-diagnostics
+// expected-bounds-no-diagnostics
+
+#define NULL (void*)0
+#define __counted_by(f) __attribute__((counted_by(f)))
+#define __counted_by_or_null(f) __attribute__((counted_by_or_null(f)))
+#define __sized_by(f) __attribute__((sized_by(f)))
+
+//==============================================================================
+// Test: counted_by on void* is allowed (warns with -Wpointer-arith)
+//==============================================================================
+
+struct test_void_ptr_gnu {
+ int count;
+ // expected-warn-warning at +2{{'counted_by' on a pointer to void is a GNU extension, treated as 'sized_by'}}
+ // expected-warn-note at +1{{use '__sized_by' to suppress this warning}}
+ void* buf __counted_by(count);
+};
+
+struct test_const_void_ptr_gnu {
+ int count;
+ // expected-warn-warning at +2{{'counted_by' on a pointer to void is a GNU extension, treated as 'sized_by'}}
+ // expected-warn-note at +1{{use '__sized_by' to suppress this warning}}
+ const void* buf __counted_by(count);
+};
+
+struct test_volatile_void_ptr_gnu {
+ int count;
+ // expected-warn-warning at +2{{'counted_by' on a pointer to void is a GNU extension, treated as 'sized_by'}}
+ // expected-warn-note at +1{{use '__sized_by' to suppress this warning}}
+ volatile void* buf __counted_by(count);
+};
+
+struct test_const_volatile_void_ptr_gnu {
+ int count;
+ // expected-warn-warning at +2{{'counted_by' on a pointer to void is a GNU extension, treated as 'sized_by'}}
+ // expected-warn-note at +1{{use '__sized_by' to suppress this warning}}
+ const volatile void* buf __counted_by(count);
+};
+
+// Verify sized_by still works the same way (always allowed, no warning)
+struct test_sized_by_void_ptr {
+ int size;
+ void* buf __sized_by(size); // OK in both modes, no warning
+};
+
+//==============================================================================
+// Test: counted_by_or_null on void* behaves the same
+//==============================================================================
+
+struct test_void_ptr_or_null_gnu {
+ int count;
+ // expected-warn-warning at +2{{'counted_by_or_null' on a pointer to void is a GNU extension, treated as 'sized_by_or_null'}}
+ // expected-warn-note at +1{{use '__sized_by_or_null' to suppress this warning}}
+ void* buf __counted_by_or_null(count);
+};
+
+struct test_const_void_ptr_or_null_gnu {
+ int count;
+ // expected-warn-warning at +2{{'counted_by_or_null' on a pointer to void is a GNU extension, treated as 'sized_by_or_null'}}
+ // expected-warn-note at +1{{use '__sized_by_or_null' to suppress this warning}}
+ const void* buf __counted_by_or_null(count);
+};
+
+//==============================================================================
+// Test: Using void* __counted_by(...) pointers (not just declaring them)
+//==============================================================================
+
+// Verify that void* __counted_by pointers can be used as rvalues, assigned to,
+// passed to functions, etc.
+
+void* use_as_rvalue(struct test_void_ptr_gnu* t) {
+ return t->buf;
+}
+
+void assign_to_pointer(struct test_void_ptr_gnu* t) {
+ t->buf = NULL;
+ t->count = 0;
+}
+
+extern void* my_allocator(unsigned long);
+
+void assign_from_allocator(struct test_void_ptr_gnu* t) {
+ t->buf = my_allocator(100);
+ t->count = 100;
+}
+
+void takes_void_ptr(void* p);
+
+void pass_to_function(struct test_void_ptr_gnu* t) {
+ takes_void_ptr(t->buf);
+}
+
+void* pointer_arithmetic(struct test_void_ptr_gnu* t) {
+ // expected-warn-warning at +1{{arithmetic on a pointer to void is a GNU extension}}
+ return t->buf + 10;
+}
More information about the cfe-commits
mailing list