[clang] Fixing array-to-pointer decay induced issue with conversion of the multidimentional arrays in C language. (PR #159896)
via cfe-commits
cfe-commits at lists.llvm.org
Sat Sep 20 05:16:53 PDT 2025
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang
Author: None (earnol)
<details>
<summary>Changes</summary>
This PR addresses issue https://github.com/llvm/llvm-project/issues/159603 and stops emitting diagnostics when multidimentional array attempted to be implicitly cast to the pointer type of the same base type.
---
Full diff: https://github.com/llvm/llvm-project/pull/159896.diff
2 Files Affected:
- (modified) clang/lib/Sema/SemaExpr.cpp (+135-65)
- (added) clang/test/Sema/multi-dim-array.c (+52)
``````````diff
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 3b267c1b1693d..8ad05e56e3496 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -9032,15 +9032,107 @@ static bool IsInvalidCmseNSCallConversion(Sema &S, QualType FromType,
return false;
}
+/// This helper function returns element type if QT is a multidimentional array
+static std::optional<QualType> getMultiDimentionalArrayType(Sema const *S,
+ QualType QT) {
+ const ArrayType *AT = S->Context.getAsArrayType(QT);
+ if (AT) {
+ const ConstantArrayType *CAT = llvm::dyn_cast<ConstantArrayType>(AT);
+ if (CAT) {
+ QualType ElementType = CAT->getElementType();
+ while (CAT) {
+ llvm::APInt Size = CAT->getSize();
+ // Zero sized array are no good.
+ if (Size == 0)
+ return std::optional<QualType>();
+ ElementType = CAT->getElementType();
+ CAT = llvm::dyn_cast<ConstantArrayType>(ElementType);
+ }
+ return std::optional<QualType>(
+ QualType(std::get<const Type *>(ElementType.split().asPair()), 0));
+ }
+ }
+ return std::optional<QualType>();
+}
+
+/// This helper function return AssignConvertType if pointers are not
+/// compatible
+static std::optional<AssignConvertType> checkPointerAssignmentCompatibility(
+ Sema &S, const Type *lhptee, const Type *rhptee, QualType ltrans,
+ QualType rtrans, QualType LHSType, QualType RHSType,
+ AssignConvertType ConvTy) {
+ if (!S.Context.typesAreCompatible(ltrans, rtrans)) {
+ // Check if the pointee types are compatible ignoring the sign.
+ // We explicitly check for char so that we catch "char" vs
+ // "unsigned char" on systems where "char" is unsigned.
+ if (lhptee->isCharType())
+ ltrans = S.Context.UnsignedCharTy;
+ else if (lhptee->hasSignedIntegerRepresentation())
+ ltrans = S.Context.getCorrespondingUnsignedType(ltrans);
+
+ if (rhptee->isCharType())
+ rtrans = S.Context.UnsignedCharTy;
+ else if (rhptee->hasSignedIntegerRepresentation())
+ rtrans = S.Context.getCorrespondingUnsignedType(rtrans);
+
+ if (ltrans == rtrans) {
+ // Types are compatible ignoring the sign. Qualifier incompatibility
+ // takes priority over sign incompatibility because the sign
+ // warning can be disabled.
+ if (!S.IsAssignConvertCompatible(ConvTy))
+ return ConvTy;
+
+ return AssignConvertType::IncompatiblePointerSign;
+ }
+
+ // If we are a multi-level pointer, it's possible that our issue is simply
+ // one of qualification - e.g. char ** -> const char ** is not allowed. If
+ // the eventual target type is the same and the pointers have the same
+ // level of indirection, this must be the issue.
+ if (isa<PointerType>(lhptee) && isa<PointerType>(rhptee)) {
+ Qualifiers lhq, rhq;
+ do {
+ std::tie(lhptee, lhq) =
+ cast<PointerType>(lhptee)->getPointeeType().split().asPair();
+ std::tie(rhptee, rhq) =
+ cast<PointerType>(rhptee)->getPointeeType().split().asPair();
+
+ // Inconsistent address spaces at this point is invalid, even if the
+ // address spaces would be compatible.
+ // FIXME: This doesn't catch address space mismatches for pointers of
+ // different nesting levels, like:
+ // __local int *** a;
+ // int ** b = a;
+ // It's not clear how to actually determine when such pointers are
+ // invalidly incompatible.
+ if (lhq.getAddressSpace() != rhq.getAddressSpace())
+ return AssignConvertType::
+ IncompatibleNestedPointerAddressSpaceMismatch;
+
+ } while (isa<PointerType>(lhptee) && isa<PointerType>(rhptee));
+
+ if (lhptee == rhptee)
+ return AssignConvertType::IncompatibleNestedPointerQualifiers;
+ }
+
+ // General pointer incompatibility takes priority over qualifiers.
+ if (RHSType->isFunctionPointerType() && LHSType->isFunctionPointerType())
+ return AssignConvertType::IncompatibleFunctionPointer;
+
+ // Return pointers as incompatible
+ return AssignConvertType::IncompatiblePointer;
+ }
+ return std::optional<AssignConvertType>();
+}
+
// checkPointerTypesForAssignment - This is a very tricky routine (despite
// being closely modeled after the C99 spec:-). The odd characteristic of this
// routine is it effectively iqnores the qualifiers on the top level pointee.
// This circumvents the usual type rules specified in 6.2.7p1 & 6.7.5.[1-3].
// FIXME: add a couple examples in this comment.
-static AssignConvertType checkPointerTypesForAssignment(Sema &S,
- QualType LHSType,
- QualType RHSType,
- SourceLocation Loc) {
+static AssignConvertType
+checkPointerTypesForAssignment(Sema &S, QualType LHSType, QualType RHSType,
+ QualType OrigRHSType, SourceLocation Loc) {
assert(LHSType.isCanonical() && "LHS not canonicalized!");
assert(RHSType.isCanonical() && "RHS not canonicalized!");
@@ -9128,65 +9220,26 @@ static AssignConvertType checkPointerTypesForAssignment(Sema &S,
// C99 6.5.16.1p1 (constraint 3): both operands are pointers to qualified or
// unqualified versions of compatible types, ...
- QualType ltrans = QualType(lhptee, 0), rtrans = QualType(rhptee, 0);
- if (!S.Context.typesAreCompatible(ltrans, rtrans)) {
- // Check if the pointee types are compatible ignoring the sign.
- // We explicitly check for char so that we catch "char" vs
- // "unsigned char" on systems where "char" is unsigned.
- if (lhptee->isCharType())
- ltrans = S.Context.UnsignedCharTy;
- else if (lhptee->hasSignedIntegerRepresentation())
- ltrans = S.Context.getCorrespondingUnsignedType(ltrans);
-
- if (rhptee->isCharType())
- rtrans = S.Context.UnsignedCharTy;
- else if (rhptee->hasSignedIntegerRepresentation())
- rtrans = S.Context.getCorrespondingUnsignedType(rtrans);
-
- if (ltrans == rtrans) {
- // Types are compatible ignoring the sign. Qualifier incompatibility
- // takes priority over sign incompatibility because the sign
- // warning can be disabled.
- if (!S.IsAssignConvertCompatible(ConvTy))
- return ConvTy;
-
- return AssignConvertType::IncompatiblePointerSign;
- }
-
- // If we are a multi-level pointer, it's possible that our issue is simply
- // one of qualification - e.g. char ** -> const char ** is not allowed. If
- // the eventual target type is the same and the pointers have the same
- // level of indirection, this must be the issue.
- if (isa<PointerType>(lhptee) && isa<PointerType>(rhptee)) {
- do {
- std::tie(lhptee, lhq) =
- cast<PointerType>(lhptee)->getPointeeType().split().asPair();
- std::tie(rhptee, rhq) =
- cast<PointerType>(rhptee)->getPointeeType().split().asPair();
-
- // Inconsistent address spaces at this point is invalid, even if the
- // address spaces would be compatible.
- // FIXME: This doesn't catch address space mismatches for pointers of
- // different nesting levels, like:
- // __local int *** a;
- // int ** b = a;
- // It's not clear how to actually determine when such pointers are
- // invalidly incompatible.
- if (lhq.getAddressSpace() != rhq.getAddressSpace())
- return AssignConvertType::
- IncompatibleNestedPointerAddressSpaceMismatch;
-
- } while (isa<PointerType>(lhptee) && isa<PointerType>(rhptee));
-
- if (lhptee == rhptee)
- return AssignConvertType::IncompatibleNestedPointerQualifiers;
- }
-
- // General pointer incompatibility takes priority over qualifiers.
- if (RHSType->isFunctionPointerType() && LHSType->isFunctionPointerType())
- return AssignConvertType::IncompatibleFunctionPointer;
- return AssignConvertType::IncompatiblePointer;
+ QualType ltrans = QualType(lhptee, 0);
+ QualType rtrans;
+ std::optional<AssignConvertType> CvtT;
+ std::optional<QualType> ArrET = getMultiDimentionalArrayType(&S, OrigRHSType);
+ if (ArrET) {
+ rtrans = ArrET.value();
+ CvtT = checkPointerAssignmentCompatibility(
+ S, lhptee, rhptee, ltrans, rtrans, LHSType, RHSType, ConvTy);
+ if (CvtT && CvtT.value() != AssignConvertType::IncompatiblePointer)
+ return CvtT.value();
+ }
+ if (!ArrET ||
+ (CvtT && CvtT.value() == AssignConvertType::IncompatiblePointer)) {
+ rtrans = QualType(rhptee, 0);
+ CvtT = checkPointerAssignmentCompatibility(
+ S, lhptee, rhptee, ltrans, rtrans, LHSType, RHSType, ConvTy);
+ if (CvtT)
+ return CvtT.value();
}
+
bool DiscardingCFIUncheckedCallee, AddingCFIUncheckedCallee;
if (!S.getLangOpts().CPlusPlus &&
S.IsFunctionConversion(ltrans, rtrans, &DiscardingCFIUncheckedCallee,
@@ -9313,6 +9366,22 @@ static bool isVector(QualType QT, QualType ElementType) {
return false;
}
+/// This helper function returns pre array-to-pointer decay type if possible
+static QualType getPreDecayType(ExprResult &RHS) {
+ QualType OrigRHSType = RHS.get()->getType();
+ ImplicitCastExpr *ICE = dyn_cast<ImplicitCastExpr>(RHS.get());
+ if (ICE) {
+ Expr *E = ICE->getSubExpr();
+ if (E) {
+ DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(E);
+ if (DRE) {
+ OrigRHSType = DRE->getType();
+ }
+ }
+ }
+ return OrigRHSType;
+}
+
/// CheckAssignmentConstraints (C99 6.5.16) - This routine currently
/// has code to accommodate several GCC extensions when type checking
/// pointers. Here are some objectionable examples that GCC considers warnings:
@@ -9510,7 +9579,9 @@ AssignConvertType Sema::CheckAssignmentConstraints(QualType LHSType,
Kind = CK_NoOp;
else
Kind = CK_BitCast;
- return checkPointerTypesForAssignment(*this, LHSType, RHSType,
+
+ QualType OrigType = getPreDecayType(RHS);
+ return checkPointerTypesForAssignment(*this, LHSType, RHSType, OrigType,
RHS.get()->getBeginLoc());
}
@@ -9629,7 +9700,6 @@ AssignConvertType Sema::CheckAssignmentConstraints(QualType LHSType,
Context.getObjCClassRedefinitionType())) {
return AssignConvertType::Compatible;
}
-
return AssignConvertType::IncompatiblePointer;
}
@@ -9902,7 +9972,7 @@ AssignConvertType Sema::CheckSingleAssignmentConstraints(QualType LHSType,
RHS.get()->getType().getCanonicalType().getUnqualifiedType();
QualType CanLHS = LHSType.getCanonicalType().getUnqualifiedType();
if (CanRHS->isVoidPointerType() && CanLHS->isPointerType()) {
- Ret = checkPointerTypesForAssignment(*this, CanLHS, CanRHS,
+ Ret = checkPointerTypesForAssignment(*this, CanLHS, CanRHS, CanRHS,
RHS.get()->getExprLoc());
// Anything that's not considered perfectly compatible would be
// incompatible in C++.
diff --git a/clang/test/Sema/multi-dim-array.c b/clang/test/Sema/multi-dim-array.c
new file mode 100644
index 0000000000000..febd13a21d35e
--- /dev/null
+++ b/clang/test/Sema/multi-dim-array.c
@@ -0,0 +1,52 @@
+// RUN: %clang_cc1 %s -fsyntax-only -verify -verify=c2x -pedantic -Wno-strict-prototypes -Wno-zero-length-array
+
+int array_acceptor_good(unsigned long * par1)
+{
+ return par1 != (unsigned long *)0;
+}
+
+int array_acceptor_bad(unsigned long * par1) // expected-note {{passing argument to parameter 'par1' here}}
+{
+ return par1 != (unsigned long *)0;
+}
+
+int array_acceptor_bad2(unsigned long * par1) // expected-note {{passing argument to parameter 'par1' here}}
+{
+ return par1 != (unsigned long *)0;
+}
+
+struct S
+{
+ int a;
+};
+
+int array_acceptor_bad1(struct S * par1) // expected-note {{passing argument to parameter 'par1' here}}
+{
+ return par1 != (struct S *)0;
+}
+
+int array_struct_acceptor(struct S * par1)
+{
+ return par1 != (struct S *)0;
+}
+
+
+int array_tester()
+{
+ unsigned long mdarr[5][6];
+ double mddarr[5][6];
+ unsigned long sdarr[30];
+ unsigned long mdarr3d[5][6][2];
+ unsigned long mdarr4d[5][6][2][1];
+ unsigned long mdarrz4d[5][6][0][1];
+ struct S mdsarr[5][6][2];
+
+ array_acceptor_good(sdarr);
+ array_acceptor_good(mdarr);
+ array_acceptor_good(mdarr3d);
+ array_acceptor_good(mdarr4d);
+ array_acceptor_bad(mddarr); // expected-error {{incompatible pointer types passing 'double[5][6]' to parameter of type 'unsigned long *'}}
+ array_acceptor_bad1(mddarr); // expected-error {{incompatible pointer types passing 'double[5][6]' to parameter of type 'struct S *'}}
+ array_acceptor_bad2(mdarrz4d); // expected-error {{incompatible pointer types passing 'unsigned long[5][6][0][1]' to parameter of type 'unsigned long *'}}
+ array_struct_acceptor(mdsarr);
+}
\ No newline at end of file
``````````
</details>
https://github.com/llvm/llvm-project/pull/159896
More information about the cfe-commits
mailing list