| /* Support for buffering diagnostics before flushing them to output sinks. |
| Copyright (C) 2024-2025 Free Software Foundation, Inc. |
| Contributed by David Malcolm <dmalcolm@redhat.com>. |
| |
| 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 |
| <http://www.gnu.org/licenses/>. */ |
| |
| #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<per_sink_buffer *> (); |
| 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 |