| 1 | //===-- InstrumentorConfigFile.cpp ----------------------------------------===// |
| 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 | // The implementation of the utilities for the Instrumentor JSON configuration |
| 10 | // file. |
| 11 | // |
| 12 | //===----------------------------------------------------------------------===// |
| 13 | |
| 14 | #include "llvm/Transforms/IPO/Instrumentor.h" |
| 15 | |
| 16 | #include "llvm/ADT/StringMap.h" |
| 17 | #include "llvm/ADT/StringRef.h" |
| 18 | #include "llvm/ADT/StringSet.h" |
| 19 | #include "llvm/IR/DiagnosticInfo.h" |
| 20 | #include "llvm/IR/LLVMContext.h" |
| 21 | #include "llvm/Support/ErrorHandling.h" |
| 22 | #include "llvm/Support/JSON.h" |
| 23 | #include "llvm/Support/MemoryBuffer.h" |
| 24 | #include "llvm/Support/Path.h" |
| 25 | #include "llvm/Support/StringSaver.h" |
| 26 | #include "llvm/Support/VirtualFileSystem.h" |
| 27 | |
| 28 | #include <string> |
| 29 | |
| 30 | using namespace llvm; |
| 31 | |
| 32 | static Expected<std::unique_ptr<MemoryBuffer>> |
| 33 | setupMemoryBuffer(const Twine &Filename, vfs::FileSystem &FS) { |
| 34 | auto BufferOrErr = Filename.str() == "-" ? MemoryBuffer::getSTDIN() |
| 35 | : FS.getBufferForFile(Name: Filename); |
| 36 | if (std::error_code EC = BufferOrErr.getError()) |
| 37 | return errorCodeToError(EC); |
| 38 | return std::move(BufferOrErr.get()); |
| 39 | } |
| 40 | |
| 41 | namespace llvm { |
| 42 | namespace instrumentor { |
| 43 | |
| 44 | void writeConfigToJSON(InstrumentationConfig &IConf, StringRef OutputFile, |
| 45 | LLVMContext &Ctx) { |
| 46 | if (OutputFile.empty()) |
| 47 | return; |
| 48 | |
| 49 | std::error_code EC; |
| 50 | raw_fd_stream OS(OutputFile, EC); |
| 51 | if (EC) { |
| 52 | Ctx.diagnose(DI: DiagnosticInfoInstrumentation( |
| 53 | Twine("failed to open instrumentor configuration file for writing: " ) + |
| 54 | EC.message(), |
| 55 | DS_Warning)); |
| 56 | return; |
| 57 | } |
| 58 | |
| 59 | json::OStream J(OS, 2); |
| 60 | J.objectBegin(); |
| 61 | |
| 62 | J.attributeBegin(Key: "configuration" ); |
| 63 | J.objectBegin(); |
| 64 | for (auto *BaseCO : IConf.BaseConfigurationOptions) { |
| 65 | switch (BaseCO->Kind) { |
| 66 | case BaseConfigurationOption::STRING: |
| 67 | J.attribute(Key: BaseCO->Name, Contents: BaseCO->getString()); |
| 68 | break; |
| 69 | case BaseConfigurationOption::BOOLEAN: |
| 70 | J.attribute(Key: BaseCO->Name, Contents: BaseCO->getBool()); |
| 71 | break; |
| 72 | } |
| 73 | if (!BaseCO->Description.empty()) |
| 74 | J.attribute(Key: std::string(BaseCO->Name) + ".description" , |
| 75 | Contents: BaseCO->Description); |
| 76 | } |
| 77 | J.objectEnd(); |
| 78 | J.attributeEnd(); |
| 79 | |
| 80 | for (unsigned KindVal = 0; KindVal <= InstrumentationLocation::Last; |
| 81 | ++KindVal) { |
| 82 | auto Kind = InstrumentationLocation::KindTy(KindVal); |
| 83 | |
| 84 | auto &KindChoices = IConf.IChoices[Kind]; |
| 85 | if (KindChoices.empty()) |
| 86 | continue; |
| 87 | |
| 88 | J.attributeBegin(Key: InstrumentationLocation::getKindStr(Kind)); |
| 89 | J.objectBegin(); |
| 90 | for (auto &[Name, Choice] : KindChoices) { |
| 91 | J.attributeBegin(Key: Name); |
| 92 | J.objectBegin(); |
| 93 | J.attribute(Key: "enabled" , Contents: Choice->Enabled); |
| 94 | J.attribute(Key: "filter" , Contents: Choice->Filter); |
| 95 | J.attribute(Key: "filter.description" , |
| 96 | Contents: "Static property filter to exclude instrumentation." ); |
| 97 | for (auto &ArgIt : Choice->IRTArgs) { |
| 98 | J.attribute(Key: ArgIt.Name, Contents: ArgIt.Enabled); |
| 99 | if ((ArgIt.Flags & IRTArg::REPLACABLE) || |
| 100 | (ArgIt.Flags & IRTArg::REPLACABLE_CUSTOM)) |
| 101 | J.attribute(Key: std::string(ArgIt.Name) + ".replace" , Contents: true); |
| 102 | if (!ArgIt.Description.empty()) |
| 103 | J.attribute(Key: std::string(ArgIt.Name) + ".description" , |
| 104 | Contents: ArgIt.Description); |
| 105 | } |
| 106 | J.objectEnd(); |
| 107 | J.attributeEnd(); |
| 108 | } |
| 109 | J.objectEnd(); |
| 110 | J.attributeEnd(); |
| 111 | } |
| 112 | |
| 113 | J.objectEnd(); |
| 114 | } |
| 115 | |
| 116 | template <typename Map> |
| 117 | static StringRef closestOption(const Map &Options, StringRef Missing) { |
| 118 | uint32_t MaxEdit = 5; |
| 119 | StringRef Closest; |
| 120 | for (const auto &Key : Options.keys()) { |
| 121 | auto EditDist = Missing.edit_distance_insensitive(Other: Key, AllowReplacements: true, MaxEditDistance: MaxEdit); |
| 122 | if (EditDist < MaxEdit) { |
| 123 | Closest = Key; |
| 124 | MaxEdit = EditDist; |
| 125 | } |
| 126 | } |
| 127 | return Closest; |
| 128 | } |
| 129 | |
| 130 | bool readConfigFromJSON(InstrumentationConfig &IConf, StringRef InputFile, |
| 131 | LLVMContext &Ctx, vfs::FileSystem &FS) { |
| 132 | if (InputFile.empty()) |
| 133 | return true; |
| 134 | |
| 135 | auto BufferOrErr = setupMemoryBuffer(Filename: InputFile, FS); |
| 136 | if (Error E = BufferOrErr.takeError()) { |
| 137 | Ctx.diagnose(DI: DiagnosticInfoInstrumentation( |
| 138 | Twine("failed to open instrumentor configuration file for reading: " ) + |
| 139 | toString(E: std::move(E)), |
| 140 | DS_Warning)); |
| 141 | return false; |
| 142 | } |
| 143 | auto Buffer = std::move(BufferOrErr.get()); |
| 144 | json::Path::Root NullRoot; |
| 145 | auto Parsed = json::parse(JSON: Buffer->getBuffer()); |
| 146 | if (!Parsed) { |
| 147 | Ctx.diagnose(DI: DiagnosticInfoInstrumentation( |
| 148 | Twine("failed to parse instrumentor configuration file: " ) + |
| 149 | toString(E: Parsed.takeError()), |
| 150 | DS_Warning)); |
| 151 | return false; |
| 152 | } |
| 153 | auto *Config = Parsed->getAsObject(); |
| 154 | if (!Config) { |
| 155 | Ctx.diagnose(DI: DiagnosticInfoInstrumentation( |
| 156 | "failed to parse instrumentor configuration file, expected an object " |
| 157 | "'{ ... }'" , |
| 158 | DS_Warning)); |
| 159 | return false; |
| 160 | } |
| 161 | |
| 162 | StringMap<BaseConfigurationOption *> BCOMap; |
| 163 | for (auto *BO : IConf.BaseConfigurationOptions) |
| 164 | BCOMap[BO->Name] = BO; |
| 165 | |
| 166 | SmallPtrSet<InstrumentationOpportunity *, 32> SeenIOs; |
| 167 | for (auto &It : *Config) { |
| 168 | auto *Obj = It.second.getAsObject(); |
| 169 | if (!Obj) { |
| 170 | Ctx.diagnose(DI: DiagnosticInfoInstrumentation( |
| 171 | "malformed JSON configuration, expected an object" , DS_Warning)); |
| 172 | continue; |
| 173 | } |
| 174 | if (It.first == "configuration" ) { |
| 175 | for (auto &ObjIt : *Obj) { |
| 176 | if (auto *BO = BCOMap.lookup(Key: ObjIt.first)) { |
| 177 | switch (BO->Kind) { |
| 178 | case BaseConfigurationOption::STRING: |
| 179 | if (auto V = ObjIt.second.getAsString()) { |
| 180 | BO->setString(IConf.SS.save(S: *V)); |
| 181 | } else { |
| 182 | Ctx.diagnose(DI: DiagnosticInfoInstrumentation( |
| 183 | Twine("configuration key '" ) + StringRef(ObjIt.first) + |
| 184 | Twine("' expects a string, value ignored" ), |
| 185 | DS_Warning)); |
| 186 | } |
| 187 | break; |
| 188 | case BaseConfigurationOption::BOOLEAN: |
| 189 | if (auto V = ObjIt.second.getAsBoolean()) |
| 190 | BO->setBool(*V); |
| 191 | else { |
| 192 | Ctx.diagnose(DI: DiagnosticInfoInstrumentation( |
| 193 | Twine("configuration key '" ) + StringRef(ObjIt.first) + |
| 194 | Twine("' expects a boolean, value ignored" ), |
| 195 | DS_Warning)); |
| 196 | } |
| 197 | break; |
| 198 | } |
| 199 | } else if (!StringRef(ObjIt.first).ends_with(Suffix: ".description" )) { |
| 200 | std::string Diag = "configuration key '" + ObjIt.first.str() + |
| 201 | "' not found and ignored" ; |
| 202 | StringRef Closest = closestOption(Options: BCOMap, Missing: ObjIt.first); |
| 203 | if (!Closest.empty()) |
| 204 | Diag += "; did you mean '" + Closest.str() + "'?" ; |
| 205 | Ctx.diagnose(DI: DiagnosticInfoInstrumentation(Diag, DS_Warning)); |
| 206 | } |
| 207 | } |
| 208 | continue; |
| 209 | } |
| 210 | |
| 211 | auto &IChoiceMap = |
| 212 | IConf.IChoices[InstrumentationLocation::getKindFromStr(S: It.first)]; |
| 213 | for (auto &ObjIt : *Obj) { |
| 214 | auto *InnerObj = ObjIt.second.getAsObject(); |
| 215 | if (!InnerObj) { |
| 216 | Ctx.diagnose(DI: DiagnosticInfoInstrumentation( |
| 217 | "malformed JSON configuration, expected an object" , DS_Warning)); |
| 218 | continue; |
| 219 | } |
| 220 | auto *IO = IChoiceMap.lookup(Key: ObjIt.first); |
| 221 | if (!IO) { |
| 222 | std::string Diag = |
| 223 | "malformed JSON configuration, expected an object matching " |
| 224 | "an instrumentor choice, got '" + |
| 225 | ObjIt.first.str() + "'" ; |
| 226 | StringRef Closest = closestOption(Options: IChoiceMap, Missing: ObjIt.first); |
| 227 | if (!Closest.empty()) |
| 228 | Diag += "; did you mean '" + Closest.str() + "'?" ; |
| 229 | Ctx.diagnose(DI: DiagnosticInfoInstrumentation(Diag, DS_Warning)); |
| 230 | continue; |
| 231 | } |
| 232 | SeenIOs.insert(Ptr: IO); |
| 233 | StringMap<bool> ValueMap, ReplaceMap; |
| 234 | StringRef FilterStr; |
| 235 | StringSet<> IOOpts; |
| 236 | IOOpts.insert(key: "enabled" ); |
| 237 | IOOpts.insert(key: "filter" ); |
| 238 | for (auto &IRArg : IO->IRTArgs) |
| 239 | IOOpts.insert(key: IRArg.Name); |
| 240 | for (auto &InnerObjIt : *InnerObj) { |
| 241 | auto Name = StringRef(InnerObjIt.first); |
| 242 | if (Name == "filter" ) { |
| 243 | if (auto V = InnerObjIt.second.getAsString()) |
| 244 | FilterStr = IConf.SS.save(S: *V); |
| 245 | } else if (Name.consume_back(Suffix: ".replace" )) { |
| 246 | ReplaceMap[Name] = InnerObjIt.second.getAsBoolean().value_or(u: false); |
| 247 | } else { |
| 248 | ValueMap[Name] = InnerObjIt.second.getAsBoolean().value_or(u: false); |
| 249 | } |
| 250 | if (!IOOpts.contains(key: Name)) { |
| 251 | std::string Diag = "unrecognized JSON property '" + Name.str() + |
| 252 | "' in configuration for '" + IO->getName().str() + |
| 253 | "'" ; |
| 254 | StringRef Closest = closestOption(Options: IOOpts, Missing: Name); |
| 255 | if (!Closest.empty()) |
| 256 | Diag += "; did you mean '" + Closest.str() + "'?" ; |
| 257 | Ctx.diagnose(DI: DiagnosticInfoInstrumentation(Diag, DS_Warning)); |
| 258 | } |
| 259 | } |
| 260 | IO->Enabled = ValueMap["enabled" ]; |
| 261 | IO->Filter = FilterStr; |
| 262 | for (auto &IRArg : IO->IRTArgs) { |
| 263 | IRArg.Enabled = ValueMap[IRArg.Name]; |
| 264 | if (!ReplaceMap.lookup(Key: IRArg.Name)) { |
| 265 | IRArg.Flags &= ~IRTArg::REPLACABLE; |
| 266 | IRArg.Flags &= ~IRTArg::REPLACABLE_CUSTOM; |
| 267 | } |
| 268 | } |
| 269 | } |
| 270 | } |
| 271 | |
| 272 | for (auto &IChoiceMap : IConf.IChoices) |
| 273 | for (auto &It : IChoiceMap) |
| 274 | if (!SeenIOs.count(Ptr: It.second)) |
| 275 | It.second->Enabled = false; |
| 276 | |
| 277 | return true; |
| 278 | } |
| 279 | |
| 280 | bool readConfigPathsFile(StringRef InputFile, cl::list<std::string> &Configs, |
| 281 | LLVMContext &Ctx, vfs::FileSystem &FS) { |
| 282 | if (InputFile.empty()) |
| 283 | return true; |
| 284 | |
| 285 | auto BufferOrErr = setupMemoryBuffer(Filename: InputFile, FS); |
| 286 | if (Error E = BufferOrErr.takeError()) { |
| 287 | Ctx.diagnose(DI: DiagnosticInfoInstrumentation( |
| 288 | Twine("failed to open instrumentor configuration paths file for " |
| 289 | "reading: " ) + |
| 290 | toString(E: std::move(E)), |
| 291 | DS_Warning)); |
| 292 | return false; |
| 293 | } |
| 294 | |
| 295 | StringRef InputFilePath(sys::path::parent_path(path: InputFile)); |
| 296 | |
| 297 | auto Buffer = std::move(BufferOrErr.get()); |
| 298 | StringRef Content = Buffer->getBuffer(); |
| 299 | StringRef EOL = Content.detectEOL(); |
| 300 | do { |
| 301 | auto [LHS, RHS] = Content.split(Separator: EOL); |
| 302 | std::string ConfigPath = LHS.trim().str(); |
| 303 | if (!sys::path::is_absolute(path: ConfigPath)) { |
| 304 | SmallString<128> InputFilePathStringVec(InputFilePath); |
| 305 | sys::path::append(path&: InputFilePathStringVec, a: ConfigPath); |
| 306 | ConfigPath = InputFilePathStringVec.c_str(); |
| 307 | } |
| 308 | Configs.push_back(value: ConfigPath); |
| 309 | Content = RHS.trim(); |
| 310 | } while (!Content.empty()); |
| 311 | |
| 312 | return true; |
| 313 | } |
| 314 | |
| 315 | } // end namespace instrumentor |
| 316 | } // end namespace llvm |
| 317 | |