From 2872608b023ba57bcb0c811ad16457c9c512c6fe Mon Sep 17 00:00:00 2001 From: doe300 Date: Sat, 5 Sep 2026 09:48:00 +0200 Subject: [PATCH] Show clang-analyze results in source code view The analysis results are parsed from the SARIF file and are shown as diagnostics similar to compiler wranings. Also supports a simple algorithm for finding an original source code location for a diagnostic, if the source code changed since the analysis run. --- src/source_clang.cpp | 91 ++++++++++++++++++++++++++++++++++++++++++++ src/source_clang.hpp | 5 +++ 2 files changed, 96 insertions(+) diff --git a/src/source_clang.cpp b/src/source_clang.cpp index b3846f1..6b7f55e 100644 --- a/src/source_clang.cpp +++ b/src/source_clang.cpp @@ -12,6 +12,7 @@ #include "documentation.hpp" #include "filesystem.hpp" #include "info.hpp" +#include "sarif.hpp" #include "selection_dialog.hpp" #include "usages_clang.hpp" #include "utility.hpp" @@ -171,6 +172,16 @@ void Source::ClangViewParse::parse(size_t count) { for(auto &token : *clang_tokens) clang_tokens_offsets.emplace_back(token.get_source_range().get_offsets()); clang_diagnostics = clang_tu->get_diagnostics(); + + try { + auto analyze_results_file = Project::Build::create(this->file_path)->get_default_path() / "clang-analyze.sarif.json"; + analyze_diagnostics = SARIF::results_for_file(analyze_results_file, file_path); + } + catch(const std::exception &err) { + analyze_diagnostics.clear(); + Terminal::get().async_print("\e[31mError\e[m: Parsing clang-analyze results failed: " + std::string{err.what()} + ".\n", true); + } + parse_mutex.unlock(); dispatcher.post([this, count] { if(count != parse_count || parse_state != ParseState::processing) @@ -525,6 +536,86 @@ void Source::ClangViewParse::update_diagnostics() { } } + std::map, std::set> added_messages; + const auto add_tooltip_for_location = [this, &added_messages](const SARIF::Location &location, const SARIF::Result &result) { + auto text = result.get_message(location, true /* markdown */); + int line = location.region.start_line - 1; + if(line < 0 || line >= get_buffer()->get_line_count()) + line = get_buffer()->get_line_count() - 1; + auto start = get_iter_at_line_end(line); + int offset = location.region.start_column - 1; + if(offset >= 0 && offset < start.get_line_index()) + start = get_buffer()->get_iter_at_line_offset(line, offset); + if(start.ends_line()) { + while(!start.is_start() && start.ends_line()) + start.backward_char(); + } + + line = location.region.end_line - 1; + if(line < 0 || line >= get_buffer()->get_line_count()) + line = get_buffer()->get_line_count() - 1; + auto end = get_iter_at_line_end(line); + offset = location.region.end_column - 1; + if(offset >= 0 && offset < end.get_line_index()) + end = get_buffer()->get_iter_at_line_offset(line, offset); + + + // Check whether the source actually still matches the scan result + auto match = get_buffer()->get_text(start, end); + if(!location.region.snippet.empty() && match != location.region.snippet) { + bool context_found = false; + if(location.context_region && !location.context_region->snippet.empty()) { + // Try to find the context in case the matching source was only moved + Gtk::TextIter match_start, match_end; + if(start.forward_search(location.context_region->snippet, static_cast(0), match_start, match_end)) + context_found = true; + else if(end.backward_search(location.context_region->snippet, static_cast(0), match_start, match_end)) + context_found = true; + else if(get_buffer()->begin().forward_search(location.context_region->snippet, static_cast(0), match_start, match_end)) + context_found = true; + + if(context_found) { + match_start.forward_search(location.region.snippet, static_cast(0), start, end, match_end); + } + } + text.append(" (Source modified since scan, match ").append(context_found ? "may be" : "is").append(" inaccurate)"); + } + + auto added_it = added_messages.find(std::make_pair(start, end)); + if(added_it != added_messages.end() && added_it->second.find(text) != added_it->second.end()) + return false; + + diagnostic_offsets.emplace(start.get_offset()); + added_messages[std::make_pair(start, end)].emplace(text); + add_diagnostic_tooltip(start, end, result.level == SARIF::Result::Level::error, [text](Tooltip &tooltip) { + tooltip.insert_markdown(text); + }); + return true; + }; + + for(const auto &diagnostic : analyze_diagnostics) { + for(const auto &location : diagnostic.locations) { + if(location.artifact_location == file_path) { + if(add_tooltip_for_location(location, diagnostic)) { + num_warnings += diagnostic.level == SARIF::Result::Level::warning; + num_errors += diagnostic.level == SARIF::Result::Level::error; + } + } + } + for(const auto &code_flow : diagnostic.code_flows) { + for(const auto &thread_flow : code_flow.thread_flows) { + for(const auto &entry : thread_flow.locations) { + if(entry.location.artifact_location == file_path && entry.importance != SARIF::ThreadFlowLocation::Importance::unimportant) { + if(add_tooltip_for_location(entry.location, diagnostic)) { + num_warnings += diagnostic.level == SARIF::Result::Level::warning; + num_errors += diagnostic.level == SARIF::Result::Level::error; + } + } + } + } + } + } + status_diagnostics = std::make_tuple(num_warnings, num_errors, num_fix_its); if(update_status_diagnostics) update_status_diagnostics(this); diff --git a/src/source_clang.hpp b/src/source_clang.hpp index 78fd573..17eb50c 100644 --- a/src/source_clang.hpp +++ b/src/source_clang.hpp @@ -11,6 +11,10 @@ #include #include +namespace SARIF { + struct Result; +} // namespace SARIF + namespace Source { class ClangViewParse : public View { protected: @@ -57,6 +61,7 @@ namespace Source { void update_diagnostics() REQUIRES(parse_mutex); std::vector clang_diagnostics GUARDED_BY(parse_mutex); + std::vector analyze_diagnostics GUARDED_BY(parse_mutex); /// Removes for instance ::__1:: and ::__cxx11:: from type void remove_internal_namespaces(std::string &type);