1 | //===--- Compiler.cpp --------------------------------------------*- C++-*-===// |
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 "Compiler.h" |
10 | #include "support/Logger.h" |
11 | #include "clang/Basic/TargetInfo.h" |
12 | #include "clang/Frontend/CompilerInvocation.h" |
13 | #include "clang/Lex/PreprocessorOptions.h" |
14 | #include "clang/Serialization/PCHContainerOperations.h" |
15 | #include "llvm/ADT/StringRef.h" |
16 | |
17 | namespace clang { |
18 | namespace clangd { |
19 | |
20 | void IgnoreDiagnostics::log(DiagnosticsEngine::Level DiagLevel, |
21 | const clang::Diagnostic &Info) { |
22 | // FIXME: format lazily, in case vlog is off. |
23 | llvm::SmallString<64> Message; |
24 | Info.FormatDiagnostic(OutStr&: Message); |
25 | |
26 | llvm::SmallString<64> Location; |
27 | if (Info.hasSourceManager() && Info.getLocation().isValid()) { |
28 | auto &SourceMgr = Info.getSourceManager(); |
29 | auto Loc = SourceMgr.getFileLoc(Loc: Info.getLocation()); |
30 | llvm::raw_svector_ostream OS(Location); |
31 | Loc.print(OS, SM: SourceMgr); |
32 | OS << ":" ; |
33 | } |
34 | |
35 | clangd::vlog(Fmt: "Ignored diagnostic. {0}{1}" , Vals&: Location, Vals&: Message); |
36 | } |
37 | |
38 | void IgnoreDiagnostics::HandleDiagnostic(DiagnosticsEngine::Level DiagLevel, |
39 | const clang::Diagnostic &Info) { |
40 | IgnoreDiagnostics::log(DiagLevel, Info); |
41 | } |
42 | |
43 | static bool AllowCrashPragmasForTest = false; |
44 | void allowCrashPragmasForTest() { AllowCrashPragmasForTest = true; } |
45 | |
46 | void disableUnsupportedOptions(CompilerInvocation &CI) { |
47 | // Disable "clang -verify" diagnostics, they are rarely useful in clangd, and |
48 | // our compiler invocation set-up doesn't seem to work with it (leading |
49 | // assertions in VerifyDiagnosticConsumer). |
50 | CI.getDiagnosticOpts().VerifyDiagnostics = false; |
51 | CI.getDiagnosticOpts().ShowColors = false; |
52 | |
53 | // Disable any dependency outputting, we don't want to generate files or write |
54 | // to stdout/stderr. |
55 | CI.getDependencyOutputOpts().ShowIncludesDest = ShowIncludesDestination::None; |
56 | CI.getDependencyOutputOpts().OutputFile.clear(); |
57 | CI.getDependencyOutputOpts().HeaderIncludeOutputFile.clear(); |
58 | CI.getDependencyOutputOpts().DOTOutputFile.clear(); |
59 | CI.getDependencyOutputOpts().ModuleDependencyOutputDir.clear(); |
60 | |
61 | // Disable any pch generation/usage operations. Since serialized preamble |
62 | // format is unstable, using an incompatible one might result in unexpected |
63 | // behaviours, including crashes. |
64 | CI.getPreprocessorOpts().ImplicitPCHInclude.clear(); |
65 | CI.getPreprocessorOpts().PrecompiledPreambleBytes = {0, false}; |
66 | CI.getPreprocessorOpts().PCHThroughHeader.clear(); |
67 | CI.getPreprocessorOpts().PCHWithHdrStop = false; |
68 | CI.getPreprocessorOpts().PCHWithHdrStopCreate = false; |
69 | // Don't crash on `#pragma clang __debug parser_crash` |
70 | if (!AllowCrashPragmasForTest) |
71 | CI.getPreprocessorOpts().DisablePragmaDebugCrash = true; |
72 | |
73 | // Always default to raw container format as clangd doesn't registry any other |
74 | // and clang dies when faced with unknown formats. |
75 | CI.getHeaderSearchOpts().ModuleFormat = |
76 | PCHContainerOperations().getRawReader().getFormats().front().str(); |
77 | |
78 | CI.getFrontendOpts().Plugins.clear(); |
79 | CI.getFrontendOpts().AddPluginActions.clear(); |
80 | CI.getFrontendOpts().PluginArgs.clear(); |
81 | CI.getFrontendOpts().ProgramAction = frontend::ParseSyntaxOnly; |
82 | CI.getFrontendOpts().ActionName.clear(); |
83 | |
84 | // These options mostly affect codegen, and aren't relevant to clangd. And |
85 | // clang will die immediately when these files are not existed. |
86 | // Disable these uninteresting options to make clangd more robust. |
87 | CI.getLangOpts().NoSanitizeFiles.clear(); |
88 | CI.getLangOpts().XRayAttrListFiles.clear(); |
89 | CI.getLangOpts().ProfileListFiles.clear(); |
90 | CI.getLangOpts().XRayAlwaysInstrumentFiles.clear(); |
91 | CI.getLangOpts().XRayNeverInstrumentFiles.clear(); |
92 | } |
93 | |
94 | std::unique_ptr<CompilerInvocation> |
95 | buildCompilerInvocation(const ParseInputs &Inputs, clang::DiagnosticConsumer &D, |
96 | std::vector<std::string> *CC1Args) { |
97 | llvm::ArrayRef<std::string> Argv = Inputs.CompileCommand.CommandLine; |
98 | if (Argv.empty()) |
99 | return nullptr; |
100 | std::vector<const char *> ArgStrs; |
101 | ArgStrs.reserve(n: Argv.size() + 1); |
102 | // In asserts builds, CompilerInvocation redundantly reads/parses cc1 args as |
103 | // a sanity test. This is not useful to clangd, and costs 10% of test time. |
104 | // To avoid mismatches between assert/production builds, disable it always. |
105 | ArgStrs = {Argv.front().c_str(), "-Xclang" , "-no-round-trip-args" }; |
106 | for (const auto &S : Argv.drop_front()) |
107 | ArgStrs.push_back(x: S.c_str()); |
108 | |
109 | CreateInvocationOptions CIOpts; |
110 | CIOpts.VFS = Inputs.TFS->view(CWD: Inputs.CompileCommand.Directory); |
111 | CIOpts.CC1Args = CC1Args; |
112 | CIOpts.RecoverOnError = true; |
113 | CIOpts.Diags = |
114 | CompilerInstance::createDiagnostics(Opts: new DiagnosticOptions, Client: &D, ShouldOwnClient: false); |
115 | CIOpts.ProbePrecompiled = false; |
116 | std::unique_ptr<CompilerInvocation> CI = createInvocation(Args: ArgStrs, Opts: CIOpts); |
117 | if (!CI) |
118 | return nullptr; |
119 | // createInvocationFromCommandLine sets DisableFree. |
120 | CI->getFrontendOpts().DisableFree = false; |
121 | CI->getLangOpts().CommentOpts.ParseAllComments = true; |
122 | CI->getLangOpts().RetainCommentsFromSystemHeaders = true; |
123 | |
124 | disableUnsupportedOptions(CI&: *CI); |
125 | return CI; |
126 | } |
127 | |
128 | std::unique_ptr<CompilerInstance> |
129 | prepareCompilerInstance(std::unique_ptr<clang::CompilerInvocation> CI, |
130 | const PrecompiledPreamble *Preamble, |
131 | std::unique_ptr<llvm::MemoryBuffer> Buffer, |
132 | llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS, |
133 | DiagnosticConsumer &DiagsClient) { |
134 | assert(VFS && "VFS is null" ); |
135 | assert(!CI->getPreprocessorOpts().RetainRemappedFileBuffers && |
136 | "Setting RetainRemappedFileBuffers to true will cause a memory leak " |
137 | "of ContentsBuffer" ); |
138 | |
139 | // NOTE: we use Buffer.get() when adding remapped files, so we have to make |
140 | // sure it will be released if no error is emitted. |
141 | if (Preamble) { |
142 | Preamble->OverridePreamble(CI&: *CI, VFS, MainFileBuffer: Buffer.get()); |
143 | } else { |
144 | CI->getPreprocessorOpts().addRemappedFile( |
145 | From: CI->getFrontendOpts().Inputs[0].getFile(), To: Buffer.get()); |
146 | } |
147 | |
148 | auto Clang = std::make_unique<CompilerInstance>( |
149 | args: std::make_shared<PCHContainerOperations>()); |
150 | Clang->setInvocation(std::move(CI)); |
151 | Clang->createDiagnostics(Client: &DiagsClient, ShouldOwnClient: false); |
152 | |
153 | if (auto VFSWithRemapping = createVFSFromCompilerInvocation( |
154 | CI: Clang->getInvocation(), Diags&: Clang->getDiagnostics(), BaseFS: VFS)) |
155 | VFS = VFSWithRemapping; |
156 | Clang->createFileManager(VFS); |
157 | |
158 | if (!Clang->createTarget()) |
159 | return nullptr; |
160 | |
161 | // RemappedFileBuffers will handle the lifetime of the Buffer pointer, |
162 | // release it. |
163 | Buffer.release(); |
164 | return Clang; |
165 | } |
166 | |
167 | } // namespace clangd |
168 | } // namespace clang |
169 | |