/* Generic plugin context
   Copyright (C) 2020-2022 Free Software Foundation, Inc.

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 <cc1plugin-config.h>

#undef PACKAGE_NAME
#undef PACKAGE_STRING
#undef PACKAGE_TARNAME
#undef PACKAGE_VERSION

#include "../gcc/config.h"

#undef PACKAGE_NAME
#undef PACKAGE_STRING
#undef PACKAGE_TARNAME
#undef PACKAGE_VERSION

#include "gcc-plugin.h"
#include "system.h"
#include "coretypes.h"
#include "stringpool.h"
#include "hash-set.h"
#include "diagnostic.h"
#include "langhooks.h"
#include "langhooks-def.h"

#include "gcc-interface.h"

#include "context.hh"
#include "marshall.hh"



#ifdef __GNUC__
#pragma GCC visibility push(default)
#endif
int plugin_is_GPL_compatible;
#ifdef __GNUC__
#pragma GCC visibility pop
#endif

cc1_plugin::plugin_context *cc1_plugin::current_context;



// This is put into the lang hooks when the plugin starts.

static void
plugin_print_error_function (diagnostic_context *context, const char *file,
			     diagnostic_info *diagnostic)
{
  if (current_function_decl != NULL_TREE
      && DECL_NAME (current_function_decl) != NULL_TREE
      && strcmp (IDENTIFIER_POINTER (DECL_NAME (current_function_decl)),
		 GCC_FE_WRAPPER_FUNCTION) == 0)
    return;
  lhd_print_error_function (context, file, diagnostic);
}



location_t
cc1_plugin::plugin_context::get_location_t (const char *filename,
					    unsigned int line_number)
{
  if (filename == NULL)
    return UNKNOWN_LOCATION;

  filename = intern_filename (filename);
  linemap_add (line_table, LC_ENTER, false, filename, line_number);
  location_t loc = linemap_line_start (line_table, line_number, 0);
  linemap_add (line_table, LC_LEAVE, false, NULL, 0);
  return loc;
}

// Add a file name to FILE_NAMES and return the canonical copy.
const char *
cc1_plugin::plugin_context::intern_filename (const char *filename)
{
  const char **slot = file_names.find_slot (filename, INSERT);
  if (*slot == NULL)
    {
      /* The file name must live as long as the line map, which
	 effectively means as long as this compilation.  So, we copy
	 the string here but never free it.  */
      *slot = xstrdup (filename);
    }
  return *slot;
}

void
cc1_plugin::plugin_context::mark ()
{
  for (const auto &item : address_map)
    {
      ggc_mark (item->decl);
      ggc_mark (item->address);
    }

  for (const auto &item : preserved)
    ggc_mark (&item);
}



// Perform GC marking.

static void
gc_mark (void *, void *)
{
  if (cc1_plugin::current_context != NULL)
    cc1_plugin::current_context->mark ();
}

void
cc1_plugin::generic_plugin_init (struct plugin_name_args *plugin_info,
				 unsigned int version)
{
  long fd = -1;
  for (int i = 0; i < plugin_info->argc; ++i)
    {
      if (strcmp (plugin_info->argv[i].key, "fd") == 0)
	{
	  char *tail;
	  errno = 0;
	  fd = strtol (plugin_info->argv[i].value, &tail, 0);
	  if (*tail != '\0' || errno != 0)
	    fatal_error (input_location,
			 "%s: invalid file descriptor argument to plugin",
			 plugin_info->base_name);
	  break;
	}
    }
  if (fd == -1)
    fatal_error (input_location,
		 "%s: required plugin argument %<fd%> is missing",
		 plugin_info->base_name);

  current_context = new plugin_context (fd);

  // Handshake.
  cc1_plugin::protocol_int h_version;
  if (!current_context->require ('H')
      || ! ::cc1_plugin::unmarshall (current_context, &h_version))
    fatal_error (input_location,
		 "%s: handshake failed", plugin_info->base_name);
  if (h_version != version)
    fatal_error (input_location,
		 "%s: unknown version in handshake", plugin_info->base_name);

  register_callback (plugin_info->base_name, PLUGIN_GGC_MARKING,
		     gc_mark, NULL);

  lang_hooks.print_error_function = plugin_print_error_function;
}