https://github.com/kees updated https://github.com/llvm/llvm-project/pull/164737
>From 4609f8c24170d3caf5245a140d4ff1c7cabbec5a Mon Sep 17 00:00:00 2001 From: Kees Cook <[email protected]> Date: Wed, 22 Oct 2025 23:56:41 +0000 Subject: [PATCH 1/8] [Clang][Sema] Allow counted_by on void* in GNU mode 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. In GNU mode, 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* when GNU extensions are enabled. 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 (warnings in GNU mode, errors in strict C) and CodeGen behavior (correct byte counts from __builtin_dynamic_object_size). Update existing counted_by tests to explicitly use -std=c11 to preserve their original intent of rejecting void* in strict C mode. --- .../clang/Basic/DiagnosticSemaKinds.td | 6 ++ clang/lib/CodeGen/CGBuiltin.cpp | 8 ++- clang/lib/Sema/SemaBoundsSafety.cpp | 22 ++++++- .../CodeGen/attr-counted-by-void-ptr-gnu.c | 65 +++++++++++++++++++ .../attr-counted-by-late-parsed-struct-ptrs.c | 2 +- .../Sema/attr-counted-by-or-null-last-field.c | 4 +- ...unted-by-or-null-late-parsed-struct-ptrs.c | 2 +- .../attr-counted-by-or-null-struct-ptrs.c | 4 +- clang/test/Sema/attr-counted-by-struct-ptrs.c | 4 +- .../test/Sema/attr-counted-by-void-ptr-gnu.c | 65 +++++++++++++++++++ 10 files changed, 168 insertions(+), 14 deletions(-) create mode 100644 clang/test/CodeGen/attr-counted-by-void-ptr-gnu.c create mode 100644 clang/test/Sema/attr-counted-by-void-ptr-gnu.c diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 20b499462ae94..33f28f251f5d0 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -8114,6 +8114,12 @@ 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 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 fd14cd6926fe2..aaf8d59414498 100644 --- a/clang/lib/CodeGen/CGBuiltin.cpp +++ b/clang/lib/CodeGen/CGBuiltin.cpp @@ -1211,11 +1211,13 @@ 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 in GNU mode) on a + // 'void *', which counts bytes, not elements. auto *CAT = ElementTy->getAs<CountAttributedType>(); if (!CAT || (CAT->getKind() != CountAttributedType::SizedBy && - CAT->getKind() != CountAttributedType::SizedByOrNull)) + CAT->getKind() != CountAttributedType::SizedByOrNull && + CAT->getKind() != CountAttributedType::CountedBy && + CAT->getKind() != CountAttributedType::CountedByOrNull)) // Okay, not sure what it is now. // FIXME: Should this be an assert? return std::optional<CharUnits>(); diff --git a/clang/lib/Sema/SemaBoundsSafety.cpp b/clang/lib/Sema/SemaBoundsSafety.cpp index 39ab13653f5fe..abc7397aaa888 100644 --- a/clang/lib/Sema/SemaBoundsSafety.cpp +++ b/clang/lib/Sema/SemaBoundsSafety.cpp @@ -132,9 +132,20 @@ 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: In GNU mode, void has an implicit size of 1 byte for pointer + // arithmetic. Therefore, counted_by on void* is allowed as a GNU extension + // and behaves equivalently to sized_by (treating the count as bytes). + bool IsVoidPtrInGNUMode = PointeeTy->isVoidType() && getLangOpts().GNUMode; + if (IsVoidPtrInGNUMode) { + // Emit a warning that this is a GNU extension + Diag(FD->getBeginLoc(), diag::ext_gnu_counted_by_void_ptr) << Kind; + } else { + InvalidTypeKind = CountedByInvalidPointeeTypeKind::INCOMPLETE; + } } else if (PointeeTy->isSizelessType()) { InvalidTypeKind = CountedByInvalidPointeeTypeKind::SIZELESS; } else if (PointeeTy->isFunctionType()) { @@ -272,6 +283,11 @@ GetCountedByAttrOnIncompletePointee(QualType Ty, NamedDecl **ND) { if (!PointeeTy->isIncompleteType(ND)) return {}; + // If counted_by is on void*, it was already validated at declaration time + // as a GNU extension. No need to re-check GNU mode here. + 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..b6c2ba68732ee 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 -std=c11 -fexperimental-late-parse-attributes -fsyntax-only -verify %s #define __counted_by(f) __attribute__((counted_by(f))) 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..4cf8cb64557d2 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 -std=c11 -fsyntax-only -verify=expected,immediate %s +// RUN: %clang_cc1 -std=c11 -fsyntax-only -fexperimental-late-parse-attributes -verify=expected,late %s #define __counted_by_or_null(f) __attribute__((counted_by_or_null(f))) 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..29e235463f822 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 -std=c11 -fexperimental-late-parse-attributes -fsyntax-only -verify %s #define __counted_by_or_null(f) __attribute__((counted_by_or_null(f))) #define __counted_by(f) __attribute__((counted_by(f))) 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..7fe43a20c0c6e 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 -std=c11 -fsyntax-only -verify %s +// RUN: %clang_cc1 -std=c11 -fexperimental-late-parse-attributes -fsyntax-only -verify %s #define __counted_by_or_null(f) __attribute__((counted_by_or_null(f))) #define __counted_by(f) __attribute__((counted_by(f))) diff --git a/clang/test/Sema/attr-counted-by-struct-ptrs.c b/clang/test/Sema/attr-counted-by-struct-ptrs.c index c05d18262e2b7..d75533d7d135b 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 -std=c11 -fsyntax-only -verify %s +// RUN: %clang_cc1 -std=c11 -fsyntax-only -fexperimental-late-parse-attributes %s -verify #define __counted_by(f) __attribute__((counted_by(f))) 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..c6aa371434bc4 --- /dev/null +++ b/clang/test/Sema/attr-counted-by-void-ptr-gnu.c @@ -0,0 +1,65 @@ +// RUN: %clang_cc1 -std=gnu11 -fsyntax-only -verify=expected-nowarn %s +// RUN: %clang_cc1 -std=gnu11 -Wpointer-arith -fsyntax-only -verify=expected-warn %s +// RUN: %clang_cc1 -std=c11 -fsyntax-only -verify=expected-strict %s + +// expected-nowarn-no-diagnostics + +#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 in GNU mode, rejected in strict mode +//============================================================================== + +struct test_void_ptr_gnu { + int count; + // expected-warn-warning@+2{{'counted_by' on a pointer to void is a GNU extension, treated as 'sized_by'}} + // expected-strict-error@+1{{'counted_by' cannot be applied to a pointer with pointee of unknown size because 'void' is an incomplete type}} + void* buf __counted_by(count); +}; + +struct test_const_void_ptr_gnu { + int count; + // expected-warn-warning@+2{{'counted_by' on a pointer to void is a GNU extension, treated as 'sized_by'}} + // expected-strict-error@+1{{'counted_by' cannot be applied to a pointer with pointee of unknown size because 'const void' is an incomplete type}} + const void* buf __counted_by(count); +}; + +struct test_volatile_void_ptr_gnu { + int count; + // expected-warn-warning@+2{{'counted_by' on a pointer to void is a GNU extension, treated as 'sized_by'}} + // expected-strict-error@+1{{'counted_by' cannot be applied to a pointer with pointee of unknown size because 'volatile void' is an incomplete type}} + volatile void* buf __counted_by(count); +}; + +struct test_const_volatile_void_ptr_gnu { + int count; + // expected-warn-warning@+2{{'counted_by' on a pointer to void is a GNU extension, treated as 'sized_by'}} + // expected-strict-error@+1{{'counted_by' cannot be applied to a pointer with pointee of unknown size because 'const volatile void' is an incomplete type}} + 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@+2{{'counted_by_or_null' on a pointer to void is a GNU extension, treated as 'sized_by_or_null'}} + // expected-strict-error@+1{{'counted_by_or_null' cannot be applied to a pointer with pointee of unknown size because 'void' is an incomplete type}} + void* buf __counted_by_or_null(count); +}; + +struct test_const_void_ptr_or_null_gnu { + int count; + // expected-warn-warning@+2{{'counted_by_or_null' on a pointer to void is a GNU extension, treated as 'sized_by_or_null'}} + // expected-strict-error@+1{{'counted_by_or_null' cannot be applied to a pointer with pointee of unknown size because 'const void' is an incomplete type}} + const void* buf __counted_by_or_null(count); +}; >From 478a58213bab866ce3a49baab0f3f4079362b436 Mon Sep 17 00:00:00 2001 From: Kees Cook <[email protected]> Date: Wed, 29 Oct 2025 18:39:29 -0700 Subject: [PATCH 2/8] add note after warning --- .../include/clang/Basic/DiagnosticSemaKinds.td | 5 +++++ clang/lib/Sema/SemaBoundsSafety.cpp | 2 ++ clang/test/Sema/attr-counted-by-void-ptr-gnu.c | 18 ++++++++++++------ 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 33f28f251f5d0..22729b88f2625 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -8120,6 +8120,11 @@ def ext_gnu_counted_by_void_ptr "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/Sema/SemaBoundsSafety.cpp b/clang/lib/Sema/SemaBoundsSafety.cpp index abc7397aaa888..07da89bf606d0 100644 --- a/clang/lib/Sema/SemaBoundsSafety.cpp +++ b/clang/lib/Sema/SemaBoundsSafety.cpp @@ -143,6 +143,8 @@ bool Sema::CheckCountedByAttrOnField(FieldDecl *FD, Expr *E, bool CountInBytes, if (IsVoidPtrInGNUMode) { // 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; } else { InvalidTypeKind = CountedByInvalidPointeeTypeKind::INCOMPLETE; } diff --git a/clang/test/Sema/attr-counted-by-void-ptr-gnu.c b/clang/test/Sema/attr-counted-by-void-ptr-gnu.c index c6aa371434bc4..485b6e0b4850b 100644 --- a/clang/test/Sema/attr-counted-by-void-ptr-gnu.c +++ b/clang/test/Sema/attr-counted-by-void-ptr-gnu.c @@ -14,28 +14,32 @@ struct test_void_ptr_gnu { int count; - // expected-warn-warning@+2{{'counted_by' on a pointer to void is a GNU extension, treated as 'sized_by'}} + // expected-warn-warning@+3{{'counted_by' on a pointer to void is a GNU extension, treated as 'sized_by'}} + // expected-warn-note@+2{{use '__sized_by' to suppress this warning}} // expected-strict-error@+1{{'counted_by' cannot be applied to a pointer with pointee of unknown size because 'void' is an incomplete type}} void* buf __counted_by(count); }; struct test_const_void_ptr_gnu { int count; - // expected-warn-warning@+2{{'counted_by' on a pointer to void is a GNU extension, treated as 'sized_by'}} + // expected-warn-warning@+3{{'counted_by' on a pointer to void is a GNU extension, treated as 'sized_by'}} + // expected-warn-note@+2{{use '__sized_by' to suppress this warning}} // expected-strict-error@+1{{'counted_by' cannot be applied to a pointer with pointee of unknown size because 'const void' is an incomplete type}} const void* buf __counted_by(count); }; struct test_volatile_void_ptr_gnu { int count; - // expected-warn-warning@+2{{'counted_by' on a pointer to void is a GNU extension, treated as 'sized_by'}} + // expected-warn-warning@+3{{'counted_by' on a pointer to void is a GNU extension, treated as 'sized_by'}} + // expected-warn-note@+2{{use '__sized_by' to suppress this warning}} // expected-strict-error@+1{{'counted_by' cannot be applied to a pointer with pointee of unknown size because 'volatile void' is an incomplete type}} volatile void* buf __counted_by(count); }; struct test_const_volatile_void_ptr_gnu { int count; - // expected-warn-warning@+2{{'counted_by' on a pointer to void is a GNU extension, treated as 'sized_by'}} + // expected-warn-warning@+3{{'counted_by' on a pointer to void is a GNU extension, treated as 'sized_by'}} + // expected-warn-note@+2{{use '__sized_by' to suppress this warning}} // expected-strict-error@+1{{'counted_by' cannot be applied to a pointer with pointee of unknown size because 'const volatile void' is an incomplete type}} const volatile void* buf __counted_by(count); }; @@ -52,14 +56,16 @@ struct test_sized_by_void_ptr { struct test_void_ptr_or_null_gnu { int count; - // expected-warn-warning@+2{{'counted_by_or_null' on a pointer to void is a GNU extension, treated as 'sized_by_or_null'}} + // expected-warn-warning@+3{{'counted_by_or_null' on a pointer to void is a GNU extension, treated as 'sized_by_or_null'}} + // expected-warn-note@+2{{use '__sized_by_or_null' to suppress this warning}} // expected-strict-error@+1{{'counted_by_or_null' cannot be applied to a pointer with pointee of unknown size because 'void' is an incomplete type}} void* buf __counted_by_or_null(count); }; struct test_const_void_ptr_or_null_gnu { int count; - // expected-warn-warning@+2{{'counted_by_or_null' on a pointer to void is a GNU extension, treated as 'sized_by_or_null'}} + // expected-warn-warning@+3{{'counted_by_or_null' on a pointer to void is a GNU extension, treated as 'sized_by_or_null'}} + // expected-warn-note@+2{{use '__sized_by_or_null' to suppress this warning}} // expected-strict-error@+1{{'counted_by_or_null' cannot be applied to a pointer with pointee of unknown size because 'const void' is an incomplete type}} const void* buf __counted_by_or_null(count); }; >From a277f5102313134ed993735104cd9a45d59c271e Mon Sep 17 00:00:00 2001 From: Kees Cook <[email protected]> Date: Wed, 29 Oct 2025 18:42:18 -0700 Subject: [PATCH 3/8] assert that InvalidTypeKind is unchanged --- clang/lib/Sema/SemaBoundsSafety.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/clang/lib/Sema/SemaBoundsSafety.cpp b/clang/lib/Sema/SemaBoundsSafety.cpp index 07da89bf606d0..8d712672ea7fc 100644 --- a/clang/lib/Sema/SemaBoundsSafety.cpp +++ b/clang/lib/Sema/SemaBoundsSafety.cpp @@ -145,6 +145,7 @@ bool Sema::CheckCountedByAttrOnField(FieldDecl *FD, Expr *E, bool CountInBytes, 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; } >From 2edf4402701b764208f0058cd5aefc084e5de4b1 Mon Sep 17 00:00:00 2001 From: Kees Cook <[email protected]> Date: Wed, 29 Oct 2025 19:22:01 -0700 Subject: [PATCH 4/8] add void* usage tests --- .../test/Sema/attr-counted-by-void-ptr-gnu.c | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/clang/test/Sema/attr-counted-by-void-ptr-gnu.c b/clang/test/Sema/attr-counted-by-void-ptr-gnu.c index 485b6e0b4850b..d2dcfa3bd178c 100644 --- a/clang/test/Sema/attr-counted-by-void-ptr-gnu.c +++ b/clang/test/Sema/attr-counted-by-void-ptr-gnu.c @@ -1,9 +1,12 @@ // RUN: %clang_cc1 -std=gnu11 -fsyntax-only -verify=expected-nowarn %s // RUN: %clang_cc1 -std=gnu11 -Wpointer-arith -fsyntax-only -verify=expected-warn %s // RUN: %clang_cc1 -std=c11 -fsyntax-only -verify=expected-strict %s +// RUN: %clang_cc1 -std=gnu11 -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))) @@ -69,3 +72,37 @@ struct test_const_void_ptr_or_null_gnu { // expected-strict-error@+1{{'counted_by_or_null' cannot be applied to a pointer with pointee of unknown size because 'const void' is an incomplete type}} 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. in GNU mode. In strict C mode, the struct +// declaration itself fails so uses are never reached. + +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; +} + +void assign_from_malloc(struct test_void_ptr_gnu* t) { + extern void* malloc(unsigned long); + t->buf = malloc(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@+1{{arithmetic on a pointer to void is a GNU extension}} + return t->buf + 10; +} >From 84fe6124d8e056579db6bda9032c32fe935a8776 Mon Sep 17 00:00:00 2001 From: Kees Cook <[email protected]> Date: Wed, 29 Oct 2025 22:06:37 -0700 Subject: [PATCH 5/8] avoid using library functions for selftest --- clang/test/Sema/attr-counted-by-void-ptr-gnu.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/clang/test/Sema/attr-counted-by-void-ptr-gnu.c b/clang/test/Sema/attr-counted-by-void-ptr-gnu.c index d2dcfa3bd178c..ae57e942898fd 100644 --- a/clang/test/Sema/attr-counted-by-void-ptr-gnu.c +++ b/clang/test/Sema/attr-counted-by-void-ptr-gnu.c @@ -90,9 +90,10 @@ void assign_to_pointer(struct test_void_ptr_gnu* t) { t->count = 0; } -void assign_from_malloc(struct test_void_ptr_gnu* t) { - extern void* malloc(unsigned long); - t->buf = malloc(100); +extern void* my_allocator(unsigned long); + +void assign_from_allocator(struct test_void_ptr_gnu* t) { + t->buf = my_allocator(100); t->count = 100; } >From f50ef5867fe111020d0e8934b0df4a4a2f13b8d3 Mon Sep 17 00:00:00 2001 From: Kees Cook <[email protected]> Date: Mon, 3 Nov 2025 16:41:54 -0800 Subject: [PATCH 6/8] Do not use GNUMode; just rely on -Wpointer-arith Signed-off-by: Kees Cook <[email protected]> --- clang/lib/Sema/SemaBoundsSafety.cpp | 10 ++--- .../attr-counted-by-late-parsed-struct-ptrs.c | 6 ++- .../Sema/attr-counted-by-or-null-last-field.c | 8 ++-- ...unted-by-or-null-late-parsed-struct-ptrs.c | 6 ++- .../attr-counted-by-or-null-struct-ptrs.c | 10 +++-- clang/test/Sema/attr-counted-by-struct-ptrs.c | 10 +++-- .../test/Sema/attr-counted-by-void-ptr-gnu.c | 42 ++++++++----------- 7 files changed, 47 insertions(+), 45 deletions(-) diff --git a/clang/lib/Sema/SemaBoundsSafety.cpp b/clang/lib/Sema/SemaBoundsSafety.cpp index 8d712672ea7fc..ea405fa2eb97a 100644 --- a/clang/lib/Sema/SemaBoundsSafety.cpp +++ b/clang/lib/Sema/SemaBoundsSafety.cpp @@ -136,12 +136,12 @@ bool Sema::CheckCountedByAttrOnField(FieldDecl *FD, Expr *E, bool CountInBytes, // because we know the type can never be completed so there's no reason // to allow it. // - // Exception: In GNU mode, void has an implicit size of 1 byte for pointer - // arithmetic. Therefore, counted_by on void* is allowed as a GNU extension + // 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 IsVoidPtrInGNUMode = PointeeTy->isVoidType() && getLangOpts().GNUMode; - if (IsVoidPtrInGNUMode) { - // Emit a warning that this is a GNU extension + 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; 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 b6c2ba68732ee..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 -std=c11 -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@+2{{'counted_by' on a pointer to void is a GNU extension, treated as 'sized_by'}} + // expected-note@+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 4cf8cb64557d2..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 -std=c11 -fsyntax-only -verify=expected,immediate %s -// RUN: %clang_cc1 -std=c11 -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@+2{{'counted_by_or_null' on a pointer to void is a GNU extension, treated as 'sized_by_or_null'}} + // expected-note@+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 29e235463f822..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 -std=c11 -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@+2{{'counted_by_or_null' on a pointer to void is a GNU extension, treated as 'sized_by_or_null'}} + // expected-note@+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 7fe43a20c0c6e..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 -std=c11 -fsyntax-only -verify %s -// RUN: %clang_cc1 -std=c11 -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@+1{{'counted_by_or_null' cannot be applied to a pointer with pointee of unknown size because 'void' is an incomplete type}} + // expected-warning@+2{{'counted_by_or_null' on a pointer to void is a GNU extension, treated as 'sized_by_or_null'}} + // expected-note@+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@+1{{'counted_by_or_null' cannot be applied to a pointer with pointee of unknown size because 'void' is an incomplete type}} + // expected-warning@+2{{'counted_by_or_null' on a pointer to void is a GNU extension, treated as 'sized_by_or_null'}} + // expected-note@+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 d75533d7d135b..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 -std=c11 -fsyntax-only -verify %s -// RUN: %clang_cc1 -std=c11 -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@+1{{'counted_by' cannot be applied to a pointer with pointee of unknown size because 'void' is an incomplete type}} + // expected-warning@+2{{'counted_by' on a pointer to void is a GNU extension, treated as 'sized_by'}} + // expected-note@+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@+1{{'counted_by' cannot be applied to a pointer with pointee of unknown size because 'void' is an incomplete type}} + // expected-warning@+2{{'counted_by' on a pointer to void is a GNU extension, treated as 'sized_by'}} + // expected-note@+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 index ae57e942898fd..c1ed5f84cf935 100644 --- a/clang/test/Sema/attr-counted-by-void-ptr-gnu.c +++ b/clang/test/Sema/attr-counted-by-void-ptr-gnu.c @@ -1,7 +1,6 @@ -// RUN: %clang_cc1 -std=gnu11 -fsyntax-only -verify=expected-nowarn %s -// RUN: %clang_cc1 -std=gnu11 -Wpointer-arith -fsyntax-only -verify=expected-warn %s -// RUN: %clang_cc1 -std=c11 -fsyntax-only -verify=expected-strict %s -// RUN: %clang_cc1 -std=gnu11 -fexperimental-bounds-safety -fsyntax-only -verify=expected-bounds %s +// 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 @@ -12,38 +11,34 @@ #define __sized_by(f) __attribute__((sized_by(f))) //============================================================================== -// Test: counted_by on void* is allowed in GNU mode, rejected in strict mode +// Test: counted_by on void* is allowed (warns with -Wpointer-arith) //============================================================================== struct test_void_ptr_gnu { int count; - // expected-warn-warning@+3{{'counted_by' on a pointer to void is a GNU extension, treated as 'sized_by'}} - // expected-warn-note@+2{{use '__sized_by' to suppress this warning}} - // expected-strict-error@+1{{'counted_by' cannot be applied to a pointer with pointee of unknown size because 'void' is an incomplete type}} + // expected-warn-warning@+2{{'counted_by' on a pointer to void is a GNU extension, treated as 'sized_by'}} + // expected-warn-note@+1{{use '__sized_by' to suppress this warning}} void* buf __counted_by(count); }; struct test_const_void_ptr_gnu { int count; - // expected-warn-warning@+3{{'counted_by' on a pointer to void is a GNU extension, treated as 'sized_by'}} - // expected-warn-note@+2{{use '__sized_by' to suppress this warning}} - // expected-strict-error@+1{{'counted_by' cannot be applied to a pointer with pointee of unknown size because 'const void' is an incomplete type}} + // expected-warn-warning@+2{{'counted_by' on a pointer to void is a GNU extension, treated as 'sized_by'}} + // expected-warn-note@+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@+3{{'counted_by' on a pointer to void is a GNU extension, treated as 'sized_by'}} - // expected-warn-note@+2{{use '__sized_by' to suppress this warning}} - // expected-strict-error@+1{{'counted_by' cannot be applied to a pointer with pointee of unknown size because 'volatile void' is an incomplete type}} + // expected-warn-warning@+2{{'counted_by' on a pointer to void is a GNU extension, treated as 'sized_by'}} + // expected-warn-note@+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@+3{{'counted_by' on a pointer to void is a GNU extension, treated as 'sized_by'}} - // expected-warn-note@+2{{use '__sized_by' to suppress this warning}} - // expected-strict-error@+1{{'counted_by' cannot be applied to a pointer with pointee of unknown size because 'const volatile void' is an incomplete type}} + // expected-warn-warning@+2{{'counted_by' on a pointer to void is a GNU extension, treated as 'sized_by'}} + // expected-warn-note@+1{{use '__sized_by' to suppress this warning}} const volatile void* buf __counted_by(count); }; @@ -59,17 +54,15 @@ struct test_sized_by_void_ptr { struct test_void_ptr_or_null_gnu { int count; - // expected-warn-warning@+3{{'counted_by_or_null' on a pointer to void is a GNU extension, treated as 'sized_by_or_null'}} - // expected-warn-note@+2{{use '__sized_by_or_null' to suppress this warning}} - // expected-strict-error@+1{{'counted_by_or_null' cannot be applied to a pointer with pointee of unknown size because 'void' is an incomplete type}} + // expected-warn-warning@+2{{'counted_by_or_null' on a pointer to void is a GNU extension, treated as 'sized_by_or_null'}} + // expected-warn-note@+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@+3{{'counted_by_or_null' on a pointer to void is a GNU extension, treated as 'sized_by_or_null'}} - // expected-warn-note@+2{{use '__sized_by_or_null' to suppress this warning}} - // expected-strict-error@+1{{'counted_by_or_null' cannot be applied to a pointer with pointee of unknown size because 'const void' is an incomplete type}} + // expected-warn-warning@+2{{'counted_by_or_null' on a pointer to void is a GNU extension, treated as 'sized_by_or_null'}} + // expected-warn-note@+1{{use '__sized_by_or_null' to suppress this warning}} const void* buf __counted_by_or_null(count); }; @@ -78,8 +71,7 @@ struct test_const_void_ptr_or_null_gnu { //============================================================================== // Verify that void* __counted_by pointers can be used as rvalues, assigned to, -// passed to functions, etc. in GNU mode. In strict C mode, the struct -// declaration itself fails so uses are never reached. +// passed to functions, etc. void* use_as_rvalue(struct test_void_ptr_gnu* t) { return t->buf; >From 7afc37cf190ed77aa8a13ef3459155eb0209d7c5 Mon Sep 17 00:00:00 2001 From: Kees Cook <[email protected]> Date: Thu, 6 Nov 2025 15:57:58 -0800 Subject: [PATCH 7/8] drop needless comment --- clang/lib/Sema/SemaBoundsSafety.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/clang/lib/Sema/SemaBoundsSafety.cpp b/clang/lib/Sema/SemaBoundsSafety.cpp index ea405fa2eb97a..de9adf8ef5a1b 100644 --- a/clang/lib/Sema/SemaBoundsSafety.cpp +++ b/clang/lib/Sema/SemaBoundsSafety.cpp @@ -286,8 +286,6 @@ GetCountedByAttrOnIncompletePointee(QualType Ty, NamedDecl **ND) { if (!PointeeTy->isIncompleteType(ND)) return {}; - // If counted_by is on void*, it was already validated at declaration time - // as a GNU extension. No need to re-check GNU mode here. if (PointeeTy->isVoidType()) return {}; >From 40469ae3beffca393b7a713c6559544573ed61f5 Mon Sep 17 00:00:00 2001 From: Kees Cook <[email protected]> Date: Thu, 6 Nov 2025 15:58:29 -0800 Subject: [PATCH 8/8] CAT->getKind() checking is redundant if we want all possible kinds --- clang/lib/CodeGen/CGBuiltin.cpp | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/clang/lib/CodeGen/CGBuiltin.cpp b/clang/lib/CodeGen/CGBuiltin.cpp index aaf8d59414498..c5c03a7f70c81 100644 --- a/clang/lib/CodeGen/CGBuiltin.cpp +++ b/clang/lib/CodeGen/CGBuiltin.cpp @@ -1211,16 +1211,10 @@ llvm::Value *CodeGenFunction::emitCountedByPointerSize( getContext().getTypeSizeInChars(ElementTy->getPointeeType()); if (ElementSize.isZero()) { - // This might be a __sized_by (or __counted_by in GNU mode) on a + // 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 && - CAT->getKind() != CountAttributedType::CountedBy && - CAT->getKind() != CountAttributedType::CountedByOrNull)) - // 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(); } _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
