[Clang] Fix reference binding traits with cv-qualified array types#198698
[Clang] Fix reference binding traits with cv-qualified array types#198698frederick-vs-ja wants to merge 1 commit into
Conversation
|
@llvm/pr-subscribers-hlsl @llvm/pr-subscribers-clang Author: A. Jiang (frederick-vs-ja) ChangesWhen the source type is a cv-qualified array type that is denoted by an alias, Clang currently incorrectly drops cv-qualifiers from the array type. Per C++26 [expr.type]p2 (initially fixed by CWG1261), cv-qualification is kept for array prvalues. Fixes #198580. Full diff: https://github.com/llvm/llvm-project/pull/198698.diff 3 Files Affected:
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 843a01f57c39f..afb10d4340599 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -570,6 +570,8 @@ Bug Fixes in This Version
an array via an element-at-a-time copy loop (#GH192026)
- Fixed an issue where certain designated initializers would be rejected for constexpr variables. (#GH193373)
- Fixed a crash when ``#embed`` is used with C++ modules (#GH195350)
+- Fixed mishandling of cv-qualified array types in ``__reference_constructs_from_temporary`` and
+ ``__reference_converts_from_temporary`` builtins. (#GH198580)
Bug Fixes to Compiler Builtins
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index 96a398aa21dad..cda3d8dba08e2 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -3679,13 +3679,15 @@ QualType QualType::getNonLValueExprType(const ASTContext &Context) const {
if (const auto *RefType = getTypePtr()->getAs<ReferenceType>())
return RefType->getPointeeType();
- // C++0x [basic.lval]:
- // Class prvalues can have cv-qualified types; non-class prvalues always
- // have cv-unqualified types.
+ // C++26 [expr.type]p2 (see also CWG1261):
+ // If a prvalue initially has the type "cv T", where T is a cv-unqualified
+ // non-class, non-array type, the type of the expression is adjusted to T
+ // prior to any further analysis.
//
// See also C99 6.3.2.1p2.
if (!Context.getLangOpts().CPlusPlus ||
- (!getTypePtr()->isDependentType() && !getTypePtr()->isRecordType()))
+ (!getTypePtr()->isDependentType() && !getTypePtr()->isRecordType() &&
+ !getTypePtr()->isArrayType()))
return getUnqualifiedType();
return *this;
diff --git a/clang/test/SemaCXX/type-traits.cpp b/clang/test/SemaCXX/type-traits.cpp
index 8decb1f61395e..3b2fe8a78f37e 100644
--- a/clang/test/SemaCXX/type-traits.cpp
+++ b/clang/test/SemaCXX/type-traits.cpp
@@ -3310,6 +3310,44 @@ void reference_constructs_from_temporary_checks() {
// For scalar types, cv-qualifications are dropped first for prvalues.
static_assert(__reference_constructs_from_temporary(int&&, const int));
static_assert(__reference_constructs_from_temporary(int&&, volatile int));
+ // For array types, cv-qualifications are *kept* for prvalues.
+ // Uses with type aliases need to be verified, see https://llvm.org/PR198580.
+ static_assert(!__reference_constructs_from_temporary(const int(&)[10], volatile int[10]));
+ static_assert(!__reference_constructs_from_temporary(const IntAr&, volatile int[10]));
+ static_assert(!__reference_constructs_from_temporary(const int(&)[10], volatile IntAr));
+ static_assert(!__reference_constructs_from_temporary(const IntAr&, volatile IntAr));
+ static_assert(!__reference_constructs_from_temporary(int(&&)[10], volatile int[10]));
+ static_assert(!__reference_constructs_from_temporary(IntAr&&, volatile int[10]));
+ static_assert(!__reference_constructs_from_temporary(int(&&)[10], volatile IntAr));
+ static_assert(!__reference_constructs_from_temporary(IntAr&&, volatile IntAr));
+ static_assert(__reference_constructs_from_temporary(volatile int(&&)[10], volatile int[10]));
+ static_assert(__reference_constructs_from_temporary(volatile IntAr&&, volatile int[10]));
+ static_assert(__reference_constructs_from_temporary(volatile int(&&)[10], volatile IntAr));
+ static_assert(__reference_constructs_from_temporary(volatile IntAr&&, volatile IntAr));
+ static_assert(!__reference_constructs_from_temporary(int*&&, volatile int[10]));
+ static_assert(!__reference_constructs_from_temporary(int*&&, volatile IntAr));
+ static_assert(!__reference_constructs_from_temporary(int* const&, volatile int[10]));
+ static_assert(!__reference_constructs_from_temporary(int* const&, volatile IntAr));
+ static_assert(__reference_constructs_from_temporary(volatile int*&&, volatile int[10]));
+ static_assert(__reference_constructs_from_temporary(volatile int*&&, volatile IntAr));
+ static_assert(!__reference_constructs_from_temporary(const Empty(&)[10], volatile Empty[10]));
+ static_assert(!__reference_constructs_from_temporary(const EmptyAr&, volatile Empty[10]));
+ static_assert(!__reference_constructs_from_temporary(const Empty(&)[10], volatile Empty));
+ static_assert(!__reference_constructs_from_temporary(const EmptyAr&, volatile Empty));
+ static_assert(!__reference_constructs_from_temporary(Empty(&&)[10], volatile Empty[10]));
+ static_assert(!__reference_constructs_from_temporary(EmptyAr&&, volatile int[10]));
+ static_assert(!__reference_constructs_from_temporary(Empty(&&)[10], volatile EmptyAr));
+ static_assert(!__reference_constructs_from_temporary(EmptyAr&&, volatile EmptyAr));
+ static_assert(__reference_constructs_from_temporary(volatile Empty(&&)[10], volatile Empty[10]));
+ static_assert(__reference_constructs_from_temporary(volatile EmptyAr&&, volatile Empty[10]));
+ static_assert(__reference_constructs_from_temporary(volatile Empty(&&)[10], volatile EmptyAr));
+ static_assert(__reference_constructs_from_temporary(volatile EmptyAr&&, volatile EmptyAr));
+ static_assert(!__reference_constructs_from_temporary(Empty*&&, volatile Empty[10]));
+ static_assert(!__reference_constructs_from_temporary(Empty*&&, volatile EmptyAr));
+ static_assert(!__reference_constructs_from_temporary(Empty* const&, volatile Empty[10]));
+ static_assert(!__reference_constructs_from_temporary(Empty* const&, volatile EmptyAr));
+ static_assert(__reference_constructs_from_temporary(volatile Empty*&&, volatile Empty[10]));
+ static_assert(__reference_constructs_from_temporary(volatile Empty*&&, volatile EmptyAr));
// Additional checks
static_assert(__reference_constructs_from_temporary(POD const&, Derives));
@@ -3377,6 +3415,44 @@ void reference_converts_from_temporary_checks() {
// For scalar types, cv-qualifications are dropped first for prvalues.
static_assert(__reference_converts_from_temporary(int&&, const int));
static_assert(__reference_converts_from_temporary(int&&, volatile int));
+ // For array types, cv-qualifications are *kept* for prvalues.
+ // Uses with type aliases need to be verified, see https://llvm.org/PR198580.
+ static_assert(!__reference_converts_from_temporary(const int(&)[10], volatile int[10]));
+ static_assert(!__reference_converts_from_temporary(const IntAr&, volatile int[10]));
+ static_assert(!__reference_converts_from_temporary(const int(&)[10], volatile IntAr));
+ static_assert(!__reference_converts_from_temporary(const IntAr&, volatile IntAr));
+ static_assert(!__reference_converts_from_temporary(int(&&)[10], volatile int[10]));
+ static_assert(!__reference_converts_from_temporary(IntAr&&, volatile int[10]));
+ static_assert(!__reference_converts_from_temporary(int(&&)[10], volatile IntAr));
+ static_assert(!__reference_converts_from_temporary(IntAr&&, volatile IntAr));
+ static_assert(__reference_converts_from_temporary(volatile int(&&)[10], volatile int[10]));
+ static_assert(__reference_converts_from_temporary(volatile IntAr&&, volatile int[10]));
+ static_assert(__reference_converts_from_temporary(volatile int(&&)[10], volatile IntAr));
+ static_assert(__reference_converts_from_temporary(volatile IntAr&&, volatile IntAr));
+ static_assert(!__reference_converts_from_temporary(int*&&, volatile int[10]));
+ static_assert(!__reference_converts_from_temporary(int*&&, volatile IntAr));
+ static_assert(!__reference_converts_from_temporary(int* const&, volatile int[10]));
+ static_assert(!__reference_converts_from_temporary(int* const&, volatile IntAr));
+ static_assert(__reference_converts_from_temporary(volatile int*&&, volatile int[10]));
+ static_assert(__reference_converts_from_temporary(volatile int*&&, volatile IntAr));
+ static_assert(!__reference_converts_from_temporary(const Empty(&)[10], volatile Empty[10]));
+ static_assert(!__reference_converts_from_temporary(const EmptyAr&, volatile Empty[10]));
+ static_assert(!__reference_converts_from_temporary(const Empty(&)[10], volatile Empty));
+ static_assert(!__reference_converts_from_temporary(const EmptyAr&, volatile Empty));
+ static_assert(!__reference_converts_from_temporary(Empty(&&)[10], volatile Empty[10]));
+ static_assert(!__reference_converts_from_temporary(EmptyAr&&, volatile int[10]));
+ static_assert(!__reference_converts_from_temporary(Empty(&&)[10], volatile EmptyAr));
+ static_assert(!__reference_converts_from_temporary(EmptyAr&&, volatile EmptyAr));
+ static_assert(__reference_converts_from_temporary(volatile Empty(&&)[10], volatile Empty[10]));
+ static_assert(__reference_converts_from_temporary(volatile EmptyAr&&, volatile Empty[10]));
+ static_assert(__reference_converts_from_temporary(volatile Empty(&&)[10], volatile EmptyAr));
+ static_assert(__reference_converts_from_temporary(volatile EmptyAr&&, volatile EmptyAr));
+ static_assert(!__reference_converts_from_temporary(Empty*&&, volatile Empty[10]));
+ static_assert(!__reference_converts_from_temporary(Empty*&&, volatile EmptyAr));
+ static_assert(!__reference_converts_from_temporary(Empty* const&, volatile Empty[10]));
+ static_assert(!__reference_converts_from_temporary(Empty* const&, volatile EmptyAr));
+ static_assert(__reference_converts_from_temporary(volatile Empty*&&, volatile Empty[10]));
+ static_assert(__reference_converts_from_temporary(volatile Empty*&&, volatile EmptyAr));
// Additional checks
static_assert(__reference_converts_from_temporary(POD const&, Derives));
|
🐧 Linux x64 Test Results
✅ The build succeeded and all tests passed. |
🪟 Windows x64 Test Results
✅ The build succeeded and all tests passed. |
| if (!Context.getLangOpts().CPlusPlus || | ||
| (!getTypePtr()->isDependentType() && !getTypePtr()->isRecordType())) | ||
| (!getTypePtr()->isDependentType() && !getTypePtr()->isRecordType() && | ||
| !getTypePtr()->isArrayType())) |
There was a problem hiding this comment.
Test failure was related to this.
llvm-project/clang/test/SemaHLSL/Language/InitIncompleteArrays.hlsl
Lines 15 to 18 in 05c1a26
After the seemingly reasonable change in QualType::getNonPackExpansionType, __decltype((T)0) fails to remove hlsl_private from an array type.
There was a problem hiding this comment.
The failure can be resolved by __typeof_unqual__. However, there's still potential breaking change of __decltype for HLSL. Should we note this in release note or avoid behavior change for HLSL?
There was a problem hiding this comment.
CC @llvm-beanz because it's an HLSL question I don't feel confident answering.
There was a problem hiding this comment.
If this is a non-lvalue expression type, won't the array have been decayed to a pointer already?
0aa5ca5 to
dd5f6ea
Compare
When the source type is a cv-qualified array type that is denoted by an alias, Clang currently incorrectly drops cv-qualifiers from the array type. Per C++26 [expr.type]p2 (initially fixed by CWG1261), cv-qualification is kept for array prvalues. HLSL is consistently affected - this patch makes `__decltype` keep address space qualifiers for array prvalues in HLSL.
dd5f6ea to
ce70ad5
Compare
cor3ntin
left a comment
There was a problem hiding this comment.
Would you be willing to ad a dr test for cwg1261 in this PR?
|
Pre-CWG1261 wording is also quoted in some other location(s) now. It's possible that Clang has already effectively implemented CWG1261, but it's a bit too tiring for me to check the implementation status thoroughly at this moment. |
Endilll
left a comment
There was a problem hiding this comment.
This change is backported to all language modes without restrictions. Do we need an ABI tag?
CC @AaronBallman
| if (!Context.getLangOpts().CPlusPlus || | ||
| (!getTypePtr()->isDependentType() && !getTypePtr()->isRecordType())) | ||
| (!getTypePtr()->isDependentType() && !getTypePtr()->isRecordType() && | ||
| !getTypePtr()->isArrayType())) |
There was a problem hiding this comment.
Between this code and [basic.type]/2, this change seems correct. However, this change seems to be placed rather deep compared to the goal of fixing two type traits. Could it be that we have duplicated code paths in SemaInit.cpp that should be calling this function?
| if (!Context.getLangOpts().CPlusPlus || | ||
| (!getTypePtr()->isDependentType() && !getTypePtr()->isRecordType())) | ||
| (!getTypePtr()->isDependentType() && !getTypePtr()->isRecordType() && | ||
| !getTypePtr()->isArrayType())) |
There was a problem hiding this comment.
If this is a non-lvalue expression type, won't the array have been decayed to a pointer already?
| (!getTypePtr()->isDependentType() && !getTypePtr()->isRecordType())) | ||
| (!getTypePtr()->isDependentType() && !getTypePtr()->isRecordType() && | ||
| !getTypePtr()->isArrayType())) | ||
| return getUnqualifiedType(); |
There was a problem hiding this comment.
Drive-by: are we sure this is correct for _Atomic in C? (This relates to the question of whether we expect this to be called after rvalue conversion or not; if the rvalue conversion has happened before calling this, then the type won't be atomic any longer anyway.)
Because it impacts type traits, I think an ABI tag would be warranted (every time we change type traits, we usually quietly break how some template specializations get resolved). |
When the source type is a cv-qualified array type that is denoted by an alias, Clang currently incorrectly drops cv-qualifiers from the array type.
Per C++26 [expr.type]p2 (initially fixed by CWG1261), cv-qualification is kept for array prvalues.
HLSL is consistently affected - this patch makes
__decltypekeep address space qualifiers for array prvalues in HLSL.Fixes #198580.