From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by sourceware.org (Postfix) with ESMTPS id 30576385829E for ; Tue, 21 Nov 2023 22:20:24 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 30576385829E Authentication-Results: sourceware.org; dmarc=pass (p=none dis=none) header.from=redhat.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=redhat.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org 30576385829E Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=170.10.133.124 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1700605230; cv=none; b=UuCl4/iev0+RtYix1XVLLOrzEpVvhK7wVfZevnMb6aXV6JGbZ5OLM4dMFA+LDxNglNatSS1LjkRbLBdJUK6cFtPJg9KoTbhBDct+KcTy7VGENH39UlaauYaq0MZIlAZA/DsJXNBgI7Rp55o5vaytDL3C/K0iJJxnlDEvFtQBgVU= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1700605230; c=relaxed/simple; bh=04YdDcy7cVZRGyGwKcQFzILP6lL8VgOEhJo6gYHyEQ0=; h=DKIM-Signature:From:To:Subject:Date:Message-Id:MIME-Version; b=QBnTPFCIPhkaQ9+3lEV+YD1AwXIEuiwYiSqPnmmPElTW8mnnpE38RqBGxrkJ2IBdRnbDLZWMK100yO3HZj3MpTrBmLljlLP5fPAkyGVfdn06TiiTYJoZvzYQqevMq7PFFq3xVKDhRWF0JWb9HF8StURpYo8MQt29d7c5VHtm55k= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1700605223; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=iIOW2B5O3QoObeYM0D7PiEn5JlhrB3EL178p0FVTOuM=; b=QaaL1t6iCrvpLcdBiSU6ldX9FOaaKvWd6ew6eVyxKlSnTudd61IQSUXFLOx7ropX5R5aYp Vr0k8lu/1BIJpumnamLKdOOc9mPx8OZDKtzWAvBKMc1/nGgq4W47bkJ2sQBjc6RN+xGXZB mXApwXUlc/9UpLqrWT4WL4p/RRsY9Ms= Received: from mimecast-mx02.redhat.com (mx-ext.redhat.com [66.187.233.73]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-558-ElQLAH2dOEeYIa7Sd5lZlw-1; Tue, 21 Nov 2023 17:20:22 -0500 X-MC-Unique: ElQLAH2dOEeYIa7Sd5lZlw-1 Received: from smtp.corp.redhat.com (int-mx10.intmail.prod.int.rdu2.redhat.com [10.11.54.10]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mimecast-mx02.redhat.com (Postfix) with ESMTPS id 22DB21C01406; Tue, 21 Nov 2023 22:20:22 +0000 (UTC) Received: from t14s.localdomain.com (unknown [10.22.10.115]) by smtp.corp.redhat.com (Postfix) with ESMTP id C9C76492BFA; Tue, 21 Nov 2023 22:20:21 +0000 (UTC) From: David Malcolm To: gcc-patches@gcc.gnu.org, binutils@sourceware.org Cc: Nick Clifton , Simon Sobisch , David Malcolm Subject: [PATCH 2/5] libdiagnostics v2: work-in-progress implementation Date: Tue, 21 Nov 2023 17:20:15 -0500 Message-Id: <20231121222019.646253-3-dmalcolm@redhat.com> In-Reply-To: <20231121222019.646253-1-dmalcolm@redhat.com> References: <20231106222959.2707741-1-dmalcolm@redhat.com> <20231121222019.646253-1-dmalcolm@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.4.1 on 10.11.54.10 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-Spam-Status: No, score=-11.7 required=5.0 tests=BAYES_00,DKIMWL_WL_HIGH,DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,GIT_PATCH_0,KAM_SHORT,RCVD_IN_DNSWL_NONE,RCVD_IN_MSPIKE_H3,RCVD_IN_MSPIKE_WL,SPF_HELO_NONE,SPF_NONE,TXREP,T_SCC_BODY_TEXT_LINE autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on server2.sourceware.org List-Id: Changed in v2: * Changed diagnostic_location_t -> const diagnostic_physical_location * * new entrypoint: diagnostic_finish_va * new debugging entrypoints for dumping to a FILE * * Makefile.in: dropped FULL_DRIVER_NAME from libdiagnostics * fix up for my recent changes to gcc/diagnostic.h Blurb from v1: Here's a work-in-progress patch for GCC that adds the implementation of libdiagnostics. Various aspects of this need work; posting now for early feedback on overall direction. gcc/ChangeLog: * Makefile.in (lang_checks): Add check-libdiagnostics. (start.encap): Add libdiagnostics. (libdiagnostics_OBJS): New. ...plus a bunch of stuff hacked up from jit/Make-lang.in. * configure: Regenerate. * configure.ac (check_languages): Add check-libdiagnostics. * libdiagnostics.cc: New file. * libdiagnostics.map: New file. gcc/testsuite/ChangeLog: * libdiagnostics.dg/libdiagnostics.exp: New, based on jit.exp. --- gcc/Makefile.in | 131 +- gcc/configure | 2 +- gcc/configure.ac | 2 +- gcc/libdiagnostics.cc | 1306 +++++++++++++++++ gcc/libdiagnostics.map | 63 + .../libdiagnostics.dg/libdiagnostics.exp | 544 +++++++ 6 files changed, 2044 insertions(+), 4 deletions(-) create mode 100644 gcc/libdiagnostics.cc create mode 100644 gcc/libdiagnostics.map create mode 100644 gcc/testsuite/libdiagnostics.dg/libdiagnostics.exp diff --git a/gcc/Makefile.in b/gcc/Makefile.in index 7b2f0c0f629c..7ad9eb442220 100644 --- a/gcc/Makefile.in +++ b/gcc/Makefile.in @@ -618,7 +618,7 @@ host_xm_defines=@host_xm_defines@ xm_file_list=@xm_file_list@ xm_include_list=@xm_include_list@ xm_defines=@xm_defines@ -lang_checks= +lang_checks=check-libdiagnostics lang_checks_parallelized= lang_opt_files=@lang_opt_files@ $(srcdir)/c-family/c.opt $(srcdir)/common.opt $(srcdir)/params.opt $(srcdir)/analyzer/analyzer.opt lang_specs_files=@lang_specs_files@ @@ -2169,7 +2169,7 @@ all.cross: native gcc-cross$(exeext) cpp$(exeext) specs \ libgcc-support lang.all.cross doc selftest @GENINSRC@ srcextra # This is what must be made before installing GCC and converting libraries. start.encap: native xgcc$(exeext) cpp$(exeext) specs \ - libgcc-support lang.start.encap @GENINSRC@ srcextra + libgcc-support lang.start.encap libdiagnostics @GENINSRC@ srcextra # These can't be made until after GCC can run. rest.encap: lang.rest.encap # This is what is made with the host's compiler @@ -2258,6 +2258,133 @@ cpp$(exeext): $(GCC_OBJS) c-family/cppspec.o libcommon-target.a $(LIBDEPS) \ c-family/cppspec.o $(EXTRA_GCC_OBJS) libcommon-target.a \ $(EXTRA_GCC_LIBS) $(LIBS) + +libdiagnostics_OBJS = libdiagnostics.o \ + libcommon.a + +# FIXME: +# Define the names for selecting jit in LANGUAGES. +# Note that it would be nice to move the dependency on g++ +# into the jit rule, but that needs a little bit of work +# to do the right thing within all.cross. + +LIBDIAGNOSTICS_VERSION_NUM = 0 +LIBDIAGNOSTICS_MINOR_NUM = 0 +LIBDIAGNOSTICS_RELEASE_NUM = 1 + +ifneq (,$(findstring mingw,$(target))) +LIBDIAGNOSTICS_FILENAME = libdiagnostics-$(LIBDIAGNOSTICS_VERSION_NUM).dll +LIBDIAGNOSTICS_IMPORT_LIB = libdiagnostics.dll.a + +libdiagnostics: $(LIBDIAGNOSTICS_FILENAME) + +else + +ifneq (,$(findstring darwin,$(host))) + +LIBDIAGNOSTICS_AGE = 1 +LIBDIAGNOSTICS_BASENAME = libdiagnostics + +LIBDIAGNOSTICS_SONAME = \ + ${libdir}/$(LIBDIAGNOSTICS_BASENAME).$(LIBDIAGNOSTICS_VERSION_NUM).dylib +LIBDIAGNOSTICS_FILENAME = $(LIBDIAGNOSTICS_BASENAME).$(LIBDIAGNOSTICS_VERSION_NUM).dylib +LIBDIAGNOSTICS_LINKER_NAME = $(LIBDIAGNOSTICS_BASENAME).dylib + +# Conditionalize the use of the LD_VERSION_SCRIPT_OPTION and +# LD_SONAME_OPTION depending if configure found them, using $(if) +# We have to define a LIBDIAGNOSTICS_COMMA here, otherwise the commas in the "true" +# result are treated as separators by the $(if). +LIBDIAGNOSTICS_COMMA := , +LIBDIAGNOSTICS_VERSION_SCRIPT_OPTION = \ + $(if $(LD_VERSION_SCRIPT_OPTION),\ + -Wl$(LIBDIAGNOSTICS_COMMA)$(LD_VERSION_SCRIPT_OPTION)$(LIBDIAGNOSTICS_COMMA)$(srcdir)/libdiagnostics.map) + +LIBDIAGNOSTICS_SONAME_OPTION = \ + $(if $(LD_SONAME_OPTION), \ + -Wl$(LIBDIAGNOSTICS_COMMA)$(LD_SONAME_OPTION)$(LIBDIAGNOSTICS_COMMA)$(LIBDIAGNOSTICS_SONAME)) + +LIBDIAGNOSTICS_SONAME_SYMLINK = $(LIBDIAGNOSTICS_FILENAME) +LIBDIAGNOSTICS_LINKER_NAME_SYMLINK = $(LIBDIAGNOSTICS_LINKER_NAME) + +libdiagnostics: $(LIBDIAGNOSTICS_FILENAME) \ + $(LIBDIAGNOSTICS_SYMLINK) \ + $(LIBDIAGNOSTICS_LINKER_NAME_SYMLINK) + +else + +LIBDIAGNOSTICS_LINKER_NAME = libdiagnostics.so +LIBDIAGNOSTICS_SONAME = $(LIBDIAGNOSTICS_LINKER_NAME).$(LIBDIAGNOSTICS_VERSION_NUM) +LIBDIAGNOSTICS_FILENAME = \ + $(LIBDIAGNOSTICS_SONAME).$(LIBDIAGNOSTICS_MINOR_NUM).$(LIBDIAGNOSTICS_RELEASE_NUM) + +LIBDIAGNOSTICS_LINKER_NAME_SYMLINK = $(LIBDIAGNOSTICS_LINKER_NAME) +LIBDIAGNOSTICS_SONAME_SYMLINK = $(LIBDIAGNOSTICS_SONAME) + +# Conditionalize the use of the LD_VERSION_SCRIPT_OPTION and +# LD_SONAME_OPTION depending if configure found them, using $(if) +# We have to define a LIBDIAGNOSTICS_COMMA here, otherwise the commas in the "true" +# result are treated as separators by the $(if). +LIBDIAGNOSTICS_COMMA := , +LIBDIAGNOSTICS_VERSION_SCRIPT_OPTION = \ + $(if $(LD_VERSION_SCRIPT_OPTION),\ + -Wl$(LIBDIAGNOSTICS_COMMA)$(LD_VERSION_SCRIPT_OPTION)$(LIBDIAGNOSTICS_COMMA)$(srcdir)/libdiagnostics.map) + +LIBDIAGNOSTICS_SONAME_OPTION = \ + $(if $(LD_SONAME_OPTION), \ + -Wl$(LIBDIAGNOSTICS_COMMA)$(LD_SONAME_OPTION)$(LIBDIAGNOSTICS_COMMA)$(LIBDIAGNOSTICS_SONAME)) + +libdiagnostics: $(LIBDIAGNOSTICS_FILENAME) \ + $(LIBDIAGNOSTICS_SYMLINK) \ + $(LIBDIAGNOSTICS_LINKER_NAME_SYMLINK) + +endif +endif + +libdiagnostics.serial = $(LIBDIAGNOSTICS_FILENAME) + +# Tell GNU make to ignore these if they exist. +.PHONY: libdiagnostics + +ifneq (,$(findstring mingw,$(target))) +# Create import library +LIBDIAGNOSTICS_EXTRA_OPTS = -Wl,--out-implib,$(LIBDIAGNOSTICS_IMPORT_LIB) +else + +ifneq (,$(findstring darwin,$(host))) +# TODO : Construct a Darwin-style symbol export file. +LIBDIAGNOSTICS_EXTRA_OPTS = -Wl,-compatibility_version,$(LIBDIAGNOSTICS_VERSION_NUM) \ + -Wl,-current_version,$(LIBDIAGNOSTICS_VERSION_NUM).$(LIBDIAGNOSTICS_MINOR_NUM).$(LIBDIAGNOSTICS_AGE) \ + $(LIBDIAGNOSTICS_VERSION_SCRIPT_OPTION) \ + $(LIBDIAGNOSTICS_SONAME_OPTION) +else + +LIBDIAGNOSTICS_EXTRA_OPTS = $(LIBDIAGNOSTICS_VERSION_SCRIPT_OPTION) \ + $(LIBDIAGNOSTICS_SONAME_OPTION) +endif +endif + +$(LIBDIAGNOSTICS_FILENAME): $(libdiagnostics_OBJS) $(CPPLIB) $(EXTRA_GCC_LIBS) $(LIBS) \ + $(LIBDEPS) $(srcdir)/libdiagnostics.map + @$(call LINK_PROGRESS,$(INDEX.libdiagnostics),start) + +$(LLINKER) $(ALL_LINKERFLAGS) $(LDFLAGS) -o $@ -shared \ + $(libdiagnostics_OBJS) \ + $(CPPLIB) $(EXTRA_GCC_LIBS) $(LIBS) \ + $(LIBDIAGNOSTICS_EXTRA_OPTS) + @$(call LINK_PROGRESS,$(INDEX.libdiagnostics),end) + +# Create symlinks when not building for Windows +ifeq (,$(findstring mingw,$(target))) + +ifeq (,$(findstring darwin,$(host))) +# but only one level for Darwin, version info is embedded. +$(LIBDIAGNOSTICS_SONAME_SYMLINK): $(LIBDIAGNOSTICS_FILENAME) + ln -sf $(LIBDIAGNOSTICS_FILENAME) $(LIBDIAGNOSTICS_SONAME_SYMLINK) +endif + +$(LIBDIAGNOSTICS_LINKER_NAME_SYMLINK): $(LIBDIAGNOSTICS_SONAME_SYMLINK) + ln -sf $(LIBDIAGNOSTICS_SONAME_SYMLINK) $(LIBDIAGNOSTICS_LINKER_NAME_SYMLINK) +endif + # Dump a specs file to make -B./ read these specs over installed ones. $(SPECS): xgcc$(exeext) $(GCC_FOR_TARGET) -dumpspecs > tmp-specs diff --git a/gcc/configure b/gcc/configure index cc0c3aad67be..3ae0b4eb37da 100755 --- a/gcc/configure +++ b/gcc/configure @@ -33729,7 +33729,7 @@ $as_echo "#define ENABLE_LTO 1" >>confdefs.h esac done -check_languages= +check_languages=check-libdiagnostics for language in $all_selected_languages do check_languages="$check_languages check-$language" diff --git a/gcc/configure.ac b/gcc/configure.ac index d9a35069e30d..55fbb8acebaa 100644 --- a/gcc/configure.ac +++ b/gcc/configure.ac @@ -7327,7 +7327,7 @@ changequote([,])dnl esac done -check_languages= +check_languages=check-libdiagnostics for language in $all_selected_languages do check_languages="$check_languages check-$language" diff --git a/gcc/libdiagnostics.cc b/gcc/libdiagnostics.cc new file mode 100644 index 000000000000..3236ce6837c7 --- /dev/null +++ b/gcc/libdiagnostics.cc @@ -0,0 +1,1306 @@ +/* C++ implementation of a pure C API for emitting diagnostics. + Copyright (C) 2023 Free Software Foundation, Inc. + +This file is part of GCC. + +GCC is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation; either version 3, or (at your option) any later +version. + +GCC is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License +along with GCC; see the file COPYING3. If not see +. */ + +#include "config.h" +#define INCLUDE_MEMORY +#define INCLUDE_VECTOR +#include "system.h" +#include "coretypes.h" +#include "intl.h" +#include "diagnostic.h" +#include "diagnostic-color.h" +#include "diagnostic-url.h" +#include "diagnostic-metadata.h" +#include "diagnostic-client-data-hooks.h" +#include "logical-location.h" +#include "edit-context.h" +#include "make-unique.h" +#include "libdiagnostics.h" + +class owned_nullable_string +{ +public: + owned_nullable_string () : m_str (nullptr) {} + owned_nullable_string (const char *str) + : m_str (str ? ::xstrdup (str) : nullptr) + { + } + + ~owned_nullable_string () + { + free (m_str); + } + + void set (const char *str) + { + free (m_str); + m_str = str ? ::xstrdup (str) : nullptr; + } + + const char *get_str () const { return m_str; } + + char *xstrdup () const + { + return m_str ? ::xstrdup (m_str) : nullptr; + } + +private: + char *m_str; +}; + +/* This has to be a "struct" as it is exposed in the C API. */ + +struct diagnostic_file +{ + diagnostic_file (const char *name, const char *sarif_source_language) + : m_name (name), m_sarif_source_language (sarif_source_language) + { + } + + const char *get_name () const { return m_name.get_str (); } + const char *get_sarif_source_language () const + { + return m_sarif_source_language.get_str (); + } + +private: + owned_nullable_string m_name; + owned_nullable_string m_sarif_source_language; +}; + +/* This has to be a "struct" as it is exposed in the C API. */ + +struct diagnostic_physical_location +{ + diagnostic_physical_location (location_t inner) : m_inner (inner) {} + + location_t m_inner; +}; + +static location_t +as_location_t (const diagnostic_physical_location *loc) +{ + if (!loc) + return UNKNOWN_LOCATION; + return loc->m_inner; +} + +/* This has to be a "struct" as it is exposed in the C API. */ + +struct diagnostic_logical_location : public logical_location +{ + diagnostic_logical_location (enum diagnostic_logical_location_kind_t kind, + const diagnostic_logical_location *parent, + const char *short_name, + const char *fully_qualified_name, + const char *decorated_name) + : m_kind (kind), + m_parent (parent), + m_short_name (short_name), + m_fully_qualified_name (fully_qualified_name), + m_decorated_name (decorated_name) + { + } + + const char *get_short_name () const final override + { + return m_short_name.get_str (); + } + const char *get_name_with_scope () const final override + { + return m_fully_qualified_name.get_str (); + } + const char *get_internal_name () const final override + { + return m_decorated_name.get_str (); + } + enum logical_location_kind get_kind () const final override + { + switch (m_kind) + { + default: + gcc_unreachable (); + case DIAGNOSTIC_LOGICAL_LOCATION_KIND_FUNCTION: + return LOGICAL_LOCATION_KIND_FUNCTION; + case DIAGNOSTIC_LOGICAL_LOCATION_KIND_MEMBER: + return LOGICAL_LOCATION_KIND_MEMBER; + case DIAGNOSTIC_LOGICAL_LOCATION_KIND_MODULE: + return LOGICAL_LOCATION_KIND_MODULE; + case DIAGNOSTIC_LOGICAL_LOCATION_KIND_NAMESPACE: + return LOGICAL_LOCATION_KIND_NAMESPACE; + case DIAGNOSTIC_LOGICAL_LOCATION_KIND_TYPE: + return LOGICAL_LOCATION_KIND_TYPE; + case DIAGNOSTIC_LOGICAL_LOCATION_KIND_RETURN_TYPE: + return LOGICAL_LOCATION_KIND_RETURN_TYPE; + case DIAGNOSTIC_LOGICAL_LOCATION_KIND_PARAMETER: + return LOGICAL_LOCATION_KIND_PARAMETER; + case DIAGNOSTIC_LOGICAL_LOCATION_KIND_VARIABLE: + return LOGICAL_LOCATION_KIND_VARIABLE; + } + } + + enum diagnostic_logical_location_kind_t get_external_kind () const + { + return m_kind; + } + + const diagnostic_logical_location *get_parent () const { return m_parent; } + +private: + enum diagnostic_logical_location_kind_t m_kind; + const diagnostic_logical_location *m_parent; + owned_nullable_string m_short_name; + owned_nullable_string m_fully_qualified_name; + owned_nullable_string m_decorated_name; +}; + +class sink +{ +public: + virtual ~sink (); + + void begin_group () + { + m_dc.begin_group (); + } + void end_group () + { + m_dc.end_group (); + } + + void emit (diagnostic &diag, const char *msgid, va_list *args) + LIBDIAGNOSTICS_PARAM_GCC_FORMAT_STRING(3, 0); + +protected: + sink (diagnostic_manager &mgr); + + diagnostic_manager &m_mgr; + + /* One context per sink. */ + diagnostic_context m_dc; +}; + +/* This has to be a "struct" as it is exposed in the C API. */ + +struct diagnostic_text_sink : public sink +{ +public: + diagnostic_text_sink (diagnostic_manager &mgr, + FILE *dst_stream, + enum diagnostic_colorize colorize); + + void + on_begin_text_diagnostic (diagnostic_info *info); + + diagnostic_source_printing_options &get_source_printing_options () + { + return m_dc.m_source_printing; + } + +private: + const diagnostic_logical_location *m_current_logical_loc; +}; + +class sarif_sink : public sink +{ +public: + sarif_sink (diagnostic_manager &mgr, + FILE *dst_stream, + enum diagnostic_sarif_version version); +}; + +/* Helper for the linemap code. */ + +static size_t +round_alloc_size (size_t s) +{ + return s; +} + +class impl_diagnostic_client_data_hooks : public diagnostic_client_data_hooks +{ +public: + impl_diagnostic_client_data_hooks (diagnostic_manager &mgr) + : m_mgr (mgr) + {} + + const client_version_info *get_any_version_info () const final override; + const logical_location *get_current_logical_location () const final override; + const char * maybe_get_sarif_source_language (const char *filename) + const final override; + void add_sarif_invocation_properties (sarif_object &invocation_obj) + const final override; + +private: + diagnostic_manager &m_mgr; +}; + +class impl_client_version_info : public client_version_info +{ +public: + const char *get_tool_name () const final override + { + return m_name.get_str (); + } + + char *maybe_make_full_name () const final override + { + return m_full_name.xstrdup (); + } + + const char *get_version_string () const final override + { + return m_version.get_str (); + } + + char *maybe_make_version_url () const final override + { + return m_version_url.xstrdup (); + } + + void for_each_plugin (plugin_visitor &) const final override + { + // No-op. + } + + owned_nullable_string m_name; + owned_nullable_string m_full_name; + owned_nullable_string m_version; + owned_nullable_string m_version_url; +}; + +/* This has to be a "struct" as it is exposed in the C API. */ + +struct diagnostic_manager +{ +public: + diagnostic_manager () + : m_current_diag (nullptr), + m_edit_context (m_file_cache) + { + linemap_init (&m_line_table, BUILTINS_LOCATION); + m_line_table.m_reallocator = xrealloc; + m_line_table.m_round_alloc_size = round_alloc_size; + m_line_table.default_range_bits = 5; + } + ~diagnostic_manager () + { + /* Clean up sinks first, as they can use other fields. */ + for (size_t i = 0; i < m_sinks.size (); i++) + m_sinks[i] = nullptr; + + for (auto iter : m_str_to_file_map) + delete iter.second; + + for (auto iter :m_location_t_map) + delete iter.second; + } + + line_maps *get_line_table () { return &m_line_table; } + file_cache *get_file_cache () { return &m_file_cache; } + + void write_patch (FILE *dst_stream); + + void add_sink (std::unique_ptr sink) + { + m_sinks.push_back (std::move (sink)); + } + + void emit (diagnostic &diag, const char *msgid, va_list *args) + LIBDIAGNOSTICS_PARAM_GCC_FORMAT_STRING(3, 0); + + const diagnostic_file * + new_file (const char *name, + const char *sarif_source_language) + { + if (diagnostic_file **slot = m_str_to_file_map.get (name)) + return *slot; + diagnostic_file *file = new diagnostic_file (name, sarif_source_language); + m_str_to_file_map.put (file->get_name (), file); + return file; + } + + const diagnostic_physical_location * + new_location_from_file_and_line (const diagnostic_file *file, + diagnostic_line_num_t linenum) + { + // FIXME: this is a hack... + /* Build a simple linemap describing some locations. */ + if (LINEMAPS_ORDINARY_USED (&m_line_table) == 0) + linemap_add (&m_line_table, LC_ENTER, false, file->get_name (), 0); + else + { + const line_map *map + = linemap_add (&m_line_table, LC_RENAME_VERBATIM, false, + file->get_name (), 0); + // FIXME: + ((line_map_ordinary *)map)->included_from = UNKNOWN_LOCATION; + } + linemap_line_start (&m_line_table, linenum, 100); + return new_location (linemap_position_for_column (&m_line_table, 0)); + } + + + const diagnostic_physical_location * + new_location_from_file_line_column (const diagnostic_file *file, + diagnostic_line_num_t line_num, + diagnostic_column_num_t column_num) + { + // FIXME: this is a hack... + /* Build a simple linemap describing some locations. */ + linemap_add (&m_line_table, LC_ENTER, false, file->get_name (), 0); + linemap_line_start (&m_line_table, line_num, 100); + return new_location (linemap_position_for_column (&m_line_table, column_num)); + } + + const diagnostic_physical_location * + new_location_from_range (const diagnostic_physical_location *loc_caret, + const diagnostic_physical_location *loc_start, + const diagnostic_physical_location *loc_end) + { + return new_location + (m_line_table.make_location (as_location_t (loc_caret), + as_location_t (loc_start), + as_location_t (loc_end))); + } + + const diagnostic_logical_location * + new_logical_location (enum diagnostic_logical_location_kind_t kind, + const diagnostic_logical_location *parent, + const char *short_name, + const char *fully_qualified_name, + const char *decorated_name) + { + std::unique_ptr logical_loc + = ::make_unique (kind, + parent, + short_name, + fully_qualified_name, + decorated_name); + const diagnostic_logical_location *result = logical_loc.get (); + m_logical_locs.push_back (std::move (logical_loc)); + return result; + } + + void begin_group () + { + for (auto &sink : m_sinks) + sink->begin_group (); + } + + void end_group () + { + for (auto &sink : m_sinks) + sink->end_group (); + } + + const char * + maybe_get_sarif_source_language (const char *filename) + { + if (diagnostic_file **slot = m_str_to_file_map.get (filename)) + { + gcc_assert (*slot); + return (*slot)->get_sarif_source_language (); + } + return nullptr; + } + + const diagnostic *get_current_diag () { return m_current_diag; } + + const client_version_info *get_client_version_info () const + { + return &m_client_version_info; + } + impl_client_version_info *get_client_version_info () + { + return &m_client_version_info; + } + + void + assert_valid_diagnostic_physical_location (const diagnostic_physical_location *loc) const + { + // TODO + (void)loc; + } + + /* FIXME: Various things still used the "line_table" global variable. + Set it to be this diagnostic_manager's m_line_table. + Ideally we should eliminate this global (and this function). */ + void set_line_table_global () const + { + line_table = const_cast (&m_line_table); + } + +private: + const diagnostic_physical_location * + new_location (location_t loc) + { + if (loc == UNKNOWN_LOCATION) + return nullptr; + if (diagnostic_physical_location **slot = m_location_t_map.get (loc)) + return *slot; + diagnostic_physical_location *phys_loc + = new diagnostic_physical_location (loc); + m_location_t_map.put (loc, phys_loc); + return phys_loc; + } + + line_maps m_line_table; + file_cache m_file_cache; + impl_client_version_info m_client_version_info; + std::vector> m_sinks; + hash_map m_str_to_file_map; + hash_map, + diagnostic_physical_location *> m_location_t_map; + std::vector> m_logical_locs; + const diagnostic *m_current_diag; + edit_context m_edit_context; +}; + +class impl_rich_location : public rich_location +{ +public: + impl_rich_location (line_maps *set) + : rich_location (set, UNKNOWN_LOCATION) + {} +}; + +class impl_range_label : public range_label +{ +public: + impl_range_label (const char *text) + : m_text (xstrdup (text)) + {} + + ~impl_range_label () { free (m_text); } + + label_text get_text (unsigned) const final override + { + return label_text::borrow (m_text); + } + +private: + char *m_text; +}; + +class impl_rule : public diagnostic_metadata::rule +{ +public: + impl_rule (const char *title, const char *url) + : m_title (title), + m_url (url) + { + } + + char *make_description () const final override + { + return m_title.xstrdup (); + } + + char *make_url () const final override + { + return m_url.xstrdup (); + } + +private: + owned_nullable_string m_title; + owned_nullable_string m_url; +}; + +/* This has to be a "struct" as it is exposed in the C API. */ + +struct diagnostic +{ +public: + diagnostic (diagnostic_manager &diag_mgr, + enum diagnostic_level level) + : m_diag_mgr (diag_mgr), + m_level (level), + m_rich_loc (diag_mgr.get_line_table ()), + m_logical_loc (nullptr) + {} + + diagnostic_manager &get_manager () const + { + return m_diag_mgr; + } + + enum diagnostic_level get_level () const { return m_level; } + + rich_location *get_rich_location () { return &m_rich_loc; } + const diagnostic_metadata *get_metadata () { return &m_metadata; } + +#if 0 + diagnostic_location_t get_location () const { return m_loc; } +#endif + + void set_cwe (unsigned cwe_id) + { + m_metadata.add_cwe (cwe_id); + } + + void add_rule (const char *title, + const char *url) + { + std::unique_ptr rule = ::make_unique (title, url); + m_metadata.add_rule (*rule.get ()); + m_rules.push_back (std::move (rule)); + } + + void set_location (const diagnostic_physical_location *loc) + { + m_rich_loc.set_range (0, as_location_t (loc), SHOW_RANGE_WITH_CARET); + } + + void + add_location (const diagnostic_physical_location *loc) + { + m_rich_loc.add_range (as_location_t (loc), + SHOW_RANGE_WITHOUT_CARET); + } + + void + add_location_with_label (const diagnostic_physical_location *loc, + const char *text) + { + std::unique_ptr label = ::make_unique (text); + m_rich_loc.add_range (as_location_t (loc), + SHOW_RANGE_WITHOUT_CARET, + label.get ()); + m_labels.push_back (std::move (label)); + } + + void + set_logical_location (const diagnostic_logical_location *logical_loc) + { + m_logical_loc = logical_loc; + } + const diagnostic_logical_location *get_logical_location () const + { + return m_logical_loc; + } + +private: + diagnostic_manager &m_diag_mgr; + enum diagnostic_level m_level; + impl_rich_location m_rich_loc; + const diagnostic_logical_location *m_logical_loc; + diagnostic_metadata m_metadata; + std::vector> m_labels; + std::vector> m_rules; +}; + +static diagnostic_t +diagnostic_t_from_diagnostic_level (enum diagnostic_level level) +{ + switch (level) + { + default: + gcc_unreachable (); + case DIAGNOSTIC_LEVEL_ERROR: + return DK_ERROR; + case DIAGNOSTIC_LEVEL_WARNING: + return DK_WARNING; + case DIAGNOSTIC_LEVEL_NOTE: + return DK_NOTE; + } +} + +/* class impl_diagnostic_client_data_hooks. */ + +const client_version_info * +impl_diagnostic_client_data_hooks::get_any_version_info () const +{ + return m_mgr.get_client_version_info (); +} + +const logical_location * +impl_diagnostic_client_data_hooks::get_current_logical_location () const +{ + gcc_assert (m_mgr.get_current_diag ()); + + return m_mgr.get_current_diag ()->get_logical_location (); +} + +const char * +impl_diagnostic_client_data_hooks:: +maybe_get_sarif_source_language (const char *filename) const +{ + return m_mgr.maybe_get_sarif_source_language (filename); +} + +void +impl_diagnostic_client_data_hooks:: +add_sarif_invocation_properties (sarif_object &) const +{ + // No-op. +} + +/* class sink. */ + +void +sink::emit (diagnostic &diag, const char *msgid, va_list *args) +{ + diagnostic_info info; + diagnostic_set_info (&info, msgid, args, diag.get_rich_location (), + diagnostic_t_from_diagnostic_level (diag.get_level ())); + info.metadata = diag.get_metadata (); + diagnostic_report_diagnostic (&m_dc, &info); +} + +sink::sink (diagnostic_manager &mgr) +: m_mgr (mgr) +{ + diagnostic_initialize (&m_dc, 0); + m_dc.m_client_aux_data = this; + m_dc.set_client_data_hooks (new impl_diagnostic_client_data_hooks (mgr)); +} + +sink::~sink () +{ + diagnostic_finish (&m_dc); +} + +/* struct diagnostic_text_sink : public sink. */ + +diagnostic_text_sink::diagnostic_text_sink (diagnostic_manager &mgr, + FILE *dst_stream, + enum diagnostic_colorize colorize) +: sink (mgr) +{ + m_dc.set_show_cwe (true); + m_dc.set_show_rules (true); + + diagnostic_color_rule_t color_rule; + switch (colorize) + { + default: + gcc_unreachable (); + case DIAGNOSTIC_COLORIZE_IF_TTY: + color_rule = DIAGNOSTICS_COLOR_AUTO; + break; + case DIAGNOSTIC_COLORIZE_NO: + color_rule = DIAGNOSTICS_COLOR_NO; + break; + case DIAGNOSTIC_COLORIZE_YES: + color_rule = DIAGNOSTICS_COLOR_YES; + break; + } + diagnostic_color_init (&m_dc, color_rule); + m_dc.printer->buffer->stream = dst_stream; + diagnostic_starter (&m_dc) + = [] (diagnostic_context *context, + diagnostic_info *info) + { + diagnostic_text_sink *sink + = static_cast (context->m_client_aux_data); + sink->on_begin_text_diagnostic (info); + }; + m_dc.m_source_printing.enabled = true; + m_dc.m_source_printing.colorize_source_p = true; + + /* We don't currently expose a way for clients to manipulate the + following. */ + m_dc.m_source_printing.show_labels_p = true; + m_dc.m_source_printing.show_line_numbers_p = true; + m_dc.m_source_printing.min_margin_width = 6; +} + +void +diagnostic_text_sink::on_begin_text_diagnostic (diagnostic_info *info) +{ + const diagnostic *diag = m_mgr.get_current_diag (); + gcc_assert (diag); + const diagnostic_logical_location *diag_logical_loc + = diag->get_logical_location (); + if (m_current_logical_loc != diag_logical_loc) + { + m_current_logical_loc = diag_logical_loc; + if (m_current_logical_loc) + { + char *old_prefix = pp_take_prefix (m_dc.printer); + switch (m_current_logical_loc->get_kind ()) + { + default: + break; + case LOGICAL_LOCATION_KIND_FUNCTION: + if (const char *name + = m_current_logical_loc->get_name_with_scope ()) + { + pp_printf (m_dc.printer, _("In function %qs"), name); + pp_character (m_dc.printer, ':'); + pp_newline (m_dc.printer); + } + break; + // FIXME: handle other cases + } + m_dc.printer->prefix = old_prefix; + } + } + pp_set_prefix (m_dc.printer, + diagnostic_build_prefix (&m_dc, info)); +} + +/* class sarif_sink : public sink. */ + +sarif_sink::sarif_sink (diagnostic_manager &mgr, + FILE *dst_stream, + enum diagnostic_sarif_version) +: sink (mgr) +{ + diagnostic_output_format_init_sarif_stream (&m_dc, dst_stream); +} + +/* struct diagnostic_manager. */ + +void +diagnostic_manager::write_patch (FILE *dst_stream) +{ + pretty_printer pp; + pp.buffer->stream = dst_stream; + m_edit_context.print_diff (&pp, true); + pp_flush (&pp); +} + +void +diagnostic_manager::emit (diagnostic &diag, const char *msgid, va_list *args) +{ + set_line_table_global (); + + m_current_diag = &diag; + for (auto &sink : m_sinks) + { + va_list arg_copy; + va_copy (arg_copy, *args); + sink->emit (diag, msgid, &arg_copy); + } + + rich_location *rich_loc = diag.get_rich_location (); + if (rich_loc->fixits_can_be_auto_applied_p ()) + m_edit_context.add_fixits (rich_loc); + + m_current_diag = nullptr; +} + +/* Public entrypoints. */ + +/* Public entrypoint for clients to acquire a diagnostic_manager. */ + +diagnostic_manager * +diagnostic_manager_new (void) +{ + return new diagnostic_manager (); +} + +/* Public entrypoint for clients to release a diagnostic_manager. */ + +void +diagnostic_manager_release (diagnostic_manager *diag_mgr) +{ + delete diag_mgr; +} + +/* Public entrypoint. */ + +void +diagnostic_manager_set_tool_name (diagnostic_manager *diag_mgr, + const char *value) +{ + gcc_assert (diag_mgr); + gcc_assert (value); + + diag_mgr->get_client_version_info ()->m_name.set (value); +} + +/* Public entrypoint. */ + +void +diagnostic_manager_set_full_name (diagnostic_manager *diag_mgr, + const char *value) +{ + gcc_assert (diag_mgr); + gcc_assert (value); + + diag_mgr->get_client_version_info ()->m_full_name.set (value); +} + +/* Public entrypoint. */ + +void +diagnostic_manager_set_version_string (diagnostic_manager *diag_mgr, + const char *value) +{ + gcc_assert (diag_mgr); + gcc_assert (value); + + diag_mgr->get_client_version_info ()->m_version.set (value); +} + +/* Public entrypoint. */ + +void +diagnostic_manager_set_version_url (diagnostic_manager *diag_mgr, + const char *value) +{ + gcc_assert (diag_mgr); + gcc_assert (value); + + diag_mgr->get_client_version_info ()->m_version_url.set (value); +} + +/* Public entrypoint. */ + +diagnostic_text_sink * +diagnostic_manager_add_text_sink (diagnostic_manager *diag_mgr, + FILE *dst_stream, + enum diagnostic_colorize colorize) +{ + gcc_assert (diag_mgr); + gcc_assert (dst_stream); + + diagnostic_text_sink *result + = new diagnostic_text_sink (*diag_mgr, dst_stream, colorize); + diag_mgr->add_sink (std::unique_ptr (result)); + return result; +} + +/* Public entrypoint. */ + +void +diagnostic_text_sink_set_source_printing_enabled (diagnostic_text_sink *text_sink, + int value) +{ + gcc_assert (text_sink); + + text_sink->get_source_printing_options ().enabled = value; +} + +/* Public entrypoint. */ + +void +diagnostic_text_sink_set_labelled_source_colorization_enabled (diagnostic_text_sink *text_sink, + int value) +{ + gcc_assert (text_sink); + + text_sink->get_source_printing_options ().colorize_source_p = value; +} + + +/* Public entrypoint. */ + +void +diagnostic_manager_add_sarif_sink (diagnostic_manager *diag_mgr, + FILE *dst_stream, + enum diagnostic_sarif_version version) +{ + gcc_assert (diag_mgr); + gcc_assert (dst_stream); + + diag_mgr->add_sink (make_unique (*diag_mgr, dst_stream, version)); +} + +/* Public entrypoint. */ + +void +diagnostic_manager_write_patch (diagnostic_manager *diag_mgr, + FILE *dst_stream) +{ + gcc_assert (diag_mgr); + gcc_assert (dst_stream); + + diag_mgr->write_patch (dst_stream); +} + +/* Public entrypoint. */ + +const diagnostic_file * +diagnostic_manager_new_file (diagnostic_manager *diag_mgr, + const char *name, + const char *sarif_source_language) +{ + gcc_assert (diag_mgr); + gcc_assert (name); + + return diag_mgr->new_file (name, sarif_source_language); +} + +void +diagnostic_manager_debug_dump_file (diagnostic_manager *, + const diagnostic_file *file, + FILE *out) +{ + gcc_assert (out); + if (file) + { + if (file->get_sarif_source_language ()) + { + fprintf (out, "file(name=\"%s\", sarif_source_language=\"%s\")", + file->get_name (), + file->get_sarif_source_language ()); + } + else + { + fprintf (out, "file(name=\"%s\")", + file->get_name ()); + } + } + else + fprintf (out, "(null)"); +} + + +/* Public entrypoint. */ + +const diagnostic_physical_location * +diagnostic_manager_new_location_from_file_and_line (diagnostic_manager *diag_mgr, + const diagnostic_file *file, + diagnostic_line_num_t linenum) +{ + gcc_assert (diag_mgr); + gcc_assert (file); + + return diag_mgr->new_location_from_file_and_line (file, linenum); +} + +/* Public entrypoint. */ + +const diagnostic_physical_location * +diagnostic_manager_new_location_from_file_line_column (diagnostic_manager *diag_mgr, + const diagnostic_file *file, + diagnostic_line_num_t line_num, + diagnostic_column_num_t column_num) +{ + gcc_assert (diag_mgr); + gcc_assert (file); + + return diag_mgr->new_location_from_file_line_column (file, + line_num, + column_num); +} + +/* Public entrypoint. */ + +const diagnostic_physical_location * +diagnostic_manager_new_location_from_range (diagnostic_manager *diag_mgr, + const diagnostic_physical_location *loc_caret, + const diagnostic_physical_location *loc_start, + const diagnostic_physical_location *loc_end) +{ + gcc_assert (diag_mgr); + + return diag_mgr->new_location_from_range (loc_caret, + loc_start, + loc_end); +} + +/* Public entrypoint. */ + +void +diagnostic_manager_debug_dump_location (const diagnostic_manager *diag_mgr, + const diagnostic_physical_location *loc, + FILE *out) +{ + gcc_assert (diag_mgr); + gcc_assert (out); + + if (loc) + { + const location_t cpplib_loc = as_location_t (loc); + diag_mgr->set_line_table_global (); + const expanded_location exp_loc (expand_location (cpplib_loc)); + + diagnostic_context dc; + diagnostic_initialize (&dc, 0); + dc.m_show_column = true; + + label_text loc_text = dc.get_location_text (exp_loc); + fprintf (out, "%s", loc_text.get ()); + } + else + fprintf (out, "(null)"); +} + +/* Public entrypoint. */ + +const diagnostic_logical_location * +diagnostic_manager_new_logical_location (diagnostic_manager *diag_mgr, + enum diagnostic_logical_location_kind_t kind, + const diagnostic_logical_location *parent, + const char *short_name, + const char *fully_qualified_name, + const char *decorated_name) +{ + gcc_assert (diag_mgr); + + return diag_mgr->new_logical_location (kind, + parent, + short_name, + fully_qualified_name, + decorated_name); +} + +void +diagnostic_manager_debug_dump_logical_location (const diagnostic_manager *diag_mgr, + const diagnostic_logical_location *loc, + FILE *out) +{ + gcc_assert (diag_mgr); + gcc_assert (out); + + if (loc) + { + fprintf (out, "logical_location(kind="); + switch (loc->get_external_kind ()) + { + default: + gcc_unreachable (); + case DIAGNOSTIC_LOGICAL_LOCATION_KIND_FUNCTION: + fprintf (out, "function"); + break; + case DIAGNOSTIC_LOGICAL_LOCATION_KIND_MEMBER: + fprintf (out, "member"); + break; + case DIAGNOSTIC_LOGICAL_LOCATION_KIND_MODULE: + fprintf (out, "module"); + break; + case DIAGNOSTIC_LOGICAL_LOCATION_KIND_NAMESPACE: + fprintf (out, "namespace"); + break; + case DIAGNOSTIC_LOGICAL_LOCATION_KIND_TYPE: + fprintf (out, "file"); + break; + case DIAGNOSTIC_LOGICAL_LOCATION_KIND_RETURN_TYPE: + fprintf (out, "return_type"); + break; + case DIAGNOSTIC_LOGICAL_LOCATION_KIND_PARAMETER: + fprintf (out, "parameter"); + break; + case DIAGNOSTIC_LOGICAL_LOCATION_KIND_VARIABLE: + fprintf (out, "variable"); + break; + } + if (const diagnostic_logical_location *parent = loc->get_parent ()) + diagnostic_manager_debug_dump_logical_location (diag_mgr, + parent, + out); + if (const char *val = loc->get_short_name ()) + fprintf (out, ", short_name=\"%s\"", val); + if (const char *val = loc->get_name_with_scope ()) + fprintf (out, ", fully_qualified_name=\"%s\"", val); + if (const char *val = loc->get_internal_name ()) + fprintf (out, ", decorated_name=\"%s\"", val); + fprintf (out, ")"); + } + else + fprintf (out, "(null)"); +} + +/* Public entrypoint. */ + +void +diagnostic_manager_begin_group (diagnostic_manager *diag_mgr) +{ + gcc_assert (diag_mgr); + diag_mgr->begin_group (); +} + +/* Public entrypoint. */ + +extern void +diagnostic_manager_end_group (diagnostic_manager *diag_mgr) +{ + gcc_assert (diag_mgr); + diag_mgr->end_group (); +} + +/* Public entrypoint. */ + +diagnostic * +diagnostic_begin (diagnostic_manager *diag_mgr, + enum diagnostic_level level) +{ + gcc_assert (diag_mgr); + + return new diagnostic (*diag_mgr, level); +} + +/* Public entrypoint. */ + +void +diagnostic_set_cwe (diagnostic *diag, + unsigned cwe_id) +{ + gcc_assert (diag); + + diag->set_cwe (cwe_id); +} + +/* Public entrypoint. */ + +void +diagnostic_add_rule (diagnostic *diag, + const char *title, + const char *url) +{ + gcc_assert (diag); + + diag->add_rule (title, url); +} + +/* Public entrypoint. */ + +void +diagnostic_set_location (diagnostic *diag, + const diagnostic_physical_location *loc) +{ + gcc_assert (diag); + diag->get_manager ().assert_valid_diagnostic_physical_location (loc); + + diag->set_location (loc); +} + +/* Public entrypoint. */ + +void +diagnostic_add_location (diagnostic *diag, + const diagnostic_physical_location *loc) +{ + gcc_assert (diag); + diag->get_manager ().assert_valid_diagnostic_physical_location (loc); + + diag->add_location (loc); +} + +/* Public entrypoint. */ + +void +diagnostic_add_location_with_label (diagnostic *diag, + const diagnostic_physical_location *loc, + const char *text) +{ + gcc_assert (diag); + diag->get_manager ().assert_valid_diagnostic_physical_location (loc); + gcc_assert (text); + + diag->add_location_with_label (loc, text); +} + +/* Public entrypoint. */ + +void +diagnostic_set_logical_location (diagnostic *diag, + const diagnostic_logical_location *logical_loc) +{ + gcc_assert (diag); + gcc_assert (logical_loc); + + diag->set_logical_location (logical_loc); +} + +/* Public entrypoint. */ + +void +diagnostic_add_fix_it_hint_insert_before (diagnostic *diag, + const diagnostic_physical_location *loc, + const char *addition) +{ + gcc_assert (diag); + diag->get_manager ().assert_valid_diagnostic_physical_location (loc); + gcc_assert (addition); + + diag->get_manager ().set_line_table_global (); + diag->get_rich_location ()->add_fixit_insert_before (as_location_t (loc), + addition); +} + +/* Public entrypoint. */ + +void +diagnostic_add_fix_it_hint_insert_after (diagnostic *diag, + const diagnostic_physical_location *loc, + const char *addition) +{ + gcc_assert (diag); + diag->get_manager ().assert_valid_diagnostic_physical_location (loc); + gcc_assert (addition); + + diag->get_manager ().set_line_table_global (); + diag->get_rich_location ()->add_fixit_insert_after (as_location_t (loc), + addition); +} + +/* Public entrypoint. */ + +void +diagnostic_add_fix_it_hint_replace (diagnostic *diag, + const diagnostic_physical_location *loc, + const char *replacement) +{ + gcc_assert (diag); + diag->get_manager ().assert_valid_diagnostic_physical_location (loc); + gcc_assert (replacement); + + diag->get_manager ().set_line_table_global (); + diag->get_rich_location ()->add_fixit_replace (as_location_t (loc), + replacement); +} + +/* Public entrypoint. */ + +void +diagnostic_add_fix_it_hint_delete (diagnostic *diag, + const diagnostic_physical_location *loc) +{ + gcc_assert (diag); + diag->get_manager ().assert_valid_diagnostic_physical_location (loc); + + diag->get_manager ().set_line_table_global (); + diag->get_rich_location ()->add_fixit_remove (as_location_t (loc)); +} + +/* Public entrypoint. */ + +void +diagnostic_finish (diagnostic *diag, const char *gmsgid, ...) +{ + gcc_assert (diag); + + va_list args; + va_start (args, gmsgid); + diagnostic_finish_va (diag, gmsgid, &args); + va_end (args); +} + +/* Public entrypoint. */ + +void +diagnostic_finish_va (diagnostic *diag, const char *gmsgid, va_list *args) +{ + gcc_assert (diag); + + if (const char *tool_name + = diag->get_manager ().get_client_version_info ()->m_name.get_str ()) + progname = tool_name; + else + progname = "progname"; + auto_diagnostic_group d; + diag->get_manager ().emit (*diag, gmsgid, args); + delete diag; +} diff --git a/gcc/libdiagnostics.map b/gcc/libdiagnostics.map new file mode 100644 index 000000000000..2a7666859fcb --- /dev/null +++ b/gcc/libdiagnostics.map @@ -0,0 +1,63 @@ +# Linker script for libdiagnostics.so +# Copyright (C) 2023 Free Software Foundation, Inc. +# Contributed by David Malcolm . +# +# This file is part of GCC. +# +# GCC is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GCC is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GCC; see the file COPYING3. If not see +# . */ + +# The initial release of the library. +LIBDIAGNOSTICS_ABI_0 +{ + global: + # Keep this list in order of decls in header file. + diagnostic_manager_new; + diagnostic_manager_release; + diagnostic_manager_set_tool_name; + diagnostic_manager_set_full_name; + diagnostic_manager_set_version_string; + diagnostic_manager_set_version_url; + diagnostic_manager_add_text_sink; + diagnostic_text_sink_set_source_printing_enabled; + diagnostic_text_sink_set_labelled_source_colorization_enabled; + diagnostic_manager_add_sarif_sink; + diagnostic_manager_write_patch; + diagnostic_manager_new_file; + diagnostic_manager_debug_dump_file; + diagnostic_manager_new_location_from_file_and_line; + diagnostic_manager_new_location_from_file_line_column; + diagnostic_manager_new_location_from_range; + diagnostic_manager_debug_dump_location; + diagnostic_manager_new_logical_location; + diagnostic_manager_debug_dump_logical_location; + diagnostic_manager_begin_group; + diagnostic_manager_end_group; + diagnostic_begin; + diagnostic_set_cwe; + diagnostic_add_rule; + diagnostic_set_location; + diagnostic_set_location_with_label; + diagnostic_add_location; + diagnostic_add_location_with_label; + diagnostic_set_logical_location; + diagnostic_add_fix_it_hint_insert_before; + diagnostic_add_fix_it_hint_insert_after; + diagnostic_add_fix_it_hint_replace; + diagnostic_add_fix_it_hint_delete; + diagnostic_finish; + diagnostic_finish_va; + + local: *; +}; diff --git a/gcc/testsuite/libdiagnostics.dg/libdiagnostics.exp b/gcc/testsuite/libdiagnostics.dg/libdiagnostics.exp new file mode 100644 index 000000000000..bd50a5568a73 --- /dev/null +++ b/gcc/testsuite/libdiagnostics.dg/libdiagnostics.exp @@ -0,0 +1,544 @@ +# Test code for libdiagnostics.so +# +# FIXME: +# +# Test code for libdiagnostics.so +# We will compile each of jit.dg/test-*.c into an executable +# dynamically linked against libgccjit.so, and then run each +# such executable. +# +# These executables call into the libgccjit.so API to create +# code, compile it, and run it, verifying that the results +# are as expected. See harness.h for shared code used by all +# such executables. +# +# The executables call into DejaGnu's unit testing C API to +# report PASS/FAIL results, which this script gathers back +# up into the Tcl world, reporting a summary of all results +# across all of the executables. + +# Kludge alert: +# We need g++_init so that it can find the stdlib include path. +# +# g++_init (in lib/g++.exp) uses g++_maybe_build_wrapper, +# which normally comes from the definition of +# ${tool}_maybe_build_wrapper within lib/wrapper.exp. +# +# However, for us, ${tool} is "jit". +# Hence we load wrapper.exp with tool == "g++", so that +# g++_maybe_build_wrapper is defined. +set tool g++ +load_lib wrapper.exp +set tool diagnostics + +load_lib dg.exp +load_lib prune.exp +load_lib target-supports.exp +load_lib gcc-defs.exp +load_lib timeout.exp +load_lib target-libpath.exp +load_lib gcc.exp +load_lib g++.exp +load_lib dejagnu.exp +load_lib target-supports-dg.exp + +# # Skip these tests for targets that don't support -ldiagnostics +# if { ![check_effective_target_ldiagnostics] } { +# return +# } + +# The default do-what keyword. +set dg-do-what-default compile + +# Look for lines of the form: +# definitely lost: 11,316 bytes in 235 blocks +# indirectly lost: 352 bytes in 4 blocks +# Ideally these would report zero bytes lost (which is a PASS); +# for now, report non-zero leaks as XFAILs. +proc report_leak {kind name logfile line} { + set match [regexp "$kind lost: .*" $line result] + if $match { + verbose "Saw \"$result\" within \"$line\"" 4 + # Extract bytes and blocks. + # These can contain commas as well as numerals, + # but we only care about whether we have zero. + regexp "$kind lost: (.+) bytes in (.+) blocks" \ + $result -> bytes blocks + verbose "bytes: '$bytes'" 4 + verbose "blocks: '$blocks'" 4 + if { $bytes == 0 } { + pass "$name: $logfile: $result" + } else { + xfail "$name: $logfile: $result" + } + } +} + +proc parse_valgrind_logfile {name logfile} { + verbose "parse_valgrind_logfile: $logfile" 2 + if [catch {set f [open $logfile]}] { + fail "$name: unable to read $logfile" + return + } + + while { [gets $f line] >= 0 } { + # Strip off the PID prefix e.g. ==7675== + set line [regsub "==\[0-9\]*== " $line ""] + verbose $line 2 + + report_leak "definitely" $name $logfile $line + report_leak "indirectly" $name $logfile $line + } + close $f +} + +# Given WRES, the result from "wait", issue a PASS +# if the spawnee exited cleanly, or a FAIL for various kinds of +# unexpected exits. + +proc verify_exit_status { executable wres } { + lassign $wres pid spawnid os_error_flag value + verbose "pid: $pid" 3 + verbose "spawnid: $spawnid" 3 + verbose "os_error_flag: $os_error_flag" 3 + verbose "value: $value" 3 + + # Detect segfaults etc: + if { [llength $wres] > 4 } { + if { [lindex $wres 4] == "CHILDKILLED" } { + fail "$executable killed: $wres" + return + } + } + if { $os_error_flag != 0 } { + fail "$executable: OS error: $wres" + return + } + if { $value != 0 } { + fail "$executable: non-zero exit code: $wres" + return + } + pass "$executable exited cleanly" +} + +# This is host_execute from dejagnu.exp commit +# 126a089777158a7891ff975473939f08c0e31a1c +# with the following patch applied, and renaming to "fixed_host_execute". +# See the discussion at +# http://lists.gnu.org/archive/html/dejagnu/2014-10/msg00000.html +# +# --- /usr/share/dejagnu/dejagnu.exp.old 2014-10-08 13:38:57.274068541 -0400 +# +++ /usr/share/dejagnu/dejagnu.exp 2014-10-10 12:27:51.113813659 -0400 +# @@ -113,8 +113,6 @@ proc host_execute {args} { +# set timetol 0 +# set arguments "" +# +# - expect_before buffer_full { perror "Buffer full" } +# - +# if { [llength $args] == 0} { +# set executable $args +# } else { + + +# Execute the executable file, and anaylyse the output for the +# test state keywords. +# Returns: +# A "" (empty) string if everything worked, or an error message +# if there was a problem. +# +proc fixed_host_execute {args} { + global env + global text + global spawn_id + + verbose "fixed_host_execute: $args" + + set timeoutmsg "Timed out: Never got started, " + set timeout 100 + set file all + set timetol 0 + set arguments "" + + if { [llength $args] == 0} { + set executable $args + } else { + set executable [lindex $args 0] + set params [lindex $args 1] + } + + verbose "The executable is $executable" 2 + if {![file exists ${executable}]} { + perror "The executable, \"$executable\" is missing" 0 + return "No source file found" + } elseif {![file executable ${executable}]} { + perror "The executable, \"$executable\" is not usable" 0 + return "Bad executable found" + } + + verbose "params: $params" 2 + + # spawn the executable and look for the DejaGnu output messages from the + # test case. + # spawn -noecho -open [open "|./${executable}" "r"] + + # Run under valgrind if RUN_UNDER_VALGRIND is present in the environment. + # Note that it's best to configure gcc with --enable-valgrind-annotations + # when testing under valgrind. + set run_under_valgrind [info exists env(RUN_UNDER_VALGRIND)] + if $run_under_valgrind { + set valgrind_logfile "${executable}.valgrind.txt" + set valgrind_params {"valgrind"} + lappend valgrind_params "--leak-check=full" + lappend valgrind_params "--log-file=${valgrind_logfile}" + } else { + set valgrind_params {} + } + verbose "valgrind_params: $valgrind_params" 2 + + set args ${valgrind_params} + lappend args "./${executable}" + set args [concat $args ${params}] + verbose "args: $args" 2 + + # We checked that the executable exists above, and can be executed, but + # that does not cover other reasons that the launch could fail (e.g. + # missing or malformed params); catch such cases here and report them. + set err [catch "spawn -noecho $args" pid] + set sub_proc_id $spawn_id + if { $pid <= 0 || $err != 0 || $sub_proc_id < 0 } { + warning "failed to spawn : $args : err = $err" + } + + # Increase the buffer size, if needed to avoid spurious buffer-full + # events; GCC uses 10000; chose a power of two here. + set current_max_match [match_max -i $sub_proc_id] + if { $current_max_match < 8192 } { + match_max -i $sub_proc_id 8192 + set used [match_max -i $sub_proc_id] + } + + # If we get a buffer-full error, that seems to be unrecoverable so try to + # exit in a reasonable manner to avoid wedged processes. + expect_after full_buffer { + verbose -log "fixed_host_execute: $args FULL BUFFER" + # FIXME: this all assumes that closing the connection will cause the + # sub-process to terminate (but that is not going to be the case if, + # for example, there is something started with -nohup somewhere). + # We should explicitly kill it here. + # Set the process to be a nowait exit. + wait -nowait -i $sub_proc_id + catch close + perror "${executable} got full_buffer" + return "${executable} got full_buffer" + } + + set prefix "\[^\r\n\]*" + # Work around a Darwin tcl or termios bug that sometimes inserts extra + # CR characters into the cooked tty stream + set endline "\r\n" + if { [istarget *-*-darwin*] } { + set endline "\r(\r)*\n" + } + + # Note that the logic here assumes that we cannot (validly) get single + # carriage return or line feed characters in the stream. If those occur, + # it will stop any further matching. We arange for the matching to be + # at the start of the buffer - so that if there is any spurious output + # to be discarded, it must be done explicitly - not by matching part-way + # through the buffer. + expect { + -re "^$prefix\[0-9\]\[0-9\]:..:..:${text}*$endline" { + regsub "\[\n\r\t\]*NOTE: $text\r\n" $expect_out(0,string) "" output + verbose "$output" 3 + set timetol 0 + exp_continue + } + -re "^\tNOTE: (\[^\r\n\]+)$endline" { + # discard notes. + verbose "Ignored note: $expect_out(1,string)" 2 + set timetol 0 + exp_continue + } + -re "^\tPASSED: (\[^\r\n\]+)$endline" { + pass "$expect_out(1,string)" + set timetol 0 + exp_continue + } + -re "^\tFAILED: (\[^\r\n\]+)$endline" { + fail "$expect_out(1,string)" + set timetol 0 + exp_continue + } + -re "^\tUNTESTED: (\[^\r\n\]+)$endline" { + untested "$expect_out(1,string)" + set timetol 0 + exp_continue + } + -re "^\tUNRESOLVED: (\[^\r\n\]+)$endline" { + unresolved "$expect_out(1,string)" + set timetol 0 + exp_continue + } + -re "^$prefix$endline" { + # This matches and discards any other lines (including blank ones). + if { [string length $expect_out(buffer)] <= 2 } { + set output "blank line" + } else { + set output [string range $expect_out(buffer) 0 end-2] + } + verbose -log "DISCARDED $expect_out(spawn_id) : $output" + exp_continue + } + eof { + # This seems to be the only way that we can reliably know that the + # output is finished since there are cases where further output + # follows the dejagnu test harness totals. + verbose "saw eof" 2 + } + timeout { + if { $timetol <= 2 } { + verbose -log "Timed out with retry (timeout = $timeout)" + incr timetol + exp_continue + } else { + warning "Timed out executing testcase (timeout = $timeout)" + catch close + return "Timed out executing test case" + } + } + } + + # Use "wait" to pick up the sub-process exit state. If the sub-process is + # writing to a file (perhaps under valgrind) then that also needs to be + # complete; only attempt this on a valid spawn. + if { $sub_proc_id > 0 } { + verbose "waiting for $sub_proc_id" 1 + # Be explicit about what we are waiting for. + catch "wait -i $sub_proc_id" wres + verbose "wres: $wres" 2 + verify_exit_status $executable $wres + } + + if $run_under_valgrind { + upvar 2 name name + parse_valgrind_logfile $name $valgrind_logfile + } + + # force a close of the executable to be safe. + catch close + + return "" +} + +# (end of code from dejagnu.exp) + +# GCC_UNDER_TEST is needed by gcc_target_compile +global GCC_UNDER_TEST +if ![info exists GCC_UNDER_TEST] { + set GCC_UNDER_TEST "[find_gcc]" +} + +g++_init + +# Initialize dg. +dg-init + +# Gather a list of all tests. + +# C tests within the testsuite: gcc/testsuite/libdiagnostics.dg/test-*.c +set tests [find $srcdir/$subdir test-*.c] + +set tests [lsort $tests] + +verbose "tests: $tests" + +# libgloss has found the driver (as "xgcc" or "gcc) and stored +# its full path as GCC_UNDER_TEST. +proc get_path_of_driver {} { + global GCC_UNDER_TEST + + verbose "GCC_UNDER_TEST: $GCC_UNDER_TEST" + set binary [lindex $GCC_UNDER_TEST 0] + verbose "binary: $binary" + + return [file dirname $binary] +} + +# Expand "SRCDIR" within ARG to the location of the top-level +# src directory + +proc diagnostics-expand-vars {arg} { + verbose "diagnostics-expand-vars: $arg" + global srcdir + verbose " srcdir: $srcdir" + # "srcdir" is that of the gcc/testsuite directory, so + # we need to go up two levels. + set arg [string map [list "SRCDIR" $srcdir/../..] $arg] + verbose " new arg: $arg" + return $arg +} + +# Parameters used when invoking the executables built from the test cases. + +global diagnostics-exe-params +set diagnostics-exe-params {} + +# Set "diagnostics-exe-params", expanding "SRCDIR" in each arg to the location of +# the top-level srcdir. + +proc dg-diagnostics-set-exe-params { args } { + verbose "dg-diagnostics-set-exe-params: $args" + + global diagnostics-exe-params + set diagnostics-exe-params {} + # Skip initial arg (line number) + foreach arg [lrange $args 1 [llength $args] ] { + lappend diagnostics-exe-params [diagnostics-expand-vars $arg] + } +} + +proc diagnostics-dg-test { prog do_what extra_tool_flags } { + verbose "within diagnostics-dg-test..." + verbose " prog: $prog" + verbose " do_what: $do_what" + verbose " extra_tool_flags: $extra_tool_flags" + + global dg-do-what-default + set dg-do-what [list ${dg-do-what-default} "" P] + + set tmp [dg-get-options $prog] + foreach op $tmp { + verbose "Processing option: $op" 3 + set status [catch "$op" errmsg] + if { $status != 0 } { + if { 0 && [info exists errorInfo] } { + # This also prints a backtrace which will just confuse + # testcase writers, so it's disabled. + perror "$name: $errorInfo\n" + } else { + perror "$name: $errmsg for \"$op\"\n" + } + perror "$name: $errmsg for \"$op\"" 0 + return + } + } + + # If we're not supposed to try this test on this target, we're done. + if { [lindex ${dg-do-what} 1] == "N" } { + unsupported "$name" + verbose "$name not supported on this target, skipping it" 3 + return + } + + # Determine what to name the built executable. + # + # We simply append .exe to the filename, e.g. + # "test-foo.c.exe" + # since some testcases exist in both + # "test-foo.c" and + # "test-foo.cc" + # variants, and we don't want them to clobber each other's + # executables. + # + # This also ensures that the source name makes it into the + # pass/fail output, so that we can distinguish e.g. which test-foo + # is failing. + set output_file "[file tail $prog].exe" + verbose "output_file: $output_file" + + # Create the test executable: + set extension [file extension $prog] + if {$extension == ".cc"} { + set compilation_function "g++_target_compile" + set options "{additional_flags=$extra_tool_flags}" + } else { + set compilation_function "gcc_target_compile" + # Until recently, assumed -fgnu89-inline + # Ideally we should fixincludes it (PR other/63613), but + # for now add -fgnu89-inline when compiling C JIT testcases. + # See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=63613 + # and http://lists.gnu.org/archive/html/dejagnu/2014-10/msg00011.html + set options "{additional_flags=$extra_tool_flags -fgnu89-inline}" + } + verbose "compilation_function=$compilation_function" + verbose "options=$options" + + set comp_output [$compilation_function $prog $output_file \ + "executable" $options] + upvar 1 name name + if ![diagnostics_check_compile "$name" "initial compilation" \ + $output_file $comp_output] then { + return + } + + # Run the test executable, capturing the PASS/FAIL textual output + # from the C API, converting it into the Tcl API. + + # We need to set LD_LIBRARY_PATH so that the test files can find + # libdiagnostics.so + # Do this using set_ld_library_path_env_vars from target-libpath.exp + # We will restore the old value later using + # restore_ld_library_path_env_vars. + + # Unfortunately this API only supports a single saved value, rather + # than a stack, and g++_init has already called into this API, + # injecting the appropriate value for LD_LIBRARY_PATH for finding + # the built copy of libstdc++. + # Hence the call to restore_ld_library_path_env_vars would restore + # the *initial* value of LD_LIBRARY_PATH, and attempts to run + # a C++ testcase after running any prior testcases would thus look + # in the wrong place for libstdc++. This led to failures at startup + # of the form: + # ./tut01-hello-world.cc.exe: /lib64/libstdc++.so.6: version `GLIBCXX_3.4.21' not found (required by ./tut01-hello-world.cc.exe) + # when the built libstdc++ is more recent that the system libstdc++. + # + # As a workaround, reset the variable "orig_environment_saved" within + # target-libpath.exp, so that the {set|restore}_ld_library_path_env_vars + # API saves/restores the current value of LD_LIBRARY_PATH (as set up + # by g++_init). + global orig_environment_saved + set orig_environment_saved 0 + + global ld_library_path + global base_dir + set ld_library_path "$base_dir/../../" + set_ld_library_path_env_vars + + # dejagnu.exp's host_execute has code to scrape out test results + # from the DejaGnu C API and bring back into the tcl world, so we + # use that to invoke the built code. + # However, it appears to be buggy; see: + # http://lists.gnu.org/archive/html/dejagnu/2014-10/msg00000.html + # We instead call a patched local copy, "fixed_host_execute", defined + # above. + + global diagnostics-exe-params + set args ${diagnostics-exe-params} + set diagnostics-exe-params {} + + set result [fixed_host_execute $output_file $args ] + verbose "result: $result" + + restore_ld_library_path_env_vars + + # Normally we would return $comp_output and $output_file to the + # caller, which would delete $output_file, the generated executable. + # If we need to debug, it's handy to be able to suppress this behavior, + # keeping the executable around. + + global env + set preserve_executables [info exists env(PRESERVE_EXECUTABLES)] + if $preserve_executables { + set output_file "" + } + + return [list $comp_output $output_file] +} + +set DEFAULT_CFLAGS "-I$srcdir/.. -ldiagnostics -g -Wall -Werror" + +# Main loop. This will invoke jig-dg-test on each test-*.c file. +dg-runtest $tests "" $DEFAULT_CFLAGS + +# All done. +dg-finish -- 2.26.3