/* Support for buffering diagnostics before flushing them to output sinks. Copyright (C) 2024-2025 Free Software Foundation, Inc. Contributed by David Malcolm . This file is part of GCC. GCC is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. GCC is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GCC; see the file COPYING3. If not see . */ #include "config.h" #include "system.h" #include "coretypes.h" #include "diagnostic.h" #include "diagnostics/buffering.h" #include "diagnostics/sink.h" namespace diagnostics { /* Methods fns of diagnostics::context relating to buffering. */ /* If BUFFER_ is non-null, use BUFFER as the active diagnostics::buffer on this context. BUFFER is borrowed. If BUFFER_ is null, stop any buffering on this context until the next call to this function. */ void context::set_diagnostic_buffer (buffer *buffer_) { /* We don't allow changing buffering within a diagnostic group (to simplify handling of buffered diagnostics within the diagnostic_format implementations). */ gcc_assert (m_diagnostic_groups.m_group_nesting_depth == 0); /* Likewise, for simplicity, we only allow changing buffers at nesting level 0. */ gcc_assert (m_diagnostic_groups.m_diagnostic_nesting_level == 0); m_diagnostic_buffer = buffer_; if (buffer_) { buffer_->ensure_per_sink_buffers (); gcc_assert (buffer_->m_per_sink_buffers); gcc_assert (buffer_->m_per_sink_buffers->length () == m_sinks.length ()); for (unsigned idx = 0; idx < m_sinks.length (); ++idx) { auto sink_ = m_sinks[idx]; auto per_sink_buffer = (*buffer_->m_per_sink_buffers)[idx]; sink_->set_buffer (per_sink_buffer); } } else for (auto sink_ : m_sinks) sink_->set_buffer (nullptr); } /* Clear BUFFER_ without flushing it. */ void context::clear_diagnostic_buffer (buffer &buffer_) { if (buffer_.m_per_sink_buffers) for (auto per_sink_buffer_ : *buffer_.m_per_sink_buffers) per_sink_buffer_->clear (); buffer_.m_diagnostic_counters.clear (); /* We need to reset last_location, otherwise we may skip caret lines when we actually give a diagnostic. */ m_last_location = UNKNOWN_LOCATION; } /* Flush the diagnostics in BUFFER_ to this context, clearing BUFFER_. */ void context::flush_diagnostic_buffer (buffer &buffer_) { bool had_errors = (buffer_.diagnostic_count (kind::error) > 0 || buffer_.diagnostic_count (kind::werror) > 0); if (buffer_.m_per_sink_buffers) for (auto per_sink_buffer_ : *buffer_.m_per_sink_buffers) per_sink_buffer_->flush (); buffer_.m_diagnostic_counters.move_to (m_diagnostic_counters); action_after_output (had_errors ? kind::error : kind::warning); check_max_errors (true); } /* class diagnostics::buffer. */ buffer::buffer (context &ctxt) : m_ctxt (ctxt), m_per_sink_buffers (nullptr) { } buffer::~buffer () { if (m_per_sink_buffers) { for (auto iter : *m_per_sink_buffers) delete iter; delete m_per_sink_buffers; } } void buffer::dump (FILE *out, int indent) const { m_diagnostic_counters.dump (out, indent + 2); fprintf (out, "%*sm_per_sink_buffers:\n", indent, ""); if (m_per_sink_buffers) for (auto per_sink_buffer_ : *m_per_sink_buffers) per_sink_buffer_->dump (out, indent + 2); else fprintf (out, "%*s(none)\n", indent + 2, ""); } bool buffer::empty_p () const { if (m_per_sink_buffers) for (auto per_sink_buffer_ : *m_per_sink_buffers) /* Query initial buffer. */ return per_sink_buffer_->empty_p (); return true; } void buffer::move_to (buffer &dest) { /* Bail if there's nothing to move. */ if (!m_per_sink_buffers) return; m_diagnostic_counters.move_to (dest.m_diagnostic_counters); if (!dest.m_per_sink_buffers) { /* Optimization for the "move to empty" case: simply move the vec to the dest. */ dest.m_per_sink_buffers = m_per_sink_buffers; m_per_sink_buffers = nullptr; return; } dest.ensure_per_sink_buffers (); gcc_assert (m_per_sink_buffers); gcc_assert (m_per_sink_buffers->length () == m_ctxt.m_sinks.length ()); gcc_assert (dest.m_per_sink_buffers); gcc_assert (dest.m_per_sink_buffers->length () == m_ctxt.m_sinks.length ()); for (unsigned idx = 0; idx < m_ctxt.m_sinks.length (); ++idx) { auto per_sink_buffer_src = (*m_per_sink_buffers)[idx]; auto per_sink_buffer_dest = (*dest.m_per_sink_buffers)[idx]; per_sink_buffer_src->move_to (*per_sink_buffer_dest); } } /* Lazily get the output formats to create their own kind of buffers. We can't change the output sinks on a context once this has been called on any diagnostics::buffer instances for that context, since there's no way to update all diagnostics::buffer instances for that context. */ void buffer::ensure_per_sink_buffers () { if (!m_per_sink_buffers) { m_per_sink_buffers = new auto_vec (); for (unsigned idx = 0; idx < m_ctxt.m_sinks.length (); ++idx) { auto sink_ = m_ctxt.m_sinks[idx]; auto per_sink_buffer = sink_->make_per_sink_buffer (); m_per_sink_buffers->safe_push (per_sink_buffer.release ()); } } gcc_assert (m_per_sink_buffers); gcc_assert (m_per_sink_buffers->length () == m_ctxt.m_sinks.length ()); } } // namespace diagnostics