Skip to content

[Clang] remove qualifiers in getCanonicalTypeUnqualified#170271

Merged
AaronBallman merged 2 commits intollvm:mainfrom
rosefromthedead:gctu-remove-quals
Dec 10, 2025
Merged

[Clang] remove qualifiers in getCanonicalTypeUnqualified#170271
AaronBallman merged 2 commits intollvm:mainfrom
rosefromthedead:gctu-remove-quals

Conversation

@rosefromthedead
Copy link
Contributor

It was assumed that since this is a method on Type, there are no quals present because usually you get a Type by using operator-> on a QualType. However, quals aren't always stored in the outermost QualType. For example, if const A is used as a template parameter, the outer QualType does not have the const flag set. The const actually lives in the canonical type inside a SubstTemplateTypeParmType. We therefore need to explicitly remove quals before returning.

Fixes #135273


I've been looking at all the call sites as suggested in #167881 (comment) to check that their usage of this method is sane. So far, most of the sites seem to either want unqualified types or not care, so this patch should handle some spooky edge cases of those. Some of them need more thinking, which I'll do soon.

Supersedes #167881

It was assumed that since this is a method on Type, there are no quals
present because usually you get a Type by using operator-> on a QualType.
However, quals aren't always stored in the outermost QualType. For
example, if const A is used as a template parameter, the outer QualType
does not have the const flag set. The const actually lives in the
canonical type inside a SubstTemplateTypeParmType. We therefore need to
explicitly remove quals before returning.

Fixes llvm#135273
@github-actions
Copy link

github-actions bot commented Dec 2, 2025

Thank you for submitting a Pull Request (PR) to the LLVM Project!

This PR will be automatically labeled and the relevant teams will be notified.

If you wish to, you can add reviewers by using the "Reviewers" section on this page.

If this is not working for you, it is probably because you do not have write permissions for the repository. In which case you can instead tag reviewers by name in a comment by using @ followed by their GitHub username.

If you have received no comments on your PR for a week, you can request a review by "ping"ing the PR by adding a comment “Ping”. The common courtesy "ping" rate is once a week. Please remember that you are asking for valuable time from other developers.

If you have further questions, they may be answered by the LLVM GitHub User Guide.

You can also ask questions in a comment on this PR, on the LLVM Discord or on the forums.

@rosefromthedead
Copy link
Contributor Author

@AaronBallman AaronBallman added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" labels Dec 2, 2025
Copy link
Contributor

@cor3ntin cor3ntin left a comment

Choose a reason for hiding this comment

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

This change needs a release note.
Please add an entry to clang/docs/ReleaseNotes.rst in the section the most adapted to the change, and referencing any Github issue this change fixes. Thanks!

@llvmbot
Copy link
Member

llvmbot commented Dec 2, 2025

@llvm/pr-subscribers-clang

Author: Rose Hudson (rosefromthedead)

Changes

It was assumed that since this is a method on Type, there are no quals present because usually you get a Type by using operator-> on a QualType. However, quals aren't always stored in the outermost QualType. For example, if const A is used as a template parameter, the outer QualType does not have the const flag set. The const actually lives in the canonical type inside a SubstTemplateTypeParmType. We therefore need to explicitly remove quals before returning.

Fixes #135273


I've been looking at all the call sites as suggested in #167881 (comment) to check that their usage of this method is sane. So far, most of the sites seem to either want unqualified types or not care, so this patch should handle some spooky edge cases of those. Some of them need more thinking, which I'll do soon.

Supersedes #167881


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

3 Files Affected:

  • (modified) clang/docs/ReleaseNotes.rst (+1)
  • (modified) clang/include/clang/AST/CanonicalType.h (+2-1)
  • (modified) clang/test/SemaCXX/type-traits.cpp (+10)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index da064534c25d9..0aa899817f3ea 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -567,6 +567,7 @@ Bug Fixes to C++ Support
 - Fix a crash when extracting unavailable member type from alias in template deduction. (#GH165560)
 - Fix incorrect diagnostics for lambdas with init-captures inside braced initializers. (#GH163498)
 - Fixed spurious diagnoses of certain nested lambda expressions. (#GH149121) (#GH156579)
+- Fix the result of ``__is_pointer_interconvertible_base_of`` when arguments are qualified and passed via template parameters. (#GH135273)
 
 Bug Fixes to AST Handling
 ^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/clang/include/clang/AST/CanonicalType.h b/clang/include/clang/AST/CanonicalType.h
index 87bbd7b5d885d..b46fc8f50fa23 100644
--- a/clang/include/clang/AST/CanonicalType.h
+++ b/clang/include/clang/AST/CanonicalType.h
@@ -213,7 +213,8 @@ inline bool operator!=(CanQual<T> x, CanQual<U> y) {
 using CanQualType = CanQual<Type>;
 
 inline CanQualType Type::getCanonicalTypeUnqualified() const {
-  return CanQualType::CreateUnsafe(getCanonicalTypeInternal());
+  return CanQualType::CreateUnsafe(
+      getCanonicalTypeInternal().getUnqualifiedType());
 }
 
 inline const StreamingDiagnostic &operator<<(const StreamingDiagnostic &DB,
diff --git a/clang/test/SemaCXX/type-traits.cpp b/clang/test/SemaCXX/type-traits.cpp
index 9ef44d0346b48..561c9ca8286b9 100644
--- a/clang/test/SemaCXX/type-traits.cpp
+++ b/clang/test/SemaCXX/type-traits.cpp
@@ -1812,6 +1812,11 @@ union Union {};
 union UnionIncomplete;
 struct StructIncomplete; // #StructIncomplete
 
+#if __cplusplus >= 201402L
+template<class _Base, class _Derived>
+inline constexpr bool IsPointerInterconvertibleBaseOfV = __is_pointer_interconvertible_base_of(_Base, _Derived);
+#endif
+
 void is_pointer_interconvertible_base_of(int n)
 {
   static_assert(__is_pointer_interconvertible_base_of(Base, Derived));
@@ -1880,6 +1885,11 @@ void is_pointer_interconvertible_base_of(int n)
   static_assert(!__is_pointer_interconvertible_base_of(void(&)(int), void(&)(char)));
   static_assert(!__is_pointer_interconvertible_base_of(void(*)(int), void(*)(int)));
   static_assert(!__is_pointer_interconvertible_base_of(void(*)(int), void(*)(char)));
+
+#if __cplusplus >= 201402L
+  static_assert(IsPointerInterconvertibleBaseOfV<const Base, Base>);
+  static_assert(IsPointerInterconvertibleBaseOfV<const Base, Derived>);
+#endif
 }
 }
 

Copy link
Contributor

@mizvekov mizvekov left a comment

Choose a reason for hiding this comment

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

LGTM, Thanks.

Copy link
Collaborator

@AaronBallman AaronBallman left a comment

Choose a reason for hiding this comment

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

The changes LGTM but I do have a question: any time we change how type traits are resolved, we silently break ABI for some users. Should we have an ABI versioning check so people can get the old behavior back if they need to? I don't think __is_pointer_interconvertible_base_of() is used so often that we need to (https://sourcegraph.com/search?q=context:global+lang:C%2B%2B+__is_pointer_interconvertible_base_of+count:10000000+-file:.*clang.*+-file:.*test.*&patternType=keyword&sm=0) but I still think the question is worth asking others.

@cor3ntin
Copy link
Contributor

cor3ntin commented Dec 2, 2025

@AaronBallman right, it's a conformance fix to a recently added built-in. I would hope no one relies on the old behavior nor should we keep the old behavior around.

@AaronBallman
Copy link
Collaborator

@AaronBallman right, it's a conformance fix to a recently added built-in. I would hope no one relies on the old behavior nor should we keep the old behavior around.

Thanks for confirmation! Then I think we should move forward with these changes and just keep an eye on the issues list in case users report a disruption. We can add the ABI check before the release if we need to.

@rjmccall
Copy link
Contributor

rjmccall commented Dec 2, 2025

IIRC, the original point of this function was to expose a way to get the canonical type extremely cheaply for operations that are just going to ignore local qualifiers anyway. The problem is not that that operation exists — it's good to use a cheaper operation when you can — it's that it's misnamed in a way that's encouraging misuse. We should probably break it into getUnqualifiedCanonicalType, which does the change you're proposing here, and getCanonicalTypeWithoutLocalQualifiers(), which hopefully has a sufficiently discouraging name that people won't accidentally use it.

@rosefromthedead
Copy link
Contributor Author

rosefromthedead commented Dec 4, 2025

That works for me, too. I was wary that that approach might not be worth the churn, but it could make for a more sane interface.

@rosefromthedead
Copy link
Contributor Author

Actually, I think the performance cost is negligible, so it's not really worth splitting into two. What do you think?

@rjmccall
Copy link
Contributor

rjmccall commented Dec 4, 2025

getCanonicalTypeWithoutLocalQualifiers is basically just loading the canonical type field from the Type object, so it's an extremely cheap operation for what it is, whereas the other is a non-trivial operation that at least needs a call. Are you arguing that it just doesn't matter in practice?

@rosefromthedead
Copy link
Contributor Author

I think it isn't a significant slowdown for getCanonicalTypeUnqualified itself because all functions called by getUnqualifiedType are defined in headers so it all ends up getting inlined, and it's not such an expensive operation in theory. Looking at the generated assembly of one of the call sites it doesn't seem like much, if any, overhead.

I also think it's not a significant slowdown for clang because I'm assuming that this function isn't hot, but I haven't checked.

@rosefromthedead
Copy link
Contributor Author

@rjmccall please could you clarify: do you consider these concerns blocking for this patch?

@rjmccall
Copy link
Contributor

rjmccall commented Dec 9, 2025

Well, I don't have any evidence that this is specifically hot, so I won't insist. Go ahead.

@rosefromthedead
Copy link
Contributor Author

@mizvekov @AaronBallman with the above in mind, does the patch still look good to you? If so, please merge it. I don't have commit rights.

Copy link
Collaborator

@AaronBallman AaronBallman left a comment

Choose a reason for hiding this comment

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

LGTM!

@AaronBallman AaronBallman merged commit 8c49509 into llvm:main Dec 10, 2025
11 checks passed
@github-actions
Copy link

@rosefromthedead Congratulations on having your first Pull Request (PR) merged into the LLVM Project!

Your changes will be combined with recent changes from other authors, then tested by our build bots. If there is a problem with a build, you may receive a report in an email or a comment on this PR.

Please check whether problems have been caused by your change specifically, as the builds can include changes from many authors. It is not uncommon for your change to be included in a build that fails due to someone else's changes, or infrastructure issues.

How to do this, and the rest of the post-merge process, is covered in detail here.

If your change does cause a problem, it may be reverted, or you can revert it yourself. This is a normal part of LLVM development. You can fix your changes and open a new PR to merge them again.

If you don't get any reports, no action is required from you. Your changes are working as expected, well done!

@llvm-ci
Copy link

llvm-ci commented Dec 10, 2025

LLVM Buildbot has detected a new failure on builder clang-m68k-linux-cross running on suse-gary-m68k-cross while building clang at step 4 "build stage 1".

Full details are available at: https://lab.llvm.org/buildbot/#/builders/27/builds/20140

Here is the relevant piece of the build log for the reference
Step 4 (build stage 1) failure: 'ninja' (failure)
...
[225/1437] Building CXX object tools/clang/lib/Parse/CMakeFiles/obj.clangParse.dir/ParseExprCXX.cpp.o
[226/1437] Building CXX object tools/clang/lib/Parse/CMakeFiles/obj.clangParse.dir/ParseTemplate.cpp.o
[227/1437] Building CXX object tools/clang/lib/Parse/CMakeFiles/obj.clangParse.dir/ParseTentative.cpp.o
[228/1437] Building CXX object tools/clang/lib/Parse/CMakeFiles/obj.clangParse.dir/Parser.cpp.o
[229/1437] Building CXX object tools/clang/lib/Parse/CMakeFiles/obj.clangParse.dir/ParseOpenACC.cpp.o
[230/1437] Building CXX object tools/clang/lib/Parse/CMakeFiles/obj.clangParse.dir/ParseExpr.cpp.o
[231/1437] Building CXX object tools/clang/lib/Parse/CMakeFiles/obj.clangParse.dir/ParseHLSLRootSignature.cpp.o
[232/1437] Building CXX object tools/clang/lib/Parse/CMakeFiles/obj.clangParse.dir/ParseStmt.cpp.o
[233/1437] Building CXX object tools/clang/lib/Serialization/CMakeFiles/obj.clangSerialization.dir/TemplateArgumentHasher.cpp.o
[234/1437] Building CXX object tools/clang/lib/Sema/CMakeFiles/obj.clangSema.dir/SemaOpenMP.cpp.o
FAILED: tools/clang/lib/Sema/CMakeFiles/obj.clangSema.dir/SemaOpenMP.cpp.o 
/usr/bin/c++ -DCLANG_EXPORTS -DGTEST_HAS_RTTI=0 -D_DEBUG -D_GLIBCXX_ASSERTIONS -D_GLIBCXX_USE_CXX11_ABI=1 -D_GNU_SOURCE -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS -I/var/lib/buildbot/workers/suse-gary-m68k-cross/clang-m68k-linux-cross/stage1/tools/clang/lib/Sema -I/var/lib/buildbot/workers/suse-gary-m68k-cross/clang-m68k-linux-cross/llvm/clang/lib/Sema -I/var/lib/buildbot/workers/suse-gary-m68k-cross/clang-m68k-linux-cross/llvm/clang/include -I/var/lib/buildbot/workers/suse-gary-m68k-cross/clang-m68k-linux-cross/stage1/tools/clang/include -I/var/lib/buildbot/workers/suse-gary-m68k-cross/clang-m68k-linux-cross/stage1/include -I/var/lib/buildbot/workers/suse-gary-m68k-cross/clang-m68k-linux-cross/llvm/llvm/include -fPIC -fno-semantic-interposition -fvisibility-inlines-hidden -Werror=date-time -Wall -Wextra -Wno-unused-parameter -Wwrite-strings -Wcast-qual -Wno-missing-field-initializers -pedantic -Wno-long-long -Wimplicit-fallthrough -Wno-uninitialized -Wno-nonnull -Wno-class-memaccess -Wno-dangling-reference -Wno-redundant-move -Wno-pessimizing-move -Wno-array-bounds -Wno-stringop-overread -Wno-noexcept-type -Wdelete-non-virtual-dtor -Wsuggest-override -Wno-comment -Wno-misleading-indentation -Wctad-maybe-unsupported -fdiagnostics-color -ffunction-sections -fdata-sections -fno-common -Woverloaded-virtual -O3 -DNDEBUG -std=c++17  -fno-exceptions -funwind-tables -fno-rtti -UNDEBUG -MD -MT tools/clang/lib/Sema/CMakeFiles/obj.clangSema.dir/SemaOpenMP.cpp.o -MF tools/clang/lib/Sema/CMakeFiles/obj.clangSema.dir/SemaOpenMP.cpp.o.d -o tools/clang/lib/Sema/CMakeFiles/obj.clangSema.dir/SemaOpenMP.cpp.o -c /var/lib/buildbot/workers/suse-gary-m68k-cross/clang-m68k-linux-cross/llvm/clang/lib/Sema/SemaOpenMP.cpp
c++: fatal error: Killed signal terminated program cc1plus
compilation terminated.
[235/1437] Building CXX object tools/clang/lib/Serialization/CMakeFiles/obj.clangSerialization.dir/GeneratePCH.cpp.o
[236/1437] Building CXX object tools/clang/lib/Frontend/CMakeFiles/obj.clangFrontend.dir/DependencyFile.cpp.o
[237/1437] Building CXX object tools/clang/lib/Parse/CMakeFiles/obj.clangParse.dir/ParseOpenMP.cpp.o
[238/1437] Building CXX object tools/clang/lib/Frontend/CMakeFiles/obj.clangFrontend.dir/ModuleDependencyCollector.cpp.o
[239/1437] Building CXX object tools/clang/lib/Frontend/CMakeFiles/obj.clangFrontend.dir/TestModuleFileExtension.cpp.o
[240/1437] Building CXX object tools/clang/lib/Sema/CMakeFiles/obj.clangSema.dir/SemaTemplateDeductionGuide.cpp.o
[241/1437] Building CXX object tools/clang/lib/Frontend/CMakeFiles/obj.clangFrontend.dir/ASTMerge.cpp.o
[242/1437] Building CXX object tools/clang/lib/Frontend/CMakeFiles/obj.clangFrontend.dir/ChainedIncludesSource.cpp.o
[243/1437] Building CXX object tools/clang/lib/Serialization/CMakeFiles/obj.clangSerialization.dir/ASTWriterDecl.cpp.o
[244/1437] Building CXX object tools/clang/lib/Frontend/CMakeFiles/obj.clangFrontend.dir/InitPreprocessor.cpp.o
[245/1437] Building CXX object tools/clang/lib/Frontend/CMakeFiles/obj.clangFrontend.dir/PrecompiledPreamble.cpp.o
[246/1437] Building CXX object tools/clang/lib/Serialization/CMakeFiles/obj.clangSerialization.dir/ASTWriterStmt.cpp.o
[247/1437] Building CXX object tools/clang/lib/Serialization/CMakeFiles/obj.clangSerialization.dir/ASTReaderStmt.cpp.o
[248/1437] Building CXX object tools/clang/lib/Parse/CMakeFiles/obj.clangParse.dir/ParsePragma.cpp.o
[249/1437] Building CXX object tools/clang/lib/Frontend/CMakeFiles/obj.clangFrontend.dir/FrontendActions.cpp.o
[250/1437] Building CXX object tools/clang/lib/Frontend/CMakeFiles/obj.clangFrontend.dir/FrontendAction.cpp.o
[251/1437] Building CXX object tools/clang/lib/Frontend/CMakeFiles/obj.clangFrontend.dir/CompilerInstance.cpp.o
[252/1437] Building CXX object tools/clang/lib/Sema/CMakeFiles/obj.clangSema.dir/SemaTemplateDeduction.cpp.o
[253/1437] Building CXX object tools/clang/lib/Frontend/CMakeFiles/obj.clangFrontend.dir/ASTUnit.cpp.o
[254/1437] Building CXX object tools/clang/lib/Sema/CMakeFiles/obj.clangSema.dir/SemaDecl.cpp.o
[255/1437] Building CXX object tools/clang/lib/Frontend/CMakeFiles/obj.clangFrontend.dir/ASTConsumers.cpp.o
[256/1437] Building CXX object tools/clang/lib/Sema/CMakeFiles/obj.clangSema.dir/SemaTemplate.cpp.o
In file included from /var/lib/buildbot/workers/suse-gary-m68k-cross/clang-m68k-linux-cross/llvm/clang/lib/Sema/TreeTransform.h:48,
                 from /var/lib/buildbot/workers/suse-gary-m68k-cross/clang-m68k-linux-cross/llvm/clang/lib/Sema/SemaTemplate.cpp:11:
In constructor ‘clang::LocalInstantiationScope::LocalInstantiationScope(clang::Sema&, bool, bool)’,
    inlined from ‘bool clang::Sema::CheckTemplateArgumentList(clang::TemplateDecl*, clang::TemplateParameterList*, clang::SourceLocation, clang::TemplateArgumentListInfo&, const clang::DefaultArguments&, bool, CheckTemplateArgumentInfo&, bool, bool*)’ at /var/lib/buildbot/workers/suse-gary-m68k-cross/clang-m68k-linux-cross/llvm/clang/lib/Sema/SemaTemplate.cpp:5867:48:
/var/lib/buildbot/workers/suse-gary-m68k-cross/clang-m68k-linux-cross/llvm/clang/include/clang/Sema/Template.h:442:41: warning: storing the address of local variable ‘InstScope’ in ‘*this.clang::Sema::CurrentInstantiationScope’ [-Wdangling-pointer=]
  442 |       SemaRef.CurrentInstantiationScope = this;
      |       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~
/var/lib/buildbot/workers/suse-gary-m68k-cross/clang-m68k-linux-cross/llvm/clang/lib/Sema/SemaTemplate.cpp: In member function ‘bool clang::Sema::CheckTemplateArgumentList(clang::TemplateDecl*, clang::TemplateParameterList*, clang::SourceLocation, clang::TemplateArgumentListInfo&, const clang::DefaultArguments&, bool, CheckTemplateArgumentInfo&, bool, bool*)’:
/var/lib/buildbot/workers/suse-gary-m68k-cross/clang-m68k-linux-cross/llvm/clang/lib/Sema/SemaTemplate.cpp:5867:27: note: ‘InstScope’ declared here
 5867 |   LocalInstantiationScope InstScope(*this, true);
      |                           ^~~~~~~~~
/var/lib/buildbot/workers/suse-gary-m68k-cross/clang-m68k-linux-cross/llvm/clang/lib/Sema/SemaTemplate.cpp:5846:34: note: ‘this’ declared here
 5846 |     bool *ConstraintsNotSatisfied) {

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] Inconsistent __is_pointer_interconvertible_base_of results for direct vs. templated calls

7 participants