Clang Project

clang_source_code/tools/clang-refactor/TestSupport.cpp
1//===--- TestSupport.cpp - Clang-based refactoring tool -------------------===//
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/// \file
10/// This file implements routines that provide refactoring testing
11/// utilities.
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
27using namespace llvm;
28
29namespace clang {
30namespace refactor {
31
32void 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
41bool 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      // Translate the offset pair to a true source range.
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
66namespace {
67
68void dumpChanges(const tooling::AtomicChanges &Changesraw_ostream &OS) {
69  for (const auto &Change : Changes)
70    OS << const_cast<tooling::AtomicChange &>(Change).toYAMLString() << "\n";
71}
72
73bool 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
84bool printRewrittenSources(const tooling::AtomicChanges &Changes,
85                           raw_ostream &OS) {
86  std::set<std::stringFiles;
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
109class TestRefactoringResultConsumer final
110    : public ClangRefactorToolConsumerInterface {
111public:
112  TestRefactoringResultConsumer(const TestSelectionRangesInFile &TestRanges)
113      : TestRanges(TestRanges) {
114    Results.push_back({});
115  }
116
117  ~TestRefactoringResultConsumer() {
118    // Ensure all results are checked.
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
138private:
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); // error has occurred.
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
160std::pair<unsignedunsignedgetLineColumn(StringRef Filename,
161                                            unsigned Offset) {
162  ErrorOr<std::unique_ptr<MemoryBuffer>> ErrOrFile =
163      MemoryBuffer::getFile(Filename);
164  if (!ErrOrFile)
165    return {00};
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// end anonymous namespace
174
175bool TestRefactoringResultConsumer::handleAllResults() {
176  bool Failed = false;
177  for (auto &Group : llvm::enumerate(Results)) {
178    // All ranges in the group must produce the same result.
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      // Verify that this result corresponds to the canonical result.
205      if (CanonicalErrorMessage) {
206        // The error messages must match.
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        // The results must match.
212        if (HasResult && areChangesSame(*Result, *CanonicalResult))
213          continue;
214      }
215      Failed = true;
216      // Report the mismatch.
217      std::pair<unsignedunsigned> 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    // Dump the results:
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
257std::unique_ptr<ClangRefactorToolConsumerInterface>
258TestSelectionRangesInFile::createConsumer() const {
259  return llvm::make_unique<TestRefactoringResultConsumer>(*this);
260}
261
262/// Adds the \p ColumnOffset to file offset \p Offset, without going past a
263/// newline.
264static unsigned addColumnOffset(StringRef Sourceunsigned 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
274static unsigned addEndLineOffsetAndEndColumn(StringRef Sourceunsigned 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    // Line offset goes out of bounds.
282    if (NewlinePos == StringRef::npos)
283      break;
284    LineOffset += NewlinePos + 1;
285    Line = Line.drop_front(NewlinePos + 1);
286  }
287  // Source now points to the line at +lineOffset;
288  size_t LineStart = Source.find_last_of("\r\n"/*From=*/Offset + LineOffset);
289  return addColumnOffset(
290      Source, LineStart == StringRef::npos ? 0 : LineStart + 1, Column - 1);
291}
292
293Optional<TestSelectionRangesInFile>
294findTestSelectionRanges(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  // See the doc comment for this function for the explanation of this
305  // syntax.
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    // Try to detect mistyped 'range:' comments to ensure tests don't miss
327    // anything.
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    // Allow CHECK: comments to contain range= commands.
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      // Don't forget to drop the '+'!
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// end namespace refactor
391// end namespace clang
392