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
9#include "llvm/Support/SourceMgrDiagnosticVerifier.h"
10#include "llvm/Support/MemoryBuffer.h"
11#include "llvm/Support/raw_ostream.h"
12
13using namespace llvm;
14
15/// Given a diagnostic kind, return a human readable string for it.
16static StringRef getDiagKindStr(SourceMgr::DiagKind kind) {
17 switch (kind) {
18 case SourceMgr::DK_Note:
19 return "note";
20 case SourceMgr::DK_Warning:
21 return "warning";
22 case SourceMgr::DK_Error:
23 return "error";
24 case SourceMgr::DK_Remark:
25 return "remark";
26 }
27 llvm_unreachable("Unknown SourceMgr::DiagKind");
28}
29
30bool SourceMgrDiagnosticVerifier::ExpectedDiag::emitError(
31 raw_ostream &os, SourceMgr &mgr, const Twine &msg) const {
32 if (fileLoc.isValid()) {
33 SMRange range(fileLoc, SMLoc::getFromPointer(Ptr: fileLoc.getPointer() +
34 substring.size()));
35 mgr.PrintMessage(OS&: os, Loc: fileLoc, Kind: SourceMgr::DK_Error, Msg: msg, Ranges: range);
36 } else {
37 mgr.PrintMessage(OS&: os, Loc: fileLoc, Kind: SourceMgr::DK_Error, Msg: msg);
38 }
39 return false;
40}
41
42bool SourceMgrDiagnosticVerifier::ExpectedDiag::match(StringRef str) const {
43 // If this isn't a regex diagnostic, we simply check if the string was
44 // contained.
45 if (substringRegex)
46 return substringRegex->match(String: str);
47 return str.contains(Other: substring);
48}
49
50bool SourceMgrDiagnosticVerifier::ExpectedDiag::computeRegex(raw_ostream &os,
51 SourceMgr &mgr) {
52 std::string regexStr;
53 raw_string_ostream regexOS(regexStr);
54 StringRef strToProcess = substring;
55 while (!strToProcess.empty()) {
56 // Find the next regex block.
57 size_t regexIt = strToProcess.find(Str: "{{");
58 if (regexIt == StringRef::npos) {
59 regexOS << Regex::escape(String: strToProcess);
60 break;
61 }
62 regexOS << Regex::escape(String: strToProcess.take_front(N: regexIt));
63 strToProcess = strToProcess.drop_front(N: regexIt + 2);
64
65 // Find the end of the regex block.
66 size_t regexEndIt = strToProcess.find(Str: "}}");
67 if (regexEndIt == StringRef::npos)
68 return emitError(os, mgr, msg: "found start of regex with no end '}}'");
69 StringRef regexBlock = strToProcess.take_front(N: regexEndIt);
70
71 // Validate that the regex is actually valid.
72 std::string regexError;
73 if (!Regex(regexBlock).isValid(Error&: regexError))
74 return emitError(os, mgr, msg: "invalid regex: " + regexError);
75
76 regexOS << '(' << regexBlock << ')';
77 strToProcess = strToProcess.drop_front(N: regexEndIt + 2);
78 }
79 substringRegex = Regex(regexStr);
80 return true;
81}
82
83MutableArrayRef<SourceMgrDiagnosticVerifier::ExpectedDiag>
84SourceMgrDiagnosticVerifier::computeExpectedDiags(raw_ostream &os,
85 SourceMgr &mgr,
86 const MemoryBuffer *buf) {
87 // If the buffer is invalid, return an empty list.
88 if (!buf)
89 return {};
90 auto &expectedDiags = expectedDiagsPerFile[buf->getBufferIdentifier()];
91
92 // The number of the last line that did not correlate to a designator.
93 unsigned lastNonDesignatorLine = 0;
94
95 // The indices of designators that apply to the next non designator line.
96 SmallVector<unsigned, 1> designatorsForNextLine;
97
98 // Scan the file for expected-* designators.
99 SmallVector<StringRef, 100> lines;
100 buf->getBuffer().split(A&: lines, Separator: '\n');
101 for (unsigned lineNo = 0, e = lines.size(); lineNo < e; ++lineNo) {
102 SmallVector<StringRef, 4> matches;
103 if (!expected.match(String: lines[lineNo].rtrim(), Matches: &matches)) {
104 // Check for designators that apply to this line.
105 if (!designatorsForNextLine.empty()) {
106 for (unsigned diagIndex : designatorsForNextLine)
107 expectedDiags[diagIndex].lineNo = lineNo + 1;
108 designatorsForNextLine.clear();
109 }
110 lastNonDesignatorLine = lineNo;
111 continue;
112 }
113
114 // Point to the start of expected-*.
115 SMLoc expectedStart = SMLoc::getFromPointer(Ptr: matches[0].data());
116
117 SourceMgr::DiagKind kind;
118 if (matches[1] == "error")
119 kind = SourceMgr::DK_Error;
120 else if (matches[1] == "warning")
121 kind = SourceMgr::DK_Warning;
122 else if (matches[1] == "remark")
123 kind = SourceMgr::DK_Remark;
124 else {
125 assert(matches[1] == "note");
126 kind = SourceMgr::DK_Note;
127 }
128 ExpectedDiag record(kind, lineNo + 1, expectedStart, matches[5]);
129
130 // Check to see if this is a regex match, i.e. it includes the `-re`.
131 if (!matches[2].empty() && !record.computeRegex(os, mgr)) {
132 ok = false;
133 continue;
134 }
135
136 StringRef offsetMatch = matches[3];
137 if (!offsetMatch.empty()) {
138 offsetMatch = offsetMatch.drop_front(N: 1);
139
140 // Get the integer value without the @ and +/- prefix.
141 if (offsetMatch[0] == '+' || offsetMatch[0] == '-') {
142 int offset;
143 offsetMatch.drop_front().getAsInteger(Radix: 0, Result&: offset);
144
145 if (offsetMatch.front() == '+')
146 record.lineNo += offset;
147 else
148 record.lineNo -= offset;
149 } else if (offsetMatch.consume_front(Prefix: "unknown")) {
150 // This is matching unknown locations.
151 record.fileLoc = SMLoc();
152 expectedUnknownLocDiags.emplace_back(Args: std::move(record));
153 continue;
154 } else if (offsetMatch.consume_front(Prefix: "above")) {
155 // If the designator applies 'above' we add it to the last non
156 // designator line.
157 record.lineNo = lastNonDesignatorLine + 1;
158 } else {
159 // Otherwise, this is a 'below' designator and applies to the next
160 // non-designator line.
161 assert(offsetMatch.consume_front("below"));
162 designatorsForNextLine.push_back(Elt: expectedDiags.size());
163
164 // Set the line number to the last in the case that this designator
165 // ends up dangling.
166 record.lineNo = e;
167 }
168 }
169 expectedDiags.emplace_back(Args: std::move(record));
170 }
171 return expectedDiags;
172}
173
174std::optional<MutableArrayRef<SourceMgrDiagnosticVerifier::ExpectedDiag>>
175SourceMgrDiagnosticVerifier::getExpectedDiags(StringRef bufName) {
176 auto expectedDiags = expectedDiagsPerFile.find(Key: bufName);
177 if (expectedDiags != expectedDiagsPerFile.end())
178 return MutableArrayRef<ExpectedDiag>(expectedDiags->second);
179 return std::nullopt;
180}
181
182SourceMgrDiagnosticVerifier::MatchResult
183SourceMgrDiagnosticVerifier::process(raw_ostream &os, SourceMgr &mgr,
184 SourceMgr::DiagKind kind, bool hasLoc,
185 const MemoryBuffer *buf, unsigned lineNo,
186 StringRef message, bool reportUnexpected) {
187 MutableArrayRef<ExpectedDiag> diags;
188 if (hasLoc) {
189 // If the buffer couldn't be resolved, `diags` stays empty: a diagnostic
190 // with a location in an unknown file can never match anything.
191 if (buf) {
192 if (auto maybeDiags = getExpectedDiags(bufName: buf->getBufferIdentifier()))
193 diags = *maybeDiags;
194 else
195 diags = computeExpectedDiags(os, mgr, buf);
196 }
197 } else {
198 diags = expectedUnknownLocDiags;
199 }
200
201 // Search for a matching expected diagnostic.
202 // If we find something that is close then emit a more specific error.
203 ExpectedDiag *nearMiss = nullptr;
204
205 // If this was an expected error, remember that we saw it and return.
206 for (auto &e : diags) {
207 // File line must match (unless it's an unknown location).
208 if (hasLoc && e.lineNo != lineNo)
209 continue;
210 if (e.match(str: message)) {
211 if (e.kind == kind) {
212 e.matched = true;
213 return MatchResult::Matched;
214 }
215
216 // If this only differs based on the diagnostic kind, then consider it
217 // to be a near miss.
218 nearMiss = &e;
219 }
220 }
221
222 if (!reportUnexpected)
223 return MatchResult::Ignored;
224
225 ok = false;
226
227 // Otherwise, emit an error for the near miss.
228 if (nearMiss) {
229 mgr.PrintMessage(OS&: os, Loc: nearMiss->fileLoc, Kind: SourceMgr::DK_Error,
230 Msg: "'" + getDiagKindStr(kind) +
231 "' diagnostic emitted when expecting a '" +
232 getDiagKindStr(kind: nearMiss->kind) + "'");
233 return MatchResult::NearMiss;
234 }
235 return MatchResult::Unexpected;
236}
237
238bool SourceMgrDiagnosticVerifier::verify(raw_ostream &os, SourceMgr &mgr) {
239 // Verify that all expected errors were seen.
240 auto checkExpectedDiags = [&](ExpectedDiag &diag) {
241 if (!diag.matched) {
242 diag.emitError(os, mgr,
243 msg: "expected " + getDiagKindStr(kind: diag.kind) + " \"" +
244 diag.substring + "\" was not produced");
245 ok = false;
246 }
247 };
248 for (auto &expectedDiagsPair : expectedDiagsPerFile)
249 for (auto &diag : expectedDiagsPair.second)
250 checkExpectedDiags(diag);
251 for (auto &diag : expectedUnknownLocDiags)
252 checkExpectedDiags(diag);
253 expectedDiagsPerFile.clear();
254 return ok;
255}
256