LLVM 24.0.0git
SourceMgrDiagnosticVerifier.cpp
Go to the documentation of this file.
1//===- SourceMgrDiagnosticVerifier.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
10#include "llvm/ADT/STLExtras.h"
13
14using namespace llvm;
15
16/// Given a diagnostic kind, return a human readable string for it.
18 switch (kind) {
20 return "note";
22 return "warning";
24 return "error";
26 return "remark";
27 }
28 llvm_unreachable("Unknown SourceMgr::DiagKind");
29}
30
32 raw_ostream &os, SourceMgr &mgr, const Twine &msg) const {
33 if (fileLoc.isValid()) {
34 SMRange range(fileLoc, SMLoc::getFromPointer(fileLoc.getPointer() +
35 substring.size()));
36 mgr.PrintMessage(os, fileLoc, SourceMgr::DK_Error, msg, range);
37 } else {
39 }
40 return false;
41}
42
44 // If this isn't a regex diagnostic, we simply check if the string was
45 // contained.
47 return substringRegex->match(str);
48 return str.contains(substring);
49}
50
52 SourceMgr &mgr) {
53 std::string regexStr;
54 raw_string_ostream regexOS(regexStr);
55 StringRef strToProcess = substring;
56 while (!strToProcess.empty()) {
57 // Find the next regex block.
58 size_t regexIt = strToProcess.find("{{");
59 if (regexIt == StringRef::npos) {
60 regexOS << Regex::escape(strToProcess);
61 break;
62 }
63 regexOS << Regex::escape(strToProcess.take_front(regexIt));
64 strToProcess = strToProcess.drop_front(regexIt + 2);
65
66 // Find the end of the regex block.
67 size_t regexEndIt = strToProcess.find("}}");
68 if (regexEndIt == StringRef::npos)
69 return emitError(os, mgr, "found start of regex with no end '}}'");
70 StringRef regexBlock = strToProcess.take_front(regexEndIt);
71
72 // Validate that the regex is actually valid.
73 std::string regexError;
74 if (!Regex(regexBlock).isValid(regexError))
75 return emitError(os, mgr, "invalid regex: " + regexError);
76
77 regexOS << '(' << regexBlock << ')';
78 strToProcess = strToProcess.drop_front(regexEndIt + 2);
79 }
80 substringRegex = Regex(regexStr);
81 return true;
82}
83
85 assert(!prefixes.empty() && "Diagnostic verifier needs at least one prefix");
86 std::string pattern = "(^|[^[:alnum:]_-])(";
88 prefixes,
89 [&](StringRef prefix) {
90 assert(!prefix.empty() && "Diagnostic verifier prefix cannot be empty");
91 pattern += Regex::escape(prefix);
92 },
93 [&] { pattern += '|'; });
94 pattern += ")-(error|note|remark|warning)(-re)?"
95 "( *@([+-][0-9]+|above|below|unknown))? *{{(.*)}}$";
96 return Regex(pattern);
97}
98
102
105 SourceMgr &mgr,
106 const MemoryBuffer *buf) {
107 // If the buffer is invalid, return an empty list.
108 if (!buf)
109 return {};
110 auto &expectedDiags = expectedDiagsPerFile[buf->getBufferIdentifier()];
111
112 // The number of the last line that did not correlate to a designator.
113 unsigned lastNonDesignatorLine = 0;
114
115 // The indices of designators that apply to the next non designator line.
116 SmallVector<unsigned, 1> designatorsForNextLine;
117
118 // Scan the file for expected-* designators.
120 buf->getBuffer().split(lines, '\n');
121 for (unsigned lineNo = 0, e = lines.size(); lineNo < e; ++lineNo) {
123 if (!expected.match(lines[lineNo].rtrim(), &matches)) {
124 // Check for designators that apply to this line.
125 if (!designatorsForNextLine.empty()) {
126 for (unsigned diagIndex : designatorsForNextLine)
127 expectedDiags[diagIndex].lineNo = lineNo + 1;
128 designatorsForNextLine.clear();
129 }
130 lastNonDesignatorLine = lineNo;
131 continue;
132 }
133
134 // Point to the start of <prefix>-<kind>.
135 SMLoc expectedStart = SMLoc::getFromPointer(matches[2].data());
136
138 if (matches[3] == "error")
139 kind = SourceMgr::DK_Error;
140 else if (matches[3] == "warning")
142 else if (matches[3] == "remark")
144 else {
145 assert(matches[3] == "note");
146 kind = SourceMgr::DK_Note;
147 }
148 ExpectedDiag record(kind, lineNo + 1, expectedStart, matches[7]);
149
150 // Check to see if this is a regex match, i.e. it includes the `-re`.
151 if (!matches[4].empty() && !record.computeRegex(os, mgr)) {
152 ok = false;
153 continue;
154 }
155
156 StringRef offsetMatch = matches[6];
157 if (!offsetMatch.empty()) {
158 // Get the integer value without the @ and +/- prefix.
159 if (offsetMatch[0] == '+' || offsetMatch[0] == '-') {
160 int offset;
161 offsetMatch.drop_front().getAsInteger(0, offset);
162
163 if (offsetMatch.front() == '+')
164 record.lineNo += offset;
165 else
166 record.lineNo -= offset;
167 } else if (offsetMatch.consume_front("unknown")) {
168 // This is matching unknown locations.
169 record.fileLoc = SMLoc();
170 expectedUnknownLocDiags.emplace_back(std::move(record));
171 continue;
172 } else if (offsetMatch.consume_front("above")) {
173 // If the designator applies 'above' we add it to the last non
174 // designator line.
175 record.lineNo = lastNonDesignatorLine + 1;
176 } else {
177 // Otherwise, this is a 'below' designator and applies to the next
178 // non-designator line.
179 assert(offsetMatch.consume_front("below"));
180 designatorsForNextLine.push_back(expectedDiags.size());
181
182 // Set the line number to the last in the case that this designator
183 // ends up dangling.
184 record.lineNo = e;
185 }
186 }
187 expectedDiags.emplace_back(std::move(record));
188 }
189 return expectedDiags;
190}
191
192std::optional<MutableArrayRef<SourceMgrDiagnosticVerifier::ExpectedDiag>>
194 auto expectedDiags = expectedDiagsPerFile.find(bufName);
195 if (expectedDiags != expectedDiagsPerFile.end())
196 return MutableArrayRef<ExpectedDiag>(expectedDiags->second);
197 return std::nullopt;
198}
199
202 SourceMgr::DiagKind kind, bool hasLoc,
203 const MemoryBuffer *buf, unsigned lineNo,
204 StringRef message, bool reportUnexpected) {
206 if (hasLoc) {
207 // If the buffer couldn't be resolved, `diags` stays empty: a diagnostic
208 // with a location in an unknown file can never match anything.
209 if (buf) {
210 if (auto maybeDiags = getExpectedDiags(buf->getBufferIdentifier()))
211 diags = *maybeDiags;
212 else
213 diags = computeExpectedDiags(os, mgr, buf);
214 }
215 } else {
216 diags = expectedUnknownLocDiags;
217 }
218
219 // Search for a matching expected diagnostic.
220 // If we find something that is close then emit a more specific error.
221 ExpectedDiag *nearMiss = nullptr;
222
223 // If this was an expected error, remember that we saw it and return.
224 for (auto &e : diags) {
225 // File line must match (unless it's an unknown location).
226 if (hasLoc && e.lineNo != lineNo)
227 continue;
228 if (e.match(message)) {
229 if (e.kind == kind) {
230 e.matched = true;
232 }
233
234 // If this only differs based on the diagnostic kind, then consider it
235 // to be a near miss.
236 nearMiss = &e;
237 }
238 }
239
240 if (!reportUnexpected)
242
243 ok = false;
244
245 // Otherwise, emit an error for the near miss.
246 if (nearMiss) {
247 mgr.PrintMessage(os, nearMiss->fileLoc, SourceMgr::DK_Error,
248 "'" + getDiagKindStr(kind) +
249 "' diagnostic emitted when expecting a '" +
250 getDiagKindStr(nearMiss->kind) + "'");
252 }
254}
255
257 // Verify that all expected errors were seen.
258 auto checkExpectedDiags = [&](ExpectedDiag &diag) {
259 if (!diag.matched) {
260 diag.emitError(os, mgr,
261 "expected " + getDiagKindStr(diag.kind) + " \"" +
262 diag.substring + "\" was not produced");
263 ok = false;
264 }
265 };
266 for (auto &expectedDiagsPair : expectedDiagsPerFile)
267 for (auto &diag : expectedDiagsPair.second)
268 checkExpectedDiags(diag);
269 for (auto &diag : expectedUnknownLocDiags)
270 checkExpectedDiags(diag);
271 expectedDiagsPerFile.clear();
272 expectedUnknownLocDiags.clear();
273 return ok;
274}
assert(UImm &&(UImm !=~static_cast< T >(0)) &&"Invalid immediate!")
bool hasLoc(const Instruction &I)
Definition Debugify.cpp:403
static bool isValid(const char C)
Returns true if C is a valid mangled character: <0-9a-zA-Z_>.
This file contains some templates that are useful if you are working with the STL at all.
static StringRef getDiagKindStr(SourceMgr::DiagKind kind)
Given a diagnostic kind, return a human readable string for it.
static Regex buildExpectedRegex(ArrayRef< StringRef > prefixes)
static Split data
Represent a constant reference to an array (0 or more elements consecutively in memory),...
Definition ArrayRef.h:40
bool empty() const
Check if the array is empty.
Definition ArrayRef.h:136
This interface provides simple read-only access to a block of memory, and provides simple methods for...
virtual StringRef getBufferIdentifier() const
Return an identifier for this buffer, typically the filename it was read from.
StringRef getBuffer() const
Represent a mutable reference to an array (0 or more elements consecutively in memory),...
Definition ArrayRef.h:294
static LLVM_ABI std::string escape(StringRef String)
Turn String into a regex by escaping its special characters.
Definition Regex.cpp:251
Represents a location in source code.
Definition SMLoc.h:22
static SMLoc getFromPointer(const char *Ptr)
Definition SMLoc.h:35
Represents a range in source code.
Definition SMLoc.h:47
reference emplace_back(ArgTypes &&... Args)
void push_back(const T &Elt)
This is a 'vector' (really, a variable-sized array), optimized for the case when the array is small.
MatchResult
The result of matching a single actual diagnostic against the expected diagnostics recorded for its l...
@ Matched
The diagnostic matched an expected diagnostic (kind and text).
@ Unexpected
The diagnostic did not match any expected diagnostic.
@ Ignored
The diagnostic did not match, but reportUnexpected was false, so nothing was printed and nothing need...
@ NearMiss
The diagnostic's text matched an expected diagnostic, but its kind didn't; a message about this has a...
bool verify(raw_ostream &os, SourceMgr &mgr)
Reports (through mgr) any expected diagnostic that was never matched by a call to process.
MatchResult process(raw_ostream &os, SourceMgr &mgr, SourceMgr::DiagKind kind, bool hasLoc, const MemoryBuffer *buf, unsigned lineNo, StringRef message, bool reportUnexpected=true)
Matches a single actual diagnostic against the expected diagnostics recorded for buf / lineNo,...
MutableArrayRef< ExpectedDiag > computeExpectedDiags(raw_ostream &os, SourceMgr &mgr, const MemoryBuffer *buf)
Computes and caches the list of expected diagnostics for buf, if not already cached.
std::optional< MutableArrayRef< ExpectedDiag > > getExpectedDiags(StringRef bufName)
Returns the cached expected diagnostics for the buffer named bufName, or std::nullopt if computeExpec...
SourceMgrDiagnosticVerifier(ArrayRef< StringRef > prefixes={ "expected"})
Recognizes directives starting with any of prefixes (e.g.
This owns the files read by a parser, handles include stacks, and handles diagnostic wrangling.
Definition SourceMgr.h:34
LLVM_ABI void PrintMessage(raw_ostream &OS, SMLoc Loc, DiagKind Kind, const Twine &Msg, ArrayRef< SMRange > Ranges={}, ArrayRef< SMFixIt > FixIts={}, bool ShowColors=true) const
Emit a message about the specified location with the specified string.
Represent a constant reference to a string, i.e.
Definition StringRef.h:56
std::pair< StringRef, StringRef > split(char Separator) const
Split into two substrings around the first occurrence of a separator character.
Definition StringRef.h:736
static constexpr size_t npos
Definition StringRef.h:58
bool getAsInteger(unsigned Radix, T &Result) const
Parse the current string as an integer of the specified radix.
Definition StringRef.h:490
constexpr bool empty() const
Check if the string is empty.
Definition StringRef.h:141
StringRef drop_front(size_t N=1) const
Return a StringRef equal to 'this' but with the first N elements dropped.
Definition StringRef.h:635
char front() const
Get the first character in the string.
Definition StringRef.h:147
bool contains(StringRef Other) const
Return true if the given string is a substring of *this, and false otherwise.
Definition StringRef.h:446
StringRef take_front(size_t N=1) const
Return a StringRef equal to 'this' but with only the first N elements remaining.
Definition StringRef.h:606
size_t find(char C, size_t From=0) const
Search for the first character C in the string.
Definition StringRef.h:290
bool consume_front(char Prefix)
Returns true if this StringRef has the given prefix and removes that prefix.
Definition StringRef.h:661
Twine - A lightweight data structure for efficiently representing the concatenation of temporary valu...
Definition Twine.h:82
This class implements an extremely fast bulk output stream that can only output to a stream.
Definition raw_ostream.h:53
A raw_ostream that writes to an std::string.
#define llvm_unreachable(msg)
Marks that the current location is not supposed to be reachable.
This is an optimization pass for GlobalISel generic memory operations.
void interleave(ForwardIterator begin, ForwardIterator end, UnaryFunctor each_fn, NullaryFunctor between_fn)
An STL-style algorithm similar to std::for_each that applies a second functor between every pair of e...
Definition STLExtras.h:2291
MutableArrayRef(T &OneElt) -> MutableArrayRef< T >
A single diagnostic expected via an 'expected-<kind>' comment.
std::optional< Regex > substringRegex
An optional regex matcher, if the expected diagnostic substring was a regex string.
StringRef substring
The substring that is expected to be within the diagnostic.
LLVM_ABI bool computeRegex(raw_ostream &os, SourceMgr &mgr)
Computes the regex matcher for a '-re' diagnostic's substring.
SourceMgr::DiagKind kind
The severity of the diagnostic expected.
SMLoc fileLoc
The location of the expected diagnostic within the input file.
LLVM_ABI bool match(StringRef str) const
Returns true if this diagnostic matches the given message.
unsigned lineNo
The line number the expected diagnostic should be on.
LLVM_ABI bool emitError(raw_ostream &os, SourceMgr &mgr, const Twine &msg) const
Prints msg at this diagnostic's location and returns false, for use as return emitError(....