Skip to content

Commit f6c1e65

Browse files
authored
[clang] Implement a __is_bitwise_cloneable builtin type trait. (#86512)
This patch implements a `__is_bitwise_cloneable` builtin in clang. The builtin is used as a guard to check a type can be safely bitwise copied by memcpy. It's functionally similar to `__is_trivially_copyable`, but covers a wider range of types (e.g. classes with virtual functions). The compiler guarantees that after copy, the destination object has the same object representations as the source object. And it is up to user to guarantee that program semantic constraints are satisfied. Context: https://discourse.llvm.org/t/extension-for-creating-objects-via-memcpy
1 parent 6841321 commit f6c1e65

File tree

9 files changed

+134
-0
lines changed

9 files changed

+134
-0
lines changed

clang/docs/LanguageExtensions.rst

+24
Original file line numberDiff line numberDiff line change
@@ -4016,6 +4016,30 @@ Note that the `size` argument must be a compile time constant.
40164016
40174017
Note that this intrinsic cannot yet be called in a ``constexpr`` context.
40184018
4019+
``__is_bitwise_cloneable``
4020+
--------------------------
4021+
4022+
A type trait is used to check whether a type can be safely copied by memcpy.
4023+
4024+
**Syntax**:
4025+
4026+
.. code-block:: c++
4027+
4028+
bool __is_bitwise_cloneable(Type)
4029+
4030+
**Description**:
4031+
4032+
Objects of bitwise cloneable types can be bitwise copied by memcpy/memmove. The
4033+
Clang compiler warrants that this behavior is well defined, and won't be
4034+
broken by compiler optimizations and sanitizers.
4035+
4036+
For implicit-lifetime types, the lifetime of the new object is implicitly
4037+
started after the copy. For other types (e.g., classes with virtual methods),
4038+
the lifetime isn't started, and using the object results in undefined behavior
4039+
according to the C++ Standard.
4040+
4041+
This builtin can be used in constant expressions.
4042+
40194043
Atomic Min/Max builtins with memory ordering
40204044
--------------------------------------------
40214045

clang/docs/ReleaseNotes.rst

+3
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,9 @@ Non-comprehensive list of changes in this release
340340
``-Winvalid-constexpr`` is not enabled for the function definition, which
341341
should result in mild compile-time performance improvements.
342342

343+
- Added ``__is_bitwise_cloneable`` which is used to check whether a type
344+
can be safely copied by memcpy/memmove.
345+
343346
New Compiler Flags
344347
------------------
345348
- ``-fsanitize=implicit-bitfield-conversion`` checks implicit truncation and

clang/include/clang/AST/Type.h

+14
Original file line numberDiff line numberDiff line change
@@ -1120,6 +1120,20 @@ class QualType {
11201120
/// Return true if this is a trivially copyable type (C++0x [basic.types]p9)
11211121
bool isTriviallyCopyableType(const ASTContext &Context) const;
11221122

1123+
/// Return true if the type is safe to bitwise copy using memcpy/memmove.
1124+
///
1125+
/// This is an extension in clang: bitwise cloneable types act as trivially
1126+
/// copyable types, meaning their underlying bytes can be safely copied by
1127+
/// memcpy or memmove. After the copy, the destination object has the same
1128+
/// object representation.
1129+
///
1130+
/// However, there are cases where it is not safe to copy:
1131+
/// - When sanitizers, such as AddressSanitizer, add padding with poison,
1132+
/// which can cause issues if those poisoned padding bits are accessed.
1133+
/// - Types with Objective-C lifetimes, where specific runtime
1134+
/// semantics may not be preserved during a bitwise copy.
1135+
bool isBitwiseCloneableType(const ASTContext &Context) const;
1136+
11231137
/// Return true if this is a trivially copyable type
11241138
bool isTriviallyCopyConstructibleType(const ASTContext &Context) const;
11251139

clang/include/clang/Basic/TokenKinds.def

+2
Original file line numberDiff line numberDiff line change
@@ -542,6 +542,8 @@ TYPE_TRAIT_2(__reference_converts_from_temporary, ReferenceConvertsFromTemporary
542542
// is not exposed to users.
543543
TYPE_TRAIT_2(/*EmptySpellingName*/, IsDeducible, KEYCXX)
544544

545+
TYPE_TRAIT_1(__is_bitwise_cloneable, IsBitwiseCloneable, KEYALL)
546+
545547
// Embarcadero Expression Traits
546548
EXPRESSION_TRAIT(__is_lvalue_expr, IsLValueExpr, KEYCXX)
547549
EXPRESSION_TRAIT(__is_rvalue_expr, IsRValueExpr, KEYCXX)

clang/lib/AST/Type.cpp

+37
Original file line numberDiff line numberDiff line change
@@ -2749,6 +2749,43 @@ bool QualType::isTriviallyCopyableType(const ASTContext &Context) const {
27492749
/*IsCopyConstructible=*/false);
27502750
}
27512751

2752+
// FIXME: each call will trigger a full computation, cache the result.
2753+
bool QualType::isBitwiseCloneableType(const ASTContext &Context) const {
2754+
auto CanonicalType = getCanonicalType();
2755+
if (CanonicalType.hasNonTrivialObjCLifetime())
2756+
return false;
2757+
if (CanonicalType->isArrayType())
2758+
return Context.getBaseElementType(CanonicalType)
2759+
.isBitwiseCloneableType(Context);
2760+
2761+
if (CanonicalType->isIncompleteType())
2762+
return false;
2763+
const auto *RD = CanonicalType->getAsRecordDecl(); // struct/union/class
2764+
if (!RD)
2765+
return true;
2766+
2767+
// Never allow memcpy when we're adding poisoned padding bits to the struct.
2768+
// Accessing these posioned bits will trigger false alarms on
2769+
// SanitizeAddressFieldPadding etc.
2770+
if (RD->mayInsertExtraPadding())
2771+
return false;
2772+
2773+
for (auto *const Field : RD->fields()) {
2774+
if (!Field->getType().isBitwiseCloneableType(Context))
2775+
return false;
2776+
}
2777+
2778+
if (const auto *CXXRD = dyn_cast<CXXRecordDecl>(RD)) {
2779+
for (auto Base : CXXRD->bases())
2780+
if (!Base.getType().isBitwiseCloneableType(Context))
2781+
return false;
2782+
for (auto VBase : CXXRD->vbases())
2783+
if (!VBase.getType().isBitwiseCloneableType(Context))
2784+
return false;
2785+
}
2786+
return true;
2787+
}
2788+
27522789
bool QualType::isTriviallyCopyConstructibleType(
27532790
const ASTContext &Context) const {
27542791
return isTriviallyCopyableTypeImpl(*this, Context,

clang/lib/Sema/SemaExprCXX.cpp

+3
Original file line numberDiff line numberDiff line change
@@ -5126,6 +5126,7 @@ static bool CheckUnaryTypeTraitTypeCompleteness(Sema &S, TypeTrait UTT,
51265126
case UTT_IsStandardLayout:
51275127
case UTT_IsPOD:
51285128
case UTT_IsLiteral:
5129+
case UTT_IsBitwiseCloneable:
51295130
// By analogy, is_trivially_relocatable and is_trivially_equality_comparable
51305131
// impose the same constraints.
51315132
case UTT_IsTriviallyRelocatable:
@@ -5619,6 +5620,8 @@ static bool EvaluateUnaryTypeTrait(Sema &Self, TypeTrait UTT,
56195620
return C.hasUniqueObjectRepresentations(T);
56205621
case UTT_IsTriviallyRelocatable:
56215622
return T.isTriviallyRelocatableType(C);
5623+
case UTT_IsBitwiseCloneable:
5624+
return T.isBitwiseCloneableType(C);
56225625
case UTT_IsReferenceable:
56235626
return T.isReferenceable();
56245627
case UTT_CanPassInRegs:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// RUN: %clang_cc1 -triple x86_64-unknown-linux -DSANITIZER_ENABLED -fsanitize=address -fsanitize-address-field-padding=1 %s
2+
// RUN: %clang_cc1 -triple x86_64-unknown-linux %s
3+
4+
struct S {
5+
~S() {}
6+
virtual void foo() {}
7+
8+
int buffer[1];
9+
int other_field = 0;
10+
};
11+
12+
union U {
13+
S s;
14+
};
15+
16+
struct Derived : S {};
17+
18+
static_assert(!__is_trivially_copyable(S));
19+
#ifdef SANITIZER_ENABLED
20+
// Don't allow memcpy when the struct has poisoned padding bits.
21+
// The sanitizer adds posion padding bits to struct S.
22+
static_assert(sizeof(S) > 16);
23+
static_assert(!__is_bitwise_cloneable(S));
24+
static_assert(sizeof(U) == sizeof(S)); // no padding bit for U.
25+
static_assert(!__is_bitwise_cloneable(U));
26+
static_assert(!__is_bitwise_cloneable(S[2]));
27+
static_assert(!__is_bitwise_cloneable(Derived));
28+
#else
29+
static_assert(sizeof(S) == 16);
30+
static_assert(__is_bitwise_cloneable(S));
31+
static_assert(__is_bitwise_cloneable(U));
32+
static_assert(__is_bitwise_cloneable(S[2]));
33+
static_assert(__is_bitwise_cloneable(Derived));
34+
#endif
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// RUN: %clang_cc1 -fsyntax-only -verify %s
2+
//
3+
struct DynamicClass { virtual int Foo(); };
4+
static_assert(!__is_trivially_copyable(DynamicClass));
5+
static_assert(__is_bitwise_cloneable(DynamicClass));
6+
7+
struct InComplete; // expected-note{{forward declaration}}
8+
static_assert(!__is_bitwise_cloneable(InComplete)); // expected-error{{incomplete type 'InComplete' used in type trait expression}}

clang/test/SemaObjCXX/arc-type-traits.mm

+9
Original file line numberDiff line numberDiff line change
@@ -221,3 +221,12 @@
221221
TRAIT_IS_TRUE(__is_trivially_relocatable, HasStrong);
222222
TRAIT_IS_FALSE(__is_trivially_relocatable, HasWeak);
223223
TRAIT_IS_TRUE(__is_trivially_relocatable, HasUnsafeUnretained);
224+
225+
// __is_bitwise_cloneable
226+
TRAIT_IS_FALSE(__is_bitwise_cloneable, __strong id);
227+
TRAIT_IS_FALSE(__is_bitwise_cloneable, __weak id);
228+
TRAIT_IS_FALSE(__is_bitwise_cloneable, __autoreleasing id);
229+
TRAIT_IS_TRUE(__is_trivial, __unsafe_unretained id);
230+
TRAIT_IS_FALSE(__is_bitwise_cloneable, HasStrong);
231+
TRAIT_IS_FALSE(__is_bitwise_cloneable, HasWeak);
232+
TRAIT_IS_TRUE(__is_bitwise_cloneable, HasUnsafeUnretained);

0 commit comments

Comments
 (0)