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