Author: Balázs Benics
Date: 2026-08-11T11:21:37+01:00
New Revision: 341abe2e90f1aea923afce842d3bdd962c136fe4

URL: 
https://github.com/llvm/llvm-project/commit/341abe2e90f1aea923afce842d3bdd962c136fe4
DIFF: 
https://github.com/llvm/llvm-project/commit/341abe2e90f1aea923afce842d3bdd962c136fe4.diff

LOG: [analyzer] Fix -analyzer-output=html assert on reversed and macro ranges 
(#214463)

HTMLDiagnostics::HighlightRange guarded against a reversed range by
comparing line numbers, so a same-line reversal - which is what the
piece for an implicit copy constructor carries - reached html::HighlightRange.
Its scan walks from begin to end, ran off the end of the buffer, and asserted:
https://godbolt.org/z/sTb5qfjjd

  Invalid position to insert! (RewriteRope.h)

It also added the end token's length itself and then passed a token range to
html::HighlightRange, which measured the token again, this time from the
interior. For most tokens the two cancel, but where the tail re-lexes longer
the highlight reached past the end of the range, e.g. over a trailing ';'.

Use getExpansionRangeInFile(), which rejects reversed and cross-file ranges,
then convert once and tell html::HighlightRange the range is already 
char-granular.

A range ending inside a macro expansion now covers the whole macro use, so
the highlight nests around the 'macro' element rather than ending inside it.

Assisted-By: claude

Added: 
    clang/test/Analysis/html_diagnostics/highlight-range-mapping.cpp

Modified: 
    clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp

Removed: 
    


################################################################################
diff  --git a/clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp 
b/clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp
index 04829bffeecd9..93f65ec497429 100644
--- a/clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp
+++ b/clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp
@@ -22,6 +22,7 @@
 #include "clang/Basic/LLVM.h"
 #include "clang/Basic/SourceLocation.h"
 #include "clang/Basic/SourceManager.h"
+#include "clang/Frontend/DiagnosticRenderer.h"
 #include "clang/Lex/Lexer.h"
 #include "clang/Lex/Preprocessor.h"
 #include "clang/Lex/Token.h"
@@ -1257,35 +1258,15 @@ void HTMLDiagnostics::HighlightRange(Rewriter& R, 
FileID BugFileID,
   SourceManager &SM = R.getSourceMgr();
   const LangOptions &LangOpts = R.getLangOpts();
 
-  SourceLocation InstantiationStart = SM.getExpansionLoc(Range.getBegin());
-  unsigned StartLineNo = SM.getExpansionLineNumber(InstantiationStart);
-
-  SourceLocation InstantiationEnd = SM.getExpansionLoc(Range.getEnd());
-  unsigned EndLineNo = SM.getExpansionLineNumber(InstantiationEnd);
-
-  if (EndLineNo < StartLineNo)
+  std::optional<CharSourceRange> FileRange = getExpansionRangeInFile(
+      CharSourceRange::getTokenRange(Range), BugFileID, SM);
+  if (!FileRange)
     return;
 
-  if (SM.getFileID(InstantiationStart) != BugFileID ||
-      SM.getFileID(InstantiationEnd) != BugFileID)
-    return;
-
-  // Compute the column number of the end.
-  unsigned EndColNo = SM.getExpansionColumnNumber(InstantiationEnd);
-  unsigned OldEndColNo = EndColNo;
-
-  if (EndColNo) {
-    // Add in the length of the token, so that we cover multi-char tokens.
-    EndColNo += Lexer::MeasureTokenLength(Range.getEnd(), SM, LangOpts)-1;
-  }
-
-  // Highlight the range.  Make the span tag the outermost tag for the
-  // selected range.
-
-  SourceLocation E =
-    InstantiationEnd.getLocWithOffset(EndColNo - OldEndColNo);
-
-  html::HighlightRange(R, InstantiationStart, E, HighlightStart, HighlightEnd);
+  CharSourceRange CharRange = Lexer::getAsCharRange(*FileRange, SM, LangOpts);
+  html::HighlightRange(R, CharRange.getBegin(), CharRange.getEnd(),
+                       HighlightStart, HighlightEnd,
+                       /*IsTokenRange=*/false);
 }
 
 StringRef HTMLDiagnostics::generateKeyboardNavigationJavascript() {

diff  --git a/clang/test/Analysis/html_diagnostics/highlight-range-mapping.cpp 
b/clang/test/Analysis/html_diagnostics/highlight-range-mapping.cpp
new file mode 100644
index 0000000000000..a03f264b81835
--- /dev/null
+++ b/clang/test/Analysis/html_diagnostics/highlight-range-mapping.cpp
@@ -0,0 +1,43 @@
+// RUN: rm -rf %t
+// RUN: mkdir %t
+// RUN: %clang_analyze_cc1 -analyzer-checker=core,deadcode.DeadStores \
+// RUN:   -analyzer-output=html -o %t -std=c++17 -verify %s
+// RUN: cat %t/report-*.html | FileCheck %s
+
+// CHECK-DAG is required: one report file is emitted per diagnostic, and their
+// names are content hashes, so the glob order is not the source order.
+
+// expected-warning@+1 {{Value assigned to field 'i' in implicit constructor 
is uninitialized}}
+struct S {
+  int i;
+};
+
+// The piece for the implicit copy constructor carries a reversed range.
+// The old guard only compared line numbers, so a same-line reversal reached
+// html::HighlightRange, whose scan then ran off the end of the buffer and
+// crashed.
+void reversed_range() {
+  S arr[1];
+
+  auto [a] = arr; // no-crash
+  // expected-warning@-1 {{Value stored to '[a]' during its initialization is 
never read}}
+}
+
+// The end token's length used to be added twice, so the highlight reached 
over the ';'.
+// CHECK-DAG: <span class="mrange">&amp;<span 
class='string_literal'>"abc"</span></span>;
+void overshoot() {
+  const char (*q)[4];
+  q = &"abc"; // expected-warning {{Value stored to 'q' is never read}}
+}
+
+// A range ending inside a macro expansion now covers the whole macro use, so
+// the highlight nests correctly around the 'macro' element instead of ending
+// inside it.
+// CHECK-DAG: <span class="mrange">{{.*}}<span class='macro'>DEREF(p)<span 
class='macro_popup'>(*(p))</span></span>{{.*}}</span>
+#define DEREF(p) (*(p))
+
+void ends_inside_expansion(int *p) {
+  if (!p)
+    DEREF(p) = 1; // expected-warning {{Dereference of null pointer}}
+}
+


        
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to