Skip to content

[C++20][Modules] Fix crash/compiler error due broken AST links #123648

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

Conversation

dmpolukhin
Copy link
Contributor

Summary:
This PR fixes bugreport #122493 The root problem is the same as before lambda function and DeclRefExpr references a variable that does not belong to the same module as the enclosing function body. Therefore iteration over the function body doesn’t visit the VarDecl. Before this change RelatedDeclsMap was created only for canonical decl but in reality it has to be done for the definition of the function that does not always match the canonical decl.

Test Plan: check-clang

Summary:
This PR fixes bugreport llvm#122493
The root problem is the same as before lambda function and DeclRefExpr references
a variable that does not belong to the same module as the enclosing function body.
Therefore iteration over the function body doesn’t visit the VarDecl. Before this
change RelatedDeclsMap was created only for canonical decl but in reality it has to
be done for the definition of the function that does not always match the canonical decl.

Test Plan: check-clang
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:modules C++20 modules and Clang Header Modules labels Jan 20, 2025
@llvmbot
Copy link
Member

llvmbot commented Jan 20, 2025

@llvm/pr-subscribers-clang

@llvm/pr-subscribers-clang-modules

Author: Dmitry Polukhin (dmpolukhin)

Changes

Summary:
This PR fixes bugreport #122493 The root problem is the same as before lambda function and DeclRefExpr references a variable that does not belong to the same module as the enclosing function body. Therefore iteration over the function body doesn’t visit the VarDecl. Before this change RelatedDeclsMap was created only for canonical decl but in reality it has to be done for the definition of the function that does not always match the canonical decl.

Test Plan: check-clang


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

2 Files Affected:

  • (modified) clang/lib/Serialization/ASTWriterDecl.cpp (+3-2)
  • (added) clang/test/Headers/crash-instantiated-in-scope-cxx-modules5.cpp (+92)
diff --git a/clang/lib/Serialization/ASTWriterDecl.cpp b/clang/lib/Serialization/ASTWriterDecl.cpp
index f8ed155ca389d7..371b4b29e85991 100644
--- a/clang/lib/Serialization/ASTWriterDecl.cpp
+++ b/clang/lib/Serialization/ASTWriterDecl.cpp
@@ -1571,9 +1571,10 @@ void ASTDeclWriter::VisitCXXRecordDecl(CXXRecordDecl *D) {
     } else {
       Record.push_back(0);
     }
-    // For lambdas inside canonical FunctionDecl remember the mapping.
+    // For lambdas inside template functions, remember the mapping to
+    // deserialize them together.
     if (auto FD = llvm::dyn_cast_or_null<FunctionDecl>(D->getDeclContext());
-        FD && FD->isCanonicalDecl()) {
+        FD && FD->isDependentContext() && FD->isThisDeclarationADefinition()) {
       Writer.RelatedDeclsMap[Writer.GetDeclRef(FD)].push_back(
           Writer.GetDeclRef(D));
     }
diff --git a/clang/test/Headers/crash-instantiated-in-scope-cxx-modules5.cpp b/clang/test/Headers/crash-instantiated-in-scope-cxx-modules5.cpp
new file mode 100644
index 00000000000000..ca80a3fd21dd00
--- /dev/null
+++ b/clang/test/Headers/crash-instantiated-in-scope-cxx-modules5.cpp
@@ -0,0 +1,92 @@
+// RUN: rm -fR %t
+// RUN: split-file %s %t
+// RUN: cd %t
+// RUN: %clang_cc1 -verify -std=c++20 -xc++ -emit-module module.cppmap -fmodule-name=mock_resolver -o mock_resolver.pcm
+// RUN: %clang_cc1 -verify -std=c++20 -xc++ -emit-module module.cppmap -fmodule-name=sql_internal -o sql_internal.pcm
+// RUN: %clang_cc1 -verify -std=c++20 -xc++ -fmodule-file=mock_resolver.pcm -fmodule-file=sql_internal.pcm main.cc -o main.o
+
+//--- module.cppmap
+module "mock_resolver" {
+  export *
+  module "mock_resolver.h" {
+    export *
+    header "mock_resolver.h"
+  }
+}
+
+module "sql_internal" {
+  export *
+  module "sql_transform_builder.h" {
+    export *
+    header "sql_transform_builder.h"
+  }
+}
+
+//--- set_bits2.h
+// expected-no-diagnostics
+#pragma once
+
+template <typename T>
+void fwd(const T& x) {}
+
+namespace vox::bitset {
+
+template <typename TFunc>
+void ForEachSetBit2(const TFunc&) {
+  fwd([](int) {
+    const int bit_index_base = 0;
+    (void)[&](int) {
+      int v = bit_index_base;
+    };
+  });
+}
+
+}  // namespace vox::bitset
+
+//--- sql_transform_builder.h
+// expected-no-diagnostics
+#pragma once
+
+#include "set_bits2.h"
+
+class QualifyingSet3 {
+ public:
+  void GetIndexes() const {
+    vox::bitset::ForEachSetBit2([]() {});
+  }
+};
+
+template <typename T>
+void DoTransform() {
+  vox::bitset::ForEachSetBit2([]() {});
+}
+
+//--- mock_resolver.h
+// expected-no-diagnostics
+#pragma once 
+#include "set_bits2.h"
+
+class QualifyingSet2 {
+ public:
+  void GetIndexes() const {
+    vox::bitset::ForEachSetBit2([]() {});
+  }
+};
+
+//--- main.cc
+// expected-no-diagnostics
+#include "sql_transform_builder.h"
+
+template <typename Callable>
+void get(const Callable& fn) {
+  fwd<Callable>(fn);
+}
+
+namespace {
+
+void test() {
+  get([]() {});
+  DoTransform<int>();
+}
+
+} // namespace

Copy link
Member

@ChuanqiXu9 ChuanqiXu9 left a comment

Choose a reason for hiding this comment

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

LGTM

Copy link
Collaborator

@hokein hokein left a comment

Choose a reason for hiding this comment

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

before this change RelatedDeclsMap was created only for canonical decl but in reality it has to be done for the definition of the function that does not always match the canonical decl.

I'm new to the module codebase, and it is not obvious to me. Could you elaborate on this? Perhaps you could use the provided test case to illustrate the distinction.

Copy link
Collaborator

@hokein hokein left a comment

Choose a reason for hiding this comment

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

We have initiated tests to verify this fix within our codebase. It will take some time to get the results.

@dmpolukhin
Copy link
Contributor Author

On our codebase I see no difference with and without this fix but at least it seems that it doesn't break anything. @hokein please let me know the results of your testing when ready. I'll wait before merging this PR.

Copy link
Collaborator

@hokein hokein left a comment

Choose a reason for hiding this comment

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

I might be missing some historical context, but am I correct in understanding that #104512, #109167, #111992, and this patch together form a complete (and hopefully final) fix for the crashes that occur when a function and its associated lambda are loaded from different modules?

And we seem to miss a release note in llvm-project/clang/docs/ReleaseNotes.rst.

/// to ensure that the definition for related decls comes from the same module
/// as the enclosing main decl. Without this, due to lazy deserialization,
/// the definition for the main decl and related decls may come from different
/// modules.
Copy link
Collaborator

Choose a reason for hiding this comment

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

If I understand correctly, this map is used to ensure that the main declaration and its related declarations are always deserialized from the same module in the following cases:

  • Lambda inside a function declaration: The main declaration is the enclosing function, and the related declarations are the lambda declarations.
  • Defined friend declaration inside a CXXRecord declaration: The main declaration is the enclosing record, and the related declarations are the friend declarations.

Is my understanding correct?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, that's right understanding. Initially it was only for lambda functions but later case with inline friend functions were identified. The mechanism in its current form is very generic so it can be used for other case if we find them.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Thanks for confirming.

I think it would be worth documenting this (it took me some time to fully understand what RelatedDeclsMap is and how it's used). Clear documentation could help others in the future.

@hokein
Copy link
Collaborator

hokein commented Jan 22, 2025

On our codebase I see no difference with and without this fix but at least it seems that it doesn't break anything. @hokein please let me know the results of your testing when ready. I'll wait before merging this PR.

Our tests were completed today, and the results look good. There are no new breakages, and the patch fixes the issue we encountered in #122493. I think this patch is good.

Copy link
Contributor Author

@dmpolukhin dmpolukhin left a comment

Choose a reason for hiding this comment

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

Added release note

/// to ensure that the definition for related decls comes from the same module
/// as the enclosing main decl. Without this, due to lazy deserialization,
/// the definition for the main decl and related decls may come from different
/// modules.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, that's right understanding. Initially it was only for lambda functions but later case with inline friend functions were identified. The mechanism in its current form is very generic so it can be used for other case if we find them.

Copy link
Collaborator

@hokein hokein 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! It looks good from my side, just small nits. Feel free to land it.

/// to ensure that the definition for related decls comes from the same module
/// as the enclosing main decl. Without this, due to lazy deserialization,
/// the definition for the main decl and related decls may come from different
/// modules.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Thanks for confirming.

I think it would be worth documenting this (it took me some time to fully understand what RelatedDeclsMap is and how it's used). Clear documentation could help others in the future.

// force the definition to be the one inside the definition of the template
// class. Remember this relation to deserialize them together.
if (auto *RD = dyn_cast<CXXRecordDecl>(D->getLexicalParent());
isDefinitionInDependentContext(RD)) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: we seem to miss checking RD is not nullptr

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added but I think it cannot happen after getFriendObjectKind check.

Copy link
Collaborator

@hokein hokein left a comment

Choose a reason for hiding this comment

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

Let's ship it.

@dmpolukhin dmpolukhin merged commit cad6bba into llvm:main Jan 23, 2025
9 checks passed
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.

4 participants