1 //===--- HTMLDiagnostics.cpp - HTML Diagnostics for Paths ----*- C++ -*-===// 2 // 3 // The LLVM Compiler Infrastructure 4 // 5 // This file is distributed under the University of Illinois Open Source 6 // License. See LICENSE.TXT for details. 7 // 8 //===----------------------------------------------------------------------===// 9 // 10 // This file defines the HTMLDiagnostics object. 11 // 12 //===----------------------------------------------------------------------===// 13 14 #include "clang/AST/ASTContext.h" 15 #include "clang/AST/Decl.h" 16 #include "clang/Basic/FileManager.h" 17 #include "clang/Basic/SourceManager.h" 18 #include "clang/Lex/Lexer.h" 19 #include "clang/Lex/Preprocessor.h" 20 #include "clang/Rewrite/Core/HTMLRewrite.h" 21 #include "clang/Rewrite/Core/Rewriter.h" 22 #include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h" 23 #include "clang/StaticAnalyzer/Core/CheckerManager.h" 24 #include "clang/StaticAnalyzer/Core/IssueHash.h" 25 #include "clang/StaticAnalyzer/Core/PathDiagnosticConsumers.h" 26 #include "llvm/Support/Errc.h" 27 #include "llvm/Support/FileSystem.h" 28 #include "llvm/Support/MemoryBuffer.h" 29 #include "llvm/Support/Path.h" 30 #include "llvm/Support/raw_ostream.h" 31 #include <sstream> 32 33 using namespace clang; 34 using namespace ento; 35 36 //===----------------------------------------------------------------------===// 37 // Boilerplate. 38 //===----------------------------------------------------------------------===// 39 40 namespace { 41 42 class HTMLDiagnostics : public PathDiagnosticConsumer { 43 std::string Directory; 44 bool createdDir, noDir; 45 const Preprocessor &PP; 46 AnalyzerOptions &AnalyzerOpts; 47 public: 48 HTMLDiagnostics(AnalyzerOptions &AnalyzerOpts, const std::string& prefix, const Preprocessor &pp); 49 50 ~HTMLDiagnostics() override { FlushDiagnostics(nullptr); } 51 52 void FlushDiagnosticsImpl(std::vector<const PathDiagnostic *> &Diags, 53 FilesMade *filesMade) override; 54 55 StringRef getName() const override { 56 return "HTMLDiagnostics"; 57 } 58 59 unsigned ProcessMacroPiece(raw_ostream &os, 60 const PathDiagnosticMacroPiece& P, 61 unsigned num); 62 63 void HandlePiece(Rewriter& R, FileID BugFileID, 64 const PathDiagnosticPiece& P, unsigned num, unsigned max); 65 66 void HighlightRange(Rewriter& R, FileID BugFileID, SourceRange Range, 67 const char *HighlightStart = "<span class=\"mrange\">", 68 const char *HighlightEnd = "</span>"); 69 70 void ReportDiag(const PathDiagnostic& D, 71 FilesMade *filesMade); 72 }; 73 74 } // end anonymous namespace 75 76 HTMLDiagnostics::HTMLDiagnostics(AnalyzerOptions &AnalyzerOpts, 77 const std::string& prefix, 78 const Preprocessor &pp) 79 : Directory(prefix), createdDir(false), noDir(false), PP(pp), AnalyzerOpts(AnalyzerOpts) { 80 } 81 82 void ento::createHTMLDiagnosticConsumer(AnalyzerOptions &AnalyzerOpts, 83 PathDiagnosticConsumers &C, 84 const std::string& prefix, 85 const Preprocessor &PP) { 86 C.push_back(new HTMLDiagnostics(AnalyzerOpts, prefix, PP)); 87 } 88 89 //===----------------------------------------------------------------------===// 90 // Report processing. 91 //===----------------------------------------------------------------------===// 92 93 void HTMLDiagnostics::FlushDiagnosticsImpl( 94 std::vector<const PathDiagnostic *> &Diags, 95 FilesMade *filesMade) { 96 for (std::vector<const PathDiagnostic *>::iterator it = Diags.begin(), 97 et = Diags.end(); it != et; ++it) { 98 ReportDiag(**it, filesMade); 99 } 100 } 101 102 void HTMLDiagnostics::ReportDiag(const PathDiagnostic& D, 103 FilesMade *filesMade) { 104 105 // Create the HTML directory if it is missing. 106 if (!createdDir) { 107 createdDir = true; 108 if (std::error_code ec = llvm::sys::fs::create_directories(Directory)) { 109 llvm::errs() << "warning: could not create directory '" 110 << Directory << "': " << ec.message() << '\n'; 111 112 noDir = true; 113 114 return; 115 } 116 } 117 118 if (noDir) 119 return; 120 121 // First flatten out the entire path to make it easier to use. 122 PathPieces path = D.path.flatten(/*ShouldFlattenMacros=*/false); 123 124 // The path as already been prechecked that all parts of the path are 125 // from the same file and that it is non-empty. 126 const SourceManager &SMgr = path.front()->getLocation().getManager(); 127 assert(!path.empty()); 128 FileID FID = 129 path.front()->getLocation().asLocation().getExpansionLoc().getFileID(); 130 assert(FID.isValid()); 131 132 // Create a new rewriter to generate HTML. 133 Rewriter R(const_cast<SourceManager&>(SMgr), PP.getLangOpts()); 134 135 // Get the function/method name 136 SmallString<128> declName("unknown"); 137 int offsetDecl = 0; 138 if (const Decl *DeclWithIssue = D.getDeclWithIssue()) { 139 if (const NamedDecl *ND = dyn_cast<NamedDecl>(DeclWithIssue)) { 140 declName = ND->getDeclName().getAsString(); 141 } 142 143 if (const Stmt *Body = DeclWithIssue->getBody()) { 144 // Retrieve the relative position of the declaration which will be used 145 // for the file name 146 FullSourceLoc L( 147 SMgr.getExpansionLoc(path.back()->getLocation().asLocation()), 148 SMgr); 149 FullSourceLoc FunL(SMgr.getExpansionLoc(Body->getLocStart()), SMgr); 150 offsetDecl = L.getExpansionLineNumber() - FunL.getExpansionLineNumber(); 151 } 152 } 153 154 // Process the path. 155 // Maintain the counts of extra note pieces separately. 156 unsigned TotalPieces = path.size(); 157 unsigned TotalNotePieces = 158 std::count_if(path.begin(), path.end(), 159 [](const IntrusiveRefCntPtr<PathDiagnosticPiece> &p) { 160 return isa<PathDiagnosticNotePiece>(p.get()); 161 }); 162 163 unsigned TotalRegularPieces = TotalPieces - TotalNotePieces; 164 unsigned NumRegularPieces = TotalRegularPieces; 165 unsigned NumNotePieces = TotalNotePieces; 166 167 for (auto I = path.rbegin(), E = path.rend(); I != E; ++I) { 168 if (isa<PathDiagnosticNotePiece>(I->get())) { 169 // This adds diagnostic bubbles, but not navigation. 170 // Navigation through note pieces would be added later, 171 // as a separate pass through the piece list. 172 HandlePiece(R, FID, **I, NumNotePieces, TotalNotePieces); 173 --NumNotePieces; 174 } else { 175 HandlePiece(R, FID, **I, NumRegularPieces, TotalRegularPieces); 176 --NumRegularPieces; 177 } 178 } 179 180 // Add line numbers, header, footer, etc. 181 182 // unsigned FID = R.getSourceMgr().getMainFileID(); 183 html::EscapeText(R, FID); 184 html::AddLineNumbers(R, FID); 185 186 // If we have a preprocessor, relex the file and syntax highlight. 187 // We might not have a preprocessor if we come from a deserialized AST file, 188 // for example. 189 190 html::SyntaxHighlight(R, FID, PP); 191 html::HighlightMacros(R, FID, PP); 192 193 // Get the full directory name of the analyzed file. 194 195 const FileEntry* Entry = SMgr.getFileEntryForID(FID); 196 197 // This is a cludge; basically we want to append either the full 198 // working directory if we have no directory information. This is 199 // a work in progress. 200 201 llvm::SmallString<0> DirName; 202 203 if (llvm::sys::path::is_relative(Entry->getName())) { 204 llvm::sys::fs::current_path(DirName); 205 DirName += '/'; 206 } 207 208 int LineNumber = path.back()->getLocation().asLocation().getExpansionLineNumber(); 209 int ColumnNumber = path.back()->getLocation().asLocation().getExpansionColumnNumber(); 210 211 // Add the name of the file as an <h1> tag. 212 { 213 std::string s; 214 llvm::raw_string_ostream os(s); 215 216 os << "<!-- REPORTHEADER -->\n" 217 << "<h3>Bug Summary</h3>\n<table class=\"simpletable\">\n" 218 "<tr><td class=\"rowname\">File:</td><td>" 219 << html::EscapeText(DirName) 220 << html::EscapeText(Entry->getName()) 221 << "</td></tr>\n<tr><td class=\"rowname\">Warning:</td><td>" 222 "<a href=\"#EndPath\">line " 223 << LineNumber 224 << ", column " 225 << ColumnNumber 226 << "</a><br />" 227 << D.getVerboseDescription() << "</td></tr>\n"; 228 229 // The navigation across the extra notes pieces. 230 unsigned NumExtraPieces = 0; 231 for (const auto &Piece : path) { 232 if (const auto *P = dyn_cast<PathDiagnosticNotePiece>(Piece.get())) { 233 int LineNumber = 234 P->getLocation().asLocation().getExpansionLineNumber(); 235 int ColumnNumber = 236 P->getLocation().asLocation().getExpansionColumnNumber(); 237 os << "<tr><td class=\"rowname\">Note:</td><td>" 238 << "<a href=\"#Note" << NumExtraPieces << "\">line " 239 << LineNumber << ", column " << ColumnNumber << "</a><br />" 240 << P->getString() << "</td></tr>"; 241 ++NumExtraPieces; 242 } 243 } 244 245 // Output any other meta data. 246 247 for (PathDiagnostic::meta_iterator I=D.meta_begin(), E=D.meta_end(); 248 I!=E; ++I) { 249 os << "<tr><td></td><td>" << html::EscapeText(*I) << "</td></tr>\n"; 250 } 251 252 os << "</table>\n<!-- REPORTSUMMARYEXTRA -->\n" 253 "<h3>Annotated Source Code</h3>\n"; 254 255 R.InsertTextBefore(SMgr.getLocForStartOfFile(FID), os.str()); 256 } 257 258 // Embed meta-data tags. 259 { 260 std::string s; 261 llvm::raw_string_ostream os(s); 262 263 StringRef BugDesc = D.getVerboseDescription(); 264 if (!BugDesc.empty()) 265 os << "\n<!-- BUGDESC " << BugDesc << " -->\n"; 266 267 StringRef BugType = D.getBugType(); 268 if (!BugType.empty()) 269 os << "\n<!-- BUGTYPE " << BugType << " -->\n"; 270 271 PathDiagnosticLocation UPDLoc = D.getUniqueingLoc(); 272 FullSourceLoc L(SMgr.getExpansionLoc(UPDLoc.isValid() 273 ? UPDLoc.asLocation() 274 : D.getLocation().asLocation()), 275 SMgr); 276 const Decl *DeclWithIssue = D.getDeclWithIssue(); 277 278 StringRef BugCategory = D.getCategory(); 279 if (!BugCategory.empty()) 280 os << "\n<!-- BUGCATEGORY " << BugCategory << " -->\n"; 281 282 os << "\n<!-- BUGFILE " << DirName << Entry->getName() << " -->\n"; 283 284 os << "\n<!-- FILENAME " << llvm::sys::path::filename(Entry->getName()) << " -->\n"; 285 286 os << "\n<!-- FUNCTIONNAME " << declName << " -->\n"; 287 288 os << "\n<!-- ISSUEHASHCONTENTOFLINEINCONTEXT " 289 << GetIssueHash(SMgr, L, D.getCheckName(), D.getBugType(), DeclWithIssue, 290 PP.getLangOpts()) << " -->\n"; 291 292 os << "\n<!-- BUGLINE " 293 << LineNumber 294 << " -->\n"; 295 296 os << "\n<!-- BUGCOLUMN " 297 << ColumnNumber 298 << " -->\n"; 299 300 os << "\n<!-- BUGPATHLENGTH " << path.size() << " -->\n"; 301 302 // Mark the end of the tags. 303 os << "\n<!-- BUGMETAEND -->\n"; 304 305 // Insert the text. 306 R.InsertTextBefore(SMgr.getLocForStartOfFile(FID), os.str()); 307 } 308 309 // Add CSS, header, and footer. 310 311 html::AddHeaderFooterInternalBuiltinCSS(R, FID, Entry->getName()); 312 313 // Get the rewrite buffer. 314 const RewriteBuffer *Buf = R.getRewriteBufferFor(FID); 315 316 if (!Buf) { 317 llvm::errs() << "warning: no diagnostics generated for main file.\n"; 318 return; 319 } 320 321 // Create a path for the target HTML file. 322 int FD; 323 SmallString<128> Model, ResultPath; 324 325 if (!AnalyzerOpts.shouldWriteStableReportFilename()) { 326 llvm::sys::path::append(Model, Directory, "report-%%%%%%.html"); 327 if (std::error_code EC = 328 llvm::sys::fs::make_absolute(Model)) { 329 llvm::errs() << "warning: could not make '" << Model 330 << "' absolute: " << EC.message() << '\n'; 331 return; 332 } 333 if (std::error_code EC = 334 llvm::sys::fs::createUniqueFile(Model, FD, ResultPath)) { 335 llvm::errs() << "warning: could not create file in '" << Directory 336 << "': " << EC.message() << '\n'; 337 return; 338 } 339 340 } else { 341 int i = 1; 342 std::error_code EC; 343 do { 344 // Find a filename which is not already used 345 std::stringstream filename; 346 Model = ""; 347 filename << "report-" 348 << llvm::sys::path::filename(Entry->getName()).str() 349 << "-" << declName.c_str() 350 << "-" << offsetDecl 351 << "-" << i << ".html"; 352 llvm::sys::path::append(Model, Directory, 353 filename.str()); 354 EC = llvm::sys::fs::openFileForWrite(Model, 355 FD, 356 llvm::sys::fs::F_RW | 357 llvm::sys::fs::F_Excl); 358 if (EC && EC != llvm::errc::file_exists) { 359 llvm::errs() << "warning: could not create file '" << Model 360 << "': " << EC.message() << '\n'; 361 return; 362 } 363 i++; 364 } while (EC); 365 } 366 367 llvm::raw_fd_ostream os(FD, true); 368 369 if (filesMade) 370 filesMade->addDiagnostic(D, getName(), 371 llvm::sys::path::filename(ResultPath)); 372 373 // Emit the HTML to disk. 374 for (RewriteBuffer::iterator I = Buf->begin(), E = Buf->end(); I!=E; ++I) 375 os << *I; 376 } 377 378 void HTMLDiagnostics::HandlePiece(Rewriter& R, FileID BugFileID, 379 const PathDiagnosticPiece& P, 380 unsigned num, unsigned max) { 381 382 // For now, just draw a box above the line in question, and emit the 383 // warning. 384 FullSourceLoc Pos = P.getLocation().asLocation(); 385 386 if (!Pos.isValid()) 387 return; 388 389 SourceManager &SM = R.getSourceMgr(); 390 assert(&Pos.getManager() == &SM && "SourceManagers are different!"); 391 std::pair<FileID, unsigned> LPosInfo = SM.getDecomposedExpansionLoc(Pos); 392 393 if (LPosInfo.first != BugFileID) 394 return; 395 396 const llvm::MemoryBuffer *Buf = SM.getBuffer(LPosInfo.first); 397 const char* FileStart = Buf->getBufferStart(); 398 399 // Compute the column number. Rewind from the current position to the start 400 // of the line. 401 unsigned ColNo = SM.getColumnNumber(LPosInfo.first, LPosInfo.second); 402 const char *TokInstantiationPtr =Pos.getExpansionLoc().getCharacterData(); 403 const char *LineStart = TokInstantiationPtr-ColNo; 404 405 // Compute LineEnd. 406 const char *LineEnd = TokInstantiationPtr; 407 const char* FileEnd = Buf->getBufferEnd(); 408 while (*LineEnd != '\n' && LineEnd != FileEnd) 409 ++LineEnd; 410 411 // Compute the margin offset by counting tabs and non-tabs. 412 unsigned PosNo = 0; 413 for (const char* c = LineStart; c != TokInstantiationPtr; ++c) 414 PosNo += *c == '\t' ? 8 : 1; 415 416 // Create the html for the message. 417 418 const char *Kind = nullptr; 419 bool IsNote = false; 420 bool SuppressIndex = (max == 1); 421 switch (P.getKind()) { 422 case PathDiagnosticPiece::Call: 423 llvm_unreachable("Calls and extra notes should already be handled"); 424 case PathDiagnosticPiece::Event: Kind = "Event"; break; 425 case PathDiagnosticPiece::ControlFlow: Kind = "Control"; break; 426 // Setting Kind to "Control" is intentional. 427 case PathDiagnosticPiece::Macro: Kind = "Control"; break; 428 case PathDiagnosticPiece::Note: 429 Kind = "Note"; 430 IsNote = true; 431 SuppressIndex = true; 432 break; 433 } 434 435 std::string sbuf; 436 llvm::raw_string_ostream os(sbuf); 437 438 os << "\n<tr><td class=\"num\"></td><td class=\"line\"><div id=\""; 439 440 if (IsNote) 441 os << "Note" << num; 442 else if (num == max) 443 os << "EndPath"; 444 else 445 os << "Path" << num; 446 447 os << "\" class=\"msg"; 448 if (Kind) 449 os << " msg" << Kind; 450 os << "\" style=\"margin-left:" << PosNo << "ex"; 451 452 // Output a maximum size. 453 if (!isa<PathDiagnosticMacroPiece>(P)) { 454 // Get the string and determining its maximum substring. 455 const auto &Msg = P.getString(); 456 unsigned max_token = 0; 457 unsigned cnt = 0; 458 unsigned len = Msg.size(); 459 460 for (char C : Msg) 461 switch (C) { 462 default: 463 ++cnt; 464 continue; 465 case ' ': 466 case '\t': 467 case '\n': 468 if (cnt > max_token) max_token = cnt; 469 cnt = 0; 470 } 471 472 if (cnt > max_token) 473 max_token = cnt; 474 475 // Determine the approximate size of the message bubble in em. 476 unsigned em; 477 const unsigned max_line = 120; 478 479 if (max_token >= max_line) 480 em = max_token / 2; 481 else { 482 unsigned characters = max_line; 483 unsigned lines = len / max_line; 484 485 if (lines > 0) { 486 for (; characters > max_token; --characters) 487 if (len / characters > lines) { 488 ++characters; 489 break; 490 } 491 } 492 493 em = characters / 2; 494 } 495 496 if (em < max_line/2) 497 os << "; max-width:" << em << "em"; 498 } 499 else 500 os << "; max-width:100em"; 501 502 os << "\">"; 503 504 if (!SuppressIndex) { 505 os << "<table class=\"msgT\"><tr><td valign=\"top\">"; 506 os << "<div class=\"PathIndex"; 507 if (Kind) os << " PathIndex" << Kind; 508 os << "\">" << num << "</div>"; 509 510 if (num > 1) { 511 os << "</td><td><div class=\"PathNav\"><a href=\"#Path" 512 << (num - 1) 513 << "\" title=\"Previous event (" 514 << (num - 1) 515 << ")\">←</a></div></td>"; 516 } 517 518 os << "</td><td>"; 519 } 520 521 if (const PathDiagnosticMacroPiece *MP = 522 dyn_cast<PathDiagnosticMacroPiece>(&P)) { 523 524 os << "Within the expansion of the macro '"; 525 526 // Get the name of the macro by relexing it. 527 { 528 FullSourceLoc L = MP->getLocation().asLocation().getExpansionLoc(); 529 assert(L.isFileID()); 530 StringRef BufferInfo = L.getBufferData(); 531 std::pair<FileID, unsigned> LocInfo = L.getDecomposedLoc(); 532 const char* MacroName = LocInfo.second + BufferInfo.data(); 533 Lexer rawLexer(SM.getLocForStartOfFile(LocInfo.first), PP.getLangOpts(), 534 BufferInfo.begin(), MacroName, BufferInfo.end()); 535 536 Token TheTok; 537 rawLexer.LexFromRawLexer(TheTok); 538 for (unsigned i = 0, n = TheTok.getLength(); i < n; ++i) 539 os << MacroName[i]; 540 } 541 542 os << "':\n"; 543 544 if (!SuppressIndex) { 545 os << "</td>"; 546 if (num < max) { 547 os << "<td><div class=\"PathNav\"><a href=\"#"; 548 if (num == max - 1) 549 os << "EndPath"; 550 else 551 os << "Path" << (num + 1); 552 os << "\" title=\"Next event (" 553 << (num + 1) 554 << ")\">→</a></div></td>"; 555 } 556 557 os << "</tr></table>"; 558 } 559 560 // Within a macro piece. Write out each event. 561 ProcessMacroPiece(os, *MP, 0); 562 } 563 else { 564 os << html::EscapeText(P.getString()); 565 566 if (!SuppressIndex) { 567 os << "</td>"; 568 if (num < max) { 569 os << "<td><div class=\"PathNav\"><a href=\"#"; 570 if (num == max - 1) 571 os << "EndPath"; 572 else 573 os << "Path" << (num + 1); 574 os << "\" title=\"Next event (" 575 << (num + 1) 576 << ")\">→</a></div></td>"; 577 } 578 579 os << "</tr></table>"; 580 } 581 } 582 583 os << "</div></td></tr>"; 584 585 // Insert the new html. 586 unsigned DisplayPos = LineEnd - FileStart; 587 SourceLocation Loc = 588 SM.getLocForStartOfFile(LPosInfo.first).getLocWithOffset(DisplayPos); 589 590 R.InsertTextBefore(Loc, os.str()); 591 592 // Now highlight the ranges. 593 ArrayRef<SourceRange> Ranges = P.getRanges(); 594 for (ArrayRef<SourceRange>::iterator I = Ranges.begin(), 595 E = Ranges.end(); I != E; ++I) { 596 HighlightRange(R, LPosInfo.first, *I); 597 } 598 } 599 600 static void EmitAlphaCounter(raw_ostream &os, unsigned n) { 601 unsigned x = n % ('z' - 'a'); 602 n /= 'z' - 'a'; 603 604 if (n > 0) 605 EmitAlphaCounter(os, n); 606 607 os << char('a' + x); 608 } 609 610 unsigned HTMLDiagnostics::ProcessMacroPiece(raw_ostream &os, 611 const PathDiagnosticMacroPiece& P, 612 unsigned num) { 613 614 for (PathPieces::const_iterator I = P.subPieces.begin(), E=P.subPieces.end(); 615 I!=E; ++I) { 616 617 if (const PathDiagnosticMacroPiece *MP = 618 dyn_cast<PathDiagnosticMacroPiece>(*I)) { 619 num = ProcessMacroPiece(os, *MP, num); 620 continue; 621 } 622 623 if (PathDiagnosticEventPiece *EP = dyn_cast<PathDiagnosticEventPiece>(*I)) { 624 os << "<div class=\"msg msgEvent\" style=\"width:94%; " 625 "margin-left:5px\">" 626 "<table class=\"msgT\"><tr>" 627 "<td valign=\"top\"><div class=\"PathIndex PathIndexEvent\">"; 628 EmitAlphaCounter(os, num++); 629 os << "</div></td><td valign=\"top\">" 630 << html::EscapeText(EP->getString()) 631 << "</td></tr></table></div>\n"; 632 } 633 } 634 635 return num; 636 } 637 638 void HTMLDiagnostics::HighlightRange(Rewriter& R, FileID BugFileID, 639 SourceRange Range, 640 const char *HighlightStart, 641 const char *HighlightEnd) { 642 SourceManager &SM = R.getSourceMgr(); 643 const LangOptions &LangOpts = R.getLangOpts(); 644 645 SourceLocation InstantiationStart = SM.getExpansionLoc(Range.getBegin()); 646 unsigned StartLineNo = SM.getExpansionLineNumber(InstantiationStart); 647 648 SourceLocation InstantiationEnd = SM.getExpansionLoc(Range.getEnd()); 649 unsigned EndLineNo = SM.getExpansionLineNumber(InstantiationEnd); 650 651 if (EndLineNo < StartLineNo) 652 return; 653 654 if (SM.getFileID(InstantiationStart) != BugFileID || 655 SM.getFileID(InstantiationEnd) != BugFileID) 656 return; 657 658 // Compute the column number of the end. 659 unsigned EndColNo = SM.getExpansionColumnNumber(InstantiationEnd); 660 unsigned OldEndColNo = EndColNo; 661 662 if (EndColNo) { 663 // Add in the length of the token, so that we cover multi-char tokens. 664 EndColNo += Lexer::MeasureTokenLength(Range.getEnd(), SM, LangOpts)-1; 665 } 666 667 // Highlight the range. Make the span tag the outermost tag for the 668 // selected range. 669 670 SourceLocation E = 671 InstantiationEnd.getLocWithOffset(EndColNo - OldEndColNo); 672 673 html::HighlightRange(R, InstantiationStart, E, HighlightStart, HighlightEnd); 674 } 675