| //===-- InstrumentorConfigFile.cpp ----------------------------------------===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| // |
| // The implementation of the utilities for the Instrumentor JSON configuration |
| // file. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "llvm/Transforms/IPO/Instrumentor.h" |
| |
| #include "llvm/ADT/StringMap.h" |
| #include "llvm/ADT/StringRef.h" |
| #include "llvm/IR/DiagnosticInfo.h" |
| #include "llvm/IR/LLVMContext.h" |
| #include "llvm/Support/ErrorHandling.h" |
| #include "llvm/Support/JSON.h" |
| #include "llvm/Support/MemoryBuffer.h" |
| #include "llvm/Support/Path.h" |
| #include "llvm/Support/StringSaver.h" |
| #include "llvm/Support/VirtualFileSystem.h" |
| |
| #include <string> |
| |
| using namespace llvm; |
| |
| static Expected<std::unique_ptr<MemoryBuffer>> |
| setupMemoryBuffer(const Twine &Filename, vfs::FileSystem &FS) { |
| auto BufferOrErr = Filename.str() == "-" ? MemoryBuffer::getSTDIN() |
| : FS.getBufferForFile(Filename); |
| if (std::error_code EC = BufferOrErr.getError()) |
| return errorCodeToError(EC); |
| return std::move(BufferOrErr.get()); |
| } |
| |
| namespace llvm { |
| namespace instrumentor { |
| |
| void writeConfigToJSON(InstrumentationConfig &IConf, StringRef OutputFile, |
| LLVMContext &Ctx) { |
| if (OutputFile.empty()) |
| return; |
| |
| std::error_code EC; |
| raw_fd_stream OS(OutputFile, EC); |
| if (EC) { |
| Ctx.diagnose(DiagnosticInfoInstrumentation( |
| Twine("failed to open instrumentor configuration file for writing: ") + |
| EC.message(), |
| DS_Warning)); |
| return; |
| } |
| |
| json::OStream J(OS, 2); |
| J.objectBegin(); |
| |
| J.attributeBegin("configuration"); |
| J.objectBegin(); |
| for (auto *BaseCO : IConf.BaseConfigurationOptions) { |
| switch (BaseCO->Kind) { |
| case BaseConfigurationOption::STRING: |
| J.attribute(BaseCO->Name, BaseCO->getString()); |
| break; |
| case BaseConfigurationOption::BOOLEAN: |
| J.attribute(BaseCO->Name, BaseCO->getBool()); |
| break; |
| } |
| if (!BaseCO->Description.empty()) |
| J.attribute(std::string(BaseCO->Name) + ".description", |
| BaseCO->Description); |
| } |
| J.objectEnd(); |
| J.attributeEnd(); |
| |
| for (unsigned KindVal = 0; KindVal <= InstrumentationLocation::Last; |
| ++KindVal) { |
| auto Kind = InstrumentationLocation::KindTy(KindVal); |
| |
| auto &KindChoices = IConf.IChoices[Kind]; |
| if (KindChoices.empty()) |
| continue; |
| |
| J.attributeBegin(InstrumentationLocation::getKindStr(Kind)); |
| J.objectBegin(); |
| for (auto &[Name, Choice] : KindChoices) { |
| J.attributeBegin(Name); |
| J.objectBegin(); |
| J.attribute("enabled", Choice->Enabled); |
| J.attribute("filter", Choice->Filter); |
| J.attribute("filter.description", |
| "Static property filter to exclude instrumentation."); |
| for (auto &ArgIt : Choice->IRTArgs) { |
| J.attribute(ArgIt.Name, ArgIt.Enabled); |
| if ((ArgIt.Flags & IRTArg::REPLACABLE) || |
| (ArgIt.Flags & IRTArg::REPLACABLE_CUSTOM)) |
| J.attribute(std::string(ArgIt.Name) + ".replace", true); |
| if (!ArgIt.Description.empty()) |
| J.attribute(std::string(ArgIt.Name) + ".description", |
| ArgIt.Description); |
| } |
| J.objectEnd(); |
| J.attributeEnd(); |
| } |
| J.objectEnd(); |
| J.attributeEnd(); |
| } |
| |
| J.objectEnd(); |
| } |
| |
| bool readConfigFromJSON(InstrumentationConfig &IConf, StringRef InputFile, |
| LLVMContext &Ctx, vfs::FileSystem &FS) { |
| if (InputFile.empty()) |
| return true; |
| |
| auto BufferOrErr = setupMemoryBuffer(InputFile, FS); |
| if (Error E = BufferOrErr.takeError()) { |
| Ctx.diagnose(DiagnosticInfoInstrumentation( |
| Twine("failed to open instrumentor configuration file for reading: ") + |
| toString(std::move(E)), |
| DS_Warning)); |
| return false; |
| } |
| auto Buffer = std::move(BufferOrErr.get()); |
| json::Path::Root NullRoot; |
| auto Parsed = json::parse(Buffer->getBuffer()); |
| if (!Parsed) { |
| Ctx.diagnose(DiagnosticInfoInstrumentation( |
| Twine("failed to parse instrumentor configuration file: ") + |
| toString(Parsed.takeError()), |
| DS_Warning)); |
| return false; |
| } |
| auto *Config = Parsed->getAsObject(); |
| if (!Config) { |
| Ctx.diagnose(DiagnosticInfoInstrumentation( |
| "failed to parse instrumentor configuration file, expected an object " |
| "'{ ... }'", |
| DS_Warning)); |
| return false; |
| } |
| |
| StringMap<BaseConfigurationOption *> BCOMap; |
| for (auto *BO : IConf.BaseConfigurationOptions) |
| BCOMap[BO->Name] = BO; |
| |
| SmallPtrSet<InstrumentationOpportunity *, 32> SeenIOs; |
| for (auto &It : *Config) { |
| auto *Obj = It.second.getAsObject(); |
| if (!Obj) { |
| Ctx.diagnose(DiagnosticInfoInstrumentation( |
| "malformed JSON configuration, expected an object", DS_Warning)); |
| continue; |
| } |
| if (It.first == "configuration") { |
| for (auto &ObjIt : *Obj) { |
| if (auto *BO = BCOMap.lookup(ObjIt.first)) { |
| switch (BO->Kind) { |
| case BaseConfigurationOption::STRING: |
| if (auto V = ObjIt.second.getAsString()) { |
| BO->setString(IConf.SS.save(*V)); |
| } else { |
| Ctx.diagnose(DiagnosticInfoInstrumentation( |
| Twine("configuration key '") + ObjIt.first.str() + |
| Twine("' expects a string, value ignored"), |
| DS_Warning)); |
| } |
| break; |
| case BaseConfigurationOption::BOOLEAN: |
| if (auto V = ObjIt.second.getAsBoolean()) |
| BO->setBool(*V); |
| else { |
| Ctx.diagnose(DiagnosticInfoInstrumentation( |
| Twine("configuration key '") + ObjIt.first.str() + |
| Twine("' expects a boolean, value ignored"), |
| DS_Warning)); |
| } |
| break; |
| } |
| } else if (!StringRef(ObjIt.first).ends_with(".description")) { |
| Ctx.diagnose(DiagnosticInfoInstrumentation( |
| Twine("configuration key '") + ObjIt.first.str() + |
| Twine("' not found and ignored"), |
| DS_Warning)); |
| } |
| } |
| continue; |
| } |
| |
| auto &IChoiceMap = |
| IConf.IChoices[InstrumentationLocation::getKindFromStr(It.first)]; |
| for (auto &ObjIt : *Obj) { |
| auto *InnerObj = ObjIt.second.getAsObject(); |
| if (!InnerObj) { |
| Ctx.diagnose(DiagnosticInfoInstrumentation( |
| "malformed JSON configuration, expected an object", DS_Warning)); |
| continue; |
| } |
| auto *IO = IChoiceMap.lookup(ObjIt.first); |
| if (!IO) { |
| Ctx.diagnose(DiagnosticInfoInstrumentation( |
| Twine("malformed JSON configuration, expected an object matching " |
| "an instrumentor choice, got ") + |
| ObjIt.first.str(), |
| DS_Warning)); |
| continue; |
| } |
| SeenIOs.insert(IO); |
| StringMap<bool> ValueMap, ReplaceMap; |
| StringRef FilterStr; |
| for (auto &InnerObjIt : *InnerObj) { |
| auto Name = StringRef(InnerObjIt.first); |
| if (Name == "filter") { |
| if (auto V = InnerObjIt.second.getAsString()) |
| FilterStr = IConf.SS.save(*V); |
| } else if (Name.consume_back(".replace")) { |
| ReplaceMap[Name] = InnerObjIt.second.getAsBoolean().value_or(false); |
| } else { |
| ValueMap[Name] = InnerObjIt.second.getAsBoolean().value_or(false); |
| } |
| } |
| IO->Enabled = ValueMap["enabled"]; |
| IO->Filter = FilterStr; |
| for (auto &IRArg : IO->IRTArgs) { |
| IRArg.Enabled = ValueMap[IRArg.Name]; |
| if (!ReplaceMap.lookup(IRArg.Name)) { |
| IRArg.Flags &= ~IRTArg::REPLACABLE; |
| IRArg.Flags &= ~IRTArg::REPLACABLE_CUSTOM; |
| } |
| } |
| } |
| } |
| |
| for (auto &IChoiceMap : IConf.IChoices) |
| for (auto &It : IChoiceMap) |
| if (!SeenIOs.count(It.second)) |
| It.second->Enabled = false; |
| |
| return true; |
| } |
| |
| bool readConfigPathsFile(StringRef InputFile, cl::list<std::string> &Configs, |
| LLVMContext &Ctx, vfs::FileSystem &FS) { |
| if (InputFile.empty()) |
| return true; |
| |
| auto BufferOrErr = setupMemoryBuffer(InputFile, FS); |
| if (Error E = BufferOrErr.takeError()) { |
| Ctx.diagnose(DiagnosticInfoInstrumentation( |
| Twine("failed to open instrumentor configuration paths file for " |
| "reading: ") + |
| toString(std::move(E)), |
| DS_Warning)); |
| return false; |
| } |
| |
| StringRef InputFilePath(sys::path::parent_path(InputFile)); |
| |
| auto Buffer = std::move(BufferOrErr.get()); |
| StringRef Content = Buffer->getBuffer(); |
| StringRef EOL = Content.detectEOL(); |
| do { |
| auto [LHS, RHS] = Content.split(EOL); |
| std::string ConfigPath = LHS.trim().str(); |
| if (!sys::path::is_absolute(ConfigPath)) { |
| SmallString<128> InputFilePathStringVec(InputFilePath); |
| sys::path::append(InputFilePathStringVec, ConfigPath); |
| ConfigPath = InputFilePathStringVec.c_str(); |
| } |
| Configs.push_back(ConfigPath); |
| Content = RHS.trim(); |
| } while (!Content.empty()); |
| |
| return true; |
| } |
| |
| } // end namespace instrumentor |
| } // end namespace llvm |