llvmorg-github-actions[bot] wrote:

<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-clang

Author: Yan (likeamahoney)

<details>
<summary>Changes</summary>

Fixes [174301](https://github.com/llvm/llvm-project/issues/174301)

When a dependent qualified type is written without the `typename` keyword (the 
C++20 / extension "implicit typename" recovery), its nested-name-specifier is 
parsed while "entering" a context. In a class or at namespace scope the parser 
cannot yet tell a type-specifier apart from an out-of-line member definition, 
so it parses the prefix in entering mode. As a side effect an intermediate 
dependent member -- e.g. `B` in `A&lt;T&gt;::B::C` - is speculatively resolved 
against the template pattern instead of being left as a dependent name.

Such a resolved nested-name-specifier does not survive instantiation: the 
member is never re-resolved against the instantiated parent, so the qualified 
type stays spuriously dependent in otherwise non-dependent code. That dependent 
type then makes CTAD / `auto` deduction silently bail out, leaving an undeduced 
placeholder that crashes codegen with:

Assertion `!A-&gt;getDeducedType().isNull() &amp;&amp; "cannot request the size 
of an undeduced or dependent auto type"' failed.

Once `getTypeName` knows the qualified-id names a type (the implicit-typename 
path), rebuild the qualifier so that every component resolved against a 
dependent (non current-instantiation) context becomes a `DependentNameType` - 
exactly the type produced when `typename` is written explicitly. This is the 
recovery the existing FIXME in `ActOnTemplateIdType` refers to. Out-of-line 
definitions are parsed as declarators and do not reach this path, so they are 
unaffected. 

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


3 Files Affected:

- (modified) clang/docs/ReleaseNotes.rst (+4) 
- (modified) clang/lib/Sema/SemaDecl.cpp (+91) 
- (added) clang/test/SemaTemplate/implicit-typename-instantiation.cpp (+52) 


``````````diff
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index d4c286644033b..a0764ae606492 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -812,6 +812,10 @@ Bug Fixes to C++ Support
 - Fixed an issue where Clang incorrectly accepted invalid unqualified uses of 
local nested class names outside their declaring scope. (#GH184622)
 - Fixed a crash when parsing invalid friend declaration with storage-class 
specifier. (#GH186569)
 - Fixed a missing vtable for ``dynamic_cast<FinalClass *>(this)`` in a 
function template. (#GH198511)
+- Fixed a crash when a dependent qualified type whose nested-name-specifier 
contains an
+  intermediate dependent member (e.g. ``A<T>::B::C``) was written without the 
``typename``
+  keyword. The intermediate member was speculatively resolved against the 
template pattern and
+  the resulting type spuriously stayed dependent after instantiation. 
(#GH174301)
 
 Bug Fixes to AST Handling
 ^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index d45c3eb35094f..71f9c9f016f8b 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -273,6 +273,82 @@ static ParsedType recoverFromTypeInKnownDependentBase(Sema 
&S,
   return S.CreateParsedType(T, Builder.getTypeSourceInfo(Context, T));
 }
 
+/// A dependent qualified type written without the 'typename' keyword (accepted
+/// as the C++20 / extension "implicit typename" recovery) has its
+/// nested-name-specifier parsed while "entering" a context. As a side effect,
+/// intermediate dependent members -- e.g. 'B' in 'A<T>::B::C' -- are
+/// speculatively resolved against the template pattern instead of being left 
as
+/// dependent names. Such a resolved nested-name-specifier does not survive
+/// template instantiation: the member is never re-resolved against the
+/// instantiated parent, so a spuriously dependent type leaks into otherwise
+/// non-dependent code (and later crashes e.g. CTAD / codegen).
+///
+/// Rebuild the qualifier so that every component resolved against a dependent
+/// (non current-instantiation) context becomes a DependentNameType, matching
+/// the type produced when 'typename' is written explicitly.
+static NestedNameSpecifierLoc
+rebuildDependentlyResolvedQualifier(Sema &S, NestedNameSpecifierLoc QLoc) {
+  if (!QLoc || QLoc.getNestedNameSpecifier().getKind() !=
+                   NestedNameSpecifier::Kind::Type)
+    return QLoc;
+
+  TypeLoc TL = QLoc.castAsTypeLoc();
+
+  // Only members that were resolved to a concrete declaration (typedef, tag, 
or
+  // using) carry a qualifier + name we could re-express as a dependent name.
+  NestedNameSpecifierLoc PrefixLoc;
+  SourceLocation KeywordLoc, NameLoc;
+  const IdentifierInfo *II = nullptr;
+  if (auto T = TL.getAs<TypedefTypeLoc>()) {
+    PrefixLoc = T.getQualifierLoc();
+    KeywordLoc = T.getElaboratedKeywordLoc();
+    NameLoc = T.getNameLoc();
+    II = T.getDecl()->getIdentifier();
+  } else if (auto T = TL.getAs<TagTypeLoc>()) {
+    PrefixLoc = T.getQualifierLoc();
+    KeywordLoc = T.getElaboratedKeywordLoc();
+    NameLoc = T.getNameLoc();
+    II = T.getDecl()->getIdentifier();
+  } else if (auto T = TL.getAs<UsingTypeLoc>()) {
+    PrefixLoc = T.getQualifierLoc();
+    KeywordLoc = T.getElaboratedKeywordLoc();
+    NameLoc = T.getNameLoc();
+    II = T.getDecl()->getIdentifier();
+  } else if (auto T = TL.getAs<UnresolvedUsingTypeLoc>()) {
+    PrefixLoc = T.getQualifierLoc();
+    KeywordLoc = T.getElaboratedKeywordLoc();
+    NameLoc = T.getNameLoc();
+    II = T.getDecl()->getIdentifier();
+  } else {
+    return QLoc;
+  }
+
+  if (!II || !PrefixLoc)
+    return QLoc;
+
+  NestedNameSpecifierLoc NewPrefixLoc =
+      rebuildDependentlyResolvedQualifier(S, PrefixLoc);
+
+  // The member can only be left unresolved (a dependent name) if its prefix is
+  // itself dependent; otherwise the original resolution is correct.
+  if (!NewPrefixLoc.getNestedNameSpecifier().isDependent())
+    return QLoc;
+
+  ASTContext &Ctx = S.Context;
+  QualType DNT = Ctx.getDependentNameType(
+      ElaboratedTypeKeyword::None, NewPrefixLoc.getNestedNameSpecifier(), II);
+
+  TypeLocBuilder TLB;
+  auto DTL = TLB.push<DependentNameTypeLoc>(DNT);
+  DTL.setElaboratedKeywordLoc(KeywordLoc);
+  DTL.setQualifierLoc(NewPrefixLoc);
+  DTL.setNameLoc(NameLoc);
+
+  CXXScopeSpec Rebuilt;
+  Rebuilt.Make(Ctx, TLB.getTypeLocInContext(Ctx, DNT), QLoc.getEndLoc());
+  return Rebuilt.getWithLocInContext(Ctx);
+}
+
 ParsedType Sema::getTypeName(const IdentifierInfo &II, SourceLocation NameLoc,
                              Scope *S, CXXScopeSpec *SS, bool isClassName,
                              bool HasTrailingDot, ParsedType ObjectTypePtr,
@@ -298,6 +374,21 @@ ParsedType Sema::getTypeName(const IdentifierInfo &II, 
SourceLocation NameLoc,
 
     if (!LookupCtx) {
       if (isDependentScopeSpecifier(*SS)) {
+        // The qualifier is dependent and we now know (from the grammar) that
+        // this names a type. If it was parsed while entering a context, an
+        // intermediate dependent member may have been speculatively resolved
+        // against the template pattern; rebuild it as a dependent name so it
+        // survives instantiation. See rebuildDependentlyResolvedQualifier.
+        if (NestedNameSpecifierLoc QLoc = SS->getWithLocInContext(Context)) {
+          NestedNameSpecifierLoc NewQLoc =
+              rebuildDependentlyResolvedQualifier(*this, QLoc);
+          if (NewQLoc.getNestedNameSpecifier() !=
+              QLoc.getNestedNameSpecifier()) {
+            SS->clear();
+            SS->Adopt(NewQLoc);
+          }
+        }
+
         // C++ [temp.res]p3:
         //   A qualified-id that refers to a type and in which the
         //   nested-name-specifier depends on a template-parameter (14.6.2)
diff --git a/clang/test/SemaTemplate/implicit-typename-instantiation.cpp 
b/clang/test/SemaTemplate/implicit-typename-instantiation.cpp
new file mode 100644
index 0000000000000..22c8e23b2d92c
--- /dev/null
+++ b/clang/test/SemaTemplate/implicit-typename-instantiation.cpp
@@ -0,0 +1,52 @@
+// RUN: %clang_cc1 -fsyntax-only -verify=cxx17 -std=c++17 %s
+// RUN: %clang_cc1 -fsyntax-only -verify=cxx20 -std=c++20 %s
+// cxx20-no-diagnostics
+
+// When 'typename' is omitted from a dependent qualified type whose
+// nested-name-specifier contains an intermediate dependent member
+// (e.g. the 'Inner' / 'Node_traits' below), the implicit-typename recovery
+// used to leave the intermediate member speculatively resolved against the
+// template pattern. That nested-name-specifier did not survive instantiation,
+// producing a spuriously dependent type that crashed CTAD and codegen.
+// Omitting and writing 'typename' must produce the same type.
+
+namespace intermediate_typedef {
+template <class T> struct Iter { T *p; };
+template <class T> struct Traits { typedef Iter<T> Const_iterator; };
+template <class T> struct ListBase { typedef Traits<T> Node_traits; };
+
+template <class T> struct List {
+  // cxx17-warning@+1 {{missing 'typename' prior to dependent type name 
'ListBase<T>::Node_traits::Const_iterator' is a C++20 extension}}
+  typedef ListBase<T>::Node_traits::Const_iterator const_iterator;
+  const_iterator begin() { return const_iterator(); }
+};
+
+struct S {};
+template <class U> void use(U) {}
+
+void test() {
+  List<S> lst;
+  Iter it = lst.begin(); // CTAD; previously crashed.
+  use(it);
+  static_assert(__is_same(decltype(it), Iter<S>));
+}
+} // namespace intermediate_typedef
+
+namespace intermediate_tag {
+// Reduced from llvm/llvm-project#174301.
+template <class T> struct Tester {
+  struct Inner { using T2 = int; };
+  static void test();
+};
+
+template <class T>
+// cxx17-warning@+1 {{missing 'typename' prior to dependent type name 
'Tester<T>::Inner::T2' is a C++20 extension}}
+Tester<T>::Inner::T2 getInnerT2() { return {}; }
+
+template <class T> void Tester<T>::test() {
+  auto x = getInnerT2<T>();
+  static_assert(__is_same(decltype(x), int));
+}
+
+template struct Tester<int>;
+} // namespace intermediate_tag

``````````

</details>


https://github.com/llvm/llvm-project/pull/205762
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to