Coverage Report

Created: 2020-09-19 12:23

/Users/buildslave/jenkins/workspace/coverage/llvm-project/clang/tools/clang-diff/ClangDiff.cpp
Line
Count
Source (jump to first uncovered line)
1
//===- ClangDiff.cpp - compare source files by AST nodes ------*- C++ -*- -===//
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
// This file implements a tool for syntax tree based comparison using
10
// Tooling/ASTDiff.
11
//
12
//===----------------------------------------------------------------------===//
13
14
#include "clang/Tooling/ASTDiff/ASTDiff.h"
15
#include "clang/Tooling/CommonOptionsParser.h"
16
#include "clang/Tooling/Tooling.h"
17
#include "llvm/Support/CommandLine.h"
18
19
using namespace llvm;
20
using namespace clang;
21
using namespace clang::tooling;
22
23
static cl::OptionCategory ClangDiffCategory("clang-diff options");
24
25
static cl::opt<bool>
26
    ASTDump("ast-dump",
27
            cl::desc("Print the internal representation of the AST."),
28
            cl::init(false), cl::cat(ClangDiffCategory));
29
30
static cl::opt<bool> ASTDumpJson(
31
    "ast-dump-json",
32
    cl::desc("Print the internal representation of the AST as JSON."),
33
    cl::init(false), cl::cat(ClangDiffCategory));
34
35
static cl::opt<bool> PrintMatches("dump-matches",
36
                                  cl::desc("Print the matched nodes."),
37
                                  cl::init(false), cl::cat(ClangDiffCategory));
38
39
static cl::opt<bool> HtmlDiff("html",
40
                              cl::desc("Output a side-by-side diff in HTML."),
41
                              cl::init(false), cl::cat(ClangDiffCategory));
42
43
static cl::opt<std::string> SourcePath(cl::Positional, cl::desc("<source>"),
44
                                       cl::Required,
45
                                       cl::cat(ClangDiffCategory));
46
47
static cl::opt<std::string> DestinationPath(cl::Positional,
48
                                            cl::desc("<destination>"),
49
                                            cl::Optional,
50
                                            cl::cat(ClangDiffCategory));
51
52
static cl::opt<std::string> StopAfter("stop-diff-after",
53
                                      cl::desc("<topdown|bottomup>"),
54
                                      cl::Optional, cl::init(""),
55
                                      cl::cat(ClangDiffCategory));
56
57
static cl::opt<int> MaxSize("s", cl::desc("<maxsize>"), cl::Optional,
58
                            cl::init(-1), cl::cat(ClangDiffCategory));
59
60
static cl::opt<std::string> BuildPath("p", cl::desc("Build path"), cl::init(""),
61
                                      cl::Optional, cl::cat(ClangDiffCategory));
62
63
static cl::list<std::string> ArgsAfter(
64
    "extra-arg",
65
    cl::desc("Additional argument to append to the compiler command line"),
66
    cl::cat(ClangDiffCategory));
67
68
static cl::list<std::string> ArgsBefore(
69
    "extra-arg-before",
70
    cl::desc("Additional argument to prepend to the compiler command line"),
71
    cl::cat(ClangDiffCategory));
72
73
28
static void addExtraArgs(std::unique_ptr<CompilationDatabase> &Compilations) {
74
28
  if (!Compilations)
75
17
    return;
76
11
  auto AdjustingCompilations =
77
11
      std::make_unique<ArgumentsAdjustingCompilations>(
78
11
          std::move(Compilations));
79
11
  AdjustingCompilations->appendArgumentsAdjuster(
80
11
      getInsertArgumentAdjuster(ArgsBefore, ArgumentInsertPosition::BEGIN));
81
11
  AdjustingCompilations->appendArgumentsAdjuster(
82
11
      getInsertArgumentAdjuster(ArgsAfter, ArgumentInsertPosition::END));
83
11
  Compilations = std::move(AdjustingCompilations);
84
11
}
85
86
static std::unique_ptr<ASTUnit>
87
getAST(const std::unique_ptr<CompilationDatabase> &CommonCompilations,
88
17
       const StringRef Filename) {
89
17
  std::string ErrorMessage;
90
17
  std::unique_ptr<CompilationDatabase> Compilations;
91
17
  if (!CommonCompilations) {
92
0
    Compilations = CompilationDatabase::autoDetectFromSource(
93
0
        BuildPath.empty() ? Filename : BuildPath, ErrorMessage);
94
0
    if (!Compilations) {
95
0
      llvm::errs()
96
0
          << "Error while trying to load a compilation database, running "
97
0
             "without flags.\n"
98
0
          << ErrorMessage;
99
0
      Compilations =
100
0
          std::make_unique<clang::tooling::FixedCompilationDatabase>(
101
0
              ".", std::vector<std::string>());
102
0
    }
103
0
  }
104
17
  addExtraArgs(Compilations);
105
17
  std::array<std::string, 1> Files = {{std::string(Filename)}};
106
17
  ClangTool Tool(Compilations ? 
*Compilations0
: *CommonCompilations, Files);
107
17
  std::vector<std::unique_ptr<ASTUnit>> ASTs;
108
17
  Tool.buildASTs(ASTs);
109
17
  if (ASTs.size() != Files.size())
110
0
    return nullptr;
111
17
  return std::move(ASTs[0]);
112
17
}
113
114
4
static char hexdigit(int N) { return N &= 0xf, N + (N < 10 ? 
'0'3
:
'a' - 101
); }
115
116
static const char HtmlDiffHeader[] = R"(
117
<html>
118
<head>
119
<meta charset='utf-8'/>
120
<style>
121
span.d { color: red; }
122
span.u { color: #cc00cc; }
123
span.i { color: green; }
124
span.m { font-weight: bold; }
125
span   { font-weight: normal; color: black; }
126
div.code {
127
  width: 48%;
128
  height: 98%;
129
  overflow: scroll;
130
  float: left;
131
  padding: 0 0 0.5% 0.5%;
132
  border: solid 2px LightGrey;
133
  border-radius: 5px;
134
}
135
</style>
136
</head>
137
<script type='text/javascript'>
138
highlightStack = []
139
function clearHighlight() {
140
  while (highlightStack.length) {
141
    var [l, r] = highlightStack.pop()
142
    document.getElementById(l).style.backgroundColor = 'inherit'
143
    if (r[1] != '-')
144
      document.getElementById(r).style.backgroundColor = 'inherit'
145
  }
146
}
147
function highlight(event) {
148
  var id = event.target['id']
149
  doHighlight(id)
150
}
151
function doHighlight(id) {
152
  clearHighlight()
153
  source = document.getElementById(id)
154
  if (!source.attributes['tid'])
155
    return
156
  var mapped = source
157
  while (mapped && mapped.parentElement && mapped.attributes['tid'].value.substr(1) === '-1')
158
    mapped = mapped.parentElement
159
  var tid = null, target = null
160
  if (mapped) {
161
    tid = mapped.attributes['tid'].value
162
    target = document.getElementById(tid)
163
  }
164
  if (source.parentElement && source.parentElement.classList.contains('code'))
165
    return
166
  source.style.backgroundColor = 'lightgrey'
167
  source.scrollIntoView()
168
  if (target) {
169
    if (mapped === source)
170
      target.style.backgroundColor = 'lightgrey'
171
    target.scrollIntoView()
172
  }
173
  highlightStack.push([id, tid])
174
  location.hash = '#' + id
175
}
176
function scrollToBoth() {
177
  doHighlight(location.hash.substr(1))
178
}
179
function changed(elem) {
180
  return elem.classList.length == 0
181
}
182
function nextChangedNode(prefix, increment, number) {
183
  do {
184
    number += increment
185
    var elem = document.getElementById(prefix + number)
186
  } while(elem && !changed(elem))
187
  return elem ? number : null
188
}
189
function handleKey(e) {
190
  var down = e.code === "KeyJ"
191
  var up = e.code === "KeyK"
192
  if (!down && !up)
193
    return
194
  var id = highlightStack[0] ? highlightStack[0][0] : 'R0'
195
  var oldelem = document.getElementById(id)
196
  var number = parseInt(id.substr(1))
197
  var increment = down ? 1 : -1
198
  var lastnumber = number
199
  var prefix = id[0]
200
  do {
201
    number = nextChangedNode(prefix, increment, number)
202
    var elem = document.getElementById(prefix + number)
203
    if (up && elem) {
204
      while (elem.parentElement && changed(elem.parentElement))
205
        elem = elem.parentElement
206
      number = elem.id.substr(1)
207
    }
208
  } while ((down && id !== 'R0' && oldelem.contains(elem)))
209
  if (!number)
210
    number = lastnumber
211
  elem = document.getElementById(prefix + number)
212
  doHighlight(prefix + number)
213
}
214
window.onload = scrollToBoth
215
window.onkeydown = handleKey
216
</script>
217
<body>
218
<div onclick='highlight(event)'>
219
)";
220
221
4.30k
static void printHtml(raw_ostream &OS, char C) {
222
4.30k
  switch (C) {
223
0
  case '&':
224
0
    OS << "&amp;";
225
0
    break;
226
0
  case '<':
227
0
    OS << "&lt;";
228
0
    break;
229
0
  case '>':
230
0
    OS << "&gt;";
231
0
    break;
232
0
  case '\'':
233
0
    OS << "&#x27;";
234
0
    break;
235
14
  case '"':
236
14
    OS << "&quot;";
237
14
    break;
238
4.29k
  default:
239
4.29k
    OS << C;
240
4.30k
  }
241
4.30k
}
242
243
236
static void printHtml(raw_ostream &OS, const StringRef Str) {
244
236
  for (char C : Str)
245
2.18k
    printHtml(OS, C);
246
236
}
247
248
53
static std::string getChangeKindAbbr(diff::ChangeKind Kind) {
249
53
  switch (Kind) {
250
0
  case diff::None:
251
0
    return "";
252
11
  case diff::Delete:
253
11
    return "d";
254
18
  case diff::Update:
255
18
    return "u";
256
10
  case diff::Insert:
257
10
    return "i";
258
10
  case diff::Move:
259
10
    return "m";
260
4
  case diff::UpdateMove:
261
4
    return "u m";
262
0
  }
263
0
  llvm_unreachable("Invalid enumeration value.");
264
0
}
265
266
static unsigned printHtmlForNode(raw_ostream &OS, const diff::ASTDiff &Diff,
267
                                 diff::SyntaxTree &Tree, bool IsLeft,
268
139
                                 diff::NodeId Id, unsigned Offset) {
269
139
  const diff::Node &Node = Tree.getNode(Id);
270
139
  char MyTag, OtherTag;
271
139
  diff::NodeId LeftId, RightId;
272
139
  diff::NodeId TargetId = Diff.getMapped(Tree, Id);
273
139
  if (IsLeft) {
274
70
    MyTag = 'L';
275
70
    OtherTag = 'R';
276
70
    LeftId = Id;
277
70
    RightId = TargetId;
278
69
  } else {
279
69
    MyTag = 'R';
280
69
    OtherTag = 'L';
281
69
    LeftId = TargetId;
282
69
    RightId = Id;
283
69
  }
284
139
  unsigned Begin, End;
285
139
  std::tie(Begin, End) = Tree.getSourceRangeOffsets(Node);
286
139
  const SourceManager &SrcMgr = Tree.getASTContext().getSourceManager();
287
139
  auto Code = SrcMgr.getBuffer(SrcMgr.getMainFileID())->getBuffer();
288
1.95k
  for (; Offset < Begin; 
++Offset1.81k
)
289
1.81k
    printHtml(OS, Code[Offset]);
290
139
  OS << "<span id='" << MyTag << Id << "' "
291
139
     << "tid='" << OtherTag << TargetId << "' ";
292
139
  OS << "title='";
293
139
  printHtml(OS, Node.getTypeLabel());
294
139
  OS << "\n" << LeftId << " -> " << RightId;
295
139
  std::string Value = Tree.getNodeValue(Node);
296
139
  if (!Value.empty()) {
297
97
    OS << "\n";
298
97
    printHtml(OS, Value);
299
97
  }
300
139
  OS << "'";
301
139
  if (Node.Change != diff::None)
302
53
    OS << " class='" << getChangeKindAbbr(Node.Change) << "'";
303
139
  OS << ">";
304
139
305
139
  for (diff::NodeId Child : Node.Children)
306
137
    Offset = printHtmlForNode(OS, Diff, Tree, IsLeft, Child, Offset);
307
139
308
375
  for (; Offset < End; 
++Offset236
)
309
236
    printHtml(OS, Code[Offset]);
310
139
  if (Id == Tree.getRootId()) {
311
2
    End = Code.size();
312
76
    for (; Offset < End; 
++Offset74
)
313
74
      printHtml(OS, Code[Offset]);
314
2
  }
315
139
  OS << "</span>";
316
139
  return Offset;
317
139
}
318
319
11
static void printJsonString(raw_ostream &OS, const StringRef Str) {
320
143
  for (signed char C : Str) {
321
143
    switch (C) {
322
0
    case '"':
323
0
      OS << R"(\")";
324
0
      break;
325
0
    case '\\':
326
0
      OS << R"(\\)";
327
0
      break;
328
1
    case '\n':
329
1
      OS << R"(\n)";
330
1
      break;
331
1
    case '\t':
332
1
      OS << R"(\t)";
333
1
      break;
334
141
    default:
335
141
      if ('\x00' <= C && C <= '\x1f') {
336
2
        OS << R"(\u00)" << hexdigit(C >> 4) << hexdigit(C);
337
139
      } else {
338
139
        OS << C;
339
139
      }
340
143
    }
341
143
  }
342
11
}
343
344
static void printNodeAttributes(raw_ostream &OS, diff::SyntaxTree &Tree,
345
7
                                diff::NodeId Id) {
346
7
  const diff::Node &N = Tree.getNode(Id);
347
7
  OS << R"("id":)" << int(Id);
348
7
  OS << R"(,"type":")" << N.getTypeLabel() << '"';
349
7
  auto Offsets = Tree.getSourceRangeOffsets(N);
350
7
  OS << R"(,"begin":)" << Offsets.first;
351
7
  OS << R"(,"end":)" << Offsets.second;
352
7
  std::string Value = Tree.getNodeValue(N);
353
7
  if (!Value.empty()) {
354
5
    OS << R"(,"value":")";
355
5
    printJsonString(OS, Value);
356
5
    OS << '"';
357
5
  }
358
7
}
359
360
static void printNodeAsJson(raw_ostream &OS, diff::SyntaxTree &Tree,
361
7
                            diff::NodeId Id) {
362
7
  const diff::Node &N = Tree.getNode(Id);
363
7
  OS << "{";
364
7
  printNodeAttributes(OS, Tree, Id);
365
7
  auto Identifier = N.getIdentifier();
366
7
  auto QualifiedIdentifier = N.getQualifiedIdentifier();
367
7
  if (Identifier) {
368
4
    OS << R"(,"identifier":")";
369
4
    printJsonString(OS, *Identifier);
370
4
    OS << R"(")";
371
4
    if (QualifiedIdentifier && *Identifier != *QualifiedIdentifier) {
372
1
      OS << R"(,"qualified_identifier":")";
373
1
      printJsonString(OS, *QualifiedIdentifier);
374
1
      OS << R"(")";
375
1
    }
376
4
  }
377
7
  OS << R"(,"children":[)";
378
7
  if (N.Children.size() > 0) {
379
4
    printNodeAsJson(OS, Tree, N.Children[0]);
380
6
    for (size_t I = 1, E = N.Children.size(); I < E; 
++I2
) {
381
2
      OS << ",";
382
2
      printNodeAsJson(OS, Tree, N.Children[I]);
383
2
    }
384
4
  }
385
7
  OS << "]}";
386
7
}
387
388
static void printNode(raw_ostream &OS, diff::SyntaxTree &Tree,
389
443
                      diff::NodeId Id) {
390
443
  if (Id.isInvalid()) {
391
1
    OS << "None";
392
1
    return;
393
1
  }
394
442
  OS << Tree.getNode(Id).getTypeLabel();
395
442
  std::string Value = Tree.getNodeValue(Id);
396
442
  if (!Value.empty())
397
227
    OS << ": " << Value;
398
442
  OS << "(" << Id << ")";
399
442
}
400
401
4
static void printTree(raw_ostream &OS, diff::SyntaxTree &Tree) {
402
65
  for (diff::NodeId Id : Tree) {
403
285
    for (int I = 0; I < Tree.getNode(Id).Depth; 
++I220
)
404
220
      OS << " ";
405
65
    printNode(OS, Tree, Id);
406
65
    OS << "\n";
407
65
  }
408
4
}
409
410
static void printDstChange(raw_ostream &OS, diff::ASTDiff &Diff,
411
                           diff::SyntaxTree &SrcTree, diff::SyntaxTree &DstTree,
412
216
                           diff::NodeId Dst) {
413
216
  const diff::Node &DstNode = DstTree.getNode(Dst);
414
216
  diff::NodeId Src = Diff.getMapped(DstTree, Dst);
415
216
  switch (DstNode.Change) {
416
157
  case diff::None:
417
157
    break;
418
0
  case diff::Delete:
419
0
    llvm_unreachable("The destination tree can't have deletions.");
420
9
  case diff::Update:
421
9
    OS << "Update ";
422
9
    printNode(OS, SrcTree, Src);
423
9
    OS << " to " << DstTree.getNodeValue(Dst) << "\n";
424
9
    break;
425
50
  case diff::Insert:
426
50
  case diff::Move:
427
50
  case diff::UpdateMove:
428
50
    if (DstNode.Change == diff::Insert)
429
34
      OS << "Insert";
430
16
    else if (DstNode.Change == diff::Move)
431
14
      OS << "Move";
432
2
    else if (DstNode.Change == diff::UpdateMove)
433
2
      OS << "Update and Move";
434
50
    OS << " ";
435
50
    printNode(OS, DstTree, Dst);
436
50
    OS << " into ";
437
50
    printNode(OS, DstTree, DstNode.Parent);
438
50
    OS << " at " << DstTree.findPositionInParent(Dst) << "\n";
439
50
    break;
440
216
  }
441
216
}
442
443
11
int main(int argc, const char **argv) {
444
11
  std::string ErrorMessage;
445
11
  std::unique_ptr<CompilationDatabase> CommonCompilations =
446
11
      FixedCompilationDatabase::loadFromCommandLine(argc, argv, ErrorMessage);
447
11
  if (!CommonCompilations && 
!ErrorMessage.empty()0
)
448
0
    llvm::errs() << ErrorMessage;
449
11
  cl::HideUnrelatedOptions(ClangDiffCategory);
450
11
  if (!cl::ParseCommandLineOptions(argc, argv)) {
451
0
    cl::PrintOptionValues();
452
0
    return 1;
453
0
  }
454
11
455
11
  addExtraArgs(CommonCompilations);
456
11
457
11
  if (ASTDump || 
ASTDumpJson7
) {
458
5
    if (!DestinationPath.empty()) {
459
0
      llvm::errs() << "Error: Please specify exactly one filename.\n";
460
0
      return 1;
461
0
    }
462
5
    std::unique_ptr<ASTUnit> AST = getAST(CommonCompilations, SourcePath);
463
5
    if (!AST)
464
0
      return 1;
465
5
    diff::SyntaxTree Tree(AST->getASTContext());
466
5
    if (ASTDump) {
467
4
      printTree(llvm::outs(), Tree);
468
4
      return 0;
469
4
    }
470
1
    llvm::outs() << R"({"filename":")";
471
1
    printJsonString(llvm::outs(), SourcePath);
472
1
    llvm::outs() << R"(","root":)";
473
1
    printNodeAsJson(llvm::outs(), Tree, Tree.getRootId());
474
1
    llvm::outs() << "}\n";
475
1
    return 0;
476
1
  }
477
6
478
6
  if (DestinationPath.empty()) {
479
0
    llvm::errs() << "Error: Exactly two paths are required.\n";
480
0
    return 1;
481
0
  }
482
6
483
6
  std::unique_ptr<ASTUnit> Src = getAST(CommonCompilations, SourcePath);
484
6
  std::unique_ptr<ASTUnit> Dst = getAST(CommonCompilations, DestinationPath);
485
6
  if (!Src || !Dst)
486
0
    return 1;
487
6
488
6
  diff::ComparisonOptions Options;
489
6
  if (MaxSize != -1)
490
2
    Options.MaxSize = MaxSize;
491
6
  if (!StopAfter.empty()) {
492
1
    if (StopAfter == "topdown")
493
1
      Options.StopAfterTopDown = true;
494
0
    else if (StopAfter != "bottomup") {
495
0
      llvm::errs() << "Error: Invalid argument for -stop-after\n";
496
0
      return 1;
497
0
    }
498
6
  }
499
6
  diff::SyntaxTree SrcTree(Src->getASTContext());
500
6
  diff::SyntaxTree DstTree(Dst->getASTContext());
501
6
  diff::ASTDiff Diff(SrcTree, DstTree, Options);
502
6
503
6
  if (HtmlDiff) {
504
1
    llvm::outs() << HtmlDiffHeader << "<pre>";
505
1
    llvm::outs() << "<div id='L' class='code'>";
506
1
    printHtmlForNode(llvm::outs(), Diff, SrcTree, true, SrcTree.getRootId(), 0);
507
1
    llvm::outs() << "</div>";
508
1
    llvm::outs() << "<div id='R' class='code'>";
509
1
    printHtmlForNode(llvm::outs(), Diff, DstTree, false, DstTree.getRootId(),
510
1
                     0);
511
1
    llvm::outs() << "</div>";
512
1
    llvm::outs() << "</pre></div></body></html>\n";
513
1
    return 0;
514
1
  }
515
5
516
216
  
for (diff::NodeId Dst : DstTree)5
{
517
216
    diff::NodeId Src = Diff.getMapped(DstTree, Dst);
518
216
    if (PrintMatches && 
Src.isValid()154
) {
519
120
      llvm::outs() << "Match ";
520
120
      printNode(llvm::outs(), SrcTree, Src);
521
120
      llvm::outs() << " to ";
522
120
      printNode(llvm::outs(), DstTree, Dst);
523
120
      llvm::outs() << "\n";
524
120
    }
525
216
    printDstChange(llvm::outs(), Diff, SrcTree, DstTree, Dst);
526
216
  }
527
211
  for (diff::NodeId Src : SrcTree) {
528
211
    if (Diff.getMapped(SrcTree, Src).isInvalid()) {
529
29
      llvm::outs() << "Delete ";
530
29
      printNode(llvm::outs(), SrcTree, Src);
531
29
      llvm::outs() << "\n";
532
29
    }
533
211
  }
534
5
535
5
  return 0;
536
5
}