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][Sema] fix outline member function template with default align crash #80288

Conversation

jcsxky
Copy link
Contributor

@jcsxky jcsxky commented Feb 1, 2024

Try to fix issue and some extented problem. Root cause of current issue is that error handling in instantiation of function parameter with default initialization on sizeof or align expression. When instance an out-of-line template member function, depth of TemplateTypeParmDecl in default initialization doesn't change while depth of other template parameter does and this will lead to some template parameter uninstanced. Also, sometime it will leader to wrong instantiation when it uses the template parameter of the template class.
Fix it by add template args of context. This will make MultiLevelTemplateArgumentList::getNumLevels matching the depth of template parameter. Testcase with some static_assert demonstrates the template parameter has been instanced correctly.
But, the default initialization of lambda expression compiles failed when only checking if the member function is out-of-line. We should check the PrimaryFunctionTemplateDecl of the funtion if it's out-of-line.

@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" labels Feb 1, 2024
@llvmbot
Copy link
Collaborator

llvmbot commented Feb 1, 2024

@llvm/pr-subscribers-clang

Author: Qizhi Hu (jcsxky)

Changes

Try to fix issue and some extented problem. Root cause of current issue is that error handling in instantiation of function parameter with default initialization on sizeof or align expression. When instance an out-of-line template member function, depth of TemplateTypeParmDecl in default initialization doesn't change while depth of other template parameter does and this will lead to some template parameter uninstanced. Also, sometime it will leader to wrong instantiation when it uses the template parameter of the template class.
Fix it by add template args of context. This will make MultiLevelTemplateArgumentList::getNumLevels matching the depth of template parameter. Testcase with some static_assert demonstrates the template parameter has been instanced correctly.
But, the default initialization of lambda expression compiles failed when only checking if the member function is out-of-line. We should check the PrimaryFunctionTemplateDecl of the funtion if it's out-of-line.


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

3 Files Affected:

  • (modified) clang/docs/ReleaseNotes.rst (+4)
  • (modified) clang/lib/Sema/SemaTemplateInstantiate.cpp (+12-2)
  • (added) clang/test/SemaTemplate/default-parm-init.cpp (+190)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index ec9e3ef07057f..263d64d610950 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -182,6 +182,10 @@ Bug Fixes to C++ Support
   and (`#79745 <https://github.com/llvm/llvm-project/issues/79745>`_)
 - Fix incorrect code generation caused by the object argument of ``static operator()`` and ``static operator[]`` calls not being evaluated.
   Fixes (`#67976 <https://github.com/llvm/llvm-project/issues/67976>`_)
+- Fix a crash when specializing an out-of-line member function with a default
+  parameter where we did an incorrect specialization of the initialization of
+  the default parameter.
+  Fixes (`#68490 <https://github.com/llvm/llvm-project/issues/68490>`_)
 
 Bug Fixes to AST Handling
 ^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/clang/lib/Sema/SemaTemplateInstantiate.cpp b/clang/lib/Sema/SemaTemplateInstantiate.cpp
index e12186d7d82f8..c107702aeba1c 100644
--- a/clang/lib/Sema/SemaTemplateInstantiate.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiate.cpp
@@ -3049,6 +3049,7 @@ bool Sema::SubstDefaultArgument(
     //   default argument expression appears.
     ContextRAII SavedContext(*this, FD);
     std::unique_ptr<LocalInstantiationScope> LIS;
+    MultiLevelTemplateArgumentList NewTemplateArgs = TemplateArgs;
 
     if (ForCallExpr) {
       // When instantiating a default argument due to use in a call expression,
@@ -3061,11 +3062,20 @@ bool Sema::SubstDefaultArgument(
           /*ForDefinition*/ false);
       if (addInstantiatedParametersToScope(FD, PatternFD, *LIS, TemplateArgs))
         return true;
+      const FunctionTemplateDecl *PrimaryTemplate = FD->getPrimaryTemplate();
+      if (PrimaryTemplate && PrimaryTemplate->isOutOfLine()) {
+        TemplateArgumentList *CurrentTemplateArgumentList =
+            TemplateArgumentList::CreateCopy(getASTContext(),
+                                             TemplateArgs.getInnermost());
+        NewTemplateArgs = getTemplateInstantiationArgs(
+            FD, FD->getDeclContext(), /*Final=*/false,
+            CurrentTemplateArgumentList, /*RelativeToPrimary=*/true);
+      }
     }
 
     runWithSufficientStackSpace(Loc, [&] {
-      Result = SubstInitializer(PatternExpr, TemplateArgs,
-                                /*DirectInit*/false);
+      Result = SubstInitializer(PatternExpr, NewTemplateArgs,
+                                /*DirectInit*/ false);
     });
   }
   if (Result.isInvalid())
diff --git a/clang/test/SemaTemplate/default-parm-init.cpp b/clang/test/SemaTemplate/default-parm-init.cpp
new file mode 100644
index 0000000000000..6ad69c39d93dc
--- /dev/null
+++ b/clang/test/SemaTemplate/default-parm-init.cpp
@@ -0,0 +1,190 @@
+// RUN: %clang_cc1 -fsyntax-only -std=c++17 -verify %s
+// RUN: %clang_cc1 -fsyntax-only -std=c++20 -verify %s
+// expected-no-diagnostics
+
+namespace std {
+
+template<typename Signature> class function;
+
+template<typename R, typename... Args> class invoker_base {
+public: 
+  virtual ~invoker_base() { } 
+  virtual R invoke(Args...) = 0; 
+  virtual invoker_base* clone() = 0;
+};
+
+template<typename F, typename R, typename... Args> 
+class functor_invoker : public invoker_base<R, Args...> {
+public: 
+  explicit functor_invoker(const F& f) : f(f) { } 
+  R invoke(Args... args) { return f(args...); } 
+  functor_invoker* clone() { return new functor_invoker(f); }
+
+private:
+  F f;
+};
+
+template<typename R, typename... Args>
+class function<R (Args...)> {
+public: 
+  typedef R result_type;
+  function() : invoker (0) { }
+  function(const function& other) : invoker(0) { 
+    if (other.invoker)
+      invoker = other.invoker->clone();
+  }
+
+  template<typename F> function(const F& f) : invoker(0) {
+    invoker = new functor_invoker<F, R, Args...>(f);
+  }
+
+  ~function() { 
+    if (invoker)
+      delete invoker;
+  }
+
+  function& operator=(const function& other) { 
+    function(other).swap(*this); 
+    return *this;
+  }
+
+  template<typename F> 
+  function& operator=(const F& f) {
+    function(f).swap(*this); 
+    return *this;
+  }
+
+  void swap(function& other) { 
+    invoker_base<R, Args...>* tmp = invoker; 
+    invoker = other.invoker; 
+    other.invoker = tmp;
+  }
+
+  result_type operator()(Args... args) const { 
+    return invoker->invoke(args...);
+  }
+
+private: 
+  invoker_base<R, Args...>* invoker;
+};
+
+}
+
+template<typename TemplateParam>
+struct Problem{
+  template<typename FunctionTemplateParam>
+  constexpr int FuncAlign(int param = alignof(FunctionTemplateParam));
+
+  template<typename FunctionTemplateParam>
+  constexpr int FuncSizeof(int param = sizeof(FunctionTemplateParam));
+
+  template<typename FunctionTemplateParam>
+  constexpr int FuncAlign2(int param = alignof(TemplateParam));
+
+  template<typename FunctionTemplateParam>
+  constexpr int FuncSizeof2(int param = sizeof(TemplateParam));
+};
+
+template<typename TemplateParam>
+struct Problem<TemplateParam*>{
+  template<typename FunctionTemplateParam>
+  constexpr int FuncAlign(int param = alignof(FunctionTemplateParam));
+
+  template<typename FunctionTemplateParam>
+  constexpr int FuncSizeof(int param = sizeof(FunctionTemplateParam));
+
+  template<typename FunctionTemplateParam>
+  constexpr int FuncAlign2(int param = alignof(TemplateParam));
+
+  template<typename FunctionTemplateParam>
+  constexpr int FuncSizeof2(int param = sizeof(TemplateParam));
+};
+
+template<typename TemplateParam>
+template<typename FunctionTemplateParam>
+constexpr int Problem<TemplateParam*>::FuncAlign(int param) {
+	return 2U*param;
+}
+
+template<typename TemplateParam>
+template<typename FunctionTemplateParam>
+constexpr int Problem<TemplateParam*>::FuncSizeof(int param) {
+    return 2U*param;
+}
+
+template<typename TemplateParam>
+template<typename FunctionTemplateParam>
+constexpr int Problem<TemplateParam*>::FuncAlign2(int param) {
+	return 2U*param;
+}
+
+template<typename TemplateParam>
+template<typename FunctionTemplateParam>
+constexpr int Problem<TemplateParam*>::FuncSizeof2(int param) {
+	return 2U*param;
+}
+
+template <>
+template<typename FunctionTemplateParam>
+constexpr int Problem<int>::FuncAlign(int param) {
+	return param;
+}
+
+template <>
+template<typename FunctionTemplateParam>
+constexpr int Problem<int>::FuncSizeof(int param) {
+	return param;
+}
+
+template <>
+template<typename FunctionTemplateParam>
+constexpr int Problem<int>::FuncAlign2(int param) {
+	return param;
+}
+
+template <>
+template<typename FunctionTemplateParam>
+constexpr int Problem<int>::FuncSizeof2(int param) {
+	return param;
+}
+
+void foo(){
+    Problem<int> p = {};
+    static_assert(p.FuncAlign<char>() == alignof(char));
+    static_assert(p.FuncSizeof<char>() == sizeof(char));
+    static_assert(p.FuncAlign2<char>() == alignof(int));
+    static_assert(p.FuncSizeof2<char>() == sizeof(int));
+    Problem<short*> q = {};
+    static_assert(q.FuncAlign<char>() == 2U * alignof(char));
+    static_assert(q.FuncSizeof<char>() == 2U * sizeof(char));
+    static_assert(q.FuncAlign2<char>() == 2U *alignof(short));
+    static_assert(q.FuncSizeof2<char>() == 2U * sizeof(short));
+}
+
+template <typename T>
+class A {
+ public:
+  void run(
+    std::function<void(T&)> f1 = [](auto&&) {},
+    std::function<void(T&)> f2 = [](auto&&) {});
+ private:
+  class Helper {
+   public:
+    explicit Helper(std::function<void(T&)> f2) : f2_(f2) {}
+    std::function<void(T&)> f2_;
+  };
+};
+
+template <typename T>
+void A<T>::run(std::function<void(T&)> f1,
+               std::function<void(T&)> f2) {
+  Helper h(f2);
+}
+
+struct B {};
+
+int main() {
+    A<B> a;
+    a.run([&](auto& l) {});
+    return 0;
+}
\ No newline at end of file

@jcsxky
Copy link
Contributor Author

jcsxky commented Feb 1, 2024

details:

template<typename FunctionTemplateParam>
constexpr int FuncAlign(int param = alignof(FunctionTemplateParam));

template <>
template<typename FunctionTemplateParam>
constexpr int Problem<int>::FuncAlign(int param) { // depth of FunctionTemplateParam in default 
                                       // initialization of  alignof(FunctionTemplateParam) is still 1
	return param;
}

Add template instantiation args of context(TemplateClassSpecialize) to MultiLevelTemplateArgumentList and level of MultiLevelTemplateArgumentList is 2, and make alignof(FunctionTemplateParam) instantiated successfully.

void run(
    std::function<void(T&)> f1 = [](auto&&) {},
    std::function<void(T&)> f2 = [](auto&&) {});
template <typename T>
void A<T>::run(std::function<void(T&)> f1,
               std::function<void(T&)> f2) {
  Helper h(f2);
}

Here we can't add template instantiation args of context because level of MultiLevelTemplateArgumentList in this case is 2 and it's the same with depth of auto&& . This remind us make the condition

const FunctionTemplateDecl *PrimaryTemplate = FD->getPrimaryTemplate();
if (PrimaryTemplate && PrimaryTemplate->isOutOfLine())

I think this should distinguish the first case from others.

@jcsxky jcsxky force-pushed the fix_member_function_template_with_default_align_crash branch from b936099 to 3d7adb1 Compare February 1, 2024 14:06
@jcsxky jcsxky force-pushed the fix_member_function_template_with_default_align_crash branch 2 times, most recently from 88c382b to 329e78b Compare February 2, 2024 01:45
@jcsxky jcsxky force-pushed the fix_member_function_template_with_default_align_crash branch from 329e78b to cfcffbf Compare February 3, 2024 13:10
@jcsxky jcsxky merged commit 752c172 into llvm:main Feb 3, 2024
5 checks passed
agozillon pushed a commit to agozillon/llvm-project that referenced this pull request Feb 5, 2024
… crash (llvm#80288)

Try to fix [issue](llvm#68490 )
and some extented problem. Root cause of current issue is that error
handling in instantiation of function parameter with default
initialization on sizeof or align expression. When instance an
out-of-line template member function, depth of `TemplateTypeParmDecl` in
default initialization doesn't change while depth of other template
parameter does and this will lead to some template parameter
uninstanced. Also, sometime it will leader to wrong instantiation when
it uses the template parameter of the template class.
Fix it by add template args of context. This will make
MultiLevelTemplateArgumentList::getNumLevels matching the depth of
template parameter. Testcase with some static_assert demonstrates the
template parameter has been instanced correctly.
But, the default initialization of lambda expression compiles failed
when only checking if the member function is out-of-line. We should
check the `PrimaryFunctionTemplateDecl` of the funtion if it's
out-of-line.

Co-authored-by: huqizhi <836744285@qq.com>
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] template specialization + member function template with default arg and alignof crash
3 participants