/* SARIF output for diagnostics
   Copyright (C) 2018-2024 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_LIST
#define INCLUDE_MAP
#define INCLUDE_MEMORY
#define INCLUDE_VECTOR
#include "system.h"
#include "coretypes.h"
#include "diagnostic.h"
#include "diagnostic-metadata.h"
#include "diagnostic-path.h"
#include "json.h"
#include "cpplib.h"
#include "logical-location.h"
#include "diagnostic-client-data-hooks.h"
#include "diagnostic-diagram.h"
#include "text-art/canvas.h"
#include "diagnostic-format-sarif.h"
#include "ordered-hash-map.h"
#include "sbitmap.h"
#include "make-unique.h"
#include "selftest.h"
#include "selftest-diagnostic.h"
#include "selftest-diagnostic-show-locus.h"
#include "selftest-json.h"
#include "text-range-label.h"

/* Forward decls.  */
class sarif_builder;
class content_renderer;
  class escape_nonascii_renderer;

/* Subclasses of sarif_object.
   Keep these in order of their descriptions in the specification.  */
class sarif_artifact_content; // 3.3
class sarif_artifact_location; // 3.4
class sarif_message; // 3.11
class sarif_multiformat_message_string; // 3.12
class sarif_log; // 3.13
class sarif_run; // 3.14
class sarif_tool; // 3.18
class sarif_tool_component; // 3.19
class sarif_invocation; // 3.20
class sarif_artifact; // 3.24
class sarif_location_manager; // not in the spec
class sarif_result; // 3.27
class sarif_location; // 3.28
class sarif_physical_location; // 3.29
class sarif_region; // 3.30
class sarif_logical_location; // 3.33
class sarif_location_relationship; // 3.34
class sarif_code_flow; // 3.36
class sarif_thread_flow; // 3.37
class sarif_thread_flow_location; // 3.38
class sarif_reporting_descriptor; // 3.49
class sarif_reporting_descriptor_reference; // 3.53
class sarif_tool_component_reference; // 3.54
class sarif_fix; // 3.55
class sarif_artifact_change; // 3.56
class sarif_replacement; // 3.57
class sarif_ice_notification; // 3.58

// Valid values for locationRelationship's "kinds" property (3.34.3)

enum class location_relationship_kind
{
  includes,
  is_included_by,
  relevant,

  NUM_KINDS
};

/* Declarations of subclasses of sarif_object.
   Keep these in order of their descriptions in the specification.  */

/* Subclass of sarif_object for SARIF "artifactContent" objects
   (SARIF v2.1.0 section 3.3).  */

class sarif_artifact_content : public sarif_object {};

/* Subclass of sarif_object for SARIF "artifactLocation" objects
   (SARIF v2.1.0 section 3.4).  */

class sarif_artifact_location : public sarif_object {};

/* Subclass of sarif_object for SARIF "message" objects
   (SARIF v2.1.0 section 3.11).  */

class sarif_message : public sarif_object {};

/* Subclass of sarif_object for SARIF "multiformatMessageString" objects
   (SARIF v2.1.0 section 3.12).  */

class sarif_multiformat_message_string : public sarif_object {};

/* Subclass of sarif_object for SARIF "log" objects
   (SARIF v2.1.0 section 3.13).  */

class sarif_log : public sarif_object {};

/* Subclass of sarif_object for SARIF "run" objects
   (SARIF v2.1.0 section 3.14).  */

class sarif_run : public sarif_object {};

/* Subclass of sarif_object for SARIF "tool" objects
   (SARIF v2.1.0 section 3.18).  */

class sarif_tool : public sarif_object {};

/* Subclass of sarif_object for SARIF "toolComponent" objects
   (SARIF v2.1.0 section 3.19).  */

class sarif_tool_component : public sarif_object {};

/* Make a JSON string for the current date and time.
   See SARIF v2.1.0 section 3.9 "Date/time properties".
   Given that we don't run at the very beginning/end of the
   process, it doesn't make sense to be more accurate than
   the current second.  */

static std::unique_ptr<json::string>
make_date_time_string_for_current_time ()
{
  time_t t = time (nullptr);
  struct tm *tm = gmtime (&t);
  char buf[256];
  snprintf (buf, sizeof (buf) - 1,
	    ("%04i-%02i-%02iT"
	     "%02i:%02i:%02iZ"),
	    tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
	    tm->tm_hour, tm->tm_min, tm->tm_sec);
  return ::make_unique<json::string> (buf);
}

/* Subclass of sarif_object for SARIF "invocation" objects
   (SARIF v2.1.0 section 3.20).  */

class sarif_invocation : public sarif_object
{
public:
  sarif_invocation (sarif_builder &builder,
		    const char * const *original_argv);

  void add_notification_for_ice (diagnostic_context &context,
				 const diagnostic_info &diagnostic,
				 sarif_builder &builder);
  void prepare_to_flush (diagnostic_context &context);

private:
  std::unique_ptr<json::array> m_notifications_arr;
  bool m_success;
};

/* Corresponds to values for the SARIF artifact objects "roles" property.
   (SARIF v2.1.0 section 3.24.6).  */

enum class diagnostic_artifact_role
{
  analysis_target, /* "analysisTarget".  */
  debug_output_file, /* "debugOutputFile".  */
  result_file, /* "resultFile".  */

  /* "scannedFile" added in 2.2;
     see https://github.com/oasis-tcs/sarif-spec/issues/459 */
  scanned_file,

  traced_file, /* "tracedFile".  */

  NUM_ROLES
};

/* Subclass of sarif_object for SARIF artifact objects
   (SARIF v2.1.0 section 3.24).  */

class sarif_artifact : public sarif_object
{
public:
  sarif_artifact (const char *filename)
  : m_filename (filename),
    m_roles ((unsigned)diagnostic_artifact_role::NUM_ROLES),
    m_embed_contents (false)
  {
    bitmap_clear (m_roles);
  }

  void add_role (enum diagnostic_artifact_role role,
		 bool embed_contents);

  bool embed_contents_p () const { return m_embed_contents; }
  void populate_contents (sarif_builder &builder);
  void populate_roles ();

private:
  const char *m_filename;
  auto_sbitmap m_roles;

  /* Flag to track whether this artifact should have a "contents" property
     (SARIF v2.1.0 section 3.24.8).
     We only add the contents for those artifacts that have a location
     referencing them (so that a consumer might want to quote the source).   */
  bool m_embed_contents;
};

/* A class for sarif_objects that own a "namespace" of numeric IDs for
   managing location objects within them.  Currently (SARIF v2.1.0)
   this is just for sarif_result (section 3.28.2), but it will likely
   eventually also be for notification objects; see
   https://github.com/oasis-tcs/sarif-spec/issues/540

   Consider locations with chains of include information e.g.

   > include-chain-1.c:
   >   #include "include-chain-1.h"

   include-chain-1.h:
     | // First set of decls, which will be referenced in notes
     | #include "include-chain-1-1.h"
     |
     | // Second set of decls, which will trigger the errors
     | #include "include-chain-1-2.h"

   include-chain-1-1.h:
     | int p;
     | int q;

   include-chain-1-1.h:
     | char p;
     | char q;

   GCC's textual output emits:
     |   In file included from PATH/include-chain-1.h:5,
     |                    from PATH/include-chain-1.c:30:
     |   PATH/include-chain-1-2.h:1:6: error: conflicting types for 'p'; have 'char'
     |       1 | char p;
     |         |      ^
     |   In file included from PATH/include-chain-1.h:2:
     |   PATH/include-chain-1-1.h:1:5: note: previous declaration of 'p' with type 'int'
     |       1 | int p;
     |         |     ^
     |   PATH/include-chain-1-2.h:2:6: error: conflicting types for 'q'; have 'char'
     |       2 | char q;
     |         |      ^
     |   PATH/include-chain-1-1.h:2:5: note: previous declaration of 'q' with type 'int'
     |       2 | int q;
     |         |     ^

   Whenever a SARIF location is added for a location_t that
   was #included from somewhere, we queue up the creation of a SARIF
   location for the location of the #include.  The worklist of queued
   locations is flushed when the result is finished, which lazily creates
   any additional related locations for the include chain, and the
   relationships between the locations.  Doing so can lead to further
   include locations being processed.  The worklist approach allows us
   to lazily explore the relevant part of the directed graph of location_t
   values implicit in our line_maps structure, replicating it as a directed
   graph of SARIF locations within the SARIF result object, like this:

   [0]: error in include-chain-1-2.h ("conflicting types for 'p'; have 'char'")
   [1]: #include "include-chain-1-2.h" in include-chain-1.h
   [2]: note in include-chain-1-2.h ("previous declaration of 'p' with type 'int'")
   [3]: #include "include-chain-1-1.h" in include-chain-1.h
   [4]: #include "include-chain-1.h" in include-chain-1.c

   where we want to capture this "includes" graph in SARIF form:
   . +-----------------------------------+ +----------------------------------+
   . |"id": 0                            | |"id": 2                           |
   . | error: "conflicting types for 'p';| | note: previous declaration of 'p'|
   . |  have 'char'"|                    | | with type 'int'")                |
   . | in include-chain-1-2.h            | | in include-chain-1-1.h           |
   . +-----------------------------------+ +----------------------------------+
   .            ^         |                            ^         |
   .   includes |         | included-by       includes |         | included-by
   .            |         V                            |         V
   .  +--------------------------------+    +--------------------------------+
   .  |"id": 1                         |    |"id": 3                         |
   .  | #include "include-chain-1-2.h" |    | #include "include-chain-1-1.h" |
   .  | in include-chain-1.h           |    | in include-chain-1.h           |
   .  +--------------------------------+    +--------------------------------+
   .                   ^     |                       ^    |
   .          includes |     | included-by  includes |    | included-by
   .                   |     V                       |    V
   .                  +------------------------------------+
   .                  |"id": 4                             |
   .                  | The  #include "include-chain-1.h"  |
   .                  | in include-chain-1.c               |
   .                  +------------------------------------+
 */

class sarif_location_manager : public sarif_object
{
public:
  /* A worklist of pending actions needed to fully process this object.

     This lets us lazily walk our data structures to build the
     directed graph of locations, whilst keeping "notes" at the top
     of the "relatedLocations" array, and avoiding the need for
     recursion.  */
  struct worklist_item
  {
    enum class kind
    {
     /* Process a #include relationship where m_location_obj
	was #included-d at m_where.  */
     included_from,

     /* Process a location_t that was added as a secondary location
	to a rich_location without a label.  */
     unlabelled_secondary_location
    };

    worklist_item (sarif_location &location_obj,
		   enum kind kind,
		   location_t where)
      : m_location_obj (location_obj),
	m_kind (kind),
	m_where (where)
    {
    }

    sarif_location &m_location_obj;
    enum kind m_kind;
    location_t m_where;
  };

  sarif_location_manager ()
  : m_next_location_id (0)
  {
  }

  unsigned allocate_location_id ()
  {
    return m_next_location_id++;
  }

  virtual void
  add_related_location (std::unique_ptr<sarif_location> location_obj) = 0;

  void
  add_relationship_to_worklist (sarif_location &location_obj,
				enum worklist_item::kind kind,
				location_t where);

  void
  process_worklist (sarif_builder &builder);

  void
  process_worklist_item (sarif_builder &builder,
			 const worklist_item &item);
private:
  unsigned m_next_location_id;

  std::list<worklist_item> m_worklist;
  std::map<location_t, sarif_location *> m_included_from_locations;
  std::map<location_t, sarif_location *> m_unlabelled_secondary_locations;
};

/* Subclass of sarif_object for SARIF "result" objects
   (SARIF v2.1.0 section 3.27).
   Each SARIF result object has its own "namespace" of numeric IDs for
   managing location objects (SARIF v2.1.0 section 3.28.2). */

class sarif_result : public sarif_location_manager
{
public:
  sarif_result () : m_related_locations_arr (nullptr) {}

  void
  on_nested_diagnostic (diagnostic_context &context,
			const diagnostic_info &diagnostic,
			diagnostic_t orig_diag_kind,
			sarif_builder &builder);
  void on_diagram (diagnostic_context &context,
		   const diagnostic_diagram &diagram,
		   sarif_builder &builder);

  void
  add_related_location (std::unique_ptr<sarif_location> location_obj)
    final override;

private:
  json::array *m_related_locations_arr; // borrowed
};

/* Subclass of sarif_object for SARIF "location" objects
   (SARIF v2.1.0 section 3.28).
   A location object can have an "id" which must be unique within
   the enclosing result, if any (see SARIF v2.1.0 section 3.28.2).  */

class sarif_location : public sarif_object
{
public:
  long lazily_add_id (sarif_location_manager &loc_mgr);
  long get_id () const;

  void lazily_add_relationship (sarif_location &target,
				enum location_relationship_kind kind,
				sarif_location_manager &loc_mgr);

private:
  sarif_location_relationship &
  lazily_add_relationship_object (sarif_location &target,
				  sarif_location_manager &loc_mgr);

  json::array &lazily_add_relationships_array ();

  std::map<sarif_location *,
	   sarif_location_relationship *> m_relationships_map;
};

/* Subclass of sarif_object for SARIF "physicalLocation" objects
   (SARIF v2.1.0 section 3.29).  */

class sarif_physical_location : public sarif_object {};

/* Subclass of sarif_object for SARIF "region" objects
   (SARIF v2.1.0 section 3.30).  */

class sarif_region : public sarif_object {};

/* Subclass of sarif_object for SARIF "locationRelationship" objects
   (SARIF v2.1.0 section 3.34).  */

class sarif_location_relationship : public sarif_object
{
public:
  sarif_location_relationship (sarif_location &target,
			       sarif_location_manager &loc_mgr);

  long get_target_id () const;

  void lazily_add_kind (enum location_relationship_kind kind);

private:
  auto_sbitmap m_kinds;
};

/* Subclass of sarif_object for SARIF "codeFlow" objects
   (SARIF v2.1.0 section 3.36).  */

class sarif_code_flow : public sarif_object {};

/* Subclass of sarif_object for SARIF "threadFlow" objects
   (SARIF v2.1.0 section 3.37).  */

class sarif_thread_flow : public sarif_object
{
public:
  sarif_thread_flow (const diagnostic_thread &thread);

  void
  add_location
    (std::unique_ptr<sarif_thread_flow_location> thread_flow_loc_obj);

private:
  json::array *m_locations_arr; // borrowed
};

/* Subclass of sarif_object for SARIF "threadFlowLocation" objects
   (SARIF v2.1.0 section 3.38).  */

class sarif_thread_flow_location : public sarif_object {};

/* Subclass of sarif_object for SARIF "reportingDescriptor" objects
   (SARIF v2.1.0 section 3.49).  */

class sarif_reporting_descriptor : public sarif_object {};

/* Subclass of sarif_object for SARIF "reportingDescriptorReference" objects
   (SARIF v2.1.0 section 3.53).  */

class sarif_reporting_descriptor_reference : public sarif_object {};

/* Subclass of sarif_object for SARIF "toolComponentReference" objects
   (SARIF v2.1.0 section 3.54).  */

class sarif_tool_component_reference : public sarif_object {};

/* Subclass of sarif_object for SARIF "fix" objects
   (SARIF v2.1.0 section 3.55).  */

class sarif_fix : public sarif_object {};

/* Subclass of sarif_object for SARIF "artifactChange" objects
   (SARIF v2.1.0 section 3.56).  */

class sarif_artifact_change : public sarif_object {};

/* Subclass of sarif_object for SARIF "replacement" objects
   (SARIF v2.1.0 section 3.57).  */

class sarif_replacement : public sarif_object {};

/* Subclass of sarif_object for SARIF "notification" objects
   (SARIF v2.1.0 section 3.58).

   This subclass is specifically for notifying when an
   internal compiler error occurs.  */

class sarif_ice_notification : public sarif_location_manager
{
public:
  sarif_ice_notification (diagnostic_context &context,
			  const diagnostic_info &diagnostic,
			  sarif_builder &builder);

  void
  add_related_location (std::unique_ptr<sarif_location> location_obj)
    final override;
};

/* Abstract base class for use when making an  "artifactContent"
   object (SARIF v2.1.0 section 3.3): generate a value for the
   3.3.4 "rendered" property.
   Can return nullptr, for "no property".  */

class content_renderer
{
public:
  virtual ~content_renderer () {}

  virtual std::unique_ptr<sarif_multiformat_message_string>
  render (const sarif_builder &builder) const = 0;
};

/* A class for managing SARIF output (for -fdiagnostics-format=sarif-stderr
   and -fdiagnostics-format=sarif-file).

   As diagnostics occur, we build "result" JSON objects, and
   accumulate state:
   - which source files are referenced
   - which warnings are emitted
   - which CWEs are used

   At the end of the compile, we use the above to build the full SARIF
   object tree, adding the result objects to the correct place, and
   creating objects for the various source files, warnings and CWEs
   referenced.

   Implemented:
   - fix-it hints
   - CWE metadata
   - diagnostic groups (see limitations below)
   - logical locations (e.g. cfun)
   - labelled ranges (as annotations)
   - secondary ranges without labels (as related locations)

   Known limitations:
   - GCC supports one-deep nesting of diagnostics (via auto_diagnostic_group),
     but we only capture location and message information from such nested
     diagnostics (e.g. we ignore fix-it hints on them)
   - although we capture command-line arguments (section 3.20.2), we don't
     yet capture response files.
   - doesn't capture "artifact.encoding" property
     (SARIF v2.1.0 section 3.24.9).
   - doesn't capture hashes of the source files
     ("artifact.hashes" property (SARIF v2.1.0 section 3.24.11).
   - doesn't capture the "analysisTarget" property
     (SARIF v2.1.0 section 3.27.13).
   - doesn't capture -Werror cleanly
   - doesn't capture inlining information (can SARIF handle this?)
   - doesn't capture macro expansion information (can SARIF handle this?).  */

class sarif_builder
{
public:
  sarif_builder (diagnostic_context &context,
		 const line_maps *line_maps,
		 const char *main_input_filename_,
		 bool formatted);

  void end_diagnostic (diagnostic_context &context,
		       const diagnostic_info &diagnostic,
		       diagnostic_t orig_diag_kind);
  void emit_diagram (diagnostic_context &context,
		     const diagnostic_diagram &diagram);
  void end_group ();

  std::unique_ptr<sarif_result> take_current_result ()
  {
    return std::move (m_cur_group_result);
  }

  std::unique_ptr<sarif_log> flush_to_object ();
  void flush_to_file (FILE *outf);

  std::unique_ptr<json::array>
  make_locations_arr (sarif_location_manager &loc_mgr,
		      const diagnostic_info &diagnostic,
		      enum diagnostic_artifact_role role);
  std::unique_ptr<sarif_location>
  make_location_object (sarif_location_manager &loc_mgr,
			const rich_location &rich_loc,
			const logical_location *logical_loc,
			enum diagnostic_artifact_role role);
  std::unique_ptr<sarif_location>
  make_location_object (sarif_location_manager &loc_mgr,
			location_t where,
			enum diagnostic_artifact_role role);
  std::unique_ptr<sarif_message>
  make_message_object (const char *msg) const;
  std::unique_ptr<sarif_message>
  make_message_object_for_diagram (diagnostic_context &context,
				   const diagnostic_diagram &diagram);
  std::unique_ptr<sarif_artifact_content>
  maybe_make_artifact_content_object (const char *filename) const;

  std::unique_ptr<sarif_artifact_location>
  make_artifact_location_object (const char *filename);

private:
  std::unique_ptr<sarif_result>
  make_result_object (diagnostic_context &context,
		      const diagnostic_info &diagnostic,
		      diagnostic_t orig_diag_kind);
  void
  add_any_include_chain (sarif_location_manager &loc_mgr,
			 sarif_location &location_obj,
			 location_t where);
  void
  set_any_logical_locs_arr (sarif_location &location_obj,
			    const logical_location *logical_loc);
  std::unique_ptr<sarif_location>
  make_location_object (sarif_location_manager &loc_mgr,
			const diagnostic_event &event,
			enum diagnostic_artifact_role role);
  std::unique_ptr<sarif_code_flow>
  make_code_flow_object (sarif_result &result,
			 const diagnostic_path &path);
  std::unique_ptr<sarif_thread_flow_location>
  make_thread_flow_location_object (sarif_result &result,
				    const diagnostic_event &event,
				    int path_event_idx);
  std::unique_ptr<json::array>
  maybe_make_kinds_array (diagnostic_event::meaning m) const;
  std::unique_ptr<sarif_physical_location>
  maybe_make_physical_location_object (location_t loc,
				       enum diagnostic_artifact_role role,
				       int column_override,
				       const content_renderer *snippet_renderer);
  std::unique_ptr<sarif_artifact_location>
  make_artifact_location_object (location_t loc);
  std::unique_ptr<sarif_artifact_location>
  make_artifact_location_object_for_pwd () const;
  std::unique_ptr<sarif_region>
  maybe_make_region_object (location_t loc,
			    int column_override) const;
  std::unique_ptr<sarif_region>
  maybe_make_region_object_for_context (location_t loc,
					const content_renderer *snippet_renderer) const;
  std::unique_ptr<sarif_region>
  make_region_object_for_hint (const fixit_hint &hint) const;
  std::unique_ptr<sarif_multiformat_message_string>
  make_multiformat_message_string (const char *msg) const;
  std::unique_ptr<sarif_log>
  make_top_level_object (std::unique_ptr<sarif_invocation> invocation_obj,
			 std::unique_ptr<json::array> results);
  std::unique_ptr<sarif_run>
  make_run_object (std::unique_ptr<sarif_invocation> invocation_obj,
		   std::unique_ptr<json::array> results);
  std::unique_ptr<sarif_tool>
  make_tool_object ();
  std::unique_ptr<sarif_tool_component>
  make_driver_tool_component_object ();
  std::unique_ptr<json::array> maybe_make_taxonomies_array () const;
  std::unique_ptr<sarif_tool_component>
  maybe_make_cwe_taxonomy_object () const;
  std::unique_ptr<sarif_tool_component_reference>
  make_tool_component_reference_object_for_cwe () const;
  std::unique_ptr<sarif_reporting_descriptor>
  make_reporting_descriptor_object_for_warning (diagnostic_context &context,
						const diagnostic_info &diagnostic,
						diagnostic_t orig_diag_kind,
						const char *option_text);
  std::unique_ptr<sarif_reporting_descriptor>
  make_reporting_descriptor_object_for_cwe_id (int cwe_id) const;
  std::unique_ptr<sarif_reporting_descriptor_reference>
  make_reporting_descriptor_reference_object_for_cwe_id (int cwe_id);
  sarif_artifact &
  get_or_create_artifact (const char *filename,
			  enum diagnostic_artifact_role role,
			  bool embed_contents);
  char *
  get_source_lines (const char *filename,
		    int start_line,
		    int end_line) const;
  std::unique_ptr<sarif_artifact_content>
  maybe_make_artifact_content_object (const char *filename,
				      int start_line,
				      int end_line,
				      const content_renderer *r) const;
  std::unique_ptr<sarif_fix>
  make_fix_object (const rich_location &rich_loc);
  std::unique_ptr<sarif_artifact_change>
  make_artifact_change_object (const rich_location &richloc);
  std::unique_ptr<sarif_replacement>
  make_replacement_object (const fixit_hint &hint) const;
  std::unique_ptr<sarif_artifact_content>
  make_artifact_content_object (const char *text) const;
  int get_sarif_column (expanded_location exploc) const;

  diagnostic_context &m_context;
  const line_maps *m_line_maps;

  /* The JSON object for the invocation object.  */
  std::unique_ptr<sarif_invocation> m_invocation_obj;

  /* The JSON array of pending diagnostics.  */
  std::unique_ptr<json::array> m_results_array;

  /* The JSON object for the result object (if any) in the current
     diagnostic group.  */
  std::unique_ptr<sarif_result> m_cur_group_result;

  /* Ideally we'd use std::unique_ptr<sarif_artifact> here, but I had
     trouble getting this to work when building with GCC 4.8.  */
  ordered_hash_map <nofree_string_hash,
		    sarif_artifact *> m_filename_to_artifact_map;

  bool m_seen_any_relative_paths;
  hash_set <free_string_hash> m_rule_id_set;
  std::unique_ptr<json::array> m_rules_arr;

  /* The set of all CWE IDs we've seen, if any.  */
  hash_set <int_hash <int, 0, 1> > m_cwe_id_set;

  int m_tabstop;

  bool m_formatted;
};

/* class sarif_object : public json::object.  */

sarif_property_bag &
sarif_object::get_or_create_properties ()
{
  json::value *properties_val = get ("properties");
  if (properties_val)
    {
      if (properties_val->get_kind () == json::JSON_OBJECT)
	return *static_cast <sarif_property_bag *> (properties_val);
    }

  sarif_property_bag *bag = new sarif_property_bag ();
  set ("properties", bag);
  return *bag;
}

/* class sarif_invocation : public sarif_object.  */

sarif_invocation::sarif_invocation (sarif_builder &builder,
				    const char * const *original_argv)
: m_notifications_arr (::make_unique<json::array> ()),
  m_success (true)
{
  // "arguments" property (SARIF v2.1.0 section 3.20.2)
  if (original_argv)
    {
      auto arguments_arr = ::make_unique<json::array> ();
      for (size_t i = 0; original_argv[i]; ++i)
	arguments_arr->append_string (original_argv[i]);
      set<json::array> ("arguments", std::move (arguments_arr));
    }

  // "workingDirectory" property (SARIF v2.1.0 section 3.20.19)
  if (const char *pwd = getpwd ())
    set<sarif_artifact_location> ("workingDirectory",
				  builder.make_artifact_location_object (pwd));

  // "startTimeUtc" property (SARIF v2.1.0 section 3.20.7)
  set<json::string> ("startTimeUtc",
		     make_date_time_string_for_current_time ());
}

/* Handle an internal compiler error DIAGNOSTIC occurring on CONTEXT.
   Add an object representing the ICE to the notifications array.  */

void
sarif_invocation::add_notification_for_ice (diagnostic_context &context,
					    const diagnostic_info &diagnostic,
					    sarif_builder &builder)
{
  m_success = false;

  m_notifications_arr->append<sarif_ice_notification>
    (::make_unique<sarif_ice_notification> (context, diagnostic, builder));
}

void
sarif_invocation::prepare_to_flush (diagnostic_context &context)
{
  /* "executionSuccessful" property (SARIF v2.1.0 section 3.20.14).  */
  if (context.execution_failed_p ())
    m_success = false;
  set_bool ("executionSuccessful", m_success);

  /* "toolExecutionNotifications" property (SARIF v2.1.0 section 3.20.21).  */
  set ("toolExecutionNotifications", std::move (m_notifications_arr));

  /* Call client hook, allowing it to create a custom property bag for
     this object (SARIF v2.1.0 section 3.8) e.g. for recording time vars.  */
  if (auto client_data_hooks = context.get_client_data_hooks ())
    client_data_hooks->add_sarif_invocation_properties (*this);

  // "endTimeUtc" property (SARIF v2.1.0 section 3.20.8);
  set<json::string> ("endTimeUtc",
		     make_date_time_string_for_current_time ());
}

/* class sarif_artifact : public sarif_object.  */

/* Add ROLE to this artifact's roles.
   If EMBED_CONTENTS is true, then flag that we will attempt to embed the
   contents of this artifact when writing it out.  */

void
sarif_artifact::add_role (enum diagnostic_artifact_role role,
			  bool embed_contents)
{
  /* TODO(SARIF 2.2): "scannedFile" is to be added as a role in SARIF 2.2;
     see https://github.com/oasis-tcs/sarif-spec/issues/459

     For now, skip them.
     Ultimately, we probably shouldn't bother embedding the contents
     of such artifacts, just the snippets.  */
  if (role == diagnostic_artifact_role::scanned_file)
    return;

  if (embed_contents)
    m_embed_contents = true;

  /* In SARIF v2.1.0 section 3.24.6 "roles" property:
     "resultFile" is for an artifact
     "which the analysis tool was not explicitly instructed to scan",
     whereas "analysisTarget" is for one where the
     "analysis tool was instructed to scan this artifact".
     Hence the latter excludes the former.  */
  if (role == diagnostic_artifact_role::result_file)
    if (bitmap_bit_p (m_roles, (int)diagnostic_artifact_role::analysis_target))
	return;

  bitmap_set_bit (m_roles, (int)role);
}

/* Populate the "contents" property (SARIF v2.1.0 section 3.24.8).
   We do this after initialization to
   (a) ensure that any charset options have been set
   (b) only populate it for artifacts that are referenced by a location.  */

void
sarif_artifact::populate_contents (sarif_builder &builder)
{
  if (auto artifact_content_obj
	= builder.maybe_make_artifact_content_object (m_filename))
    set<sarif_artifact_content> ("contents", std::move (artifact_content_obj));
}

/* Get a string for ROLE corresponding to the
   SARIF v2.1.0 section 3.24.6 "roles" property.  */

static const char *
get_artifact_role_string (enum diagnostic_artifact_role role)
{
  switch (role)
    {
    default:
      gcc_unreachable ();
    case diagnostic_artifact_role::analysis_target:
      return "analysisTarget";
    case diagnostic_artifact_role::debug_output_file:
      return "debugOutputFile";
    case diagnostic_artifact_role::result_file:
      return "resultFile";
    case diagnostic_artifact_role::scanned_file:
      return "scannedFile";
    case diagnostic_artifact_role::traced_file:
      return "tracedFile";
    }
}

/* Populate the "roles" property of this sarif_artifact with a new
   json::array for the artifact.roles property (SARIF v2.1.0 section 3.24.6)
   containing strings such as "analysisTarget", "resultFile"
   and/or "tracedFile".  */

void
sarif_artifact::populate_roles ()
{
  if (bitmap_empty_p (m_roles))
    return;
  auto roles_arr (::make_unique<json::array> ());
  for (int i = 0; i < (int)diagnostic_artifact_role::NUM_ROLES; i++)
    if (bitmap_bit_p (m_roles, i))
      {
	enum diagnostic_artifact_role role = (enum diagnostic_artifact_role)i;
	roles_arr->append_string (get_artifact_role_string (role));
      }
  set<json::array> ("roles", std::move (roles_arr));
}

/* class sarif_location_manager : public sarif_object.  */

void
sarif_location_manager::
add_relationship_to_worklist (sarif_location &location_obj,
			      enum worklist_item::kind kind,
			      location_t where)
{
  m_worklist.push_back (worklist_item (location_obj,
				       kind,
				       where));
}

/* Process all items in this result's worklist.
   Doing so may temporarily add new items to the end
   of the worklist.
   Handling any item should be "lazy", and thus we should
   eventually drain the queue and terminate.  */

void
sarif_location_manager::process_worklist (sarif_builder &builder)
{
  while (!m_worklist.empty ())
    {
      const worklist_item &item = m_worklist.front ();
      process_worklist_item (builder, item);
      m_worklist.pop_front ();
    }
}

/* Process one item in this result's worklist, potentially
   adding new items to the end of the worklist.  */

void
sarif_location_manager::process_worklist_item (sarif_builder &builder,
					       const worklist_item &item)
{
  switch (item.m_kind)
    {
    default:
      gcc_unreachable ();
    case worklist_item::kind::included_from:
      {
	sarif_location &included_loc_obj = item.m_location_obj;
	sarif_location *includer_loc_obj = nullptr;
	auto iter = m_included_from_locations.find (item.m_where);
	if (iter != m_included_from_locations.end ())
	  includer_loc_obj = iter->second;
	else
	  {
	    std::unique_ptr<sarif_location> new_loc_obj
	      = builder.make_location_object
		  (*this,
		   item.m_where,
		   diagnostic_artifact_role::scanned_file);
	    includer_loc_obj = new_loc_obj.get ();
	    add_related_location (std::move (new_loc_obj));
	    auto kv
	      = std::pair<location_t, sarif_location *> (item.m_where,
							 includer_loc_obj);
	    m_included_from_locations.insert (kv);
	  }

	includer_loc_obj->lazily_add_relationship
	  (included_loc_obj,
	   location_relationship_kind::includes,
	   *this);
	included_loc_obj.lazily_add_relationship
	  (*includer_loc_obj,
	   location_relationship_kind::is_included_by,
	   *this);
      }
      break;
    case worklist_item::kind::unlabelled_secondary_location:
      {
	sarif_location &primary_loc_obj = item.m_location_obj;
	sarif_location *secondary_loc_obj = nullptr;
	auto iter = m_unlabelled_secondary_locations.find (item.m_where);
	if (iter != m_unlabelled_secondary_locations.end ())
	  secondary_loc_obj = iter->second;
	else
	  {
	    std::unique_ptr<sarif_location> new_loc_obj
	      = builder.make_location_object
		  (*this,
		   item.m_where,
		   diagnostic_artifact_role::scanned_file);
	    secondary_loc_obj = new_loc_obj.get ();
	    add_related_location (std::move (new_loc_obj));
	    auto kv
	      = std::pair<location_t, sarif_location *> (item.m_where,
							 secondary_loc_obj);
	    m_unlabelled_secondary_locations.insert (kv);
	  }
	gcc_assert (secondary_loc_obj);
	primary_loc_obj.lazily_add_relationship
	  (*secondary_loc_obj,
	   location_relationship_kind::relevant,
	   *this);
      }
      break;
    }
}

/* class sarif_result : public sarif_location_manager.  */

/* Handle secondary diagnostics that occur within a diagnostic group.
   The closest SARIF seems to have to nested diagnostics is the
   "relatedLocations" property of result objects (SARIF v2.1.0 section 3.27.22),
   so we lazily set this property and populate the array if and when
   secondary diagnostics occur (such as notes to a warning).  */

void
sarif_result::on_nested_diagnostic (diagnostic_context &context,
				    const diagnostic_info &diagnostic,
				    diagnostic_t /*orig_diag_kind*/,
				    sarif_builder &builder)
{
  /* We don't yet generate meaningful logical locations for notes;
     sometimes these will related to current_function_decl, but
     often they won't.  */
  auto location_obj
    = builder.make_location_object (*this, *diagnostic.richloc, nullptr,
				    diagnostic_artifact_role::result_file);
  auto message_obj
    = builder.make_message_object (pp_formatted_text (context.printer));
  pp_clear_output_area (context.printer);
  location_obj->set<sarif_message> ("message", std::move (message_obj));

  add_related_location (std::move (location_obj));
}

/* Handle diagrams that occur within a diagnostic group.
   The closest thing in SARIF seems to be to add a location to the
   "releatedLocations" property  (SARIF v2.1.0 section 3.27.22),
   and to put the diagram into the "message" property of that location
   (SARIF v2.1.0 section 3.28.5).  */

void
sarif_result::on_diagram (diagnostic_context &context,
			  const diagnostic_diagram &diagram,
			  sarif_builder &builder)
{
  auto location_obj = ::make_unique<sarif_location> ();
  auto message_obj
    = builder.make_message_object_for_diagram (context, diagram);
  location_obj->set<sarif_message> ("message", std::move (message_obj));

  add_related_location (std::move (location_obj));
}

/* Implementation of sarif_location_manager::add_related_location vfunc
   for result objects.

   Add LOCATION_OBJ to this result's "relatedLocations" array,
   creating it if it doesn't yet exist.  */

void
sarif_result::
add_related_location (std::unique_ptr<sarif_location> location_obj)
{
  if (!m_related_locations_arr)
    {
      m_related_locations_arr = new json::array ();
      /* Give ownership of m_related_locations_arr to json::object;
	 keep a borrowed ptr.  */
      set ("relatedLocations", m_related_locations_arr);
    }
  m_related_locations_arr->append (std::move (location_obj));
}

/* class sarif_location : public sarif_object.  */

/* Ensure this location has an "id" and return it.
   Use LOC_MGR if an id needs to be allocated.

   See the "id" property (3.28.2).

   We use this to only assign ids to locations that are
   referenced by another sarif object; others have no "id".   */

long
sarif_location::lazily_add_id (sarif_location_manager &loc_mgr)
{
  long id = get_id ();
  if (id != -1)
    return id;
  id = loc_mgr.allocate_location_id ();
  set_integer ("id", id);
  gcc_assert (id != -1);
  return id;
}

/* Get the id of this location, or -1 if it doesn't have one.  */

long
sarif_location::get_id () const
{
  json::value *id = get ("id");
  if (!id)
    return -1;
  gcc_assert (id->get_kind () == json::JSON_INTEGER);
  return static_cast <json::integer_number *> (id)->get ();
}

// 3.34.3 kinds property
static const char *
get_string_for_location_relationship_kind (enum location_relationship_kind kind)
{
  switch (kind)
    {
    default:
      gcc_unreachable ();
    case location_relationship_kind::includes:
      return "includes";
    case location_relationship_kind::is_included_by:
      return "isIncludedBy";
    case location_relationship_kind::relevant:
      return "relevant";
    }
}

/* Lazily populate this location's "relationships" property (3.28.7)
   with the relationship of KIND to TARGET, creating objects
   as necessary.
   Use LOC_MGR for any locations that need "id" values.  */

void
sarif_location::lazily_add_relationship (sarif_location &target,
					 enum location_relationship_kind kind,
					 sarif_location_manager &loc_mgr)
{
  sarif_location_relationship &relationship_obj
    = lazily_add_relationship_object (target, loc_mgr);

  relationship_obj.lazily_add_kind (kind);
}

/* Lazily populate this location's "relationships" property (3.28.7)
   with a location_relationship to TARGET, creating objects
   as necessary.
   Use LOC_MGR for any locations that need "id" values.  */

sarif_location_relationship &
sarif_location::lazily_add_relationship_object (sarif_location &target,
						sarif_location_manager &loc_mgr)
{
  /* See if THIS already has a locationRelationship referencing TARGET.  */
  auto iter = m_relationships_map.find (&target);
  if (iter != m_relationships_map.end ())
    {
      /* We already have a locationRelationship from THIS to TARGET.  */
      sarif_location_relationship *relationship = iter->second;
      gcc_assert (relationship->get_target_id() == target.get_id ());
      return *relationship;
    }

  // Ensure that THIS has a "relationships" property (3.28.7).
  json::array &relationships_arr = lazily_add_relationships_array ();

  /* No existing locationRelationship from THIS to TARGET; make one,
     record it, and add it to the "relationships" array.  */
  auto relationship_obj
    = ::make_unique<sarif_location_relationship> (target, loc_mgr);
  sarif_location_relationship *relationship = relationship_obj.get ();
  auto kv
    = std::pair<sarif_location *,
		sarif_location_relationship *> (&target, relationship);
  m_relationships_map.insert (kv);

  relationships_arr.append (std::move (relationship_obj));

  return *relationship;
}

/* Ensure this location has a "relationships" array (3.28.7).  */

json::array &
sarif_location::lazily_add_relationships_array ()
{
  const char *const property_name = "relationships";
  if (json::value *relationships = get (property_name))
    {
      gcc_assert (relationships->get_kind () == json::JSON_ARRAY);
      return *static_cast <json::array *> (relationships);
    }
  json::array *relationships_arr = new json::array ();
  set (property_name, relationships_arr);
  return *relationships_arr;
}

/* class sarif_ice_notification : public sarif_location_manager.  */

/* sarif_ice_notification's ctor.
   DIAGNOSTIC is an internal compiler error.  */

sarif_ice_notification::
sarif_ice_notification (diagnostic_context &context,
			const diagnostic_info &diagnostic,
			sarif_builder &builder)
{
  /* "locations" property (SARIF v2.1.0 section 3.58.4).  */
  auto locations_arr
    = builder.make_locations_arr (*this,
				  diagnostic,
				  diagnostic_artifact_role::result_file);
  set<json::array> ("locations", std::move (locations_arr));

  /* "message" property (SARIF v2.1.0 section 3.85.5).  */
  auto message_obj
    = builder.make_message_object (pp_formatted_text (context.printer));
  pp_clear_output_area (context.printer);
  set<sarif_message> ("message", std::move (message_obj));

  /* "level" property (SARIF v2.1.0 section 3.58.6).  */
  set_string ("level", "error");
}

/* Implementation of sarif_location_manager::add_related_location vfunc
   for notifications.  */

void
sarif_ice_notification::
add_related_location (std::unique_ptr<sarif_location> location_obj)
{
  /* TODO(SARIF 2.2): see https://github.com/oasis-tcs/sarif-spec/issues/540
     For now, discard all related locations within a notification.  */
  location_obj = nullptr;
}

/* class sarif_location_relationship : public sarif_object.  */

sarif_location_relationship::
sarif_location_relationship (sarif_location &target,
			     sarif_location_manager &loc_mgr)
: m_kinds ((unsigned)location_relationship_kind::NUM_KINDS)
{
  bitmap_clear (m_kinds);
  set_integer ("target", target.lazily_add_id (loc_mgr));
}

long
sarif_location_relationship::get_target_id () const
{
  json::value *id = get ("id");
  gcc_assert (id);
  return static_cast <json::integer_number *> (id)->get ();
}

void
sarif_location_relationship::
lazily_add_kind (enum location_relationship_kind kind)
{
  if (bitmap_bit_p (m_kinds, (int)kind))
    return; // already have this kind
  bitmap_set_bit (m_kinds, (int)kind);

  // 3.34.3 kinds property
  json::array *kinds_arr = nullptr;
  if (json::value *kinds_val = get ("kinds"))
    {
      gcc_assert (kinds_val->get_kind () == json::JSON_ARRAY);
    }
  else
    {
      kinds_arr = new json::array ();
      set ("kinds", kinds_arr);
    }
  const char *kind_str = get_string_for_location_relationship_kind (kind);
  kinds_arr->append_string (kind_str);
}

/* class sarif_thread_flow : public sarif_object.  */

sarif_thread_flow::sarif_thread_flow (const diagnostic_thread &thread)
{
  /* "id" property (SARIF v2.1.0 section 3.37.2).  */
  label_text name (thread.get_name (false));
  set_string ("id", name.get ());

  /* "locations" property (SARIF v2.1.0 section 3.37.6).  */
  m_locations_arr = new json::array ();

  /* Give ownership of m_locations_arr to json::object;
     keep a borrowed ptr.  */
  set ("locations", m_locations_arr);
}

void
sarif_thread_flow::
add_location (std::unique_ptr<sarif_thread_flow_location> thread_flow_loc_obj)
{
  m_locations_arr->append (std::move (thread_flow_loc_obj));
}

/* class sarif_builder.  */

/* sarif_builder's ctor.  */

sarif_builder::sarif_builder (diagnostic_context &context,
			      const line_maps *line_maps,
			      const char *main_input_filename_,
			      bool formatted)
: m_context (context),
  m_line_maps (line_maps),
  m_invocation_obj
    (::make_unique<sarif_invocation> (*this,
				      context.get_original_argv ())),
  m_results_array (new json::array ()),
  m_cur_group_result (nullptr),
  m_seen_any_relative_paths (false),
  m_rule_id_set (),
  m_rules_arr (new json::array ()),
  m_tabstop (context.m_tabstop),
  m_formatted (formatted)
{
  gcc_assert (m_line_maps);

  /* Mark MAIN_INPUT_FILENAME_ as the artifact that the tool was
     instructed to scan.
     Only quote the contents if it gets referenced by physical locations,
     since otherwise the "no diagnostics" case would quote the main input
     file, and doing so noticeably bloated the output seen in analyzer
     integration testing (build directory went from 20G -> 21G).  */
  get_or_create_artifact (main_input_filename_,
			  diagnostic_artifact_role::analysis_target,
			  false);
}

/* Implementation of "end_diagnostic" for SARIF output.  */

void
sarif_builder::end_diagnostic (diagnostic_context &context,
			       const diagnostic_info &diagnostic,
			       diagnostic_t orig_diag_kind)
{
  if (diagnostic.kind == DK_ICE || diagnostic.kind == DK_ICE_NOBT)
    {
      m_invocation_obj->add_notification_for_ice (context, diagnostic, *this);
      return;
    }

  if (m_cur_group_result)
    /* Nested diagnostic.  */
    m_cur_group_result->on_nested_diagnostic (context,
					      diagnostic,
					      orig_diag_kind,
					      *this);
  else
    {
      /* Top-level diagnostic.  */
      m_cur_group_result
	= make_result_object (context, diagnostic, orig_diag_kind);
    }
}

/* Implementation of diagnostic_context::m_diagrams.m_emission_cb
   for SARIF output.  */

void
sarif_builder::emit_diagram (diagnostic_context &context,
			     const diagnostic_diagram &diagram)
{
  /* We must be within the emission of a top-level diagnostic.  */
  gcc_assert (m_cur_group_result);
  m_cur_group_result->on_diagram (context, diagram, *this);
}

/* Implementation of "end_group_cb" for SARIF output.  */

void
sarif_builder::end_group ()
{
  if (m_cur_group_result)
    {
      m_cur_group_result->process_worklist (*this);
      m_results_array->append<sarif_result> (std::move (m_cur_group_result));
    }
}

/* Create a top-level object, and add it to all the results
   (and other entities) we've seen so far, moving ownership
   to the object.  */

std::unique_ptr<sarif_log>
sarif_builder::flush_to_object ()
{
  m_invocation_obj->prepare_to_flush (m_context);
  std::unique_ptr<sarif_log> top
    = make_top_level_object (std::move (m_invocation_obj),
			     std::move (m_results_array));
  return top;
}

/* Create a top-level object, and add it to all the results
   (and other entities) we've seen so far.

   Flush it all to OUTF.  */

void
sarif_builder::flush_to_file (FILE *outf)
{
  std::unique_ptr<sarif_log> top = flush_to_object ();
  top->dump (outf, m_formatted);
  fprintf (outf, "\n");
}

/* Attempt to convert DIAG_KIND to a suitable value for the "level"
   property (SARIF v2.1.0 section 3.27.10).

   Return nullptr if there isn't one.  */

static const char *
maybe_get_sarif_level (diagnostic_t diag_kind)
{
  switch (diag_kind)
    {
    case DK_WARNING:
      return "warning";
    case DK_ERROR:
      return "error";
    case DK_NOTE:
    case DK_ANACHRONISM:
      return "note";
    default:
      return nullptr;
    }
}

/* Make a string for DIAG_KIND suitable for use a ruleId
   (SARIF v2.1.0 section 3.27.5) as a fallback for when we don't
   have anything better to use.  */

static char *
make_rule_id_for_diagnostic_kind (diagnostic_t diag_kind)
{
  /* Lose the trailing ": ".  */
  const char *kind_text = get_diagnostic_kind_text (diag_kind);
  size_t len = strlen (kind_text);
  gcc_assert (len > 2);
  gcc_assert (kind_text[len - 2] == ':');
  gcc_assert (kind_text[len - 1] == ' ');
  char *rstrip = xstrdup (kind_text);
  rstrip[len - 2] = '\0';
  return rstrip;
}

/* Make a "result" object (SARIF v2.1.0 section 3.27) for DIAGNOSTIC.  */

std::unique_ptr<sarif_result>
sarif_builder::make_result_object (diagnostic_context &context,
				   const diagnostic_info &diagnostic,
				   diagnostic_t orig_diag_kind)
{
  auto result_obj = ::make_unique<sarif_result> ();

  /* "ruleId" property (SARIF v2.1.0 section 3.27.5).  */
  /* Ideally we'd have an option_name for these.  */
  if (char *option_text
	= context.make_option_name (diagnostic.option_index,
				    orig_diag_kind, diagnostic.kind))
    {
      /* Lazily create reportingDescriptor objects for and add to m_rules_arr.
	 Set ruleId referencing them.  */
      result_obj->set_string ("ruleId", option_text);
      if (m_rule_id_set.contains (option_text))
	free (option_text);
      else
	{
	  /* This is the first time we've seen this ruleId.  */
	  /* Add to set, taking ownership.  */
	  m_rule_id_set.add (option_text);

	  m_rules_arr->append<sarif_reporting_descriptor>
	    (make_reporting_descriptor_object_for_warning (context,
							   diagnostic,
							   orig_diag_kind,
							   option_text));
	}
    }
  else
    {
      /* Otherwise, we have an "error" or a stray "note"; use the
	 diagnostic kind as the ruleId, so that the result object at least
	 has a ruleId.
	 We don't bother creating reportingDescriptor objects for these.  */
      char *rule_id = make_rule_id_for_diagnostic_kind (orig_diag_kind);
      result_obj->set_string ("ruleId", rule_id);
      free (rule_id);
    }

  if (diagnostic.metadata)
    {
      /* "taxa" property (SARIF v2.1.0 section 3.27.8).  */
      if (int cwe_id = diagnostic.metadata->get_cwe ())
	{
	  auto taxa_arr = ::make_unique<json::array> ();
	  taxa_arr->append<sarif_reporting_descriptor_reference>
	    (make_reporting_descriptor_reference_object_for_cwe_id (cwe_id));
	  result_obj->set<json::array> ("taxa", std::move (taxa_arr));
	}

      diagnostic.metadata->maybe_add_sarif_properties (*result_obj);
    }

  /* "level" property (SARIF v2.1.0 section 3.27.10).  */
  if (const char *sarif_level = maybe_get_sarif_level (diagnostic.kind))
    result_obj->set_string ("level", sarif_level);

  /* "message" property (SARIF v2.1.0 section 3.27.11).  */
  auto message_obj
    = make_message_object (pp_formatted_text (context.printer));
  pp_clear_output_area (context.printer);
  result_obj->set<sarif_message> ("message", std::move (message_obj));

  /* "locations" property (SARIF v2.1.0 section 3.27.12).  */
  result_obj->set<json::array>
    ("locations",
     make_locations_arr (*result_obj.get (),
			 diagnostic,
			 diagnostic_artifact_role::result_file));

  /* "codeFlows" property (SARIF v2.1.0 section 3.27.18).  */
  if (const diagnostic_path *path = diagnostic.richloc->get_path ())
    {
      auto code_flows_arr = ::make_unique<json::array> ();
      code_flows_arr->append<sarif_code_flow>
	(make_code_flow_object (*result_obj.get (),
				*path));
      result_obj->set<json::array> ("codeFlows", std::move (code_flows_arr));
    }

  /* The "relatedLocations" property (SARIF v2.1.0 section 3.27.22) is
     set up later, if any nested diagnostics occur within this diagnostic
     group.  */

  /* "fixes" property (SARIF v2.1.0 section 3.27.30).  */
  const rich_location *richloc = diagnostic.richloc;
  if (richloc->get_num_fixit_hints ())
    {
      auto fix_arr = ::make_unique<json::array> ();
      fix_arr->append<sarif_fix> (make_fix_object (*richloc));
      result_obj->set<json::array> ("fixes", std::move (fix_arr));
    }

  return result_obj;
}

/* Make a "reportingDescriptor" object (SARIF v2.1.0 section 3.49)
   for a GCC warning.  */

std::unique_ptr<sarif_reporting_descriptor>
sarif_builder::
make_reporting_descriptor_object_for_warning (diagnostic_context &context,
					      const diagnostic_info &diagnostic,
					      diagnostic_t /*orig_diag_kind*/,
					      const char *option_text)
{
  auto reporting_desc = ::make_unique<sarif_reporting_descriptor> ();

  /* "id" property (SARIF v2.1.0 section 3.49.3).  */
  reporting_desc->set_string ("id", option_text);

  /* We don't implement "name" property (SARIF v2.1.0 section 3.49.7), since
     it seems redundant compared to "id".  */

  /* "helpUri" property (SARIF v2.1.0 section 3.49.12).  */
  if (char *option_url = context.make_option_url (diagnostic.option_index))
    {
      reporting_desc->set_string ("helpUri", option_url);
      free (option_url);
    }

  return reporting_desc;
}

/* Make a "reportingDescriptor" object (SARIF v2.1.0 section 3.49)
   for CWE_ID, for use within the CWE taxa array.  */

std::unique_ptr<sarif_reporting_descriptor>
sarif_builder::make_reporting_descriptor_object_for_cwe_id (int cwe_id) const
{
  auto reporting_desc = ::make_unique<sarif_reporting_descriptor> ();

  /* "id" property (SARIF v2.1.0 section 3.49.3).  */
  {
    pretty_printer pp;
    pp_printf (&pp, "%i", cwe_id);
    reporting_desc->set_string ("id", pp_formatted_text (&pp));
  }

  /* "helpUri" property (SARIF v2.1.0 section 3.49.12).  */
  {
    char *url = get_cwe_url (cwe_id);
    reporting_desc->set_string ("helpUri", url);
    free (url);
  }

  return reporting_desc;
}

/* Make a "reportingDescriptorReference" object (SARIF v2.1.0 section 3.52)
   referencing CWE_ID, for use within a result object.
   Also, add CWE_ID to m_cwe_id_set.  */

std::unique_ptr<sarif_reporting_descriptor_reference>
sarif_builder::
make_reporting_descriptor_reference_object_for_cwe_id (int cwe_id)
{
  auto desc_ref_obj = ::make_unique<sarif_reporting_descriptor_reference> ();

  /* "id" property (SARIF v2.1.0 section 3.52.4).  */
  {
    pretty_printer pp;
    pp_printf (&pp, "%i", cwe_id);
    desc_ref_obj->set_string ("id", pp_formatted_text (&pp));
  }

  /* "toolComponent" property (SARIF v2.1.0 section 3.52.7).  */
  desc_ref_obj->set<sarif_tool_component_reference>
    ("toolComponent", make_tool_component_reference_object_for_cwe ());

  /* Add CWE_ID to our set.  */
  gcc_assert (cwe_id > 0);
  m_cwe_id_set.add (cwe_id);

  return desc_ref_obj;
}

/* Make a "toolComponentReference" object (SARIF v2.1.0 section 3.54) that
   references the CWE taxonomy.  */

std::unique_ptr<sarif_tool_component_reference>
sarif_builder::
make_tool_component_reference_object_for_cwe () const
{
  auto comp_ref_obj = ::make_unique<sarif_tool_component_reference> ();

  /* "name" property  (SARIF v2.1.0 section 3.54.3).  */
  comp_ref_obj->set_string ("name", "cwe");

  return comp_ref_obj;
}

/* Make an array suitable for use as the "locations" property of:
   - a "result" object (SARIF v2.1.0 section 3.27.12), or
   - a "notification" object (SARIF v2.1.0 section 3.58.4).
   Use LOC_MGR for any locations that need "id" values.  */

std::unique_ptr<json::array>
sarif_builder::make_locations_arr (sarif_location_manager &loc_mgr,
				   const diagnostic_info &diagnostic,
				   enum diagnostic_artifact_role role)
{
  auto locations_arr = ::make_unique<json::array> ();
  const logical_location *logical_loc = nullptr;
  if (auto client_data_hooks = m_context.get_client_data_hooks ())
    logical_loc = client_data_hooks->get_current_logical_location ();

  auto location_obj
    = make_location_object (loc_mgr, *diagnostic.richloc, logical_loc, role);
  /* Don't add entirely empty location objects to the array.  */
  if (!location_obj->is_empty ())
    locations_arr->append<sarif_location> (std::move (location_obj));

  return locations_arr;
}

/* If LOGICAL_LOC is non-null, use it to create a "logicalLocations" property
   within LOCATION_OBJ (SARIF v2.1.0 section 3.28.4).  */

void
sarif_builder::
set_any_logical_locs_arr (sarif_location &location_obj,
			  const logical_location *logical_loc)
{
  if (!logical_loc)
    return;
  auto location_locs_arr = ::make_unique<json::array> ();
  location_locs_arr->append<sarif_logical_location>
    (make_sarif_logical_location_object (*logical_loc));
  location_obj.set<json::array> ("logicalLocations",
				 std::move (location_locs_arr));
}

/* Make a "location" object (SARIF v2.1.0 section 3.28) for RICH_LOC
   and LOGICAL_LOC.
   Use LOC_MGR for any locations that need "id" values, and for
   any worklist items.  */

std::unique_ptr<sarif_location>
sarif_builder::make_location_object (sarif_location_manager &loc_mgr,
				     const rich_location &rich_loc,
				     const logical_location *logical_loc,
				     enum diagnostic_artifact_role role)
{
  class escape_nonascii_renderer : public content_renderer
  {
  public:
    escape_nonascii_renderer (const rich_location &richloc,
			      enum diagnostics_escape_format escape_format)
    : m_richloc (richloc),
      m_escape_format (escape_format)
    {}

    std::unique_ptr<sarif_multiformat_message_string>
    render (const sarif_builder &builder) const final override
    {
      diagnostic_context dc;
      diagnostic_initialize (&dc, 0);
      dc.m_source_printing.enabled = true;
      dc.m_source_printing.colorize_source_p = false;
      dc.m_source_printing.show_labels_p = true;
      dc.m_source_printing.show_line_numbers_p = true;

      rich_location my_rich_loc (m_richloc);
      my_rich_loc.set_escape_on_output (true);

      dc.set_escape_format (m_escape_format);
      diagnostic_show_locus (&dc, &my_rich_loc, DK_ERROR);

      std::unique_ptr<sarif_multiformat_message_string> result
	= builder.make_multiformat_message_string
	    (pp_formatted_text (dc.printer));

      diagnostic_finish (&dc);

      return result;
    }
  private:
    const rich_location &m_richloc;
    enum diagnostics_escape_format m_escape_format;
  } the_renderer (rich_loc,
		  m_context.get_escape_format ());

  auto location_obj = ::make_unique<sarif_location> ();

  /* Get primary loc from RICH_LOC.  */
  location_t loc = rich_loc.get_loc ();

  /* "physicalLocation" property (SARIF v2.1.0 section 3.28.3).  */
  const content_renderer *snippet_renderer
    = rich_loc.escape_on_output_p () ? &the_renderer : nullptr;
  if (auto phs_loc_obj
	= maybe_make_physical_location_object (loc, role,
					       rich_loc.get_column_override (),
					       snippet_renderer))
    location_obj->set<sarif_physical_location> ("physicalLocation",
						std::move (phs_loc_obj));

  /* "logicalLocations" property (SARIF v2.1.0 section 3.28.4).  */
  set_any_logical_locs_arr (*location_obj, logical_loc);

  /* Handle labelled ranges and/or secondary locations.  */
  {
    std::unique_ptr<json::array> annotations_arr = nullptr;
    for (unsigned int i = 0; i < rich_loc.get_num_locations (); i++)
      {
	const location_range *range = rich_loc.get_range (i);
	bool handled = false;
	if (const range_label *label = range->m_label)
	  {
	    label_text text = label->get_text (i);
	    if (text.get ())
	      {
		/* Create annotations for any labelled ranges.  */
		location_t range_loc = rich_loc.get_loc (i);
		auto region
		  = maybe_make_region_object (range_loc,
					      rich_loc.get_column_override ());
		if (region)
		  {
		    if (!annotations_arr)
		      annotations_arr = ::make_unique<json::array> ();
		    region->set<sarif_message>
		      ("message", make_message_object (text.get ()));
		    annotations_arr->append<sarif_region> (std::move (region));
		    handled = true;
		  }
	      }
	  }

	/* Add related locations for any secondary locations in RICH_LOC
	   that don't have labels (and thus aren't added to "annotations"). */
	if (i > 0 && !handled)
	  loc_mgr.add_relationship_to_worklist
	    (*location_obj.get (),
	     sarif_location_manager::worklist_item::kind::unlabelled_secondary_location,
	     range->m_loc);
      }
    if (annotations_arr)
      /* "annotations" property (SARIF v2.1.0 section 3.28.6).  */
      location_obj->set<json::array> ("annotations",
				      std::move (annotations_arr));
  }

  add_any_include_chain (loc_mgr, *location_obj.get (), loc);

  /* A flag for hinting that the diagnostic involves issues at the
     level of character encodings (such as homoglyphs, or misleading
     bidirectional control codes), and thus that it will be helpful
     to the user if we show some representation of
     how the characters in the pertinent source lines are encoded.  */
  if (rich_loc.escape_on_output_p ())
    {
      sarif_property_bag &bag = location_obj->get_or_create_properties ();
      bag.set_bool ("gcc/escapeNonAscii", rich_loc.escape_on_output_p ());
    }

  return location_obj;
}

/* If WHERE was #included from somewhere, add a worklist item
   to LOC_MGR to lazily add a location for the #include location,
   and relationships between it and the LOCATION_OBJ.
   Compare with diagnostic_context::report_current_module, but rather
   than iterating the current chain, we add the next edge and iterate
   in the worklist, so that edges are only added once.  */

void
sarif_builder::add_any_include_chain (sarif_location_manager &loc_mgr,
				      sarif_location &location_obj,
				      location_t where)
{
  if (where <= BUILTINS_LOCATION)
    return;

  const line_map_ordinary *map = nullptr;
  linemap_resolve_location (m_line_maps, where,
			    LRK_MACRO_DEFINITION_LOCATION,
			    &map);

  if (!map)
    return;

  location_t include_loc = linemap_included_from (map);
  map = linemap_included_from_linemap (m_line_maps, map);
  if (!map)
    return;
  loc_mgr.add_relationship_to_worklist
    (location_obj,
     sarif_result::worklist_item::kind::included_from,
     include_loc);
}

/* Make a "location" object (SARIF v2.1.0 section 3.28) for WHERE
   within an include chain.  */

std::unique_ptr<sarif_location>
sarif_builder::make_location_object (sarif_location_manager &loc_mgr,
				     location_t loc,
				     enum diagnostic_artifact_role role)
{
  auto location_obj = ::make_unique<sarif_location> ();

  /* "physicalLocation" property (SARIF v2.1.0 section 3.28.3).  */
  if (auto phs_loc_obj
      = maybe_make_physical_location_object (loc, role, 0, nullptr))
    location_obj->set<sarif_physical_location> ("physicalLocation",
						std::move (phs_loc_obj));

  add_any_include_chain (loc_mgr, *location_obj.get (), loc);

  return location_obj;
}

/* Make a "location" object (SARIF v2.1.0 section 3.28) for EVENT
   within a diagnostic_path.  */

std::unique_ptr<sarif_location>
sarif_builder::make_location_object (sarif_location_manager &loc_mgr,
				     const diagnostic_event &event,
				     enum diagnostic_artifact_role role)
{
  auto location_obj = ::make_unique<sarif_location> ();

  /* "physicalLocation" property (SARIF v2.1.0 section 3.28.3).  */
  location_t loc = event.get_location ();
  if (auto phs_loc_obj
	= maybe_make_physical_location_object (loc, role, 0, nullptr))
    location_obj->set<sarif_physical_location> ("physicalLocation",
						std::move (phs_loc_obj));

  /* "logicalLocations" property (SARIF v2.1.0 section 3.28.4).  */
  const logical_location *logical_loc = event.get_logical_location ();
  set_any_logical_locs_arr (*location_obj, logical_loc);

  /* "message" property (SARIF v2.1.0 section 3.28.5).  */
  label_text ev_desc = event.get_desc (false);
  location_obj->set<sarif_message> ("message",
				    make_message_object (ev_desc.get ()));

  add_any_include_chain (loc_mgr, *location_obj.get (), loc);

  return location_obj;
}

/* Make a "physicalLocation" object (SARIF v2.1.0 section 3.29) for LOC.

   If COLUMN_OVERRIDE is non-zero, then use it as the column number
   if LOC has no column information.

   Ensure that we have an artifact object for the file, adding ROLE to it,
   and flagging that we will attempt to embed the contents of the artifact
   when writing it out.  */

std::unique_ptr<sarif_physical_location>
sarif_builder::
maybe_make_physical_location_object (location_t loc,
				     enum diagnostic_artifact_role role,
				     int column_override,
				     const content_renderer *snippet_renderer)
{
  if (loc <= BUILTINS_LOCATION || LOCATION_FILE (loc) == nullptr)
    return nullptr;

  auto phys_loc_obj = ::make_unique<sarif_physical_location> ();

  /* "artifactLocation" property (SARIF v2.1.0 section 3.29.3).  */
  phys_loc_obj->set<sarif_artifact_location>
    ("artifactLocation", make_artifact_location_object (loc));
  get_or_create_artifact (LOCATION_FILE (loc), role, true);

  /* "region" property (SARIF v2.1.0 section 3.29.4).  */
  if (auto region_obj = maybe_make_region_object (loc, column_override))
    phys_loc_obj->set<sarif_region> ("region", std::move (region_obj));

  /* "contextRegion" property (SARIF v2.1.0 section 3.29.5).  */
  if (auto context_region_obj
	= maybe_make_region_object_for_context (loc, snippet_renderer))
    phys_loc_obj->set<sarif_region> ("contextRegion",
				     std::move (context_region_obj));

  /* Instead, we add artifacts to the run as a whole,
     with artifact.contents.
     Could do both, though.  */

  return phys_loc_obj;
}

/* Make an "artifactLocation" object (SARIF v2.1.0 section 3.4) for LOC,
   or return nullptr.  */

std::unique_ptr<sarif_artifact_location>
sarif_builder::make_artifact_location_object (location_t loc)
{
  return make_artifact_location_object (LOCATION_FILE (loc));
}

/* The ID value for use in "uriBaseId" properties (SARIF v2.1.0 section 3.4.4)
   for when we need to express paths relative to PWD.  */

#define PWD_PROPERTY_NAME ("PWD")

/* Make an "artifactLocation" object (SARIF v2.1.0 section 3.4) for FILENAME,
   or return nullptr.  */

std::unique_ptr<sarif_artifact_location>
sarif_builder::make_artifact_location_object (const char *filename)
{
  auto artifact_loc_obj = ::make_unique<sarif_artifact_location> ();

  /* "uri" property (SARIF v2.1.0 section 3.4.3).  */
  artifact_loc_obj->set_string ("uri", filename);

  if (filename[0] != '/')
    {
      /* If we have a relative path, set the "uriBaseId" property
	 (SARIF v2.1.0 section 3.4.4).  */
      artifact_loc_obj->set_string ("uriBaseId", PWD_PROPERTY_NAME);
      m_seen_any_relative_paths = true;
    }

  return artifact_loc_obj;
}

/* Get the PWD, or nullptr, as an absolute file-based URI,
   adding a trailing forward slash (as required by SARIF v2.1.0
   section 3.14.14).  */

static char *
make_pwd_uri_str ()
{
  /* The prefix of a file-based URI, up to, but not including the path. */
#define FILE_PREFIX ("file://")

  const char *pwd = getpwd ();
  if (!pwd)
    return nullptr;
  size_t len = strlen (pwd);
  if (len == 0 || pwd[len - 1] != '/')
    return concat (FILE_PREFIX, pwd, "/", nullptr);
  else
    {
      gcc_assert (pwd[len - 1] == '/');
      return concat (FILE_PREFIX, pwd, nullptr);
    }
}

/* Make an "artifactLocation" object (SARIF v2.1.0 section 3.4) for the pwd,
   for use in the "run.originalUriBaseIds" property (SARIF v2.1.0
   section 3.14.14) when we have any relative paths.  */

std::unique_ptr<sarif_artifact_location>
sarif_builder::make_artifact_location_object_for_pwd () const
{
  auto artifact_loc_obj = ::make_unique<sarif_artifact_location> ();

  /* "uri" property (SARIF v2.1.0 section 3.4.3).  */
  if (char *pwd = make_pwd_uri_str ())
    {
      gcc_assert (strlen (pwd) > 0);
      gcc_assert (pwd[strlen (pwd) - 1] == '/');
      artifact_loc_obj->set_string ("uri", pwd);
      free (pwd);
    }

  return artifact_loc_obj;
}

/* Get the column number within EXPLOC.  */

int
sarif_builder::get_sarif_column (expanded_location exploc) const
{
  cpp_char_column_policy policy (m_tabstop, cpp_wcwidth);
  return location_compute_display_column (m_context.get_file_cache (),
					  exploc, policy);
}

/* Make a "region" object (SARIF v2.1.0 section 3.30) for LOC,
   or return nullptr.

   If COLUMN_OVERRIDE is non-zero, then use it as the column number
   if LOC has no column information.  */

std::unique_ptr<sarif_region>
sarif_builder::maybe_make_region_object (location_t loc,
					 int column_override) const
{
  location_t caret_loc = get_pure_location (loc);

  if (caret_loc <= BUILTINS_LOCATION)
    return nullptr;

  location_t start_loc = get_start (loc);
  location_t finish_loc = get_finish (loc);

  expanded_location exploc_caret = expand_location (caret_loc);
  expanded_location exploc_start = expand_location (start_loc);
  expanded_location exploc_finish = expand_location (finish_loc);

  if (exploc_start.file !=exploc_caret.file)
    return nullptr;
  if (exploc_finish.file !=exploc_caret.file)
    return nullptr;

  auto region_obj = ::make_unique<sarif_region> ();

  /* "startLine" property (SARIF v2.1.0 section 3.30.5) */
  if (exploc_start.line > 0)
    region_obj->set_integer ("startLine", exploc_start.line);

  /* "startColumn" property (SARIF v2.1.0 section 3.30.6).

     We use column == 0 to mean the whole line, so omit the column
     information for this case, unless COLUMN_OVERRIDE is non-zero,
     (for handling certain awkward lexer diagnostics)  */

  if (exploc_start.column == 0 && column_override)
    /* Use the provided column number.  */
    exploc_start.column = column_override;

  if (exploc_start.column > 0)
    {
      int start_column = get_sarif_column (exploc_start);
      region_obj->set_integer ("startColumn", start_column);
    }

  /* "endLine" property (SARIF v2.1.0 section 3.30.7) */
  if (exploc_finish.line != exploc_start.line
      && exploc_finish.line > 0)
    region_obj->set_integer ("endLine", exploc_finish.line);

  /* "endColumn" property (SARIF v2.1.0 section 3.30.8).
     This expresses the column immediately beyond the range.

     We use column == 0 to mean the whole line, so omit the column
     information for this case.  */
  if (exploc_finish.column > 0)
    {
      int next_column = get_sarif_column (exploc_finish) + 1;
      region_obj->set_integer ("endColumn", next_column);
    }

  return region_obj;
}

/* Make a "region" object (SARIF v2.1.0 section 3.30) for the "contextRegion"
   property (SARIF v2.1.0 section 3.29.5) of a "physicalLocation".

   This is similar to maybe_make_region_object, but ignores column numbers,
   covering the line(s) as a whole, and including a "snippet" property
   embedding those source lines, making it easier for consumers to show
   the pertinent source.  */

std::unique_ptr<sarif_region>
sarif_builder::
maybe_make_region_object_for_context (location_t loc,
				      const content_renderer *snippet_renderer)
  const
{
  location_t caret_loc = get_pure_location (loc);

  if (caret_loc <= BUILTINS_LOCATION)
    return nullptr;

  location_t start_loc = get_start (loc);
  location_t finish_loc = get_finish (loc);

  expanded_location exploc_caret = expand_location (caret_loc);
  expanded_location exploc_start = expand_location (start_loc);
  expanded_location exploc_finish = expand_location (finish_loc);

  if (exploc_start.file !=exploc_caret.file)
    return nullptr;
  if (exploc_finish.file !=exploc_caret.file)
    return nullptr;

  auto region_obj = ::make_unique<sarif_region> ();

  /* "startLine" property (SARIF v2.1.0 section 3.30.5) */
  if (exploc_start.line > 0)
    region_obj->set_integer ("startLine", exploc_start.line);

  /* "endLine" property (SARIF v2.1.0 section 3.30.7) */
  if (exploc_finish.line != exploc_start.line
      && exploc_finish.line > 0)
    region_obj->set_integer ("endLine", exploc_finish.line);

  /* "snippet" property (SARIF v2.1.0 section 3.30.13).  */
  if (auto artifact_content_obj
	= maybe_make_artifact_content_object (exploc_start.file,
					      exploc_start.line,
					      exploc_finish.line,
					      snippet_renderer))
    region_obj->set<sarif_artifact_content> ("snippet",
					     std::move (artifact_content_obj));

  return region_obj;
}

/* Make a "region" object (SARIF v2.1.0 section 3.30) for the deletion region
   of HINT (as per SARIF v2.1.0 section 3.57.3).  */

std::unique_ptr<sarif_region>
sarif_builder::make_region_object_for_hint (const fixit_hint &hint) const
{
  location_t start_loc = hint.get_start_loc ();
  location_t next_loc = hint.get_next_loc ();

  expanded_location exploc_start = expand_location (start_loc);
  expanded_location exploc_next = expand_location (next_loc);

  auto region_obj = ::make_unique<sarif_region> ();

  /* "startLine" property (SARIF v2.1.0 section 3.30.5) */
  region_obj->set_integer ("startLine", exploc_start.line);

  /* "startColumn" property (SARIF v2.1.0 section 3.30.6) */
  int start_col = get_sarif_column (exploc_start);
  region_obj->set_integer ("startColumn", start_col);

  /* "endLine" property (SARIF v2.1.0 section 3.30.7) */
  if (exploc_next.line != exploc_start.line)
    region_obj->set_integer ("endLine", exploc_next.line);

  /* "endColumn" property (SARIF v2.1.0 section 3.30.8).
     This expresses the column immediately beyond the range.  */
  int next_col =  get_sarif_column (exploc_next);
  region_obj->set_integer ("endColumn", next_col);

  return region_obj;
}

/* Attempt to get a string for a logicalLocation's "kind" property
   (SARIF v2.1.0 section 3.33.7).
   Return nullptr if unknown.  */

static const char *
maybe_get_sarif_kind (enum logical_location_kind kind)
{
  switch (kind)
    {
    default:
      gcc_unreachable ();
    case LOGICAL_LOCATION_KIND_UNKNOWN:
      return nullptr;

    case LOGICAL_LOCATION_KIND_FUNCTION:
      return "function";
    case LOGICAL_LOCATION_KIND_MEMBER:
      return "member";
    case LOGICAL_LOCATION_KIND_MODULE:
      return "module";
    case LOGICAL_LOCATION_KIND_NAMESPACE:
      return "namespace";
    case LOGICAL_LOCATION_KIND_TYPE:
      return "type";
    case LOGICAL_LOCATION_KIND_RETURN_TYPE:
      return "returnType";
    case LOGICAL_LOCATION_KIND_PARAMETER:
      return "parameter";
    case LOGICAL_LOCATION_KIND_VARIABLE:
      return "variable";
    }
}

/* Make a "logicalLocation" object (SARIF v2.1.0 section 3.33) for LOGICAL_LOC,
   or return nullptr.  */

std::unique_ptr<sarif_logical_location>
make_sarif_logical_location_object (const logical_location &logical_loc)
{
  auto logical_loc_obj = ::make_unique<sarif_logical_location> ();

  /* "name" property (SARIF v2.1.0 section 3.33.4).  */
  if (const char *short_name = logical_loc.get_short_name ())
    logical_loc_obj->set_string ("name", short_name);

  /* "fullyQualifiedName" property (SARIF v2.1.0 section 3.33.5).  */
  if (const char *name_with_scope = logical_loc.get_name_with_scope ())
    logical_loc_obj->set_string ("fullyQualifiedName", name_with_scope);

  /* "decoratedName" property (SARIF v2.1.0 section 3.33.6).  */
  if (const char *internal_name = logical_loc.get_internal_name ())
    logical_loc_obj->set_string ("decoratedName", internal_name);

  /* "kind" property (SARIF v2.1.0 section 3.33.7).  */
  enum logical_location_kind kind = logical_loc.get_kind ();
  if (const char *sarif_kind_str = maybe_get_sarif_kind (kind))
    logical_loc_obj->set_string ("kind", sarif_kind_str);

  return logical_loc_obj;
}

/* Make a "codeFlow" object (SARIF v2.1.0 section 3.36) for PATH.  */

std::unique_ptr<sarif_code_flow>
sarif_builder::make_code_flow_object (sarif_result &result,
				      const diagnostic_path &path)
{
  auto code_flow_obj = ::make_unique <sarif_code_flow> ();

  /* "threadFlows" property (SARIF v2.1.0 section 3.36.3).  */
  auto thread_flows_arr = ::make_unique<json::array> ();

  /* Walk the events, consolidating into per-thread threadFlow objects,
     using the index with PATH as the overall executionOrder.  */
  hash_map<int_hash<diagnostic_thread_id_t, -1, -2>,
	   sarif_thread_flow *> thread_id_map; // borrowed
  for (unsigned i = 0; i < path.num_events (); i++)
    {
      const diagnostic_event &event = path.get_event (i);
      const diagnostic_thread_id_t thread_id = event.get_thread_id ();
      sarif_thread_flow *thread_flow_obj;

      if (sarif_thread_flow **slot = thread_id_map.get (thread_id))
	thread_flow_obj = *slot;
      else
	{
	  const diagnostic_thread &thread = path.get_thread (thread_id);
	  thread_flow_obj = new sarif_thread_flow (thread);
	  thread_id_map.put (thread_id, thread_flow_obj); // borrowed
	  thread_flows_arr->append (thread_flow_obj);
	}

      /* Add event to thread's threadFlow object.  */
      std::unique_ptr<sarif_thread_flow_location> thread_flow_loc_obj
	= make_thread_flow_location_object (result, event, i);
      thread_flow_obj->add_location (std::move (thread_flow_loc_obj));
    }
  code_flow_obj->set<json::array> ("threadFlows", std::move (thread_flows_arr));

  return code_flow_obj;
}

/* Make a "threadFlowLocation" object (SARIF v2.1.0 section 3.38) for EVENT.  */

std::unique_ptr<sarif_thread_flow_location>
sarif_builder::make_thread_flow_location_object (sarif_result &result,
						 const diagnostic_event &ev,
						 int path_event_idx)
{
  auto thread_flow_loc_obj = ::make_unique<sarif_thread_flow_location> ();

  /* Give diagnostic_event subclasses a chance to add custom properties
     via a property bag.  */
  ev.maybe_add_sarif_properties (*thread_flow_loc_obj);

  /* "location" property (SARIF v2.1.0 section 3.38.3).  */
  thread_flow_loc_obj->set<sarif_location>
    ("location",
     make_location_object (result, ev, diagnostic_artifact_role::traced_file));

  /* "kinds" property (SARIF v2.1.0 section 3.38.8).  */
  diagnostic_event::meaning m = ev.get_meaning ();
  if (auto kinds_arr = maybe_make_kinds_array (m))
    thread_flow_loc_obj->set<json::array> ("kinds", std::move (kinds_arr));

  /* "nestingLevel" property (SARIF v2.1.0 section 3.38.10).  */
  thread_flow_loc_obj->set_integer ("nestingLevel", ev.get_stack_depth ());

  /* "executionOrder" property (SARIF v2.1.0 3.38.11).
     Offset by 1 to match the human-readable values emitted by %@.  */
  thread_flow_loc_obj->set_integer ("executionOrder", path_event_idx + 1);

  /* It might be nice to eventually implement the following for -fanalyzer:
     - the "stack" property (SARIF v2.1.0 section 3.38.5)
     - the "state" property (SARIF v2.1.0 section 3.38.9)
     - the "importance" property (SARIF v2.1.0 section 3.38.13).  */

  return thread_flow_loc_obj;
}

/* If M has any known meaning, make a json array suitable for the "kinds"
   property of a "threadFlowLocation" object (SARIF v2.1.0 section 3.38.8).

   Otherwise, return nullptr.  */

std::unique_ptr<json::array>
sarif_builder::maybe_make_kinds_array (diagnostic_event::meaning m) const
{
  if (m.m_verb == diagnostic_event::VERB_unknown
      && m.m_noun == diagnostic_event::NOUN_unknown
      && m.m_property == diagnostic_event::PROPERTY_unknown)
    return nullptr;

  auto kinds_arr = ::make_unique<json::array> ();
  if (const char *verb_str
	= diagnostic_event::meaning::maybe_get_verb_str (m.m_verb))
    kinds_arr->append_string (verb_str);
  if (const char *noun_str
	= diagnostic_event::meaning::maybe_get_noun_str (m.m_noun))
    kinds_arr->append_string (noun_str);
  if (const char *property_str
	= diagnostic_event::meaning::maybe_get_property_str (m.m_property))
    kinds_arr->append_string (property_str);
  return kinds_arr;
}

/* Make a "message" object (SARIF v2.1.0 section 3.11) for MSG.  */

std::unique_ptr<sarif_message>
sarif_builder::make_message_object (const char *msg) const
{
  auto message_obj = ::make_unique<sarif_message> ();

  /* "text" property (SARIF v2.1.0 section 3.11.8).  */
  message_obj->set_string ("text", msg);

  return message_obj;
}

/* Make a "message" object (SARIF v2.1.0 section 3.11) for DIAGRAM.
   We emit the diagram as a code block within the Markdown part
   of the message.  */

std::unique_ptr<sarif_message>
sarif_builder::make_message_object_for_diagram (diagnostic_context &context,
						const diagnostic_diagram &diagram)
{
  auto message_obj = ::make_unique<sarif_message> ();

  /* "text" property (SARIF v2.1.0 section 3.11.8).  */
  message_obj->set_string ("text", diagram.get_alt_text ());

  char *saved_prefix = pp_take_prefix (context.printer);
  pp_set_prefix (context.printer, nullptr);

  /* "To produce a code block in Markdown, simply indent every line of
     the block by at least 4 spaces or 1 tab."
     Here we use 4 spaces.  */
  diagram.get_canvas ().print_to_pp (context.printer, "    ");
  pp_set_prefix (context.printer, saved_prefix);

  /* "markdown" property (SARIF v2.1.0 section 3.11.9).  */
  message_obj->set_string ("markdown", pp_formatted_text (context.printer));

  pp_clear_output_area (context.printer);

  return message_obj;
}

/* Make a "multiformatMessageString object" (SARIF v2.1.0 section 3.12)
   for MSG.  */

std::unique_ptr<sarif_multiformat_message_string>
sarif_builder::make_multiformat_message_string (const char *msg) const
{
  auto message_obj = ::make_unique<sarif_multiformat_message_string> ();

  /* "text" property (SARIF v2.1.0 section 3.12.3).  */
  message_obj->set_string ("text", msg);

  return message_obj;
}

#define SARIF_SCHEMA "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json"
#define SARIF_VERSION "2.1.0"

/* Make a top-level "sarifLog" object (SARIF v2.1.0 section 3.13).  */

std::unique_ptr<sarif_log>
sarif_builder::
make_top_level_object (std::unique_ptr<sarif_invocation> invocation_obj,
		       std::unique_ptr<json::array> results)
{
  auto log_obj = ::make_unique<sarif_log> ();

  /* "$schema" property (SARIF v2.1.0 section 3.13.3) .  */
  log_obj->set_string ("$schema", SARIF_SCHEMA);

  /* "version" property (SARIF v2.1.0 section 3.13.2).  */
  log_obj->set_string ("version", SARIF_VERSION);

  /* "runs" property (SARIF v2.1.0 section 3.13.4).  */
  auto run_arr = ::make_unique<json::array> ();
  auto run_obj = make_run_object (std::move (invocation_obj),
				  std::move (results));
  run_arr->append<sarif_run> (std::move (run_obj));
  log_obj->set<json::array> ("runs", std::move (run_arr));

  return log_obj;
}

/* Make a "run" object (SARIF v2.1.0 section 3.14).  */

std::unique_ptr<sarif_run>
sarif_builder::
make_run_object (std::unique_ptr<sarif_invocation> invocation_obj,
		 std::unique_ptr<json::array> results)
{
  auto run_obj = ::make_unique<sarif_run> ();

  /* "tool" property (SARIF v2.1.0 section 3.14.6).  */
  run_obj->set<sarif_tool> ("tool", make_tool_object ());

  /* "taxonomies" property (SARIF v2.1.0 section 3.14.8).  */
  if (auto taxonomies_arr = maybe_make_taxonomies_array ())
    run_obj->set<json::array> ("taxonomies", std::move (taxonomies_arr));

  /* "invocations" property (SARIF v2.1.0 section 3.14.11).  */
  {
    auto invocations_arr = ::make_unique<json::array> ();
    invocations_arr->append (std::move (invocation_obj));
    run_obj->set<json::array> ("invocations", std::move (invocations_arr));
  }

  /* "originalUriBaseIds (SARIF v2.1.0 section 3.14.14).  */
  if (m_seen_any_relative_paths)
    {
      auto orig_uri_base_ids = ::make_unique<json::object> ();
      orig_uri_base_ids->set<sarif_artifact_location>
	(PWD_PROPERTY_NAME, make_artifact_location_object_for_pwd ());
      run_obj->set<json::object> ("originalUriBaseIds",
				  std::move (orig_uri_base_ids));
    }

  /* "artifacts" property (SARIF v2.1.0 section 3.14.15).  */
  auto artifacts_arr = ::make_unique<json::array> ();
  for (auto iter : m_filename_to_artifact_map)
    {
      sarif_artifact *artifact_obj = iter.second;
      if (artifact_obj->embed_contents_p ())
	artifact_obj->populate_contents (*this);
      artifact_obj->populate_roles ();
      artifacts_arr->append (artifact_obj);
    }
  run_obj->set<json::array> ("artifacts", std::move (artifacts_arr));

  /* "results" property (SARIF v2.1.0 section 3.14.23).  */
  run_obj->set<json::array> ("results", std::move (results));

  return run_obj;
}

/* Make a "tool" object (SARIF v2.1.0 section 3.18).  */

std::unique_ptr<sarif_tool>
sarif_builder::make_tool_object ()
{
  auto tool_obj = ::make_unique<sarif_tool> ();

  /* "driver" property (SARIF v2.1.0 section 3.18.2).  */
  tool_obj->set<sarif_tool_component> ("driver",
				       make_driver_tool_component_object ());

  /* Report plugins via the "extensions" property
     (SARIF v2.1.0 section 3.18.3).  */
  if (auto client_data_hooks = m_context.get_client_data_hooks ())
    if (const client_version_info *vinfo
	  = client_data_hooks->get_any_version_info ())
      {
	class my_plugin_visitor : public client_version_info :: plugin_visitor
	{
	public:
	  void on_plugin (const diagnostic_client_plugin_info &p) final override
	  {
	    /* Create a "toolComponent" object (SARIF v2.1.0 section 3.19)
	       for the plugin.  */
	    auto plugin_obj = ::make_unique<sarif_tool_component> ();

	    /* "name" property (SARIF v2.1.0 section 3.19.8).  */
	    if (const char *short_name = p.get_short_name ())
	      plugin_obj->set_string ("name", short_name);

	    /* "fullName" property (SARIF v2.1.0 section 3.19.9).  */
	    if (const char *full_name = p.get_full_name ())
	      plugin_obj->set_string ("fullName", full_name);

	    /* "version" property (SARIF v2.1.0 section 3.19.13).  */
	    if (const char *version = p.get_version ())
	      plugin_obj->set_string ("version", version);

	    m_plugin_objs.push_back (std::move (plugin_obj));
	  }
	  std::vector<std::unique_ptr<sarif_tool_component>> m_plugin_objs;
	};
	my_plugin_visitor v;
	vinfo->for_each_plugin (v);
	if (v.m_plugin_objs.size () > 0)
	  {
	    auto extensions_arr = ::make_unique<json::array> ();
	    for (auto &iter : v.m_plugin_objs)
	      extensions_arr->append<sarif_tool_component> (std::move (iter));
	    tool_obj->set<json::array> ("extensions",
					std::move (extensions_arr));
	  }
      }

  /* Perhaps we could also show GMP, MPFR, MPC, isl versions as other
     "extensions" (see toplev.cc: print_version).  */

  return tool_obj;
}

/* Make a "toolComponent" object (SARIF v2.1.0 section 3.19) for what SARIF
   calls the "driver" (see SARIF v2.1.0 section 3.18.1).  */

std::unique_ptr<sarif_tool_component>
sarif_builder::make_driver_tool_component_object ()
{
  auto driver_obj = ::make_unique<sarif_tool_component> ();

  if (auto client_data_hooks = m_context.get_client_data_hooks ())
    if (const client_version_info *vinfo
	  = client_data_hooks->get_any_version_info ())
      {
	/* "name" property (SARIF v2.1.0 section 3.19.8).  */
	if (const char *name = vinfo->get_tool_name ())
	  driver_obj->set_string ("name", name);

	/* "fullName" property (SARIF v2.1.0 section 3.19.9).  */
	if (char *full_name = vinfo->maybe_make_full_name ())
	  {
	    driver_obj->set_string ("fullName", full_name);
	    free (full_name);
	  }

	/* "version" property (SARIF v2.1.0 section 3.19.13).  */
	if (const char *version = vinfo->get_version_string ())
	  driver_obj->set_string ("version", version);

	/* "informationUri" property (SARIF v2.1.0 section 3.19.17).  */
	if (char *version_url =  vinfo->maybe_make_version_url ())
	  {
	    driver_obj->set_string ("informationUri", version_url);
	    free (version_url);
	  }
      }

  /* "rules" property (SARIF v2.1.0 section 3.19.23).  */
  driver_obj->set<json::array> ("rules", std::move (m_rules_arr));

  return driver_obj;
}

/* If we've seen any CWE IDs, make an array for the "taxonomies" property
   (SARIF v2.1.0 section 3.14.8) of a run object, containing a single
   "toolComponent" (3.19) as per 3.19.3, representing the CWE.

   Otherwise return nullptr.  */

std::unique_ptr<json::array>
sarif_builder::maybe_make_taxonomies_array () const
{
  auto cwe_obj = maybe_make_cwe_taxonomy_object ();
  if (!cwe_obj)
    return nullptr;

  /* "taxonomies" property (SARIF v2.1.0 section 3.14.8).  */
  auto taxonomies_arr = ::make_unique<json::array> ();
  taxonomies_arr->append<sarif_tool_component> (std::move (cwe_obj));
  return taxonomies_arr;
}

/* If we've seen any CWE IDs, make a "toolComponent" object
   (SARIF v2.1.0 section 3.19) representing the CWE taxonomy, as per 3.19.3.
   Populate the "taxa" property with all of the CWE IDs in m_cwe_id_set.

   Otherwise return nullptr.  */

std::unique_ptr<sarif_tool_component>
sarif_builder::maybe_make_cwe_taxonomy_object () const
{
  if (m_cwe_id_set.is_empty ())
    return nullptr;

  auto taxonomy_obj = ::make_unique<sarif_tool_component> ();

  /* "name" property (SARIF v2.1.0 section 3.19.8).  */
  taxonomy_obj->set_string ("name", "CWE");

  /* "version" property (SARIF v2.1.0 section 3.19.13).  */
  taxonomy_obj->set_string ("version", "4.7");

  /* "organization" property (SARIF v2.1.0 section 3.19.18).  */
  taxonomy_obj->set_string ("organization", "MITRE");

  /* "shortDescription" property (SARIF v2.1.0 section 3.19.19).  */
  taxonomy_obj->set<sarif_multiformat_message_string>
    ("shortDescription",
     make_multiformat_message_string ("The MITRE"
				      " Common Weakness Enumeration"));

  /* "taxa" property (SARIF v2.1.0 3.section 3.19.25).  */
  auto taxa_arr = ::make_unique<json::array> ();
  for (auto cwe_id : m_cwe_id_set)
    taxa_arr->append<sarif_reporting_descriptor>
      (make_reporting_descriptor_object_for_cwe_id (cwe_id));
  taxonomy_obj->set<json::array> ("taxa", std::move (taxa_arr));

  return taxonomy_obj;
}

/* Ensure that we have an "artifact" object (SARIF v2.1.0 section 3.24)
   for FILENAME, adding it to m_filename_to_artifact_map if not already
   found, and adding ROLE to it.
   If EMBED_CONTENTS is true, then flag that we will attempt to embed the
   contents of this artifact when writing it out.  */

sarif_artifact &
sarif_builder::get_or_create_artifact (const char *filename,
				       enum diagnostic_artifact_role role,
				       bool embed_contents)
{
  if (auto *slot = m_filename_to_artifact_map.get (filename))
    {
      (*slot)->add_role (role, embed_contents);
      return **slot;
    }

  sarif_artifact *artifact_obj = new sarif_artifact (filename);
  artifact_obj->add_role (role, embed_contents);
  m_filename_to_artifact_map.put (filename, artifact_obj);

  /* "location" property (SARIF v2.1.0 section 3.24.2).  */
  artifact_obj->set<sarif_artifact_location>
    ("location", make_artifact_location_object (filename));

  /* "sourceLanguage" property (SARIF v2.1.0 section 3.24.10).  */
  switch (role)
    {
    default:
      gcc_unreachable ();
    case diagnostic_artifact_role::analysis_target:
    case diagnostic_artifact_role::result_file:
    case diagnostic_artifact_role::scanned_file:
    case diagnostic_artifact_role::traced_file:
      /* Assume that these are in the source language.  */
      if (auto client_data_hooks = m_context.get_client_data_hooks ())
	if (const char *source_lang
	    = client_data_hooks->maybe_get_sarif_source_language (filename))
	  artifact_obj->set_string ("sourceLanguage", source_lang);
      break;

    case diagnostic_artifact_role::debug_output_file:
      /* Assume that these are not in the source language.  */
      break;
    }

  return *artifact_obj;
}

/* Make an "artifactContent" object (SARIF v2.1.0 section 3.3) for the
   full contents of FILENAME.  */

std::unique_ptr<sarif_artifact_content>
sarif_builder::maybe_make_artifact_content_object (const char *filename) const
{
  /* Let input.cc handle any charset conversion.  */
  char_span utf8_content
    = m_context.get_file_cache ().get_source_file_content (filename);
  if (!utf8_content)
    return nullptr;

  /* Don't add it if it's not valid UTF-8.  */
  if (!cpp_valid_utf8_p(utf8_content.get_buffer (), utf8_content.length ()))
    return nullptr;

  auto artifact_content_obj = ::make_unique<sarif_artifact_content> ();
  artifact_content_obj->set<json::string>
    ("text",
     ::make_unique <json::string> (utf8_content.get_buffer (),
				   utf8_content.length ()));
  return artifact_content_obj;
}

/* Attempt to read the given range of lines from FILENAME; return
   a freshly-allocated 0-terminated buffer containing them, or nullptr.  */

char *
sarif_builder::get_source_lines (const char *filename,
				 int start_line,
				 int end_line) const
{
  auto_vec<char> result;

  for (int line = start_line; line <= end_line; line++)
    {
      char_span line_content
	= m_context.get_file_cache ().get_source_line (filename, line);
      if (!line_content.get_buffer ())
	return nullptr;
      result.reserve (line_content.length () + 1);
      for (size_t i = 0; i < line_content.length (); i++)
	result.quick_push (line_content[i]);
      result.quick_push ('\n');
    }
  result.safe_push ('\0');

  return xstrdup (result.address ());
}

/* Make an "artifactContent" object (SARIF v2.1.0 section 3.3) for the given
   run of lines within FILENAME (including the endpoints).
   If R is non-NULL, use it to potentially set the "rendered"
   property (3.3.4).  */

std::unique_ptr<sarif_artifact_content>
sarif_builder::
maybe_make_artifact_content_object (const char *filename,
				    int start_line,
				    int end_line,
				    const content_renderer *r) const
{
  char *text_utf8 = get_source_lines (filename, start_line, end_line);

  if (!text_utf8)
    return nullptr;

  /* Don't add it if it's not valid UTF-8.  */
  if (!cpp_valid_utf8_p(text_utf8, strlen(text_utf8)))
    {
      free (text_utf8);
      return nullptr;
    }

  auto artifact_content_obj = ::make_unique<sarif_artifact_content> ();
  artifact_content_obj->set_string ("text", text_utf8);
  free (text_utf8);

  /* 3.3.4 "rendered" property.  */
  if (r)
    if (std::unique_ptr<sarif_multiformat_message_string> rendered
	  = r->render (*this))
      artifact_content_obj->set ("rendered", std::move (rendered));

  return artifact_content_obj;
}

/* Make a "fix" object (SARIF v2.1.0 section 3.55) for RICHLOC.  */

std::unique_ptr<sarif_fix>
sarif_builder::make_fix_object (const rich_location &richloc)
{
  auto fix_obj = ::make_unique<sarif_fix> ();

  /* "artifactChanges" property (SARIF v2.1.0 section 3.55.3).  */
  /* We assume that all fix-it hints in RICHLOC affect the same file.  */
  auto artifact_change_arr = ::make_unique<json::array> ();
  artifact_change_arr->append<sarif_artifact_change>
    (make_artifact_change_object (richloc));
  fix_obj->set<json::array> ("artifactChanges",
			     std::move (artifact_change_arr));

  return fix_obj;
}

/* Make an "artifactChange" object (SARIF v2.1.0 section 3.56) for RICHLOC.  */

std::unique_ptr<sarif_artifact_change>
sarif_builder::make_artifact_change_object (const rich_location &richloc)
{
  auto artifact_change_obj = ::make_unique<sarif_artifact_change> ();

  /* "artifactLocation" property (SARIF v2.1.0 section 3.56.2).  */
  artifact_change_obj->set<sarif_artifact_location>
    ("artifactLocation",
     make_artifact_location_object (richloc.get_loc ()));

  /* "replacements" property (SARIF v2.1.0 section 3.56.3).  */
  auto replacement_arr = ::make_unique<json::array> ();
  for (unsigned int i = 0; i < richloc.get_num_fixit_hints (); i++)
    {
      const fixit_hint *hint = richloc.get_fixit_hint (i);
      replacement_arr->append<sarif_replacement>
	(make_replacement_object (*hint));
    }
  artifact_change_obj->set<json::array> ("replacements",
					 std::move (replacement_arr));

  return artifact_change_obj;
}

/* Make a "replacement" object (SARIF v2.1.0 section 3.57) for HINT.  */

std::unique_ptr<sarif_replacement>
sarif_builder::make_replacement_object (const fixit_hint &hint) const
{
  auto replacement_obj = ::make_unique<sarif_replacement> ();

  /* "deletedRegion" property (SARIF v2.1.0 section 3.57.3).  */
  replacement_obj->set<sarif_region> ("deletedRegion",
				      make_region_object_for_hint (hint));

  /* "insertedContent" property (SARIF v2.1.0 section 3.57.4).  */
  replacement_obj->set<sarif_artifact_content>
    ("insertedContent",
     make_artifact_content_object (hint.get_string ()));

  return replacement_obj;
}

/* Make an "artifactContent" object (SARIF v2.1.0 section 3.3) for TEXT.  */

std::unique_ptr<sarif_artifact_content>
sarif_builder::make_artifact_content_object (const char *text) const
{
  auto content_obj = ::make_unique<sarif_artifact_content> ();

  /* "text" property (SARIF v2.1.0 section 3.3.2).  */
  content_obj->set_string ("text", text);

  return content_obj;
}

/* Callback for diagnostic_context::ice_handler_cb for when an ICE
   occurs.  */

static void
sarif_ice_handler (diagnostic_context *context)
{
  /* Attempt to ensure that a .sarif file is written out.  */
  diagnostic_finish (context);

  /* Print a header for the remaining output to stderr, and
     return, attempting to print the usual ICE messages to
     stderr.  Hopefully this will be helpful to the user in
     indicating what's gone wrong (also for DejaGnu, for pruning
     those messages).   */
  fnotice (stderr, "Internal compiler error:\n");
}

class sarif_output_format : public diagnostic_output_format
{
public:
  ~sarif_output_format ()
  {
    /* Any sarifResult objects should have been handled by now.
       If not, then something's gone wrong with diagnostic
       groupings.  */
    std::unique_ptr<sarif_result> pending_result
      = m_builder.take_current_result ();
    gcc_assert (!pending_result);
  }

  void on_begin_group () final override
  {
    /* No-op,  */
  }
  void on_end_group () final override
  {
    m_builder.end_group ();
  }
  void
  on_begin_diagnostic (const diagnostic_info &) final override
  {
    /* No-op,  */
  }
  void
  on_end_diagnostic (const diagnostic_info &diagnostic,
		     diagnostic_t orig_diag_kind) final override
  {
    m_builder.end_diagnostic (m_context, diagnostic, orig_diag_kind);
  }
  void on_diagram (const diagnostic_diagram &diagram) final override
  {
    m_builder.emit_diagram (m_context, diagram);
  }

protected:
  sarif_output_format (diagnostic_context &context,
		       const line_maps *line_maps,
		       const char *main_input_filename_,
		       bool formatted)
  : diagnostic_output_format (context),
    m_builder (context, line_maps, main_input_filename_, formatted)
  {}

  sarif_builder m_builder;
};

class sarif_stream_output_format : public sarif_output_format
{
public:
  sarif_stream_output_format (diagnostic_context &context,
			      const line_maps *line_maps,
			      const char *main_input_filename_,
			      bool formatted,
			      FILE *stream)
  : sarif_output_format (context, line_maps, main_input_filename_, formatted),
    m_stream (stream)
  {
  }
  ~sarif_stream_output_format ()
  {
    m_builder.flush_to_file (m_stream);
  }
  bool machine_readable_stderr_p () const final override
  {
    return m_stream == stderr;
  }
private:
  FILE *m_stream;
};

class sarif_file_output_format : public sarif_output_format
{
public:
  sarif_file_output_format (diagnostic_context &context,
			    const line_maps *line_maps,
			    const char *main_input_filename_,
			    bool formatted,
			    const char *base_file_name)
  : sarif_output_format (context, line_maps, main_input_filename_, formatted),
    m_base_file_name (xstrdup (base_file_name))
  {
  }
  ~sarif_file_output_format ()
  {
    char *filename = concat (m_base_file_name, ".sarif", nullptr);
    free (m_base_file_name);
    m_base_file_name = nullptr;
    FILE *outf = fopen (filename, "w");
    if (!outf)
      {
	const char *errstr = xstrerror (errno);
	fnotice (stderr, "error: unable to open '%s' for writing: %s\n",
		 filename, errstr);
	free (filename);
	return;
      }
    m_builder.flush_to_file (outf);
    fclose (outf);
    free (filename);
  }
  bool machine_readable_stderr_p () const final override
  {
    return false;
  }

private:
  char *m_base_file_name;
};

/* Populate CONTEXT in preparation for SARIF output (either to stderr, or
   to a file).  */

static void
diagnostic_output_format_init_sarif (diagnostic_context &context)
{
  /* Suppress normal textual path output.  */
  context.set_path_format (DPF_NONE);

  /* Override callbacks.  */
  context.set_ice_handler_callback (sarif_ice_handler);

  /* The metadata is handled in SARIF format, rather than as text.  */
  context.set_show_cwe (false);
  context.set_show_rules (false);

  /* The option is handled in SARIF format, rather than as text.  */
  context.set_show_option_requested (false);

  /* Don't colorize the text.  */
  pp_show_color (context.printer) = false;
  context.set_show_highlight_colors (false);
}

/* Populate CONTEXT in preparation for SARIF output to stderr.  */

void
diagnostic_output_format_init_sarif_stderr (diagnostic_context &context,
					    const line_maps *line_maps,
					    const char *main_input_filename_,
					    bool formatted)
{
  gcc_assert (line_maps);
  diagnostic_output_format_init_sarif (context);
  context.set_output_format
    (new sarif_stream_output_format (context,
				     line_maps,
				     main_input_filename_,
				     formatted,
				     stderr));
}

/* Populate CONTEXT in preparation for SARIF output to a file named
   BASE_FILE_NAME.sarif.  */

void
diagnostic_output_format_init_sarif_file (diagnostic_context &context,
					  const line_maps *line_maps,
					  const char *main_input_filename_,
					  bool formatted,
					  const char *base_file_name)
{
  gcc_assert (line_maps);
  diagnostic_output_format_init_sarif (context);
  context.set_output_format
    (new sarif_file_output_format (context,
				   line_maps,
				   main_input_filename_,
				   formatted,
				   base_file_name));
}

/* Populate CONTEXT in preparation for SARIF output to STREAM.  */

void
diagnostic_output_format_init_sarif_stream (diagnostic_context &context,
					    const line_maps *line_maps,
					    const char *main_input_filename_,
					    bool formatted,
					    FILE *stream)
{
  gcc_assert (line_maps);
  diagnostic_output_format_init_sarif (context);
  context.set_output_format
    (new sarif_stream_output_format (context,
				     line_maps,
				     main_input_filename_,
				     formatted,
				     stream));
}

#if CHECKING_P

namespace selftest {

/* A subclass of sarif_output_format for writing selftests.
   The JSON output is cached internally, rather than written
   out to a file.  */

class test_sarif_diagnostic_context : public test_diagnostic_context
{
public:
  test_sarif_diagnostic_context (const char *main_input_filename)
  {
    diagnostic_output_format_init_sarif (*this);

    m_format = new buffered_output_format (*this,
					   line_table,
					   main_input_filename,
					   true);
    set_output_format (m_format); // give ownership;
  }

  std::unique_ptr<sarif_log> flush_to_object ()
  {
    return m_format->flush_to_object ();
  }

private:
  class buffered_output_format : public sarif_output_format
  {
  public:
    buffered_output_format (diagnostic_context &context,
			    const line_maps *line_maps,
			    const char *main_input_filename_,
			    bool formatted)
    : sarif_output_format (context, line_maps, main_input_filename_, formatted)
    {
    }
    bool machine_readable_stderr_p () const final override
    {
      return false;
    }
    std::unique_ptr<sarif_log> flush_to_object ()
    {
      return m_builder.flush_to_object ();
    }
  };

  buffered_output_format *m_format; // borrowed
};

/* Test making a sarif_location for a complex rich_location
   with labels and escape-on-output.  */

static void
test_make_location_object (const line_table_case &case_)
{
  diagnostic_show_locus_fixture_one_liner_utf8 f (case_);
  location_t line_end = linemap_position_for_column (line_table, 31);

  /* Don't attempt to run the tests if column data might be unavailable.  */
  if (line_end > LINE_MAP_MAX_LOCATION_WITH_COLS)
    return;

  test_diagnostic_context dc;

  sarif_builder builder (dc, line_table, "MAIN_INPUT_FILENAME", true);

  /* These "columns" are byte offsets, whereas later on the columns
     in the generated SARIF use sarif_builder::get_sarif_column and
     thus respect tabs, encoding.  */
  const location_t foo
    = make_location (linemap_position_for_column (line_table, 1),
		     linemap_position_for_column (line_table, 1),
		     linemap_position_for_column (line_table, 8));
  const location_t bar
    = make_location (linemap_position_for_column (line_table, 12),
		     linemap_position_for_column (line_table, 12),
		     linemap_position_for_column (line_table, 17));
  const location_t field
    = make_location (linemap_position_for_column (line_table, 19),
		     linemap_position_for_column (line_table, 19),
		     linemap_position_for_column (line_table, 30));

  text_range_label label0 ("label0");
  text_range_label label1 ("label1");
  text_range_label label2 ("label2");

  rich_location richloc (line_table, foo, &label0, nullptr);
  richloc.add_range (bar, SHOW_RANGE_WITHOUT_CARET, &label1);
  richloc.add_range (field, SHOW_RANGE_WITHOUT_CARET, &label2);
  richloc.set_escape_on_output (true);

  sarif_result result;

  std::unique_ptr<sarif_location> location_obj
    = builder.make_location_object
    (result, richloc, nullptr, diagnostic_artifact_role::analysis_target);
  ASSERT_NE (location_obj, nullptr);

  auto physical_location
    = EXPECT_JSON_OBJECT_WITH_OBJECT_PROPERTY (location_obj.get (),
					       "physicalLocation");
  {
    auto region
      = EXPECT_JSON_OBJECT_WITH_OBJECT_PROPERTY (physical_location, "region");
    ASSERT_JSON_INT_PROPERTY_EQ (region, "startLine", 1);
    ASSERT_JSON_INT_PROPERTY_EQ (region, "startColumn", 1);
    ASSERT_JSON_INT_PROPERTY_EQ (region, "endColumn", 7);
  }
  {
    auto context_region
      = EXPECT_JSON_OBJECT_WITH_OBJECT_PROPERTY (physical_location,
						 "contextRegion");
    ASSERT_JSON_INT_PROPERTY_EQ (context_region, "startLine", 1);

    {
      auto snippet
	= EXPECT_JSON_OBJECT_WITH_OBJECT_PROPERTY (context_region, "snippet");

      /* We expect the snippet's "text" to be a copy of the content.  */
      ASSERT_JSON_STRING_PROPERTY_EQ (snippet, "text",  f.m_content);

      /* We expect the snippet to have a "rendered" whose "text" has a
	 pure ASCII escaped copy of the line (with labels, etc).  */
      {
	auto rendered
	  = EXPECT_JSON_OBJECT_WITH_OBJECT_PROPERTY (snippet, "rendered");
	ASSERT_JSON_STRING_PROPERTY_EQ
	  (rendered, "text",
	   "1 | <U+1F602>_foo = <U+03C0>_bar.<U+1F602>_field<U+03C0>;\n"
	   "  | ^~~~~~~~~~~~~   ~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~\n"
	   "  | |               |            |\n"
	   "  | label0          label1       label2\n");
      }
    }
  }
  auto annotations
    = EXPECT_JSON_OBJECT_WITH_ARRAY_PROPERTY (location_obj.get (),
					      "annotations");
  ASSERT_EQ (annotations->size (), 3);
  {
    {
      auto a0 = (*annotations)[0];
      ASSERT_JSON_INT_PROPERTY_EQ (a0, "startLine", 1);
      ASSERT_JSON_INT_PROPERTY_EQ (a0, "startColumn", 1);
      ASSERT_JSON_INT_PROPERTY_EQ (a0, "endColumn", 7);
      auto message
	= EXPECT_JSON_OBJECT_WITH_OBJECT_PROPERTY (a0, "message");
      ASSERT_JSON_STRING_PROPERTY_EQ (message, "text", "label0");
    }
    {
      auto a1 = (*annotations)[1];
      ASSERT_JSON_INT_PROPERTY_EQ (a1, "startLine", 1);
      ASSERT_JSON_INT_PROPERTY_EQ (a1, "startColumn", 10);
      ASSERT_JSON_INT_PROPERTY_EQ (a1, "endColumn", 15);
      auto message
	= EXPECT_JSON_OBJECT_WITH_OBJECT_PROPERTY (a1, "message");
      ASSERT_JSON_STRING_PROPERTY_EQ (message, "text", "label1");
    }
    {
      auto a2 = (*annotations)[2];
      ASSERT_JSON_INT_PROPERTY_EQ (a2, "startLine", 1);
      ASSERT_JSON_INT_PROPERTY_EQ (a2, "startColumn", 16);
      ASSERT_JSON_INT_PROPERTY_EQ (a2, "endColumn", 25);
      auto message
	= EXPECT_JSON_OBJECT_WITH_OBJECT_PROPERTY (a2, "message");
      ASSERT_JSON_STRING_PROPERTY_EQ (message, "text", "label2");
    }
  }
}

/* Test of reporting a diagnostic at UNKNOWN_LOCATION to a
   diagnostic_context and examining the generated sarif_log.
   Verify various basic properties. */

static void
test_simple_log ()
{
  test_sarif_diagnostic_context dc ("MAIN_INPUT_FILENAME");

  rich_location richloc (line_table, UNKNOWN_LOCATION);
  dc.report (DK_ERROR, richloc, nullptr, 0, "this is a test: %i", 42);

  auto log_ptr = dc.flush_to_object ();

  // 3.13 sarifLog:
  auto log = log_ptr.get ();
  ASSERT_JSON_STRING_PROPERTY_EQ (log, "$schema", SARIF_SCHEMA); // 3.13.3
  ASSERT_JSON_STRING_PROPERTY_EQ (log, "version", SARIF_VERSION); // 3.13.2

  auto runs = EXPECT_JSON_OBJECT_WITH_ARRAY_PROPERTY (log, "runs"); // 3.13.4
  ASSERT_EQ (runs->size (), 1);

  // 3.14 "run" object:
  auto run = (*runs)[0];

  {
    // 3.14.6:
    auto tool = EXPECT_JSON_OBJECT_WITH_OBJECT_PROPERTY (run, "tool");

    EXPECT_JSON_OBJECT_WITH_OBJECT_PROPERTY (tool, "driver"); // 3.18.2
  }

  {
    // 3.14.11
    auto invocations
      = EXPECT_JSON_OBJECT_WITH_ARRAY_PROPERTY (run, "invocations");
    ASSERT_EQ (invocations->size (), 1);

    {
      // 3.20 "invocation" object:
      auto invocation = (*invocations)[0];

      // 3.20.3 arguments property

      // 3.20.7 startTimeUtc property
      EXPECT_JSON_OBJECT_WITH_STRING_PROPERTY (invocation, "startTimeUtc");

      // 3.20.8 endTimeUtc property
      EXPECT_JSON_OBJECT_WITH_STRING_PROPERTY (invocation, "endTimeUtc");

      // 3.20.19 workingDirectory property
      {
	auto wd_obj
	  = EXPECT_JSON_OBJECT_WITH_OBJECT_PROPERTY (invocation,
						     "workingDirectory");
	EXPECT_JSON_OBJECT_WITH_STRING_PROPERTY (wd_obj, "uri");
      }

      // 3.20.21 toolExecutionNotifications property
      auto notifications
	= EXPECT_JSON_OBJECT_WITH_ARRAY_PROPERTY
	    (invocation, "toolExecutionNotifications");
      ASSERT_EQ (notifications->size (), 0);
    }
  }

  {
    // 3.14.15:
    auto artifacts = EXPECT_JSON_OBJECT_WITH_ARRAY_PROPERTY (run, "artifacts");
    ASSERT_EQ (artifacts->size (), 1);

    {
      // 3.24 "artifact" object:
      auto artifact = (*artifacts)[0];

      // 3.24.2:
      auto location
	= EXPECT_JSON_OBJECT_WITH_OBJECT_PROPERTY (artifact, "location");
      ASSERT_JSON_STRING_PROPERTY_EQ (location, "uri", "MAIN_INPUT_FILENAME");

      // 3.24.6:
      auto roles = EXPECT_JSON_OBJECT_WITH_ARRAY_PROPERTY (artifact, "roles");
      ASSERT_EQ (roles->size (), 1);
      {
	auto role = (*roles)[0];
	ASSERT_JSON_STRING_EQ (role, "analysisTarget");
      }
    }
  }

  {
    // 3.14.23:
    auto results = EXPECT_JSON_OBJECT_WITH_ARRAY_PROPERTY (run, "results");
    ASSERT_EQ (results->size (), 1);

    {
      // 3.27 "result" object:
      auto result = (*results)[0];
      ASSERT_JSON_STRING_PROPERTY_EQ (result, "ruleId", "error");
      ASSERT_JSON_STRING_PROPERTY_EQ (result, "level", "error"); // 3.27.10

      {
	// 3.27.11:
	auto message
	  = EXPECT_JSON_OBJECT_WITH_OBJECT_PROPERTY (result, "message");
	ASSERT_JSON_STRING_PROPERTY_EQ (message, "text",
					"this is a test: 42");
      }

      // 3.27.12:
      auto locations
	= EXPECT_JSON_OBJECT_WITH_ARRAY_PROPERTY (result, "locations");
      ASSERT_EQ (locations->size (), 0);
    }
  }
}

/* As above, but with a "real" location_t.  */

static void
test_simple_log_2 (const line_table_case &case_)
{
  auto_fix_quotes fix_quotes;

  const char *const content
    /* 000000000111111
       123456789012345.  */
    = "unsinged int i;\n";
  diagnostic_show_locus_fixture f (case_, content);
  location_t line_end = linemap_position_for_column (line_table, 31);

  /* Don't attempt to run the tests if column data might be unavailable.  */
  if (line_end > LINE_MAP_MAX_LOCATION_WITH_COLS)
    return;

  test_sarif_diagnostic_context dc (f.get_filename ());

  const location_t typo_loc
    = make_location (linemap_position_for_column (line_table, 1),
		     linemap_position_for_column (line_table, 1),
		     linemap_position_for_column (line_table, 8));

  rich_location richloc (line_table, typo_loc);
  dc.report (DK_ERROR, richloc, nullptr, 0,
	     "did you misspell %qs again?",
	     "unsigned");

  auto log_ptr = dc.flush_to_object ();

  // 3.13 sarifLog:
  auto log = log_ptr.get ();

  auto runs = EXPECT_JSON_OBJECT_WITH_ARRAY_PROPERTY (log, "runs"); // 3.13.4
  ASSERT_EQ (runs->size (), 1);

  // 3.14 "run" object:
  auto run = (*runs)[0];

  {
    // 3.14.23:
    auto results = EXPECT_JSON_OBJECT_WITH_ARRAY_PROPERTY (run, "results");
    ASSERT_EQ (results->size (), 1);

    {
      // 3.27 "result" object:
      auto result = (*results)[0];
      ASSERT_JSON_STRING_PROPERTY_EQ (result, "ruleId", "error");
      ASSERT_JSON_STRING_PROPERTY_EQ (result, "level", "error"); // 3.27.10

      {
	// 3.27.11:
	auto message
	  = EXPECT_JSON_OBJECT_WITH_OBJECT_PROPERTY (result, "message");
	ASSERT_JSON_STRING_PROPERTY_EQ (message, "text",
					"did you misspell `unsigned' again?");
      }

      // 3.27.12:
      auto locations
	= EXPECT_JSON_OBJECT_WITH_ARRAY_PROPERTY (result, "locations");
      ASSERT_EQ (locations->size (), 1);

      {
	// 3.28 "location" object:
	auto location = (*locations)[0];

	auto physical_location
	  = EXPECT_JSON_OBJECT_WITH_OBJECT_PROPERTY (location,
						     "physicalLocation");
	{
	  auto region
	    = EXPECT_JSON_OBJECT_WITH_OBJECT_PROPERTY (physical_location,
						       "region");
	  ASSERT_JSON_INT_PROPERTY_EQ (region, "startLine", 1);
	  ASSERT_JSON_INT_PROPERTY_EQ (region, "startColumn", 1);
	  ASSERT_JSON_INT_PROPERTY_EQ (region, "endColumn", 9);
	}
	{
	  auto context_region
	    = EXPECT_JSON_OBJECT_WITH_OBJECT_PROPERTY (physical_location,
						       "contextRegion");
	  ASSERT_JSON_INT_PROPERTY_EQ (context_region, "startLine", 1);

	  {
	    auto snippet
	      = EXPECT_JSON_OBJECT_WITH_OBJECT_PROPERTY (context_region,
							 "snippet");

	    /* We expect the snippet's "text" to be a copy of the content.  */
	    ASSERT_JSON_STRING_PROPERTY_EQ (snippet, "text",  f.m_content);
	  }
	}
      }
    }
  }
}

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

void
diagnostic_format_sarif_cc_tests ()
{
  for_each_line_table_case (test_make_location_object);
  test_simple_log ();
  for_each_line_table_case (test_simple_log_2);
}

} // namespace selftest

#endif /* CHECKING_P */