-
Notifications
You must be signed in to change notification settings - Fork 13.6k
[Clang] Detect bit copies that should be relocation. #139104
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
base: main
Are you sure you want to change the base?
Conversation
We already warn when memcpy/memmove involves a non-trivially-copyable type. Although we only warn when the target (not the source) is not trivially copyable. As we moved to deprecate the old is __is_trivially_relocatable trait, it's useful to help users migrating away from memcpy. Note that currently __builtin_trivially_relocate and memmove have the same effect, but that will change as ptrauth learn to handle relocatable types, we add preconditions (ubsan), etc.
@llvm/pr-subscribers-clang Author: cor3ntin (cor3ntin) ChangesWe already warn when memcpy/memmove involves a non-trivially-copyable type. Although we only warn when the target (not the source) is not trivially copyable. As we moved to deprecate the old is __is_trivially_relocatable trait, it's useful to help users migrating away from memcpy. Note that currently __builtin_trivially_relocate and memmove have the same effect, but that will change as ptrauth learn to handle relocatable types, we add preconditions (ubsan), etc. Full diff: https://github.com/llvm/llvm-project/pull/139104.diff 3 Files Affected:
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index e1b9ed0647bb9..65315fe0d38d7 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -853,6 +853,13 @@ def warn_cxxstruct_memaccess : Warning<
"first argument in call to "
"%0 is a pointer to non-trivially copyable type %1">,
InGroup<NonTrivialMemcall>;
+
+def warn_cxxstruct_memaccess_relocatable
+ : Warning<"calling %0 with a pointer to a type %1 which is trivially "
+ "relocatable "
+ "but not trivially copyable; did you mean to call %2?">,
+ InGroup<NonTrivialMemcall>;
+
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<
diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index 5a0cec3d112db..98aaa51dd3462 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -9659,6 +9659,42 @@ void Sema::CheckMemaccessArguments(const CallExpr *Call,
if (BId == Builtin::BIbzero && !FirstArgTy->getAs<PointerType>())
return;
+ // Try to detect a relocation operation
+ if (getLangOpts().CPlusPlus &&
+ (BId == Builtin::BImemmove || BId == Builtin::BImemcpy)) {
+ const Expr *Dest = Call->getArg(0)->IgnoreParenImpCasts();
+ const Expr *Src = Call->getArg(1)->IgnoreParenImpCasts();
+ QualType DestTy = Dest->getType();
+ QualType SrcTy = Src->getType();
+
+ QualType DestPointeeTy = DestTy->getPointeeType();
+ QualType SrcPointeeTy = SrcTy->getPointeeType();
+ bool HasSameTargetAndSource =
+ !DestPointeeTy.isNull() && !SrcPointeeTy.isNull() &&
+ Context.hasSameUnqualifiedType(DestPointeeTy, SrcPointeeTy);
+
+ if (HasSameTargetAndSource &&
+ !DestPointeeTy.getUnqualifiedType()->isIncompleteType() &&
+ !DestPointeeTy.isConstQualified() && !SrcPointeeTy.isConstQualified() &&
+ !DestPointeeTy.isTriviallyCopyableType(getASTContext()) &&
+ SemaRef.IsCXXTriviallyRelocatableType(DestPointeeTy)) {
+
+ bool SuggestStd = getLangOpts().CPlusPlus26 && getStdNamespace();
+ if (const Decl *D = Call->getReferencedDeclOfCallee();
+ D && !D->isInStdNamespace())
+ SuggestStd = false;
+
+ StringRef Replacement = SuggestStd ? "std::trivially_relocate"
+ : "__builtin_trivially_relocate";
+
+ DiagRuntimeBehavior(Dest->getExprLoc(), Dest,
+ PDiag(diag::warn_cxxstruct_memaccess_relocatable)
+ << Call->getCallee()->getSourceRange() << FnName
+ << DestPointeeTy << Replacement);
+ return;
+ }
+ }
+
for (unsigned ArgIdx = 0; ArgIdx != LastArg; ++ArgIdx) {
const Expr *Dest = Call->getArg(ArgIdx)->IgnoreParenImpCasts();
SourceRange ArgRange = Call->getArg(ArgIdx)->getSourceRange();
diff --git a/clang/test/SemaCXX/warn-memaccess.cpp b/clang/test/SemaCXX/warn-memaccess.cpp
index 2e60539b3e48e..20113170b9e93 100644
--- a/clang/test/SemaCXX/warn-memaccess.cpp
+++ b/clang/test/SemaCXX/warn-memaccess.cpp
@@ -1,4 +1,5 @@
-// RUN: %clang_cc1 -fsyntax-only -verify -std=c++11 -Wnontrivial-memcall %s
+// RUN: %clang_cc1 -fsyntax-only -std=c++11 -verify=expected,cxx11 -Wnontrivial-memcall %s
+// RUN: %clang_cc1 -fsyntax-only -std=c++2c -verify=expected,cxx26 -Wnontrivial-memcall %s
extern "C" void *bzero(void *, unsigned);
extern "C" void *memset(void *, int, unsigned);
@@ -8,6 +9,9 @@ extern "C" void *memcpy(void *s1, const void *s2, unsigned n);
class TriviallyCopyable {};
class NonTriviallyCopyable { NonTriviallyCopyable(const NonTriviallyCopyable&);};
struct Incomplete;
+class Relocatable {
+ virtual void f();
+};
void test_bzero(TriviallyCopyable* tc,
NonTriviallyCopyable *ntc,
@@ -83,3 +87,34 @@ void test_memmove(TriviallyCopyable* tc0, TriviallyCopyable* tc1,
// OK
memmove((void*)ntc0, (void*)ntc1, sizeof(*ntc0));
}
+
+
+void test_possible_relocation(Relocatable* r, const Relocatable* r2) {
+ // expected-warning@+1 {{calling 'memcpy' with a pointer to a type 'Relocatable' which is trivially relocatable but not trivially copyable; did you mean to call __builtin_trivially_relocate?}}
+ memcpy(r, r, sizeof(*r));
+ // expected-warning@+1 {{calling 'memmove' with a pointer to a type 'Relocatable' which is trivially relocatable but not trivially copyable; did you mean to call __builtin_trivially_relocate?}}
+ memmove(r, r, sizeof(*r));
+
+ // expected-warning@+2{{destination for this 'memcpy' call is a pointer to dynamic class 'Relocatable'}}
+ // expected-note@+1{{explicitly cast the pointer to silence this warning}}
+ memcpy(r, r2, sizeof(*r));
+
+ // expected-warning@+2{{destination for this 'memmove' call is a pointer to dynamic class 'Relocatable'}}
+ // expected-note@+1{{explicitly cast the pointer to silence this warning}}
+ memmove(r, r2, sizeof(*r));
+}
+
+namespace std {
+ using ::memcpy;
+ using ::memmove;
+};
+
+
+void test_possible_relocation_std(Relocatable* r, const Relocatable* r2) {
+ // cxx11-warning@+2 {{calling 'memcpy' with a pointer to a type 'Relocatable' which is trivially relocatable but not trivially copyable; did you mean to call __builtin_trivially_relocate?}}
+ // cxx26-warning@+1 {{calling 'memcpy' with a pointer to a type 'Relocatable' which is trivially relocatable but not trivially copyable; did you mean to call std::trivially_relocate?}}
+ std::memcpy(r, r, sizeof(*r));
+ // cxx11-warning@+2 {{calling 'memmove' with a pointer to a type 'Relocatable' which is trivially relocatable but not trivially copyable; did you mean to call __builtin_trivially_relocate?}}
+ // cxx26-warning@+1 {{calling 'memmove' with a pointer to a type 'Relocatable' which is trivially relocatable but not trivially copyable; did you mean to call std::trivially_relocate?}}
+ std::memmove(r, r, sizeof(*r));
+}
|
clang/lib/Sema/SemaChecking.cpp
Outdated
if (const Decl *D = Call->getReferencedDeclOfCallee(); | ||
D && !D->isInStdNamespace()) | ||
SuggestStd = false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So if I’m reading this correctly this will cause us to suggest std::trivially_relocate
if the user called std::memcpy
, and __builtin_trivially_relocate
if they called some other memcpy
that isn’t defined in the std
namespace. Is there a reason why we wouldn’t just always suggest std::trivially_relocate
if C++26 is enabled (or is this because the header providing that function might not be included which could potentially be confusing if someone then uses std::trivially_relocate
but then we start complaining about it not being found?).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, the header might not be included.
I don't think we would have a way to generally detect a function exists in the STL
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, the header might not be included. I don't think we would have a way to generally detect a function exists in the STL
Yeah, I don’t think we need to care about that because there is no good way of doing that (and presumably we’d end up suggesting ‘include this header to get it’ anyway like we do w/ other functions which is fine imo). But in that case, imo we can just always suggest std::trivially_relocate
in C++26 mode even if the memcpy
we’re calling isn’t in the std
namespace.
def warn_cxxstruct_memaccess_relocatable | ||
: Warning<"calling %0 with a pointer to a type %1 which is trivially " | ||
"relocatable " | ||
"but not trivially copyable; did you mean to call %2?">, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"but not trivially copyable; did you mean to call %2?">, | |
"but not trivially copyable; did you mean to call '%2'?">, |
This one needs to be quoted manually because it’s just a StringRef
.
Also, should this come w/ a release note? Or does this just count as part of adding support for trivial relocation? |
Yup, this is "implement trivial relocation". There are more PRs coming! |
We already warn when memcpy/memmove involves a non-trivially-copyable type. Although we only warn when the target (not the source) is not trivially copyable.
As we moved to deprecate the old is __is_trivially_relocatable trait, it's useful to help users migrating away from memcpy.
Note that currently __builtin_trivially_relocate and memmove have the same effect, but that will change as ptrauth learn to handle relocatable types, we add preconditions (ubsan), etc.