1 //===--- ConfigCompile.cpp - Translating Fragments into Config ------------===// 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 // Fragments are applied to Configs in two steps: 10 // 11 // 1. (When the fragment is first loaded) 12 // FragmentCompiler::compile() traverses the Fragment and creates 13 // function objects that know how to apply the configuration. 14 // 2. (Every time a config is required) 15 // CompiledFragment() executes these functions to populate the Config. 16 // 17 // Work could be split between these steps in different ways. We try to 18 // do as much work as possible in the first step. For example, regexes are 19 // compiled in stage 1 and captured by the apply function. This is because: 20 // 21 // - it's more efficient, as the work done in stage 1 must only be done once 22 // - problems can be reported in stage 1, in stage 2 we must silently recover 23 // 24 //===----------------------------------------------------------------------===// 25 26 #include "CompileCommands.h" 27 #include "Config.h" 28 #include "ConfigFragment.h" 29 #include "support/Logger.h" 30 #include "support/Trace.h" 31 #include "llvm/ADT/STLExtras.h" 32 #include "llvm/ADT/StringRef.h" 33 #include "llvm/ADT/StringSwitch.h" 34 #include "llvm/Support/Regex.h" 35 #include "llvm/Support/SMLoc.h" 36 #include "llvm/Support/SourceMgr.h" 37 38 namespace clang { 39 namespace clangd { 40 namespace config { 41 namespace { 42 43 struct CompiledFragmentImpl { 44 // The independent conditions to check before using settings from this config. 45 // The following fragment has *two* conditions: 46 // If: { Platform: [mac, linux], PathMatch: foo/.* } 47 // All of them must be satisfied: the platform and path conditions are ANDed. 48 // The OR logic for the platform condition is implemented inside the function. 49 std::vector<llvm::unique_function<bool(const Params &) const>> Conditions; 50 // Mutations that this fragment will apply to the configuration. 51 // These are invoked only if the conditions are satisfied. 52 std::vector<llvm::unique_function<void(Config &) const>> Apply; 53 54 bool operator()(const Params &P, Config &C) const { 55 for (const auto &C : Conditions) { 56 if (!C(P)) { 57 dlog("Config fragment {0}: condition not met", this); 58 return false; 59 } 60 } 61 dlog("Config fragment {0}: applying {1} rules", this, Apply.size()); 62 for (const auto &A : Apply) 63 A(C); 64 return true; 65 } 66 }; 67 68 // Wrapper around condition compile() functions to reduce arg-passing. 69 struct FragmentCompiler { 70 CompiledFragmentImpl &Out; 71 DiagnosticCallback Diagnostic; 72 llvm::SourceMgr *SourceMgr; 73 74 llvm::Optional<llvm::Regex> compileRegex(const Located<std::string> &Text) { 75 std::string Anchored = "^(" + *Text + ")$"; 76 llvm::Regex Result(Anchored); 77 std::string RegexError; 78 if (!Result.isValid(RegexError)) { 79 diag(Error, "Invalid regex " + Anchored + ": " + RegexError, Text.Range); 80 return llvm::None; 81 } 82 return Result; 83 } 84 85 // Helper with similar API to StringSwitch, for parsing enum values. 86 template <typename T> class EnumSwitch { 87 FragmentCompiler &Outer; 88 llvm::StringRef EnumName; 89 const Located<std::string> &Input; 90 llvm::Optional<T> Result; 91 llvm::SmallVector<llvm::StringLiteral, 8> ValidValues; 92 93 public: 94 EnumSwitch(llvm::StringRef EnumName, const Located<std::string> &In, 95 FragmentCompiler &Outer) 96 : Outer(Outer), EnumName(EnumName), Input(In) {} 97 98 EnumSwitch &map(llvm::StringLiteral Name, T Value) { 99 assert(!llvm::is_contained(ValidValues, Name) && "Duplicate value!"); 100 ValidValues.push_back(Name); 101 if (!Result && *Input == Name) 102 Result = Value; 103 return *this; 104 } 105 106 llvm::Optional<T> value() { 107 if (!Result) 108 Outer.diag( 109 Warning, 110 llvm::formatv("Invalid {0} value '{1}'. Valid values are {2}.", 111 EnumName, *Input, llvm::join(ValidValues, ", ")) 112 .str(), 113 Input.Range); 114 return Result; 115 }; 116 }; 117 118 // Attempt to parse a specified string into an enum. 119 // Yields llvm::None and produces a diagnostic on failure. 120 // 121 // Optional<T> Value = compileEnum<En>("Foo", Frag.Foo) 122 // .map("Foo", Enum::Foo) 123 // .map("Bar", Enum::Bar) 124 // .value(); 125 template <typename T> 126 EnumSwitch<T> compileEnum(llvm::StringRef EnumName, 127 const Located<std::string> &In) { 128 return EnumSwitch<T>(EnumName, In, *this); 129 } 130 131 void compile(Fragment &&F) { 132 compile(std::move(F.If)); 133 compile(std::move(F.CompileFlags)); 134 compile(std::move(F.Index)); 135 } 136 137 void compile(Fragment::IfBlock &&F) { 138 if (F.HasUnrecognizedCondition) 139 Out.Conditions.push_back([&](const Params &) { return false; }); 140 141 auto PathMatch = std::make_unique<std::vector<llvm::Regex>>(); 142 for (auto &Entry : F.PathMatch) { 143 if (auto RE = compileRegex(Entry)) 144 PathMatch->push_back(std::move(*RE)); 145 } 146 if (!PathMatch->empty()) { 147 Out.Conditions.push_back( 148 [PathMatch(std::move(PathMatch))](const Params &P) { 149 if (P.Path.empty()) 150 return false; 151 return llvm::any_of(*PathMatch, [&](const llvm::Regex &RE) { 152 return RE.match(P.Path); 153 }); 154 }); 155 } 156 157 auto PathExclude = std::make_unique<std::vector<llvm::Regex>>(); 158 for (auto &Entry : F.PathExclude) { 159 if (auto RE = compileRegex(Entry)) 160 PathExclude->push_back(std::move(*RE)); 161 } 162 if (!PathExclude->empty()) { 163 Out.Conditions.push_back( 164 [PathExclude(std::move(PathExclude))](const Params &P) { 165 if (P.Path.empty()) 166 return false; 167 return llvm::none_of(*PathExclude, [&](const llvm::Regex &RE) { 168 return RE.match(P.Path); 169 }); 170 }); 171 } 172 } 173 174 void compile(Fragment::CompileFlagsBlock &&F) { 175 if (!F.Remove.empty()) { 176 auto Remove = std::make_shared<ArgStripper>(); 177 for (auto &A : F.Remove) 178 Remove->strip(*A); 179 Out.Apply.push_back([Remove(std::shared_ptr<const ArgStripper>( 180 std::move(Remove)))](Config &C) { 181 C.CompileFlags.Edits.push_back( 182 [Remove](std::vector<std::string> &Args) { 183 Remove->process(Args); 184 }); 185 }); 186 } 187 188 if (!F.Add.empty()) { 189 std::vector<std::string> Add; 190 for (auto &A : F.Add) 191 Add.push_back(std::move(*A)); 192 Out.Apply.push_back([Add(std::move(Add))](Config &C) { 193 C.CompileFlags.Edits.push_back([Add](std::vector<std::string> &Args) { 194 Args.insert(Args.end(), Add.begin(), Add.end()); 195 }); 196 }); 197 } 198 } 199 200 void compile(Fragment::IndexBlock &&F) { 201 if (F.Background) { 202 if (auto Val = compileEnum<Config::BackgroundPolicy>("Background", 203 **F.Background) 204 .map("Build", Config::BackgroundPolicy::Build) 205 .map("Skip", Config::BackgroundPolicy::Skip) 206 .value()) 207 Out.Apply.push_back([Val](Config &C) { C.Index.Background = *Val; }); 208 } 209 } 210 211 void compile(Fragment::StyleBlock &&F) { 212 if (!F.FullyQualifiedNamespaces.empty()) { 213 std::vector<std::string> FullyQualifiedNamespaces; 214 for (auto &N : F.FullyQualifiedNamespaces) { 215 // Normalize the data by dropping both leading and trailing :: 216 StringRef Namespace(*N); 217 Namespace.consume_front("::"); 218 Namespace.consume_back("::"); 219 FullyQualifiedNamespaces.push_back(Namespace.str()); 220 } 221 Out.Apply.push_back([FullyQualifiedNamespaces( 222 std::move(FullyQualifiedNamespaces))](Config &C) { 223 C.Style.FullyQualifiedNamespaces.insert( 224 C.Style.FullyQualifiedNamespaces.begin(), 225 FullyQualifiedNamespaces.begin(), FullyQualifiedNamespaces.end()); 226 }); 227 } 228 } 229 230 constexpr static llvm::SourceMgr::DiagKind Error = llvm::SourceMgr::DK_Error; 231 constexpr static llvm::SourceMgr::DiagKind Warning = 232 llvm::SourceMgr::DK_Warning; 233 void diag(llvm::SourceMgr::DiagKind Kind, llvm::StringRef Message, 234 llvm::SMRange Range) { 235 if (Range.isValid() && SourceMgr != nullptr) 236 Diagnostic(SourceMgr->GetMessage(Range.Start, Kind, Message, Range)); 237 else 238 Diagnostic(llvm::SMDiagnostic("", Kind, Message)); 239 } 240 }; 241 242 } // namespace 243 244 CompiledFragment Fragment::compile(DiagnosticCallback D) && { 245 llvm::StringRef ConfigFile = "<unknown>"; 246 std::pair<unsigned, unsigned> LineCol = {0, 0}; 247 if (auto *SM = Source.Manager.get()) { 248 unsigned BufID = SM->getMainFileID(); 249 LineCol = SM->getLineAndColumn(Source.Location, BufID); 250 ConfigFile = SM->getBufferInfo(BufID).Buffer->getBufferIdentifier(); 251 } 252 trace::Span Tracer("ConfigCompile"); 253 SPAN_ATTACH(Tracer, "ConfigFile", ConfigFile); 254 auto Result = std::make_shared<CompiledFragmentImpl>(); 255 vlog("Config fragment: compiling {0}:{1} -> {2}", ConfigFile, LineCol.first, 256 Result.get()); 257 258 FragmentCompiler{*Result, D, Source.Manager.get()}.compile(std::move(*this)); 259 // Return as cheaply-copyable wrapper. 260 return [Result(std::move(Result))](const Params &P, Config &C) { 261 return (*Result)(P, C); 262 }; 263 } 264 265 } // namespace config 266 } // namespace clangd 267 } // namespace clang 268