[clang] [C] Add (new) -Wimplicit-void-ptr-cast to -Wc++-compat (PR #136855)
Aaron Ballman via cfe-commits
cfe-commits at lists.llvm.org
Wed Apr 23 10:52:41 PDT 2025
https://github.com/AaronBallman updated https://github.com/llvm/llvm-project/pull/136855
>From 73a0a93e22976fd8ffdd5df70c459b648b7dd06d Mon Sep 17 00:00:00 2001
From: Aaron Ballman <aaron at aaronballman.com>
Date: Wed, 23 Apr 2025 07:15:42 -0400
Subject: [PATCH 1/3] Diagnose implicit void * casts under -Wc++-compat
---
.../include/clang/Basic/DiagnosticSemaKinds.td | 3 +++
clang/include/clang/Sema/Sema.h | 17 +++++++++++++++++
clang/lib/Sema/SemaDeclAttr.cpp | 4 ++--
clang/lib/Sema/SemaExpr.cpp | 14 +++++++++++---
clang/lib/Sema/SemaInit.cpp | 7 +++----
clang/lib/Sema/SemaObjC.cpp | 4 ++--
clang/lib/Sema/SemaObjCProperty.cpp | 11 ++++++-----
clang/lib/Sema/SemaOverload.cpp | 1 +
8 files changed, 45 insertions(+), 16 deletions(-)
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 45b6e1dc29980..84dc0a7206e63 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -8682,6 +8682,9 @@ def err_typecheck_missing_return_type_incompatible : Error<
"return type must match previous return type}0,1 when %select{block "
"literal|lambda expression}2 has unspecified explicit return type">;
+def warn_compatible_implicit_pointer_conv : Warning<
+ "implicit conversion from %diff{$ to $|type to incompatible type}0,1 is not "
+ "permitted in C++">, InGroup<CXXCompat>, DefaultIgnore;
def note_incomplete_class_and_qualified_id : Note<
"conformance of forward class %0 to protocol %1 cannot be confirmed">;
def warn_incompatible_qualified_id : Warning<
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 96d81e618494a..0c77c5b5ca30a 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -7786,6 +7786,11 @@ class Sema final : public SemaBase {
/// Compatible - the types are compatible according to the standard.
Compatible,
+ /// CompatibleVoidPtrToNonVoidPtr - The types are compatible in C because
+ /// a void * can implicitly convert to another pointer type, which we
+ /// differentiate for better diagnostic behavior.
+ CompatibleVoidPtrToNonVoidPtr,
+
/// PointerToInt - The assignment converts a pointer to an int, which we
/// accept as an extension.
PointerToInt,
@@ -7866,6 +7871,18 @@ class Sema final : public SemaBase {
Incompatible
};
+ bool IsAssignConvertCompatible(AssignConvertType ConvTy) {
+ switch (ConvTy) {
+ default:
+ return false;
+ case Compatible:
+ case CompatiblePointerDiscardsQualifiers:
+ case CompatibleVoidPtrToNonVoidPtr:
+ return true;
+ }
+ llvm_unreachable("impossible");
+ }
+
/// DiagnoseAssignmentResult - Emit a diagnostic, if required, for the
/// assignment conversion type specified by ConvTy. This returns true if the
/// conversion was invalid or false if the conversion was accepted.
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index 0cadbff13bdbf..642a62765d0d9 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -3589,8 +3589,8 @@ static void handleCleanupAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
// If this ever proves to be a problem it should be easy to fix.
QualType Ty = S.Context.getPointerType(cast<VarDecl>(D)->getType());
QualType ParamTy = FD->getParamDecl(0)->getType();
- if (S.CheckAssignmentConstraints(FD->getParamDecl(0)->getLocation(),
- ParamTy, Ty) != Sema::Compatible) {
+ if (!S.IsAssignConvertCompatible(S.CheckAssignmentConstraints(
+ FD->getParamDecl(0)->getLocation(), ParamTy, Ty))) {
S.Diag(Loc, diag::err_attribute_cleanup_func_arg_incompatible_type)
<< NI.getName() << ParamTy << Ty;
return;
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 01a021443c94f..8d862fd179823 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -9062,8 +9062,12 @@ checkPointerTypesForAssignment(Sema &S, QualType LHSType, QualType RHSType,
}
if (rhptee->isVoidType()) {
+ // In C, void * to another pointer type is compatible, but we want to note
+ // that there will be an implicit conversion happening here.
if (lhptee->isIncompleteOrObjectType())
- return ConvTy;
+ return ConvTy == Sema::Compatible && !S.getLangOpts().CPlusPlus
+ ? Sema::CompatibleVoidPtrToNonVoidPtr
+ : ConvTy;
// As an extension, we allow cast to/from void* to function pointer.
assert(lhptee->isFunctionType());
@@ -9098,7 +9102,7 @@ checkPointerTypesForAssignment(Sema &S, QualType LHSType, QualType RHSType,
// Types are compatible ignoring the sign. Qualifier incompatibility
// takes priority over sign incompatibility because the sign
// warning can be disabled.
- if (ConvTy != Sema::Compatible)
+ if (S.IsAssignConvertCompatible(ConvTy))
return ConvTy;
return Sema::IncompatiblePointerSign;
@@ -16979,7 +16983,11 @@ bool Sema::DiagnoseAssignmentResult(AssignConvertType ConvTy,
case Compatible:
DiagnoseAssignmentEnum(DstType, SrcType, SrcExpr);
return false;
-
+ case CompatibleVoidPtrToNonVoidPtr:
+ // Still a valid conversion, but we may want to diagnose for C++
+ // compatibility reasons.
+ DiagKind = diag::warn_compatible_implicit_pointer_conv;
+ break;
case PointerToInt:
if (getLangOpts().CPlusPlus) {
DiagKind = diag::err_typecheck_convert_pointer_int;
diff --git a/clang/lib/Sema/SemaInit.cpp b/clang/lib/Sema/SemaInit.cpp
index 0910a820438b0..f04a154bcfd5f 100644
--- a/clang/lib/Sema/SemaInit.cpp
+++ b/clang/lib/Sema/SemaInit.cpp
@@ -8328,10 +8328,9 @@ ExprResult InitializationSequence::Perform(Sema &S,
// If this is a call, allow conversion to a transparent union.
ExprResult CurInitExprRes = CurInit;
- if (ConvTy != Sema::Compatible &&
- Entity.isParameterKind() &&
- S.CheckTransparentUnionArgumentConstraints(Step->Type, CurInitExprRes)
- == Sema::Compatible)
+ if (!S.IsAssignConvertCompatible(ConvTy) && Entity.isParameterKind() &&
+ S.CheckTransparentUnionArgumentConstraints(
+ Step->Type, CurInitExprRes) == Sema::Compatible)
ConvTy = Sema::Compatible;
if (CurInitExprRes.isInvalid())
return ExprError();
diff --git a/clang/lib/Sema/SemaObjC.cpp b/clang/lib/Sema/SemaObjC.cpp
index 9b24b5f052119..eba4a7cb6010c 100644
--- a/clang/lib/Sema/SemaObjC.cpp
+++ b/clang/lib/Sema/SemaObjC.cpp
@@ -2341,8 +2341,8 @@ static void checkCollectionLiteralElement(Sema &S, QualType TargetElementType,
QualType ElementType = Element->getType();
ExprResult ElementResult(Element);
if (ElementType->getAs<ObjCObjectPointerType>() &&
- S.CheckSingleAssignmentConstraints(TargetElementType, ElementResult,
- false, false) != Sema::Compatible) {
+ !S.IsAssignConvertCompatible(S.CheckSingleAssignmentConstraints(
+ TargetElementType, ElementResult, false, false))) {
S.Diag(Element->getBeginLoc(), diag::warn_objc_collection_literal_element)
<< ElementType << ElementKind << TargetElementType
<< Element->getSourceRange();
diff --git a/clang/lib/Sema/SemaObjCProperty.cpp b/clang/lib/Sema/SemaObjCProperty.cpp
index f37982eddace9..3e962fcb8b0e5 100644
--- a/clang/lib/Sema/SemaObjCProperty.cpp
+++ b/clang/lib/Sema/SemaObjCProperty.cpp
@@ -1349,9 +1349,9 @@ Decl *SemaObjC::ActOnPropertyImplDecl(
PropertyIvarType->castAs<ObjCObjectPointerType>(),
IvarType->castAs<ObjCObjectPointerType>());
else {
- compat = (SemaRef.CheckAssignmentConstraints(
- PropertyIvarLoc, PropertyIvarType, IvarType) ==
- Sema::Compatible);
+ compat = SemaRef.IsAssignConvertCompatible(
+ SemaRef.CheckAssignmentConstraints(PropertyIvarLoc,
+ PropertyIvarType, IvarType));
}
if (!compat) {
Diag(PropertyDiagLoc, diag::err_property_ivar_type)
@@ -1702,8 +1702,9 @@ bool SemaObjC::DiagnosePropertyAccessorMismatch(ObjCPropertyDecl *property,
PropertyRValueType->getAs<ObjCObjectPointerType>()) &&
(getterObjCPtr = GetterType->getAs<ObjCObjectPointerType>()))
compat = Context.canAssignObjCInterfaces(getterObjCPtr, propertyObjCPtr);
- else if (SemaRef.CheckAssignmentConstraints(
- Loc, GetterType, PropertyRValueType) != Sema::Compatible) {
+ else if (!SemaRef.IsAssignConvertCompatible(
+ SemaRef.CheckAssignmentConstraints(Loc, GetterType,
+ PropertyRValueType))) {
Diag(Loc, diag::err_property_accessor_type)
<< property->getDeclName() << PropertyRValueType
<< GetterMethod->getSelector() << GetterType;
diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp
index 5b224b6c08fef..a2ea4bb9afe15 100644
--- a/clang/lib/Sema/SemaOverload.cpp
+++ b/clang/lib/Sema/SemaOverload.cpp
@@ -2518,6 +2518,7 @@ static bool IsStandardConversion(Sema &S, Expr* From, QualType ToType,
ImplicitConversionKind SecondConv;
switch (Conv) {
case Sema::Compatible:
+ case Sema::CompatibleVoidPtrToNonVoidPtr: // __attribute__((overloadable))
SecondConv = ICK_C_Only_Conversion;
break;
// For our purposes, discarding qualifiers is just as bad as using an
>From de78049bd55044550555971c7978f9dd71ea7afc Mon Sep 17 00:00:00 2001
From: Aaron Ballman <aaron at aaronballman.com>
Date: Wed, 23 Apr 2025 08:17:06 -0400
Subject: [PATCH 2/3] Fix behavior, add tests, add release notes
---
clang/docs/ReleaseNotes.rst | 3 ++
clang/include/clang/Basic/DiagnosticGroups.td | 3 +-
.../clang/Basic/DiagnosticSemaKinds.td | 13 +++++++--
clang/lib/Sema/SemaExpr.cpp | 2 +-
clang/test/Sema/implicit-void-ptr-cast.c | 28 +++++++++++++++++++
5 files changed, 44 insertions(+), 5 deletions(-)
create mode 100644 clang/test/Sema/implicit-void-ptr-cast.c
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index bec670e573ca6..881c1c2f8b12d 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -140,6 +140,9 @@ C Language Changes
- Clang now allows an ``inline`` specifier on a typedef declaration of a
function type in Microsoft compatibility mode. #GH124869
- Clang now allows ``restrict`` qualifier for array types with pointer elements (#GH92847).
+- Added ``-Wimplicit-void-ptr-cast``, grouped under ``-Wc++-compat``, which
+ diagnoses implicit conversion from ``void *`` to another pointer type as
+ being incompatible with C++. (#GH17792)
C2y Feature Support
^^^^^^^^^^^^^^^^^^^
diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td
index 59036b695da85..6441b8049ed8d 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -155,7 +155,8 @@ def C99Compat : DiagGroup<"c99-compat">;
def C23Compat : DiagGroup<"c23-compat">;
def : DiagGroup<"c2x-compat", [C23Compat]>;
-def CXXCompat: DiagGroup<"c++-compat">;
+def ImplicitVoidPtrCast : DiagGroup<"implicit-void-ptr-cast">;
+def CXXCompat: DiagGroup<"c++-compat", [ImplicitVoidPtrCast]>;
def ExternCCompat : DiagGroup<"extern-c-compat">;
def KeywordCompat : DiagGroup<"keyword-compat">;
def GNUCaseRange : DiagGroup<"gnu-case-range">;
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index a43ab2f03f1a0..8ff170520aafe 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -8687,10 +8687,17 @@ def err_typecheck_missing_return_type_incompatible : Error<
"%diff{return type $ must match previous return type $|"
"return type must match previous return type}0,1 when %select{block "
"literal|lambda expression}2 has unspecified explicit return type">;
-
def warn_compatible_implicit_pointer_conv : Warning<
- "implicit conversion from %diff{$ to $|type to incompatible type}0,1 is not "
- "permitted in C++">, InGroup<CXXCompat>, DefaultIgnore;
+ "implicit conversion when %select{"
+ "%diff{assigning to $ from type $|assigning to type from type}0,1|"
+ "%diff{passing $ to parameter of type $|passing type to parameter of type}0,1|"
+ "%diff{returning $ from a function with result type $|returning type from a function with result type}0,1|"
+ "<CLANG BUG IF YOU SEE THIS>|" // converting
+ "%diff{initializing $ with an expression of type $|initializing type with an expression of type}0,1|"
+ "%diff{sending $ to parameter of type $|sending type to parameter of type}0,1|"
+ "<CLANG BUG IF YOU SEE THIS>" // casting
+ "}2 is not permitted in C++">,
+ InGroup<ImplicitVoidPtrCast>, DefaultIgnore;
def note_incomplete_class_and_qualified_id : Note<
"conformance of forward class %0 to protocol %1 cannot be confirmed">;
def warn_incompatible_qualified_id : Warning<
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 4f09253450083..252d93e66a7e8 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -9102,7 +9102,7 @@ checkPointerTypesForAssignment(Sema &S, QualType LHSType, QualType RHSType,
// Types are compatible ignoring the sign. Qualifier incompatibility
// takes priority over sign incompatibility because the sign
// warning can be disabled.
- if (S.IsAssignConvertCompatible(ConvTy))
+ if (!S.IsAssignConvertCompatible(ConvTy))
return ConvTy;
return Sema::IncompatiblePointerSign;
diff --git a/clang/test/Sema/implicit-void-ptr-cast.c b/clang/test/Sema/implicit-void-ptr-cast.c
new file mode 100644
index 0000000000000..bf981c51e724e
--- /dev/null
+++ b/clang/test/Sema/implicit-void-ptr-cast.c
@@ -0,0 +1,28 @@
+// RUN: %clang_cc1 -fsyntax-only -verify -Wimplicit-void-ptr-cast %s
+// RUN: %clang_cc1 -fsyntax-only -verify -Wc++-compat %s
+// RUN: %clang_cc1 -fsyntax-only -verify=good %s
+// RUN: %clang_cc1 -fsyntax-only -verify=good -Wc++-compat -Wno-implicit-void-ptr-cast %s
+// good-no-diagnostics
+
+typedef __typeof__(sizeof(int)) size_t;
+extern void *malloc(size_t);
+
+void func(int *); // expected-note {{passing argument to parameter here}}
+
+void test(void) {
+ int *x = malloc(sizeof(char)); // expected-warning {{implicit conversion when initializing 'int *' with an expression of type 'void *' is not permitted in C++}}
+ x = malloc(sizeof(char)); // expected-warning {{implicit conversion when assigning to 'int *' from type 'void *' is not permitted in C++}}
+ func(malloc(sizeof(char))); // expected-warning {{implicit conversion when passing 'void *' to parameter of type 'int *' is not permitted in C++}}
+ x = (int *)malloc(sizeof(char));
+
+ void *vp = 0;
+ x = vp; // expected-warning {{implicit conversion when assigning to 'int *' from type 'void *' is not permitted in C++}}
+ vp = vp;
+
+ x = (void *)malloc(sizeof(char)); // expected-warning {{implicit conversion when assigning to 'int *' from type 'void *' is not permitted in C++}}
+ const int *y = vp; // expected-warning {{implicit conversion when initializing 'const int *' with an expression of type 'void *' is not permitted in C++}}
+}
+
+int *other_func(void *ptr) {
+ return ptr; // expected-warning {{implicit conversion when returning 'void *' from a function with result type 'int *' is not permitted in C++}}
+}
>From b02b1e93dbb18fcb350d98557cfbd1052bfa7a60 Mon Sep 17 00:00:00 2001
From: Aaron Ballman <aaron at aaronballman.com>
Date: Wed, 23 Apr 2025 13:52:04 -0400
Subject: [PATCH 3/3] Update the test
Use a bookmark, per review request
Add C++ RUN line to show we diagnose invalid constructs in C++
---
clang/test/Sema/implicit-void-ptr-cast.c | 30 ++++++++++++++++--------
1 file changed, 20 insertions(+), 10 deletions(-)
diff --git a/clang/test/Sema/implicit-void-ptr-cast.c b/clang/test/Sema/implicit-void-ptr-cast.c
index bf981c51e724e..df18eeebd9347 100644
--- a/clang/test/Sema/implicit-void-ptr-cast.c
+++ b/clang/test/Sema/implicit-void-ptr-cast.c
@@ -1,5 +1,6 @@
-// RUN: %clang_cc1 -fsyntax-only -verify -Wimplicit-void-ptr-cast %s
-// RUN: %clang_cc1 -fsyntax-only -verify -Wc++-compat %s
+// RUN: %clang_cc1 -fsyntax-only -verify=c -Wimplicit-void-ptr-cast %s
+// RUN: %clang_cc1 -fsyntax-only -verify=c -Wc++-compat %s
+// RUN: %clang_cc1 -fsyntax-only -verify=cxx -x c++ %s
// RUN: %clang_cc1 -fsyntax-only -verify=good %s
// RUN: %clang_cc1 -fsyntax-only -verify=good -Wc++-compat -Wno-implicit-void-ptr-cast %s
// good-no-diagnostics
@@ -7,22 +8,31 @@
typedef __typeof__(sizeof(int)) size_t;
extern void *malloc(size_t);
-void func(int *); // expected-note {{passing argument to parameter here}}
+void func(int *); // #func-param
void test(void) {
- int *x = malloc(sizeof(char)); // expected-warning {{implicit conversion when initializing 'int *' with an expression of type 'void *' is not permitted in C++}}
- x = malloc(sizeof(char)); // expected-warning {{implicit conversion when assigning to 'int *' from type 'void *' is not permitted in C++}}
- func(malloc(sizeof(char))); // expected-warning {{implicit conversion when passing 'void *' to parameter of type 'int *' is not permitted in C++}}
+ int *x = malloc(sizeof(char)); // c-warning {{implicit conversion when initializing 'int *' with an expression of type 'void *' is not permitted in C++}} \
+ cxx-error {{cannot initialize a variable of type 'int *' with an rvalue of type 'void *'}}
+ x = malloc(sizeof(char)); // c-warning {{implicit conversion when assigning to 'int *' from type 'void *' is not permitted in C++}} \
+ cxx-error {{assigning to 'int *' from incompatible type 'void *'}}
+ func(malloc(sizeof(char))); // c-warning {{implicit conversion when passing 'void *' to parameter of type 'int *' is not permitted in C++}} \
+ c-note@#func-param {{passing argument to parameter here}} \
+ cxx-error {{no matching function for call to 'func'}} \
+ cxx-note@#func-param {{candidate function not viable: cannot convert argument of incomplete type 'void *' to 'int *' for 1st argument}}
x = (int *)malloc(sizeof(char));
void *vp = 0;
- x = vp; // expected-warning {{implicit conversion when assigning to 'int *' from type 'void *' is not permitted in C++}}
+ x = vp; // c-warning {{implicit conversion when assigning to 'int *' from type 'void *' is not permitted in C++}} \
+ cxx-error {{assigning to 'int *' from incompatible type 'void *'}}
vp = vp;
- x = (void *)malloc(sizeof(char)); // expected-warning {{implicit conversion when assigning to 'int *' from type 'void *' is not permitted in C++}}
- const int *y = vp; // expected-warning {{implicit conversion when initializing 'const int *' with an expression of type 'void *' is not permitted in C++}}
+ x = (void *)malloc(sizeof(char)); // c-warning {{implicit conversion when assigning to 'int *' from type 'void *' is not permitted in C++}} \
+ cxx-error {{assigning to 'int *' from incompatible type 'void *'}}
+ const int *y = vp; // c-warning {{implicit conversion when initializing 'const int *' with an expression of type 'void *' is not permitted in C++}} \
+ cxx-error {{cannot initialize a variable of type 'const int *' with an lvalue of type 'void *'}}
}
int *other_func(void *ptr) {
- return ptr; // expected-warning {{implicit conversion when returning 'void *' from a function with result type 'int *' is not permitted in C++}}
+ return ptr; // c-warning {{implicit conversion when returning 'void *' from a function with result type 'int *' is not permitted in C++}} \
+ cxx-error {{cannot initialize return object of type 'int *' with an lvalue of type 'void *'}}
}
More information about the cfe-commits
mailing list