Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[clang][ASTImporter] Fix AST import if anonymous namespaces are merged #128735

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
May 6, 2025

Conversation

balazske
Copy link
Collaborator

I discovered one faulty case that is shown in the second of the added tests (an anonymous namespace is imported that resides in a extern "C" block). I did not check for other possibilities for namespaces that are not in a TU or namespace but at least the code should handle all cases.

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

llvmbot commented Feb 25, 2025

@llvm/pr-subscribers-clang

Author: Balázs Kéri (balazske)

Changes

I discovered one faulty case that is shown in the second of the added tests (an anonymous namespace is imported that resides in a extern "C" block). I did not check for other possibilities for namespaces that are not in a TU or namespace but at least the code should handle all cases.


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

2 Files Affected:

  • (modified) clang/lib/AST/ASTImporter.cpp (+4-3)
  • (modified) clang/unittests/AST/ASTImporterTest.cpp (+76)
diff --git a/clang/lib/AST/ASTImporter.cpp b/clang/lib/AST/ASTImporter.cpp
index 43da76e14d0a3..0b6ce3a377207 100644
--- a/clang/lib/AST/ASTImporter.cpp
+++ b/clang/lib/AST/ASTImporter.cpp
@@ -2638,11 +2638,12 @@ ExpectedDecl ASTNodeImporter::VisitNamespaceDecl(NamespaceDecl *D) {
   if (!Name) {
     // This is an anonymous namespace. Adopt an existing anonymous
     // namespace if we can.
-    // FIXME: Not testable.
-    if (auto *TU = dyn_cast<TranslationUnitDecl>(DC))
+    DeclContext *EnclosingDC = DC->getEnclosingNamespaceContext();
+    if (auto *TU = dyn_cast<TranslationUnitDecl>(EnclosingDC))
       MergeWithNamespace = TU->getAnonymousNamespace();
     else
-      MergeWithNamespace = cast<NamespaceDecl>(DC)->getAnonymousNamespace();
+      MergeWithNamespace =
+          cast<NamespaceDecl>(EnclosingDC)->getAnonymousNamespace();
   } else {
     SmallVector<NamedDecl *, 4> ConflictingDecls;
     auto FoundDecls = Importer.findDeclsInToCtx(DC, Name);
diff --git a/clang/unittests/AST/ASTImporterTest.cpp b/clang/unittests/AST/ASTImporterTest.cpp
index 0bac95eb40b20..07714ff45a4de 100644
--- a/clang/unittests/AST/ASTImporterTest.cpp
+++ b/clang/unittests/AST/ASTImporterTest.cpp
@@ -10492,6 +10492,79 @@ TEST_P(ASTImporterOptionSpecificTestBase,
   EXPECT_EQ(ToFr1Imp, ToFr1);
 }
 
+struct ImportAndMergeAnonymousNamespace
+    : public ASTImporterOptionSpecificTestBase {
+protected:
+  void test(const char *ToCode, const char *FromCode) {
+    Decl *ToTU = getToTuDecl(ToCode, Lang_CXX11);
+    Decl *FromTU = getTuDecl(FromCode, Lang_CXX11);
+    auto *FromNS = FirstDeclMatcher<NamespaceDecl>().match(
+        FromTU, namespaceDecl(isAnonymous()));
+    auto *ToNS = FirstDeclMatcher<NamespaceDecl>().match(
+        ToTU, namespaceDecl(isAnonymous()));
+    auto *FromF = FirstDeclMatcher<FunctionDecl>().match(
+        FromTU, functionDecl(hasName("f")));
+    auto *ImportedF = Import(FromF, Lang_CXX11);
+    EXPECT_TRUE(ImportedF);
+    EXPECT_EQ(ImportedF->getDeclContext(), ToNS);
+    auto *ImportedNS = Import(FromNS, Lang_CXX11);
+    EXPECT_EQ(ImportedNS, ToNS);
+  }
+};
+
+TEST_P(ImportAndMergeAnonymousNamespace, NamespaceInTU) {
+  const char *ToCode =
+      R"(
+      namespace {
+      }
+      )";
+  const char *FromCode =
+      R"(
+      namespace {
+        void f();
+      }
+      )";
+  test(ToCode, FromCode);
+}
+
+TEST_P(ImportAndMergeAnonymousNamespace, NamespaceInLinkageSpec) {
+  const char *ToCode =
+      R"(
+      extern "C" {
+      namespace {
+      }
+      }
+      )";
+  const char *FromCode =
+      R"(
+      extern "C" {
+      namespace {
+        void f();
+      }
+      }
+      )";
+  test(ToCode, FromCode);
+}
+
+TEST_P(ImportAndMergeAnonymousNamespace, NamespaceInNamespace) {
+  const char *ToCode =
+      R"(
+      namespace X {
+        namespace {
+        }
+      }
+      )";
+  const char *FromCode =
+      R"(
+      namespace X {
+        namespace {
+          void f();
+        }
+      }
+      )";
+  test(ToCode, FromCode);
+}
+
 INSTANTIATE_TEST_SUITE_P(ParameterizedTests, ASTImporterLookupTableTest,
                          DefaultTestValuesForRunOptions);
 
@@ -10578,6 +10651,9 @@ INSTANTIATE_TEST_SUITE_P(ParameterizedTests, ImportMatrixType,
 INSTANTIATE_TEST_SUITE_P(ParameterizedTests, ImportTemplateParmDeclDefaultValue,
                          DefaultTestValuesForRunOptions);
 
+ISTANTIATE_TEST_SUITE_P(ParameterizedTests, ImportAndMergeAnonymousNamespace,
+                        DefaultTestValuesForRunOptions);
+
 // FIXME: Make ImportOpenCLPipe test work.
 // INSTANTIATE_TEST_SUITE_P(ParameterizedTests, ImportOpenCLPipe,
 //                          DefaultTestValuesForRunOptions);

Copy link
Collaborator

@shafik shafik left a comment

Choose a reason for hiding this comment

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

I did not fully understand this part of the summary I did not check for other possibilities for namespaces that are not in a TU or namespace but at least the code should handle all cases but I take it there are cases you are not testing?

Is there a reason not to add those tests and at least if they don't pass, they can fixed a follow-up PR but we then at least have failing tests we know we need to address?

@balazske
Copy link
Collaborator Author

I meant that I did not verify where a namespace declaration is allowed, if it is allowed with another declaration as parent (which is not a LinkageSpecDecl and not NamespaceDecl and not TranslationUnitDecl). It looks likely that no other parent is possible.

Copy link
Contributor

@NagyDonat NagyDonat left a comment

Choose a reason for hiding this comment

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

This commit looks good to me, although I'm not very familiar with this area, so perhaps wait a few days for potential other feedback.

I agree that the sentence "I did not check for other possibilities for namespaces that are not in a TU or namespace but at least the code should handle all cases." should be removed from the commit message because it's confusing, but I agree that it's OK to use cast<NamespaceDecl> in the code (and there is no need to mention this explicitly).

As I read the testcases I became a bit curious about the situations where the target and the source contain anonymous namespaces in different contexts (directly in the TU VS in an extern "C" block VS in another namespace). What happens in those situations? Do I understand it correctly that the anonymous namespaces are not merged and should not be merged? If these are not stupid questions and this is not already tested elsewhere, then perhaps it would be nice to add a few testcases for it.

@NagyDonat NagyDonat requested a review from shafik April 24, 2025 09:00
@balazske
Copy link
Collaborator Author

The current behavior is the same for named or anonymous namespace: If there is an existing namespace with same name in the target ("To") TU, the imported one is merged into this (this is what was wrong before this fix, the anonymous namespace was not merged if it was inside a extern "C" block). Probably not this is the best behavior (for anonymous namespace), but there is only one "anonymous namespace" for a single TU so it is not even possible to do it other way (without a difficult renaming).

@NagyDonat
Copy link
Contributor

NagyDonat commented Apr 28, 2025

If there is only one anonymous namespace for each TU, then what does the AST import do in a situation like

TEST_P(ImportAndMergeAnonymousNamespace, NamespaceInLinkageSpec) {
  const char *ToCode =
      R"(
      extern "C" {
      namespace {
      }
      }
      )";
  const char *FromCode =
      R"(
      namespace {
        void f();
      }
      )";
  test(ToCode, FromCode);
}

where one side is within an extern "C" block and the other is not?

Are these two anonymous namespaces merged by the ASTImporter code? Does this behavior cause incorrect/problematic behavior?

@balazske
Copy link
Collaborator Author

I am now not really sure what happens, if there is a single anonymous namespace for a TU or there are separate ones for the anonymous namespaces in for example extern "C" blocks (and how it should work according to the C++ standard). Probably adding more tests where there is a name conflict will show what happens. The ideal solution looks like to separate the imported anonymous namespace from the existing one but this may be difficult to implement.

@balazske balazske merged commit 4b30b3f into llvm:main May 6, 2025
11 checks passed
GeorgeARM pushed a commit to GeorgeARM/llvm-project that referenced this pull request May 7, 2025
llvm#128735)

Fix of a faulty case that is shown in the second of the added
tests (an anonymous namespace is imported that resides in a `extern "C"`
block).
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.

4 participants