Skip to content

Conversation

@ojhunt
Copy link
Contributor

@ojhunt ojhunt commented Apr 24, 2025

When serializing and deserializing a FunctionDecl we don't recover whether or not the decl was a type aware allocator or destroying delete, because in the final PR that information was placed in a side table in ASTContext.

In principle it should be possible to re-do the semantic checks to determine what these flags should be when deserializing, but it seems like the most robust path is simply recording the flags directly in the serialized AST.

@github-actions
Copy link

github-actions bot commented Apr 24, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

When serializing and deserializing a FunctionDecl we don't recover
whether or not the decl was a type aware allocator or destroying
delete, because in the final PR that information was placed in a
side table in ASTContext.

In principle it should be possible to re-do the semantic checks to
determine what these flags should be when deserializing, but it
seems like the most robust path is simply recording the flags
directly in the serialized AST.
@ojhunt ojhunt force-pushed the users/ojhunt/module-deserialize-destroying-and-type-aware-allocators branch from fdb5037 to dec6509 Compare April 24, 2025 01:57
@ojhunt ojhunt self-assigned this Apr 24, 2025
@ojhunt ojhunt marked this pull request as ready for review April 24, 2025 01:57
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:modules C++20 modules and Clang Header Modules labels Apr 24, 2025
@ojhunt ojhunt requested a review from ChuanqiXu9 April 24, 2025 01:58
@llvmbot
Copy link
Member

llvmbot commented Apr 24, 2025

@llvm/pr-subscribers-clang-modules

Author: Oliver Hunt (ojhunt)

Changes

When serializing and deserializing a FunctionDecl we don't recover whether or not the decl was a type aware allocator or destroying delete, because in the final PR that information was placed in a side table in ASTContext.

In principle it should be possible to re-do the semantic checks to determine what these flags should be when deserializing, but it seems like the most robust path is simply recording the flags directly in the serialized AST.


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

7 Files Affected:

  • (modified) clang/lib/Serialization/ASTReaderDecl.cpp (+2)
  • (modified) clang/lib/Serialization/ASTWriterDecl.cpp (+2)
  • (added) clang/test/Modules/Inputs/PR137102/module.modulemap (+1)
  • (added) clang/test/Modules/Inputs/PR137102/type_aware_destroying_new_delete.h (+52)
  • (added) clang/test/Modules/type-aware-destroying-new-and-delete-modules.cpp (+23)
  • (added) clang/test/PCH/Inputs/type_aware_destroying_new_delete.h (+52)
  • (added) clang/test/PCH/type-aware-destroying-new-and-delete-pch.cpp (+27)
diff --git a/clang/lib/Serialization/ASTReaderDecl.cpp b/clang/lib/Serialization/ASTReaderDecl.cpp
index 5545cbc8d608c..0f54aa5c5e062 100644
--- a/clang/lib/Serialization/ASTReaderDecl.cpp
+++ b/clang/lib/Serialization/ASTReaderDecl.cpp
@@ -1076,6 +1076,8 @@ void ASTDeclReader::VisitFunctionDecl(FunctionDecl *FD) {
   FD->setFriendConstraintRefersToEnclosingTemplate(
       FunctionDeclBits.getNextBit());
   FD->setUsesSEHTry(FunctionDeclBits.getNextBit());
+  FD->setIsDestroyingOperatorDelete(FunctionDeclBits.getNextBit());
+  FD->setIsTypeAwareOperatorNewOrDelete(FunctionDeclBits.getNextBit());
 
   FD->EndRangeLoc = readSourceLocation();
   if (FD->isExplicitlyDefaulted())
diff --git a/clang/lib/Serialization/ASTWriterDecl.cpp b/clang/lib/Serialization/ASTWriterDecl.cpp
index 3a7a23481ea98..d1f92cea4dfea 100644
--- a/clang/lib/Serialization/ASTWriterDecl.cpp
+++ b/clang/lib/Serialization/ASTWriterDecl.cpp
@@ -847,6 +847,8 @@ void ASTDeclWriter::VisitFunctionDecl(FunctionDecl *D) {
   FunctionDeclBits.addBit(D->isInstantiatedFromMemberTemplate());
   FunctionDeclBits.addBit(D->FriendConstraintRefersToEnclosingTemplate());
   FunctionDeclBits.addBit(D->usesSEHTry());
+  FunctionDeclBits.addBit(D->isDestroyingOperatorDelete());
+  FunctionDeclBits.addBit(D->isTypeAwareOperatorNewOrDelete());
   Record.push_back(FunctionDeclBits);
 
   Record.AddSourceLocation(D->getEndLoc());
diff --git a/clang/test/Modules/Inputs/PR137102/module.modulemap b/clang/test/Modules/Inputs/PR137102/module.modulemap
new file mode 100644
index 0000000000000..337aff5821e7f
--- /dev/null
+++ b/clang/test/Modules/Inputs/PR137102/module.modulemap
@@ -0,0 +1 @@
+module type_aware_destroying_new_delete { header "type_aware_destroying_new_delete.h" export * }
diff --git a/clang/test/Modules/Inputs/PR137102/type_aware_destroying_new_delete.h b/clang/test/Modules/Inputs/PR137102/type_aware_destroying_new_delete.h
new file mode 100644
index 0000000000000..f96a9ea0c8a41
--- /dev/null
+++ b/clang/test/Modules/Inputs/PR137102/type_aware_destroying_new_delete.h
@@ -0,0 +1,52 @@
+
+namespace std {
+    struct destroying_delete_t { };
+    template <class T> struct type_identity {
+        using type = T;
+    };
+    typedef __SIZE_TYPE__ size_t;
+    enum class align_val_t : size_t;
+};
+
+struct A {
+    A();
+   void *operator new(std::size_t);
+   void operator delete(A*, std::destroying_delete_t);
+};
+
+struct B {
+    B();
+    void *operator new(std::type_identity<B>, std::size_t, std::align_val_t);
+    void operator delete(std::type_identity<B>, void*, std::size_t, std::align_val_t);
+};
+
+struct C {
+    C();
+    template <class T> void *operator new(std::type_identity<T>, std::size_t, std::align_val_t);
+    template <class T> void operator delete(std::type_identity<T>, void*, std::size_t, std::align_val_t);
+};
+
+struct D {
+    D();
+};
+void *operator new(std::type_identity<D>, std::size_t, std::align_val_t);
+void operator delete(std::type_identity<D>, void*, std::size_t, std::align_val_t);
+
+struct E {
+    E();
+};
+template <class T> void *operator new(std::type_identity<T>, std::size_t, std::align_val_t);
+template <class T> void operator delete(std::type_identity<T>, void*, std::size_t, std::align_val_t);
+
+void in_module_tests() {
+  A* a = new A;
+  delete a;
+  B *b = new B;
+  delete b;
+  C *c = new C;
+  delete c;
+  D *d = new D;
+  delete d;
+  E *e = new E;
+  delete e;
+}
diff --git a/clang/test/Modules/type-aware-destroying-new-and-delete-modules.cpp b/clang/test/Modules/type-aware-destroying-new-and-delete-modules.cpp
new file mode 100644
index 0000000000000..e88f8a8791147
--- /dev/null
+++ b/clang/test/Modules/type-aware-destroying-new-and-delete-modules.cpp
@@ -0,0 +1,23 @@
+// RUN: rm -rf %t
+// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -x c++ -std=c++26 -fmodules-cache-path=%t -I %S/Inputs/PR137102 -emit-llvm-only %s
+// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -x c++ -std=c++26 -fmodules-cache-path=%t -I %S/Inputs/PR137102 -emit-llvm-only %s -triple i686-windows
+
+#include "type_aware_destroying_new_delete.h"
+
+
+static void call_in_module_function(void) {
+    in_module_tests();
+}
+
+void out_of_module_tests() {
+    A* a = new A;
+    delete a;
+    B *b = new B;
+    delete b;
+    C *c = new C;
+    delete c;
+    D *d = new D;
+    delete d;
+    E *e = new E;
+    delete e;
+}
diff --git a/clang/test/PCH/Inputs/type_aware_destroying_new_delete.h b/clang/test/PCH/Inputs/type_aware_destroying_new_delete.h
new file mode 100644
index 0000000000000..42d609c0f5c26
--- /dev/null
+++ b/clang/test/PCH/Inputs/type_aware_destroying_new_delete.h
@@ -0,0 +1,52 @@
+
+namespace std {
+    struct destroying_delete_t { };
+    template <class T> struct type_identity {
+        using type = T;
+    };
+    typedef __SIZE_TYPE__ size_t;
+    enum class align_val_t : size_t;
+};
+
+struct A {
+    A();
+   void *operator new(std::size_t);
+   void operator delete(A*, std::destroying_delete_t);
+};
+
+struct B {
+    B();
+    void *operator new(std::type_identity<B>, std::size_t, std::align_val_t);
+    void operator delete(std::type_identity<B>, void*, std::size_t, std::align_val_t);
+};
+
+struct C {
+    C();
+    template <class T> void *operator new(std::type_identity<T>, std::size_t, std::align_val_t);
+    template <class T> void operator delete(std::type_identity<T>, void*, std::size_t, std::align_val_t);
+};
+
+struct D {
+    D();
+};
+void *operator new(std::type_identity<D>, std::size_t, std::align_val_t);
+void operator delete(std::type_identity<D>, void*, std::size_t, std::align_val_t);
+
+struct E {
+    E();
+};
+template <class T> void *operator new(std::type_identity<T>, std::size_t, std::align_val_t);
+template <class T> void operator delete(std::type_identity<T>, void*, std::size_t, std::align_val_t);
+
+void in_pch_tests() {
+  A* a = new A;
+  delete a;
+  B *b = new B;
+  delete b;
+  C *c = new C;
+  delete c;
+  D *d = new D;
+  delete d;
+  E *e = new E;
+  delete e;
+}
diff --git a/clang/test/PCH/type-aware-destroying-new-and-delete-pch.cpp b/clang/test/PCH/type-aware-destroying-new-and-delete-pch.cpp
new file mode 100644
index 0000000000000..d8f7f5dd50c78
--- /dev/null
+++ b/clang/test/PCH/type-aware-destroying-new-and-delete-pch.cpp
@@ -0,0 +1,27 @@
+// Test this without pch.
+// RUN: %clang_cc1 -x c++ -std=c++26 -include %S/Inputs/type_aware_destroying_new_delete.h -emit-llvm -o - %s
+
+// Test with pch.
+// RUN: %clang_cc1 -x c++ -std=c++26 -emit-pch -o %t %S/Inputs/type_aware_destroying_new_delete.h
+// RUN: %clang_cc1 -x c++ -std=c++26 -include-pch %t -emit-llvm -o - %s 
+
+// RUN: %clang_cc1 -x c++ -std=c++11 -emit-pch -fpch-instantiate-templates -o %t %S/Inputs/type_aware_destroying_new_delete.h
+// RUN: %clang_cc1 -x c++ -std=c++11 -include-pch %t -emit-llvm -o - %s
+
+
+static void call_in_pch_function(void) {
+    in_pch_tests();
+}
+
+void out_of_pch_tests() {
+    A* a = new A;
+    delete a;
+    B *b = new B;
+    delete b;
+    C *c = new C;
+    delete c;
+    D *d = new D;
+    delete d;
+    E *e = new E;
+    delete e;
+}

@llvmbot
Copy link
Member

llvmbot commented Apr 24, 2025

@llvm/pr-subscribers-clang

Author: Oliver Hunt (ojhunt)

Changes

When serializing and deserializing a FunctionDecl we don't recover whether or not the decl was a type aware allocator or destroying delete, because in the final PR that information was placed in a side table in ASTContext.

In principle it should be possible to re-do the semantic checks to determine what these flags should be when deserializing, but it seems like the most robust path is simply recording the flags directly in the serialized AST.


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

7 Files Affected:

  • (modified) clang/lib/Serialization/ASTReaderDecl.cpp (+2)
  • (modified) clang/lib/Serialization/ASTWriterDecl.cpp (+2)
  • (added) clang/test/Modules/Inputs/PR137102/module.modulemap (+1)
  • (added) clang/test/Modules/Inputs/PR137102/type_aware_destroying_new_delete.h (+52)
  • (added) clang/test/Modules/type-aware-destroying-new-and-delete-modules.cpp (+23)
  • (added) clang/test/PCH/Inputs/type_aware_destroying_new_delete.h (+52)
  • (added) clang/test/PCH/type-aware-destroying-new-and-delete-pch.cpp (+27)
diff --git a/clang/lib/Serialization/ASTReaderDecl.cpp b/clang/lib/Serialization/ASTReaderDecl.cpp
index 5545cbc8d608c..0f54aa5c5e062 100644
--- a/clang/lib/Serialization/ASTReaderDecl.cpp
+++ b/clang/lib/Serialization/ASTReaderDecl.cpp
@@ -1076,6 +1076,8 @@ void ASTDeclReader::VisitFunctionDecl(FunctionDecl *FD) {
   FD->setFriendConstraintRefersToEnclosingTemplate(
       FunctionDeclBits.getNextBit());
   FD->setUsesSEHTry(FunctionDeclBits.getNextBit());
+  FD->setIsDestroyingOperatorDelete(FunctionDeclBits.getNextBit());
+  FD->setIsTypeAwareOperatorNewOrDelete(FunctionDeclBits.getNextBit());
 
   FD->EndRangeLoc = readSourceLocation();
   if (FD->isExplicitlyDefaulted())
diff --git a/clang/lib/Serialization/ASTWriterDecl.cpp b/clang/lib/Serialization/ASTWriterDecl.cpp
index 3a7a23481ea98..d1f92cea4dfea 100644
--- a/clang/lib/Serialization/ASTWriterDecl.cpp
+++ b/clang/lib/Serialization/ASTWriterDecl.cpp
@@ -847,6 +847,8 @@ void ASTDeclWriter::VisitFunctionDecl(FunctionDecl *D) {
   FunctionDeclBits.addBit(D->isInstantiatedFromMemberTemplate());
   FunctionDeclBits.addBit(D->FriendConstraintRefersToEnclosingTemplate());
   FunctionDeclBits.addBit(D->usesSEHTry());
+  FunctionDeclBits.addBit(D->isDestroyingOperatorDelete());
+  FunctionDeclBits.addBit(D->isTypeAwareOperatorNewOrDelete());
   Record.push_back(FunctionDeclBits);
 
   Record.AddSourceLocation(D->getEndLoc());
diff --git a/clang/test/Modules/Inputs/PR137102/module.modulemap b/clang/test/Modules/Inputs/PR137102/module.modulemap
new file mode 100644
index 0000000000000..337aff5821e7f
--- /dev/null
+++ b/clang/test/Modules/Inputs/PR137102/module.modulemap
@@ -0,0 +1 @@
+module type_aware_destroying_new_delete { header "type_aware_destroying_new_delete.h" export * }
diff --git a/clang/test/Modules/Inputs/PR137102/type_aware_destroying_new_delete.h b/clang/test/Modules/Inputs/PR137102/type_aware_destroying_new_delete.h
new file mode 100644
index 0000000000000..f96a9ea0c8a41
--- /dev/null
+++ b/clang/test/Modules/Inputs/PR137102/type_aware_destroying_new_delete.h
@@ -0,0 +1,52 @@
+
+namespace std {
+    struct destroying_delete_t { };
+    template <class T> struct type_identity {
+        using type = T;
+    };
+    typedef __SIZE_TYPE__ size_t;
+    enum class align_val_t : size_t;
+};
+
+struct A {
+    A();
+   void *operator new(std::size_t);
+   void operator delete(A*, std::destroying_delete_t);
+};
+
+struct B {
+    B();
+    void *operator new(std::type_identity<B>, std::size_t, std::align_val_t);
+    void operator delete(std::type_identity<B>, void*, std::size_t, std::align_val_t);
+};
+
+struct C {
+    C();
+    template <class T> void *operator new(std::type_identity<T>, std::size_t, std::align_val_t);
+    template <class T> void operator delete(std::type_identity<T>, void*, std::size_t, std::align_val_t);
+};
+
+struct D {
+    D();
+};
+void *operator new(std::type_identity<D>, std::size_t, std::align_val_t);
+void operator delete(std::type_identity<D>, void*, std::size_t, std::align_val_t);
+
+struct E {
+    E();
+};
+template <class T> void *operator new(std::type_identity<T>, std::size_t, std::align_val_t);
+template <class T> void operator delete(std::type_identity<T>, void*, std::size_t, std::align_val_t);
+
+void in_module_tests() {
+  A* a = new A;
+  delete a;
+  B *b = new B;
+  delete b;
+  C *c = new C;
+  delete c;
+  D *d = new D;
+  delete d;
+  E *e = new E;
+  delete e;
+}
diff --git a/clang/test/Modules/type-aware-destroying-new-and-delete-modules.cpp b/clang/test/Modules/type-aware-destroying-new-and-delete-modules.cpp
new file mode 100644
index 0000000000000..e88f8a8791147
--- /dev/null
+++ b/clang/test/Modules/type-aware-destroying-new-and-delete-modules.cpp
@@ -0,0 +1,23 @@
+// RUN: rm -rf %t
+// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -x c++ -std=c++26 -fmodules-cache-path=%t -I %S/Inputs/PR137102 -emit-llvm-only %s
+// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -x c++ -std=c++26 -fmodules-cache-path=%t -I %S/Inputs/PR137102 -emit-llvm-only %s -triple i686-windows
+
+#include "type_aware_destroying_new_delete.h"
+
+
+static void call_in_module_function(void) {
+    in_module_tests();
+}
+
+void out_of_module_tests() {
+    A* a = new A;
+    delete a;
+    B *b = new B;
+    delete b;
+    C *c = new C;
+    delete c;
+    D *d = new D;
+    delete d;
+    E *e = new E;
+    delete e;
+}
diff --git a/clang/test/PCH/Inputs/type_aware_destroying_new_delete.h b/clang/test/PCH/Inputs/type_aware_destroying_new_delete.h
new file mode 100644
index 0000000000000..42d609c0f5c26
--- /dev/null
+++ b/clang/test/PCH/Inputs/type_aware_destroying_new_delete.h
@@ -0,0 +1,52 @@
+
+namespace std {
+    struct destroying_delete_t { };
+    template <class T> struct type_identity {
+        using type = T;
+    };
+    typedef __SIZE_TYPE__ size_t;
+    enum class align_val_t : size_t;
+};
+
+struct A {
+    A();
+   void *operator new(std::size_t);
+   void operator delete(A*, std::destroying_delete_t);
+};
+
+struct B {
+    B();
+    void *operator new(std::type_identity<B>, std::size_t, std::align_val_t);
+    void operator delete(std::type_identity<B>, void*, std::size_t, std::align_val_t);
+};
+
+struct C {
+    C();
+    template <class T> void *operator new(std::type_identity<T>, std::size_t, std::align_val_t);
+    template <class T> void operator delete(std::type_identity<T>, void*, std::size_t, std::align_val_t);
+};
+
+struct D {
+    D();
+};
+void *operator new(std::type_identity<D>, std::size_t, std::align_val_t);
+void operator delete(std::type_identity<D>, void*, std::size_t, std::align_val_t);
+
+struct E {
+    E();
+};
+template <class T> void *operator new(std::type_identity<T>, std::size_t, std::align_val_t);
+template <class T> void operator delete(std::type_identity<T>, void*, std::size_t, std::align_val_t);
+
+void in_pch_tests() {
+  A* a = new A;
+  delete a;
+  B *b = new B;
+  delete b;
+  C *c = new C;
+  delete c;
+  D *d = new D;
+  delete d;
+  E *e = new E;
+  delete e;
+}
diff --git a/clang/test/PCH/type-aware-destroying-new-and-delete-pch.cpp b/clang/test/PCH/type-aware-destroying-new-and-delete-pch.cpp
new file mode 100644
index 0000000000000..d8f7f5dd50c78
--- /dev/null
+++ b/clang/test/PCH/type-aware-destroying-new-and-delete-pch.cpp
@@ -0,0 +1,27 @@
+// Test this without pch.
+// RUN: %clang_cc1 -x c++ -std=c++26 -include %S/Inputs/type_aware_destroying_new_delete.h -emit-llvm -o - %s
+
+// Test with pch.
+// RUN: %clang_cc1 -x c++ -std=c++26 -emit-pch -o %t %S/Inputs/type_aware_destroying_new_delete.h
+// RUN: %clang_cc1 -x c++ -std=c++26 -include-pch %t -emit-llvm -o - %s 
+
+// RUN: %clang_cc1 -x c++ -std=c++11 -emit-pch -fpch-instantiate-templates -o %t %S/Inputs/type_aware_destroying_new_delete.h
+// RUN: %clang_cc1 -x c++ -std=c++11 -include-pch %t -emit-llvm -o - %s
+
+
+static void call_in_pch_function(void) {
+    in_pch_tests();
+}
+
+void out_of_pch_tests() {
+    A* a = new A;
+    delete a;
+    B *b = new B;
+    delete b;
+    C *c = new C;
+    delete c;
+    D *d = new D;
+    delete d;
+    E *e = new E;
+    delete e;
+}

@ojhunt
Copy link
Contributor Author

ojhunt commented Apr 24, 2025

Ok, added PCH and module tests - verified both tests fail without this fix to the serialization, and they pass now.

Copy link
Contributor

@alexfh alexfh left a comment

Choose a reason for hiding this comment

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

Thanks for the fix! I verified that this fixes both kinds of issues we're seeing after #113510.

@alexfh
Copy link
Contributor

alexfh commented Apr 24, 2025

I hope you won't mind if I merge the fix to get us unblocked. It looks rather uncontroversial and feedback (if any) can be addressed post-commit.

@alexfh alexfh merged commit 0975c09 into llvm:main Apr 24, 2025
13 of 14 checks passed
IanWood1 pushed a commit to IanWood1/llvm-project that referenced this pull request May 6, 2025
llvm#137102)

When serializing and deserializing a FunctionDecl we don't recover
whether or not the decl was a type aware allocator or destroying delete,
because in the final PR that information was placed in a side table in
ASTContext.

In principle it should be possible to re-do the semantic checks to
determine what these flags should be when deserializing, but it seems
like the most robust path is simply recording the flags directly in the
serialized AST.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

clang:modules C++20 modules and Clang Header Modules clang Clang issues not falling into any other category

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants