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
29using namespace llvm;
30
31static Expected<std::unique_ptr<MemoryBuffer>>
32setupMemoryBuffer(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
40namespace llvm {
41namespace instrumentor {
42
43void 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
115bool 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
246bool 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