Skip to content
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

[clangd] Use TargetOpts from preamble when building ASTs #88381

Merged
merged 1 commit into from
Apr 18, 2024

Conversation

kadircet
Copy link
Member

Building ASTs with compile flags that are incompatible to the ones used
for the Preamble are not really supported by clang and can trigger
crashes.

In an ideal world, we should be re-using not only TargetOpts, but the
full ParseInputs from the Preamble to prevent such failures.

Unfortunately current contracts of ThreadSafeFS makes this a non-safe
change for certain implementations. As there are no guarantees that the
same ThreadSafeFS is going to be valid in the Context::current() we're
building the AST in.

@llvmbot
Copy link
Collaborator

llvmbot commented Apr 11, 2024

@llvm/pr-subscribers-clang-tools-extra

@llvm/pr-subscribers-clangd

Author: kadir çetinkaya (kadircet)

Changes

Building ASTs with compile flags that are incompatible to the ones used
for the Preamble are not really supported by clang and can trigger
crashes.

In an ideal world, we should be re-using not only TargetOpts, but the
full ParseInputs from the Preamble to prevent such failures.

Unfortunately current contracts of ThreadSafeFS makes this a non-safe
change for certain implementations. As there are no guarantees that the
same ThreadSafeFS is going to be valid in the Context::current() we're
building the AST in.


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

4 Files Affected:

  • (modified) clang-tools-extra/clangd/ParsedAST.cpp (+1)
  • (modified) clang-tools-extra/clangd/Preamble.cpp (+1)
  • (modified) clang-tools-extra/clangd/Preamble.h (+5)
  • (modified) clang-tools-extra/clangd/unittests/ParsedASTTests.cpp (+32-7)
diff --git a/clang-tools-extra/clangd/ParsedAST.cpp b/clang-tools-extra/clangd/ParsedAST.cpp
index 3ff759415f7c8b..b6e784db4719fe 100644
--- a/clang-tools-extra/clangd/ParsedAST.cpp
+++ b/clang-tools-extra/clangd/ParsedAST.cpp
@@ -451,6 +451,7 @@ ParsedAST::build(llvm::StringRef Filename, const ParseInputs &Inputs,
   DiagnosticConsumer *DiagConsumer = &ASTDiags;
   IgnoreDiagnostics DropDiags;
   if (Preamble) {
+    CI->TargetOpts = Preamble->TargetOpts;
     Patch = PreamblePatch::createFullPatch(Filename, Inputs, *Preamble);
     Patch->apply(*CI);
   }
diff --git a/clang-tools-extra/clangd/Preamble.cpp b/clang-tools-extra/clangd/Preamble.cpp
index f181c7befec156..d579e90adc49df 100644
--- a/clang-tools-extra/clangd/Preamble.cpp
+++ b/clang-tools-extra/clangd/Preamble.cpp
@@ -700,6 +700,7 @@ buildPreamble(PathRef FileName, CompilerInvocation CI,
     Result->Marks = CapturedInfo.takeMarks();
     Result->StatCache = StatCache;
     Result->MainIsIncludeGuarded = CapturedInfo.isMainFileIncludeGuarded();
+    Result->TargetOpts = CI.TargetOpts;
     if (PreambleCallback) {
       trace::Span Tracer("Running PreambleCallback");
       auto Ctx = CapturedInfo.takeLife();
diff --git a/clang-tools-extra/clangd/Preamble.h b/clang-tools-extra/clangd/Preamble.h
index 37da3833748a9c..160b884beb56bb 100644
--- a/clang-tools-extra/clangd/Preamble.h
+++ b/clang-tools-extra/clangd/Preamble.h
@@ -30,6 +30,7 @@
 #include "clang-include-cleaner/Record.h"
 #include "support/Path.h"
 #include "clang/Basic/SourceManager.h"
+#include "clang/Basic/TargetOptions.h"
 #include "clang/Frontend/CompilerInvocation.h"
 #include "clang/Frontend/PrecompiledPreamble.h"
 #include "clang/Lex/Lexer.h"
@@ -97,6 +98,10 @@ struct PreambleData {
   // Version of the ParseInputs this preamble was built from.
   std::string Version;
   tooling::CompileCommand CompileCommand;
+  // Target options used when building the preamble. Changes in target can cause
+  // crashes when deserializing preamble, this enables consumers to use the
+  // same target (without reparsing CompileCommand).
+  std::shared_ptr<TargetOptions> TargetOpts = nullptr;
   PrecompiledPreamble Preamble;
   std::vector<Diag> Diags;
   // Processes like code completions and go-to-definitions will need #include
diff --git a/clang-tools-extra/clangd/unittests/ParsedASTTests.cpp b/clang-tools-extra/clangd/unittests/ParsedASTTests.cpp
index 500b72b9b327a0..4bb76cd6ab8304 100644
--- a/clang-tools-extra/clangd/unittests/ParsedASTTests.cpp
+++ b/clang-tools-extra/clangd/unittests/ParsedASTTests.cpp
@@ -12,10 +12,7 @@
 //===----------------------------------------------------------------------===//
 
 #include "../../clang-tidy/ClangTidyCheck.h"
-#include "../../clang-tidy/ClangTidyModule.h"
-#include "../../clang-tidy/ClangTidyModuleRegistry.h"
 #include "AST.h"
-#include "CompileCommands.h"
 #include "Compiler.h"
 #include "Config.h"
 #include "Diagnostics.h"
@@ -32,7 +29,6 @@
 #include "clang/Basic/SourceLocation.h"
 #include "clang/Basic/SourceManager.h"
 #include "clang/Basic/TokenKinds.h"
-#include "clang/Lex/PPCallbacks.h"
 #include "clang/Tooling/Syntax/Tokens.h"
 #include "llvm/ADT/StringRef.h"
 #include "llvm/Testing/Annotations/Annotations.h"
@@ -41,6 +37,7 @@
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
 #include <memory>
+#include <string_view>
 #include <utility>
 #include <vector>
 
@@ -347,9 +344,8 @@ TEST(ParsedASTTest, CollectsMainFileMacroExpansions) {
   }
   for (const auto &R : AST.getMacros().UnknownMacros)
     MacroExpansionPositions.push_back(R.StartOffset);
-  EXPECT_THAT(
-      MacroExpansionPositions,
-      testing::UnorderedElementsAreArray(TestCase.points()));
+  EXPECT_THAT(MacroExpansionPositions,
+              testing::UnorderedElementsAreArray(TestCase.points()));
 }
 
 MATCHER_P(withFileName, Inc, "") { return arg.FileName == Inc; }
@@ -768,6 +764,35 @@ TEST(ParsedASTTest, GracefulFailureOnAssemblyFile) {
       << "Should not try to build AST for assembly source file";
 }
 
+TEST(ParsedASTTest, PreambleWithDifferentTarget) {
+  constexpr std::string_view kPreambleTarget = "x86_64";
+  // Specifically picking __builtin_va_list as it triggers crashes when
+  // switching to wasm.
+  // It's due to different predefined types in different targets.
+  auto TU = TestTU::withHeaderCode("void foo(__builtin_va_list);");
+  TU.Code = "void bar() { foo(2); }";
+  TU.ExtraArgs.emplace_back("-target");
+  TU.ExtraArgs.emplace_back(kPreambleTarget);
+  const auto Preamble = TU.preamble();
+
+  // Switch target to wasm.
+  TU.ExtraArgs.pop_back();
+  TU.ExtraArgs.emplace_back("wasm32");
+
+  IgnoreDiagnostics Diags;
+  MockFS FS;
+  auto Inputs = TU.inputs(FS);
+  auto CI = buildCompilerInvocation(Inputs, Diags);
+  ASSERT_TRUE(CI) << "Failed to build compiler invocation";
+
+  auto AST = ParsedAST::build(testPath(TU.Filename), std::move(Inputs),
+                              std::move(CI), {}, Preamble);
+
+  ASSERT_TRUE(AST);
+  // We use the target from preamble, not with the most-recent flags.
+  EXPECT_EQ(AST->getASTContext().getTargetInfo().getTriple().getArchName(),
+            llvm::StringRef(kPreambleTarget));
+}
 } // namespace
 } // namespace clangd
 } // namespace clang

Copy link
Contributor

@ilya-biryukov ilya-biryukov left a comment

Choose a reason for hiding this comment

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

Just to confirm I understand the broader context: we only need it when building the AST because the rest of the code (completions, signature help) actually already uses a compile command and FS from preamble inputs, right?

@@ -451,6 +451,7 @@ ParsedAST::build(llvm::StringRef Filename, const ParseInputs &Inputs,
DiagnosticConsumer *DiagConsumer = &ASTDiags;
IgnoreDiagnostics DropDiags;
if (Preamble) {
CI->TargetOpts = Preamble->TargetOpts;
Patch = PreamblePatch::createFullPatch(Filename, Inputs, *Preamble);
Copy link
Contributor

Choose a reason for hiding this comment

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

Idea: should we make this part of PreamblePatch?
It seems to fit the concept (take inputs from preamble, patch up compiler invocation based on it). Although I'm not sure if I'm misreading its intention.

Copy link
Member Author

Choose a reason for hiding this comment

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

well, idea also crossed my mind while putting together the patch but wasn't so sure if that would be the best place. since preamble-patch'ing was mostly for adjusting the compilation to "patch" changes in the preamble contents.

but considering the fact that this actually needs to be done on all the places that re-uses a preamble (CodeCompletion, SignatureHelp), I guess PreamblePatch is a nice common ground (I was leaning towards prepareCompilerInstance but unfortunately that one can't depend on Preamble.h :/)

@@ -768,6 +764,35 @@ TEST(ParsedASTTest, GracefulFailureOnAssemblyFile) {
<< "Should not try to build AST for assembly source file";
}

TEST(ParsedASTTest, PreambleWithDifferentTarget) {
constexpr std::string_view kPreambleTarget = "x86_64";
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure if we'll hit this in practice, but this test will fail if the specified targets are disabled during builds.
We might need to guard it against wrong build configurations.

I'd wait until some buildbot fails, though, maybe we never actually run Clangd tests when the set of targets is small.

Copy link
Member Author

Choose a reason for hiding this comment

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

yeah happy to wait and see if some build bots fail

clang-tools-extra/clangd/ParsedAST.cpp Outdated Show resolved Hide resolved
@kadircet
Copy link
Member Author

Just to confirm I understand the broader context: we only need it when building the AST because the rest of the code (completions, signature help) actually already uses a compile command and FS from preamble inputs, right?

That was my remembrance, but apparently it was wrong :/ Changed the logic to cover those cases as well && added some tests.

@ilya-biryukov
Copy link
Contributor

All good from my side, but asked @sam-mccall or @usx95 to check the context propagation bit as I'm missing context to make sure it's correct.

Building ASTs with compile flags that are incompatible to the ones used
for the Preamble are not really supported by clang and can trigger
crashes.

In an ideal world, we should be re-using not only TargetOpts, but the
full ParseInputs from the Preamble to prevent such failures.

Unfortunately current contracts of ThreadSafeFS makes this a non-safe
change for certain implementations. As there are no guarantees that the
same ThreadSafeFS is going to be valid in the Context::current() we're
building the AST in.
@kadircet kadircet merged commit 2cdbc9c into llvm:main Apr 18, 2024
3 of 4 checks passed
@kadircet kadircet deleted the prop_target branch April 18, 2024 14:58
@zibi2
Copy link
Contributor

zibi2 commented Apr 19, 2024

@kadircet FYI, I noticed our Linux on Power down stream build failues related to this change. The upstream builds are also faling see https://lab.llvm.org/buildbot/#/builders/57/builds/34415 for example.

ld.lld: error: undefined symbol: llvm::Triple::getArchName() const
>>> referenced by ParsedASTTests.cpp

This symbol is define in Triple.cpp.o which is part of libLLVMTargetParser.a which is not built. Not building this archive and a bunch of others is the root of the problem.

Can you have a look and try to fix it?
Thanks, Zibi.

@kadircet
Copy link
Member Author

Hi @zibi2, sorry for missing the breakage in the buildbot. ce2f642 should fix it.

@zibi2
Copy link
Contributor

zibi2 commented Apr 19, 2024

Hi @zibi2, sorry for missing the breakage in the buildbot. ce2f642 should fix it.

Thank you for a quick fix. I confirm the fix resolved the issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants