Coverage Report

Created: 2020-02-18 08:44

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