Author: Aaron Ballman
Date: 2025-04-24T06:37:11-04:00
New Revision: 15321d2c9e686b382262339fa17c5445b1b2609f

URL: 
https://github.com/llvm/llvm-project/commit/15321d2c9e686b382262339fa17c5445b1b2609f
DIFF: 
https://github.com/llvm/llvm-project/commit/15321d2c9e686b382262339fa17c5445b1b2609f.diff

LOG: [C] Add (new) -Wimplicit-void-ptr-cast to -Wc++-compat (#136855)

This introduces a new diagnostic group (-Wimplicit-void-ptr-cast),
grouped under -Wc++-compat, which diagnoses implicit conversions from
void * to another pointer type in C. It's a common source of
incompatibility with C++ and is something GCC diagnoses (though GCC does
not have a specific warning group for this).

Fixes #17792

Added: 
    clang/test/Sema/implicit-void-ptr-cast.c

Modified: 
    clang/docs/ReleaseNotes.rst
    clang/include/clang/Basic/DiagnosticGroups.td
    clang/include/clang/Basic/DiagnosticSemaKinds.td
    clang/include/clang/Sema/Sema.h
    clang/lib/Sema/SemaDeclAttr.cpp
    clang/lib/Sema/SemaExpr.cpp
    clang/lib/Sema/SemaInit.cpp
    clang/lib/Sema/SemaObjC.cpp
    clang/lib/Sema/SemaObjCProperty.cpp
    clang/lib/Sema/SemaOverload.cpp

Removed: 
    


################################################################################
diff  --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 03ee627e1db71..d1f24fb23d44d 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 c562802efba57..8ff170520aafe 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -8687,7 +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 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/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
+    /// 
diff erentiate 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 3b5cf3661a52f..c960868badb52 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 41869995f90d3..283d910a09d54 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;
@@ -16980,7 +16984,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 042de8d8a821a..9c8f7bef35e4c 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

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..df18eeebd9347
--- /dev/null
+++ b/clang/test/Sema/implicit-void-ptr-cast.c
@@ -0,0 +1,38 @@
+// 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
+
+typedef __typeof__(sizeof(int)) size_t;
+extern void *malloc(size_t);
+
+void func(int *); // #func-param
+
+void test(void) {
+  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; // 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)); // 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; // 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 *'}}
+}


        
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to