/* Helper class for deferring path creation until a diagnostic is emitted.
   Copyright (C) 2019-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"
#define INCLUDE_VECTOR
#include "system.h"
#include "coretypes.h"
#include "tree.h"
#include "version.h"
#include "intl.h"
#include "diagnostic.h"
#include "lazy-diagnostic-path.h"
#include "make-unique.h"
#include "selftest.h"
#include "selftest-diagnostic.h"
#include "simple-diagnostic-path.h"
#include "gcc-rich-location.h"
#include "diagnostic-format-text.h"

/* class lazy_diagnostic_path : public diagnostic_path.  */

/* Implementation of diagnostic_path vfuncs in terms of a lazily-generated
   path.  */

unsigned
lazy_diagnostic_path::num_events () const
{
  lazily_generate_path ();
  return m_inner_path->num_events ();
}

const diagnostic_event &
lazy_diagnostic_path::get_event (int idx) const
{
  lazily_generate_path ();
  return m_inner_path->get_event (idx);
}

unsigned
lazy_diagnostic_path::num_threads () const
{
  lazily_generate_path ();
  return m_inner_path->num_threads ();
}

const diagnostic_thread &
lazy_diagnostic_path::get_thread (diagnostic_thread_id_t idx) const
{
  lazily_generate_path ();
  return m_inner_path->get_thread (idx);
}

bool
lazy_diagnostic_path::same_function_p (int event_idx_a,
					   int event_idx_b) const
{
  lazily_generate_path ();
  return m_inner_path->same_function_p (event_idx_a, event_idx_b);
}

void
lazy_diagnostic_path::lazily_generate_path () const
{
  if (!m_inner_path)
    m_inner_path = make_inner_path ();
  gcc_assert (m_inner_path != nullptr);
}

#if CHECKING_P

namespace selftest {

class test_lazy_path : public lazy_diagnostic_path
{
public:
  test_lazy_path (pretty_printer &pp)
  : m_pp (pp)
  {
  }
  std::unique_ptr<diagnostic_path> make_inner_path () const final override
  {
    tree fntype_void_void
      = build_function_type_array (void_type_node, 0, NULL);
    tree fndecl_foo = build_fn_decl ("foo", fntype_void_void);
    auto path = ::make_unique<simple_diagnostic_path> (&m_pp);
    path->add_event (UNKNOWN_LOCATION, fndecl_foo, 0, "first %qs", "free");
    path->add_event (UNKNOWN_LOCATION, fndecl_foo, 0, "double %qs", "free");
    return path;
  }
private:
  pretty_printer &m_pp;
};

static void
test_intraprocedural_path (pretty_printer *event_pp)
{
  test_lazy_path path (*event_pp);
  ASSERT_FALSE (path.generated_p ());
  ASSERT_EQ (path.num_events (), 2);
  ASSERT_TRUE (path.generated_p ());
  ASSERT_EQ (path.num_threads (), 1);
  ASSERT_FALSE (path.interprocedural_p ());
  ASSERT_STREQ (path.get_event (0).get_desc (*event_pp).get (),
		"first `free'");
  ASSERT_STREQ (path.get_event (1).get_desc (*event_pp).get (),
		"double `free'");
}

/* Implementation of diagnostic_option_manager for which all
   options are disabled, for use in selftests.
   Note that this is *not* called for diagnostic_option_id (0), which
   means "always warn"  */

class all_warnings_disabled : public diagnostic_option_manager
{
public:
  int option_enabled_p (diagnostic_option_id) const final override
  {
    /* Treat all options as disabled.  */
    return 0;
  }
  char *make_option_name (diagnostic_option_id,
			  diagnostic_t,
			  diagnostic_t) const final override
  {
    return nullptr;
  }
  char *make_option_url (diagnostic_option_id) const final override
  {
    return nullptr;
  }
};

static void
test_emission (pretty_printer *event_pp)
{
  struct test_rich_location : public gcc_rich_location
  {
    test_rich_location (pretty_printer &event_pp)
    : gcc_rich_location (UNKNOWN_LOCATION),
      m_path (event_pp)
    {
      set_path (&m_path);
    }
    test_lazy_path m_path;
  };

  /* Verify that we don't bother generating the inner path if the warning
     is skipped.  */
  {
    test_diagnostic_context dc;
    dc.set_option_manager (::make_unique<all_warnings_disabled> (), 0);

    test_rich_location rich_loc (*event_pp);
    ASSERT_FALSE (rich_loc.m_path.generated_p ());

    diagnostic_option_id option_id (42); // has to be non-zero
    bool emitted
      = dc.emit_diagnostic_with_group (DK_WARNING, rich_loc, nullptr,
				       option_id,
				       "this warning should be skipped");
    ASSERT_FALSE (emitted);
    ASSERT_FALSE (rich_loc.m_path.generated_p ());
  }

  /* Verify that we *do* generate the inner path for a diagnostic that
     is emitted, such as an error.  */
  {
    test_diagnostic_context dc;

    test_rich_location rich_loc (*event_pp);
    ASSERT_FALSE (rich_loc.m_path.generated_p ());

    bool emitted
      = dc.emit_diagnostic_with_group (DK_ERROR, rich_loc, nullptr, 0,
				       "this is a test");
    ASSERT_TRUE (emitted);
    ASSERT_TRUE (rich_loc.m_path.generated_p ());

    /* Verify that the path works as expected.  */
    dc.set_path_format (DPF_INLINE_EVENTS);
    diagnostic_text_output_format sink (dc);
    pp_buffer (sink.get_printer ())->m_flush_p = false;
    sink.print_path (rich_loc.m_path);
    ASSERT_STREQ (pp_formatted_text (sink.get_printer ()),
		  "  `foo': event 1\n"
		  " (1): first `free'\n"
		  "  `foo': event 2\n"
		  " (2): double `free'\n");
  }
}

/* Run all of the selftests within this file.  */

void
lazy_diagnostic_path_cc_tests ()
{
  /* In a few places we use the global dc's printer to determine
     colorization so ensure this off during the tests.  */
  pretty_printer *global_pp = global_dc->get_reference_printer ();
  const bool saved_show_color = pp_show_color (global_pp);
  pp_show_color (global_pp) = false;

  auto_fix_quotes fix_quotes;
  std::unique_ptr<pretty_printer> event_pp
    = std::unique_ptr<pretty_printer> (global_pp->clone ());

  test_intraprocedural_path (event_pp.get ());
  test_emission (event_pp.get ());

  pp_show_color (global_pp) = saved_show_color;
}

} // namespace selftest

#endif /* #if CHECKING_P */