[llvm-branch-commits] [clang] [analyzer] Fix -analyzer-output=html assert on reversed and macro ranges (PR #214463)
Balázs Benics via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Thu Aug 6 07:40:47 PDT 2026
https://github.com/steakhal updated https://github.com/llvm/llvm-project/pull/214463
>From 75f7825870367dc2e28e43b8b55249f0fcb6ed66 Mon Sep 17 00:00:00 2001
From: Balazs Benics <benicsbalazs at gmail.com>
Date: Thu, 6 Aug 2026 11:11:06 +0100
Subject: [PATCH] [analyzer] Fix -analyzer-output=html assert on reversed and
macro ranges
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
---
.../StaticAnalyzer/Core/HTMLDiagnostics.cpp | 35 ++++-----------
.../highlight-range-mapping.cpp | 43 +++++++++++++++++++
2 files changed, 51 insertions(+), 27 deletions(-)
create mode 100644 clang/test/Analysis/html_diagnostics/highlight-range-mapping.cpp
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 at +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 at -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">&<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}}
+}
+
More information about the llvm-branch-commits
mailing list