Skip to content
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

[clang] Fix array types comparison in getCommonSugaredType #131649

Closed
wants to merge 2 commits into from

Conversation

spavloff
Copy link
Collaborator

Const-qualification of an array caused by constexpr specifier can produce QualType, where the const qualifier is set both as fast qualifier and as a qualifier of the array element type. It can result in a compiler crash, because such QualType does not compare equal to the same type but without extra qualification.

To avoid the crash, the redundant qualifiers are removed while searching for common sugar.

It fixes #97005 (Clang crashed in ASTContext::getCommonSugaredType).

Const-qualification of an array caused by constexpr specifier can
produce QualType, where the const qualifier is set both as fast
qualifier and as a qualifier of the array element type. It can result in
a compiler crash, because such QualType does not compare equal to the
same type but without extra qualification.

To avoid the crash, the redundant qualifiers are removed while searching
for common sugar.

It fixes llvm#97005 (Clang
crashed in ASTContext::getCommonSugaredType).
@spavloff spavloff self-assigned this Mar 17, 2025
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" labels Mar 17, 2025
@llvmbot
Copy link
Member

llvmbot commented Mar 17, 2025

@llvm/pr-subscribers-clang

Author: Serge Pavlov (spavloff)

Changes

Const-qualification of an array caused by constexpr specifier can produce QualType, where the const qualifier is set both as fast qualifier and as a qualifier of the array element type. It can result in a compiler crash, because such QualType does not compare equal to the same type but without extra qualification.

To avoid the crash, the redundant qualifiers are removed while searching for common sugar.

It fixes #97005 (Clang crashed in ASTContext::getCommonSugaredType).


Full diff: https://github.com/llvm/llvm-project/pull/131649.diff

2 Files Affected:

  • (modified) clang/lib/AST/ASTContext.cpp (+9)
  • (added) clang/test/SemaCXX/constexpr-implicit-const-97005.cpp (+10)
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index 7ed5b033d9bd8..2614c4235c994 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -14171,6 +14171,15 @@ static QualType getCommonSugarTypeNode(ASTContext &Ctx, const Type *X,
 static auto unwrapSugar(SplitQualType &T, Qualifiers &QTotal) {
   SmallVector<SplitQualType, 8> R;
   while (true) {
+    if (const auto *ATy = dyn_cast<ArrayType>(T.Ty)) {
+      // C++ 9.3.3.4p3: Any type of the form "cv-qualifier-seq array of N U" is
+      // adjusted to "array of N cv-qualifier-seq U".
+      // C23 6.7.3p10: If the specification of an array type includes any type
+      // qualifiers, both the array and the element type are so-qualified.
+      //
+      // To simplify comparison remove the redundant qualifiers from the array.
+      T.Quals.removeCVRQualifiers(Qualifiers::Const | Qualifiers::Volatile);
+    }
     QTotal.addConsistentQualifiers(T.Quals);
     QualType NT = T.Ty->getLocallyUnqualifiedSingleStepDesugaredType();
     if (NT == QualType(T.Ty, 0))
diff --git a/clang/test/SemaCXX/constexpr-implicit-const-97005.cpp b/clang/test/SemaCXX/constexpr-implicit-const-97005.cpp
new file mode 100644
index 0000000000000..db6106998d02b
--- /dev/null
+++ b/clang/test/SemaCXX/constexpr-implicit-const-97005.cpp
@@ -0,0 +1,10 @@
+// RUN: %clang_cc1 -triple x86_64-linux-gnu %s
+
+bool a;
+constexpr const unsigned char c[] = { 5 };
+constexpr const unsigned char d[1] = { 0 };
+auto b = (a ? d : c);
+
+constexpr const unsigned char c1[][1] = {{ 5 }};
+constexpr const unsigned char d1[1][1] = {{ 0 }};
+auto b1 = (a ? d1 : c1);

// qualifiers, both the array and the element type are so-qualified.
//
// To simplify comparison remove the redundant qualifiers from the array.
T.Quals.removeCVRQualifiers(Qualifiers::Const | Qualifiers::Volatile);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tossing the const qualifier is fine if the array element type is already const-qualified... but do we actually know it is at this point? Or do we need something like T.Ty = getAsArrayType(getQualifiedType(T)) to make sure the qualifiers get preserved?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the updated version, only qualifiers found in element type are removed.

@mizvekov
Copy link
Contributor

The test for this should actually go in clang/test/SemaCXX/sugar-common-types.cpp, following the style in the file.

One example test, without using constexpr: https://godbolt.org/z/8PGa4aTPx

enum class Z {};

using ConstInt = const int;
using ConstIntArray1 = ConstInt[1];
using ConstIntArrayInc = ConstInt[];

const ConstIntArray1 c = { 5 };
const ConstIntArrayInc d = { 0 };
Z t1 = false ? d : c;

Also I note there is a separate bug here in the type printer: https://godbolt.org/z/5afcxnor7

We get a diagnostic: error: cannot initialize a variable of type 'Z' with an lvalue of type 'const ConstIntArray1' (aka 'const const int[1]'). We can drop that redundant extra const, because otherwise it does not round trip to that doubly qualified type anyway.

About the fix, I think it's at the wrong level and we shouldn't just be dropping the qualifiers like that, but it's been a while I implemented this functionality and I would need some time to think about it.

@mizvekov
Copy link
Contributor

Regarding the double const , I think this is not a type printer bug per se.

By indiscriminately adding a qualifier to a type, we can produce a type which cannot be written, such as this const qualified array case. As such, we can never do a good job printing these anyway.

There are two issues:

  • A single step desugaring with qualifier preservation could cause this. This could be produced as part of the type 'aka' in diagnostics. We probably should add a special case for arrays when single step desugaring.
  • When adding const for constexpr declarations, we could probably add some new kind of type sugar node which represents how this qualifier was added.

Comment on lines 14174 to 14181
if (const auto *ATy = dyn_cast<ArrayType>(T.Ty)) {
// C++ 9.3.3.4p3: Any type of the form "cv-qualifier-seq array of N U" is
// adjusted to "array of N cv-qualifier-seq U".
// C23 6.7.3p10: If the specification of an array type includes any type
// qualifiers, both the array and the element type are so-qualified.
//
// To simplify comparison remove the redundant qualifiers from the array.
T.Quals.removeCVRQualifiers(Qualifiers::Const | Qualifiers::Volatile);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is at the wrong level to fix the problem.
We have the logic for dealing with Array qualifiers in getCommonArrayElementType.

That's where we adjust the outer qualifiers for Array types accordingly to the qualifiers of the element type.

Looks like there is a bug in the qualifier algebra there:

  QX += EX.getQualifiers() - RQ;
  QY += EY.getQualifiers() - RQ;

We want to break down those two lines to:

  QX += EX.getQualifiers();
  QY += EY.getQualifiers();
  QX -= RQ;
  QY -= RQ;

I believe this should fix your problem.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change makes sense, it can fix te case of "array of arry", but it cannot fix the case from #97005:

bool a;
constexpr const unsigned char c[] = { 5 };
constexpr const unsigned char d[1] = { 0 };
auto b = (a ? d : c);

Here types of d and d do not contain sugar, corresponding canonical types are identical, getCommonNonSugarTypeNode is not called and getCommonArrayElementType is not called. However their types are not equal and assert(QX == QY) fails.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright, this needs more thought, but regardless, getCommonArrayElementType is the correct place to fix the problem.

We don't want to add special cases for ArrayTypes in unwrapSugar, because it's in the fast path, and most type nodes are not ArrayTypes, and most types don't even have ArrayTypes in them.

So this is why things are layered this way.

Comment on lines 1 to 10
// RUN: %clang_cc1 -triple x86_64-linux-gnu %s

bool a;
constexpr const unsigned char c[] = { 5 };
constexpr const unsigned char d[1] = { 0 };
auto b = (a ? d : c);

constexpr const unsigned char c1[][1] = {{ 5 }};
constexpr const unsigned char d1[1][1] = {{ 0 }};
auto b1 = (a ? d1 : c1);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please move this test to clang/test/SemaCXX/sugar-common-types.cpp, adjusting according to the function and style of that file.

Ie we want to make sure the inner sugar is preserved in the output.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test has been moved.

- Now only qualifiers that are found in array element are cleared,
- Test are moved into a new location.
Copy link

⚠️ C/C++ code formatter, clang-format found issues in your code. ⚠️

You can test this locally with the following command:
git-clang-format --diff 3f62718c4a907125af31faa62365bdf11ddef7b6 9bc13aee5775af148941826b74e5c9fbaab907c7 --extensions cpp -- clang/lib/AST/ASTContext.cpp clang/test/SemaCXX/sugar-common-types.cpp
View the diff from clang-format here.
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index 28f4332162..5429cdc704 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -14171,7 +14171,7 @@ static QualType getCommonSugarTypeNode(ASTContext &Ctx, const Type *X,
 /// Returns element type of the array, probably multidimensional, specified by
 /// the given ArrayType.
 /// \returns Canonical type of the array element.
-static QualType getNonArrayElementType(const ArrayType* ATy) {
+static QualType getNonArrayElementType(const ArrayType *ATy) {
   QualType ElTy = ATy->getElementType().getCanonicalType();
   while (auto *SubArr = dyn_cast<ArrayType>(ElTy.getTypePtr()))
     ElTy = SubArr->getElementType();

@mizvekov
Copy link
Contributor

I posted an alternative fix for this here: #132559

@spavloff
Copy link
Collaborator Author

Fixed in #132559.

@spavloff spavloff closed this Mar 26, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Clang crashed in ASTContext::getCommonSugaredType
4 participants