//===-- 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 using namespace llvm; static Expected> 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 BCOMap; for (auto *BO : IConf.BaseConfigurationOptions) BCOMap[BO->Name] = BO; SmallPtrSet 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 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 &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