Skip to content

[clang][AST] fix ast-print of extern <lang> with >=2 declarators, fixed, fixed #98795

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

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

temyurchenko
Copy link
Contributor

Relanding of #93913. Now, it also fixes incorrect declaration context in function parameters creation.

@llvmbot llvmbot added clang Clang issues not falling into any other category lldb clang:frontend Language frontend issues, e.g. anything involving "Sema" labels Jul 14, 2024
@llvmbot
Copy link
Member

llvmbot commented Jul 14, 2024

@llvm/pr-subscribers-hlsl
@llvm/pr-subscribers-clang

@llvm/pr-subscribers-lldb

Author: None (temyurchenko)

Changes

Relanding of #93913. Now, it also fixes incorrect declaration context in function parameters creation.


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

7 Files Affected:

  • (modified) clang/lib/AST/Decl.cpp (+21-6)
  • (modified) clang/lib/AST/DeclPrinter.cpp (+41-14)
  • (modified) clang/lib/Sema/SemaDecl.cpp (+1-1)
  • (modified) clang/lib/Sema/SemaExpr.cpp (+1-1)
  • (added) clang/test/AST/ast-print-language-linkage.cpp (+31)
  • (modified) lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.cpp (+1-1)
  • (modified) lldb/source/Plugins/ExpressionParser/Clang/NameSearchContext.cpp (+5-5)
diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp
index 1f19dadafa44e..35973d99f906d 100644
--- a/clang/lib/AST/Decl.cpp
+++ b/clang/lib/AST/Decl.cpp
@@ -576,13 +576,16 @@ template <typename T> static bool isFirstInExternCContext(T *D) {
   return First->isInExternCContext();
 }
 
-static bool isSingleLineLanguageLinkage(const Decl &D) {
-  if (const auto *SD = dyn_cast<LinkageSpecDecl>(D.getDeclContext()))
-    if (!SD->hasBraces())
-      return true;
+static bool isUnbracedLanguageLinkage(const DeclContext *DC) {
+  if (const auto *SD = dyn_cast_if_present<LinkageSpecDecl>(DC))
+    return !SD->hasBraces();
   return false;
 }
 
+static bool hasUnbracedLanguageLinkage(const Decl &D) {
+  return isUnbracedLanguageLinkage(D.getDeclContext());
+}
+
 static bool isDeclaredInModuleInterfaceOrPartition(const NamedDecl *D) {
   if (auto *M = D->getOwningModule())
     return M->isInterfaceOrPartition();
@@ -644,7 +647,7 @@ LinkageComputer::getLVForNamespaceScopeDecl(const NamedDecl *D,
 
       if (Var->getStorageClass() != SC_Extern &&
           Var->getStorageClass() != SC_PrivateExtern &&
-          !isSingleLineLanguageLinkage(*Var))
+          !hasUnbracedLanguageLinkage(*Var))
         return LinkageInfo::internal();
     }
 
@@ -2133,6 +2136,12 @@ VarDecl::VarDecl(Kind DK, ASTContext &C, DeclContext *DC,
                 "ParmVarDeclBitfields too large!");
   static_assert(sizeof(NonParmVarDeclBitfields) <= sizeof(unsigned),
                 "NonParmVarDeclBitfields too large!");
+
+  // The unbraced `extern "C"` invariant is that the storage class
+  // specifier is omitted in the source code, i.e. SC_None (but is,
+  // implicitly, `extern`).
+  assert(!isUnbracedLanguageLinkage(DC) || SC == SC_None);
+
   AllBits = 0;
   VarDeclBits.SClass = SC;
   // Everything else is implicitly initialized to false.
@@ -2316,7 +2325,7 @@ VarDecl::isThisDeclarationADefinition(ASTContext &C) const {
   //   A declaration directly contained in a linkage-specification is treated
   //   as if it contains the extern specifier for the purpose of determining
   //   the linkage of the declared name and whether it is a definition.
-  if (isSingleLineLanguageLinkage(*this))
+  if (hasUnbracedLanguageLinkage(*this))
     return DeclarationOnly;
 
   // C99 6.9.2p2:
@@ -3041,6 +3050,12 @@ FunctionDecl::FunctionDecl(Kind DK, ASTContext &C, DeclContext *DC,
       DeclContext(DK), redeclarable_base(C), Body(), ODRHash(0),
       EndRangeLoc(NameInfo.getEndLoc()), DNLoc(NameInfo.getInfo()) {
   assert(T.isNull() || T->isFunctionType());
+
+  // The unbraced `extern "C"` invariant is that the storage class
+  // specifier is omitted in the source code, i.e. SC_None (but is,
+  // implicitly, `extern`).
+  assert(!isUnbracedLanguageLinkage(DC) || S == SC_None);
+
   FunctionDeclBits.SClass = S;
   FunctionDeclBits.IsInline = isInlineSpecified;
   FunctionDeclBits.IsInlineSpecified = isInlineSpecified;
diff --git a/clang/lib/AST/DeclPrinter.cpp b/clang/lib/AST/DeclPrinter.cpp
index 0cf4e64f83b8d..9250a7f6eceb2 100644
--- a/clang/lib/AST/DeclPrinter.cpp
+++ b/clang/lib/AST/DeclPrinter.cpp
@@ -633,7 +633,7 @@ static void printExplicitSpecifier(ExplicitSpecifier ES, llvm::raw_ostream &Out,
   Out << Proto;
 }
 
-static void MaybePrintTagKeywordIfSupressingScopes(PrintingPolicy &Policy,
+static void maybePrintTagKeywordIfSupressingScopes(PrintingPolicy &Policy,
                                                    QualType T,
                                                    llvm::raw_ostream &Out) {
   StringRef prefix = T->isClassType()       ? "class "
@@ -643,6 +643,22 @@ static void MaybePrintTagKeywordIfSupressingScopes(PrintingPolicy &Policy,
   Out << prefix;
 }
 
+/// Return the language of the linkage spec of `D`, if applicable.
+///
+/// \Return - "C" if `D` has been declared with unbraced `extern "C"`
+///         - "C++" if `D` has been declared with unbraced `extern "C++"`
+///         - nullptr in any other case
+static const char *tryGetUnbracedLinkageLanguage(const Decl *D) {
+  const auto *SD = dyn_cast<LinkageSpecDecl>(D->getDeclContext());
+  if (!SD || SD->hasBraces())
+    return nullptr;
+  if (SD->getLanguage() == LinkageSpecLanguageIDs::C)
+    return "C";
+  assert(SD->getLanguage() == LinkageSpecLanguageIDs::CXX &&
+         "unknown language in linkage specification");
+  return "C++";
+}
+
 void DeclPrinter::VisitFunctionDecl(FunctionDecl *D) {
   if (!D->getDescribedFunctionTemplate() &&
       !D->isFunctionTemplateSpecialization()) {
@@ -662,6 +678,11 @@ void DeclPrinter::VisitFunctionDecl(FunctionDecl *D) {
   CXXConversionDecl *ConversionDecl = dyn_cast<CXXConversionDecl>(D);
   CXXDeductionGuideDecl *GuideDecl = dyn_cast<CXXDeductionGuideDecl>(D);
   if (!Policy.SuppressSpecifiers) {
+    if (const char *Lang = tryGetUnbracedLinkageLanguage(D)) {
+      // the "extern" specifier is implicit
+      assert(D->getStorageClass() == SC_None);
+      Out << "extern \"" << Lang << "\" ";
+    }
     switch (D->getStorageClass()) {
     case SC_None: break;
     case SC_Extern: Out << "extern "; break;
@@ -807,7 +828,7 @@ void DeclPrinter::VisitFunctionDecl(FunctionDecl *D) {
       }
       if (!Policy.SuppressTagKeyword && Policy.SuppressScope &&
           !Policy.SuppressUnwrittenScope)
-        MaybePrintTagKeywordIfSupressingScopes(Policy, AFT->getReturnType(),
+        maybePrintTagKeywordIfSupressingScopes(Policy, AFT->getReturnType(),
                                                Out);
       AFT->getReturnType().print(Out, Policy, Proto);
       Proto.clear();
@@ -932,6 +953,11 @@ void DeclPrinter::VisitVarDecl(VarDecl *D) {
     : D->getASTContext().getUnqualifiedObjCPointerType(D->getType());
 
   if (!Policy.SuppressSpecifiers) {
+    if (const char *Lang = tryGetUnbracedLinkageLanguage(D)) {
+      // the "extern" specifier is implicit
+      assert(D->getStorageClass() == SC_None);
+      Out << "extern \"" << Lang << "\" ";
+    }
     StorageClass SC = D->getStorageClass();
     if (SC != SC_None)
       Out << VarDecl::getStorageClassSpecifierString(SC) << " ";
@@ -961,7 +987,7 @@ void DeclPrinter::VisitVarDecl(VarDecl *D) {
 
   if (!Policy.SuppressTagKeyword && Policy.SuppressScope &&
       !Policy.SuppressUnwrittenScope)
-    MaybePrintTagKeywordIfSupressingScopes(Policy, T, Out);
+    maybePrintTagKeywordIfSupressingScopes(Policy, T, Out);
 
   printDeclType(T, (isa<ParmVarDecl>(D) && Policy.CleanUglifiedParameters &&
                     D->getIdentifier())
@@ -1064,6 +1090,8 @@ void DeclPrinter::VisitNamespaceAliasDecl(NamespaceAliasDecl *D) {
 
 void DeclPrinter::VisitEmptyDecl(EmptyDecl *D) {
   prettyPrintAttributes(D);
+  if (const char *Lang = tryGetUnbracedLinkageLanguage(D))
+    Out << "extern \"" << Lang << "\";";
 }
 
 void DeclPrinter::VisitCXXRecordDecl(CXXRecordDecl *D) {
@@ -1136,22 +1164,21 @@ void DeclPrinter::VisitCXXRecordDecl(CXXRecordDecl *D) {
 }
 
 void DeclPrinter::VisitLinkageSpecDecl(LinkageSpecDecl *D) {
-  const char *l;
+  if (!D->hasBraces()) {
+    VisitDeclContext(D);
+    return;
+  }
+  const char *L;
   if (D->getLanguage() == LinkageSpecLanguageIDs::C)
-    l = "C";
+    L = "C";
   else {
     assert(D->getLanguage() == LinkageSpecLanguageIDs::CXX &&
            "unknown language in linkage specification");
-    l = "C++";
+    L = "C++";
   }
-
-  Out << "extern \"" << l << "\" ";
-  if (D->hasBraces()) {
-    Out << "{\n";
-    VisitDeclContext(D);
-    Indent() << "}";
-  } else
-    Visit(*D->decls_begin());
+  Out << "extern \"" << L << "\" {\n";
+  VisitDeclContext(D);
+  Indent() << "}";
 }
 
 void DeclPrinter::printTemplateParameters(const TemplateParameterList *Params,
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 4b9b735f1cfb4..4e2f02ba7007a 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -2380,7 +2380,7 @@ FunctionDecl *Sema::CreateBuiltin(IdentifierInfo *II, QualType Type,
   }
 
   FunctionDecl *New = FunctionDecl::Create(Context, Parent, Loc, Loc, II, Type,
-                                           /*TInfo=*/nullptr, SC_Extern,
+                                           /*TInfo=*/nullptr, SC_None,
                                            getCurFPFeatures().isFPConstrained(),
                                            false, Type->isFunctionProtoType());
   New->setImplicit();
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 76145f291887c..60075120fe030 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -6286,7 +6286,7 @@ static FunctionDecl *rewriteBuiltinFunctionDecl(Sema *Sema, ASTContext &Context,
   FunctionDecl *OverloadDecl = FunctionDecl::Create(
       Context, Parent, FDecl->getLocation(), FDecl->getLocation(),
       FDecl->getIdentifier(), OverloadTy,
-      /*TInfo=*/nullptr, SC_Extern, Sema->getCurFPFeatures().isFPConstrained(),
+      /*TInfo=*/nullptr, SC_None, Sema->getCurFPFeatures().isFPConstrained(),
       false,
       /*hasPrototype=*/true);
   SmallVector<ParmVarDecl*, 16> Params;
diff --git a/clang/test/AST/ast-print-language-linkage.cpp b/clang/test/AST/ast-print-language-linkage.cpp
new file mode 100644
index 0000000000000..7e4dc3f25f062
--- /dev/null
+++ b/clang/test/AST/ast-print-language-linkage.cpp
@@ -0,0 +1,31 @@
+// RUN: %clang_cc1 -ast-print %s -o - | FileCheck %s
+
+// CHECK: extern "C" int printf(const char *, ...);
+extern "C" int printf(const char *...);
+
+// CHECK: extern "C++" int f(int);
+// CHECK-NEXT: extern "C++" int g(int);
+extern "C++" int f(int), g(int);
+
+// CHECK: extern "C" char a;
+// CHECK-NEXT: extern "C" char b;
+extern "C" char a, b;
+
+// CHECK: extern "C" {
+// CHECK-NEXT:  void foo();
+// CHECK-NEXT:  int x;
+// CHECK-NEXT:  int y;
+// CHECK-NEXT:  extern short z;
+// CHECK-NEXT: }
+extern "C" {
+  void foo(void);
+  int x, y;
+  extern short z;
+}
+
+// CHECK: extern "C" {
+// CHECK-NEXT: }
+extern "C" {}
+
+// CHECK: extern "C++";
+extern "C++";
diff --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.cpp b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.cpp
index 1dd98567f8d69..12aa1bd60bcb7 100644
--- a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.cpp
+++ b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.cpp
@@ -881,7 +881,7 @@ class CodeComplete : public CodeCompleteConsumer {
         else
           ToInsert += "(";
         raw_string_ostream OS(Description);
-        F->print(OS, m_desc_policy, false);
+        F->print(OS, m_desc_policy);
         OS.flush();
       } else if (const VarDecl *V = dyn_cast<VarDecl>(D)) {
         Description = V->getType().getAsString(m_desc_policy);
diff --git a/lldb/source/Plugins/ExpressionParser/Clang/NameSearchContext.cpp b/lldb/source/Plugins/ExpressionParser/Clang/NameSearchContext.cpp
index da59855a9f162..da02e4c72cd19 100644
--- a/lldb/source/Plugins/ExpressionParser/Clang/NameSearchContext.cpp
+++ b/lldb/source/Plugins/ExpressionParser/Clang/NameSearchContext.cpp
@@ -77,7 +77,8 @@ clang::NamedDecl *NameSearchContext::AddFunDecl(const CompilerType &type,
 
   clang::FunctionDecl *func_decl = FunctionDecl::Create(
       ast, context, SourceLocation(), SourceLocation(), decl_name, qual_type,
-      nullptr, SC_Extern, /*UsesFPIntrin=*/false, isInlineSpecified, hasWrittenPrototype,
+      nullptr, SC_None, /*UsesFPIntrin=*/false, isInlineSpecified,
+      hasWrittenPrototype,
       isConstexprSpecified ? ConstexprSpecKind::Constexpr
                            : ConstexprSpecKind::Unspecified);
 
@@ -97,10 +98,9 @@ clang::NamedDecl *NameSearchContext::AddFunDecl(const CompilerType &type,
     for (ArgIndex = 0; ArgIndex < NumArgs; ++ArgIndex) {
       QualType arg_qual_type(func_proto_type->getParamType(ArgIndex));
 
-      parm_var_decls.push_back(
-          ParmVarDecl::Create(ast, const_cast<DeclContext *>(context),
-                              SourceLocation(), SourceLocation(), nullptr,
-                              arg_qual_type, nullptr, SC_Static, nullptr));
+      parm_var_decls.push_back(ParmVarDecl::Create(
+          ast, func_decl, SourceLocation(), SourceLocation(), nullptr,
+          arg_qual_type, nullptr, SC_Static, nullptr));
     }
 
     func_decl->setParams(ArrayRef<ParmVarDecl *>(parm_var_decls));

@llvmbot llvmbot added the HLSL HLSL Language Support label Jul 14, 2024
@temyurchenko
Copy link
Contributor Author

temyurchenko commented Jul 14, 2024

@AaronBallman could you please review?

I've fixed at least the LLDB tests. There was an actual bug in function parameters construction that was revealed by the invariant check.

However, I can't run the whole LLDB suite. Even among the reported failing tests, some are «unsupported» on my machine. Even without my changes, a lot of LLDB tests are unsupported or failing, I guess some were killed by oom-killer.
On the first attempt, @gulfemsavrun suggested to run the LLDB test suite on this PR. I would be glad, if that offer is still on.

Regarding the orc-rt test failures: I tried building one of the failing objects and I can't reproduce the failure. Perhaps, because the tests are run on Windows and I'm running on Linux? It would be very helpful if @Prabhuk could run the failing command on a debug build.

@AaronBallman
Copy link
Collaborator

@AaronBallman could you please review?

I've fixed at least the LLDB tests. There was an actual bug in function parameters construction that was revealed by the invariant check.

However, I can't run the whole LLDB suite. Even among the reported failing tests, some are «unsupported» on my machine. Even without my changes, a lot of LLDB tests are unsupported or failing, I guess some were killed by oom-killer. On the first attempt, @gulfemsavrun suggested to run the LLDB test suite on this PR. I would be glad, if that offer is still on.

CC @JDevlieghere

Regarding the orc-rt test failures: I tried building one of the failing objects and I can't reproduce the failure. Perhaps, because the tests are run on Windows and I'm running on Linux? It would be very helpful if @Prabhuk could run the failing command on a debug build.

CC @lhames

@lhames
Copy link
Contributor

lhames commented Jul 18, 2024

Regarding the orc-rt test failures: I tried building one of the failing objects and I can't reproduce the failure. Perhaps, because the tests are run on Windows and I'm running on Linux? It would be very helpful if @Prabhuk could run the failing command on a debug build.

CC @lhames

This is a frontend crash, rather than a JIT one, and I don't have access to a Windows dev machine so I'm not well placed to look into this.

@temyurchenko
Copy link
Contributor Author

A bit lost as to what I should do now, @AaronBallman.

@AaronBallman
Copy link
Collaborator

Ping @JDevlieghere for lldb test suite assistance.

As for ORC JIT, the compiler-rt project does not claim to support Windows to begin with, so it's unclear whether the bot should not be testing on Windows or whether compiler-rt needs to update their support claims. CC @vitalybuka for more opinions/help on this one (my preference is for Windows to be a first-class citizen with compiler-rt, but if nobody is willing to do the maintenance work, we should not let bots failing when testing compiler-rt on Windows be a blocking concern).

@labath
Copy link
Collaborator

labath commented Aug 6, 2024

I don't have the full context but I can definitely run the lldb test suite for you. I'm building now. (BTW, I see there are some merge conflicts on the PR.)

@labath
Copy link
Collaborator

labath commented Aug 6, 2024

I don't have the full context but I can definitely run the lldb test suite for you. I'm building now. (BTW, I see there are some merge conflicts on the PR.)

I have the results now. The patch is based on a fairly old revision, so it didn't work for me without a python 3.12 fix (22ea97d). After patching that it, I got a clean check-lldb run (on x86_64-linux).

@temyurchenko
Copy link
Contributor Author

I don't have the full context but I can definitely run the lldb test suite for you. I'm building now. (BTW, I see there are some merge conflicts on the PR.)

I have the results now. The patch is based on a fairly old revision, so it didn't work for me without a python 3.12 fix (22ea97d). After patching that it, I got a clean check-lldb run (on x86_64-linux).

Thank you so much! Glad to hear that.

The PR hasn't been updated for a while, but I will fix the conflicts now

temyurchenko and others added 4 commits August 6, 2024 09:33
…lvm#93131)

Problem: the printer used to ignore all but the first declarator for
unbraced language linkage declarators. Furthemore, that one would
be printed without the final semicolon.

Solution: for unbraced case we traverse all declarators via
`VisitDeclContext`. Furthermore, in appropriate visitors we query
for whether they are a part of the unbraced extern language linkage
spec, and if so, print appropriately.
Quoting 9.11.8:
> A declaration directly contained in a linkage-specification is
> treated as if it contains the extern specifier (9.2.2) for the
> purpose of determining the linkage of the declared name and whether
> it is a definition. Such a declaration shall not specify a storage
> class.

So, the invariant is that function and variable declarations within an
unbraced language linkage specification have `StorageClass` set to
`SC_None`.

Problem: in several places the invariant was broken.

Solution: remove the explicit `SC_Extern` specification. Note, that my
changes result in the `extern` being implicit in some functions
that are not contained in a language linkage specification.
It needs to be the function declaration that the parameter belongs to,
   not the context of that function.
@temyurchenko
Copy link
Contributor Author

Ping @JDevlieghere for lldb test suite assistance.

As for ORC JIT, the compiler-rt project does not claim to support Windows to begin with, so it's unclear whether the bot should not be testing on Windows or whether compiler-rt needs to update their support claims. CC @vitalybuka for more opinions/help on this one (my preference is for Windows to be a first-class citizen with compiler-rt, but if nobody is willing to do the maintenance work, we should not let bots failing when testing compiler-rt on Windows be a blocking concern).

Pinging @vitalybuka.

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 HLSL HLSL Language Support lldb
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants