1 //===--- TestTU.cpp - Scratch source files for testing --------------------===//
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7 //===----------------------------------------------------------------------===//
10 #include "CompileCommands.h"
12 #include "Diagnostics.h"
14 #include "index/FileIndex.h"
15 #include "clang/AST/RecursiveASTVisitor.h"
16 #include "clang/Basic/Diagnostic.h"
17 #include "clang/Frontend/CompilerInvocation.h"
18 #include "llvm/ADT/ScopeExit.h"
19 #include "llvm/Support/ScopedPrinter.h"
20 #include "llvm/Support/raw_ostream.h"
26 ParseInputs
TestTU::inputs(MockFS
&FS
) const {
27 std::string FullFilename
= testPath(Filename
),
28 FullHeaderName
= testPath(HeaderFilename
),
29 ImportThunk
= testPath("import_thunk.h");
30 // We want to implicitly include HeaderFilename without messing up offsets.
31 // -include achieves this, but sometimes we want #import (to simulate a header
32 // guard without messing up offsets). In this case, use an intermediate file.
33 std::string ThunkContents
= "#import \"" + FullHeaderName
+ "\"\n";
35 FS
.Files
= AdditionalFiles
;
36 FS
.Files
[FullFilename
] = Code
;
37 FS
.Files
[FullHeaderName
] = HeaderCode
;
38 FS
.Files
[ImportThunk
] = ThunkContents
;
41 Inputs
.FeatureModules
= FeatureModules
;
42 auto &Argv
= Inputs
.CompileCommand
.CommandLine
;
44 // In tests, unless explicitly specified otherwise, omit predefined macros
45 // (__GNUC__ etc) for a 25% speedup. There are hundreds, and we'd generate,
46 // parse, serialize, and re-parse them!
47 if (!PredefineMacros
) {
48 Argv
.push_back("-Xclang");
49 Argv
.push_back("-undef");
51 // FIXME: this shouldn't need to be conditional, but it breaks a
52 // GoToDefinition test for some reason (getMacroArgExpandedLocation fails).
53 if (!HeaderCode
.empty()) {
54 Argv
.push_back("-include");
55 Argv
.push_back(ImplicitHeaderGuard
? ImportThunk
: FullHeaderName
);
56 // ms-compatibility changes the meaning of #import.
57 // The default is OS-dependent (on windows), ensure it's off.
58 if (ImplicitHeaderGuard
)
59 Inputs
.CompileCommand
.CommandLine
.push_back("-fno-ms-compatibility");
61 Argv
.insert(Argv
.end(), ExtraArgs
.begin(), ExtraArgs
.end());
62 // Put the file name at the end -- this allows the extra arg (-xc++) to
63 // override the language setting.
64 Argv
.push_back(FullFilename
);
66 auto Mangler
= CommandMangler::forTests();
67 Mangler(Inputs
.CompileCommand
, FullFilename
);
68 Inputs
.CompileCommand
.Filename
= FullFilename
;
69 Inputs
.CompileCommand
.Directory
= testRoot();
70 Inputs
.Contents
= Code
;
71 if (OverlayRealFileSystemForModules
)
72 FS
.OverlayRealFileSystemForModules
= true;
74 Inputs
.Opts
= ParseOptions();
75 if (ClangTidyProvider
)
76 Inputs
.ClangTidyProvider
= ClangTidyProvider
;
77 Inputs
.Index
= ExternalIndex
;
81 void initializeModuleCache(CompilerInvocation
&CI
) {
82 llvm::SmallString
<128> ModuleCachePath
;
83 if (llvm::sys::fs::createUniqueDirectory("module-cache", ModuleCachePath
)) {
84 llvm::errs() << "Failed to create temp directory for module-cache";
87 CI
.getHeaderSearchOpts().ModuleCachePath
= ModuleCachePath
.c_str();
90 void deleteModuleCache(const std::string ModuleCachePath
) {
91 if (!ModuleCachePath
.empty()) {
92 if (llvm::sys::fs::remove_directories(ModuleCachePath
)) {
93 llvm::errs() << "Failed to delete temp directory for module-cache";
99 std::shared_ptr
<const PreambleData
>
100 TestTU::preamble(PreambleParsedCallback PreambleCallback
) const {
102 auto Inputs
= inputs(FS
);
103 IgnoreDiagnostics Diags
;
104 auto CI
= buildCompilerInvocation(Inputs
, Diags
);
105 assert(CI
&& "Failed to build compilation invocation.");
106 if (OverlayRealFileSystemForModules
)
107 initializeModuleCache(*CI
);
108 auto ModuleCacheDeleter
= llvm::make_scope_exit(
109 std::bind(deleteModuleCache
, CI
->getHeaderSearchOpts().ModuleCachePath
));
110 return clang::clangd::buildPreamble(testPath(Filename
), *CI
, Inputs
,
111 /*StoreInMemory=*/true, PreambleCallback
);
114 ParsedAST
TestTU::build() const {
116 auto Inputs
= inputs(FS
);
117 Inputs
.Opts
= ParseOpts
;
119 auto CI
= buildCompilerInvocation(Inputs
, Diags
);
120 assert(CI
&& "Failed to build compilation invocation.");
121 if (OverlayRealFileSystemForModules
)
122 initializeModuleCache(*CI
);
123 auto ModuleCacheDeleter
= llvm::make_scope_exit(
124 std::bind(deleteModuleCache
, CI
->getHeaderSearchOpts().ModuleCachePath
));
126 auto Preamble
= clang::clangd::buildPreamble(testPath(Filename
), *CI
, Inputs
,
127 /*StoreInMemory=*/true,
128 /*PreambleCallback=*/nullptr);
129 auto AST
= ParsedAST::build(testPath(Filename
), Inputs
, std::move(CI
),
130 Diags
.take(), Preamble
);
132 llvm::errs() << "Failed to build code:\n" << Code
;
135 // Check for error diagnostics and report gtest failures (unless expected).
136 // This guards against accidental syntax errors silently subverting tests.
137 // error-ok is awfully primitive - using clang -verify would be nicer.
138 // Ownership and layering makes it pretty hard.
139 bool ErrorOk
= [&, this] {
140 llvm::StringLiteral Marker
= "error-ok";
141 if (llvm::StringRef(Code
).contains(Marker
) ||
142 llvm::StringRef(HeaderCode
).contains(Marker
))
144 for (const auto &KV
: this->AdditionalFiles
)
145 if (llvm::StringRef(KV
.second
).contains(Marker
))
150 // We always build AST with a fresh preamble in TestTU.
151 for (const auto &D
: AST
->getDiagnostics())
152 if (D
.Severity
>= DiagnosticsEngine::Error
) {
154 << "TestTU failed to build (suppress with /*error-ok*/): \n"
155 << D
<< "\n\nFor code:\n"
157 std::abort(); // Stop after first error for simplicity.
160 return std::move(*AST
);
163 SymbolSlab
TestTU::headerSymbols() const {
165 return std::get
<0>(indexHeaderSymbols(/*Version=*/"null", AST
.getASTContext(),
166 AST
.getPreprocessor(),
167 AST
.getCanonicalIncludes()));
170 RefSlab
TestTU::headerRefs() const {
172 return std::get
<1>(indexMainDecls(AST
));
175 std::unique_ptr
<SymbolIndex
> TestTU::index() const {
177 auto Idx
= std::make_unique
<FileIndex
>();
178 Idx
->updatePreamble(testPath(Filename
), /*Version=*/"null",
179 AST
.getASTContext(), AST
.getPreprocessor(),
180 AST
.getCanonicalIncludes());
181 Idx
->updateMain(testPath(Filename
), AST
);
182 return std::move(Idx
);
185 const Symbol
&findSymbol(const SymbolSlab
&Slab
, llvm::StringRef QName
) {
186 const Symbol
*Result
= nullptr;
187 for (const Symbol
&S
: Slab
) {
188 if (QName
!= (S
.Scope
+ S
.Name
).str())
191 llvm::errs() << "Multiple symbols named " << QName
<< ":\n"
192 << *Result
<< "\n---\n"
194 assert(false && "QName is not unique");
199 llvm::errs() << "No symbol named " << QName
<< " in "
200 << llvm::to_string(Slab
);
201 assert(false && "No symbol with QName");
206 // RAII scoped class to disable TraversalScope for a ParsedAST.
207 class TraverseHeadersToo
{
209 std::vector
<Decl
*> ScopeToRestore
;
212 TraverseHeadersToo(ParsedAST
&AST
)
213 : Ctx(AST
.getASTContext()), ScopeToRestore(Ctx
.getTraversalScope()) {
214 Ctx
.setTraversalScope({Ctx
.getTranslationUnitDecl()});
216 ~TraverseHeadersToo() { Ctx
.setTraversalScope(std::move(ScopeToRestore
)); }
219 const NamedDecl
&findDecl(ParsedAST
&AST
, llvm::StringRef QName
) {
220 auto &Ctx
= AST
.getASTContext();
221 auto LookupDecl
= [&Ctx
](const DeclContext
&Scope
,
222 llvm::StringRef Name
) -> const NamedDecl
& {
223 auto LookupRes
= Scope
.lookup(DeclarationName(&Ctx
.Idents
.get(Name
)));
224 assert(!LookupRes
.empty() && "Lookup failed");
225 assert(LookupRes
.isSingleResult() && "Lookup returned multiple results");
226 return *LookupRes
.front();
229 const DeclContext
*Scope
= Ctx
.getTranslationUnitDecl();
232 for (std::tie(Cur
, Rest
) = QName
.split("::"); !Rest
.empty();
233 std::tie(Cur
, Rest
) = Rest
.split("::")) {
234 Scope
= &cast
<DeclContext
>(LookupDecl(*Scope
, Cur
));
236 return LookupDecl(*Scope
, Cur
);
239 const NamedDecl
&findDecl(ParsedAST
&AST
,
240 std::function
<bool(const NamedDecl
&)> Filter
) {
241 TraverseHeadersToo
Too(AST
);
242 struct Visitor
: RecursiveASTVisitor
<Visitor
> {
244 llvm::SmallVector
<const NamedDecl
*, 1> Decls
;
245 bool VisitNamedDecl(const NamedDecl
*ND
) {
252 Visitor
.TraverseDecl(AST
.getASTContext().getTranslationUnitDecl());
253 if (Visitor
.Decls
.size() != 1) {
254 llvm::errs() << Visitor
.Decls
.size() << " symbols matched.\n";
255 assert(Visitor
.Decls
.size() == 1);
257 return *Visitor
.Decls
.front();
260 const NamedDecl
&findUnqualifiedDecl(ParsedAST
&AST
, llvm::StringRef Name
) {
261 return findDecl(AST
, [Name
](const NamedDecl
&ND
) {
262 if (auto *ID
= ND
.getIdentifier())
263 if (ID
->getName() == Name
)
269 } // namespace clangd