Skip to content

Commit 2cdbc9c

Browse files
authored
[clangd] Use TargetOpts from preamble when building ASTs (#88381)
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.
1 parent 44c876f commit 2cdbc9c

File tree

4 files changed

+69
-7
lines changed

4 files changed

+69
-7
lines changed

clang-tools-extra/clangd/Preamble.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -700,6 +700,7 @@ buildPreamble(PathRef FileName, CompilerInvocation CI,
700700
Result->Marks = CapturedInfo.takeMarks();
701701
Result->StatCache = StatCache;
702702
Result->MainIsIncludeGuarded = CapturedInfo.isMainFileIncludeGuarded();
703+
Result->TargetOpts = CI.TargetOpts;
703704
if (PreambleCallback) {
704705
trace::Span Tracer("Running PreambleCallback");
705706
auto Ctx = CapturedInfo.takeLife();
@@ -913,6 +914,12 @@ PreamblePatch PreamblePatch::createMacroPatch(llvm::StringRef FileName,
913914
}
914915

915916
void PreamblePatch::apply(CompilerInvocation &CI) const {
917+
// Make sure the compilation uses same target opts as the preamble. Clang has
918+
// no guarantees around using arbitrary options when reusing PCHs, and
919+
// different target opts can result in crashes, see
920+
// ParsedASTTest.PreambleWithDifferentTarget.
921+
CI.TargetOpts = Baseline->TargetOpts;
922+
916923
// No need to map an empty file.
917924
if (PatchContents.empty())
918925
return;

clang-tools-extra/clangd/Preamble.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#include "clang-include-cleaner/Record.h"
3131
#include "support/Path.h"
3232
#include "clang/Basic/SourceManager.h"
33+
#include "clang/Basic/TargetOptions.h"
3334
#include "clang/Frontend/CompilerInvocation.h"
3435
#include "clang/Frontend/PrecompiledPreamble.h"
3536
#include "clang/Lex/Lexer.h"
@@ -97,6 +98,10 @@ struct PreambleData {
9798
// Version of the ParseInputs this preamble was built from.
9899
std::string Version;
99100
tooling::CompileCommand CompileCommand;
101+
// Target options used when building the preamble. Changes in target can cause
102+
// crashes when deserializing preamble, this enables consumers to use the
103+
// same target (without reparsing CompileCommand).
104+
std::shared_ptr<TargetOptions> TargetOpts = nullptr;
100105
PrecompiledPreamble Preamble;
101106
std::vector<Diag> Diags;
102107
// Processes like code completions and go-to-definitions will need #include

clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4160,7 +4160,32 @@ TEST(CompletionTest, DoNotCrash) {
41604160
auto Completions = completions(Case);
41614161
}
41624162
}
4163+
TEST(CompletionTest, PreambleFromDifferentTarget) {
4164+
constexpr std::string_view PreambleTarget = "x86_64";
4165+
constexpr std::string_view Contents =
4166+
"int foo(int); int num; int num2 = foo(n^";
41634167

4168+
Annotations Test(Contents);
4169+
auto TU = TestTU::withCode(Test.code());
4170+
TU.ExtraArgs.emplace_back("-target");
4171+
TU.ExtraArgs.emplace_back(PreambleTarget);
4172+
auto Preamble = TU.preamble();
4173+
ASSERT_TRUE(Preamble);
4174+
// Switch target to wasm.
4175+
TU.ExtraArgs.pop_back();
4176+
TU.ExtraArgs.emplace_back("wasm32");
4177+
4178+
MockFS FS;
4179+
auto Inputs = TU.inputs(FS);
4180+
auto Result = codeComplete(testPath(TU.Filename), Test.point(),
4181+
Preamble.get(), Inputs, {});
4182+
auto Signatures =
4183+
signatureHelp(testPath(TU.Filename), Test.point(), *Preamble, Inputs, {});
4184+
4185+
// Make sure we don't crash.
4186+
EXPECT_THAT(Result.Completions, Not(testing::IsEmpty()));
4187+
EXPECT_THAT(Signatures.signatures, Not(testing::IsEmpty()));
4188+
}
41644189
} // namespace
41654190
} // namespace clangd
41664191
} // namespace clang

clang-tools-extra/clangd/unittests/ParsedASTTests.cpp

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,7 @@
1212
//===----------------------------------------------------------------------===//
1313

1414
#include "../../clang-tidy/ClangTidyCheck.h"
15-
#include "../../clang-tidy/ClangTidyModule.h"
16-
#include "../../clang-tidy/ClangTidyModuleRegistry.h"
1715
#include "AST.h"
18-
#include "CompileCommands.h"
1916
#include "Compiler.h"
2017
#include "Config.h"
2118
#include "Diagnostics.h"
@@ -32,7 +29,6 @@
3229
#include "clang/Basic/SourceLocation.h"
3330
#include "clang/Basic/SourceManager.h"
3431
#include "clang/Basic/TokenKinds.h"
35-
#include "clang/Lex/PPCallbacks.h"
3632
#include "clang/Tooling/Syntax/Tokens.h"
3733
#include "llvm/ADT/StringRef.h"
3834
#include "llvm/Testing/Annotations/Annotations.h"
@@ -41,6 +37,7 @@
4137
#include "gmock/gmock.h"
4238
#include "gtest/gtest.h"
4339
#include <memory>
40+
#include <string_view>
4441
#include <utility>
4542
#include <vector>
4643

@@ -347,9 +344,8 @@ TEST(ParsedASTTest, CollectsMainFileMacroExpansions) {
347344
}
348345
for (const auto &R : AST.getMacros().UnknownMacros)
349346
MacroExpansionPositions.push_back(R.StartOffset);
350-
EXPECT_THAT(
351-
MacroExpansionPositions,
352-
testing::UnorderedElementsAreArray(TestCase.points()));
347+
EXPECT_THAT(MacroExpansionPositions,
348+
testing::UnorderedElementsAreArray(TestCase.points()));
353349
}
354350

355351
MATCHER_P(withFileName, Inc, "") { return arg.FileName == Inc; }
@@ -768,6 +764,35 @@ TEST(ParsedASTTest, GracefulFailureOnAssemblyFile) {
768764
<< "Should not try to build AST for assembly source file";
769765
}
770766

767+
TEST(ParsedASTTest, PreambleWithDifferentTarget) {
768+
constexpr std::string_view kPreambleTarget = "x86_64";
769+
// Specifically picking __builtin_va_list as it triggers crashes when
770+
// switching to wasm.
771+
// It's due to different predefined types in different targets.
772+
auto TU = TestTU::withHeaderCode("void foo(__builtin_va_list);");
773+
TU.Code = "void bar() { foo(2); }";
774+
TU.ExtraArgs.emplace_back("-target");
775+
TU.ExtraArgs.emplace_back(kPreambleTarget);
776+
const auto Preamble = TU.preamble();
777+
778+
// Switch target to wasm.
779+
TU.ExtraArgs.pop_back();
780+
TU.ExtraArgs.emplace_back("wasm32");
781+
782+
IgnoreDiagnostics Diags;
783+
MockFS FS;
784+
auto Inputs = TU.inputs(FS);
785+
auto CI = buildCompilerInvocation(Inputs, Diags);
786+
ASSERT_TRUE(CI) << "Failed to build compiler invocation";
787+
788+
auto AST = ParsedAST::build(testPath(TU.Filename), std::move(Inputs),
789+
std::move(CI), {}, Preamble);
790+
791+
ASSERT_TRUE(AST);
792+
// We use the target from preamble, not with the most-recent flags.
793+
EXPECT_EQ(AST->getASTContext().getTargetInfo().getTriple().getArchName(),
794+
llvm::StringRef(kPreambleTarget));
795+
}
771796
} // namespace
772797
} // namespace clangd
773798
} // namespace clang

0 commit comments

Comments
 (0)