1 //===--- TestTU.cpp - Scratch source files for testing --------------------===// 2 // 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 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include "TestTU.h" 10 #include "CompileCommands.h" 11 #include "Compiler.h" 12 #include "Diagnostics.h" 13 #include "TestFS.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" 21 #include <cstdlib> 22 23 namespace clang { 24 namespace clangd { 25 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"; 34 35 FS.Files = AdditionalFiles; 36 FS.Files[FullFilename] = Code; 37 FS.Files[FullHeaderName] = HeaderCode; 38 FS.Files[ImportThunk] = ThunkContents; 39 40 ParseInputs Inputs; 41 Inputs.FeatureModules = FeatureModules; 42 auto &Argv = Inputs.CompileCommand.CommandLine; 43 Argv = {"clang"}; 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"); 50 } 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"); 60 } 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); 65 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; 73 Inputs.TFS = &FS; 74 Inputs.Opts = ParseOptions(); 75 if (ClangTidyProvider) 76 Inputs.ClangTidyProvider = ClangTidyProvider; 77 Inputs.Index = ExternalIndex; 78 return Inputs; 79 } 80 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"; 85 std::abort(); 86 } 87 CI.getHeaderSearchOpts().ModuleCachePath = ModuleCachePath.c_str(); 88 } 89 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"; 94 std::abort(); 95 } 96 } 97 } 98 99 std::shared_ptr<const PreambleData> 100 TestTU::preamble(PreambleParsedCallback PreambleCallback) const { 101 MockFS FS; 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); 112 } 113 114 ParsedAST TestTU::build() const { 115 MockFS FS; 116 auto Inputs = inputs(FS); 117 Inputs.Opts = ParseOpts; 118 StoreDiags Diags; 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)); 125 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); 131 if (!AST) { 132 llvm::errs() << "Failed to build code:\n" << Code; 133 std::abort(); 134 } 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)) 143 return true; 144 for (const auto &KV : this->AdditionalFiles) 145 if (llvm::StringRef(KV.second).contains(Marker)) 146 return true; 147 return false; 148 }(); 149 if (!ErrorOk) { 150 // We always build AST with a fresh preamble in TestTU. 151 for (const auto &D : AST->getDiagnostics()) 152 if (D.Severity >= DiagnosticsEngine::Error) { 153 llvm::errs() 154 << "TestTU failed to build (suppress with /*error-ok*/): \n" 155 << D << "\n\nFor code:\n" 156 << Code; 157 std::abort(); // Stop after first error for simplicity. 158 } 159 } 160 return std::move(*AST); 161 } 162 163 SymbolSlab TestTU::headerSymbols() const { 164 auto AST = build(); 165 return std::get<0>(indexHeaderSymbols( 166 /*Version=*/"null", AST.getASTContext(), AST.getPreprocessor(), 167 AST.getPragmaIncludes())); 168 } 169 170 RefSlab TestTU::headerRefs() const { 171 auto AST = build(); 172 return std::get<1>(indexMainDecls(AST)); 173 } 174 175 std::unique_ptr<SymbolIndex> TestTU::index() const { 176 auto AST = build(); 177 auto Idx = std::make_unique<FileIndex>(/*SupportContainedRefs=*/true); 178 Idx->updatePreamble(testPath(Filename), /*Version=*/"null", 179 AST.getASTContext(), AST.getPreprocessor(), 180 AST.getPragmaIncludes()); 181 Idx->updateMain(testPath(Filename), AST); 182 return std::move(Idx); 183 } 184 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()) 189 continue; 190 if (Result) { 191 llvm::errs() << "Multiple symbols named " << QName << ":\n" 192 << *Result << "\n---\n" 193 << S; 194 assert(false && "QName is not unique"); 195 } 196 Result = &S; 197 } 198 if (!Result) { 199 llvm::errs() << "No symbol named " << QName << " in " 200 << llvm::to_string(Slab); 201 assert(false && "No symbol with QName"); 202 } 203 return *Result; 204 } 205 206 // RAII scoped class to disable TraversalScope for a ParsedAST. 207 class TraverseHeadersToo { 208 ASTContext &Ctx; 209 std::vector<Decl *> ScopeToRestore; 210 211 public: 212 TraverseHeadersToo(ParsedAST &AST) 213 : Ctx(AST.getASTContext()), ScopeToRestore(Ctx.getTraversalScope()) { 214 Ctx.setTraversalScope({Ctx.getTranslationUnitDecl()}); 215 } 216 ~TraverseHeadersToo() { Ctx.setTraversalScope(std::move(ScopeToRestore)); } 217 }; 218 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(); 227 }; 228 229 const DeclContext *Scope = Ctx.getTranslationUnitDecl(); 230 231 StringRef Cur, Rest; 232 for (std::tie(Cur, Rest) = QName.split("::"); !Rest.empty(); 233 std::tie(Cur, Rest) = Rest.split("::")) { 234 Scope = &cast<DeclContext>(LookupDecl(*Scope, Cur)); 235 } 236 return LookupDecl(*Scope, Cur); 237 } 238 239 const NamedDecl &findDecl(ParsedAST &AST, 240 std::function<bool(const NamedDecl &)> Filter) { 241 TraverseHeadersToo Too(AST); 242 struct Visitor : RecursiveASTVisitor<Visitor> { 243 decltype(Filter) F; 244 llvm::SmallVector<const NamedDecl *, 1> Decls; 245 bool VisitNamedDecl(const NamedDecl *ND) { 246 if (F(*ND)) 247 Decls.push_back(ND); 248 return true; 249 } 250 } Visitor; 251 Visitor.F = Filter; 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); 256 } 257 return *Visitor.Decls.front(); 258 } 259 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) 264 return true; 265 return false; 266 }); 267 } 268 269 } // namespace clangd 270 } // namespace clang 271