1//===- RemarkFilter.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// Generic tool to filter remarks
10//
11//===----------------------------------------------------------------------===//
12
13#include "RemarkUtilHelpers.h"
14#include "RemarkUtilRegistry.h"
15
16#include "llvm/Support/Error.h"
17#include "llvm/Support/Regex.h"
18#include <map>
19
20using namespace llvm;
21using namespace remarks;
22using namespace llvm::remarkutil;
23
24// Note: Avoid using the identifier "filter" in this file, as it is prone to
25// namespace collision with headers that might get included e.g.
26// curses.h.
27
28static cl::SubCommand
29 FilterSub("filter",
30 "Filter remarks based on specified criteria. "
31 "Can be used to merge multiple remark files.\n"
32 "Multiple input files are processed in argument order and their "
33 "outputs are combined into a single output file.");
34
35INPUT_FORMAT_COMMAND_LINE_OPTIONS(FilterSub)
36OUTPUT_FORMAT_COMMAND_LINE_OPTIONS(FilterSub)
37OUTPUT_COMMAND_LINE_OPTIONS(FilterSub)
38REMARK_FILTER_COMMAND_LINE_OPTIONS(FilterSub)
39
40static cl::list<std::string> InputFileNames(
41 cl::Positional, cl::OneOrMore, cl::list_init<std::string>(Vals: {"-"}),
42 cl::desc("<input file> [<input file> ...]"), cl::sub(FilterSub));
43
44static cl::opt<bool>
45 ExcludeOpt("exclude",
46 cl::desc("Keep all remarks except those matching the filter"),
47 cl::init(Val: false), cl::sub(FilterSub));
48static cl::opt<bool> SortOpt("sort", cl::desc("Sort remarks (expensive!)"),
49 cl::init(Val: false), cl::sub(FilterSub));
50static cl::opt<bool> DedupeOpt("dedupe",
51 cl::desc("Deduplicate remarks (expensive!)"),
52 cl::init(Val: false), cl::sub(FilterSub));
53
54REMARK_FILTER_SETUP_FUNC()
55
56namespace {
57
58class FilterTool {
59public:
60 Filters Filter;
61
62 bool Sort = false;
63 bool Dedupe = false;
64 bool Exclude = false;
65
66 FilterTool(Filters Filter) : Filter(std::move(Filter)) {}
67 ~FilterTool() { finalize(); }
68
69 Error processInputFile(StringRef InputFileName) {
70 auto MaybeBuf = getInputMemoryBuffer(InputFileName);
71 if (!MaybeBuf)
72 return MaybeBuf.takeError();
73 auto MaybeParser =
74 createRemarkParser(ParserFormat: InputFormat, Buf: (*MaybeBuf)->getBuffer());
75 if (!MaybeParser)
76 return MaybeParser.takeError();
77 auto &Parser = **MaybeParser;
78
79 if (Error E = setupSerializer(Parser.ParserFormat))
80 return E;
81
82 auto MaybeRemark = Parser.next();
83 for (; MaybeRemark; MaybeRemark = Parser.next()) {
84 Remark &Remark = **MaybeRemark;
85 if (Filter.filterRemark(Remark) == Exclude)
86 continue;
87 emit(RPtr: std::move(*MaybeRemark));
88 }
89 auto E = MaybeRemark.takeError();
90 if (!E.isA<EndOfFileError>())
91 return E;
92 consumeError(Err: std::move(E));
93 return Error::success();
94 }
95
96 void finalize() {
97 if (!Serializer)
98 return;
99 emitBuffered();
100 OF->keep();
101 Serializer = nullptr;
102 }
103
104private:
105 std::unique_ptr<ToolOutputFile> OF;
106 std::unique_ptr<RemarkSerializer> Serializer;
107
108 /// Compare Remarks through unique_ptr
109 struct RemarkPtrCompare {
110 bool operator()(const std::unique_ptr<Remark> &LHS,
111 const std::unique_ptr<Remark> &RHS) const {
112 assert(LHS && RHS && "Invalid pointers to compare.");
113 return *LHS < *RHS;
114 }
115 };
116
117 // Buffer all remarks if required (for sorting/deduplication).
118 // For now, use std::map (like the RemarkLinker) for easy sorting. We
119 // should be capitalizing on the fact that the strings are interned.
120 std::map<std::unique_ptr<Remark>, size_t, RemarkPtrCompare> Remarks;
121 StringTable StrTab;
122
123 /// Set up the RemarkSerializer lazily, so automatic output format detection
124 /// can default to the automatically detected input format from the first file
125 /// we process.
126 Error setupSerializer(Format DefaultFormat) {
127 if (Serializer)
128 return Error::success();
129 Format SerializerFormat =
130 getSerializerFormat(OutputFileName, SelectedFormat: OutputFormat, DefaultFormat);
131 auto MaybeOF = getOutputFileForRemarks(OutputFileName, OutputFormat: SerializerFormat);
132 if (!MaybeOF)
133 return MaybeOF.takeError();
134 OF = std::move(*MaybeOF);
135 auto MaybeSerializer = createRemarkSerializer(RemarksFormat: SerializerFormat, OS&: OF->os());
136 if (!MaybeSerializer)
137 return MaybeSerializer.takeError();
138 Serializer = std::move(*MaybeSerializer);
139 return Error::success();
140 }
141
142 void emit(std::unique_ptr<Remark> RPtr) {
143 Remark &R = *RPtr;
144 if (!Sort && !Dedupe) {
145 Serializer->emit(Remark: R);
146 return;
147 }
148 StrTab.internalize(R);
149 auto [It, Inserted] = Remarks.try_emplace(k: std::move(RPtr), args: 1);
150 if (!Dedupe && !Inserted)
151 ++It->second;
152 }
153
154 void emitBuffered() {
155 for (auto &[R, Count] : Remarks) {
156 for (size_t I = 0; I < Count; ++I)
157 Serializer->emit(Remark: *R);
158 }
159 Remarks.clear();
160 }
161};
162
163} // namespace
164
165static Error tryFilter() {
166 auto MaybeFilter = getRemarkFilters();
167 if (!MaybeFilter)
168 return MaybeFilter.takeError();
169 FilterTool Tool(std::move(*MaybeFilter));
170 Tool.Sort = SortOpt;
171 Tool.Dedupe = DedupeOpt;
172 Tool.Exclude = ExcludeOpt;
173
174 for (auto &InputFileName : InputFileNames) {
175 if (Error E = Tool.processInputFile(InputFileName))
176 return E;
177 }
178 return Error::success();
179}
180
181static CommandRegistration FilterReg(&FilterSub, tryFilter);
182