1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
10 | |
11 | |
12 | |
13 | |
14 | |
15 | #include "TestSupport.h" |
16 | #include "clang/Basic/DiagnosticError.h" |
17 | #include "clang/Basic/SourceManager.h" |
18 | #include "clang/Lex/Lexer.h" |
19 | #include "llvm/ADT/STLExtras.h" |
20 | #include "llvm/Support/Error.h" |
21 | #include "llvm/Support/ErrorOr.h" |
22 | #include "llvm/Support/LineIterator.h" |
23 | #include "llvm/Support/MemoryBuffer.h" |
24 | #include "llvm/Support/Regex.h" |
25 | #include "llvm/Support/raw_ostream.h" |
26 | |
27 | using namespace llvm; |
28 | |
29 | namespace clang { |
30 | namespace refactor { |
31 | |
32 | void TestSelectionRangesInFile::dump(raw_ostream &OS) const { |
33 | for (const auto &Group : GroupedRanges) { |
34 | OS << "Test selection group '" << Group.Name << "':\n"; |
35 | for (const auto &Range : Group.Ranges) { |
36 | OS << " " << Range.Begin << "-" << Range.End << "\n"; |
37 | } |
38 | } |
39 | } |
40 | |
41 | bool TestSelectionRangesInFile::foreachRange( |
42 | const SourceManager &SM, |
43 | llvm::function_ref<void(SourceRange)> Callback) const { |
44 | const FileEntry *FE = SM.getFileManager().getFile(Filename); |
45 | FileID FID = FE ? SM.translateFile(FE) : FileID(); |
46 | if (!FE || FID.isInvalid()) { |
47 | llvm::errs() << "error: -selection=test:" << Filename |
48 | << " : given file is not in the target TU"; |
49 | return true; |
50 | } |
51 | SourceLocation FileLoc = SM.getLocForStartOfFile(FID); |
52 | for (const auto &Group : GroupedRanges) { |
53 | for (const TestSelectionRange &Range : Group.Ranges) { |
54 | |
55 | SourceLocation Start = |
56 | SM.getMacroArgExpandedLocation(FileLoc.getLocWithOffset(Range.Begin)); |
57 | SourceLocation End = |
58 | SM.getMacroArgExpandedLocation(FileLoc.getLocWithOffset(Range.End)); |
59 | (0) . __assert_fail ("Start.isValid() && End.isValid() && \"unexpected invalid range\"", "/home/seafit/code_projects/clang_source/clang/tools/clang-refactor/TestSupport.cpp", 59, __PRETTY_FUNCTION__))" file_link="../../../include/assert.h.html#88" macro="true">assert(Start.isValid() && End.isValid() && "unexpected invalid range"); |
60 | Callback(SourceRange(Start, End)); |
61 | } |
62 | } |
63 | return false; |
64 | } |
65 | |
66 | namespace { |
67 | |
68 | void dumpChanges(const tooling::AtomicChanges &Changes, raw_ostream &OS) { |
69 | for (const auto &Change : Changes) |
70 | OS << const_cast<tooling::AtomicChange &>(Change).toYAMLString() << "\n"; |
71 | } |
72 | |
73 | bool areChangesSame(const tooling::AtomicChanges &LHS, |
74 | const tooling::AtomicChanges &RHS) { |
75 | if (LHS.size() != RHS.size()) |
76 | return false; |
77 | for (const auto &I : llvm::zip(LHS, RHS)) { |
78 | if (!(std::get<0>(I) == std::get<1>(I))) |
79 | return false; |
80 | } |
81 | return true; |
82 | } |
83 | |
84 | bool printRewrittenSources(const tooling::AtomicChanges &Changes, |
85 | raw_ostream &OS) { |
86 | std::set<std::string> Files; |
87 | for (const auto &Change : Changes) |
88 | Files.insert(Change.getFilePath()); |
89 | tooling::ApplyChangesSpec Spec; |
90 | Spec.Cleanup = false; |
91 | for (const auto &File : Files) { |
92 | llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> BufferErr = |
93 | llvm::MemoryBuffer::getFile(File); |
94 | if (!BufferErr) { |
95 | llvm::errs() << "failed to open" << File << "\n"; |
96 | return true; |
97 | } |
98 | auto Result = tooling::applyAtomicChanges(File, (*BufferErr)->getBuffer(), |
99 | Changes, Spec); |
100 | if (!Result) { |
101 | llvm::errs() << toString(Result.takeError()); |
102 | return true; |
103 | } |
104 | OS << *Result; |
105 | } |
106 | return false; |
107 | } |
108 | |
109 | class TestRefactoringResultConsumer final |
110 | : public ClangRefactorToolConsumerInterface { |
111 | public: |
112 | TestRefactoringResultConsumer(const TestSelectionRangesInFile &TestRanges) |
113 | : TestRanges(TestRanges) { |
114 | Results.push_back({}); |
115 | } |
116 | |
117 | ~TestRefactoringResultConsumer() { |
118 | |
119 | for (auto &Group : Results) { |
120 | for (auto &Result : Group) { |
121 | if (!Result) { |
122 | (void)llvm::toString(Result.takeError()); |
123 | } |
124 | } |
125 | } |
126 | } |
127 | |
128 | void handleError(llvm::Error Err) override { handleResult(std::move(Err)); } |
129 | |
130 | void handle(tooling::AtomicChanges Changes) override { |
131 | handleResult(std::move(Changes)); |
132 | } |
133 | |
134 | void handle(tooling::SymbolOccurrences Occurrences) override { |
135 | tooling::RefactoringResultConsumer::handle(std::move(Occurrences)); |
136 | } |
137 | |
138 | private: |
139 | bool handleAllResults(); |
140 | |
141 | void handleResult(Expected<tooling::AtomicChanges> Result) { |
142 | Results.back().push_back(std::move(Result)); |
143 | size_t GroupIndex = Results.size() - 1; |
144 | if (Results.back().size() >= |
145 | TestRanges.GroupedRanges[GroupIndex].Ranges.size()) { |
146 | ++GroupIndex; |
147 | if (GroupIndex >= TestRanges.GroupedRanges.size()) { |
148 | if (handleAllResults()) |
149 | exit(1); |
150 | return; |
151 | } |
152 | Results.push_back({}); |
153 | } |
154 | } |
155 | |
156 | const TestSelectionRangesInFile &TestRanges; |
157 | std::vector<std::vector<Expected<tooling::AtomicChanges>>> Results; |
158 | }; |
159 | |
160 | std::pair<unsigned, unsigned> getLineColumn(StringRef Filename, |
161 | unsigned Offset) { |
162 | ErrorOr<std::unique_ptr<MemoryBuffer>> ErrOrFile = |
163 | MemoryBuffer::getFile(Filename); |
164 | if (!ErrOrFile) |
165 | return {0, 0}; |
166 | StringRef Source = ErrOrFile.get()->getBuffer(); |
167 | Source = Source.take_front(Offset); |
168 | size_t LastLine = Source.find_last_of("\r\n"); |
169 | return {Source.count('\n') + 1, |
170 | (LastLine == StringRef::npos ? Offset : Offset - LastLine) + 1}; |
171 | } |
172 | |
173 | } |
174 | |
175 | bool TestRefactoringResultConsumer::handleAllResults() { |
176 | bool Failed = false; |
177 | for (auto &Group : llvm::enumerate(Results)) { |
178 | |
179 | Optional<tooling::AtomicChanges> CanonicalResult; |
180 | Optional<std::string> CanonicalErrorMessage; |
181 | for (auto &I : llvm::enumerate(Group.value())) { |
182 | Expected<tooling::AtomicChanges> &Result = I.value(); |
183 | std::string ErrorMessage; |
184 | bool HasResult = !!Result; |
185 | if (!HasResult) { |
186 | handleAllErrors( |
187 | Result.takeError(), |
188 | [&](StringError &Err) { ErrorMessage = Err.getMessage(); }, |
189 | [&](DiagnosticError &Err) { |
190 | const PartialDiagnosticAt &Diag = Err.getDiagnostic(); |
191 | llvm::SmallString<100> DiagText; |
192 | Diag.second.EmitToString(getDiags(), DiagText); |
193 | ErrorMessage = DiagText.str().str(); |
194 | }); |
195 | } |
196 | if (!CanonicalResult && !CanonicalErrorMessage) { |
197 | if (HasResult) |
198 | CanonicalResult = std::move(*Result); |
199 | else |
200 | CanonicalErrorMessage = std::move(ErrorMessage); |
201 | continue; |
202 | } |
203 | |
204 | |
205 | if (CanonicalErrorMessage) { |
206 | |
207 | if (!HasResult && ErrorMessage == *CanonicalErrorMessage) |
208 | continue; |
209 | } else { |
210 | (0) . __assert_fail ("CanonicalResult && \"missing canonical result\"", "/home/seafit/code_projects/clang_source/clang/tools/clang-refactor/TestSupport.cpp", 210, __PRETTY_FUNCTION__))" file_link="../../../include/assert.h.html#88" macro="true">assert(CanonicalResult && "missing canonical result"); |
211 | |
212 | if (HasResult && areChangesSame(*Result, *CanonicalResult)) |
213 | continue; |
214 | } |
215 | Failed = true; |
216 | |
217 | std::pair<unsigned, unsigned> LineColumn = getLineColumn( |
218 | TestRanges.Filename, |
219 | TestRanges.GroupedRanges[Group.index()].Ranges[I.index()].Begin); |
220 | llvm::errs() |
221 | << "error: unexpected refactoring result for range starting at " |
222 | << LineColumn.first << ':' << LineColumn.second << " in group '" |
223 | << TestRanges.GroupedRanges[Group.index()].Name << "':\n "; |
224 | if (HasResult) |
225 | llvm::errs() << "valid result"; |
226 | else |
227 | llvm::errs() << "error '" << ErrorMessage << "'"; |
228 | llvm::errs() << " does not match initial "; |
229 | if (CanonicalErrorMessage) |
230 | llvm::errs() << "error '" << *CanonicalErrorMessage << "'\n"; |
231 | else |
232 | llvm::errs() << "valid result\n"; |
233 | if (HasResult && !CanonicalErrorMessage) { |
234 | llvm::errs() << " Expected to Produce:\n"; |
235 | dumpChanges(*CanonicalResult, llvm::errs()); |
236 | llvm::errs() << " Produced:\n"; |
237 | dumpChanges(*Result, llvm::errs()); |
238 | } |
239 | } |
240 | |
241 | |
242 | const auto &TestGroup = TestRanges.GroupedRanges[Group.index()]; |
243 | if (!CanonicalResult) { |
244 | llvm::outs() << TestGroup.Ranges.size() << " '" << TestGroup.Name |
245 | << "' results:\n"; |
246 | llvm::outs() << *CanonicalErrorMessage << "\n"; |
247 | } else { |
248 | llvm::outs() << TestGroup.Ranges.size() << " '" << TestGroup.Name |
249 | << "' results:\n"; |
250 | if (printRewrittenSources(*CanonicalResult, llvm::outs())) |
251 | return true; |
252 | } |
253 | } |
254 | return Failed; |
255 | } |
256 | |
257 | std::unique_ptr<ClangRefactorToolConsumerInterface> |
258 | TestSelectionRangesInFile::createConsumer() const { |
259 | return llvm::make_unique<TestRefactoringResultConsumer>(*this); |
260 | } |
261 | |
262 | |
263 | |
264 | static unsigned addColumnOffset(StringRef Source, unsigned Offset, |
265 | unsigned ColumnOffset) { |
266 | if (!ColumnOffset) |
267 | return Offset; |
268 | StringRef Substr = Source.drop_front(Offset).take_front(ColumnOffset); |
269 | size_t NewlinePos = Substr.find_first_of("\r\n"); |
270 | return Offset + |
271 | (NewlinePos == StringRef::npos ? ColumnOffset : (unsigned)NewlinePos); |
272 | } |
273 | |
274 | static unsigned addEndLineOffsetAndEndColumn(StringRef Source, unsigned Offset, |
275 | unsigned LineNumberOffset, |
276 | unsigned Column) { |
277 | StringRef Line = Source.drop_front(Offset); |
278 | unsigned LineOffset = 0; |
279 | for (; LineNumberOffset != 0; --LineNumberOffset) { |
280 | size_t NewlinePos = Line.find_first_of("\r\n"); |
281 | |
282 | if (NewlinePos == StringRef::npos) |
283 | break; |
284 | LineOffset += NewlinePos + 1; |
285 | Line = Line.drop_front(NewlinePos + 1); |
286 | } |
287 | |
288 | size_t LineStart = Source.find_last_of("\r\n", Offset + LineOffset); |
289 | return addColumnOffset( |
290 | Source, LineStart == StringRef::npos ? 0 : LineStart + 1, Column - 1); |
291 | } |
292 | |
293 | Optional<TestSelectionRangesInFile> |
294 | findTestSelectionRanges(StringRef Filename) { |
295 | ErrorOr<std::unique_ptr<MemoryBuffer>> ErrOrFile = |
296 | MemoryBuffer::getFile(Filename); |
297 | if (!ErrOrFile) { |
298 | llvm::errs() << "error: -selection=test:" << Filename |
299 | << " : could not open the given file"; |
300 | return None; |
301 | } |
302 | StringRef Source = ErrOrFile.get()->getBuffer(); |
303 | |
304 | |
305 | |
306 | static Regex RangeRegex("range[[:blank:]]*([[:alpha:]_]*)?[[:blank:]]*=[[:" |
307 | "blank:]]*(\\+[[:digit:]]+)?[[:blank:]]*(->[[:blank:]" |
308 | "]*[\\+\\:[:digit:]]+)?"); |
309 | |
310 | std::map<std::string, SmallVector<TestSelectionRange, 8>> GroupedRanges; |
311 | |
312 | LangOptions LangOpts; |
313 | LangOpts.CPlusPlus = 1; |
314 | LangOpts.CPlusPlus11 = 1; |
315 | Lexer Lex(SourceLocation::getFromRawEncoding(0), LangOpts, Source.begin(), |
316 | Source.begin(), Source.end()); |
317 | Lex.SetCommentRetentionState(true); |
318 | Token Tok; |
319 | for (Lex.LexFromRawLexer(Tok); Tok.isNot(tok::eof); |
320 | Lex.LexFromRawLexer(Tok)) { |
321 | if (Tok.isNot(tok::comment)) |
322 | continue; |
323 | StringRef Comment = |
324 | Source.substr(Tok.getLocation().getRawEncoding(), Tok.getLength()); |
325 | SmallVector<StringRef, 4> Matches; |
326 | |
327 | |
328 | auto DetectMistypedCommand = [&]() -> bool { |
329 | if (Comment.contains_lower("range") && Comment.contains("=") && |
330 | !Comment.contains_lower("run") && !Comment.contains("CHECK")) { |
331 | llvm::errs() << "error: suspicious comment '" << Comment |
332 | << "' that " |
333 | "resembles the range command found\n"; |
334 | llvm::errs() << "note: please reword if this isn't a range command\n"; |
335 | } |
336 | return false; |
337 | }; |
338 | |
339 | if (!RangeRegex.match(Comment, &Matches) || Comment.contains("CHECK")) { |
340 | if (DetectMistypedCommand()) |
341 | return None; |
342 | continue; |
343 | } |
344 | unsigned Offset = Tok.getEndLoc().getRawEncoding(); |
345 | unsigned ColumnOffset = 0; |
346 | if (!Matches[2].empty()) { |
347 | |
348 | if (Matches[2].drop_front().getAsInteger(10, ColumnOffset)) |
349 | (0) . __assert_fail ("false && \"regex should have produced a number\"", "/home/seafit/code_projects/clang_source/clang/tools/clang-refactor/TestSupport.cpp", 349, __PRETTY_FUNCTION__))" file_link="../../../include/assert.h.html#88" macro="true">assert(false && "regex should have produced a number"); |
350 | } |
351 | Offset = addColumnOffset(Source, Offset, ColumnOffset); |
352 | unsigned EndOffset; |
353 | |
354 | if (!Matches[3].empty()) { |
355 | static Regex EndLocRegex( |
356 | "->[[:blank:]]*(\\+[[:digit:]]+):([[:digit:]]+)"); |
357 | SmallVector<StringRef, 4> EndLocMatches; |
358 | if (!EndLocRegex.match(Matches[3], &EndLocMatches)) { |
359 | if (DetectMistypedCommand()) |
360 | return None; |
361 | continue; |
362 | } |
363 | unsigned EndLineOffset = 0, EndColumn = 0; |
364 | if (EndLocMatches[1].drop_front().getAsInteger(10, EndLineOffset) || |
365 | EndLocMatches[2].getAsInteger(10, EndColumn)) |
366 | (0) . __assert_fail ("false && \"regex should have produced a number\"", "/home/seafit/code_projects/clang_source/clang/tools/clang-refactor/TestSupport.cpp", 366, __PRETTY_FUNCTION__))" file_link="../../../include/assert.h.html#88" macro="true">assert(false && "regex should have produced a number"); |
367 | EndOffset = addEndLineOffsetAndEndColumn(Source, Offset, EndLineOffset, |
368 | EndColumn); |
369 | } else { |
370 | EndOffset = Offset; |
371 | } |
372 | TestSelectionRange Range = {Offset, EndOffset}; |
373 | auto It = GroupedRanges.insert(std::make_pair( |
374 | Matches[1].str(), SmallVector<TestSelectionRange, 8>{Range})); |
375 | if (!It.second) |
376 | It.first->second.push_back(Range); |
377 | } |
378 | if (GroupedRanges.empty()) { |
379 | llvm::errs() << "error: -selection=test:" << Filename |
380 | << ": no 'range' commands"; |
381 | return None; |
382 | } |
383 | |
384 | TestSelectionRangesInFile TestRanges = {Filename.str(), {}}; |
385 | for (auto &Group : GroupedRanges) |
386 | TestRanges.GroupedRanges.push_back({Group.first, std::move(Group.second)}); |
387 | return std::move(TestRanges); |
388 | } |
389 | |
390 | } |
391 | } |
392 | |