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 1AB563858005 for ; Mon, 6 Nov 2023 22:30:16 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 1AB563858005 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 1AB563858005 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=1699309821; cv=none; b=az0F7n7azFGSFRBBwRpbtEQB+C5EcCST9pImKqoVs4AbOsfojtoa0Pxfo1U3xMltCOa1DH84In6so48UIdq27CFTjQmPCZacwev8f3g7SOjC3W8bTLuvtVCnLUK6bNZ/BN813ujhpsXaZV54Z1rJbub7S2W/pzpimiBzoF104T8= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1699309821; c=relaxed/simple; bh=nb0mGhL4vqEj3sRDgPkMHcxYS/fwBJvWzE/ry172FoA=; h=DKIM-Signature:From:To:Subject:Date:Message-Id:MIME-Version; b=WC8x7Vz1VlHsu4j/PQLCpA37AZYsMpy4VV2Hj2xNL7WJ84t24UfH36dSie5jZUaCy8fB4LxG9gRzs9d4NQBxYqlAZzwktsYddy0umF+D9GX9VYVL0q9dhRsmwZi4TI/I4JZ78edNILfPN+s8Pf8HOcLappzTMrLCnYyvW70IDDc= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1699309815; 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=tRnK28vswCgwVvn/mhaLh/k46QQdCeMEnw63fCt80u0=; b=S0b3ztPK7TC9xFoZI6Ga9SS8ZGLFrs0e9JNxKdTdfiATbMjUAdQSstng/GuDmZgmPUfOjm cF5YX3ytCjUM422i7gSDJcswDSiGdKpDhrF38w030iCQMfhnuVn+8VH3en2orP7qrKkj2D je66mZbt9+qUZWAJmofowB3dh+IzER8= Received: from mimecast-mx02.redhat.com (mimecast-mx02.redhat.com [66.187.233.88]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-558-hXqfhPJ4PQKt5wLsjC3ffQ-1; Mon, 06 Nov 2023 17:30:12 -0500 X-MC-Unique: hXqfhPJ4PQKt5wLsjC3ffQ-1 Received: from smtp.corp.redhat.com (int-mx02.intmail.prod.int.rdu2.redhat.com [10.11.54.2]) (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 77C50811E7D; Mon, 6 Nov 2023 22:30:12 +0000 (UTC) Received: from t14s.localdomain.com (unknown [10.22.8.62]) by smtp.corp.redhat.com (Postfix) with ESMTP id 2834040C6EB9; Mon, 6 Nov 2023 22:30:12 +0000 (UTC) From: David Malcolm To: gcc-patches@gcc.gnu.org, binutils@sourceware.org Cc: Nick Clifton , Simon Sobisch , David Malcolm Subject: [PATCH 2/2] libdiagnostics: work-in-progress implementation Date: Mon, 6 Nov 2023 17:29:58 -0500 Message-Id: <20231106222959.2707741-3-dmalcolm@redhat.com> In-Reply-To: <20231106222959.2707741-1-dmalcolm@redhat.com> References: <20231106222959.2707741-1-dmalcolm@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.4.1 on 10.11.54.2 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.4 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=unavailable autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on server2.sourceware.org List-Id: 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. For example, the testsuite doesn't yet check the output from the test client programs (and I'm not quite sure of the best way to express that in DejaGnu). 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. * input.h: Add FIXME. * libdiagnostics.cc: New file. * libdiagnostics.map: New file. gcc/testsuite/ChangeLog: * libdiagnostics.dg/libdiagnostics.exp: New, based on jit.exp. --- gcc/Makefile.in | 134 +- gcc/configure | 2 +- gcc/configure.ac | 2 +- gcc/input.h | 2 +- gcc/libdiagnostics.cc | 1124 +++++++++++++++++ gcc/libdiagnostics.map | 57 + .../libdiagnostics.dg/libdiagnostics.exp | 544 ++++++++ 7 files changed, 1860 insertions(+), 5 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 ff77d3cdc64..8f93ae48024 100644 --- a/gcc/Makefile.in +++ b/gcc/Makefile.in @@ -611,7 +611,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@ @@ -2153,7 +2153,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 @@ -2242,6 +2242,136 @@ 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) \ + $(FULL_DRIVER_NAME) + +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) \ + $(FULL_DRIVER_NAME) + +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) \ + $(FULL_DRIVER_NAME) + +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 77f33ee4df6..abcb70b1b5f 100755 --- a/gcc/configure +++ b/gcc/configure @@ -31997,7 +31997,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 10982cdfc09..5968670b2d6 100644 --- a/gcc/configure.ac +++ b/gcc/configure.ac @@ -7281,7 +7281,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/input.h b/gcc/input.h index 5eac1dc40a6..e4d230cc112 100644 --- a/gcc/input.h +++ b/gcc/input.h @@ -113,10 +113,10 @@ class char_span size_t m_n_elts; }; +// FIXME: eliminate these; use global_dc->m_file_cache extern char_span location_get_source_line (const char *file_path, int line); extern char *get_source_text_between (location_t, location_t); extern char_span get_source_file_content (const char *file_path); - extern bool location_missing_trailing_newline (const char *file_path); /* Forward decl of slot within file_cache, so that the definition doesn't diff --git a/gcc/libdiagnostics.cc b/gcc/libdiagnostics.cc new file mode 100644 index 00000000000..788b28edc53 --- /dev/null +++ b/gcc/libdiagnostics.cc @@ -0,0 +1,1124 @@ +/* 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_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; + } + } + +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); + + static char * + get_option_name_cb (diagnostic_context *, int, diagnostic_t, diagnostic_t) + { + return nullptr; // FIXME + } + + diagnostic_manager &m_mgr; + + /* One context per sink. */ + diagnostic_context m_dc; +}; + +class text_sink : public sink +{ +public: + text_sink (diagnostic_manager &mgr, + FILE *dst_stream, + enum diagnostic_colorize colorize); + + void + on_begin_text_diagnostic (diagnostic_info *info); + +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) + { + 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; + } + + 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; + } + + diagnostic_location_t + 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 linemap_position_for_column (&m_line_table, 0); + } + + + diagnostic_location_t + 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 linemap_position_for_column (&m_line_table, column_num); + } + + diagnostic_location_t + new_location_from_range (diagnostic_location_t loc_caret, + diagnostic_location_t loc_start, + diagnostic_location_t loc_end) + { + return m_line_table.make_location (loc_caret, loc_start, 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_location_t (diagnostic_location_t 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 () + { + line_table = &m_line_table; + } + +private: + 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; + 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 (diagnostic_location_t loc) + { + m_rich_loc.set_range (0, loc, SHOW_RANGE_WITH_CARET); + } + + void + add_location (diagnostic_location_t loc) + { + m_rich_loc.add_range (loc, SHOW_RANGE_WITHOUT_CARET); + } + + void + add_location_with_label (diagnostic_location_t loc, + const char *text) + { + std::unique_ptr label = ::make_unique (text); + m_rich_loc.add_range (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.m_option_name = get_option_name_cb; + m_dc.set_client_data_hooks (new impl_diagnostic_client_data_hooks (mgr)); + m_dc.file_cache_init (); +} + +sink::~sink () +{ + diagnostic_finish (&m_dc); +} + +/* class text_sink : public sink. */ + +text_sink::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; + m_dc.m_text_callbacks.begin_diagnostic + = [] (diagnostic_context *context, + diagnostic_info *info) + { + text_sink *sink = static_cast (context->m_client_aux_data); + sink->on_begin_text_diagnostic (info); + }; + m_dc.m_source_printing.enabled = true; // FIXME + m_dc.m_source_printing.colorize_source_p = true; // FIXME + m_dc.m_source_printing.show_labels_p = true; // FIXME + m_dc.m_source_printing.show_line_numbers_p = true; // FIXME + m_dc.m_source_printing.min_margin_width = 6; // FIXME +} + +void +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. */ + +void +diagnostic_manager_add_text_sink (diagnostic_manager *diag_mgr, + FILE *dst_stream, + enum diagnostic_colorize colorize) +{ + gcc_assert (diag_mgr); + gcc_assert (dst_stream); + + diag_mgr->add_sink (make_unique (*diag_mgr, dst_stream, colorize)); +} + +/* 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); +} + +/* Public entrypoint. */ + +diagnostic_location_t +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. */ + +diagnostic_location_t +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. */ + +diagnostic_location_t +diagnostic_manager_new_location_from_range (diagnostic_manager *diag_mgr, + diagnostic_location_t loc_caret, + diagnostic_location_t loc_start, + diagnostic_location_t loc_end) +{ + gcc_assert (diag_mgr); + + return diag_mgr->new_location_from_range (loc_caret, + loc_start, + loc_end); +} + + +// FIXME: emit text to stderr + +#if 0 +void +diagnostic_location_debug (diagnostic_manager *diag_mgr, + diagnostic_location_t loc, + FILE *stream) +{ + text_sink sink (stream); + diagnostic d (*diag_mgr, DIAGNOSTIC_LEVEL_NOTE); +} +#endif + +/* 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); +} + +/* 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, + diagnostic_location_t loc) +{ + gcc_assert (diag); + diag->get_manager ().assert_valid_diagnostic_location_t (loc); + + diag->set_location (loc); +} + +/* Public entrypoint. */ + +void +diagnostic_add_location (diagnostic *diag, + diagnostic_location_t loc) +{ + gcc_assert (diag); + diag->get_manager ().assert_valid_diagnostic_location_t (loc); + + diag->add_location (loc); +} + +/* Public entrypoint. */ + +void +diagnostic_add_location_with_label (diagnostic *diag, + diagnostic_location_t loc, + const char *text) +{ + gcc_assert (diag); + diag->get_manager ().assert_valid_diagnostic_location_t (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, + diagnostic_location_t loc, + const char *addition) +{ + gcc_assert (diag); + diag->get_manager ().assert_valid_diagnostic_location_t (loc); + gcc_assert (addition); + + diag->get_manager ().set_line_table_global (); + diag->get_rich_location ()->add_fixit_insert_before (loc, addition); +} + +/* Public entrypoint. */ + +void +diagnostic_add_fix_it_hint_insert_after (diagnostic *diag, + diagnostic_location_t loc, + const char *addition) +{ + gcc_assert (diag); + diag->get_manager ().assert_valid_diagnostic_location_t (loc); + gcc_assert (addition); + + diag->get_manager ().set_line_table_global (); + diag->get_rich_location ()->add_fixit_insert_after (loc, addition); +} + +/* Public entrypoint. */ + +void +diagnostic_add_fix_it_hint_replace (diagnostic *diag, + diagnostic_location_t loc, + const char *replacement) +{ + gcc_assert (diag); + diag->get_manager ().assert_valid_diagnostic_location_t (loc); + gcc_assert (replacement); + + diag->get_manager ().set_line_table_global (); + diag->get_rich_location ()->add_fixit_replace (loc, replacement); +} + +/* Public entrypoint. */ + +void +diagnostic_add_fix_it_hint_delete (diagnostic *diag, + diagnostic_location_t loc) +{ + gcc_assert (diag); + diag->get_manager ().assert_valid_diagnostic_location_t (loc); + + diag->get_manager ().set_line_table_global (); + diag->get_rich_location ()->add_fixit_remove (loc); +} + +/* Public entrypoint. */ + +void +diagnostic_finish (diagnostic *diag, const char *gmsgid, ...) +{ + 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; + va_list args; + va_start (args, gmsgid); + gcc_assert (diag); + diag->get_manager ().emit (*diag, gmsgid, &args); + va_end (args); + delete diag; +} diff --git a/gcc/libdiagnostics.map b/gcc/libdiagnostics.map new file mode 100644 index 00000000000..06da1d5c9c2 --- /dev/null +++ b/gcc/libdiagnostics.map @@ -0,0 +1,57 @@ +# 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_manager_add_sarif_sink; + diagnostic_manager_write_patch; + diagnostic_manager_new_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_new_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; + + local: *; +}; diff --git a/gcc/testsuite/libdiagnostics.dg/libdiagnostics.exp b/gcc/testsuite/libdiagnostics.dg/libdiagnostics.exp new file mode 100644 index 00000000000..bd50a5568a7 --- /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