blob: a3e72c930b6f8584421275a2ab57da2cf933ce42 [file] [edit]
//===-- 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