Author: Chuanqi Xu
Date: 2026-09-21T02:54:59Z
New Revision: f970f59a9d4e6b8738547d01bc33e8a12df5244b

URL: 
https://github.com/llvm/llvm-project/commit/f970f59a9d4e6b8738547d01bc33e8a12df5244b
DIFF: 
https://github.com/llvm/llvm-project/commit/f970f59a9d4e6b8738547d01bc33e8a12df5244b.diff

LOG: [C++20] [Modules] Profiling non dependent noexcept expression by pointer 
(#224528)

Another approach to solve
https://github.com/llvm/llvm-project/issues/191361 and
https://github.com/llvm/llvm-project/issues/224180 than
https://github.com/llvm/llvm-project/pull/222148

I want to avoid competing PR but I do feel the original one's approach
seems too workaround. And I want to emphasize that, in my experience,
finding the root cause is much more harder than find the solution to me.
Many thanks to @ispeters

For the approach itself, I described them in the comments of the PR.

Note that the real problem is we were profiling something is not
completely deserialized, so that even if it is not crash, the logic is
not correct.

Added: 
    clang/test/Modules/concept-specialization-deserialization.cppm

Modified: 
    clang/lib/AST/Type.cpp
    clang/unittests/Serialization/LoadSpecLazilyTest.cpp

Removed: 
    


################################################################################
diff  --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index 439625504657d..5fdac154725c3 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -4177,7 +4177,19 @@ void FunctionProtoType::Profile(llvm::FoldingSetNodeID 
&ID, QualType Result,
   if (epi.ExceptionSpec.Type == EST_Dynamic) {
     for (QualType Ex : epi.ExceptionSpec.Exceptions)
       ID.AddPointer(Ex.getAsOpaquePtr());
-  } else if (isComputedNoexcept(epi.ExceptionSpec.Type)) {
+  } else if (epi.ExceptionSpec.Type == EST_NoexceptTrue ||
+             epi.ExceptionSpec.Type == EST_NoexceptFalse) {
+    // If the exception type has already been determined, we can use the
+    // address of the expression as profiling results instead of profiling the
+    // expression.
+    //
+    // This is not only an optimization but avoids an access on uninitialized
+    // fields during the profiling.
+    //
+    // See clang/test/Modules/concept-specialization-deserialization.cppm for
+    // an example.
+    ID.AddPointer(epi.ExceptionSpec.NoexceptExpr);
+  } else if (epi.ExceptionSpec.Type == EST_DependentNoexcept) {
     // getFunctionTypeInternal compares noexcept expressions after the lookup,
     // so the key only needs their canonical form.
     epi.ExceptionSpec.NoexceptExpr->Profile(ID, Context, /*Canonical=*/true);

diff  --git a/clang/test/Modules/concept-specialization-deserialization.cppm 
b/clang/test/Modules/concept-specialization-deserialization.cppm
new file mode 100644
index 0000000000000..876008e1e5ece
--- /dev/null
+++ b/clang/test/Modules/concept-specialization-deserialization.cppm
@@ -0,0 +1,37 @@
+// RUN: mkdir -p %t
+// RUN: split-file %s %t
+//
+// RUN: %clang_cc1 -std=c++20 -emit-module-interface %t/a.cppm -o %t/a.pcm
+// RUN: %clang_cc1 -std=c++20 -fmodule-file=a=%t/a.pcm -fsyntax-only 
%t/use.cpp -verify
+//
+// RUN: %clang_cc1 -std=c++20 -emit-reduced-module-interface %t/a.cppm -o 
%t/a.pcm
+// RUN: %clang_cc1 -std=c++20 -fmodule-file=a=%t/a.pcm -fsyntax-only 
%t/use.cpp -verify
+
+//--- a.cppm
+export module a;
+template <class> concept C = true;
+
+template <class T> int fn() noexcept(C<T>);
+export using t = decltype(fn<int>());
+
+//--- use.cpp
+// expected-no-diagnostics
+import a;
+
+// During the deserialization process of fn<int>, the C<int> in noexcept 
expression
+// may be not completely deserialized. This test makes sure that we can handle 
the case.
+//
+// The ordering is:
+//
+//  Deserialize C<int>
+//
+//  Deserializing C<int>'s template argument
+//
+//  Read SubstTemplateTypeParmType::AssociatedDecl in 
readSubstTemplateTypeParmType
+//
+//  Deserialize fn<int>
+//
+//  FunctionProtoType::Profile
+//
+//  but C<int>'s template arguments is not deserialized yet.
+t x;

diff  --git a/clang/unittests/Serialization/LoadSpecLazilyTest.cpp 
b/clang/unittests/Serialization/LoadSpecLazilyTest.cpp
index f55925aeae1f2..4ae85f4f910e6 100644
--- a/clang/unittests/Serialization/LoadSpecLazilyTest.cpp
+++ b/clang/unittests/Serialization/LoadSpecLazilyTest.cpp
@@ -6,6 +6,8 @@
 //
 
//===----------------------------------------------------------------------===//
 
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/Type.h"
 #include "clang/Driver/CreateInvocationFromArgs.h"
 #include "clang/Frontend/CompilerInstance.h"
 #include "clang/Frontend/FrontendAction.h"
@@ -95,6 +97,46 @@ class LoadSpecLazilyTest : public ::testing::Test {
 
 enum class CheckingMode { Forbidden, Required };
 
+TEST_F(LoadSpecLazilyTest, ConceptSpecializationInExceptionSpecification) {
+  GenerateModuleInterface("M", R"cpp(
+export module M;
+template <class> concept C = true;
+template <class T> int fn() noexcept(C<T>);
+template <class T> int gn() noexcept(!C<T>);
+export using t = decltype(fn<int>());
+export using u = decltype(gn<int>());
+  )cpp");
+
+  auto AST = buildASTFromCodeWithArgs(
+      "import M;\nt x;\nu y;",
+      {"-std=c++20", "-fprebuilt-module-path=" + TestDir.str().str()});
+  ASSERT_TRUE(AST);
+  ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred());
+  ASTContext &Ctx = AST->getASTContext();
+
+  // Reading C<int>'s substituted argument loads fn<int>, including its
+  // exception specification, before the argument has been populated. If
+  // constructing the function type profiles C<int> at that point, its
+  // FoldingSet key changes once deserialization completes.
+  SmallVector<const FunctionProtoType *, 4> Types;
+  bool SawNoexceptTrue = false, SawNoexceptFalse = false;
+  for (const auto *T : Ctx.getTypes()) {
+    const auto *F = dyn_cast<FunctionProtoType>(T);
+    if (F && (F->getExceptionSpecType() == EST_NoexceptTrue ||
+              F->getExceptionSpecType() == EST_NoexceptFalse)) {
+      SawNoexceptTrue |= F->getExceptionSpecType() == EST_NoexceptTrue;
+      SawNoexceptFalse |= F->getExceptionSpecType() == EST_NoexceptFalse;
+      Types.push_back(F);
+    }
+  }
+  EXPECT_TRUE(SawNoexceptTrue);
+  EXPECT_TRUE(SawNoexceptFalse);
+  for (const FunctionProtoType *F : Types)
+    EXPECT_EQ(F, Ctx.getFunctionType(F->getReturnType(), F->getParamTypes(),
+                                     F->getExtProtoInfo())
+                     .getTypePtr());
+}
+
 class DeclsReaderListener : public ASTDeserializationListener {
   StringRef SpeficiedName;
   CheckingMode Mode;


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

Reply via email to