Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -8114,6 +8114,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<
Expand Down
8 changes: 5 additions & 3 deletions clang/lib/CodeGen/CGBuiltin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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>();
Expand Down
25 changes: 22 additions & 3 deletions clang/lib/Sema/SemaBoundsSafety.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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()) {
Expand Down Expand Up @@ -272,6 +286,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};
}

Expand Down
65 changes: 65 additions & 0 deletions clang/test/CodeGen/attr-counted-by-void-ptr-gnu.c
Original file line number Diff line number Diff line change
@@ -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
}
6 changes: 4 additions & 2 deletions clang/test/Sema/attr-counted-by-late-parsed-struct-ptrs.c
Original file line number Diff line number Diff line change
@@ -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)))

Expand Down Expand Up @@ -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;
};

Expand Down
8 changes: 5 additions & 3 deletions clang/test/Sema/attr-counted-by-or-null-last-field.c
Original file line number Diff line number Diff line change
@@ -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)))

Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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)))
Expand Down Expand Up @@ -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;
};

Expand Down
10 changes: 6 additions & 4 deletions clang/test/Sema/attr-counted-by-or-null-struct-ptrs.c
Original file line number Diff line number Diff line change
@@ -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)))
Expand Down Expand Up @@ -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);
};

Expand Down Expand Up @@ -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;
};

Expand Down
10 changes: 6 additions & 4 deletions clang/test/Sema/attr-counted-by-struct-ptrs.c
Original file line number Diff line number Diff line change
@@ -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)))

Expand Down Expand Up @@ -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);
};

Expand Down Expand Up @@ -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;
};

Expand Down
101 changes: 101 additions & 0 deletions clang/test/Sema/attr-counted-by-void-ptr-gnu.c
Original file line number Diff line number Diff line change
@@ -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@+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@+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@+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@+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);
};

// 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-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@+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);
};

//==============================================================================
// 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@+1{{arithmetic on a pointer to void is a GNU extension}}
return t->buf + 10;
}