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