Skip to content

Commit df45524

Browse files
[clang] Warn about memset/memcpy to NonTriviallyCopyable types
This implements a warning that's similar to what GCC does in that context: both memcpy and memset require their first and second operand to be trivially copyable, let's warn if that's not the case.
1 parent 42ec740 commit df45524

File tree

5 files changed

+98
-0
lines changed

5 files changed

+98
-0
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,10 @@ Modified Compiler Flags
266266
the ``promoted`` algorithm for complex division when possible rather than the
267267
less basic (limited range) algorithm.
268268

269+
- The ``-Wnontrivial-memaccess`` warning has been updated to also warn about
270+
passing non-trivially-copyable parameter to ``memcpy``, ``memset`` and similar
271+
functions for which it is a documented undefined behavior.
272+
269273
Removed Compiler Flags
270274
-------------------------
271275

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -790,6 +790,10 @@ def warn_cstruct_memaccess : Warning<
790790
"%1 call is a pointer to record %2 that is not trivial to "
791791
"%select{primitive-default-initialize|primitive-copy}3">,
792792
InGroup<NonTrivialMemaccess>;
793+
def warn_cxxstruct_memaccess : Warning<
794+
"%select{destination for|source of|first operand of|second operand of}0 call to "
795+
"%1 is a pointer to non-trivially copyable type %2">,
796+
InGroup<NonTrivialMemaccess>;
793797
def note_nontrivial_field : Note<
794798
"field is non-trivial to %select{copy|default-initialize}0">;
795799
def err_non_trivial_c_union_in_invalid_context : Error<

clang/lib/Sema/SemaChecking.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8899,18 +8899,32 @@ void Sema::CheckMemaccessArguments(const CallExpr *Call,
88998899
<< ArgIdx << FnName << PointeeTy
89008900
<< Call->getCallee()->getSourceRange());
89018901
else if (const auto *RT = PointeeTy->getAs<RecordType>()) {
8902+
8903+
bool IsTriviallyCopyableCXXRecord =
8904+
RT->desugar().isTriviallyCopyableType(Context);
8905+
89028906
if ((BId == Builtin::BImemset || BId == Builtin::BIbzero) &&
89038907
RT->getDecl()->isNonTrivialToPrimitiveDefaultInitialize()) {
89048908
DiagRuntimeBehavior(Dest->getExprLoc(), Dest,
89058909
PDiag(diag::warn_cstruct_memaccess)
89068910
<< ArgIdx << FnName << PointeeTy << 0);
89078911
SearchNonTrivialToInitializeField::diag(PointeeTy, Dest, *this);
8912+
} else if ((BId == Builtin::BImemset || BId == Builtin::BIbzero) &&
8913+
!IsTriviallyCopyableCXXRecord) {
8914+
DiagRuntimeBehavior(Dest->getExprLoc(), Dest,
8915+
PDiag(diag::warn_cxxstruct_memaccess)
8916+
<< ArgIdx << FnName << PointeeTy);
89088917
} else if ((BId == Builtin::BImemcpy || BId == Builtin::BImemmove) &&
89098918
RT->getDecl()->isNonTrivialToPrimitiveCopy()) {
89108919
DiagRuntimeBehavior(Dest->getExprLoc(), Dest,
89118920
PDiag(diag::warn_cstruct_memaccess)
89128921
<< ArgIdx << FnName << PointeeTy << 1);
89138922
SearchNonTrivialToCopyField::diag(PointeeTy, Dest, *this);
8923+
} else if ((BId == Builtin::BImemcpy || BId == Builtin::BImemmove) &&
8924+
!IsTriviallyCopyableCXXRecord) {
8925+
DiagRuntimeBehavior(Dest->getExprLoc(), Dest,
8926+
PDiag(diag::warn_cxxstruct_memaccess)
8927+
<< ArgIdx << FnName << PointeeTy);
89148928
} else {
89158929
continue;
89168930
}

clang/test/SemaCXX/constexpr-string.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -603,12 +603,16 @@ namespace MemcpyEtc {
603603
};
604604
constexpr bool test_nontrivial_memcpy() { // expected-error {{never produces a constant}}
605605
NonTrivial arr[3] = {};
606+
// expected-warning@+2 {{source of call to '__builtin_memcpy' is a pointer to non-trivially copyable type 'NonTrivial'}}
607+
// expected-note@+1 {{explicitly cast the pointer to silence this warning}}
606608
__builtin_memcpy(arr, arr + 1, sizeof(NonTrivial)); // expected-note 2{{non-trivially-copyable}}
607609
return true;
608610
}
609611
static_assert(test_nontrivial_memcpy()); // expected-error {{constant}} expected-note {{in call}}
610612
constexpr bool test_nontrivial_memmove() { // expected-error {{never produces a constant}}
611613
NonTrivial arr[3] = {};
614+
// expected-warning@+2 {{source of call to '__builtin_memcpy' is a pointer to non-trivially copyable type 'NonTrivial'}}
615+
// expected-note@+1 {{explicitly cast the pointer to silence this warning}}
612616
__builtin_memcpy(arr, arr + 1, sizeof(NonTrivial)); // expected-note 2{{non-trivially-copyable}}
613617
return true;
614618
}
@@ -670,6 +674,8 @@ namespace MemcpyEtc {
670674
constexpr bool test_address_of_incomplete_struct_type() { // expected-error {{never produces a constant}}
671675
struct Incomplete;
672676
extern Incomplete x, y;
677+
// expected-warning@+2 {{destination for call to '__builtin_memcpy' is a pointer to non-trivially copyable type 'Incomplete'}}
678+
// expected-note@+1 {{explicitly cast the pointer to silence this warning}}
673679
__builtin_memcpy(&x, &x, 4);
674680
// expected-note@-1 2{{cannot constant evaluate 'memcpy' between objects of incomplete type 'Incomplete'}}
675681
return true;
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// RUN: %clang_cc1 -fsyntax-only -verify -std=c++11 -Wnontrivial-memaccess %s
2+
3+
extern "C" void *bzero(void *, unsigned);
4+
extern "C" void *memset(void *, int, unsigned);
5+
extern "C" void *memmove(void *s1, const void *s2, unsigned n);
6+
extern "C" void *memcpy(void *s1, const void *s2, unsigned n);
7+
8+
class TriviallyCopyable {};
9+
class NonTriviallyCopyable { NonTriviallyCopyable(const NonTriviallyCopyable&);};
10+
11+
void test_bzero(TriviallyCopyable* tc,
12+
NonTriviallyCopyable *ntc) {
13+
// OK
14+
bzero(tc, sizeof(*tc));
15+
16+
// expected-warning@+2{{destination for call to 'bzero' is a pointer to non-trivially copyable type 'NonTriviallyCopyable'}}
17+
// expected-note@+1{{explicitly cast the pointer to silence this warning}}
18+
bzero(ntc, sizeof(*ntc));
19+
20+
// OK
21+
bzero((void*)ntc, sizeof(*ntc));
22+
}
23+
24+
void test_memset(TriviallyCopyable* tc,
25+
NonTriviallyCopyable *ntc) {
26+
// OK
27+
memset(tc, 0, sizeof(*tc));
28+
29+
// expected-warning@+2{{destination for call to 'memset' is a pointer to non-trivially copyable type 'NonTriviallyCopyable'}}
30+
// expected-note@+1{{explicitly cast the pointer to silence this warning}}
31+
memset(ntc, 0, sizeof(*ntc));
32+
33+
// OK
34+
memset((void*)ntc, 0, sizeof(*ntc));
35+
}
36+
37+
38+
void test_memcpy(TriviallyCopyable* tc0, TriviallyCopyable* tc1,
39+
NonTriviallyCopyable *ntc0, NonTriviallyCopyable *ntc1) {
40+
// OK
41+
memcpy(tc0, tc1, sizeof(*tc0));
42+
43+
// expected-warning@+2{{destination for call to 'memcpy' is a pointer to non-trivially copyable type 'NonTriviallyCopyable'}}
44+
// expected-note@+1{{explicitly cast the pointer to silence this warning}}
45+
memcpy(ntc0, ntc1, sizeof(*ntc0));
46+
47+
// expected-warning@+2{{source of call to 'memcpy' is a pointer to non-trivially copyable type 'NonTriviallyCopyable'}}
48+
// expected-note@+1{{explicitly cast the pointer to silence this warning}}
49+
memcpy((void*)ntc0, ntc1, sizeof(*ntc0));
50+
51+
// OK
52+
memcpy((void*)ntc0, (void*)ntc1, sizeof(*ntc0));
53+
}
54+
55+
void test_memmove(TriviallyCopyable* tc0, TriviallyCopyable* tc1,
56+
NonTriviallyCopyable *ntc0, NonTriviallyCopyable *ntc1) {
57+
// OK
58+
memmove(tc0, tc1, sizeof(*tc0));
59+
60+
// expected-warning@+2{{destination for call to 'memmove' is a pointer to non-trivially copyable type 'NonTriviallyCopyable'}}
61+
// expected-note@+1{{explicitly cast the pointer to silence this warning}}
62+
memmove(ntc0, ntc1, sizeof(*ntc0));
63+
64+
// expected-warning@+2{{source of call to 'memmove' is a pointer to non-trivially copyable type 'NonTriviallyCopyable'}}
65+
// expected-note@+1{{explicitly cast the pointer to silence this warning}}
66+
memmove((void*)ntc0, ntc1, sizeof(*ntc0));
67+
68+
// OK
69+
memmove((void*)ntc0, (void*)ntc1, sizeof(*ntc0));
70+
}

0 commit comments

Comments
 (0)