Skip to content

[clang] Substitute alias templates from correct context #75069

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

Merged
merged 2 commits into from
Dec 13, 2023

Conversation

Fznamznon
Copy link
Contributor

Current context set to where alias was met, not where it is declared caused incorrect access check in case alias referenced private members of the parent class.
This is a recommit of 6b1aa31 with a slight modification in order to fix reported regression.

Fixes #41693

Current context set to where alias was met, not where it is declared caused
incorrect access check in case alias referenced private members of the parent
class.
This is a recommit of 6b1aa31 with a slight modification in order to fix
reported regression.

Fixes llvm#41693
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" labels Dec 11, 2023
@llvmbot
Copy link
Member

llvmbot commented Dec 11, 2023

@llvm/pr-subscribers-clang

Author: Mariya Podchishchaeva (Fznamznon)

Changes

Current context set to where alias was met, not where it is declared caused incorrect access check in case alias referenced private members of the parent class.
This is a recommit of 6b1aa31 with a slight modification in order to fix reported regression.

Fixes #41693


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

6 Files Affected:

  • (modified) clang/docs/ReleaseNotes.rst (+3)
  • (modified) clang/include/clang/Sema/Sema.h (+1-1)
  • (modified) clang/lib/Sema/SemaCXXScopeSpec.cpp (+20-4)
  • (modified) clang/lib/Sema/SemaTemplate.cpp (+13-3)
  • (modified) clang/test/CXX/temp/temp.decls/temp.alias/p3.cpp (+3-2)
  • (modified) clang/test/SemaCXX/alias-template.cpp (+65)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index b4b5352a306c1c..1d460ae8375e88 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -677,6 +677,9 @@ Bug Fixes in This Version
   (`#62157 <https://github.com/llvm/llvm-project/issues/62157>`_) and
   (`#64885 <https://github.com/llvm/llvm-project/issues/64885>`_) and
   (`#65568 <https://github.com/llvm/llvm-project/issues/65568>`_)
+- Fixed false positive error emitted when templated alias inside a class
+  used private members of the same class.
+  Fixes (`#41693 <https://github.com/llvm/llvm-project/issues/41693>`_)
 
 Bug Fixes to Compiler Builtins
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index f45e0a7d3d52d4..d8f4b01ee455f1 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -8738,7 +8738,7 @@ class Sema final {
                              SourceLocation IILoc,
                              bool DeducedTSTContext = true);
 
-
+  bool RebuildingTypesInCurrentInstantiation = false;
   TypeSourceInfo *RebuildTypeInCurrentInstantiation(TypeSourceInfo *T,
                                                     SourceLocation Loc,
                                                     DeclarationName Name);
diff --git a/clang/lib/Sema/SemaCXXScopeSpec.cpp b/clang/lib/Sema/SemaCXXScopeSpec.cpp
index 44a40215b90dfb..b3b19b7ed7ffff 100644
--- a/clang/lib/Sema/SemaCXXScopeSpec.cpp
+++ b/clang/lib/Sema/SemaCXXScopeSpec.cpp
@@ -30,6 +30,20 @@ static CXXRecordDecl *getCurrentInstantiationOf(QualType T,
     return nullptr;
 
   const Type *Ty = T->getCanonicalTypeInternal().getTypePtr();
+  if (isa<TemplateSpecializationType>(Ty)) {
+    if (auto *Record = dyn_cast<CXXRecordDecl>(CurContext)) {
+      if (isa<ClassTemplatePartialSpecializationDecl>(Record) ||
+          Record->getDescribedClassTemplate()) {
+        const Type *ICNT = Record->getTypeForDecl();
+        QualType Injected =
+            cast<InjectedClassNameType>(ICNT)->getInjectedSpecializationType();
+
+        if (Ty == Injected->getCanonicalTypeInternal().getTypePtr())
+          return Record;
+      }
+    }
+  }
+
   if (const RecordType *RecordTy = dyn_cast<RecordType>(Ty)) {
     CXXRecordDecl *Record = cast<CXXRecordDecl>(RecordTy->getDecl());
     if (!Record->isDependentContext() ||
@@ -37,10 +51,12 @@ static CXXRecordDecl *getCurrentInstantiationOf(QualType T,
       return Record;
 
     return nullptr;
-  } else if (isa<InjectedClassNameType>(Ty))
-    return cast<InjectedClassNameType>(Ty)->getDecl();
-  else
-    return nullptr;
+  }
+
+  if (auto *ICNT = dyn_cast<InjectedClassNameType>(Ty))
+    return ICNT->getDecl();
+
+  return nullptr;
 }
 
 /// Compute the DeclContext that is associated with the given type.
diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp
index f10abeaba0d451..810a93f37d74e0 100644
--- a/clang/lib/Sema/SemaTemplate.cpp
+++ b/clang/lib/Sema/SemaTemplate.cpp
@@ -39,6 +39,7 @@
 #include "llvm/ADT/SmallBitVector.h"
 #include "llvm/ADT/SmallString.h"
 #include "llvm/ADT/StringExtras.h"
+#include "llvm/Support/SaveAndRestore.h"
 
 #include <iterator>
 #include <optional>
@@ -3990,9 +3991,16 @@ QualType Sema::CheckTemplateIdType(TemplateName Name,
     if (Inst.isInvalid())
       return QualType();
 
-    CanonType = SubstType(Pattern->getUnderlyingType(),
-                          TemplateArgLists, AliasTemplate->getLocation(),
-                          AliasTemplate->getDeclName());
+    if (!RebuildingTypesInCurrentInstantiation) {
+      Sema::ContextRAII SavedContext(*this, Pattern->getDeclContext());
+      CanonType =
+          SubstType(Pattern->getUnderlyingType(), TemplateArgLists,
+                    AliasTemplate->getLocation(), AliasTemplate->getDeclName());
+    } else {
+      CanonType =
+          SubstType(Pattern->getUnderlyingType(), TemplateArgLists,
+                    AliasTemplate->getLocation(), AliasTemplate->getDeclName());
+    }
     if (CanonType.isNull()) {
       // If this was enable_if and we failed to find the nested type
       // within enable_if in a SFINAE context, dig out the specific
@@ -11392,6 +11400,8 @@ TypeSourceInfo *Sema::RebuildTypeInCurrentInstantiation(TypeSourceInfo *T,
   if (!T || !T->getType()->isInstantiationDependentType())
     return T;
 
+  llvm::SaveAndRestore DisableContextSwitchForTypeAliases(
+      RebuildingTypesInCurrentInstantiation, true);
   CurrentInstantiationRebuilder Rebuilder(*this, Loc, Name);
   return Rebuilder.TransformType(T);
 }
diff --git a/clang/test/CXX/temp/temp.decls/temp.alias/p3.cpp b/clang/test/CXX/temp/temp.decls/temp.alias/p3.cpp
index 2d46502e1d9b35..2b33a4ef566dad 100644
--- a/clang/test/CXX/temp/temp.decls/temp.alias/p3.cpp
+++ b/clang/test/CXX/temp/temp.decls/temp.alias/p3.cpp
@@ -2,11 +2,12 @@
 
 // The example given in the standard (this is rejected for other reasons anyway).
 template<class T> struct A;
-template<class T> using B = typename A<T>::U; // expected-error {{no type named 'U' in 'A<T>'}}
+template<class T> using B = typename A<T>::U; // expected-error {{no type named 'U' in 'A<short>'}}
+                                              // expected-note@-1 {{in instantiation of template class 'A<short>' requested here}}
 template<class T> struct A {
   typedef B<T> U; // expected-note {{in instantiation of template type alias 'B' requested here}}
 };
-B<short> b;
+B<short> b; // expected-note {{in instantiation of template type alias 'B' requested here}}
 
 template<typename T> using U = int;
 
diff --git a/clang/test/SemaCXX/alias-template.cpp b/clang/test/SemaCXX/alias-template.cpp
index 5189405e23db56..dca63e15f5bb8a 100644
--- a/clang/test/SemaCXX/alias-template.cpp
+++ b/clang/test/SemaCXX/alias-template.cpp
@@ -192,3 +192,68 @@ int g = sfinae_me<int>(); // expected-error{{no matching function for call to 's
 namespace NullExceptionDecl {
 template<int... I> auto get = []() { try { } catch(...) {}; return I; }; // expected-error{{initializer contains unexpanded parameter pack 'I'}}
 }
+
+namespace GH41693 {
+// No errors when a type alias defined in a class or a friend of a class
+// accesses private members of the same class.
+struct S {
+private:
+  template <typename> static constexpr void Impl() {}
+
+public:
+  template <typename X> using U = decltype(Impl<X>());
+};
+
+using X = S::U<void>;
+struct Y {
+private:
+  static constexpr int x=0;
+
+  template <typename>
+  static constexpr int y=0;
+
+  template <typename>
+  static constexpr int foo();
+
+public:
+  template <typename U>
+  using bar1 = decltype(foo<U>());
+  using bar2 = decltype(x);
+  template <typename U>
+  using bar3 = decltype(y<U>);
+};
+
+
+using type1 = Y::bar1<float>;
+using type2 = Y::bar2;
+using type3 = Y::bar3<float>;
+
+struct theFriend{
+  template<class T>
+  using theAlias = decltype(&T::i);
+};
+
+class theC{
+  int i;
+  public:
+  friend struct theFriend;
+};
+
+int foo(){
+  (void)sizeof(theFriend::theAlias<theC>);
+}
+
+// Test case that regressed with the first iteration of the fix for GH41693.
+template <typename T> class SP {
+    T* data;
+};
+
+template <typename T> class A {
+    static SP<A> foo();
+};
+
+template<typename T> using TRet = SP<A<T>>;
+
+template<typename T> TRet<T> A<T>::foo() { return TRet<T>{};};
+
+}

@erichkeane
Copy link
Collaborator

Can you point out the diff from the previous patch?

@Fznamznon
Copy link
Contributor Author

Can you point out the diff from the previous patch?

To fix the following case:

// Test case that regressed with the first iteration of the fix
template <typename T> class SP {
    T* data;
};

template <typename T> class A {
    static SP<A> foo();
};

template<typename T> using TRet = SP<A<T>>;

template<typename T> TRet<T> A<T>::foo() { return TRet<T>{};}; // failed with error bc TRet and SP<A>
                                                                                      // had different canonical types due to my change.

I had to disable Sema::CurContext switch when the type is rebuilt by Sema::RebuildTypeInCurrentInstantiation routine since its whole point is to rebuild the type looking at it from different context. There is comment above definition of Sema::RebuildTypeInCurrentInstantiation explaining what it does.

The concrete important diff is:

--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -8738,7 +8738,7 @@ public:
                              SourceLocation IILoc,
                              bool DeducedTSTContext = true);

-
+  bool RebuildingTypesInCurrentInstantiation = false;
   TypeSourceInfo *RebuildTypeInCurrentInstantiation(TypeSourceInfo *T,
                                                     SourceLocation Loc,
                                                     DeclarationName Name);

diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp
index 3157700607e0..810a93f37d74 100644
--- a/clang/lib/Sema/SemaTemplate.cpp
+++ b/clang/lib/Sema/SemaTemplate.cpp
@@ -39,6 +39,7 @@
 #include "llvm/ADT/SmallBitVector.h"
 #include "llvm/ADT/SmallString.h"
 #include "llvm/ADT/StringExtras.h"
+#include "llvm/Support/SaveAndRestore.h"

 #include <iterator>
 #include <optional>
@@ -3990,12 +3991,12 @@ QualType Sema::CheckTemplateIdType(TemplateName Name,
     if (Inst.isInvalid())
       return QualType();

-    {
-      bool ForLambdaCallOperator = false;
-      if (const auto *Rec = dyn_cast<CXXRecordDecl>(Pattern->getDeclContext()))
-        ForLambdaCallOperator = Rec->isLambda();
-      Sema::ContextRAII SavedContext(*this, Pattern->getDeclContext(),
-                                     !ForLambdaCallOperator);
+    if (!RebuildingTypesInCurrentInstantiation) {
+      Sema::ContextRAII SavedContext(*this, Pattern->getDeclContext());
+      CanonType =
+          SubstType(Pattern->getUnderlyingType(), TemplateArgLists,
+                    AliasTemplate->getLocation(), AliasTemplate->getDeclName());
+    } else {
       CanonType =
           SubstType(Pattern->getUnderlyingType(), TemplateArgLists,
                     AliasTemplate->getLocation(), AliasTemplate->getDeclName());
@@ -11399,6 +11400,8 @@ TypeSourceInfo *Sema::RebuildTypeInCurrentInstantiation(TypeSourceInfo *T,
   if (!T || !T->getType()->isInstantiationDependentType())
     return T;

+  llvm::SaveAndRestore DisableContextSwitchForTypeAliases(
+      RebuildingTypesInCurrentInstantiation, true);
   CurrentInstantiationRebuilder Rebuilder(*this, Loc, Name);
   return Rebuilder.TransformType(T);
 }

@Fznamznon Fznamznon merged commit dbf67ea into llvm:main Dec 13, 2023
@rupprecht
Copy link
Collaborator

After this commit, I'm seeing another "out-of-line definition" error for operator= on the following reduced snippet:

class Foo {};

template <class X, class Y = void>
struct Stuff;

template <class Unused>
struct Stuff<Foo, Unused> {
  Stuff& operator=(Stuff&& that);
};

template <class T>
using Alias = Stuff<Foo, T>;

template <class T>
Alias<T>& Alias<T>::operator=(Alias<T>&& that) {
  return *this;
}

Results in:

<source>:15:21: error: out-of-line definition of 'operator=' does not match any declaration in 'Stuff<Foo, type-parameter-0-0>'
   15 | Alias<T>& Alias<T>::operator=(Alias<T>&& that) {
      |                     ^~~~~~~~

Is that error expected?

Live conformance view: https://godbolt.org/z/eanhMEevn

I'm also seeing a separate template argument for template type parameter must be a type; did you forget 'typename'? error nearby. I could add typename to appease the compiler, although I'm not sure if it's supposed to be necessary.

@Fznamznon
Copy link
Contributor Author

@rupprecht , no, this is not expected. Please feel free to revert since it is night in my time zone. Otherwise I will revert tomorrow.

MaskRay added a commit that referenced this pull request Dec 14, 2023
…)"

This reverts commit dbf67ea.

It caused spurious "out-of-line definition of 'operator=' does not match
any declaration in" error.
#75069 (comment)
@rupprecht
Copy link
Collaborator

Thanks to @MaskRay for handling the revert.

A reduction for the second typename breakage looks similar, so may be the same underlying issue. But here it is anyway, in case it ends up being a second bug in the patch:

class Foo {};

template <class T>
struct Ptr {};

template <class X, class Y = void>
class Data;

template <class Unused>
struct Data<Foo, Unused> {
  using Type = int;
  void func();
};

template <class T>
using Alias = Data<Foo, T>;

template <class T>
void Alias<T>::func() {
  auto x = Ptr<Alias<T>::Type>();
  (void)x;
}

Results in:

<source>:20:16: error: template argument for template type parameter must be a type; did you forget 'typename'?
   20 |   auto x = Ptr<Alias<T>::Type>();
      |                ^
      |                typename 

Live link, until compiler explorer catches up to the revert: https://godbolt.org/z/zvT4jzqbv

I'd be happy to just add typename in this case if the language says it's supposed to be necessary here, but I'd be just as happy to not need to make any code changes :)

qihangkong pushed a commit to rvgpu/rvgpu-llvm that referenced this pull request Apr 23, 2024
…69)"

This reverts commit dbf67ea.

It caused spurious "out-of-line definition of 'operator=' does not match
any declaration in" error.
llvm/llvm-project#75069 (comment)
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.

Invalid private member diagnostic of template using with decltype of private member method
4 participants