Skip to content

[clang] Warn about memset/memcpy to NonTriviallyCopyable types #111434

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
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
5 changes: 5 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,11 @@ Modified Compiler Flags
to utilize these vector libraries. The behavior for all other vector function
libraries remains unchanged.

- The ``-Wnontrivial-memaccess`` warning has been updated to also warn about
passing non-trivially-copyable destrination parameter to ``memcpy``,
``memset`` and similar functions for which it is a documented undefined
behavior.

Removed Compiler Flags
-------------------------

Expand Down
4 changes: 4 additions & 0 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -795,6 +795,10 @@ def warn_cstruct_memaccess : Warning<
"%1 call is a pointer to record %2 that is not trivial to "
"%select{primitive-default-initialize|primitive-copy}3">,
InGroup<NonTrivialMemaccess>;
def warn_cxxstruct_memaccess : Warning<
"first argument in call to "
"%0 is a pointer to non-trivially copyable type %1">,
InGroup<NonTrivialMemaccess>;
def note_nontrivial_field : Note<
"field is non-trivial to %select{copy|default-initialize}0">;
def err_non_trivial_c_union_in_invalid_context : Error<
Expand Down
18 changes: 18 additions & 0 deletions clang/lib/Sema/SemaChecking.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8899,18 +8899,36 @@ void Sema::CheckMemaccessArguments(const CallExpr *Call,
<< ArgIdx << FnName << PointeeTy
<< Call->getCallee()->getSourceRange());
else if (const auto *RT = PointeeTy->getAs<RecordType>()) {

bool IsTriviallyCopyableCXXRecord =
RT->desugar().isTriviallyCopyableType(Context);

if ((BId == Builtin::BImemset || BId == Builtin::BIbzero) &&
RT->getDecl()->isNonTrivialToPrimitiveDefaultInitialize()) {
DiagRuntimeBehavior(Dest->getExprLoc(), Dest,
PDiag(diag::warn_cstruct_memaccess)
<< ArgIdx << FnName << PointeeTy << 0);
SearchNonTrivialToInitializeField::diag(PointeeTy, Dest, *this);
} else if ((BId == Builtin::BImemset || BId == Builtin::BIbzero) &&
!IsTriviallyCopyableCXXRecord && ArgIdx == 0) {
// FIXME: Limiting this warning to dest argument until we decide
// whether it's valid for source argument too.
DiagRuntimeBehavior(Dest->getExprLoc(), Dest,
PDiag(diag::warn_cxxstruct_memaccess)
<< FnName << PointeeTy);
} else if ((BId == Builtin::BImemcpy || BId == Builtin::BImemmove) &&
RT->getDecl()->isNonTrivialToPrimitiveCopy()) {
DiagRuntimeBehavior(Dest->getExprLoc(), Dest,
PDiag(diag::warn_cstruct_memaccess)
<< ArgIdx << FnName << PointeeTy << 1);
SearchNonTrivialToCopyField::diag(PointeeTy, Dest, *this);
} else if ((BId == Builtin::BImemcpy || BId == Builtin::BImemmove) &&
!IsTriviallyCopyableCXXRecord && ArgIdx == 0) {
// FIXME: Limiting this warning to dest argument until we decide
// whether it's valid for source argument too.
DiagRuntimeBehavior(Dest->getExprLoc(), Dest,
PDiag(diag::warn_cxxstruct_memaccess)
<< FnName << PointeeTy);
} else {
continue;
}
Expand Down
2 changes: 2 additions & 0 deletions clang/test/SemaCXX/constexpr-string.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -670,6 +670,8 @@ namespace MemcpyEtc {
constexpr bool test_address_of_incomplete_struct_type() { // expected-error {{never produces a constant}}
struct Incomplete;
extern Incomplete x, y;
// expected-warning@+2 {{first argument in call to '__builtin_memcpy' is a pointer to non-trivially copyable type 'Incomplete'}}
// expected-note@+1 {{explicitly cast the pointer to silence this warning}}
__builtin_memcpy(&x, &x, 4);
// expected-note@-1 2{{cannot constant evaluate 'memcpy' between objects of incomplete type 'Incomplete'}}
return true;
Expand Down
68 changes: 68 additions & 0 deletions clang/test/SemaCXX/warn-memaccess.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// RUN: %clang_cc1 -fsyntax-only -verify -std=c++11 -Wnontrivial-memaccess %s

extern "C" void *bzero(void *, unsigned);
extern "C" void *memset(void *, int, unsigned);
extern "C" void *memmove(void *s1, const void *s2, unsigned n);
extern "C" void *memcpy(void *s1, const void *s2, unsigned n);

class TriviallyCopyable {};
class NonTriviallyCopyable { NonTriviallyCopyable(const NonTriviallyCopyable&);};

void test_bzero(TriviallyCopyable* tc,
NonTriviallyCopyable *ntc) {
// OK
bzero(tc, sizeof(*tc));

// expected-warning@+2{{first argument in call to 'bzero' is a pointer to non-trivially copyable type 'NonTriviallyCopyable'}}
// expected-note@+1{{explicitly cast the pointer to silence this warning}}
bzero(ntc, sizeof(*ntc));

// OK
bzero((void*)ntc, sizeof(*ntc));
}

void test_memset(TriviallyCopyable* tc,
NonTriviallyCopyable *ntc) {
// OK
memset(tc, 0, sizeof(*tc));

// expected-warning@+2{{first argument in call to 'memset' is a pointer to non-trivially copyable type 'NonTriviallyCopyable'}}
// expected-note@+1{{explicitly cast the pointer to silence this warning}}
memset(ntc, 0, sizeof(*ntc));

// OK
memset((void*)ntc, 0, sizeof(*ntc));
}


void test_memcpy(TriviallyCopyable* tc0, TriviallyCopyable* tc1,
NonTriviallyCopyable *ntc0, NonTriviallyCopyable *ntc1) {
// OK
memcpy(tc0, tc1, sizeof(*tc0));

// expected-warning@+2{{first argument in call to 'memcpy' is a pointer to non-trivially copyable type 'NonTriviallyCopyable'}}
// expected-note@+1{{explicitly cast the pointer to silence this warning}}
memcpy(ntc0, ntc1, sizeof(*ntc0));

// ~ OK
memcpy((void*)ntc0, ntc1, sizeof(*ntc0));

// OK
memcpy((void*)ntc0, (void*)ntc1, sizeof(*ntc0));
}

void test_memmove(TriviallyCopyable* tc0, TriviallyCopyable* tc1,
NonTriviallyCopyable *ntc0, NonTriviallyCopyable *ntc1) {
// OK
memmove(tc0, tc1, sizeof(*tc0));

// expected-warning@+2{{first argument in call to 'memmove' is a pointer to non-trivially copyable type 'NonTriviallyCopyable'}}
// expected-note@+1{{explicitly cast the pointer to silence this warning}}
memmove(ntc0, ntc1, sizeof(*ntc0));

// ~ OK
memmove((void*)ntc0, ntc1, sizeof(*ntc0));

// OK
memmove((void*)ntc0, (void*)ntc1, sizeof(*ntc0));
}
3 changes: 2 additions & 1 deletion libcxx/include/__memory/uninitialized_algorithms.h
Original file line number Diff line number Diff line change
Expand Up @@ -638,7 +638,8 @@ __uninitialized_allocator_relocate(_Alloc& __alloc, _Tp* __first, _Tp* __last, _
__guard.__complete();
std::__allocator_destroy(__alloc, __first, __last);
} else {
__builtin_memcpy(__result, __first, sizeof(_Tp) * (__last - __first));
// Casting to void* to suppress clang complaining that this is technically UB.
__builtin_memcpy(static_cast<void*>(__result), __first, sizeof(_Tp) * (__last - __first));
}
}

Expand Down
6 changes: 3 additions & 3 deletions libcxx/test/std/utilities/expected/types.h
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ template <int Constant>
struct TailClobberer {
constexpr TailClobberer() noexcept {
if (!std::is_constant_evaluated()) {
std::memset(this, Constant, sizeof(*this));
std::memset(static_cast<void*>(this), Constant, sizeof(*this));
}
// Always set `b` itself to `false` so that the comparison works.
b = false;
Expand Down Expand Up @@ -245,7 +245,7 @@ struct BoolWithPadding {
constexpr explicit BoolWithPadding() noexcept : BoolWithPadding(false) {}
constexpr BoolWithPadding(bool val) noexcept {
if (!std::is_constant_evaluated()) {
std::memset(this, 0, sizeof(*this));
std::memset(static_cast<void*>(this), 0, sizeof(*this));
}
val_ = val;
}
Expand All @@ -268,7 +268,7 @@ struct IntWithoutPadding {
constexpr explicit IntWithoutPadding() noexcept : IntWithoutPadding(0) {}
constexpr IntWithoutPadding(int val) noexcept {
if (!std::is_constant_evaluated()) {
std::memset(this, 0, sizeof(*this));
std::memset(static_cast<void*>(this), 0, sizeof(*this));
}
val_ = val;
}
Expand Down
4 changes: 2 additions & 2 deletions libcxx/test/support/min_allocator.h
Original file line number Diff line number Diff line change
Expand Up @@ -465,14 +465,14 @@ class safe_allocator {
TEST_CONSTEXPR_CXX20 T* allocate(std::size_t n) {
T* memory = std::allocator<T>().allocate(n);
if (!TEST_IS_CONSTANT_EVALUATED)
std::memset(memory, 0, sizeof(T) * n);
std::memset(static_cast<void*>(memory), 0, sizeof(T) * n);

return memory;
}

TEST_CONSTEXPR_CXX20 void deallocate(T* p, std::size_t n) {
if (!TEST_IS_CONSTANT_EVALUATED)
DoNotOptimize(std::memset(p, 0, sizeof(T) * n));
DoNotOptimize(std::memset(static_cast<void*>(p), 0, sizeof(T) * n));
std::allocator<T>().deallocate(p, n);
}

Expand Down
Loading