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">&<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
