From patchwork Thu Aug 15 18:11:50 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: David Malcolm X-Patchwork-Id: 1972871 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; dkim=pass (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=i6AOE/E/; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=gcc.gnu.org (client-ip=2620:52:3:1:0:246e:9693:128c; helo=server2.sourceware.org; envelope-from=gcc-patches-bounces~incoming=patchwork.ozlabs.org@gcc.gnu.org; receiver=patchwork.ozlabs.org) Received: from server2.sourceware.org (server2.sourceware.org [IPv6:2620:52:3:1:0:246e:9693:128c]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (secp384r1) server-digest SHA384) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4WlCsB6kCvz1yXl for ; Fri, 16 Aug 2024 04:12:46 +1000 (AEST) Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id BB4E6385B50C for ; Thu, 15 Aug 2024 18:12:44 +0000 (GMT) X-Original-To: gcc-patches@gcc.gnu.org Delivered-To: gcc-patches@gcc.gnu.org Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by sourceware.org (Postfix) with ESMTP id C1F353858D34 for ; Thu, 15 Aug 2024 18:12:05 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org C1F353858D34 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 C1F353858D34 Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=170.10.129.124 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1723745531; cv=none; b=btPAOPU2SjRC7wXy7/VO0mXue1gh4fawI9BHv46fVZqqIuw2jr7qX22BByP9O2PSH7Y5M8j6CA1HY0eSTgCZt1sbDd+DgfpBmuToAyaTfLJbOvwifJS0RnunCrdSsisI2IXs68cruJy7eWT6TezDPBkx+lI76aqT0CDJZE+OYqQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1723745531; c=relaxed/simple; bh=Eageo2KCq3THGiQXBehQ7Zlr4LoCugBz+HFDYA4uWSI=; h=DKIM-Signature:From:To:Subject:Date:Message-Id:MIME-Version; b=KbICyUacB5GJwdPIg2n4M7WABAXEp3vmC0ufFThZ4owQxEPDnWChfkjsR6QpIai62HpxumB0iUqL/6IVbQ1lQxGkwi+XvaQbeHgWwigEMhzLzj13Mu8DmPH1vrZ7c+fwcZHZdfb531RfVv051VY9gnSnGeAHLFry/3tjiJBnhGk= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1723745525; 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=wjGDFkPD3X7d9yOZ0sN7yO7QI2OrZt6LbBw80nDMRVc=; b=i6AOE/E/FeJqzL1fagmIU0N4d5wMVaNAb+b5jh2u0JTTwX8cFEk1FEjnkshWRgczAbXBFu HYD6bGa6i54W9NrvpDpNF4sZ1H9mjTKYydPUaEWmrfZPG5o2JZaAgOYRg29XEqNfGwF5D/ YZaSWvQUIYr7iiYsf3itYRWuLfQjF9k= Received: from mx-prod-mc-01.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-117-ex1_uc4RM2ikYpn-hyG7Lg-1; Thu, 15 Aug 2024 14:12:02 -0400 X-MC-Unique: ex1_uc4RM2ikYpn-hyG7Lg-1 Received: from mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.17]) (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 mx-prod-mc-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 4F8631955BF8 for ; Thu, 15 Aug 2024 18:12:01 +0000 (UTC) Received: from t14s.localdomain.com (unknown [10.22.16.43]) by mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 41D2D1955D47; Thu, 15 Aug 2024 18:12:00 +0000 (UTC) From: David Malcolm To: gcc-patches@gcc.gnu.org Cc: David Malcolm Subject: [PATCH 1/7] libdiagnostics v3: header Date: Thu, 15 Aug 2024 14:11:50 -0400 Message-Id: <20240815181156.1815075-2-dmalcolm@redhat.com> In-Reply-To: <20240815181156.1815075-1-dmalcolm@redhat.com> References: <20240815181156.1815075-1-dmalcolm@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.0 on 10.30.177.17 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com 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 X-BeenThere: gcc-patches@gcc.gnu.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Gcc-patches mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: gcc-patches-bounces~incoming=patchwork.ozlabs.org@gcc.gnu.org Changed in v3: * Added support for execution paths * Moved the test cases to another patch * diagnostic_manager_add_sarif_sink: add param "main_input_file" * Added diagnostic_text_sink_set_colorize * Added DIAGNOSTIC_LEVEL_SORRY * Updated copyright year Changed in v2: * Changed from diagnostic_location_t -> const diagnostic_physical_location * * Add entrypoint: diagnostic_finish_va * add new type diagnostic_text_sink, and new entrypoints for enabling/disabling options on it * add new debugging entrypoints for dumping objects to a FILE * * new test cases Blurb from v1: Here's a work-in-progress patch for GCC that adds a libdiagnostics.h header describing the public interface, along with various testcases that show usage examples for the API. Various aspects of this need work; posting now for early feedback on overall direction. How does the interface look? gcc/ChangeLog: * libdiagnostics.h: New file. --- gcc/libdiagnostics.h | 691 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 691 insertions(+) create mode 100644 gcc/libdiagnostics.h diff --git a/gcc/libdiagnostics.h b/gcc/libdiagnostics.h new file mode 100644 index 000000000000..43138a911123 --- /dev/null +++ b/gcc/libdiagnostics.h @@ -0,0 +1,691 @@ +/* A pure C API for emitting diagnostics. + Copyright (C) 2023-2024 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 +. */ + +#ifndef LIBDIAGNOSTICS_H +#define LIBDIAGNOSTICS_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/********************************************************************** + Compatibility macros. + **********************************************************************/ + +/* This macro simplifies testing whether we are using gcc, and if it + is of a particular minimum version. (Both major & minor numbers are + significant.) This macro will evaluate to 0 if we are not using + gcc at all. */ +#define LIBDIAGNOSTICS_GCC_VERSION (__GNUC__ * 1000 + __GNUC_MINOR__) + +/********************************************************************** + Macros for attributes. + **********************************************************************/ + +# if (LIBDIAGNOSTICS_GCC_VERSION >= 3003) +# define LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL(ARG_NUM) __attribute__ ((__nonnull__ (ARG_NUM))) +# else +# define LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL(ARG_NUM) +# endif /* GNUC >= 3.3 */ + +#define LIBDIAGNOSTICS_PARAM_CAN_BE_NULL(ARG_NUM) + /* empty; for the human reader */ + +#define LIBDIAGNOSTICS_PARAM_GCC_FORMAT_STRING(FMT_ARG_NUM, ARGS_ARG_NUM) \ + LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (FMT_ARG_NUM) + /* In theory we'd also add + __attribute__ ((__format__ (__gcc_diag__, FMT_ARG_NUM, ARGS_ARG_NUM))) + if LIBDIAGNOSTICS_GCC_VERSION >= 4001 + However, doing so leads to warnings from -Wformat-diag, which is part + of -Wall but undocumented, and much fussier than I'd want to inflict + on users of libdiagnostics. */ + +/********************************************************************** + Data structures and types. + All structs within the API are opaque. + **********************************************************************/ + +/* An opaque bundle of state for a client of the library. + Has zero of more "sinks" to which diagnostics are emitted. + Responsibilities: + - location-management + - caching of source file content + - patch generation. */ +typedef struct diagnostic_manager diagnostic_manager; + +/* Types relating to diagnostic output sinks. */ + +typedef struct diagnostic_text_sink diagnostic_text_sink; + +/* An enum for determining if we should colorize a text output sink. */ +enum diagnostic_colorize +{ + DIAGNOSTIC_COLORIZE_IF_TTY, + DIAGNOSTIC_COLORIZE_NO, + DIAGNOSTIC_COLORIZE_YES +}; + +/* An enum for choosing the SARIF version for a SARIF output sink. + Eventually the SARIF output may support multiple SARIF versions. */ + +enum diagnostic_sarif_version +{ + DIAGNOSTIC_SARIF_VERSION_2_1_0 +}; + +/* Types relating to "physical" source locations i.e. locations within + specific files expressed via line/column. */ + +/* Opaque type describing a particular input file. */ +typedef struct diagnostic_file diagnostic_file; + +/* Opaque type representing a key into a database of source locations within + a diagnostic_manager. Locations are created by various API calls into + the diagnostic_manager expressing source code points and ranges. They + persist until the diagnostic_manager is released, which cleans them + up. + + NULL means "UNKNOWN", and can be returned by the manager as a + fallback when a problem occurs (e.g. too many locations). + + A diagnostic_location can be a single point within the source code, + such as here (at the the '"' at the start of the string literal): + + | int i = "foo"; + | ^ + + or be a range with a start and finish, and a "caret" location. + + | a = (foo && bar) + | ~~~~~^~~~~~~ + + where the caret here is at the first "&", and the start and finish + are at the parentheses. */ + +typedef struct diagnostic_physical_location diagnostic_physical_location; + +/* Types for storing line and column information in text files. + + Both libdiagnostics and emacs number source *lines* starting at 1, but + they have differing conventions for *columns*. + + libdiagnostics uses a 1-based convention for source columns, + whereas Emacs's M-x column-number-mode uses a 0-based convention. + + For example, an error in the initial, left-hand + column of source line 3 is reported by libdiagnostics as: + + some-file.c:3:1: error: ...etc... + + On navigating to the location of that error in Emacs + (e.g. via "next-error"), + the locus is reported in the Mode Line + (assuming M-x column-number-mode) as: + + some-file.c 10% (3, 0) + + i.e. "3:1:" in libdiagnostics corresponds to "(3, 0)" in Emacs. */ + +typedef unsigned int diagnostic_line_num_t; +typedef unsigned int diagnostic_column_num_t; + +/* An opaque type describing a "logical" source location + e.g. "within function 'foo'". */ + +typedef struct diagnostic_logical_location diagnostic_logical_location; + +/* An enum for discriminating between different kinds of logical location + for a diagnostic. + + Roughly corresponds to logicalLocation's "kind" property in SARIF v2.1.0 + (section 3.33.7). */ + +enum diagnostic_logical_location_kind_t +{ + DIAGNOSTIC_LOGICAL_LOCATION_KIND_FUNCTION, + DIAGNOSTIC_LOGICAL_LOCATION_KIND_MEMBER, + DIAGNOSTIC_LOGICAL_LOCATION_KIND_MODULE, + DIAGNOSTIC_LOGICAL_LOCATION_KIND_NAMESPACE, + DIAGNOSTIC_LOGICAL_LOCATION_KIND_TYPE, + DIAGNOSTIC_LOGICAL_LOCATION_KIND_RETURN_TYPE, + DIAGNOSTIC_LOGICAL_LOCATION_KIND_PARAMETER, + DIAGNOSTIC_LOGICAL_LOCATION_KIND_VARIABLE +}; + +/* A "diagnostic" is an opaque bundle of state for a particular + diagnostic that is being constructed in memory. + + A diagnostic has a primary location and zero or more secondary + locations. For example: + + | a = (foo && bar) + | ~~~~~^~~~~~~ + + This diagnostic has a single diagnostic_location, with the caret + at the first "&", and the start/finish at the parentheses. + + Contrast with: + + | a = (foo && bar) + | ~~~ ^~ ~~~ + + This diagnostic has three locations + - The primary location (at "&&") has its caret and start location at + the first "&" and end at the second "&. + - The secondary location for "foo" has its start and finish at the "f" + and "o" of "foo"; the caret is not flagged for display, but is perhaps at + the "f" of "foo". + - Similarly, the other secondary location (for "bar") has its start and + finish at the "b" and "r" of "bar"; the caret is not flagged for + display, but is perhaps at the"b" of "bar". */ +typedef struct diagnostic diagnostic; + +enum diagnostic_level +{ + DIAGNOSTIC_LEVEL_ERROR, + DIAGNOSTIC_LEVEL_WARNING, + DIAGNOSTIC_LEVEL_NOTE, + + /* A problem where the input is valid, but the tool isn't + able to handle it. */ + DIAGNOSTIC_LEVEL_SORRY +}; + +/* Types for working with execution paths. */ +typedef struct diagnostic_execution_path diagnostic_execution_path; +typedef int diagnostic_event_id; + +/********************************************************************** + API entrypoints. + **********************************************************************/ + +/* Create a new diagnostic_manager. + The client needs to call diagnostic_release_manager on it at some + point. + Note that no output sinks are created by default. */ + +extern diagnostic_manager * +diagnostic_manager_new (void); + +/* Release a diagnostic_manager. + This will flush output to all of the output sinks, and clean up. */ + +extern void +diagnostic_manager_release (diagnostic_manager *) + LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1); + +/* Optional metadata about the manager. */ + +/* Set a string suitable for use as the value of the SARIF "name" property + (SARIF v2.1.0 section 3.19.8). */ + +extern void +diagnostic_manager_set_tool_name (diagnostic_manager *diag_mgr, + const char *value) + LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1) + LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (2); + +/* Set a string suitable for use as the value of the SARIF "fullName" property + (SARIF v2.1.0 section 3.19.9). */ + +extern void +diagnostic_manager_set_full_name (diagnostic_manager *diag_mgr, + const char *value) + LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1) + LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (2); + +/* Set a string suitable for use as the value of the SARIF "version" property + (SARIF v2.1.0 section 3.19.13). */ + +extern void +diagnostic_manager_set_version_string (diagnostic_manager *diag_mgr, + const char *value) + LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1) + LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (2); + +/* Set a string suitable for use as the value of the SARIF "informationUri" + property (SARIF v2.1.0 section 3.19.17). */ + +extern void +diagnostic_manager_set_version_url (diagnostic_manager *diag_mgr, + const char *value) + LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1) + LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (2); + +/* Destinations for diagnostics. */ + +/* Add a new output sink to DIAG_MGR, which writes GCC-style diagnostics + to DST_STREAM. + Return a borrowed pointer to the sink, which is cleaned up when DIAG_MGR + is released. + DST_STREAM is borrowed, and must outlive DIAG_MGR. + The output for each diagnostic is written and flushed as each + diagnostic is finished. */ + +extern diagnostic_text_sink * +diagnostic_manager_add_text_sink (diagnostic_manager *diag_mgr, + FILE *dst_stream, + enum diagnostic_colorize colorize) + LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1) + LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (2); + +/* Functions to manipulate text sinks. */ + +/* Enable/disable printing of source text in the text sink. + Default: enabled. */ + +extern void +diagnostic_text_sink_set_source_printing_enabled (diagnostic_text_sink *text_sink, + int value) + LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1); + +/* Update colorization of text sink. */ + +extern void +diagnostic_text_sink_set_colorize (diagnostic_text_sink *text_sink, + enum diagnostic_colorize colorize) + LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1); + +/* Enable/disable colorization of the characters of source text + that are underlined. + This should be true for clients that generate range information + (so that the ranges of code are colorized), + and false for clients that merely specify points within the + source code (to avoid e.g. colorizing just the first character in + a token, which would look strange). + Default: enabled. */ + +extern void +diagnostic_text_sink_set_labelled_source_colorization_enabled (diagnostic_text_sink *text_sink, + int value) + LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1); + +/* Add a new output sink to DIAG_MGR, which writes SARIF of the given + version to DST_STREAM. + + The output is not written until DIAG_MGR is released. + + DST_STREAM is borrowed, and must outlive DIAG_MGR. + + For the result to be a valid SARIF file according to the schema, + DIAG_MGR must have had diagnostic_manager_set_tool_name called on it. */ + +extern void +diagnostic_manager_add_sarif_sink (diagnostic_manager *diag_mgr, + FILE *dst_stream, + const diagnostic_file *main_input_file, + enum diagnostic_sarif_version version) + LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1) + LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (2) + LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (3); + +/* Write a patch to DST_STREAM consisting of all fix-it hints + on all diagnostics that have been finished on DIAG_MGR. */ + +extern void +diagnostic_manager_write_patch (diagnostic_manager *diag_mgr, + FILE *dst_stream) + LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1) + LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (2); + +/* Location management. */ + +/* Create a new diagnostic_file * for file NAME. + + Repeated calls with matching NAMEs will return the + same object. + + If SARIF_SOURCE_LANGUAGE is non-NULL, it specifies a "sourceLanguage" + value for the file when use when writing SARIF. + See SARIF v2.1.0 Appendix J for suggested values for various + programmming languages. */ + +extern const diagnostic_file * +diagnostic_manager_new_file (diagnostic_manager *diag_mgr, + const char *name, + const char *sarif_source_language) + LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1) + LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (2) + LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (3); + +/* Write a representation of FILE to OUT, for debugging. */ + +extern void +diagnostic_manager_debug_dump_file (diagnostic_manager *diag_mgr, + const diagnostic_file *file, + FILE *out) + LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1) + LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (2) + LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (3); + +/* Attempt to create a diagnostic_location representing + FILENAME:LINE_NUM, with no column information + (thus "the whole line"). */ + +extern const diagnostic_physical_location * +diagnostic_manager_new_location_from_file_and_line (diagnostic_manager *diag_mgr, + const diagnostic_file *file, + diagnostic_line_num_t line_num) + LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1) + LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (2); + +/* Attempt to create a diagnostic_physical_location representing + FILENAME:LINE_NUM:COLUMN_NUM. */ + +extern 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) + LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1) + LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (2); + +/* Attempt to create a diagnostic_physical_location representing a + range within a source file, with a highlighted "caret" location. + + All must be within the same file, but they can be on different lines. + + For example, consider the location of the binary expression below: + + ...|__________1111111112222222 + ...|12345678901234567890123456 + ...| + 521|int sum (int foo, int bar) + 522|{ + 523| return foo + bar; + ...| ~~~~^~~~~ + 524|} + + The location's caret is at the "+", line 523 column 15, but starts + earlier, at the "f" of "foo" at column 11. The finish is at the "r" + of "bar" at column 19. */ + +extern 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) + LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1) + LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (2) + LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (3) + LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (4); + +/* Write a representation of LOC to OUT, for debugging. */ + +extern void +diagnostic_manager_debug_dump_location (const diagnostic_manager *diag_mgr, + const diagnostic_physical_location *loc, + FILE *out) + LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1) + LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (2) + LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (3); + +/* A bundle of state describing a logical location in the user's source, + such as "in function 'foo'". + + SHORT_NAME can be NULL, or else a string suitable for use by + the SARIF logicalLocation "name" property (SARIF v2.1.0 section 3.33.4). + + FULLY_QUALIFIED_NAME can be NULL or else a string suitable for use by + the SARIF logicalLocation "fullyQualifiedName" property + (SARIF v2.1.0 section 3.33.5). + + DECORATED_NAME can be NULL or else a string suitable for use by + the SARIF logicalLocation "decoratedName" property + (SARIF v2.1.0 section 3.33.6). */ + +extern 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) + LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1) + LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (3) + LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (4) + LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (5) + LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (6); + +/* Write a representation of LOC to OUT, for debugging. */ + +extern void +diagnostic_manager_debug_dump_logical_location (const diagnostic_manager *diag_mgr, + const diagnostic_logical_location *loc, + FILE *out) + LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1) + LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (2) + LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (3); + +/* Diagnostic groups. */ + +/* Begin a diagnostic group. All diagnostics emitted within + DIAG_MGR after the first one will be treated as notes about + the initial diagnostic. */ + +extern void +diagnostic_manager_begin_group (diagnostic_manager *diag_mgr) + LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1); + +/* Finish a diagnostic group. */ + +extern void +diagnostic_manager_end_group (diagnostic_manager *diag_mgr) + LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1); + +/* Step-by-step creation of a diagnostic. */ + +extern diagnostic * +diagnostic_begin (diagnostic_manager *diag_mgr, + enum diagnostic_level level) + LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1); + +/* Associate this diagnostic with the given ID within + the Common Weakness Enumeration. */ + +extern void +diagnostic_set_cwe (diagnostic *diag, + unsigned cwe_id) + LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1); + +/* Associate this diagnostic with a particular rule that has been violated + (such as in a coding standard, or within a specification). + The rule must have at least one of a title and a URL, but these + can be NULL. + A diagnostic can be associated with zero or more rules. */ + +extern void +diagnostic_add_rule (diagnostic *diag, + const char *title, + const char *url) + LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1) + LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (2) + LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (3); + +/* Set the primary location of DIAG. */ + +extern void +diagnostic_set_location (diagnostic *diag, + const diagnostic_physical_location * loc) + LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1) + LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (2); + +/* Set the primary location of DIAG, with a label. */ + +extern void +diagnostic_set_location_with_label (diagnostic *diag, + const diagnostic_physical_location *loc, + const char *fmt, ...) + LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1) + LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (2) + LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (3); + +/* Add a secondary location to DIAG. */ + +extern void +diagnostic_add_location (diagnostic *diag, + const diagnostic_physical_location * loc) + LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1); + +/* Add a secondary location to DIAG, with a label. */ + +extern void +diagnostic_add_location_with_label (diagnostic *diag, + const diagnostic_physical_location *loc, + const char *text) + LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1) + LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (2) + LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (3); + +/* Set the logical location of DIAG. */ + +extern void +diagnostic_set_logical_location (diagnostic *diag, + const diagnostic_logical_location *logical_loc) + LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1) + LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (2); + +/* Fix-it hints. */ + +extern void +diagnostic_add_fix_it_hint_insert_before (diagnostic *diag, + const diagnostic_physical_location *loc, + const char *addition) + LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1) + LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (2) + LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (3); + +extern void +diagnostic_add_fix_it_hint_insert_after (diagnostic *diag, + const diagnostic_physical_location *loc, + const char *addition) + LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1) + LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (2) + LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (3); + +extern void +diagnostic_add_fix_it_hint_replace (diagnostic *diag, + const diagnostic_physical_location *loc, + const char *replacement) + LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1) + LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (2) + LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (3); + +extern void +diagnostic_add_fix_it_hint_delete (diagnostic *diag, + const diagnostic_physical_location *loc) + LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1) + LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (2); + +/* Create and borrow a pointer to an execution path for DIAG. + The path is automatically cleaned up when DIAG is finished. */ + +extern diagnostic_execution_path * +diagnostic_add_execution_path (diagnostic *diag) + LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1); + +/* Create a new execution path. + This is owned by the called and must have either + diagnostic_take_execution_path or diagnostic_execution_path_release + called on it. */ + +extern diagnostic_execution_path * +diagnostic_manager_new_execution_path (diagnostic_manager *manager) + LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1); + +/* Set DIAG to use PATH as its execution path, taking ownership of PATH. */ + +extern void +diagnostic_take_execution_path (diagnostic *diag, + diagnostic_execution_path *path) + LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1) + LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (2); + +/* Release ownership of PATH, which must not have been taken + by a diagnostic. */ + +extern void +diagnostic_execution_path_release (diagnostic_execution_path *path) + LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (1); + +/* Append an event to the end of PATH. */ + +extern diagnostic_event_id +diagnostic_execution_path_add_event (diagnostic_execution_path *path, + const diagnostic_physical_location *physical_loc, + const diagnostic_logical_location *logical_loc, + unsigned stack_depth, + const char *fmt, ...) + LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1) + LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (2) + LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (3) + LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (5) + LIBDIAGNOSTICS_PARAM_GCC_FORMAT_STRING (5, 6); + +/* Append an event to the end of PATH. */ + +extern diagnostic_event_id +diagnostic_execution_path_add_event_va (diagnostic_execution_path *path, + const diagnostic_physical_location *physical_loc, + const diagnostic_logical_location *logical_loc, + unsigned stack_depth, + const char *fmt, + va_list *args) + LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1) + LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (2) + LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (3) + LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (5) + LIBDIAGNOSTICS_PARAM_GCC_FORMAT_STRING (5, 0); + +/* Emit DIAG to all sinks of its manager, and release DIAG. + Use FMT for the message. + Note that this uses gcc's pretty-print format, which is *not* printf. + TODO: who is responsible for putting FMT through gettext? */ + +extern void +diagnostic_finish (diagnostic *diag, const char *fmt, ...) + LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1) + LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (2) + LIBDIAGNOSTICS_PARAM_GCC_FORMAT_STRING (2, 3); + +/* As diagnostic_finish, but with a va_list. */ + +extern void +diagnostic_finish_va (diagnostic *diag, const char *fmt, va_list *args) + LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1) + LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (2) + LIBDIAGNOSTICS_PARAM_GCC_FORMAT_STRING (2, 0); + +/* DEFERRED: + - thread-safety + - plural forms + - enum about what a "column number" means (bytes, unichars, etc) + - locations within binary files + - options and URLs for warnings + - enable/disable of warnings by kind + - plugin metadata. */ + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* LIBDIAGNOSTICS_H */ From patchwork Thu Aug 15 18:11:51 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: David Malcolm X-Patchwork-Id: 1972876 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; dkim=pass (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=b8MP+qad; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=gcc.gnu.org (client-ip=8.43.85.97; helo=server2.sourceware.org; envelope-from=gcc-patches-bounces~incoming=patchwork.ozlabs.org@gcc.gnu.org; receiver=patchwork.ozlabs.org) Received: from server2.sourceware.org (server2.sourceware.org [8.43.85.97]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (secp384r1) server-digest SHA384) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4WlCtd4wGlz1yfL for ; Fri, 16 Aug 2024 04:14:01 +1000 (AEST) Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id DB615385C6CB for ; Thu, 15 Aug 2024 18:13:59 +0000 (GMT) X-Original-To: gcc-patches@gcc.gnu.org Delivered-To: gcc-patches@gcc.gnu.org Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by sourceware.org (Postfix) with ESMTP id C10013858C41 for ; Thu, 15 Aug 2024 18:12:08 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org C10013858C41 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 C10013858C41 Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=170.10.129.124 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1723745539; cv=none; b=rp795In7X568aQiBkIDCIq7NHTGDvwD2eJgwM4YHUS/AkhxUOJjts9SZmKIqTxwXnBJcnCfH4sCDTDRcRMEmADBhHGI/HuVNRC0O/SkbP3uBEX/Nw2wm4y0hE56T0DvuX95GQq9UOO70uIVaYHj5qHUVNJQkpUw5H0Vz4iTsa7U= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1723745539; c=relaxed/simple; bh=MyJ4snH0fMxcH16PpelLIYdPFHPTLMz/DLiY+6CEHJA=; h=DKIM-Signature:From:To:Subject:Date:Message-Id:MIME-Version; b=WmDzqvye0PvIDxIVOkGEoF2FFCpNqj5+PnJb1N4+U/+bhYlmh9610vtMSdvrGD7sQeE/JxDJ4JTKGpNcRWYGgr41tUAS+cQuOLHIFWTNL+Ssua1jEPAcI6SCzMEq4UZATGM1L9qvzv8kZoF4KWtHe0BGObjdBDK6yT6XlitehA4= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1723745528; 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=Cjg1Ij3hkS/uJao3Ke72szLiVj+cfFVNxfjVwGwFUsw=; b=b8MP+qadXNY59aygOfM9Ele8h8vtbGb2TbwDnnOYx5/BXqViIr3EeQfAaH6mbobgknIGE8 1XGIm+SD7m/QGHBN68Gz9U1yO3/U0MRJ536l87wEIdehuTXX3KQHdPix2/lrUum8U9hEpx V6V/jNwZ7qahm18A5t76WA21E14+UyY= Received: from mx-prod-mc-01.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-597-ToC3wPlzNRClOpqItFQpxw-1; Thu, 15 Aug 2024 14:12:04 -0400 X-MC-Unique: ToC3wPlzNRClOpqItFQpxw-1 Received: from mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.17]) (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 mx-prod-mc-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 7D7C31955BF4 for ; Thu, 15 Aug 2024 18:12:03 +0000 (UTC) Received: from t14s.localdomain.com (unknown [10.22.16.43]) by mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 26F621955D44; Thu, 15 Aug 2024 18:12:01 +0000 (UTC) From: David Malcolm To: gcc-patches@gcc.gnu.org Cc: David Malcolm Subject: [PATCH 2/7] libdiagnostics v3: implementation Date: Thu, 15 Aug 2024 14:11:51 -0400 Message-Id: <20240815181156.1815075-3-dmalcolm@redhat.com> In-Reply-To: <20240815181156.1815075-1-dmalcolm@redhat.com> References: <20240815181156.1815075-1-dmalcolm@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.0 on 10.30.177.17 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com 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 X-BeenThere: gcc-patches@gcc.gnu.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Gcc-patches mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: gcc-patches-bounces~incoming=patchwork.ozlabs.org@gcc.gnu.org Changed in v3: * Added a --enable-libdiagnostics to configure.ac. It is disabled by default, and requires --enable-host-shared. * Split out gcc/testsuite/libdiagnostics.dg/libdiagnostics.exp into another patch * Update copyright year * class diagnostic_logical_location: Add get_name_for_path_output. * as_diagnostic_event_id: New function. * on_begin_text_diagnostic: Make "info" const. * diagnostic_manager::~diagnostic_manager: Free m_line_table.m_location_adhoc_data_map.data and m_line_table.info_ordinary.maps. * diagnostic_manager::new_location_from_file_and_line: Move code into ensure_linemap_for_file_and_line. * diagnostic_manager::new_location_from_file_line_column: Use ensure_linemap_for_file_and_line rather than always using LC_ENTER. * class libdiagnostics_path_event: New. * class libdiagnostics_path_thread: New. * struct diagnostic_execution_path: New. * diagnostic::diagnostic: Initialize m_path. * diagnostic::add_execution_path: New. * diagnostic::take_execution_path: New. * diagnostic::m_path: New field. * diagnostic_text_sink::diagnostic_text_sink: Initialize m_current_logical_loc. Call diagnostic_urls_init, set_show_cwe, set_show_rules, and set m_show_column. * diagnostic_text_sink::on_begin_text_diagnostic: Various fixes. * sarif_sink::sarif_sink: Add "main_input_file" param. * diagnostic_manager::write_patch: Update for change to pretty_printer. * diagnostic_manager::new_execution_path: New. * diagnostic_manager_add_sarif_sink: Add "main_input_file" param. * diagnostic_manager_debug_dump_location: Call diagnostic_finish. * diagnostic_set_logical_location: Allow logical_loc to be NULL. * diagnostic_add_execution_path: New. * diagnostic_manager_new_execution_path: New. * diagnostic_take_execution_path: New. * diagnostic_execution_path_release: New. * diagnostic_execution_path_add_event: New. * diagnostic_execution_path_add_event_va: New. 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. ChangeLog: * configure: Regenerate. * configure.ac: Add --enable-libdiagnostics. gcc/ChangeLog: * Makefile.in (enable_libdiagnostics): New. (lang_checks): If libdiagnostics is enabled, add check-libdiagnostics. (ALL_HOST_OBJS): If libdiagnostics is enabled, add $(libdiagnostics_OBJS). (start.encap): Add LIBDIAGNOSTICS. (libdiagnostics_OBJS): New. (LIBDIAGNOSTICS_VERSION_NUM): New, adapted from code in jit/Make-lang.in. (LIBDIAGNOSTICS_MINOR_NUM): Likewise. (LIBDIAGNOSTICS_RELEASE_NUM): Likewise. (LIBDIAGNOSTICS_FILENAME): Likewise. (LIBDIAGNOSTICS_IMPORT_LIB): Likewise. (libdiagnostics): Likewise. (LIBDIAGNOSTICS_AGE): Likewise. (LIBDIAGNOSTICS_BASENAME): Likewise. (LIBDIAGNOSTICS_SONAME): Likewise. (LIBDIAGNOSTICS_LINKER_NAME): Likewise. (LIBDIAGNOSTICS_COMMA): Likewise. (LIBDIAGNOSTICS_VERSION_SCRIPT_OPTION): Likewise. (LIBDIAGNOSTICS_SONAME_OPTION): Likewise. (LIBDIAGNOSTICS_SONAME_SYMLINK): Likewise. (LIBDIAGNOSTICS_LINKER_NAME_SYMLINK): Likewise. (LIBDIAGNOSTICS_FILENAME): Likewise. (libdiagnostics.serial): Likewise. (LIBDIAGNOSTICS_EXTRA_OPTS): Likewise. (install): If libdiagnostics is enabled, add install-libdiagnostics. (libdiagnostics.install-headers): New. (libdiagnostics.install-common): New, adapted from code in jit/Make-lang.in. (install-libdiagnostics): New. * configure: Regenerate. * configure.ac (check_languages): Add check-libdiagnostics. (--enable-libdiagnostics): New. * diagnostic-event-id.h (diagnostic_event_id_t::zero_based): New. * doc/install.texi (--enable-libdiagnostics): New. * libdiagnostics.cc: New file. * libdiagnostics.map: New file. Signed-off-by: David Malcolm --- configure | 42 + configure.ac | 35 + gcc/Makefile.in | 179 +++- gcc/configure | 26 +- gcc/configure.ac | 16 + gcc/diagnostic-event-id.h | 6 + gcc/doc/install.texi | 7 + gcc/libdiagnostics.cc | 1652 +++++++++++++++++++++++++++++++++++++ gcc/libdiagnostics.map | 72 ++ 9 files changed, 2032 insertions(+), 3 deletions(-) create mode 100644 gcc/libdiagnostics.cc create mode 100644 gcc/libdiagnostics.map diff --git a/configure b/configure index 51bf1d1add18..be6e41439990 100755 --- a/configure +++ b/configure @@ -691,6 +691,7 @@ extra_host_libiberty_configure_flags stage1_languages host_libs_picflag CRAB1_LIBS +enable_libdiagnostics PICFLAG host_shared gcc_host_pie @@ -844,6 +845,7 @@ enable_linker_plugin_configure_flags enable_linker_plugin_flags enable_host_pie enable_host_shared +enable_libdiagnostics enable_stage1_languages enable_objc_gc with_target_bdw_gc @@ -1578,6 +1580,7 @@ Optional Features: plugins [none] --enable-host-pie build position independent host executables --enable-host-shared build host code as shared libraries + --enable-libdiagnostics build libdiagnostics shared library --enable-stage1-languages[=all] choose additional languages to build during stage1. Mostly useful for compiler development @@ -8876,6 +8879,45 @@ fi + +# Check for libdiagnostics support. +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to enable libdiagnostics" >&5 +$as_echo_n "checking whether to enable libdiagnostics... " >&6; } +# Check whether --enable-libdiagnostics was given. +if test "${enable_libdiagnostics+set}" = set; then : + enableval=$enable_libdiagnostics; enable_libdiagnostics=$enableval +else + enable_libdiagnostics=no +fi + + +if test x$enable_libdiagnostics = xyes; then + # Disable libdiagnostics if -enable-host-shared not specified + # but not if building for Mingw. All code in Windows + # is position independent code (PIC). + case $target in + *mingw*) ;; + *) + if test x$host_shared != xyes; then + as_fn_error $? " +Enabling libdiagnostics requires --enable-host-shared. + +--enable-host-shared typically slows the rest of the compiler down by +a few %, so you must explicitly enable it. + +If you want to build both libdiagnostics and the regular compiler, it is often +best to do this via two separate configure/builds, in separate +directories, to avoid imposing the performance cost of +--enable-host-shared on the regular compiler." "$LINENO" 5 + fi + ;; + esac +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $enable_libdiagnostics" >&5 +$as_echo "$enable_libdiagnostics" >&6; } + + + # Rust requires -ldl and -lpthread if you are using an old glibc that does not include them by # default, so we check for them here diff --git a/configure.ac b/configure.ac index 20457005e299..ba2712d6dd52 100644 --- a/configure.ac +++ b/configure.ac @@ -2037,6 +2037,41 @@ fi AC_SUBST(PICFLAG) + +# Check for libdiagnostics support. +AC_MSG_CHECKING([whether to enable libdiagnostics]) +AC_ARG_ENABLE(libdiagnostics, +[AS_HELP_STRING([--enable-libdiagnostics], + [build libdiagnostics shared library])], +enable_libdiagnostics=$enableval, +enable_libdiagnostics=no) + +if test x$enable_libdiagnostics = xyes; then + # Disable libdiagnostics if -enable-host-shared not specified + # but not if building for Mingw. All code in Windows + # is position independent code (PIC). + case $target in + *mingw*) ;; + *) + if test x$host_shared != xyes; then + AC_MSG_ERROR([ +Enabling libdiagnostics requires --enable-host-shared. + +--enable-host-shared typically slows the rest of the compiler down by +a few %, so you must explicitly enable it. + +If you want to build both libdiagnostics and the regular compiler, it is often +best to do this via two separate configure/builds, in separate +directories, to avoid imposing the performance cost of +--enable-host-shared on the regular compiler.]) + fi + ;; + esac +fi +AC_MSG_RESULT($enable_libdiagnostics) +AC_SUBST(enable_libdiagnostics) + + # Rust requires -ldl and -lpthread if you are using an old glibc that does not include them by # default, so we check for them here diff --git a/gcc/Makefile.in b/gcc/Makefile.in index 8fba8f7db6a2..3e4c7bd645f9 100644 --- a/gcc/Makefile.in +++ b/gcc/Makefile.in @@ -436,6 +436,8 @@ endif enable_host_shared = @enable_host_shared@ +enable_libdiagnostics = @enable_libdiagnostics@ + enable_as_accelerator = @enable_as_accelerator@ CPPLIB = ../libcpp/libcpp.a @@ -615,6 +617,9 @@ xm_include_list=@xm_include_list@ xm_defines=@xm_defines@ lang_checks= lang_checks_parallelized= +ifeq (@enable_libdiagnostics@,yes) +lang_checks += check-libdiagnostics +endif 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@ lang_tree_files=@lang_tree_files@ @@ -1873,6 +1878,10 @@ endif # compilation or not. ALL_HOST_OBJS = $(ALL_HOST_FRONTEND_OBJS) $(ALL_HOST_BACKEND_OBJS) +ifeq (@enable_libdiagnostics@,yes) +ALL_HOST_OBJS += $(libdiagnostics_OBJS) +endif + BACKEND = libbackend.a main.o libcommon-target.a libcommon.a \ $(CPPLIB) $(LIBDECNUMBER) @@ -2179,7 +2188,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 @@ -2268,6 +2277,129 @@ 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 + +# libdiagnostics + +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 @@ -3808,6 +3940,10 @@ ifeq ($(enable_plugin),yes) install: install-plugin endif +ifeq ($(enable_libdiagnostics),yes) +install: install-libdiagnostics +endif + install-strip: override INSTALL_PROGRAM = $(INSTALL_STRIP_PROGRAM) ifneq ($(STRIP),) install-strip: STRIPPROG = $(STRIP) @@ -3984,6 +4120,47 @@ install-driver: installdirs xgcc$(exeext) fi; \ fi +libdiagnostics.install-headers: installdirs + $(INSTALL_DATA) $(srcdir)/libdiagnostics.h \ + $(DESTDIR)$(includedir)/libdiagnostics.h + $(INSTALL_DATA) $(srcdir)/libdiagnostics++.h \ + $(DESTDIR)$(includedir)/libdiagnostics++.h + +ifneq (,$(findstring mingw,$(target))) +libdiagnostics.install-common: installdirs libdiagnostics.install-headers +# Install import library + $(INSTALL_PROGRAM) $(LIBDIAGNOSTICS_IMPORT_LIB) \ + $(DESTDIR)$(libdir)/$(LIBDIAGNOSTICS_IMPORT_LIB) +# Install DLL file + $(INSTALL_PROGRAM) $(LIBDIAGNOSTICS_FILENAME) \ + $(DESTDIR)$(bindir)/$(LIBDIAGNOSTICS_FILENAME) + +else +ifneq (,$(findstring darwin,$(host))) +# but only one level for Darwin + +libdiagnostics.install-common: installdirs libdiagnostics.install-headers + $(INSTALL_PROGRAM) $(LIBDIAGNOSTICS_FILENAME) \ + $(DESTDIR)$(libdir)/$(LIBDIAGNOSTICS_FILENAME) + ln -sf \ + $(LIBDIAGNOSTICS_SONAME_SYMLINK)\ + $(DESTDIR)$(libdir)/$(LIBDIAGNOSTICS_LINKER_NAME_SYMLINK) + +else +libdiagnostics.install-common: installdirs libdiagnostics.install-headers + $(INSTALL_PROGRAM) $(LIBDIAGNOSTICS_FILENAME) \ + $(DESTDIR)$(libdir)/$(LIBDIAGNOSTICS_FILENAME) + ln -sf \ + $(LIBDIAGNOSTICS_FILENAME) \ + $(DESTDIR)$(libdir)/$(LIBDIAGNOSTICS_SONAME_SYMLINK) + ln -sf \ + $(LIBDIAGNOSTICS_SONAME_SYMLINK)\ + $(DESTDIR)$(libdir)/$(LIBDIAGNOSTICS_LINKER_NAME_SYMLINK) +endif +endif + +install-libdiagnostics: libdiagnostics.install-common + # Install the info files. # $(INSTALL_DATA) might be a relative pathname, so we can't cd into srcdir # to do the install. diff --git a/gcc/configure b/gcc/configure index 557ea5fa3ac9..5e2f4644c528 100755 --- a/gcc/configure +++ b/gcc/configure @@ -637,6 +637,8 @@ LD_PICFLAG PICFLAG enable_default_pie enable_host_bind_now +LIBDIAGNOSTICS +enable_libdiagnostics enable_host_pie enable_host_shared enable_plugin @@ -1051,6 +1053,7 @@ enable_version_specific_runtime_libs enable_plugin enable_host_shared enable_host_pie +enable_libdiagnostics enable_host_bind_now enable_libquadmath_support with_linker_hash_style @@ -1824,6 +1827,7 @@ Optional Features: --enable-plugin enable plugin support --enable-host-shared build host code as shared libraries --enable-host-pie build host code as PIE + --enable-libdiagnostics build libdiagnostics shared library --enable-host-bind-now link host code as BIND_NOW --disable-libquadmath-support disable libquadmath support for Fortran @@ -21406,7 +21410,7 @@ else lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2 lt_status=$lt_dlunknown cat > conftest.$ac_ext <<_LT_EOF -#line 21409 "configure" +#line 21413 "configure" #include "confdefs.h" #if HAVE_DLFCN_H @@ -21512,7 +21516,7 @@ else lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2 lt_status=$lt_dlunknown cat > conftest.$ac_ext <<_LT_EOF -#line 21515 "configure" +#line 21519 "configure" #include "confdefs.h" #if HAVE_DLFCN_H @@ -33788,6 +33792,9 @@ for language in $all_selected_languages do check_languages="$check_languages check-$language" done +if test x$enable_libdiagnostics = xyes; then + check_languages="$check_languages check-libdiagnostics" +fi selftest_languages= for language in $all_selected_languages @@ -34226,6 +34233,21 @@ fi +# Check whether --enable-libdiagnostics was given. +if test "${enable_libdiagnostics+set}" = set; then : + enableval=$enable_libdiagnostics; +fi + + + +if test "$enable_libdiagnostics" = "yes"; then + LIBDIAGNOSTICS='libdiagnostics' +else + LIBDIAGNOSTICS='' +fi + + + # Enable --enable-host-bind-now # Check whether --enable-host-bind-now was given. if test "${enable_host_bind_now+set}" = set; then : diff --git a/gcc/configure.ac b/gcc/configure.ac index eaa01d0d7e56..0edd1b92f5fb 100644 --- a/gcc/configure.ac +++ b/gcc/configure.ac @@ -7371,6 +7371,9 @@ for language in $all_selected_languages do check_languages="$check_languages check-$language" done +if test x$enable_libdiagnostics = xyes; then + check_languages="$check_languages check-libdiagnostics" +fi selftest_languages= for language in $all_selected_languages @@ -7603,6 +7606,19 @@ AC_ARG_ENABLE(host-pie, [build host code as PIE])]) AC_SUBST(enable_host_pie) +AC_ARG_ENABLE(libdiagnostics, +[AS_HELP_STRING([--enable-libdiagnostics], + [build libdiagnostics shared library])]) +AC_SUBST(enable_libdiagnostics) + +if test "$enable_libdiagnostics" = "yes"; then + LIBDIAGNOSTICS='libdiagnostics' +else + LIBDIAGNOSTICS='' +fi +AC_SUBST(LIBDIAGNOSTICS) + + # Enable --enable-host-bind-now AC_ARG_ENABLE(host-bind-now, [AS_HELP_STRING([--enable-host-bind-now], diff --git a/gcc/diagnostic-event-id.h b/gcc/diagnostic-event-id.h index 78c2ccbbc99d..51e802e149b7 100644 --- a/gcc/diagnostic-event-id.h +++ b/gcc/diagnostic-event-id.h @@ -47,6 +47,12 @@ class diagnostic_event_id_t return m_index + 1; } + int zero_based () const + { + gcc_assert (known_p ()); + return m_index; + } + private: static const int UNKNOWN_EVENT_IDX = -1; int m_index; // zero-based diff --git a/gcc/doc/install.texi b/gcc/doc/install.texi index 4973f195daf9..f1d64833cc8a 100644 --- a/gcc/doc/install.texi +++ b/gcc/doc/install.texi @@ -1226,6 +1226,13 @@ virtual calls in verifiable mode at all. However the libvtv library will still be built (see @option{--disable-libvtv} to turn off building libvtv). @option{--disable-vtable-verify} is the default. +@item --enable-libdiagnostics +Specify whether to build @code{libdiagnostics}, a shared library exposing +GCC's diagnostics capabilities via a C API, and a C++ wrapper API adding +``syntactic sugar''. + +This option requires @option{--enable-host-shared} on non-Windows hosts. + @item --disable-gcov Specify that the run-time library used for coverage analysis and associated host tools should not be built. diff --git a/gcc/libdiagnostics.cc b/gcc/libdiagnostics.cc new file mode 100644 index 000000000000..00afafe396b2 --- /dev/null +++ b/gcc/libdiagnostics.cc @@ -0,0 +1,1652 @@ +/* C++ implementation of a pure C API for emitting diagnostics. + Copyright (C) 2023-2024 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-path.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 (diagnostic_manager *mgr, + location_t inner) + : m_mgr (mgr), + m_inner (inner) + {} + + diagnostic_manager *m_mgr; + 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; } + + label_text get_name_for_path_output () const + { + return label_text::borrow (m_short_name.get_str ()); + } + +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; +}; + +static diagnostic_event_id +as_diagnostic_event_id (diagnostic_event_id_t id) +{ + return id.zero_based (); +} + +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 (const diagnostic_info *info); + + diagnostic_source_printing_options &get_source_printing_options () + { + return m_dc.m_source_printing; + } + + void + set_colorize (enum diagnostic_colorize colorize); + +private: + const diagnostic_logical_location *m_current_logical_loc; +}; + +class sarif_sink : public sink +{ +public: + sarif_sink (diagnostic_manager &mgr, + FILE *dst_stream, + const diagnostic_file *main_input_file, + 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; + + free (m_line_table.m_location_adhoc_data_map.data); + free (m_line_table.info_ordinary.maps); + } + + 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 line_num) + { + ensure_linemap_for_file_and_line (file, line_num); + location_t loc = linemap_position_for_column (&m_line_table, 0); + return new_location (loc); + } + + 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) + { + ensure_linemap_for_file_and_line (file, line_num); + location_t loc = linemap_position_for_column (&m_line_table, column_num); + return new_location (loc); + } + + 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; + } + + diagnostic_execution_path * + new_execution_path (); + + 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 + { + if (!loc) + return; + gcc_assert (loc->m_mgr == this); + } + + /* TODO: Various things still use 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: + void + ensure_linemap_for_file_and_line (const diagnostic_file *file, + diagnostic_line_num_t linenum) + { + /* 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 + { + line_map *map + = const_cast + (linemap_add (&m_line_table, LC_RENAME_VERBATIM, false, + file->get_name (), 0)); + ((line_map_ordinary *)map)->included_from = UNKNOWN_LOCATION; + } + linemap_line_start (&m_line_table, linenum, 100); + } + + 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 (this, 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; +}; + +class libdiagnostics_path_event : public diagnostic_event +{ +public: + libdiagnostics_path_event (const diagnostic_physical_location *physical_loc, + const diagnostic_logical_location *logical_loc, + unsigned stack_depth, + const char *gmsgid, + va_list *args) + : m_physical_loc (physical_loc), + m_logical_loc (logical_loc), + m_stack_depth (stack_depth) + { + m_desc_uncolored = make_desc (gmsgid, args, false); + m_desc_colored = make_desc (gmsgid, args, true); + } + + /* diagnostic_event vfunc implementations. */ + + location_t get_location () const final override + { + return as_location_t (m_physical_loc); + } + + int get_stack_depth () const final override + { + return m_stack_depth; + } + + label_text get_desc (bool can_colorize) const final override + { + const label_text &text = can_colorize ? m_desc_colored : m_desc_uncolored; + return label_text::borrow (text.get ()); + } + + const logical_location *get_logical_location () const + { + return m_logical_loc; + } + + meaning get_meaning () const final override + { + return meaning (); + } + + bool connect_to_next_event_p () const final override + { + return false; // TODO + } + + diagnostic_thread_id_t get_thread_id () const final override + { + return 0; + } + +private: + static label_text make_desc (const char *gmsgid, + va_list *args, + bool colorize) + { + va_list copy_of_args; + va_copy (copy_of_args, *args); + + // TODO: when should localization happen? + text_info text (gmsgid, ©_of_args, errno); + pretty_printer pp; + pp_show_color (&pp) = colorize; + pp.set_output_stream (nullptr); + pp_format (&pp, &text); + pp_output_formatted_text (&pp, nullptr); + label_text result = label_text::take (xstrdup (pp_formatted_text (&pp))); + + va_end (copy_of_args); + + return result; + } + + const diagnostic_physical_location *m_physical_loc; + const diagnostic_logical_location *m_logical_loc; + unsigned m_stack_depth; + label_text m_desc_uncolored; + label_text m_desc_colored; +}; + +class libdiagnostics_path_thread : public diagnostic_thread +{ +public: + libdiagnostics_path_thread (const char *name) : m_name (name) {} + label_text get_name (bool) const final override + { + return label_text::borrow (m_name); + } + +private: + const char *m_name; // has been i18n-ed and formatted +}; + +/* This has to be a "struct" as it is exposed in the C API. */ + +struct diagnostic_execution_path : public diagnostic_path +{ + diagnostic_execution_path () + : m_thread ("") + { + } + + diagnostic_event_id_t + add_event_va (const diagnostic_physical_location *physical_loc, + const diagnostic_logical_location *logical_loc, + unsigned stack_depth, + const char *gmsgid, + va_list *args) + { + m_events.push_back (::make_unique (physical_loc, + logical_loc, + stack_depth, + gmsgid, + args)); + return m_events.size () - 1; + } + + /* diagnostic_path vfunc implementations. */ + + unsigned num_events () const final override + { + return m_events.size (); + } + const diagnostic_event & get_event (int idx) const final override + { + return *m_events[idx]; + } + unsigned num_threads () const final override { return 1; } + const diagnostic_thread & + get_thread (diagnostic_thread_id_t) const final override + { + return m_thread; + } + + bool + same_function_p (int event_idx_a, + int event_idx_b) const final override + { + const logical_location *logical_loc_a + = m_events[event_idx_a]->get_logical_location (); + const logical_location *logical_loc_b + = m_events[event_idx_b]->get_logical_location (); + + // TODO: + /* Pointer equality, so we may want to uniqify logical loc ptrs. */ + return logical_loc_a == logical_loc_b; + } + +private: + libdiagnostics_path_thread m_thread; + std::vector> m_events; +}; + +/* 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), + m_path (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; } + + 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; + } + + diagnostic_execution_path * + add_execution_path () + { + m_path = ::make_unique (); + m_rich_loc.set_path (m_path.get ()); + return m_path.get (); + } + + void + take_execution_path (diagnostic_execution_path *path) + { + m_path = std::unique_ptr (path); + m_rich_loc.set_path (path); + } + +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; + std::unique_ptr m_path; +}; + +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; + case DIAGNOSTIC_LEVEL_SORRY: + return DK_SORRY; + } +} + +/* 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; +GCC_DIAGNOSTIC_PUSH_IGNORED(-Wsuggest-attribute=format) + diagnostic_set_info (&info, msgid, args, diag.get_rich_location (), + diagnostic_t_from_diagnostic_level (diag.get_level ())); +GCC_DIAGNOSTIC_POP + 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. */ + +static diagnostic_color_rule_t +get_color_rule (enum diagnostic_colorize colorize) +{ + switch (colorize) + { + default: + gcc_unreachable (); + case DIAGNOSTIC_COLORIZE_IF_TTY: + return DIAGNOSTICS_COLOR_AUTO; + break; + case DIAGNOSTIC_COLORIZE_NO: + return DIAGNOSTICS_COLOR_NO; + break; + case DIAGNOSTIC_COLORIZE_YES: + return DIAGNOSTICS_COLOR_YES; + break; + } +} + +diagnostic_text_sink::diagnostic_text_sink (diagnostic_manager &mgr, + FILE *dst_stream, + enum diagnostic_colorize colorize) +: sink (mgr), + m_current_logical_loc (nullptr) +{ + m_dc.set_show_cwe (true); + m_dc.set_show_rules (true); + + diagnostic_color_init (&m_dc, get_color_rule (colorize)); + diagnostic_urls_init (&m_dc); + m_dc.printer->set_output_stream (dst_stream); + diagnostic_starter (&m_dc) + = [] (diagnostic_context *context, + const diagnostic_info *info) + { + diagnostic_text_sink *sink + = static_cast (context->m_client_aux_data); + sink->on_begin_text_diagnostic (info); + }; + m_dc.set_show_cwe (true); + m_dc.set_show_rules (true); + m_dc.m_show_column = true; + 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; + m_dc.set_path_format (DPF_INLINE_EVENTS); +} + +void +diagnostic_text_sink::set_colorize (enum diagnostic_colorize colorize) +{ + diagnostic_color_init (&m_dc, get_color_rule (colorize)); +} + +void +diagnostic_text_sink::on_begin_text_diagnostic (const 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) + { + pp_set_prefix (m_dc.printer, nullptr); + 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; + // TODO: handle other cases + } + } + } + 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, + const diagnostic_file *main_input_file, + enum diagnostic_sarif_version) +: sink (mgr) +{ + const char *main_input_filename = main_input_file->get_name (); + diagnostic_output_format_init_sarif_stream (m_dc, + mgr.get_line_table (), + main_input_filename, + true, + dst_stream); +} + +/* struct diagnostic_manager. */ + +void +diagnostic_manager::write_patch (FILE *dst_stream) +{ + pretty_printer pp; + pp.set_output_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; +} + +diagnostic_execution_path * +diagnostic_manager::new_execution_path () +{ + return new diagnostic_execution_path (); +} + +/* Error-checking at the API boundary. */ + +#define FAIL_IF_NULL(PTR_ARG) \ + do { \ + GCC_DIAGNOSTIC_PUSH_IGNORED(-Wnonnull-compare) \ + if (!(PTR_ARG)) { \ + fprintf (stderr, "%s: %s must be non-NULL\n", \ + __func__, #PTR_ARG); \ + abort (); \ + } \ + GCC_DIAGNOSTIC_POP \ + } while (0) + +/* 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) +{ + FAIL_IF_NULL (diag_mgr); + FAIL_IF_NULL (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) +{ + FAIL_IF_NULL (diag_mgr); + FAIL_IF_NULL (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) +{ + FAIL_IF_NULL (diag_mgr); + FAIL_IF_NULL (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) +{ + FAIL_IF_NULL (diag_mgr); + FAIL_IF_NULL (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) +{ + FAIL_IF_NULL (diag_mgr); + FAIL_IF_NULL (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) +{ + FAIL_IF_NULL (text_sink); + + text_sink->get_source_printing_options ().enabled = value; +} + +/* Public entrypoint. */ + +void +diagnostic_text_sink_set_colorize (diagnostic_text_sink *text_sink, + enum diagnostic_colorize colorize) +{ + FAIL_IF_NULL (text_sink); + + text_sink->set_colorize (colorize); +} + +/* Public entrypoint. */ + +void +diagnostic_text_sink_set_labelled_source_colorization_enabled (diagnostic_text_sink *text_sink, + int value) +{ + FAIL_IF_NULL (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, + const diagnostic_file *main_input_file, + enum diagnostic_sarif_version version) +{ + FAIL_IF_NULL (diag_mgr); + FAIL_IF_NULL (dst_stream); + FAIL_IF_NULL (main_input_file); + + diag_mgr->add_sink (make_unique (*diag_mgr, + dst_stream, + main_input_file, + version)); +} + +/* Public entrypoint. */ + +void +diagnostic_manager_write_patch (diagnostic_manager *diag_mgr, + FILE *dst_stream) +{ + FAIL_IF_NULL (diag_mgr); + FAIL_IF_NULL (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) +{ + FAIL_IF_NULL (diag_mgr); + FAIL_IF_NULL (name); + + return diag_mgr->new_file (name, sarif_source_language); +} + +void +diagnostic_manager_debug_dump_file (diagnostic_manager *, + const diagnostic_file *file, + FILE *out) +{ + FAIL_IF_NULL (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) +{ + FAIL_IF_NULL (diag_mgr); + FAIL_IF_NULL (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) +{ + FAIL_IF_NULL (diag_mgr); + FAIL_IF_NULL (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) +{ + FAIL_IF_NULL (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) +{ + FAIL_IF_NULL (diag_mgr); + FAIL_IF_NULL (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 ()); + + diagnostic_finish (&dc); + } + 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) +{ + FAIL_IF_NULL (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) +{ + FAIL_IF_NULL (diag_mgr); + FAIL_IF_NULL (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) +{ + FAIL_IF_NULL (diag_mgr); + diag_mgr->begin_group (); +} + +/* Public entrypoint. */ + +extern void +diagnostic_manager_end_group (diagnostic_manager *diag_mgr) +{ + FAIL_IF_NULL (diag_mgr); + diag_mgr->end_group (); +} + +/* Public entrypoint. */ + +diagnostic * +diagnostic_begin (diagnostic_manager *diag_mgr, + enum diagnostic_level level) +{ + FAIL_IF_NULL (diag_mgr); + + return new diagnostic (*diag_mgr, level); +} + +/* Public entrypoint. */ + +void +diagnostic_set_cwe (diagnostic *diag, + unsigned cwe_id) +{ + FAIL_IF_NULL (diag); + + diag->set_cwe (cwe_id); +} + +/* Public entrypoint. */ + +void +diagnostic_add_rule (diagnostic *diag, + const char *title, + const char *url) +{ + FAIL_IF_NULL (diag); + + diag->add_rule (title, url); +} + +/* Public entrypoint. */ + +void +diagnostic_set_location (diagnostic *diag, + const diagnostic_physical_location *loc) +{ + FAIL_IF_NULL (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) +{ + FAIL_IF_NULL (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) +{ + FAIL_IF_NULL (diag); + diag->get_manager ().assert_valid_diagnostic_physical_location (loc); + FAIL_IF_NULL (text); + + diag->add_location_with_label (loc, text); +} + +/* Public entrypoint. */ + +void +diagnostic_set_logical_location (diagnostic *diag, + const diagnostic_logical_location *logical_loc) +{ + FAIL_IF_NULL (diag); + + 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) +{ + FAIL_IF_NULL (diag); + diag->get_manager ().assert_valid_diagnostic_physical_location (loc); + FAIL_IF_NULL (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) +{ + FAIL_IF_NULL (diag); + diag->get_manager ().assert_valid_diagnostic_physical_location (loc); + FAIL_IF_NULL (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) +{ + FAIL_IF_NULL (diag); + diag->get_manager ().assert_valid_diagnostic_physical_location (loc); + FAIL_IF_NULL (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) +{ + FAIL_IF_NULL (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. */ + +diagnostic_execution_path * +diagnostic_add_execution_path (diagnostic *diag) +{ + FAIL_IF_NULL (diag); + + return diag->add_execution_path (); +} + +/* Public entrypoint. */ + +diagnostic_execution_path * +diagnostic_manager_new_execution_path (diagnostic_manager *manager) +{ + FAIL_IF_NULL (manager); + + return manager->new_execution_path (); +} + +/* Public entrypoint. */ + +extern void +diagnostic_take_execution_path (diagnostic *diag, + diagnostic_execution_path *path) +{ + FAIL_IF_NULL (diag); + FAIL_IF_NULL (path); + + return diag->take_execution_path (path); +} + +/* Public entrypoint. */ + +void +diagnostic_execution_path_release (diagnostic_execution_path *path) +{ + delete path; +} + +/* Public entrypoint. */ + +diagnostic_event_id +diagnostic_execution_path_add_event (diagnostic_execution_path *path, + const diagnostic_physical_location *physical_loc, + const diagnostic_logical_location *logical_loc, + unsigned stack_depth, + const char *gmsgid, ...) +{ + FAIL_IF_NULL (path); + FAIL_IF_NULL (gmsgid); + + va_list args; + va_start (args, gmsgid); + diagnostic_event_id_t result = path->add_event_va (physical_loc, + logical_loc, + stack_depth, + gmsgid, &args); + va_end (args); + + return as_diagnostic_event_id (result); +} + +/* Public entrypoint. */ + +diagnostic_event_id +diagnostic_execution_path_add_event_va (diagnostic_execution_path *path, + const diagnostic_physical_location *physical_loc, + const diagnostic_logical_location *logical_loc, + unsigned stack_depth, + const char *gmsgid, + va_list *args) +{ + FAIL_IF_NULL (path); + FAIL_IF_NULL (gmsgid); + + diagnostic_event_id_t result = path->add_event_va (physical_loc, + logical_loc, + stack_depth, + gmsgid, args); + return as_diagnostic_event_id (result); +} + +/* Public entrypoint. */ + +void +diagnostic_finish (diagnostic *diag, const char *gmsgid, ...) +{ + FAIL_IF_NULL (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) +{ + FAIL_IF_NULL (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..cc32b76d016a --- /dev/null +++ b/gcc/libdiagnostics.map @@ -0,0 +1,72 @@ +# Linker script for libdiagnostics.so +# Copyright (C) 2023-2024 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_colorize; + 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_add_execution_path; + diagnostic_manager_new_execution_path; + diagnostic_take_execution_path; + diagnostic_execution_path_release; + diagnostic_execution_path_add_event; + diagnostic_execution_path_add_event_va; + + diagnostic_finish; + diagnostic_finish_va; + + local: *; +}; From patchwork Thu Aug 15 18:11:52 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: David Malcolm X-Patchwork-Id: 1972872 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; dkim=pass (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=GoU+qvnC; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=gcc.gnu.org (client-ip=2620:52:3:1:0:246e:9693:128c; helo=server2.sourceware.org; envelope-from=gcc-patches-bounces~incoming=patchwork.ozlabs.org@gcc.gnu.org; receiver=patchwork.ozlabs.org) Received: from server2.sourceware.org (server2.sourceware.org [IPv6:2620:52:3:1:0:246e:9693:128c]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (secp384r1) server-digest SHA384) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4WlCsD0yv5z1yfL for ; Fri, 16 Aug 2024 04:12:47 +1000 (AEST) Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id B69A9385DDC1 for ; Thu, 15 Aug 2024 18:12:45 +0000 (GMT) X-Original-To: gcc-patches@gcc.gnu.org Delivered-To: gcc-patches@gcc.gnu.org Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by sourceware.org (Postfix) with ESMTP id EAD4D3858403 for ; Thu, 15 Aug 2024 18:12:09 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org EAD4D3858403 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 EAD4D3858403 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=1723745533; cv=none; b=OSj986MqHDxQEGYGrrlRSHRE66UY1+6UsohrhcswUaakdpxsWxkHxSPruI2RMDR0j2uAYtixSy3XRZ4RJ3U4pgJ/XmoOyVS0Go8fYsXpUJicwsotd0FPBlzGMx3oJHwvya8hhMmFJynXIVLx7ucuyNIkTyzkPV5MEa3ZvH8gHpo= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1723745533; c=relaxed/simple; bh=mk7AYHCqcn3uW8uUtljLalj4pA0K18NGiZbG3FBZ/uA=; h=DKIM-Signature:From:To:Subject:Date:Message-Id:MIME-Version; b=EIN9l2XKqjgax1ofBn07YpiKnZ8vvTWsGtgVhXONUWhP0CkiAzOzB/+Wn7VLI6GTGrjPKbiEsAzRjFgqQu7ehxWdK6GKCMEXpXj1kzRvKfy5pshshA4dIZMYE+/Dd42Bx3ty/YMOvSYHdA+pjh8qzIaqTNUeKJHYNpPY5FCnNw0= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1723745529; 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=4wDmghX0lDRVJ2w43sD+zOKkhmG9TWtFEv+q75pVjNY=; b=GoU+qvnC6EZRC8fQ6d2OtRk1dhrgRrvvEfw8aaLzBnPxWdcgR+ltq8pSXmHflAnfhEEoDz s+Eatj93jYw8o4m3Hak7SWJtPS7Ls4r007n9aF7RpqPzNP+IQZQMbpNUIhVfnEuwQH5/Yr 40MFGjvBSkl4onfilb5PORDmhsS/MVk= Received: from mx-prod-mc-02.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-684-R2zSJakBOvOqkD6dmozymQ-1; Thu, 15 Aug 2024 14:12:05 -0400 X-MC-Unique: R2zSJakBOvOqkD6dmozymQ-1 Received: from mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.17]) (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 mx-prod-mc-02.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 077371955D53 for ; Thu, 15 Aug 2024 18:12:05 +0000 (UTC) Received: from t14s.localdomain.com (unknown [10.22.16.43]) by mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id E295F1955D44; Thu, 15 Aug 2024 18:12:03 +0000 (UTC) From: David Malcolm To: gcc-patches@gcc.gnu.org Cc: David Malcolm Subject: [PATCH 3/7] libdiagnostics v3: add C++ wrapper API Date: Thu, 15 Aug 2024 14:11:52 -0400 Message-Id: <20240815181156.1815075-4-dmalcolm@redhat.com> In-Reply-To: <20240815181156.1815075-1-dmalcolm@redhat.com> References: <20240815181156.1815075-1-dmalcolm@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.0 on 10.30.177.17 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com X-Spam-Status: No, score=-10.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, LIKELY_SPAM_BODY, RCVD_IN_DNSWL_NONE, RCVD_IN_MSPIKE_H4, 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 X-BeenThere: gcc-patches@gcc.gnu.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Gcc-patches mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: gcc-patches-bounces~incoming=patchwork.ozlabs.org@gcc.gnu.org Changed in v3: * Moved the testsuite to a separate patch * Updated copyright year * class text_sink: New. * class file: Add default ctor, copy ctor, move ctor; make m_inner non-const * class physical_location: Add default ctor * class logical_location: Make m_inner non-const * class execution_path: New * class diagnostic: Add member functions: add_rule, take_execution_path, finish_va * class manager: Add alternate ctor; add m_owned bool and use in dtor; delete copy ctor; add move ctor; add member functions set_tool_name, set_full-name, set_version_string, set_version_url, new_execution_path. Add param "main_input_file" to add_sarif_sink. Blurb from v2: This is new in v2: a C++ wrapper API that provides some syntactic sugar for calling into libdiagnostics.{h,so}. I've been "eating my own dogfood" with this by using it to write a simple client that reads a SARIF file and dumps it using the text sink: https://github.com/davidmalcolm/libdiagnostics-sarif-dump gcc/ChangeLog: * libdiagnostics++.h: New file. --- gcc/libdiagnostics++.h | 595 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 595 insertions(+) create mode 100644 gcc/libdiagnostics++.h diff --git a/gcc/libdiagnostics++.h b/gcc/libdiagnostics++.h new file mode 100644 index 000000000000..14c84934a446 --- /dev/null +++ b/gcc/libdiagnostics++.h @@ -0,0 +1,595 @@ +/* A C++ wrapper API around libdiagnostics.h for emitting diagnostics. + Copyright (C) 2023-2024 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 +. */ + +#ifndef LIBDIAGNOSTICSPP_H +#define LIBDIAGNOSTICSPP_H + +#include "libdiagnostics.h" + +namespace libdiagnostics { + +typedef diagnostic_line_num_t line_num_t; +typedef diagnostic_column_num_t column_num_t; + +class file; +class physical_location; +class logical_location; +class execution_path; +class group; +class manager; +class diagnostic; + +/* Wrapper around a borrowed diagnostic_text_sink *. */ + +class text_sink +{ +public: + text_sink (diagnostic_text_sink *inner) + : m_inner (inner) + { + } + + void + set_source_printing_enabled (int value) + { + diagnostic_text_sink_set_source_printing_enabled (m_inner, value); + } + + void + set_colorize (enum diagnostic_colorize colorize) + { + diagnostic_text_sink_set_colorize (m_inner, colorize); + } + + void + set_labelled_source_colorization_enabled (int value) + { + diagnostic_text_sink_set_labelled_source_colorization_enabled (m_inner, + value); + } + + diagnostic_text_sink *m_inner; +}; + +/* Wrapper around a const diagnostic_file *. */ + +class file +{ +public: + file () : m_inner (nullptr) {} + file (const diagnostic_file *file) : m_inner (file) {} + file (const file &other) : m_inner (other.m_inner) {} + file &operator= (const file &other) { m_inner = other.m_inner; return *this; } + + const diagnostic_file * m_inner; +}; + +/* Wrapper around a const diagnostic_physical_location *. */ + +class physical_location +{ +public: + physical_location () : m_inner (nullptr) {} + + physical_location (const diagnostic_physical_location *location) + : m_inner (location) + {} + + const diagnostic_physical_location *m_inner; +}; + +/* Wrapper around a const diagnostic_logical_location *. */ + +class logical_location +{ +public: + logical_location () : m_inner (nullptr) {} + + logical_location (const diagnostic_logical_location *logical_loc) + : m_inner (logical_loc) + {} + + const diagnostic_logical_location *m_inner; +}; + +/* RAII class around a diagnostic_execution_path *. */ + +class execution_path +{ +public: + execution_path () : m_inner (nullptr), m_owned (false) {} + + execution_path (diagnostic_execution_path *path) + : m_inner (path), m_owned (true) + {} + + execution_path (const diagnostic_execution_path *path) + : m_inner (const_cast (path)), + m_owned (false) + {} + + execution_path (const execution_path &other) = delete; + execution_path &operator= (const execution_path &other) = delete; + + execution_path (execution_path &&other) + : m_inner (other.m_inner), + m_owned (other.m_owned) + { + other.m_inner = nullptr; + other.m_owned = false; + } + + execution_path &operator= (execution_path &&other) + { + m_inner = other.m_inner; + m_owned = other.m_owned; + other.m_inner = nullptr; + other.m_owned = false; + return *this; + } + + ~execution_path () + { + if (m_owned) + diagnostic_execution_path_release (m_inner); + } + + diagnostic_event_id + add_event (physical_location physical_loc, + logical_location logical_loc, + unsigned stack_depth, + const char *fmt, ...) + LIBDIAGNOSTICS_PARAM_GCC_FORMAT_STRING (5, 6); + + diagnostic_event_id + add_event_va (physical_location physical_loc, + logical_location logical_loc, + unsigned stack_depth, + const char *fmt, + va_list *args) + LIBDIAGNOSTICS_PARAM_GCC_FORMAT_STRING (5, 0); + + diagnostic_execution_path *m_inner; + bool m_owned; +}; + +/* RAII class for starting/ending a group within a diagnostic_manager. */ + +class group +{ +public: + group (manager &mgr); + ~group (); + +private: + manager &m_mgr; +}; + +/* Wrapper around a diagnostic *. */ + +class diagnostic +{ +public: + diagnostic (::diagnostic *d) : m_inner (d) {} + + void + set_cwe (unsigned cwe_id); + + void + add_rule (const char *title, const char *url); + + void + set_location (physical_location loc); + + void + add_location_with_label (physical_location loc, + const char *text); + + void + set_logical_location (logical_location loc); + + void + add_fix_it_hint_insert_before (physical_location loc, + const char *addition); + void + add_fix_it_hint_insert_after (physical_location loc, + const char *addition); + void + add_fix_it_hint_replace (physical_location loc, + const char *replacement); + void + add_fix_it_hint_delete (physical_location loc); + + void + take_execution_path (execution_path path); + + void + finish (const char *fmt, ...) + LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (2) + LIBDIAGNOSTICS_PARAM_GCC_FORMAT_STRING (2, 3); + + void + finish_va (const char *fmt, va_list *args) + LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (2) + LIBDIAGNOSTICS_PARAM_GCC_FORMAT_STRING (2, 0); + + ::diagnostic * const m_inner; +}; + +/* Wrapper around a diagnostic_manager *, possibly with ownership. */ + +class manager +{ +public: + manager () + : m_inner (diagnostic_manager_new ()), + m_owned (true) + { + } + manager (diagnostic_manager *inner, bool owned) + : m_inner (inner), + m_owned (owned) + { + } + ~manager () + { + if (m_owned) + diagnostic_manager_release (m_inner); + } + + manager (const manager &other) = delete; + manager (manager &&other) + : m_inner (other.m_inner), + m_owned (other.m_owned) + { + other.m_inner = nullptr; + } + + void + set_tool_name (const char *value) + { + diagnostic_manager_set_tool_name (m_inner, value); + } + + void + set_full_name (const char *value) + { + diagnostic_manager_set_full_name (m_inner, value); + } + + void + set_version_string (const char *value) + { + diagnostic_manager_set_version_string (m_inner, value); + } + + void + set_version_url (const char *value) + { + diagnostic_manager_set_version_url (m_inner, value); + } + + text_sink + add_text_sink (FILE *dst_stream, + enum diagnostic_colorize colorize) + { + return text_sink + (diagnostic_manager_add_text_sink (m_inner, dst_stream, colorize)); + } + + void + add_sarif_sink (FILE *dst_stream, + file main_input_file, + enum diagnostic_sarif_version version) + { + diagnostic_manager_add_sarif_sink (m_inner, dst_stream, + main_input_file.m_inner, + version); + } + + void + write_patch (FILE *dst_stream) + { + diagnostic_manager_write_patch (m_inner, dst_stream); + } + + /* Location management. */ + + file + new_file (const char *name, + const char *sarif_source_language) + LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (2) + LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (3); + + void + debug_dump (file f, + FILE *out); + + physical_location + new_location_from_file_and_line (file f, diagnostic_line_num_t line_num); + + physical_location + new_location_from_file_line_column (file f, + line_num_t line_num, + column_num_t column_num); + + physical_location + new_location_from_range (physical_location loc_caret, + physical_location loc_start, + physical_location loc_end); + + void + debug_dump (physical_location loc, + FILE *out); + + logical_location + new_logical_location (enum diagnostic_logical_location_kind_t kind, + logical_location parent, + const char *short_name, + const char *fully_qualified_name, + const char *decorated_name); + + void + debug_dump (logical_location loc, + FILE *out); + + execution_path + new_execution_path (); + + diagnostic + begin_diagnostic (enum diagnostic_level level); + + + diagnostic_manager *m_inner; + bool m_owned; +}; + +// Implementation + +// class execution_path + +inline diagnostic_event_id +execution_path::add_event (physical_location physical_loc, + logical_location logical_loc, + unsigned stack_depth, + const char *fmt, ...) +{ + va_list args; + va_start (args, fmt); + diagnostic_event_id result = add_event_va (physical_loc, + logical_loc, + stack_depth, + fmt, &args); + va_end (args); + + return result; +} + +inline diagnostic_event_id +execution_path::add_event_va (physical_location physical_loc, + logical_location logical_loc, + unsigned stack_depth, + const char *fmt, + va_list *args) +{ + return diagnostic_execution_path_add_event_va (m_inner, + physical_loc.m_inner, + logical_loc.m_inner, + stack_depth, + fmt, + args); +} + +// class group + +inline +group::group (manager &mgr) +: m_mgr (mgr) +{ + diagnostic_manager_begin_group (m_mgr.m_inner); +} + +inline +group::~group () +{ + diagnostic_manager_end_group (m_mgr.m_inner); +} + +// class diagnostic + +inline void +diagnostic::set_cwe (unsigned cwe_id) +{ + diagnostic_set_cwe (m_inner, cwe_id); +} + +inline void +diagnostic::add_rule (const char *title, const char *url) +{ + diagnostic_add_rule (m_inner, title, url); +} + +inline void +diagnostic::set_location (physical_location loc) +{ + diagnostic_set_location (m_inner, loc.m_inner); +} + +inline void +diagnostic::add_location_with_label (physical_location loc, + const char *text) +{ + diagnostic_add_location_with_label (m_inner, loc.m_inner, text); +} + +inline void +diagnostic::set_logical_location (logical_location loc) +{ + diagnostic_set_logical_location (m_inner, loc.m_inner); +} + +inline void +diagnostic::add_fix_it_hint_insert_before (physical_location loc, + const char *addition) +{ + diagnostic_add_fix_it_hint_insert_before (m_inner, + loc.m_inner, + addition); +} + +inline void +diagnostic::add_fix_it_hint_insert_after (physical_location loc, + const char *addition) +{ + diagnostic_add_fix_it_hint_insert_after (m_inner, + loc.m_inner, + addition); +} + +inline void +diagnostic::add_fix_it_hint_replace (physical_location loc, + const char *replacement) +{ + diagnostic_add_fix_it_hint_replace (m_inner, + loc.m_inner, + replacement); +} + +inline void +diagnostic::add_fix_it_hint_delete (physical_location loc) +{ + diagnostic_add_fix_it_hint_delete (m_inner, + loc.m_inner); +} + +inline void +diagnostic::take_execution_path (execution_path path) +{ + diagnostic_take_execution_path (m_inner, + path.m_inner); + path.m_owned = false; +} + +inline void +diagnostic::finish (const char *fmt, ...) +{ + va_list ap; + va_start (ap, fmt); + diagnostic_finish_va (m_inner, fmt, &ap); + va_end (ap); +} + +inline void +diagnostic::finish_va (const char *fmt, va_list *args) +{ + diagnostic_finish_va (m_inner, fmt, args); +} + +// class manager + +inline file +manager::new_file (const char *name, + const char *sarif_source_language) +{ + return file + (diagnostic_manager_new_file (m_inner, name, sarif_source_language)); +} + +inline physical_location +manager::new_location_from_file_and_line (file f, + diagnostic_line_num_t line_num) +{ + return physical_location + (diagnostic_manager_new_location_from_file_and_line (m_inner, + f.m_inner, + line_num)); +} + +inline physical_location +manager::new_location_from_file_line_column (file f, + line_num_t line_num, + column_num_t column_num) +{ + return physical_location + (diagnostic_manager_new_location_from_file_line_column (m_inner, + f.m_inner, + line_num, + column_num)); +} + +inline physical_location +manager::new_location_from_range (physical_location loc_caret, + physical_location loc_start, + physical_location loc_end) +{ + return physical_location + (diagnostic_manager_new_location_from_range (m_inner, + loc_caret.m_inner, + loc_start.m_inner, + loc_end.m_inner)); +} + +inline void +manager::debug_dump (physical_location loc, + FILE *out) +{ + diagnostic_manager_debug_dump_location (m_inner, + loc.m_inner, + out); +} +inline logical_location +manager::new_logical_location (enum diagnostic_logical_location_kind_t kind, + logical_location parent, + const char *short_name, + const char *fully_qualified_name, + const char *decorated_name) +{ + return logical_location + (diagnostic_manager_new_logical_location (m_inner, + kind, + parent.m_inner, + short_name, + fully_qualified_name, + decorated_name)); +} + +inline void +manager::debug_dump (logical_location loc, + FILE *out) +{ + diagnostic_manager_debug_dump_logical_location (m_inner, + loc.m_inner, + out); +} + +inline execution_path +manager::new_execution_path () +{ + return execution_path (diagnostic_manager_new_execution_path (m_inner)); +} + +inline diagnostic +manager::begin_diagnostic (enum diagnostic_level level) +{ + return diagnostic (diagnostic_begin (m_inner, level)); +} + +} // namespace libdiagnostics + +#endif // #ifndef LIBDIAGNOSTICSPP_H From patchwork Thu Aug 15 18:11:53 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: David Malcolm X-Patchwork-Id: 1972873 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; dkim=pass (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=NETLb7qi; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=gcc.gnu.org (client-ip=2620:52:3:1:0:246e:9693:128c; helo=server2.sourceware.org; envelope-from=gcc-patches-bounces~incoming=patchwork.ozlabs.org@gcc.gnu.org; receiver=patchwork.ozlabs.org) Received: from server2.sourceware.org (server2.sourceware.org [IPv6:2620:52:3:1:0:246e:9693:128c]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (secp384r1) server-digest SHA384) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4WlCsJ5PS8z1yXl for ; Fri, 16 Aug 2024 04:12:52 +1000 (AEST) Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 955DC3858432 for ; Thu, 15 Aug 2024 18:12:50 +0000 (GMT) X-Original-To: gcc-patches@gcc.gnu.org Delivered-To: gcc-patches@gcc.gnu.org Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by sourceware.org (Postfix) with ESMTP id 019733858C50 for ; Thu, 15 Aug 2024 18:12:08 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 019733858C50 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 019733858C50 Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=170.10.129.124 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1723745532; cv=none; b=sh8VYcmgQVZGmLfR8ujtxWPBiz1chqyYjpwXTlA4FLx/t3JHYTffe0HGJ99EOsUWI6P0izBfDuVdacXhir+nAvLrG+FLCHEvEYA13IyIGz46tPyO2gag2emjkoxgdAsW9aaDdmy9s4Y1FeIHDcvXXdl6tkmjV9i2zTBIFjghFHY= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1723745532; c=relaxed/simple; bh=skM8dBPrFtP2KrBYa+nVLJUAKy0edUQneOv3xvTOfwI=; h=DKIM-Signature:From:To:Subject:Date:Message-Id:MIME-Version; b=c7cWUKFE78nW8gQD2rJhweb+YMEQRsN8u/yNnGNLrLItrMbKIdWnQlT/cUhUO0R7ZO94uuVAbEYFYquYhnNZ68vspF+e+E8vCfhJJ47A9iyTlmT1T+7q48KvCR7jqCd+pfVHZY7gli5z/RbOpsWCgYYJ9y9DgniLFAuakh6rVFY= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1723745528; 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=vzScpv6BnYJqsrrbV48N8LoXSihUT9ZhpNyp3pwv8YQ=; b=NETLb7qiA3FGTpLhs2OuAg4tR6aMQX4TwcwrkA7giSUxOCgYHyw+JpBiQkEiMRTj3XkSlz 0+yYB5il7ecNnuuJIYz+/GfnLxKcVonF9HerV/+L6Ad/Zu8OXI7biFM6poU0IT6F23G9ev peiD57fLSkPXXaqgwpuQZEGNldoFy0Y= Received: from mx-prod-mc-02.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-601-8Px7k84uP2688uQMBRwGeg-1; Thu, 15 Aug 2024 14:12:06 -0400 X-MC-Unique: 8Px7k84uP2688uQMBRwGeg-1 Received: from mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.17]) (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 mx-prod-mc-02.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 0E35D1955D4B for ; Thu, 15 Aug 2024 18:12:06 +0000 (UTC) Received: from t14s.localdomain.com (unknown [10.22.16.43]) by mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 2EFA61955D44; Thu, 15 Aug 2024 18:12:04 +0000 (UTC) From: David Malcolm To: gcc-patches@gcc.gnu.org Cc: David Malcolm Subject: [PATCH 4/7] testsuite: move dg-test cleanup code from gcc-dg.exp to its own file Date: Thu, 15 Aug 2024 14:11:53 -0400 Message-Id: <20240815181156.1815075-5-dmalcolm@redhat.com> In-Reply-To: <20240815181156.1815075-1-dmalcolm@redhat.com> References: <20240815181156.1815075-1-dmalcolm@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.0 on 10.30.177.17 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com 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 X-BeenThere: gcc-patches@gcc.gnu.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Gcc-patches mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: gcc-patches-bounces~incoming=patchwork.ozlabs.org@gcc.gnu.org I need to use this cleanup logic for the testsuite for libdiagnostics where it's too awkward to directly use gcc-dg.exp itself. No functional change intended. gcc/testsuite/ChangeLog: * lib/dg-test-cleanup.exp: New file, from material moved from lib/gcc-dg.exp. * lib/gcc-dg.exp: Add load_lib of dg-test-cleanup.exp. (cleanup-after-saved-dg-test): Move to lib/dg-test-cleanup.exp. (dg-test): Likewise for override. (initialize_prune_notes): Likewise. libatomic/ChangeLog: * testsuite/lib/libatomic.exp: Add "load_gcc_lib dg-test-cleanup.exp". libgomp/ChangeLog: * testsuite/lib/libgomp.exp: Add "load_gcc_lib dg-test-cleanup.exp". libitm/ChangeLog: * testsuite/lib/libitm.exp: Add "load_gcc_lib dg-test-cleanup.exp". libphobos/ChangeLog: * testsuite/lib/libphobos-dg.exp: Add "load_gcc_lib dg-test-cleanup.exp". libstdc++-v3/ChangeLog: * testsuite/lib/libstdc++.exp: Add "load_gcc_lib dg-test-cleanup.exp". libvtv/ChangeLog: * testsuite/lib/libvtv.exp: Add "load_gcc_lib dg-test-cleanup.exp". Signed-off-by: David Malcolm --- gcc/testsuite/lib/dg-test-cleanup.exp | 116 +++++++++++++++++++++++ gcc/testsuite/lib/gcc-dg.exp | 102 +------------------- libatomic/testsuite/lib/libatomic.exp | 1 + libgomp/testsuite/lib/libgomp.exp | 1 + libitm/testsuite/lib/libitm.exp | 1 + libphobos/testsuite/lib/libphobos-dg.exp | 1 + libstdc++-v3/testsuite/lib/libstdc++.exp | 1 + libvtv/testsuite/lib/libvtv.exp | 1 + 8 files changed, 123 insertions(+), 101 deletions(-) create mode 100644 gcc/testsuite/lib/dg-test-cleanup.exp diff --git a/gcc/testsuite/lib/dg-test-cleanup.exp b/gcc/testsuite/lib/dg-test-cleanup.exp new file mode 100644 index 000000000000..b2b8507a0320 --- /dev/null +++ b/gcc/testsuite/lib/dg-test-cleanup.exp @@ -0,0 +1,116 @@ +# Copyright (C) 1997-2024 Free Software Foundation, Inc. + +# This program 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 of the License, or +# (at your option) any later version. +# +# This program 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 +# . + +# We need to make sure that additional_* are cleared out after every +# test. It is not enough to clear them out *before* the next test run +# because gcc-target-compile gets run directly from some .exp files +# (outside of any test). (Those uses should eventually be eliminated.) + +# Because the DG framework doesn't provide a hook that is run at the +# end of a test, we must replace dg-test with a wrapper. + +if { [info procs saved-dg-test] == [list] } { + rename dg-test saved-dg-test + + # Helper function for cleanups that should happen after the call + # to the real dg-test, whether or not it returns normally, or + # fails with an error. + proc cleanup-after-saved-dg-test { } { + global additional_files + global additional_sources + global additional_sources_used + global additional_prunes + global compiler_conditional_xfail_data + global shouldfail + global expect_ice + global testname_with_flags + global set_target_env_var + global set_compiler_env_var + global saved_compiler_env_var + global keep_saved_temps_suffixes + global nn_line_numbers_enabled + global multiline_expected_outputs + global freeform_regexps + global save_linenr_varnames + + set additional_files "" + set additional_sources "" + set additional_sources_used "" + set additional_prunes "" + set shouldfail 0 + set expect_ice 0 + if [info exists set_target_env_var] { + unset set_target_env_var + } + if [info exists set_compiler_env_var] { + restore-compiler-env-var + unset set_compiler_env_var + unset saved_compiler_env_var + } + if [info exists keep_saved_temps_suffixes] { + unset keep_saved_temps_suffixes + } + unset_timeout_vars + if [info exists compiler_conditional_xfail_data] { + unset compiler_conditional_xfail_data + } + if [info exists testname_with_flags] { + unset testname_with_flags + } + set nn_line_numbers_enabled 0 + set multiline_expected_outputs [] + set freeform_regexps [] + + if { [info exists save_linenr_varnames] } { + foreach varname $save_linenr_varnames { + # Cleanup varname + eval global $varname + eval unset $varname + + # Cleanup varname_used, or generate defined-but-not-used + # warning. + set varname_used used_$varname + eval global $varname_used + eval set used [info exists $varname_used] + if { $used } { + eval unset $varname_used + } else { + regsub {^saved_linenr_} $varname "" org_varname + warning "dg-line var $org_varname defined, but not used" + } + } + unset save_linenr_varnames + } + + initialize_prune_notes + } + + proc dg-test { args } { + global errorInfo + + if { [ catch { eval saved-dg-test $args } errmsg ] } { + set saved_info $errorInfo + cleanup-after-saved-dg-test + error $errmsg $saved_info + } + cleanup-after-saved-dg-test + } +} + +proc initialize_prune_notes { } { + global prune_notes + set prune_notes 1 +} diff --git a/gcc/testsuite/lib/gcc-dg.exp b/gcc/testsuite/lib/gcc-dg.exp index 992062103c12..33b7e5a1e2c2 100644 --- a/gcc/testsuite/lib/gcc-dg.exp +++ b/gcc/testsuite/lib/gcc-dg.exp @@ -34,6 +34,7 @@ load_lib target-libpath.exp load_lib torture-options.exp load_lib fortran-modules.exp load_lib multiline.exp +load_lib dg-test-cleanup.exp # We set LC_ALL and LANG to C so that we get the same error messages as expected. setenv LC_ALL C @@ -990,102 +991,6 @@ proc output-exists-not { args } { } } -# We need to make sure that additional_* are cleared out after every -# test. It is not enough to clear them out *before* the next test run -# because gcc-target-compile gets run directly from some .exp files -# (outside of any test). (Those uses should eventually be eliminated.) - -# Because the DG framework doesn't provide a hook that is run at the -# end of a test, we must replace dg-test with a wrapper. - -if { [info procs saved-dg-test] == [list] } { - rename dg-test saved-dg-test - - # Helper function for cleanups that should happen after the call - # to the real dg-test, whether or not it returns normally, or - # fails with an error. - proc cleanup-after-saved-dg-test { } { - global additional_files - global additional_sources - global additional_sources_used - global additional_prunes - global compiler_conditional_xfail_data - global shouldfail - global expect_ice - global testname_with_flags - global set_target_env_var - global set_compiler_env_var - global saved_compiler_env_var - global keep_saved_temps_suffixes - global nn_line_numbers_enabled - global multiline_expected_outputs - global freeform_regexps - global save_linenr_varnames - - set additional_files "" - set additional_sources "" - set additional_sources_used "" - set additional_prunes "" - set shouldfail 0 - set expect_ice 0 - if [info exists set_target_env_var] { - unset set_target_env_var - } - if [info exists set_compiler_env_var] { - restore-compiler-env-var - unset set_compiler_env_var - unset saved_compiler_env_var - } - if [info exists keep_saved_temps_suffixes] { - unset keep_saved_temps_suffixes - } - unset_timeout_vars - if [info exists compiler_conditional_xfail_data] { - unset compiler_conditional_xfail_data - } - if [info exists testname_with_flags] { - unset testname_with_flags - } - set nn_line_numbers_enabled 0 - set multiline_expected_outputs [] - set freeform_regexps [] - - if { [info exists save_linenr_varnames] } { - foreach varname $save_linenr_varnames { - # Cleanup varname - eval global $varname - eval unset $varname - - # Cleanup varname_used, or generate defined-but-not-used - # warning. - set varname_used used_$varname - eval global $varname_used - eval set used [info exists $varname_used] - if { $used } { - eval unset $varname_used - } else { - regsub {^saved_linenr_} $varname "" org_varname - warning "dg-line var $org_varname defined, but not used" - } - } - unset save_linenr_varnames - } - - initialize_prune_notes - } - - proc dg-test { args } { - global errorInfo - - if { [ catch { eval saved-dg-test $args } errmsg ] } { - set saved_info $errorInfo - cleanup-after-saved-dg-test - error $errmsg $saved_info - } - cleanup-after-saved-dg-test - } -} - if { [info procs saved-dg-warning] == [list] \ && [info exists gcc_warning_prefix] } { rename dg-warning saved-dg-warning @@ -1329,11 +1234,6 @@ proc dg-missed { args } { variable prune_notes -proc initialize_prune_notes { } { - global prune_notes - set prune_notes 1 -} - initialize_prune_notes proc dg-note { args } { diff --git a/libatomic/testsuite/lib/libatomic.exp b/libatomic/testsuite/lib/libatomic.exp index ed6ba806732f..f90bc4b708a0 100644 --- a/libatomic/testsuite/lib/libatomic.exp +++ b/libatomic/testsuite/lib/libatomic.exp @@ -50,6 +50,7 @@ load_gcc_lib torture-options.exp load_gcc_lib timeout.exp load_gcc_lib timeout-dg.exp load_gcc_lib fortran-modules.exp +load_gcc_lib dg-test-cleanup.exp set dg-do-what-default run diff --git a/libgomp/testsuite/lib/libgomp.exp b/libgomp/testsuite/lib/libgomp.exp index 7c1092629168..cad0280f3798 100644 --- a/libgomp/testsuite/lib/libgomp.exp +++ b/libgomp/testsuite/lib/libgomp.exp @@ -41,6 +41,7 @@ load_gcc_lib scanwpaipa.exp load_gcc_lib timeout-dg.exp load_gcc_lib torture-options.exp load_gcc_lib fortran-modules.exp +load_gcc_lib dg-test-cleanup.exp # Try to load a test support file, built during libgomp configuration. # Search in '..' vs. '.' to support parallel vs. sequential testing. diff --git a/libitm/testsuite/lib/libitm.exp b/libitm/testsuite/lib/libitm.exp index 3e60797c3e31..5b387f2ec408 100644 --- a/libitm/testsuite/lib/libitm.exp +++ b/libitm/testsuite/lib/libitm.exp @@ -50,6 +50,7 @@ load_gcc_lib scanwpaipa.exp load_gcc_lib timeout-dg.exp load_gcc_lib torture-options.exp load_gcc_lib fortran-modules.exp +load_gcc_lib dg-test-cleanup.exp set dg-do-what-default run diff --git a/libphobos/testsuite/lib/libphobos-dg.exp b/libphobos/testsuite/lib/libphobos-dg.exp index 965ff025a04d..09b644ac573f 100644 --- a/libphobos/testsuite/lib/libphobos-dg.exp +++ b/libphobos/testsuite/lib/libphobos-dg.exp @@ -29,6 +29,7 @@ load_gcc_lib scanipa.exp load_gcc_lib torture-options.exp load_gcc_lib timeout-dg.exp load_gcc_lib fortran-modules.exp +load_gcc_lib dg-test-cleanup.exp load_gcc_lib gcc-dg.exp # Utility routines. diff --git a/libstdc++-v3/testsuite/lib/libstdc++.exp b/libstdc++-v3/testsuite/lib/libstdc++.exp index 2510c7f4cbb9..161e9501a379 100644 --- a/libstdc++-v3/testsuite/lib/libstdc++.exp +++ b/libstdc++-v3/testsuite/lib/libstdc++.exp @@ -73,6 +73,7 @@ load_gcc_lib timeout.exp load_gcc_lib timeout-dg.exp load_gcc_lib wrapper.exp load_gcc_lib target-utils.exp +load_gcc_lib dg-test-cleanup.exp # Useful for debugging. Pass the name of a variable and the verbosity # threshold (number of -v's on the command line). diff --git a/libvtv/testsuite/lib/libvtv.exp b/libvtv/testsuite/lib/libvtv.exp index bfd03d7d2580..61980141a7ea 100644 --- a/libvtv/testsuite/lib/libvtv.exp +++ b/libvtv/testsuite/lib/libvtv.exp @@ -48,6 +48,7 @@ load_gcc_lib scanwpaipa.exp load_gcc_lib timeout-dg.exp load_gcc_lib torture-options.exp load_gcc_lib fortran-modules.exp +load_gcc_lib dg-test-cleanup.exp set dg-do-what-default run From patchwork Thu Aug 15 18:11:54 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: David Malcolm X-Patchwork-Id: 1972874 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; dkim=pass (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=ezQwD2Wl; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=gcc.gnu.org (client-ip=2620:52:3:1:0:246e:9693:128c; helo=server2.sourceware.org; envelope-from=gcc-patches-bounces~incoming=patchwork.ozlabs.org@gcc.gnu.org; receiver=patchwork.ozlabs.org) Received: from server2.sourceware.org (server2.sourceware.org [IPv6:2620:52:3:1:0:246e:9693:128c]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (secp384r1) server-digest SHA384) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4WlCsb0cbZz1yXl for ; Fri, 16 Aug 2024 04:13:07 +1000 (AEST) Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 8EF1D3858C41 for ; Thu, 15 Aug 2024 18:13:04 +0000 (GMT) X-Original-To: gcc-patches@gcc.gnu.org Delivered-To: gcc-patches@gcc.gnu.org Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by sourceware.org (Postfix) with ESMTP id 1B5C13858D29 for ; Thu, 15 Aug 2024 18:12:11 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 1B5C13858D29 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 1B5C13858D29 Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=170.10.129.124 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1723745539; cv=none; b=Dp0XLdEyKU2qCBSmRbsuj4W2qBZxT3odgeZ3UAbVgw42E6wXyLcTQHpDJYT8JgLAfvJ7/8a7ml6r64VSo1FKV6pc1Klj0KDEMCnfgQtkTAH/k5UmHuXPxLURuoKJaEuhGDGaFHe7MXgVZKcKNxszwymdxzVNTqUeBJVH2jU/3Ak= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1723745539; c=relaxed/simple; bh=+4OPyZGEvOf1Lb9OfpHGijkBcV9UeE2TlrfEg9Uh9H4=; h=DKIM-Signature:From:To:Subject:Date:Message-Id:MIME-Version; b=HN3YXaMR4O1zgRU9fx2LxbmSszVnFn1RKiPNKt2GCii4fojkJVyhsBEqm3qmvP0xCJofbE+BfrKllpPP/PwbxWRGrpokJ13rmefaBRqEKdSqCPp4+fNoHrRtPo/ICWfKm7qR9jOmgAmQGFzjSc9PATwXYRVmfa6/cNIwaIL6FHE= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1723745530; 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=ZaG8MUyMY/5d4nJmC3MibcFTjrUkfgmCLHxIEeQocoI=; b=ezQwD2Wlb96Tunqp4YM+Bv5Fe4lCjaXRAuAS2ks/PQbWqwf8HzW3WHZ51bw2KXmRdst9PA 4WmHVtfij6oBCXrig3glqeCTaAuVZK9kNkwa7x3i7sFgfFUiobUOEF5EsvQmLuNZ3eZyYs 8f/Is9ybSrc9T3y1f4TwWrxGEs4D6LQ= Received: from mx-prod-mc-04.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-611-weCdjFDCP-6dPfsUfHMvvg-1; Thu, 15 Aug 2024 14:12:08 -0400 X-MC-Unique: weCdjFDCP-6dPfsUfHMvvg-1 Received: from mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.17]) (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 mx-prod-mc-04.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id CB83D1955D50 for ; Thu, 15 Aug 2024 18:12:07 +0000 (UTC) Received: from t14s.localdomain.com (unknown [10.22.16.43]) by mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id A6BA91955D4A; Thu, 15 Aug 2024 18:12:06 +0000 (UTC) From: David Malcolm To: gcc-patches@gcc.gnu.org Cc: David Malcolm Subject: [PATCH 5/7] libdiagnostics v3: test suite Date: Thu, 15 Aug 2024 14:11:54 -0400 Message-Id: <20240815181156.1815075-6-dmalcolm@redhat.com> In-Reply-To: <20240815181156.1815075-1-dmalcolm@redhat.com> References: <20240815181156.1815075-1-dmalcolm@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.0 on 10.30.177.17 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com X-Spam-Status: No, score=-11.5 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, KAM_LOTSOFHASH, 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 X-BeenThere: gcc-patches@gcc.gnu.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Gcc-patches mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: gcc-patches-bounces~incoming=patchwork.ozlabs.org@gcc.gnu.org Changed in v3: * split out the C and C++ API tests into this patch * heavily rewritten libdiagnostics.exp; added support for Python tests * tests updated for API changes, rewritten and extended gcc/testsuite/ChangeLog: * libdiagnostics.dg/libdiagnostics.exp: New, adapted from jit.exp. * libdiagnostics.dg/sarif.py: New. * libdiagnostics.dg/test-dump.c: New test. * libdiagnostics.dg/test-error-c.py: New test. * libdiagnostics.dg/test-error-with-note-c.py: New test. * libdiagnostics.dg/test-error-with-note.c: New test. * libdiagnostics.dg/test-error-with-note.cc: New test. * libdiagnostics.dg/test-error.c: New test. * libdiagnostics.dg/test-error.cc: New test. * libdiagnostics.dg/test-fix-it-hint-c.py: New test. * libdiagnostics.dg/test-fix-it-hint.c: New test. * libdiagnostics.dg/test-fix-it-hint.cc: New test. * libdiagnostics.dg/test-helpers++.h: New test. * libdiagnostics.dg/test-helpers.h: New test. * libdiagnostics.dg/test-labelled-ranges.c: New test. * libdiagnostics.dg/test-labelled-ranges.cc: New test. * libdiagnostics.dg/test-labelled-ranges.py: New test. * libdiagnostics.dg/test-logical-location-c.py: New test. * libdiagnostics.dg/test-logical-location.c: New test. * libdiagnostics.dg/test-metadata-c.py: New test. * libdiagnostics.dg/test-metadata.c: New test. * libdiagnostics.dg/test-multiple-lines-c.py: New test. * libdiagnostics.dg/test-multiple-lines.c: New test. * libdiagnostics.dg/test-no-column-c.py: New test. * libdiagnostics.dg/test-no-column.c: New test. * libdiagnostics.dg/test-no-diagnostics-c.py: New test. * libdiagnostics.dg/test-no-diagnostics.c: New test. * libdiagnostics.dg/test-note-with-fix-it-hint-c.py: New test. * libdiagnostics.dg/test-note-with-fix-it-hint.c: New test. * libdiagnostics.dg/test-text-sink-options.c: New test. * libdiagnostics.dg/test-warning-c.py: New test. * libdiagnostics.dg/test-warning-with-path-c.py: New test. * libdiagnostics.dg/test-warning-with-path.c: New test. * libdiagnostics.dg/test-warning.c: New test. * libdiagnostics.dg/test-write-sarif-to-file-c.py: New test. * libdiagnostics.dg/test-write-sarif-to-file.c: New test. * libdiagnostics.dg/test-write-text-to-file.c: New test. Signed-off-by: David Malcolm --- .../libdiagnostics.dg/libdiagnostics.exp | 296 ++++++++++++++++++ gcc/testsuite/libdiagnostics.dg/sarif.py | 23 ++ gcc/testsuite/libdiagnostics.dg/test-dump.c | 69 ++++ .../libdiagnostics.dg/test-error-c.py | 54 ++++ .../test-error-with-note-c.py | 50 +++ .../libdiagnostics.dg/test-error-with-note.c | 74 +++++ .../libdiagnostics.dg/test-error-with-note.cc | 55 ++++ gcc/testsuite/libdiagnostics.dg/test-error.c | 59 ++++ gcc/testsuite/libdiagnostics.dg/test-error.cc | 47 +++ .../libdiagnostics.dg/test-fix-it-hint-c.py | 46 +++ .../libdiagnostics.dg/test-fix-it-hint.c | 81 +++++ .../libdiagnostics.dg/test-fix-it-hint.cc | 74 +++++ .../libdiagnostics.dg/test-helpers++.h | 28 ++ .../libdiagnostics.dg/test-helpers.h | 72 +++++ .../libdiagnostics.dg/test-labelled-ranges.c | 69 ++++ .../libdiagnostics.dg/test-labelled-ranges.cc | 64 ++++ .../libdiagnostics.dg/test-labelled-ranges.py | 48 +++ .../test-logical-location-c.py | 37 +++ .../libdiagnostics.dg/test-logical-location.c | 79 +++++ .../libdiagnostics.dg/test-metadata-c.py | 45 +++ .../libdiagnostics.dg/test-metadata.c | 61 ++++ .../test-multiple-lines-c.py | 83 +++++ .../libdiagnostics.dg/test-multiple-lines.c | 76 +++++ .../libdiagnostics.dg/test-no-column-c.py | 35 +++ .../libdiagnostics.dg/test-no-column.c | 52 +++ .../test-no-diagnostics-c.py | 42 +++ .../libdiagnostics.dg/test-no-diagnostics.c | 25 ++ .../test-note-with-fix-it-hint-c.py | 54 ++++ .../test-note-with-fix-it-hint.c | 69 ++++ .../test-text-sink-options.c | 59 ++++ .../libdiagnostics.dg/test-warning-c.py | 54 ++++ .../test-warning-with-path-c.py | 108 +++++++ .../test-warning-with-path.c | 125 ++++++++ .../libdiagnostics.dg/test-warning.c | 65 ++++ .../test-write-sarif-to-file-c.py | 55 ++++ .../test-write-sarif-to-file.c | 55 ++++ .../test-write-text-to-file.c | 47 +++ 37 files changed, 2435 insertions(+) create mode 100644 gcc/testsuite/libdiagnostics.dg/libdiagnostics.exp create mode 100644 gcc/testsuite/libdiagnostics.dg/sarif.py create mode 100644 gcc/testsuite/libdiagnostics.dg/test-dump.c create mode 100644 gcc/testsuite/libdiagnostics.dg/test-error-c.py create mode 100644 gcc/testsuite/libdiagnostics.dg/test-error-with-note-c.py create mode 100644 gcc/testsuite/libdiagnostics.dg/test-error-with-note.c create mode 100644 gcc/testsuite/libdiagnostics.dg/test-error-with-note.cc create mode 100644 gcc/testsuite/libdiagnostics.dg/test-error.c create mode 100644 gcc/testsuite/libdiagnostics.dg/test-error.cc create mode 100644 gcc/testsuite/libdiagnostics.dg/test-fix-it-hint-c.py create mode 100644 gcc/testsuite/libdiagnostics.dg/test-fix-it-hint.c create mode 100644 gcc/testsuite/libdiagnostics.dg/test-fix-it-hint.cc create mode 100644 gcc/testsuite/libdiagnostics.dg/test-helpers++.h create mode 100644 gcc/testsuite/libdiagnostics.dg/test-helpers.h create mode 100644 gcc/testsuite/libdiagnostics.dg/test-labelled-ranges.c create mode 100644 gcc/testsuite/libdiagnostics.dg/test-labelled-ranges.cc create mode 100644 gcc/testsuite/libdiagnostics.dg/test-labelled-ranges.py create mode 100644 gcc/testsuite/libdiagnostics.dg/test-logical-location-c.py create mode 100644 gcc/testsuite/libdiagnostics.dg/test-logical-location.c create mode 100644 gcc/testsuite/libdiagnostics.dg/test-metadata-c.py create mode 100644 gcc/testsuite/libdiagnostics.dg/test-metadata.c create mode 100644 gcc/testsuite/libdiagnostics.dg/test-multiple-lines-c.py create mode 100644 gcc/testsuite/libdiagnostics.dg/test-multiple-lines.c create mode 100644 gcc/testsuite/libdiagnostics.dg/test-no-column-c.py create mode 100644 gcc/testsuite/libdiagnostics.dg/test-no-column.c create mode 100644 gcc/testsuite/libdiagnostics.dg/test-no-diagnostics-c.py create mode 100644 gcc/testsuite/libdiagnostics.dg/test-no-diagnostics.c create mode 100644 gcc/testsuite/libdiagnostics.dg/test-note-with-fix-it-hint-c.py create mode 100644 gcc/testsuite/libdiagnostics.dg/test-note-with-fix-it-hint.c create mode 100644 gcc/testsuite/libdiagnostics.dg/test-text-sink-options.c create mode 100644 gcc/testsuite/libdiagnostics.dg/test-warning-c.py create mode 100644 gcc/testsuite/libdiagnostics.dg/test-warning-with-path-c.py create mode 100644 gcc/testsuite/libdiagnostics.dg/test-warning-with-path.c create mode 100644 gcc/testsuite/libdiagnostics.dg/test-warning.c create mode 100644 gcc/testsuite/libdiagnostics.dg/test-write-sarif-to-file-c.py create mode 100644 gcc/testsuite/libdiagnostics.dg/test-write-sarif-to-file.c create mode 100644 gcc/testsuite/libdiagnostics.dg/test-write-text-to-file.c diff --git a/gcc/testsuite/libdiagnostics.dg/libdiagnostics.exp b/gcc/testsuite/libdiagnostics.dg/libdiagnostics.exp new file mode 100644 index 000000000000..d29a469ae4f8 --- /dev/null +++ b/gcc/testsuite/libdiagnostics.dg/libdiagnostics.exp @@ -0,0 +1,296 @@ +# Test code for libdiagnostics.so +# +# We will compile each of libdiagnostics.dg/test-*.{c,cc} into an executable +# dynamically linked against libdiagnostics.so, and then run each +# such executable. +# +# These executables call into the libdiagnostics.so API to emit diagnostics, +# sometimes in text form, and other times in SARIF form. + +# 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 "libdiagnostics". +# 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 libdiagnostics + +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 +load_lib valgrind.exp +load_lib scansarif.exp +load_lib dg-test-cleanup.exp + +# The default do-what keyword. +set dg-do-what-default compile + +# Adapted from jit.exp. +# +# Execute the executable file. +# 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 + + set status [catch "exec -keepnewline $args" exe_output] + verbose "Test program returned $exe_output" 2 + + if $run_under_valgrind { + upvar 2 name name + parse_valgrind_logfile $name $valgrind_logfile fail + } + + # We don't do prune_gcc_output here, as we want + # to check *exactly* what we get from libdiagnostics + + return $exe_output +} + +# (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 and C++ tests within the testsuite: gcc/testsuite/libdiagnostics.dg/test-*.{c,c++} +set c_tests [find $srcdir/$subdir test-*.c] +set cxx_tests [find $srcdir/$subdir test-*.cc] +set tests [concat $c_tests $cxx_tests] + +verbose "tests: $tests" + +# 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 libdiagnostics-dg-test { prog do_what extra_tool_flags } { + verbose "within libdiagnostics-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] + + # 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" + } else { + set compilation_function "gcc_target_compile" + } + set options "{additional_flags=$extra_tool_flags}" + verbose "compilation_function=$compilation_function" + verbose "options=$options" + + set comp_output [$compilation_function $prog $output_file \ + "executable" $options] + upvar 1 name name + if ![libdiagnostics_check_compile "$name" "initial compilation" \ + $output_file $comp_output] then { + return + } + + # Run the test executable. + + # 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 + + global diagnostics-exe-params + set args ${diagnostics-exe-params} + set diagnostics-exe-params {} + + set exe_output [fixed_host_execute $output_file $args ] + verbose "exe_output: $exe_output" + + restore_ld_library_path_env_vars + + # Analyze the output from the executable. To some what extent this + # is duplicating prune_gcc_output, but we're looking for *precise* + # output, so we can't reuse prune_gcc_output. + + global testname_with_flags + set testname_with_flags $name + + # Handle any freeform regexps. + set exe_output [handle-dg-regexps $exe_output] + + # Call into multiline.exp to handle any multiline output directives. + set exe_output [handle-multiline-outputs $exe_output] + + # Normally we would return $exe_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 $exe_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 diff --git a/gcc/testsuite/libdiagnostics.dg/sarif.py b/gcc/testsuite/libdiagnostics.dg/sarif.py new file mode 100644 index 000000000000..7daf35b58190 --- /dev/null +++ b/gcc/testsuite/libdiagnostics.dg/sarif.py @@ -0,0 +1,23 @@ +import json +import os + +def sarif_from_env(): + # return parsed JSON content a SARIF_PATH file + json_filename = os.environ['SARIF_PATH'] + json_filename += '.sarif' + print('json_filename: %r' % json_filename) + with open(json_filename) as f: + json_data = f.read() + return json.loads(json_data) + +def get_location_artifact_uri(location): + return location['physicalLocation']['artifactLocation']['uri'] + +def get_location_physical_region(location): + return location['physicalLocation']['region'] + +def get_location_snippet_text(location): + return location['physicalLocation']['contextRegion']['snippet']['text'] + +def get_location_relationships(location): + return location['relationships'] diff --git a/gcc/testsuite/libdiagnostics.dg/test-dump.c b/gcc/testsuite/libdiagnostics.dg/test-dump.c new file mode 100644 index 000000000000..9f0576d5cd3e --- /dev/null +++ b/gcc/testsuite/libdiagnostics.dg/test-dump.c @@ -0,0 +1,69 @@ +/* Usage example of dump API. */ + +#include "libdiagnostics.h" + +const int line_num = 42; + +int +main () +{ + diagnostic_manager *diag_mgr = diagnostic_manager_new (); + + const diagnostic_file *file = diagnostic_manager_new_file (diag_mgr, "foo.c", "c"); + + fprintf (stderr, "file: "); + diagnostic_manager_debug_dump_file (diag_mgr, file, stderr); + fprintf (stderr, "\n"); + /* { dg-begin-multiline-output "" } +file: file(name="foo.c", sarif_source_language="c") + { dg-end-multiline-output "" } */ + + const diagnostic_physical_location *loc_start + = diagnostic_manager_new_location_from_file_line_column (diag_mgr, file, line_num, 8); + const diagnostic_physical_location *loc_end + = diagnostic_manager_new_location_from_file_line_column (diag_mgr, file, line_num, 19); + const diagnostic_physical_location *loc_range + = diagnostic_manager_new_location_from_range (diag_mgr, + loc_start, + loc_start, + loc_end); + + fprintf (stderr, "loc_start: "); + diagnostic_manager_debug_dump_location (diag_mgr, loc_start, stderr); + fprintf (stderr, "\n"); + /* { dg-begin-multiline-output "" } +loc_start: foo.c:42:8: + { dg-end-multiline-output "" } */ + + fprintf (stderr, "loc_end: "); + diagnostic_manager_debug_dump_location (diag_mgr, loc_end, stderr); + fprintf (stderr, "\n"); + /* { dg-begin-multiline-output "" } +loc_end: foo.c:42:19: + { dg-end-multiline-output "" } */ + + fprintf (stderr, "loc_range: "); + diagnostic_manager_debug_dump_location (diag_mgr, loc_range, stderr); + fprintf (stderr, "\n"); + /* { dg-begin-multiline-output "" } +loc_range: foo.c:42:8: + { dg-end-multiline-output "" } */ + + const diagnostic_logical_location *logical_loc + = diagnostic_manager_new_logical_location (diag_mgr, + DIAGNOSTIC_LOGICAL_LOCATION_KIND_FUNCTION, + NULL, /* parent */ + "test_short_name", + "test_qualified_name", + "test_decorated_name"); + + fprintf (stderr, "logical_loc: "); + diagnostic_manager_debug_dump_logical_location (diag_mgr, logical_loc, stderr); + fprintf (stderr, "\n"); + /* { dg-begin-multiline-output "" } +logical_loc: logical_location(kind=function, short_name="test_short_name", fully_qualified_name="test_qualified_name", decorated_name="test_decorated_name") + { dg-end-multiline-output "" } */ + + diagnostic_manager_release (diag_mgr); + return 0; +}; diff --git a/gcc/testsuite/libdiagnostics.dg/test-error-c.py b/gcc/testsuite/libdiagnostics.dg/test-error-c.py new file mode 100644 index 000000000000..84e3899f3b02 --- /dev/null +++ b/gcc/testsuite/libdiagnostics.dg/test-error-c.py @@ -0,0 +1,54 @@ +from sarif import * + +import pytest + +@pytest.fixture(scope='function', autouse=True) +def sarif(): + return sarif_from_env() + +expected_line_num = 17 +expected_file_name = 'test-error.c' + +def test_sarif_output(sarif): + schema = sarif['$schema'] + assert schema == 'https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json' + + version = sarif['version'] + assert version == '2.1.0' + + runs = sarif['runs'] + run = runs[0] + + tool = run['tool'] + assert tool['driver']['name'] == expected_file_name + '.exe' + + invocations = run['invocations'] + assert len(invocations) == 1 + assert 'workingDirectory' in invocations[0] + assert 'startTimeUtc' in invocations[0] + assert invocations[0]['executionSuccessful'] == False + assert invocations[0]['toolExecutionNotifications'] == [] + assert 'endTimeUtc' in invocations[0] + + artifacts = run['artifacts'] + assert len(artifacts) == 1 + assert artifacts[0]['location']['uri'].endswith(expected_file_name) + assert artifacts[0]['sourceLanguage'] == 'c' + assert 'PRINT' in artifacts[0]['contents']['text'] + assert artifacts[0]['roles'] == ["analysisTarget"] + + results = run['results'] + assert len(results) == 1 + assert results[0]['ruleId'] == 'error' + assert results[0]['level'] == 'error' + assert results[0]['message']['text'] == "can't find 'foo'" + assert len(results[0]['locations']) == 1 + location = results[0]['locations'][0] + phys_loc = location['physicalLocation'] + assert phys_loc['artifactLocation']['uri'].endswith(expected_file_name) + assert phys_loc['region']['startLine'] == expected_line_num + assert phys_loc['region']['startColumn'] == 8 + assert phys_loc['region']['endColumn'] == 20 + assert phys_loc['contextRegion']['startLine'] == expected_line_num + assert phys_loc['contextRegion']['snippet']['text'] \ + == 'PRINT "hello world!";\n' diff --git a/gcc/testsuite/libdiagnostics.dg/test-error-with-note-c.py b/gcc/testsuite/libdiagnostics.dg/test-error-with-note-c.py new file mode 100644 index 000000000000..bb352fbac848 --- /dev/null +++ b/gcc/testsuite/libdiagnostics.dg/test-error-with-note-c.py @@ -0,0 +1,50 @@ +from sarif import * + +import pytest + +@pytest.fixture(scope='function', autouse=True) +def sarif(): + return sarif_from_env() + +expected_line_num = 18 + +def test_sarif_output_for_note(sarif): + schema = sarif['$schema'] + assert schema == 'https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json' + + version = sarif['version'] + assert version == '2.1.0' + + runs = sarif['runs'] + run = runs[0] + + tool = run['tool'] + assert tool['driver']['name'] == 'test-error-with-note.c.exe' + + results = run['results'] + assert len(results) == 1 + assert results[0]['ruleId'] == 'error' + assert results[0]['level'] == 'error' + assert results[0]['message']['text'] == "can't find 'foo'" + assert len(results[0]['locations']) == 1 + location = results[0]['locations'][0] + phys_loc = location['physicalLocation'] + assert phys_loc['artifactLocation']['uri'].endswith('test-error-with-note.c') + assert phys_loc['region']['startLine'] == expected_line_num + assert phys_loc['region']['startColumn'] == 8 + assert phys_loc['region']['endColumn'] == 20 + assert phys_loc['contextRegion']['startLine'] == expected_line_num + assert phys_loc['contextRegion']['snippet']['text'] \ + == 'PRINT "hello world!";\n' + + assert len(results[0]['relatedLocations']) == 1 + note = results[0]['relatedLocations'][0] + phys_loc = note['physicalLocation'] + assert phys_loc['artifactLocation']['uri'].endswith('test-error-with-note.c') + assert phys_loc['region']['startLine'] == expected_line_num + assert phys_loc['region']['startColumn'] == 8 + assert phys_loc['region']['endColumn'] == 20 + assert phys_loc['contextRegion']['startLine'] == expected_line_num + assert phys_loc['contextRegion']['snippet']['text'] \ + == 'PRINT "hello world!";\n' + assert note['message']['text'] == 'have you looked behind the couch?' diff --git a/gcc/testsuite/libdiagnostics.dg/test-error-with-note.c b/gcc/testsuite/libdiagnostics.dg/test-error-with-note.c new file mode 100644 index 000000000000..11a3b998c3e1 --- /dev/null +++ b/gcc/testsuite/libdiagnostics.dg/test-error-with-note.c @@ -0,0 +1,74 @@ +/* Example of emitting an error with an associated note. + + Intended output is similar to: + +PATH/test-error-with-note.c:18:8: error: can't find 'foo' + 6 | PRINT "hello world!"; + | ^~~~~~~~~~~~ +PATH/test-error-with-note.c:18:8: note: have you looked behind the couch? + + along with the equivalent in SARIF. */ + +#include "libdiagnostics.h" +#include "test-helpers.h" + +/* +_________111111111122 +123456789012345678901 +PRINT "hello world!"; +*/ +const int line_num = __LINE__ - 2; + +int +main () +{ + begin_test ("test-error-with-note.c.exe", + "test-error-with-note.c.sarif", + __FILE__, "c"); + + const diagnostic_physical_location *loc_start + = diagnostic_manager_new_location_from_file_line_column (diag_mgr, + main_file, + line_num, + 8); + const diagnostic_physical_location *loc_end + = diagnostic_manager_new_location_from_file_line_column (diag_mgr, + main_file, + line_num, + 19); + const diagnostic_physical_location *loc_range + = diagnostic_manager_new_location_from_range (diag_mgr, + loc_start, + loc_start, + loc_end); + + diagnostic_manager_begin_group (diag_mgr); + + diagnostic *err = diagnostic_begin (diag_mgr, + DIAGNOSTIC_LEVEL_ERROR); + diagnostic_set_location (err, loc_range); + diagnostic_finish (err, "can't find %qs", "foo"); + + diagnostic *note = diagnostic_begin (diag_mgr, DIAGNOSTIC_LEVEL_NOTE); + diagnostic_set_location (note, loc_range); + diagnostic_finish (note, "have you looked behind the couch?"); + + diagnostic_manager_end_group (diag_mgr); + + return end_test (); +}; + +/* Verify the output from the text sink. + { dg-regexp "\[^\n\r\]+test-error-with-note.c:18:8: error: can't find 'foo'" } + { dg-begin-multiline-output "" } + 18 | PRINT "hello world!"; + | ^~~~~~~~~~~~ + { dg-end-multiline-output "" } + { dg-regexp "\[^\n\r\]+test-error-with-note.c:18:8: note: have you looked behind the couch." } */ + +/* Verify that some JSON was written to a file with the expected name: + { dg-final { verify-sarif-file } } */ + +/* Use a Python script to verify various properties about the generated + .sarif file: + { dg-final { run-sarif-pytest test-error-with-note.c "test-error-with-note-c.py" } } */ diff --git a/gcc/testsuite/libdiagnostics.dg/test-error-with-note.cc b/gcc/testsuite/libdiagnostics.dg/test-error-with-note.cc new file mode 100644 index 000000000000..e211297c5520 --- /dev/null +++ b/gcc/testsuite/libdiagnostics.dg/test-error-with-note.cc @@ -0,0 +1,55 @@ +/* C++ example of emitting an error with an associated note. + + Intended output is similar to: + +PATH/test-error-with-note.c:17:8: error: can't find 'foo' + 17 | PRINT "hello world!"; + | ^~~~~~~~~~~~ +PATH/test-error-with-note.c:17:8: note: have you looked behind the couch? + + along with the equivalent in SARIF. */ + +#include "libdiagnostics++.h" + +/* +_________111111111122 +123456789012345678901 +PRINT "hello world!"; +*/ +const int line_num = __LINE__ - 2; + +int +main () +{ + libdiagnostics::manager mgr; + + auto file = mgr.new_file (__FILE__, "c"); + + mgr.add_text_sink (stderr, DIAGNOSTIC_COLORIZE_IF_TTY); + + auto loc_start = mgr.new_location_from_file_line_column (file, line_num, 8); + auto loc_end = mgr.new_location_from_file_line_column (file, line_num, 19); + auto loc_range = mgr.new_location_from_range (loc_start, + loc_start, + loc_end); + + libdiagnostics::group g (mgr); + + auto err (mgr.begin_diagnostic (DIAGNOSTIC_LEVEL_ERROR)); + err.set_location (loc_range); + err.finish ("can't find %qs", "foo"); + + auto note = mgr.begin_diagnostic (DIAGNOSTIC_LEVEL_NOTE); + note.set_location (loc_range); + note.finish ("have you looked behind the couch?"); + + return 0; +}; + +/* Verify the output from the text sink. + { dg-regexp "\[^\n\r\]+test-error-with-note.cc:17:8: error: can't find 'foo'" } + { dg-begin-multiline-output "" } + 17 | PRINT "hello world!"; + | ^~~~~~~~~~~~ + { dg-end-multiline-output "" } + { dg-regexp "\[^\n\r\]+test-error-with-note.cc:17:8: note: have you looked behind the couch\\\?" } */ diff --git a/gcc/testsuite/libdiagnostics.dg/test-error.c b/gcc/testsuite/libdiagnostics.dg/test-error.c new file mode 100644 index 000000000000..081dfb8e2dbb --- /dev/null +++ b/gcc/testsuite/libdiagnostics.dg/test-error.c @@ -0,0 +1,59 @@ +/* Example of emitting an error. + + Intended output is similar to: + +PATH/test-error-with-note.c:6: error: can't find 'foo' + 6 | PRINT "hello world!"; + | ^~~~~~~~~~~~ + + along with the equivalent in SARIF. */ + +#include "libdiagnostics.h" +#include "test-helpers.h" + +/* +_________111111111122 +123456789012345678901 +PRINT "hello world!"; +*/ +const int line_num = __LINE__ - 2; + +int +main () +{ + begin_test ("test-error.c.exe", + "test-error.c.sarif", + __FILE__, "c"); + + const diagnostic_physical_location *loc_start + = diagnostic_manager_new_location_from_file_line_column (diag_mgr, main_file, line_num, 8); + const diagnostic_physical_location *loc_end + = diagnostic_manager_new_location_from_file_line_column (diag_mgr, main_file, line_num, 19); + const diagnostic_physical_location *loc_range + = diagnostic_manager_new_location_from_range (diag_mgr, + loc_start, + loc_start, + loc_end); + + diagnostic *d = diagnostic_begin (diag_mgr, + DIAGNOSTIC_LEVEL_ERROR); + diagnostic_set_location (d, loc_range); + + diagnostic_finish (d, "can't find %qs", "foo"); + + return end_test (); +}; + +/* Verify the output from the text sink. + { dg-regexp "\[^\n\r\]+test-error.c:17:8: error: can't find 'foo'" } + { dg-begin-multiline-output "" } + 17 | PRINT "hello world!"; + | ^~~~~~~~~~~~ + { dg-end-multiline-output "" } */ + +/* Verify that some JSON was written to a file with the expected name: + { dg-final { verify-sarif-file } } */ + +/* Use a Python script to verify various properties about the generated + .sarif file: + { dg-final { run-sarif-pytest test-error.c "test-error-c.py" } } */ diff --git a/gcc/testsuite/libdiagnostics.dg/test-error.cc b/gcc/testsuite/libdiagnostics.dg/test-error.cc new file mode 100644 index 000000000000..6f919e473f4d --- /dev/null +++ b/gcc/testsuite/libdiagnostics.dg/test-error.cc @@ -0,0 +1,47 @@ +/* C++ example of emitting an error. + + Intended output is similar to: + +PATH/test-error.cc:16:8: error: can't find 'foo' + 16 | PRINT "hello world!"; + | ^~~~~~~~~~~~ + + along with the equivalent in SARIF. */ + +#include "libdiagnostics++.h" + +/* +_________111111111122 +123456789012345678901 +PRINT "hello world!"; +*/ +const int line_num = __LINE__ - 2; + +int +main () +{ + libdiagnostics::manager mgr; + + auto file = mgr.new_file (__FILE__, "c"); + + mgr.add_text_sink (stderr, DIAGNOSTIC_COLORIZE_IF_TTY); + + auto loc_start = mgr.new_location_from_file_line_column (file, line_num, 8); + auto loc_end = mgr.new_location_from_file_line_column (file, line_num, 19); + auto loc_range = mgr.new_location_from_range (loc_start, + loc_start, + loc_end); + + libdiagnostics::diagnostic d (mgr.begin_diagnostic (DIAGNOSTIC_LEVEL_ERROR)); + d.set_location (loc_range); + d.finish ("can't find %qs", "foo"); + + return 0; +}; + +/* Verify the output from the text sink. + { dg-regexp "\[^\n\r\]+test-error.cc:16:8: error: can't find 'foo'" } + { dg-begin-multiline-output "" } + 16 | PRINT "hello world!"; + | ^~~~~~~~~~~~ + { dg-end-multiline-output "" } */ diff --git a/gcc/testsuite/libdiagnostics.dg/test-fix-it-hint-c.py b/gcc/testsuite/libdiagnostics.dg/test-fix-it-hint-c.py new file mode 100644 index 000000000000..a70b04d22cd3 --- /dev/null +++ b/gcc/testsuite/libdiagnostics.dg/test-fix-it-hint-c.py @@ -0,0 +1,46 @@ +from sarif import * + +import pytest + +@pytest.fixture(scope='function', autouse=True) +def sarif(): + return sarif_from_env() + +def test_sarif_output_with_fixes(sarif): + schema = sarif['$schema'] + assert schema == 'https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json' + + version = sarif['version'] + assert version == '2.1.0' + + runs = sarif['runs'] + run = runs[0] + + tool = run['tool'] + assert tool['driver']['name'] == 'test-fix-it-hint.c.exe' + + results = run['results'] + assert len(results) == 1 + assert results[0]['ruleId'] == 'error' + assert results[0]['level'] == 'error' + assert results[0]['message']['text'] == "unknown field 'colour'; did you mean 'color'" + assert len(results[0]['locations']) == 1 + location = results[0]['locations'][0] + phys_loc = location['physicalLocation'] + assert phys_loc['artifactLocation']['uri'].endswith('test-fix-it-hint.c') + assert phys_loc['region']['startLine'] == 19 + assert phys_loc['region']['startColumn'] == 13 + assert phys_loc['region']['endColumn'] == 19 + assert phys_loc['contextRegion']['startLine'] == 19 + assert phys_loc['contextRegion']['snippet']['text'] \ + == ' return p->colour;\n' + + assert len(results[0]['fixes']) == 1 + fix = results[0]['fixes'][0] + assert len(fix['artifactChanges']) == 1 + change = fix['artifactChanges'][0] + assert change['artifactLocation']['uri'].endswith('test-fix-it-hint.c') + assert len(change['replacements']) == 1 + replacement = change['replacements'][0] + assert replacement['deletedRegion'] == phys_loc['region'] + assert replacement['insertedContent']['text'] == 'color' diff --git a/gcc/testsuite/libdiagnostics.dg/test-fix-it-hint.c b/gcc/testsuite/libdiagnostics.dg/test-fix-it-hint.c new file mode 100644 index 000000000000..723969ffdd04 --- /dev/null +++ b/gcc/testsuite/libdiagnostics.dg/test-fix-it-hint.c @@ -0,0 +1,81 @@ +/* Example of a fix-it hint, including patch generation. + + Intended output is similar to: + +PATH/test-fix-it-hint.c:19:13: error: unknown field 'colour'; did you mean 'color' + 19 | return p->colour; + | ^~~~~~ + | color + + along with the equivalent in SARIF, and a generated patch (on stderr) to + make the change. */ + +#include "libdiagnostics.h" +#include "test-helpers.h" + +/* +_________11111111112 +12345678901234567890 + return p->colour; +*/ +const int line_num = __LINE__ - 2; + +int +main () +{ + begin_test ("test-fix-it-hint.c.exe", + "test-fix-it-hint.c.sarif", + __FILE__, "c"); + + const diagnostic_physical_location *loc_token + = make_range (diag_mgr, main_file, line_num, 13, 18); + + diagnostic *d = diagnostic_begin (diag_mgr, + DIAGNOSTIC_LEVEL_ERROR); + diagnostic_set_location (d, loc_token); + + diagnostic_add_fix_it_hint_replace (d, loc_token, "color"); + + diagnostic_finish (d, "unknown field %qs; did you mean %qs", "colour", "color"); + + diagnostic_manager_write_patch (diag_mgr, stderr); + + return end_test (); +} + +/* Verify the output from the text sink. + { dg-regexp "\[^\n\r\]+test-fix-it-hint.c:19:13: error: unknown field 'colour'; did you mean 'color'" } + { dg-begin-multiline-output "" } + 19 | return p->colour; + | ^~~~~~ + | color + { dg-end-multiline-output "" } */ + +/* Verify the output from diagnostic_manager_write_patch. + We expect the patch to begin with a header, containing this + source filename, via an absolute path. + Given the path, we can only capture it via regexps. */ +/* { dg-regexp "\\-\\-\\- .*" } */ +/* { dg-regexp "\\+\\+\\+ .*" } */ +/* Use #if 0/#endif rather than comments, to allow the text to contain + a comment. */ +#if 0 +{ dg-begin-multiline-output "" } +@@ -16,7 +16,7 @@ + /* + _________11111111112 + 12345678901234567890 +- return p->colour; ++ return p->color; + */ + const int line_num = __LINE__ - 2; + +{ dg-end-multiline-output "" } +#endif + +/* Verify that some JSON was written to a file with the expected name: + { dg-final { verify-sarif-file } } */ + +/* Use a Python script to verify various properties about the generated + .sarif file: + { dg-final { run-sarif-pytest test-fix-it-hint.c "test-fix-it-hint-c.py" } } */ diff --git a/gcc/testsuite/libdiagnostics.dg/test-fix-it-hint.cc b/gcc/testsuite/libdiagnostics.dg/test-fix-it-hint.cc new file mode 100644 index 000000000000..92c7f07117bd --- /dev/null +++ b/gcc/testsuite/libdiagnostics.dg/test-fix-it-hint.cc @@ -0,0 +1,74 @@ +/* C++ example of a fix-it hint, including patch generation. + + Intended output is similar to: + +PATH/test-fix-it-hint.cc:19:13: error: unknown field 'colour'; did you mean 'color' + 19 | return p->colour; + | ^~~~~~ + | color + + along with the equivalent in SARIF, and a generated patch (on stderr) to + make the change. */ + +#include "libdiagnostics++.h" +#include "test-helpers++.h" + +/* +_________11111111112 +12345678901234567890 + return p->colour; +*/ +const int line_num = __LINE__ - 2; + +int +main () +{ + libdiagnostics::manager mgr; + + auto file = mgr.new_file (__FILE__, "c"); + + mgr.add_text_sink (stderr, DIAGNOSTIC_COLORIZE_IF_TTY); + + auto loc_token = make_range (mgr, file, line_num, 13, 18); + + auto d = mgr.begin_diagnostic (DIAGNOSTIC_LEVEL_ERROR); + d.set_location (loc_token); + + d.add_fix_it_hint_replace (loc_token, "color"); + + d.finish ("unknown field %qs; did you mean %qs", "colour", "color"); + + mgr.write_patch (stderr); + + return 0; +} + +/* Verify the output from the text sink. + { dg-regexp "\[^\n\r\]+test-fix-it-hint.cc:19:13: error: unknown field 'colour'; did you mean 'color'" } + { dg-begin-multiline-output "" } + 19 | return p->colour; + | ^~~~~~ + | color + { dg-end-multiline-output "" } */ + +/* Verify the output from diagnostic_manager_write_patch. + We expect the patch to begin with a header, containing this + source filename, via an absolute path. + Given the path, we can only capture it via regexps. */ +/* { dg-regexp "\\-\\-\\- .*" } */ +/* { dg-regexp "\\+\\+\\+ .*" } */ +/* Use #if 0/#endif rather than comments, to allow the text to contain + a comment. */ +#if 0 +{ dg-begin-multiline-output "" } +@@ -16,7 +16,7 @@ + /* + _________11111111112 + 12345678901234567890 +- return p->colour; ++ return p->color; + */ + const int line_num = __LINE__ - 2; + +{ dg-end-multiline-output "" } +#endif diff --git a/gcc/testsuite/libdiagnostics.dg/test-helpers++.h b/gcc/testsuite/libdiagnostics.dg/test-helpers++.h new file mode 100644 index 000000000000..c8ff2def1ffa --- /dev/null +++ b/gcc/testsuite/libdiagnostics.dg/test-helpers++.h @@ -0,0 +1,28 @@ +/* Common utility code shared between test cases. */ + +#ifndef TEST_HELPERSPP_H +#define TEST_HELPERSPP_H + +namespace libdiagnostics { + +inline physical_location +make_range (manager &mgr, + file f, + line_num_t line_num, + column_num_t start_column, + column_num_t end_column) +{ + auto loc_start = mgr.new_location_from_file_line_column (f, + line_num, + start_column); + auto loc_end = mgr.new_location_from_file_line_column (f, + line_num, + end_column); + return mgr.new_location_from_range (loc_start, + loc_start, + loc_end); +} + +} // namespace libdiagnostics + +#endif /* #ifndef TEST_HELPERSPP_H */ diff --git a/gcc/testsuite/libdiagnostics.dg/test-helpers.h b/gcc/testsuite/libdiagnostics.dg/test-helpers.h new file mode 100644 index 000000000000..c06c0637b940 --- /dev/null +++ b/gcc/testsuite/libdiagnostics.dg/test-helpers.h @@ -0,0 +1,72 @@ +/* Common utility code shared between test cases. */ + +#ifndef TEST_HELPERS_H +#define TEST_HELPERS_H + +const diagnostic_physical_location * +make_range (diagnostic_manager *diag_mgr, + const diagnostic_file *file, + diagnostic_line_num_t line_num, + diagnostic_column_num_t start_column, + diagnostic_column_num_t end_column) +{ + const diagnostic_physical_location *loc_start + = diagnostic_manager_new_location_from_file_line_column (diag_mgr, + file, + line_num, + start_column); + const diagnostic_physical_location *loc_end + = diagnostic_manager_new_location_from_file_line_column (diag_mgr, + file, + line_num, + end_column); + return diagnostic_manager_new_location_from_range (diag_mgr, + loc_start, + loc_start, + loc_end); +} + +/* A begin_test/end_test pair to consolidate the code shared by tests: + create a diagnostic_manager, a main file, a text sink, and a SARIF sink, + and clean these up after emitting zero or more diagnostics. */ + +static diagnostic_manager *diag_mgr; +static const diagnostic_file *main_file; +static FILE *sarif_outfile; + +static void +begin_test (const char *tool_name, + const char *sarif_output_name, + const char *main_file_name, + const char *source_language) +{ + diag_mgr = diagnostic_manager_new (); + + /* We need to set this for generated .sarif files to validat + against the schema. */ + diagnostic_manager_set_tool_name (diag_mgr, tool_name); + + main_file = diagnostic_manager_new_file (diag_mgr, + main_file_name, + source_language); + + diagnostic_manager_add_text_sink (diag_mgr, stderr, + DIAGNOSTIC_COLORIZE_IF_TTY); + sarif_outfile = fopen (sarif_output_name, "w"); + if (sarif_outfile) + diagnostic_manager_add_sarif_sink (diag_mgr, + sarif_outfile, + main_file, + DIAGNOSTIC_SARIF_VERSION_2_1_0); +} + +static int +end_test (void) +{ + diagnostic_manager_release (diag_mgr); + if (sarif_outfile) + fclose (sarif_outfile); + return 0; +} + +#endif /* #ifndef TEST_HELPERS_H */ diff --git a/gcc/testsuite/libdiagnostics.dg/test-labelled-ranges.c b/gcc/testsuite/libdiagnostics.dg/test-labelled-ranges.c new file mode 100644 index 000000000000..41bfbdc62feb --- /dev/null +++ b/gcc/testsuite/libdiagnostics.dg/test-labelled-ranges.c @@ -0,0 +1,69 @@ +/* Example of multiple locations, with labelling of ranges. + + Intended output is similar to: + +PATH/test-labelled-ranges.c:9:6: error: mismatching types: 'int' and 'const char *' + 19 | 42 + "foo" + | ~~ ^ ~~~~~ + | | | + | int const char * + + along with the equivalent in SARIF. */ + +#include "libdiagnostics.h" +#include "test-helpers.h" + +/* +_________11111111112 +12345678901234567890 + 42 + "foo" +*/ +const int line_num = __LINE__ - 2; + +int +main () +{ + begin_test ("test-labelled-ranges.c.exe", + "test-labelled-ranges.c.sarif", + __FILE__, "c"); + + const diagnostic_physical_location *loc_operator + = diagnostic_manager_new_location_from_file_line_column (diag_mgr, + main_file, + line_num, + 6); + + diagnostic *d = diagnostic_begin (diag_mgr, + DIAGNOSTIC_LEVEL_ERROR); + diagnostic_set_location (d, loc_operator); + diagnostic_add_location_with_label (d, + make_range (diag_mgr, + main_file, + line_num, 3, 4), + "int"); + diagnostic_add_location_with_label (d, + make_range (diag_mgr, + main_file, + line_num, 8, 12), + "const char *"); + + diagnostic_finish (d, "mismatching types: %qs and %qs", "int", "const char *"); + + return end_test (); +} + +/* Check the output from the text sink. */ +/* { dg-regexp "\[^\n\r\]+test-labelled-ranges.c:19:6: error: mismatching types: 'int' and 'const char \\*'" } */ +/* { dg-begin-multiline-output "" } + 19 | 42 + "foo" + | ~~ ^ ~~~~~ + | | | + | int const char * + { dg-end-multiline-output "" } */ + +/* Verify that some JSON was written to a file with the expected name: + { dg-final { verify-sarif-file } } */ + +/* Use a Python script to verify various properties about the generated + .sarif file: + { dg-final { run-sarif-pytest test-labelled-ranges.c "test-labelled-ranges.py" } } */ diff --git a/gcc/testsuite/libdiagnostics.dg/test-labelled-ranges.cc b/gcc/testsuite/libdiagnostics.dg/test-labelled-ranges.cc new file mode 100644 index 000000000000..1c1c050e3042 --- /dev/null +++ b/gcc/testsuite/libdiagnostics.dg/test-labelled-ranges.cc @@ -0,0 +1,64 @@ +/* C++ example of multiple locations, with labelling of ranges. + + Intended output is similar to: + +PATH/test-labelled-ranges.cc:19:6: error: mismatching types: 'int' and 'const char *' + 19 | 42 + "foo" + | ~~ ^ ~~~~~ + | | | + | int const char * + + along with the equivalent in SARIF. */ + +#include "libdiagnostics++.h" +#include "test-helpers++.h" + +/* +_________11111111112 +12345678901234567890 + 42 + "foo" +*/ +const int line_num = __LINE__ - 2; + +int +main () +{ + FILE *sarif_outfile; + libdiagnostics::manager mgr; + mgr.set_tool_name ("test-labelled-ranges.cc.exe"); + + libdiagnostics::file file = mgr.new_file (__FILE__, "c"); + + mgr.add_text_sink (stderr, DIAGNOSTIC_COLORIZE_IF_TTY); + sarif_outfile = fopen ("test-labelled-ranges.cc.sarif", "w"); + if (sarif_outfile) + mgr.add_sarif_sink (sarif_outfile, file, DIAGNOSTIC_SARIF_VERSION_2_1_0); + + auto loc_operator = mgr.new_location_from_file_line_column (file, line_num, 6); + + auto d (mgr.begin_diagnostic (DIAGNOSTIC_LEVEL_ERROR)); + d.set_location (loc_operator); + d.add_location_with_label (make_range (mgr, file, line_num, 3, 4), + "int"); + d.add_location_with_label (make_range (mgr, file, line_num, 8, 12), + "const char *"); + d.finish ("mismatching types: %qs and %qs", "int", "const char *"); + + return 0; +} + +/* Check the output from the text sink. */ +/* { dg-regexp "\[^\n\r\]+test-labelled-ranges.cc:19:6: error: mismatching types: 'int' and 'const char \\*'" } */ +/* { dg-begin-multiline-output "" } + 19 | 42 + "foo" + | ~~ ^ ~~~~~ + | | | + | int const char * + { dg-end-multiline-output "" } */ + +/* Verify that some JSON was written to a file with the expected name: + { dg-final { verify-sarif-file } } */ + +/* Use a Python script to verify various properties about the generated + .sarif file: + { dg-final { run-sarif-pytest test-labelled-ranges.cc "test-labelled-ranges.py" } } */ diff --git a/gcc/testsuite/libdiagnostics.dg/test-labelled-ranges.py b/gcc/testsuite/libdiagnostics.dg/test-labelled-ranges.py new file mode 100644 index 000000000000..f2f13867ce9f --- /dev/null +++ b/gcc/testsuite/libdiagnostics.dg/test-labelled-ranges.py @@ -0,0 +1,48 @@ +# Verify the SARIF output of test-labelled-ranges.{c,cc} + +from sarif import * + +import pytest + +@pytest.fixture(scope='function', autouse=True) +def sarif(): + return sarif_from_env() + +def test_sarif_output(sarif): + schema = sarif['$schema'] + assert schema == 'https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json' + + version = sarif['version'] + assert version == '2.1.0' + + runs = sarif['runs'] + run = runs[0] + + results = run['results'] + assert len(results) == 1 + assert results[0]['ruleId'] == 'error' + assert results[0]['level'] == 'error' + assert results[0]['message']['text'] \ + == "mismatching types: 'int' and 'const char *'" + assert len(results[0]['locations']) == 1 + location = results[0]['locations'][0] + phys_loc = location['physicalLocation'] + assert phys_loc['region']['startLine'] == 19 + assert phys_loc['region']['startColumn'] == 6 + assert phys_loc['region']['endColumn'] == 7 + assert phys_loc['contextRegion']['startLine'] == 19 + assert phys_loc['contextRegion']['snippet']['text'] \ + == ' 42 + "foo"\n' + + annotations = location['annotations'] + assert len(annotations) == 2 + + assert annotations[0]['startLine'] == 19 + assert annotations[0]['startColumn'] == 3 + assert annotations[0]['endColumn'] == 5 + assert annotations[0]['message']['text'] == 'int' + + assert annotations[1]['startLine'] == 19 + assert annotations[1]['startColumn'] == 8 + assert annotations[1]['endColumn'] == 13 + assert annotations[1]['message']['text'] == 'const char *' diff --git a/gcc/testsuite/libdiagnostics.dg/test-logical-location-c.py b/gcc/testsuite/libdiagnostics.dg/test-logical-location-c.py new file mode 100644 index 000000000000..5344b05578ca --- /dev/null +++ b/gcc/testsuite/libdiagnostics.dg/test-logical-location-c.py @@ -0,0 +1,37 @@ +from sarif import * + +import pytest + +@pytest.fixture(scope='function', autouse=True) +def sarif(): + return sarif_from_env() + +def test_sarif_output_with_logical_location(sarif): + schema = sarif['$schema'] + assert schema == 'https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json' + + version = sarif['version'] + assert version == '2.1.0' + + runs = sarif['runs'] + run = runs[0] + + tool = run['tool'] + assert tool['driver']['name'] == 'test-logical-location.c.exe' + + results = run['results'] + assert len(results) == 1 + + result = results[0] + assert result['ruleId'] == 'error' + assert result['level'] == 'error' + assert result['message']['text'] == "can't find 'foo'" + assert len(result['locations']) == 1 + location = result['locations'][0] + + assert len(location['logicalLocations']) == 1 + logical_loc = location['logicalLocations'][0] + assert logical_loc['name'] == 'test_short_name' + assert logical_loc['fullyQualifiedName'] == 'test_qualified_name' + assert logical_loc['decoratedName'] == 'test_decorated_name' + assert logical_loc['kind'] == 'function' diff --git a/gcc/testsuite/libdiagnostics.dg/test-logical-location.c b/gcc/testsuite/libdiagnostics.dg/test-logical-location.c new file mode 100644 index 000000000000..815296ff8be9 --- /dev/null +++ b/gcc/testsuite/libdiagnostics.dg/test-logical-location.c @@ -0,0 +1,79 @@ +/* Example of using a logical location. + + Intended output is similar to: + +In function 'test_qualified_name': +PATH/test-error-with-note.c:18:8: error: can't find 'foo' + 18 | PRINT "hello world!"; + | ^~~~~~~~~~~~ + + along with the equivalent in SARIF. */ + +#include "libdiagnostics.h" +#include "test-helpers.h" + +/* Placeholder source: +_________111111111122 +123456789012345678901 +PRINT "hello world!"; +*/ +const int line_num = __LINE__ - 2; + +int +main () +{ + begin_test ("test-logical-location.c.exe", + "test-logical-location.c.sarif", + __FILE__, "c"); + + const diagnostic_physical_location *loc_start + = diagnostic_manager_new_location_from_file_line_column (diag_mgr, + main_file, + line_num, + 8); + const diagnostic_physical_location *loc_end + = diagnostic_manager_new_location_from_file_line_column (diag_mgr, + main_file, + line_num, + 19); + const diagnostic_physical_location *loc_range + = diagnostic_manager_new_location_from_range (diag_mgr, + loc_start, + loc_start, + loc_end); + + diagnostic *d = diagnostic_begin (diag_mgr, + DIAGNOSTIC_LEVEL_ERROR); + diagnostic_set_location (d, loc_range); + + const diagnostic_logical_location *logical_loc + = diagnostic_manager_new_logical_location (diag_mgr, + DIAGNOSTIC_LOGICAL_LOCATION_KIND_FUNCTION, + NULL, /* parent */ + "test_short_name", + "test_qualified_name", + "test_decorated_name"); + + diagnostic_set_logical_location (d, logical_loc); + + diagnostic_finish (d, "can't find %qs", "foo"); + + return end_test (); +} + +/* Check the output from the text sink. */ +/* { dg-begin-multiline-output "" } +In function 'test_qualified_name': + { dg-end-multiline-output "" } */ +/* { dg-regexp "\[^\n\r\]+test-logical-location.c:18:8: error: can't find 'foo'" } */ +/* { dg-begin-multiline-output "" } + 18 | PRINT "hello world!"; + | ^~~~~~~~~~~~ + { dg-end-multiline-output "" } */ + +/* Verify that some JSON was written to a file with the expected name: + { dg-final { verify-sarif-file } } */ + +/* Use a Python script to verify various properties about the generated + .sarif file: + { dg-final { run-sarif-pytest test-logical-location.c "test-logical-location-c.py" } } */ diff --git a/gcc/testsuite/libdiagnostics.dg/test-metadata-c.py b/gcc/testsuite/libdiagnostics.dg/test-metadata-c.py new file mode 100644 index 000000000000..139f940ea453 --- /dev/null +++ b/gcc/testsuite/libdiagnostics.dg/test-metadata-c.py @@ -0,0 +1,45 @@ +from sarif import * + +import pytest + +@pytest.fixture(scope='function', autouse=True) +def sarif(): + return sarif_from_env() + +def test_sarif_output_metadata(sarif): + schema = sarif['$schema'] + assert schema == 'https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json' + + version = sarif['version'] + assert version == '2.1.0' + + runs = sarif['runs'] + run = runs[0] + + tool = run['tool'] + assert tool['driver']['name'] == 'FooChecker' + assert tool['driver']['fullName'] == 'FooChecker 0.1 (en_US)' + assert tool['driver']['version'] == '0.1' + assert tool['driver']['informationUri'] == 'https://www.example.com/0.1/' + + taxonomies = run["taxonomies"] + assert len(taxonomies) == 1 + + cwe = taxonomies[0] + assert cwe['name'] == 'CWE' + assert cwe['version'] == '4.7' + assert cwe['organization'] == 'MITRE' + assert cwe['shortDescription']['text'] \ + == 'The MITRE Common Weakness Enumeration' + assert len(cwe['taxa']) == 1 + assert cwe['taxa'][0]['id'] == '242' + assert cwe['taxa'][0]['helpUri'] \ + == 'https://cwe.mitre.org/data/definitions/242.html' + + results = run['results'] + assert len(results) == 1 + + result = results[0] + assert result['ruleId'] == 'warning' + assert result['level'] == 'warning' + assert result['message']['text'] == "never use 'gets'" diff --git a/gcc/testsuite/libdiagnostics.dg/test-metadata.c b/gcc/testsuite/libdiagnostics.dg/test-metadata.c new file mode 100644 index 000000000000..7881c9eca3e8 --- /dev/null +++ b/gcc/testsuite/libdiagnostics.dg/test-metadata.c @@ -0,0 +1,61 @@ +/* Example of setting a CWE and adding extra metadata. + + Intended output is similar to: + +PATH/test-metadata.c:21:3: warning: never use 'gets' [CWE-242] [STR34-C] + 21 | gets (buf); + | ^~~~~~~~~~ + + where the metadata tags are linkified in a sufficiently capable terminal, + along with the equivalent in SARIF. */ + +#include "libdiagnostics.h" +#include "test-helpers.h" + +/* Placeholder source: +_________11111111112 +12345678901234567890 +void test_cwe (void) +{ + char buf[1024]; + gets (buf); +} +*/ +const int line_num = __LINE__ - 3; + +int +main () +{ + begin_test ("FooChecker", + "test-metadata.c.sarif", + __FILE__, "c"); + + diagnostic_manager_set_full_name (diag_mgr, "FooChecker 0.1 (en_US)"); + diagnostic_manager_set_version_string (diag_mgr, "0.1"); + diagnostic_manager_set_version_url (diag_mgr, "https://www.example.com/0.1/"); + + const diagnostic_physical_location *loc_token + = make_range (diag_mgr, main_file, line_num, 3, 12); + diagnostic *d = diagnostic_begin (diag_mgr, + DIAGNOSTIC_LEVEL_WARNING); + diagnostic_set_location (d, loc_token); + diagnostic_set_cwe (d, 242); /* CWE-242: Use of Inherently Dangerous Function. */ + diagnostic_add_rule (d, "STR34-C", "https://example.com/"); + + diagnostic_finish (d, "never use %qs", "gets"); + + return end_test (); +} + +/* { dg-regexp "\[^\n\r\]+test-metadata.c:21:3: warning: never use 'gets' \\\[CWE-242\\\] \\\[STR34-C\\\]" } */ +/* { dg-begin-multiline-output "" } + 21 | gets (buf); + | ^~~~~~~~~~ + { dg-end-multiline-output "" } */ + +/* Verify that some JSON was written to a file with the expected name: + { dg-final { verify-sarif-file } } */ + +/* Use a Python script to verify various properties about the generated + .sarif file: + { dg-final { run-sarif-pytest test-metadata.c "test-metadata-c.py" } } */ diff --git a/gcc/testsuite/libdiagnostics.dg/test-multiple-lines-c.py b/gcc/testsuite/libdiagnostics.dg/test-multiple-lines-c.py new file mode 100644 index 000000000000..f54984195be7 --- /dev/null +++ b/gcc/testsuite/libdiagnostics.dg/test-multiple-lines-c.py @@ -0,0 +1,83 @@ +from sarif import * + +import pytest + +@pytest.fixture(scope='function', autouse=True) +def sarif(): + return sarif_from_env() + +def test_sarif_output(sarif): + schema = sarif['$schema'] + assert schema == 'https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json' + + version = sarif['version'] + assert version == '2.1.0' + + runs = sarif['runs'] + run = runs[0] + + tool = run['tool'] + assert tool['driver']['name'] == 'test-multiple-lines.c.exe' + + results = run['results'] + assert len(results) == 1 + result = results[0] + assert result['ruleId'] == 'warning' + assert result['level'] == 'warning' + assert result['message']['text'] == "missing comma" + assert len(result['locations']) == 1 + + # The primary location should be that of the missing comma + location = result['locations'][0] + phys_loc = location['physicalLocation'] + assert phys_loc['artifactLocation']['uri'].endswith('test-multiple-lines.c') + assert phys_loc['region']['startLine'] == 23 + assert phys_loc['region']['startColumn'] == 29 + assert phys_loc['region']['endColumn'] == 30 + assert phys_loc['contextRegion']['startLine'] == 23 + assert phys_loc['contextRegion']['snippet']['text'] \ + == ' "bar"\n' + + assert len(location['relationships']) == 3 + location['relationships'][0]['target'] == 0 + location['relationships'][0]['kinds'] == ['relevant'] + location['relationships'][1]['target'] == 1 + location['relationships'][1]['kinds'] == ['relevant'] + location['relationships'][2]['target'] == 2 + location['relationships'][2]['kinds'] == ['relevant'] + + # We should be capturing the secondary locations in relatedLocations + assert len(result['relatedLocations']) == 3 + + rel_loc_0 = result['relatedLocations'][0] + assert get_location_artifact_uri(rel_loc_0) \ + .endswith('test-multiple-lines.c') + assert get_location_snippet_text(rel_loc_0) \ + == 'const char *strs[3] = {"foo",\n' + assert get_location_physical_region(rel_loc_0)['startLine'] == 22 + assert get_location_physical_region(rel_loc_0)['startColumn'] == 24 + assert get_location_physical_region(rel_loc_0)['endColumn'] == 29 + assert rel_loc_0['id'] == 0 + assert 'relationships' not in rel_loc_0 + + rel_loc_1 = result['relatedLocations'][1] + assert get_location_artifact_uri(rel_loc_1) \ + .endswith('test-multiple-lines.c') + assert get_location_snippet_text(rel_loc_1) \ + == ' "bar"\n' + assert get_location_physical_region(rel_loc_1)['startLine'] == 23 + assert get_location_physical_region(rel_loc_1)['startColumn'] == 24 + assert get_location_physical_region(rel_loc_1)['endColumn'] == 29 + assert rel_loc_1['id'] == 1 + assert 'relationships' not in rel_loc_1 + + rel_loc_2 = result['relatedLocations'][2] + assert get_location_artifact_uri(rel_loc_2) \ + .endswith('test-multiple-lines.c') + assert get_location_snippet_text(rel_loc_2) \ + == ' "baz"};\n' + assert get_location_physical_region(rel_loc_2)['startLine'] == 24 + assert get_location_physical_region(rel_loc_2)['startColumn'] == 24 + assert get_location_physical_region(rel_loc_2)['endColumn'] == 29 + assert rel_loc_2['id'] == 2 + assert 'relationships' not in rel_loc_2 diff --git a/gcc/testsuite/libdiagnostics.dg/test-multiple-lines.c b/gcc/testsuite/libdiagnostics.dg/test-multiple-lines.c new file mode 100644 index 000000000000..765c42956ac0 --- /dev/null +++ b/gcc/testsuite/libdiagnostics.dg/test-multiple-lines.c @@ -0,0 +1,76 @@ +/* Example of a warning with multiple locations in various source lines, + with an insertion fix-it hint. + + Intended output is similar to: + +/PATH/test-multiple-lines.c:23:29: warning: missing comma + 22 | const char *strs[3] = {"foo", + | ~~~~~ + 23 | "bar" + | ~~~~~^ + 24 | "baz"}; + | ~~~~~ + + along with the equivalent in SARIF. */ + +#include "libdiagnostics.h" +#include "test-helpers.h" + +/* Placeholder source (missing comma after "bar"): +_________11111111112222222222 +12345678901234567890123456789 +const char *strs[3] = {"foo", + "bar" + "baz"}; +*/ +const int foo_line_num = __LINE__ - 4; + +int +main () +{ + begin_test ("test-multiple-lines.c.exe", + "test-multiple-lines.c.sarif", + __FILE__, "c"); + + const diagnostic_physical_location *loc_comma + = diagnostic_manager_new_location_from_file_line_column (diag_mgr, + main_file, + foo_line_num + 1, + 29); + const diagnostic_physical_location *loc_foo + = make_range (diag_mgr, main_file, foo_line_num, 24, 28); + const diagnostic_physical_location *loc_bar + = make_range (diag_mgr, main_file, foo_line_num + 1, 24, 28); + const diagnostic_physical_location *loc_baz + = make_range (diag_mgr, main_file, foo_line_num + 2, 24, 28); + + diagnostic *d = diagnostic_begin (diag_mgr, + DIAGNOSTIC_LEVEL_WARNING); + diagnostic_set_location (d, loc_comma); + diagnostic_add_location (d, loc_foo); + diagnostic_add_location (d, loc_bar); + diagnostic_add_location (d, loc_baz); + + diagnostic_add_fix_it_hint_insert_after (d, loc_bar, ","); + + diagnostic_finish (d, "missing comma"); + + return end_test (); +}; + +/* { dg-regexp "\[^\n\r\]+test-multiple-lines.c:23:29: warning: missing comma" } */ +/* { dg-begin-multiline-output "" } + 22 | const char *strs[3] = {"foo", + | ~~~~~ + 23 | "bar" + | ~~~~~^ + 24 | "baz"}; + | ~~~~~ + { dg-end-multiline-output "" } */ + +/* Verify that some JSON was written to a file with the expected name: + { dg-final { verify-sarif-file } } */ + +/* Use a Python script to verify various properties about the generated + .sarif file: + { dg-final { run-sarif-pytest test-multiple-lines.c "test-multiple-lines-c.py" } } */ diff --git a/gcc/testsuite/libdiagnostics.dg/test-no-column-c.py b/gcc/testsuite/libdiagnostics.dg/test-no-column-c.py new file mode 100644 index 000000000000..e005359e3ec0 --- /dev/null +++ b/gcc/testsuite/libdiagnostics.dg/test-no-column-c.py @@ -0,0 +1,35 @@ +from sarif import * + +import pytest + +@pytest.fixture(scope='function', autouse=True) +def sarif(): + return sarif_from_env() + +expected_line_num = 16 + +def test_sarif_output(sarif): + schema = sarif['$schema'] + assert schema == 'https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json' + + version = sarif['version'] + assert version == '2.1.0' + + runs = sarif['runs'] + run = runs[0] + + tool = run['tool'] + assert tool['driver']['name'] == 'test-no-column.c.exe' + + results = run['results'] + assert len(results) == 1 + location = results[0]['locations'][0] + phys_loc = location['physicalLocation'] + assert phys_loc['artifactLocation']['uri'].endswith('test-no-column.c') + assert phys_loc['region']['startLine'] == expected_line_num + # We should have no column properties: + assert 'startColumn' not in phys_loc['region'] + assert 'endColumn' not in phys_loc['region'] + assert phys_loc['contextRegion']['startLine'] == expected_line_num + assert phys_loc['contextRegion']['snippet']['text'] \ + == 'PRINT "hello world!";\n' diff --git a/gcc/testsuite/libdiagnostics.dg/test-no-column.c b/gcc/testsuite/libdiagnostics.dg/test-no-column.c new file mode 100644 index 000000000000..798e0f7bc8a2 --- /dev/null +++ b/gcc/testsuite/libdiagnostics.dg/test-no-column.c @@ -0,0 +1,52 @@ +/* Example of emitting an error without a column number. + + Intended output is similar to: + +PATH/test-error-with-note.c:6: error: can't find 'foo' + 6 | PRINT "hello world!"; + + along with the equivalent in SARIF. */ + +#include "libdiagnostics.h" +#include "test-helpers.h" + +/* +_________111111111122 +123456789012345678901 +PRINT "hello world!"; +*/ +const int line_num = __LINE__ - 2; + +int +main () +{ + begin_test ("test-no-column.c.exe", + "test-no-column.c.sarif", + __FILE__, "c"); + + const diagnostic_physical_location *loc + = diagnostic_manager_new_location_from_file_and_line (diag_mgr, + main_file, + line_num); + + diagnostic *d = diagnostic_begin (diag_mgr, + DIAGNOSTIC_LEVEL_ERROR); + diagnostic_set_location (d, loc); + + diagnostic_finish (d, "can't find %qs", "foo"); + + return end_test (); +} + +/* Verify the output from the text sink. + { dg-regexp "\[^\n\r\]+test-no-column.c:16: error: can't find 'foo'" } + { dg-begin-multiline-output "" } + 16 | PRINT "hello world!"; + { dg-end-multiline-output "" } */ + +/* Verify that some JSON was written to a file with the expected name: + { dg-final { verify-sarif-file } } */ + +/* Use a Python script to verify various properties about the generated + .sarif file: + { dg-final { run-sarif-pytest test-no-column.c "test-no-column-c.py" } } */ diff --git a/gcc/testsuite/libdiagnostics.dg/test-no-diagnostics-c.py b/gcc/testsuite/libdiagnostics.dg/test-no-diagnostics-c.py new file mode 100644 index 000000000000..b0edfdf4b0cd --- /dev/null +++ b/gcc/testsuite/libdiagnostics.dg/test-no-diagnostics-c.py @@ -0,0 +1,42 @@ +from sarif import * + +import pytest + +@pytest.fixture(scope='function', autouse=True) +def sarif(): + return sarif_from_env() + +expected_file_name = 'test-no-diagnostics.c' + +def test_sarif_output(sarif): + schema = sarif['$schema'] + assert schema == 'https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json' + + version = sarif['version'] + assert version == '2.1.0' + + runs = sarif['runs'] + run = runs[0] + + tool = run['tool'] + assert tool['driver']['name'] == expected_file_name + '.exe' + + invocations = run['invocations'] + assert len(invocations) == 1 + assert 'workingDirectory' in invocations[0] + assert 'startTimeUtc' in invocations[0] + assert invocations[0]['executionSuccessful'] == True + assert invocations[0]['toolExecutionNotifications'] == [] + assert 'endTimeUtc' in invocations[0] + + artifacts = run['artifacts'] + assert len(artifacts) == 1 + assert artifacts[0]['location']['uri'].endswith(expected_file_name) + assert artifacts[0]['sourceLanguage'] == 'c' + # We don't bother capturing the contents if there are + # no diagnostics to display + assert 'contents' not in artifacts[0] + assert artifacts[0]['roles'] == ["analysisTarget"] + + results = run['results'] + assert len(results) == 0 diff --git a/gcc/testsuite/libdiagnostics.dg/test-no-diagnostics.c b/gcc/testsuite/libdiagnostics.dg/test-no-diagnostics.c new file mode 100644 index 000000000000..78e186ab95ad --- /dev/null +++ b/gcc/testsuite/libdiagnostics.dg/test-no-diagnostics.c @@ -0,0 +1,25 @@ +/* Test of the "no diagnostics are emitted" case. */ + +#include "libdiagnostics.h" +#include "test-helpers.h" + +int +main () +{ + begin_test ("test-no-diagnostics.c.exe", + "test-no-diagnostics.c.sarif", + __FILE__, "c"); + + /* No-op. */ + + return end_test (); +}; + +/* There should be no output from the text sink. */ + +/* Verify that some JSON was written to a file with the expected name: + { dg-final { verify-sarif-file } } */ + +/* Use a Python script to verify various properties about the generated + .sarif file: + { dg-final { run-sarif-pytest test-no-diagnostics.c "test-no-diagnostics-c.py" } } */ diff --git a/gcc/testsuite/libdiagnostics.dg/test-note-with-fix-it-hint-c.py b/gcc/testsuite/libdiagnostics.dg/test-note-with-fix-it-hint-c.py new file mode 100644 index 000000000000..256c429420a1 --- /dev/null +++ b/gcc/testsuite/libdiagnostics.dg/test-note-with-fix-it-hint-c.py @@ -0,0 +1,54 @@ +from sarif import * + +import pytest + +@pytest.fixture(scope='function', autouse=True) +def sarif(): + return sarif_from_env() + +expected_line_num = 21 + +def test_sarif_output_with_fixes(sarif): + schema = sarif['$schema'] + assert schema == 'https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json' + + version = sarif['version'] + assert version == '2.1.0' + + runs = sarif['runs'] + run = runs[0] + + tool = run['tool'] + assert tool['driver']['name'] == 'test-note-with-fix-it-hint.c.exe' + + results = run['results'] + assert len(results) == 1 + result = results[0] + assert result['ruleId'] == 'error' + assert result['level'] == 'error' + assert result['message']['text'] == "unknown field 'colour'" + assert len(result['locations']) == 1 + location = result['locations'][0] + phys_loc = location['physicalLocation'] + assert phys_loc['artifactLocation']['uri'].endswith('test-note-with-fix-it-hint.c') + assert phys_loc['region']['startLine'] == expected_line_num + assert phys_loc['region']['startColumn'] == 13 + assert phys_loc['region']['endColumn'] == 19 + assert phys_loc['contextRegion']['startLine'] == expected_line_num + assert phys_loc['contextRegion']['snippet']['text'] \ + == ' return p->colour;\n' + + assert len(result['relatedLocations']) == 1 + note = result['relatedLocations'][0] + phys_loc = note['physicalLocation'] + assert phys_loc['artifactLocation']['uri'].endswith('test-note-with-fix-it-hint.c') + assert phys_loc['region']['startLine'] == expected_line_num + assert phys_loc['region']['startColumn'] == 13 + assert phys_loc['region']['endColumn'] == 19 + assert phys_loc['contextRegion']['startLine'] == expected_line_num + assert phys_loc['contextRegion']['snippet']['text'] \ + == ' return p->colour;\n' + assert note['message']['text'] == "did you mean 'color'" + + # TODO: we don't yet capture fix-it hints on notes (PR other/116164) + assert 'fixes' not in result diff --git a/gcc/testsuite/libdiagnostics.dg/test-note-with-fix-it-hint.c b/gcc/testsuite/libdiagnostics.dg/test-note-with-fix-it-hint.c new file mode 100644 index 000000000000..19fa7c1b46d6 --- /dev/null +++ b/gcc/testsuite/libdiagnostics.dg/test-note-with-fix-it-hint.c @@ -0,0 +1,69 @@ +/* Example of a grouped error and note, with a fix-it hint on the note. + + Intended output is similar to: + +/PATH/test-note-with-fix-it-hint.c:21:13: error: unknown field 'colour' + 21 | return p->colour; + | ^~~~~~ +/PATH/test-note-with-fix-it-hint.c:21:13: note: did you mean 'color' + 21 | return p->colour; + | ^~~~~~ + | color + + along with the equivalent in SARIF. */ + +#include "libdiagnostics.h" +#include "test-helpers.h" + +/* Placeholder source: +_________11111111112 +12345678901234567890 + return p->colour; +*/ +const int line_num = __LINE__ - 2; + +int +main () +{ + begin_test ("test-note-with-fix-it-hint.c.exe", + "test-note-with-fix-it-hint.c.sarif", + __FILE__, "c"); + + const diagnostic_physical_location *loc_token + = make_range (diag_mgr, main_file, line_num, 13, 18); + + diagnostic_manager_begin_group (diag_mgr); + + diagnostic *err = diagnostic_begin (diag_mgr, DIAGNOSTIC_LEVEL_ERROR); + diagnostic_set_location (err, loc_token); + diagnostic_finish (err, "unknown field %qs", "colour"); + + diagnostic *n = diagnostic_begin (diag_mgr, DIAGNOSTIC_LEVEL_NOTE); + diagnostic_set_location (n, loc_token); + diagnostic_add_fix_it_hint_replace (n, loc_token, "color"); + diagnostic_finish (n, "did you mean %qs", "color"); + + diagnostic_manager_end_group (diag_mgr); + + return end_test (); +} + +/* Verify the output from the text sink. + { dg-regexp "\[^\n\r\]+test-note-with-fix-it-hint.c:21:13: error: unknown field 'colour'" } + { dg-begin-multiline-output "" } + 21 | return p->colour; + | ^~~~~~ + { dg-end-multiline-output "" } + { dg-regexp "\[^\n\r\]+test-note-with-fix-it-hint.c:21:13: note: did you mean 'color'" } + { dg-begin-multiline-output "" } + 21 | return p->colour; + | ^~~~~~ + | color + { dg-end-multiline-output "" } */ + +/* Verify that some JSON was written to a file with the expected name: + { dg-final { verify-sarif-file } } */ + +/* Use a Python script to verify various properties about the generated + .sarif file: + { dg-final { run-sarif-pytest test-note-with-fix-it-hint.c "test-note-with-fix-it-hint-c.py" } } */ diff --git a/gcc/testsuite/libdiagnostics.dg/test-text-sink-options.c b/gcc/testsuite/libdiagnostics.dg/test-text-sink-options.c new file mode 100644 index 000000000000..c1468553a122 --- /dev/null +++ b/gcc/testsuite/libdiagnostics.dg/test-text-sink-options.c @@ -0,0 +1,59 @@ +/* Example of controlling options for text sinks, + with multiple text sinks, + and color output. */ + +#include "libdiagnostics.h" + +/* +_________111111111122 +123456789012345678901 +PRINT "hello world!"; +*/ +const int line_num = __LINE__ - 2; + +int +main () +{ + diagnostic_manager *diag_mgr = diagnostic_manager_new (); + + diagnostic_text_sink *sink_1 + = diagnostic_manager_add_text_sink (diag_mgr, stderr, + DIAGNOSTIC_COLORIZE_NO); + diagnostic_text_sink_set_source_printing_enabled (sink_1, 0); + + diagnostic_text_sink *sink_2 + = diagnostic_manager_add_text_sink (diag_mgr, stderr, + DIAGNOSTIC_COLORIZE_YES); + diagnostic_text_sink_set_labelled_source_colorization_enabled (sink_2, 0); + + const diagnostic_file *file = diagnostic_manager_new_file (diag_mgr, __FILE__, "c"); + const diagnostic_physical_location *loc_start + = diagnostic_manager_new_location_from_file_line_column (diag_mgr, file, line_num, 8); + const diagnostic_physical_location *loc_end + = diagnostic_manager_new_location_from_file_line_column (diag_mgr, file, line_num, 19); + const diagnostic_physical_location *loc_range + = diagnostic_manager_new_location_from_range (diag_mgr, + loc_start, + loc_start, + loc_end); + + diagnostic *d = diagnostic_begin (diag_mgr, + DIAGNOSTIC_LEVEL_ERROR); + diagnostic_set_location (d, loc_range); + + diagnostic_finish (d, "can't find %qs", "foo"); + + diagnostic_manager_release (diag_mgr); + return 0; +}; + +/* Verify the output from text sink 1. */ +/* { dg-regexp "\[^\n\r\]+test-text-sink-options.c:10:8: error: can't find 'foo'" } */ + +/* Verify the output from text sink 2. + { dg-regexp "\[^\n\r\]+test-text-sink-options.c:10:8:" } + { dg-begin-multiline-output "" } + error: can't find 'foo' + 10 | PRINT "hello world!"; + | ^~~~~~~~~~~~ + { dg-end-multiline-output "" } */ diff --git a/gcc/testsuite/libdiagnostics.dg/test-warning-c.py b/gcc/testsuite/libdiagnostics.dg/test-warning-c.py new file mode 100644 index 000000000000..77500d72a14f --- /dev/null +++ b/gcc/testsuite/libdiagnostics.dg/test-warning-c.py @@ -0,0 +1,54 @@ +from sarif import * + +import pytest + +@pytest.fixture(scope='function', autouse=True) +def sarif(): + return sarif_from_env() + +expected_line_num = 17 +expected_file_name = 'test-warning.c' + +def test_sarif_output(sarif): + schema = sarif['$schema'] + assert schema == 'https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json' + + version = sarif['version'] + assert version == '2.1.0' + + runs = sarif['runs'] + run = runs[0] + + tool = run['tool'] + assert tool['driver']['name'] == expected_file_name + '.exe' + + invocations = run['invocations'] + assert len(invocations) == 1 + assert 'workingDirectory' in invocations[0] + assert 'startTimeUtc' in invocations[0] + assert invocations[0]['executionSuccessful'] == True + assert invocations[0]['toolExecutionNotifications'] == [] + assert 'endTimeUtc' in invocations[0] + + artifacts = run['artifacts'] + assert len(artifacts) == 1 + assert artifacts[0]['location']['uri'].endswith(expected_file_name) + assert artifacts[0]['sourceLanguage'] == 'c' + assert 'PRINT' in artifacts[0]['contents']['text'] + assert artifacts[0]['roles'] == ["analysisTarget"] + + results = run['results'] + assert len(results) == 1 + assert results[0]['ruleId'] == 'warning' + assert results[0]['level'] == 'warning' + assert results[0]['message']['text'] == "this is a warning" + assert len(results[0]['locations']) == 1 + location = results[0]['locations'][0] + phys_loc = location['physicalLocation'] + assert phys_loc['artifactLocation']['uri'].endswith(expected_file_name) + assert phys_loc['region']['startLine'] == expected_line_num + assert phys_loc['region']['startColumn'] == 8 + assert phys_loc['region']['endColumn'] == 20 + assert phys_loc['contextRegion']['startLine'] == expected_line_num + assert phys_loc['contextRegion']['snippet']['text'] \ + == 'PRINT "hello world!";\n' diff --git a/gcc/testsuite/libdiagnostics.dg/test-warning-with-path-c.py b/gcc/testsuite/libdiagnostics.dg/test-warning-with-path-c.py new file mode 100644 index 000000000000..b60e807d4f75 --- /dev/null +++ b/gcc/testsuite/libdiagnostics.dg/test-warning-with-path-c.py @@ -0,0 +1,108 @@ +from sarif import * + +import pytest + +@pytest.fixture(scope='function', autouse=True) +def sarif(): + return sarif_from_env() + +final_line_num = 33 + +line_num_call_to_PyList_New = final_line_num - 7; +line_num_for_loop = final_line_num - 5; +line_num_call_to_PyList_Append = final_line_num - 3; + +expected_file_name = 'test-warning-with-path.c' + +def test_sarif_output_for_warning_with_path(sarif): + schema = sarif['$schema'] + assert schema == 'https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json' + + version = sarif['version'] + assert version == '2.1.0' + + runs = sarif['runs'] + run = runs[0] + + tool = run['tool'] + assert tool['driver']['name'] == expected_file_name + '.exe' + + results = run['results'] + assert len(results) == 1 + + result = results[0] + assert result['ruleId'] == 'warning' + assert result['level'] == 'warning' + assert result['message']['text'] \ + == "passing NULL as argument 1 to 'PyList_Append' which requires a non-NULL parameter" + assert len(result['locations']) == 1 + location = result['locations'][0] + + phys_loc = location['physicalLocation'] + assert phys_loc['artifactLocation']['uri'].endswith(expected_file_name) + assert phys_loc['region']['startLine'] \ + == line_num_call_to_PyList_Append + assert phys_loc['region']['startColumn'] == 5 + assert phys_loc['region']['endColumn'] == 30 + assert phys_loc['contextRegion']['startLine'] \ + == line_num_call_to_PyList_Append + assert phys_loc['contextRegion']['snippet']['text'] \ + == ' PyList_Append(list, item);\n' + + assert len(location['logicalLocations']) == 1 + logical_loc = location['logicalLocations'][0] + assert logical_loc['name'] == 'make_a_list_of_random_ints_badly' + assert logical_loc['fullyQualifiedName'] == 'make_a_list_of_random_ints_badly' + assert logical_loc['decoratedName'] == 'make_a_list_of_random_ints_badly' + assert logical_loc['kind'] == 'function' + + assert len(result['codeFlows']) == 1 + assert len(result['codeFlows'][0]['threadFlows']) == 1 + thread_flow = result['codeFlows'][0]['threadFlows'][0] + + assert len(thread_flow['locations']) == 3 + + tfl_0 = thread_flow['locations'][0] + tfl_0_loc = tfl_0['location'] + assert get_location_artifact_uri(tfl_0_loc).endswith(expected_file_name) + assert get_location_physical_region(tfl_0_loc)['startLine'] \ + == line_num_call_to_PyList_New + assert get_location_physical_region(tfl_0_loc)['startColumn'] == 10 + assert get_location_physical_region(tfl_0_loc)['endColumn'] == 23 + assert get_location_snippet_text(tfl_0_loc) \ + == ' list = PyList_New(0);\n' + assert tfl_0_loc['logicalLocations'] == location['logicalLocations'] + assert tfl_0_loc['message']['text'] \ + == "when 'PyList_New' fails, returning NULL" + assert tfl_0['nestingLevel'] == 0 + assert tfl_0['executionOrder'] == 1 + + tfl_1 = thread_flow['locations'][1] + tfl_1_loc = tfl_1['location'] + assert get_location_artifact_uri(tfl_1_loc).endswith(expected_file_name) + assert get_location_physical_region(tfl_1_loc)['startLine'] \ + == line_num_for_loop + assert get_location_physical_region(tfl_1_loc)['startColumn'] == 15 + assert get_location_physical_region(tfl_1_loc)['endColumn'] == 24 + assert get_location_snippet_text(tfl_1_loc) \ + == ' for (i = 0; i < count; i++) {\n' + assert tfl_1_loc['logicalLocations'] == location['logicalLocations'] + assert tfl_1_loc['message']['text'] \ + == "when 'i < count'" + assert tfl_1['nestingLevel'] == 0 + assert tfl_1['executionOrder'] == 2 + + tfl_2 = thread_flow['locations'][2] + tfl_2_loc = tfl_2['location'] + assert get_location_artifact_uri(tfl_2_loc).endswith(expected_file_name) + assert get_location_physical_region(tfl_2_loc)['startLine'] \ + == line_num_call_to_PyList_Append + assert get_location_physical_region(tfl_2_loc)['startColumn'] == 5 + assert get_location_physical_region(tfl_2_loc)['endColumn'] == 30 + assert get_location_snippet_text(tfl_2_loc) \ + == ' PyList_Append(list, item);\n' + assert tfl_2_loc['logicalLocations'] == location['logicalLocations'] + assert tfl_2_loc['message']['text'] \ + == "when calling 'PyList_Append', passing NULL from (1) as argument 1" + assert tfl_2['nestingLevel'] == 0 + assert tfl_2['executionOrder'] == 3 diff --git a/gcc/testsuite/libdiagnostics.dg/test-warning-with-path.c b/gcc/testsuite/libdiagnostics.dg/test-warning-with-path.c new file mode 100644 index 000000000000..6f1f4736344c --- /dev/null +++ b/gcc/testsuite/libdiagnostics.dg/test-warning-with-path.c @@ -0,0 +1,125 @@ +/* Example of emitting a warning with an execution path. + +TODO: + + Intended output is similar to: + + along with the equivalent in SARIF. */ + +#include "libdiagnostics.h" +#include "test-helpers.h" + +/* +_________111111111122222222223333333333444444444455555555556 +123456789012345678901234567890123456789012345678901234567890 +PyObject * +make_a_list_of_random_ints_badly(PyObject *self, + PyObject *args) +{ + PyObject *list, *item; + long count, i; + + if (!PyArg_ParseTuple(args, "i", &count)) { + return NULL; + } + + list = PyList_New(0); + + for (i = 0; i < count; i++) { + item = PyLong_FromLong(random()); + PyList_Append(list, item); + } + + return list; +*/ +const int final_line_num = __LINE__ - 2; + +const int line_num_call_to_PyList_New = final_line_num - 7; +const int line_num_for_loop = final_line_num - 5; +const int line_num_call_to_PyList_Append = final_line_num - 3; + +int +main () +{ + begin_test ("test-warning-with-path.c.exe", + "test-warning-with-path.c.sarif", + __FILE__, "c"); + + const diagnostic_physical_location *loc_call_to_PyList_New + = make_range (diag_mgr, main_file, line_num_call_to_PyList_New, 10, 22); + const diagnostic_physical_location *loc_for_cond + = make_range (diag_mgr, main_file, line_num_for_loop, 15, 23); + const diagnostic_physical_location *loc_call_to_PyList_Append + = make_range (diag_mgr, main_file, line_num_call_to_PyList_Append, 5, 29); + + const char *funcname = "make_a_list_of_random_ints_badly"; + const diagnostic_logical_location *logical_loc + = diagnostic_manager_new_logical_location (diag_mgr, + DIAGNOSTIC_LOGICAL_LOCATION_KIND_FUNCTION, + NULL, /* parent */ + funcname, + funcname, + funcname); + + diagnostic *d = diagnostic_begin (diag_mgr, + DIAGNOSTIC_LEVEL_WARNING); + diagnostic_set_location (d, loc_call_to_PyList_Append); + diagnostic_set_logical_location (d, logical_loc); + + diagnostic_execution_path *path = diagnostic_add_execution_path (d); + + diagnostic_event_id alloc_event_id + = diagnostic_execution_path_add_event (path, + loc_call_to_PyList_New, + logical_loc, 0, + "when %qs fails, returning NULL", + "PyList_New"); + diagnostic_execution_path_add_event (path, + loc_for_cond, + logical_loc, 0, + "when %qs", "i < count"); + diagnostic_execution_path_add_event (path, + loc_call_to_PyList_Append, + logical_loc, 0, + "when calling %qs, passing NULL from %@ as argument %i", + "PyList_Append", &alloc_event_id, 1); + + diagnostic_finish (d, + "passing NULL as argument %i to %qs" + " which requires a non-NULL parameter", + 1, "PyList_Append"); + + return end_test (); +}; + +/* Check the output from the text sink. + { dg-begin-multiline-output "" } +In function 'make_a_list_of_random_ints_badly': + { dg-end-multiline-output "" } + { dg-regexp "\[^\n\r\]+test-warning-with-path.c:30:5: warning: passing NULL as argument 1 to 'PyList_Append' which requires a non-NULL parameter" } + { dg-begin-multiline-output "" } + 30 | PyList_Append(list, item); + | ^~~~~~~~~~~~~~~~~~~~~~~~~ + 'make_a_list_of_random_ints_badly': events 1-3 + 26 | list = PyList_New(0); + | ^~~~~~~~~~~~~ + | | + | (1) when 'PyList_New' fails, returning NULL + 27 | + 28 | for (i = 0; i < count; i++) { + | ~~~~~~~~~ + | | + | (2) when 'i < count' + 29 | item = PyLong_FromLong(random()); + 30 | PyList_Append(list, item); + | ~~~~~~~~~~~~~~~~~~~~~~~~~ + | | + | (3) when calling 'PyList_Append', passing NULL from (1) as argument 1 + { dg-end-multiline-output "" } */ + +/* Verify that some JSON was written to a file with the expected name: + { dg-final { verify-sarif-file } } */ + +/* Use a Python script to verify various properties about the generated + .sarif file: + { dg-final { run-sarif-pytest test-warning-with-path.c "test-warning-with-path-c.py" } } */ diff --git a/gcc/testsuite/libdiagnostics.dg/test-warning.c b/gcc/testsuite/libdiagnostics.dg/test-warning.c new file mode 100644 index 000000000000..4fe855e92ca9 --- /dev/null +++ b/gcc/testsuite/libdiagnostics.dg/test-warning.c @@ -0,0 +1,65 @@ +/* Example of emitting a warning. + + Intended output is similar to: + +/PATH/test-warning.c:17:8: warning: this is a warning + 17 | PRINT "hello world!"; + | ^~~~~~~~~~~~ + + along with the equivalent in SARIF. */ + +#include "libdiagnostics.h" +#include "test-helpers.h" + +/* +_________111111111122 +123456789012345678901 +PRINT "hello world!"; +*/ +const int line_num = __LINE__ - 2; + +int +main () +{ + begin_test ("test-warning.c.exe", + "test-warning.c.sarif", + __FILE__, "c"); + + const diagnostic_physical_location *loc_start + = diagnostic_manager_new_location_from_file_line_column (diag_mgr, + main_file, + line_num, + 8); + const diagnostic_physical_location *loc_end + = diagnostic_manager_new_location_from_file_line_column (diag_mgr, + main_file, + line_num, + 19); + const diagnostic_physical_location *loc_range + = diagnostic_manager_new_location_from_range (diag_mgr, + loc_start, + loc_start, + loc_end); + + diagnostic *d = diagnostic_begin (diag_mgr, + DIAGNOSTIC_LEVEL_WARNING); + diagnostic_set_location (d, loc_range); + + diagnostic_finish (d, "this is a warning"); + + return end_test (); +} + +/* Verify the output from the text sink. + { dg-regexp "\[^\n\r\]+test-warning.c:17:8: warning: this is a warning" } + { dg-begin-multiline-output "" } + 17 | PRINT "hello world!"; + | ^~~~~~~~~~~~ + { dg-end-multiline-output "" } */ + +/* Verify that some JSON was written to a file with the expected name: + { dg-final { verify-sarif-file } } */ + +/* Use a Python script to verify various properties about the generated + .sarif file: + { dg-final { run-sarif-pytest test-warning.c "test-warning-c.py" } } */ diff --git a/gcc/testsuite/libdiagnostics.dg/test-write-sarif-to-file-c.py b/gcc/testsuite/libdiagnostics.dg/test-write-sarif-to-file-c.py new file mode 100644 index 000000000000..263b8f1a497d --- /dev/null +++ b/gcc/testsuite/libdiagnostics.dg/test-write-sarif-to-file-c.py @@ -0,0 +1,55 @@ +from sarif import * + +import pytest + +@pytest.fixture(scope='function', autouse=True) +def sarif(): + return sarif_from_env() + +expected_line_num = 8 + +def test_sarif_output(sarif): + schema = sarif['$schema'] + assert schema == 'https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json' + + version = sarif['version'] + assert version == '2.1.0' + + runs = sarif['runs'] + run = runs[0] + + tool = run['tool'] + assert tool['driver']['name'] == 'test-write-sarif-to-file.c.exe' + + invocations = run['invocations'] + assert len(invocations) == 1 + assert 'workingDirectory' in invocations[0] + assert 'startTimeUtc' in invocations[0] + assert invocations[0]['executionSuccessful'] == False + assert invocations[0]['toolExecutionNotifications'] == [] + assert 'endTimeUtc' in invocations[0] + + artifacts = run['artifacts'] + assert len(artifacts) == 1 + assert artifacts[0]['location']['uri'] \ + .endswith('test-write-sarif-to-file.c') + assert artifacts[0]['sourceLanguage'] == 'c' + assert 'PRINT' in artifacts[0]['contents']['text'] + assert artifacts[0]['roles'] == ["analysisTarget"] + + results = run['results'] + assert len(results) == 1 + assert results[0]['ruleId'] == 'error' + assert results[0]['level'] == 'error' + assert results[0]['message']['text'] == "can't find 'foo'" + assert len(results[0]['locations']) == 1 + location = results[0]['locations'][0] + phys_loc = location['physicalLocation'] + assert phys_loc['artifactLocation']['uri'] \ + .endswith('test-write-sarif-to-file.c') + assert phys_loc['region']['startLine'] == expected_line_num + assert phys_loc['region']['startColumn'] == 8 + assert phys_loc['region']['endColumn'] == 20 + assert phys_loc['contextRegion']['startLine'] == expected_line_num + assert phys_loc['contextRegion']['snippet']['text'] \ + == 'PRINT "hello world!";\n' diff --git a/gcc/testsuite/libdiagnostics.dg/test-write-sarif-to-file.c b/gcc/testsuite/libdiagnostics.dg/test-write-sarif-to-file.c new file mode 100644 index 000000000000..637935e4598b --- /dev/null +++ b/gcc/testsuite/libdiagnostics.dg/test-write-sarif-to-file.c @@ -0,0 +1,55 @@ +/* Example of writing diagnostics as SARIF to a file. */ + +#include "libdiagnostics.h" + +/* +_________111111111122 +123456789012345678901 +PRINT "hello world!"; +*/ +const int line_num = __LINE__ - 2; + +int +main () +{ + FILE *sarif_outfile = fopen ("test-write-sarif-to-file.c.sarif", "w"); + if (!sarif_outfile) + return -1; + + diagnostic_manager *diag_mgr = diagnostic_manager_new (); + diagnostic_manager_set_tool_name (diag_mgr, "test-write-sarif-to-file.c.exe"); + + const diagnostic_file *file = diagnostic_manager_new_file (diag_mgr, __FILE__, "c"); + + diagnostic_manager_add_sarif_sink (diag_mgr, sarif_outfile, file, + DIAGNOSTIC_SARIF_VERSION_2_1_0); + + const diagnostic_physical_location *loc_start + = diagnostic_manager_new_location_from_file_line_column (diag_mgr, file, line_num, 8); + const diagnostic_physical_location *loc_end + = diagnostic_manager_new_location_from_file_line_column (diag_mgr, file, line_num, 19); + const diagnostic_physical_location *loc_range + = diagnostic_manager_new_location_from_range (diag_mgr, + loc_start, + loc_start, + loc_end); + + diagnostic *d = diagnostic_begin (diag_mgr, + DIAGNOSTIC_LEVEL_ERROR); + diagnostic_set_location (d, loc_range); + + diagnostic_finish (d, "can't find %qs", "foo"); + + diagnostic_manager_release (diag_mgr); + + fclose (sarif_outfile); + + return 0; +}; + +/* Verify that some JSON was written to a file with the expected name: + { dg-final { verify-sarif-file } } */ + +/* Use a Python script to verify various properties about the generated + .sarif file: + { dg-final { run-sarif-pytest test-write-sarif-to-file.c "test-write-sarif-to-file-c.py" } } */ diff --git a/gcc/testsuite/libdiagnostics.dg/test-write-text-to-file.c b/gcc/testsuite/libdiagnostics.dg/test-write-text-to-file.c new file mode 100644 index 000000000000..8ad448c8c9ce --- /dev/null +++ b/gcc/testsuite/libdiagnostics.dg/test-write-text-to-file.c @@ -0,0 +1,47 @@ +/* Example of writing diagnostics in text form, but to a file, + rather than stderr. */ + +#include "libdiagnostics.h" + +/* +_________111111111122 +123456789012345678901 +PRINT "hello world!"; +*/ +const int line_num = __LINE__ - 2; + +int +main () +{ + FILE *outfile = fopen ("test.txt", "w"); + if (!outfile) + return -1; + + diagnostic_manager *diag_mgr = diagnostic_manager_new (); + + diagnostic_manager_add_text_sink (diag_mgr, outfile, + DIAGNOSTIC_COLORIZE_NO); + + const diagnostic_file *file = diagnostic_manager_new_file (diag_mgr, __FILE__, "c"); + const diagnostic_physical_location *loc_start + = diagnostic_manager_new_location_from_file_line_column (diag_mgr, file, line_num, 8); + const diagnostic_physical_location *loc_end + = diagnostic_manager_new_location_from_file_line_column (diag_mgr, file, line_num, 19); + const diagnostic_physical_location *loc_range + = diagnostic_manager_new_location_from_range (diag_mgr, + loc_start, + loc_start, + loc_end); + + diagnostic *d = diagnostic_begin (diag_mgr, + DIAGNOSTIC_LEVEL_ERROR); + diagnostic_set_location (d, loc_range); + + diagnostic_finish (d, "can't find %qs", "foo"); + + diagnostic_manager_release (diag_mgr); + + fclose (outfile); + + return 0; +}; From patchwork Thu Aug 15 18:11:55 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: David Malcolm X-Patchwork-Id: 1972875 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=d4WmJScR; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=gcc.gnu.org (client-ip=8.43.85.97; helo=server2.sourceware.org; envelope-from=gcc-patches-bounces~incoming=patchwork.ozlabs.org@gcc.gnu.org; receiver=patchwork.ozlabs.org) Received: from server2.sourceware.org (server2.sourceware.org [8.43.85.97]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (secp384r1) server-digest SHA384) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4WlCtd1QGMz1yXl for ; Fri, 16 Aug 2024 04:14:01 +1000 (AEST) Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 3E845385C6C6 for ; Thu, 15 Aug 2024 18:13:59 +0000 (GMT) X-Original-To: gcc-patches@gcc.gnu.org Delivered-To: gcc-patches@gcc.gnu.org Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by sourceware.org (Postfix) with ESMTP id 24C903858431 for ; Thu, 15 Aug 2024 18:12:12 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 24C903858431 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 24C903858431 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=1723745539; cv=none; b=rX99+C+2NWfHuF1m2Dm2e2YHBKdjd0xYsyfjQxPkd3cHu1Y6Iu9t/elQVxdvOOl2csj353bm7kg/ZztIvjlyZ0WhnxeVnM92MTfXkCmOad4ki+cBwBzrRT8/RBlQm1PkMtTxOufZ0rr+WPxkgTFEpw65FH3+hezMFrGgrxTMFsk= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1723745539; c=relaxed/simple; bh=/l0ujEyClJm4XzXnyD2Pa/BdCELFN6ewapmC10zu+Zk=; h=DKIM-Signature:From:To:Subject:Date:Message-Id:MIME-Version; b=s8EKWYCaGzhbukAEUCLz1JVDPbCSi1saiejYJDq/GKGtpGsgX1384dfPoSQ+rqR1UF9lTpjxnlODr21tNFx8zjdT54zI2VL9r4CnBmudcd4ALM5QFCkjgR6+XwGs+Vq4bE4SOT7MrSkfvGliF0DeHB2HwblIO2VtQ1wp7x2T+ME= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1723745531; 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=ikLngYM/BFJ79ngAwjSid9ieAmUl69TYqEOzqnV/26A=; b=d4WmJScRAzHiFu+qXZJOnH9FK+avYHESfwMi9SWZWwHbcALCgXfD7T5s48+devba06XYXN VJfKU4xOv4LH4PNH68xZHtOVSTa/FMENFZk7eOJn+E26Xg+WSCClUp4yKrVK0TfON4hsR1 EIQ/MFOAaGSh9J3I/I9v/PvRvQcjC+0= Received: from mx-prod-mc-05.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-692-5YlABPSJOJmtG9ghL69x2w-1; Thu, 15 Aug 2024 14:12:10 -0400 X-MC-Unique: 5YlABPSJOJmtG9ghL69x2w-1 Received: from mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.17]) (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 mx-prod-mc-05.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 3727A1955F3F for ; Thu, 15 Aug 2024 18:12:09 +0000 (UTC) Received: from t14s.localdomain.com (unknown [10.22.16.43]) by mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 242F01955D44; Thu, 15 Aug 2024 18:12:07 +0000 (UTC) From: David Malcolm To: gcc-patches@gcc.gnu.org Cc: David Malcolm Subject: [PATCH 6/7] json: add json parsing support Date: Thu, 15 Aug 2024 14:11:55 -0400 Message-Id: <20240815181156.1815075-7-dmalcolm@redhat.com> In-Reply-To: <20240815181156.1815075-1-dmalcolm@redhat.com> References: <20240815181156.1815075-1-dmalcolm@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.0 on 10.30.177.17 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com X-Spam-Status: No, score=-22.1 required=5.0 tests=BAYES_00, DKIM_INVALID, DKIM_SIGNED, GIT_PATCH_0, KAM_DMARC_NONE, KAM_DMARC_STATUS, KAM_LOTSOFHASH, KAM_SHORT, RCVD_IN_DNSWL_NONE, RCVD_IN_MSPIKE_H4, 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 X-BeenThere: gcc-patches@gcc.gnu.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Gcc-patches mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: gcc-patches-bounces~incoming=patchwork.ozlabs.org@gcc.gnu.org This patch implements JSON parsing support. It's based on the parsing parts of the patch I posted here: https://gcc.gnu.org/legacy-ml/gcc-patches/2017-08/msg00417.html with the parsing moved to a separate source file and header, heavily rewritten to capture source location information for JSON values, and to capture errors via a result template. I also added optional support for C and C++ style comments, which is extremely useful in DejaGnu tests. gcc/ChangeLog: * Makefile.in (OBJS-libcommon): Add json-parsing.o. * json-parsing.cc: New file. * json-parsing.h: New file. * json.cc (selftest::assert_print_eq): Remove "static". * json.h (json::array::begin): New. (json::array::end): New. (json::array::length): New. (json::array::get): New. (is_a_helper ::test): New. (is_a_helper ::test): New. (is_a_helper ::test): New. (is_a_helper ::test): New. (is_a_helper ::test): New. (is_a_helper ::test): New. (is_a_helper ::test): New. (is_a_helper ::test): New. (is_a_helper ::test): New. (is_a_helper ::test): New. (is_a_helper ::test): New. (is_a_helper ::test): New. (selftest::assert_print_eq): New decl. * selftest-run-tests.cc (selftest::run_tests): Call selftest::json_parser_cc_tests. * selftest.h (selftest::json_parser_cc_tests): New decl. Signed-off-by: David Malcolm --- gcc/Makefile.in | 2 +- gcc/json-parsing.cc | 2394 +++++++++++++++++++++++++++++++++++++ gcc/json-parsing.h | 113 ++ gcc/json.cc | 2 +- gcc/json.h | 122 +- gcc/selftest-run-tests.cc | 1 + gcc/selftest.h | 1 + 7 files changed, 2631 insertions(+), 4 deletions(-) create mode 100644 gcc/json-parsing.cc create mode 100644 gcc/json-parsing.h diff --git a/gcc/Makefile.in b/gcc/Makefile.in index 3e4c7bd645f9..64dcaddfdfbe 100644 --- a/gcc/Makefile.in +++ b/gcc/Makefile.in @@ -1832,7 +1832,7 @@ OBJS-libcommon = diagnostic-spec.o diagnostic.o diagnostic-color.o \ diagnostic-show-locus.o \ edit-context.o \ pretty-print.o intl.o \ - json.o \ + json.o json-parsing.o \ sbitmap.o \ vec.o input.o hash-table.o ggc-none.o memory-block.o \ selftest.o selftest-diagnostic.o sort.o \ diff --git a/gcc/json-parsing.cc b/gcc/json-parsing.cc new file mode 100644 index 000000000000..78188c4fef9c --- /dev/null +++ b/gcc/json-parsing.cc @@ -0,0 +1,2394 @@ +/* JSON parsing + Copyright (C) 2017-2024 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 +. */ + +#include "config.h" +#define INCLUDE_MEMORY +#include "system.h" +#include "coretypes.h" +#include "json-parsing.h" +#include "pretty-print.h" +#include "math.h" +#include "make-unique.h" +#include "selftest.h" + +using namespace json; + +/* Declarations relating to parsing JSON, all within an + anonymous namespace. */ + +namespace { + +/* A typedef representing a single unicode character. */ + +typedef unsigned unichar; + +/* An enum for discriminating different kinds of JSON token. */ + +enum token_id +{ + TOK_ERROR, + + TOK_EOF, + + /* Punctuation. */ + TOK_OPEN_SQUARE, + TOK_OPEN_CURLY, + TOK_CLOSE_SQUARE, + TOK_CLOSE_CURLY, + TOK_COLON, + TOK_COMMA, + + /* Literal names. */ + TOK_TRUE, + TOK_FALSE, + TOK_NULL, + + TOK_STRING, + TOK_FLOAT_NUMBER, + TOK_INTEGER_NUMBER +}; + +/* Human-readable descriptions of enum token_id. */ + +static const char *token_id_name[] = { + "error", + "EOF", + "'['", + "'{'", + "']'", + "'}'", + "':'", + "','", + "'true'", + "'false'", + "'null'", + "string", + "number", + "number" +}; + +/* Tokens within the JSON lexer. */ + +struct token +{ + /* The kind of token. */ + enum token_id id; + + /* The location of this token within the unicode + character stream. */ + location_map::range range; + + union + { + /* Value for TOK_ERROR and TOK_STRING. */ + char *string; + + /* Value for TOK_FLOAT_NUMBER. */ + double float_number; + + /* Value for TOK_INTEGER_NUMBER. */ + long integer_number; + } u; +}; + +/* A class for lexing JSON. */ + +class lexer +{ + public: + lexer (bool support_comments); + ~lexer (); + + std::unique_ptr add_utf8 (size_t length, const char *utf8_buf); + + const token *peek (); + + void consume (); + + private: + bool get_char (unichar &out_char, location_map::point *out_point); + void unget_char (); + location_map::point get_next_point () const; + static void dump_token (FILE *outf, const token *tok); + void lex_token (token *out); + void lex_string (token *out); + void lex_number (token *out, unichar first_char); + bool rest_of_literal (token *out, const char *suffix); + std::unique_ptr make_error (const char *msg); + bool consume_single_line_comment (token *out); + bool consume_multiline_comment (token *out); + + private: + auto_vec m_buffer; + int m_next_char_idx; + int m_next_char_line; + int m_next_char_column; + int m_prev_line_final_column; /* for handling unget_char after a '\n'. */ + + static const int MAX_TOKENS = 1; + token m_next_tokens[MAX_TOKENS]; + int m_num_next_tokens; + + bool m_support_comments; +}; + +/* A class for parsing JSON. */ + +class parser +{ + public: + parser (location_map *out_loc_map, + bool support_comments); + ~parser (); + + std::unique_ptr + add_utf8 (size_t length, const char *utf8_buf); + + parser_result_t parse_value (int depth); + parser_result_t parse_object (int depth); + parser_result_t parse_array (int depth); + + std::unique_ptr + require_eof (); + + private: + location_map::point get_next_token_start (); + location_map::point get_next_token_end (); + + std::unique_ptr + require (enum token_id tok_id); + + result> + require_one_of (enum token_id tok_id_a, enum token_id tok_id_b); + + std::unique_ptr + error_at (const location_map::range &r, + const char *fmt, ...) ATTRIBUTE_PRINTF_3; + + void maybe_record_range (json::value *jv, const location_map::range &r); + void maybe_record_range (json::value *jv, + const location_map::point &start, + const location_map::point &end); + + private: + lexer m_lexer; + location_map *m_loc_map; +}; + +} // anonymous namespace for parsing implementation + +/* Parser implementation. */ + +/* lexer's ctor. */ + +lexer::lexer (bool support_comments) +: m_buffer (), m_next_char_idx (0), + m_next_char_line (1), m_next_char_column (0), + m_prev_line_final_column (-1), + m_num_next_tokens (0), + m_support_comments (support_comments) +{ +} + +/* lexer's dtor. */ + +lexer::~lexer () +{ + while (m_num_next_tokens > 0) + consume (); +} + +/* Peek the next token. */ + +const token * +lexer::peek () +{ + if (m_num_next_tokens == 0) + { + lex_token (&m_next_tokens[0]); + m_num_next_tokens++; + } + return &m_next_tokens[0]; +} + +/* Consume the next token. */ + +void +lexer::consume () +{ + if (m_num_next_tokens == 0) + peek (); + + gcc_assert (m_num_next_tokens > 0); + gcc_assert (m_num_next_tokens <= MAX_TOKENS); + + if (0) + { + fprintf (stderr, "consuming token: "); + dump_token (stderr, &m_next_tokens[0]); + fprintf (stderr, "\n"); + } + + if (m_next_tokens[0].id == TOK_ERROR + || m_next_tokens[0].id == TOK_STRING) + free (m_next_tokens[0].u.string); + + m_num_next_tokens--; + memmove (&m_next_tokens[0], &m_next_tokens[1], + sizeof (token) * m_num_next_tokens); +} + +/* Add LENGTH bytes of UTF-8 encoded text from UTF8_BUF to this lexer's + buffer. + Return null if successful, or the error if there was a problem. */ + +std::unique_ptr +lexer::add_utf8 (size_t length, const char *utf8_buf) +{ + /* Adapted from charset.c:one_utf8_to_cppchar. */ + static const uchar masks[6] = { 0x7F, 0x1F, 0x0F, 0x07, 0x03, 0x01 }; + static const uchar patns[6] = { 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC }; + + const uchar *inbuf = (const unsigned char *) (utf8_buf); + const uchar **inbufp = &inbuf; + size_t *inbytesleftp = &length; + + while (length > 0) + { + unichar c; + const uchar *inbuf = *inbufp; + size_t nbytes, i; + + c = *inbuf; + if (c < 0x80) + { + m_buffer.safe_push (c); + *inbytesleftp -= 1; + *inbufp += 1; + continue; + } + + /* The number of leading 1-bits in the first byte indicates how many + bytes follow. */ + for (nbytes = 2; nbytes < 7; nbytes++) + if ((c & ~masks[nbytes-1]) == patns[nbytes-1]) + goto found; + return make_error ("ill-formed UTF-8 sequence"); + found: + + if (*inbytesleftp < nbytes) + return make_error ("ill-formed UTF-8 sequence"); + + c = (c & masks[nbytes-1]); + inbuf++; + for (i = 1; i < nbytes; i++) + { + unichar n = *inbuf++; + if ((n & 0xC0) != 0x80) + return make_error ("ill-formed UTF-8 sequence"); + c = ((c << 6) + (n & 0x3F)); + } + + /* Make sure the shortest possible encoding was used. */ + if (( c <= 0x7F && nbytes > 1) + || (c <= 0x7FF && nbytes > 2) + || (c <= 0xFFFF && nbytes > 3) + || (c <= 0x1FFFFF && nbytes > 4) + || (c <= 0x3FFFFFF && nbytes > 5)) + return make_error ("ill-formed UTF-8:" + " shortest possible encoding not used"); + + /* Make sure the character is valid. */ + if (c > 0x7FFFFFFF || (c >= 0xD800 && c <= 0xDFFF)) + return make_error ("ill-formed UTF-8: invalid character"); + + m_buffer.safe_push (c); + *inbufp = inbuf; + *inbytesleftp -= nbytes; + } + return nullptr; +} + +/* Attempt to get the next unicode character from this lexer's buffer. + If successful, write it to OUT_CHAR, and its location to *OUT_POINT, + and return true. + Otherwise, return false. */ + +bool +lexer::get_char (unichar &out_char, location_map::point *out_point) +{ + if (m_next_char_idx >= (int)m_buffer.length ()) + return false; + + if (out_point) + *out_point = get_next_point (); + out_char = m_buffer[m_next_char_idx++]; + + if (out_char == '\n') + { + m_next_char_line++; + m_prev_line_final_column = m_next_char_column; + m_next_char_column = 0; + } + else + m_next_char_column++; + + return true; +} + +/* Undo the last successful get_char. */ + +void +lexer::unget_char () +{ + --m_next_char_idx; + if (m_next_char_column > 0) + --m_next_char_column; + else + { + m_next_char_line--; + m_next_char_column = m_prev_line_final_column; + /* We don't support more than one unget_char in a row. */ + gcc_assert (m_prev_line_final_column != -1); + m_prev_line_final_column = -1; + } +} + +/* Get the location of the next char. */ + +location_map::point +lexer::get_next_point () const +{ + location_map::point result; + result.m_unichar_idx = m_next_char_idx; + result.m_line = m_next_char_line; + result.m_column = m_next_char_column; + return result; +} + +/* Print a textual representation of TOK to OUTF. + This is intended for debugging the lexer and parser, + rather than for user-facing output. */ + +void +lexer::dump_token (FILE *outf, const token *tok) +{ + switch (tok->id) + { + case TOK_ERROR: + fprintf (outf, "TOK_ERROR (\"%s\")", tok->u.string); + break; + + case TOK_EOF: + fprintf (outf, "TOK_EOF"); + break; + + case TOK_OPEN_SQUARE: + fprintf (outf, "TOK_OPEN_SQUARE"); + break; + + case TOK_OPEN_CURLY: + fprintf (outf, "TOK_OPEN_CURLY"); + break; + + case TOK_CLOSE_SQUARE: + fprintf (outf, "TOK_CLOSE_SQUARE"); + break; + + case TOK_CLOSE_CURLY: + fprintf (outf, "TOK_CLOSE_CURLY"); + break; + + case TOK_COLON: + fprintf (outf, "TOK_COLON"); + break; + + case TOK_COMMA: + fprintf (outf, "TOK_COMMA"); + break; + + case TOK_TRUE: + fprintf (outf, "TOK_TRUE"); + break; + + case TOK_FALSE: + fprintf (outf, "TOK_FALSE"); + break; + + case TOK_NULL: + fprintf (outf, "TOK_NULL"); + break; + + case TOK_STRING: + fprintf (outf, "TOK_STRING (\"%s\")", tok->u.string); + break; + + case TOK_FLOAT_NUMBER: + fprintf (outf, "TOK_FLOAT_NUMBER (%f)", tok->u.float_number); + break; + + case TOK_INTEGER_NUMBER: + fprintf (outf, "TOK_INTEGER_NUMBER (%ld)", tok->u.integer_number); + break; + + default: + gcc_unreachable (); + break; + } +} + +/* Treat "//" as a comment to the end of the line. + + This isn't compliant with the JSON spec, + but is very handy for writing DejaGnu tests. + + Return true if EOF and populate *OUT, false otherwise. */ + +bool +lexer::consume_single_line_comment (token *out) +{ + while (1) + { + unichar next_char; + if (!get_char (next_char, nullptr)) + { + out->id = TOK_EOF; + location_map::point p = get_next_point (); + out->range.m_start = p; + out->range.m_end = p; + return true; + } + if (next_char == '\n') + return false; + } +} + +/* Treat '/' '*' as a multiline comment until the next closing '*' '/'. + + This isn't compliant with the JSON spec, + but is very handy for writing DejaGnu tests. + + Return true if EOF and populate *OUT, false otherwise. */ + +bool +lexer::consume_multiline_comment (token *out) +{ + while (1) + { + unichar next_char; + if (!get_char (next_char, nullptr)) + { + out->id = TOK_ERROR; + gcc_unreachable (); // TODO + location_map::point p = get_next_point (); + out->range.m_start = p; + out->range.m_end = p; + return true; + } + if (next_char != '*') + continue; + if (!get_char (next_char, nullptr)) + { + out->id = TOK_ERROR; + gcc_unreachable (); // TODO + location_map::point p = get_next_point (); + out->range.m_start = p; + out->range.m_end = p; + return true; + } + if (next_char == '/') + return false; + } +} + +/* Attempt to lex the input buffer, writing the next token to OUT. + On errors, TOK_ERROR (or TOK_EOF) is written to OUT. */ + +void +lexer::lex_token (token *out) +{ + /* Skip to next non-whitespace char. */ + unichar next_char; + location_map::point start_point; + while (1) + { + if (!get_char (next_char, &start_point)) + { + out->id = TOK_EOF; + location_map::point p = get_next_point (); + out->range.m_start = p; + out->range.m_end = p; + return; + } + if (m_support_comments) + if (next_char == '/') + { + location_map::point point; + unichar next_next_char; + if (get_char (next_next_char, &point)) + { + switch (next_next_char) + { + case '/': + if (consume_single_line_comment (out)) + return; + continue; + case '*': + if (consume_multiline_comment (out)) + return; + continue; + default: + /* A stray single '/'. Break out of loop, so that we + handle it below as an unexpected character. */ + goto non_whitespace; + } + } + } + if (next_char != ' ' + && next_char != '\t' + && next_char != '\n' + && next_char != '\r') + break; + } + + non_whitespace: + + out->range.m_start = start_point; + out->range.m_end = start_point; + + switch (next_char) + { + case '[': + out->id = TOK_OPEN_SQUARE; + break; + + case '{': + out->id = TOK_OPEN_CURLY; + break; + + case ']': + out->id = TOK_CLOSE_SQUARE; + break; + + case '}': + out->id = TOK_CLOSE_CURLY; + break; + + case ':': + out->id = TOK_COLON; + break; + + case ',': + out->id = TOK_COMMA; + break; + + case '"': + lex_string (out); + break; + + case '-': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + lex_number (out, next_char); + break; + + case 't': + /* Handle literal "true". */ + if (rest_of_literal (out, "rue")) + { + out->id = TOK_TRUE; + break; + } + else + goto err; + + case 'f': + /* Handle literal "false". */ + if (rest_of_literal (out, "alse")) + { + out->id = TOK_FALSE; + break; + } + else + goto err; + + case 'n': + /* Handle literal "null". */ + if (rest_of_literal (out, "ull")) + { + out->id = TOK_NULL; + break; + } + else + goto err; + + err: + default: + out->id = TOK_ERROR; + out->u.string = xasprintf ("unexpected character: '%c'", next_char); + break; + } +} + +/* Having consumed an open-quote character from the lexer's buffer, attempt + to lex the rest of a JSON string, writing the result to OUT (or TOK_ERROR) + if an error occurred. + (ECMA-404 section 9; RFC 7159 section 7). */ + +void +lexer::lex_string (token *out) +{ + auto_vec content; + bool still_going = true; + while (still_going) + { + unichar uc; + if (!get_char (uc, &out->range.m_end)) + { + out->id = TOK_ERROR; + out->range.m_end = get_next_point (); + out->u.string = xstrdup ("EOF within string"); + return; + } + switch (uc) + { + case '"': + still_going = false; + break; + case '\\': + { + unichar next_char; + if (!get_char (next_char, &out->range.m_end)) + { + out->id = TOK_ERROR; + out->range.m_end = get_next_point (); + out->u.string = xstrdup ("EOF within string");; + return; + } + switch (next_char) + { + case '"': + case '\\': + case '/': + content.safe_push (next_char); + break; + + case 'b': + content.safe_push ('\b'); + break; + + case 'f': + content.safe_push ('\f'); + break; + + case 'n': + content.safe_push ('\n'); + break; + + case 'r': + content.safe_push ('\r'); + break; + + case 't': + content.safe_push ('\t'); + break; + + case 'u': + { + unichar result = 0; + for (int i = 0; i < 4; i++) + { + unichar hexdigit; + if (!get_char (hexdigit, &out->range.m_end)) + { + out->id = TOK_ERROR; + out->range.m_end = get_next_point (); + out->u.string = xstrdup ("EOF within string"); + return; + } + result <<= 4; + if (hexdigit >= '0' && hexdigit <= '9') + result += hexdigit - '0'; + else if (hexdigit >= 'a' && hexdigit <= 'f') + result += (hexdigit - 'a') + 10; + else if (hexdigit >= 'A' && hexdigit <= 'F') + result += (hexdigit - 'A') + 10; + else + { + out->id = TOK_ERROR; + out->range.m_start = out->range.m_end; + out->u.string = xstrdup ("bogus hex char"); + return; + } + } + content.safe_push (result); + } + break; + + default: + out->id = TOK_ERROR; + out->u.string = xstrdup ("unrecognized escape char"); + return; + } + } + break; + + default: + /* Reject unescaped control characters U+0000 through U+001F + (ECMA-404 section 9 para 1; RFC 7159 section 7 para 1). */ + if (uc <= 0x1f) + { + out->id = TOK_ERROR; + out->range.m_start = out->range.m_end; + out->u.string = xstrdup ("unescaped control char"); + return; + } + + /* Otherwise, add regular unicode code point. */ + content.safe_push (uc); + break; + } + } + + out->id = TOK_STRING; + + auto_vec utf8_buf; + // Adapted from libcpp/charset.c:one_cppchar_to_utf8 + for (unsigned i = 0; i < content.length (); i++) + { + static const uchar masks[6] = { 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC }; + static const uchar limits[6] = { 0x80, 0xE0, 0xF0, 0xF8, 0xFC, 0xFE }; + size_t nbytes; + uchar buf[6], *p = &buf[6]; + unichar c = content[i]; + + nbytes = 1; + if (c < 0x80) + *--p = c; + else + { + do + { + *--p = ((c & 0x3F) | 0x80); + c >>= 6; + nbytes++; + } + while (c >= 0x3F || (c & limits[nbytes-1])); + *--p = (c | masks[nbytes-1]); + } + + while (p < &buf[6]) + utf8_buf.safe_push (*p++); + } + + out->u.string = XNEWVEC (char, utf8_buf.length () + 1); + for (unsigned i = 0; i < utf8_buf.length (); i++) + out->u.string[i] = utf8_buf[i]; + out->u.string[utf8_buf.length ()] = '\0'; +} + +/* Having consumed FIRST_CHAR, an initial digit or '-' character from + the lexer's buffer attempt to lex the rest of a JSON number, writing + the result to OUT (or TOK_ERROR) if an error occurred. + (ECMA-404 section 8; RFC 7159 section 6). */ + +void +lexer::lex_number (token *out, unichar first_char) +{ + bool negate = false; + double value = 0.0; + if (first_char == '-') + { + negate = true; + if (!get_char (first_char, &out->range.m_end)) + { + out->id = TOK_ERROR; + out->range.m_start = out->range.m_end; + out->u.string = xstrdup ("expected digit"); + return; + } + } + + if (first_char == '0') + value = 0.0; + else if (!ISDIGIT (first_char)) + { + out->id = TOK_ERROR; + out->range.m_start = out->range.m_end; + out->u.string = xstrdup ("expected digit"); + return; + } + else + { + /* Got a nonzero digit; expect zero or more digits. */ + value = first_char - '0'; + while (1) + { + unichar uc; + location_map::point point; + if (!get_char (uc, &point)) + break; + if (ISDIGIT (uc)) + { + value *= 10; + value += uc -'0'; + out->range.m_end = point; + continue; + } + else + { + unget_char (); + break; + } + } + } + + /* Optional '.', followed by one or more decimals. */ + unichar next_char; + location_map::point point; + if (get_char (next_char, &point)) + { + if (next_char == '.') + { + /* Parse decimal digits. */ + bool had_digit = false; + double digit_factor = 0.1; + while (get_char (next_char, &point)) + { + if (!ISDIGIT (next_char)) + { + unget_char (); + break; + } + value += (next_char - '0') * digit_factor; + digit_factor *= 0.1; + had_digit = true; + out->range.m_end = point; + } + if (!had_digit) + { + out->id = TOK_ERROR; + out->range.m_start = point; + out->range.m_start = point; + out->u.string = xstrdup ("expected digit"); + return; + } + } + else + unget_char (); + } + + /* Parse 'e' and 'E'. */ + unichar exponent_char; + if (get_char (exponent_char, &point)) + { + if (exponent_char == 'e' || exponent_char == 'E') + { + /* Optional +/-. */ + unichar sign_char; + int exponent = 0; + bool negate_exponent = false; + bool had_exponent_digit = false; + if (!get_char (sign_char, &point)) + { + out->id = TOK_ERROR; + out->range.m_start = point; + out->range.m_start = point; + out->u.string = xstrdup ("EOF within exponent"); + return; + } + if (sign_char == '-') + negate_exponent = true; + else if (sign_char == '+') + ; + else if (ISDIGIT (sign_char)) + { + exponent = sign_char - '0'; + had_exponent_digit = true; + } + else + { + out->id = TOK_ERROR; + out->range.m_start = point; + out->range.m_start = point; + out->u.string + = xstrdup ("expected '-','+' or digit within exponent"); + return; + } + out->range.m_end = point; + + /* One or more digits (we might have seen the digit above, + though). */ + while (1) + { + unichar uc; + location_map::point point; + if (!get_char (uc, &point)) + break; + if (ISDIGIT (uc)) + { + exponent *= 10; + exponent += uc -'0'; + had_exponent_digit = true; + out->range.m_end = point; + continue; + } + else + { + unget_char (); + break; + } + } + if (!had_exponent_digit) + { + out->id = TOK_ERROR; + out->range.m_start = point; + out->range.m_start = point; + out->u.string = xstrdup ("expected digit within exponent"); + return; + } + if (negate_exponent) + exponent = -exponent; + value = value * pow (10, exponent); + } + else + unget_char (); + } + + if (negate) + value = -value; + + if (value == (long)value) + { + out->id = TOK_INTEGER_NUMBER; + out->u.integer_number = value; + } + else + { + out->id = TOK_FLOAT_NUMBER; + out->u.float_number = value; + } +} + +/* Determine if the next characters to be lexed match SUFFIX. + SUFFIX must be pure ASCII and not contain newlines. + If so, consume the characters and return true. + Otherwise, return false. */ + +bool +lexer::rest_of_literal (token *out, const char *suffix) +{ + int suffix_idx = 0; + int buf_idx = m_next_char_idx; + while (1) + { + if (suffix[suffix_idx] == '\0') + { + m_next_char_idx += suffix_idx; + m_next_char_column += suffix_idx; + out->range.m_end.m_unichar_idx += suffix_idx; + out->range.m_end.m_column += suffix_idx; + return true; + } + if (buf_idx >= (int)m_buffer.length ()) + return false; + /* This assumes that suffix is ASCII. */ + if (m_buffer[buf_idx] != (unichar)suffix[suffix_idx]) + return false; + buf_idx++; + suffix_idx++; + } +} + +/* Create a new error instance for MSG, using the location of the next + character for the location of the error. */ + +std::unique_ptr +lexer::make_error (const char *msg) +{ + location_map::point p; + p.m_unichar_idx = m_next_char_idx; + p.m_line = m_next_char_line; + p.m_column = m_next_char_column; + location_map::range r; + r.m_start = p; + r.m_end = p; + return ::make_unique (r, xstrdup (msg)); +} + +/* parser's ctor. */ + +parser::parser (location_map *out_loc_map, + bool support_comments) +: m_lexer (support_comments), m_loc_map (out_loc_map) +{ +} + +/* parser's dtor. */ + +parser::~parser () +{ + if (m_loc_map) + m_loc_map->on_finished_parsing (); +} + +/* Add LENGTH bytes of UTF-8 encoded text from UTF8_BUF to this parser's + lexer's buffer. */ + +std::unique_ptr +parser::add_utf8 (size_t length, const char *utf8_buf) +{ + return m_lexer.add_utf8 (length, utf8_buf); +} + +/* Parse a JSON value (object, array, number, string, or literal). + (ECMA-404 section 5; RFC 7159 section 3). */ + +parser_result_t +parser::parse_value (int depth) +{ + const token *tok = m_lexer.peek (); + + /* Avoid stack overflow with deeply-nested inputs; RFC 7159 section 9 + states: "An implementation may set limits on the maximum depth + of nesting.". + + Ideally we'd avoid this limit (e.g. by rewriting parse_value, + parse_object, and parse_array into a single function with a vec of + state). */ + const int MAX_DEPTH = 100; + if (depth >= MAX_DEPTH) + return error_at (tok->range, "maximum nesting depth exceeded: %i", + MAX_DEPTH); + + switch (tok->id) + { + case TOK_OPEN_CURLY: + return parse_object (depth); + + case TOK_STRING: + { + auto val = ::make_unique (tok->u.string); + m_lexer.consume (); + maybe_record_range (val.get (), tok->range); + return parser_result_t (std::move (val)); + } + + case TOK_OPEN_SQUARE: + return parse_array (depth); + + case TOK_FLOAT_NUMBER: + { + auto val = ::make_unique (tok->u.float_number); + m_lexer.consume (); + maybe_record_range (val.get (), tok->range); + return parser_result_t (std::move (val)); + } + + case TOK_INTEGER_NUMBER: + { + auto val = ::make_unique (tok->u.integer_number); + m_lexer.consume (); + maybe_record_range (val.get (), tok->range); + return parser_result_t (std::move (val)); + } + + case TOK_TRUE: + { + auto val = ::make_unique (JSON_TRUE); + m_lexer.consume (); + maybe_record_range (val.get (), tok->range); + return parser_result_t (std::move (val)); + } + + case TOK_FALSE: + { + auto val = ::make_unique (JSON_FALSE); + m_lexer.consume (); + maybe_record_range (val.get (), tok->range); + return parser_result_t (std::move (val)); + } + + case TOK_NULL: + { + auto val = ::make_unique (JSON_NULL); + m_lexer.consume (); + maybe_record_range (val.get (), tok->range); + return parser_result_t (std::move (val)); + } + + case TOK_ERROR: + return error_at (tok->range, "invalid JSON token: %s", tok->u.string); + + default: + return error_at (tok->range, "expected a JSON value but got %s", + token_id_name[tok->id]); + } +} + +/* Parse a JSON object. + (ECMA-404 section 6; RFC 7159 section 4). */ + +parser_result_t +parser::parse_object (int depth) +{ + location_map::point start = get_next_token_start (); + + require (TOK_OPEN_CURLY); + + auto obj = ::make_unique (); + + const token *tok = m_lexer.peek (); + if (tok->id == TOK_CLOSE_CURLY) + { + location_map::point end = get_next_token_end (); + maybe_record_range (obj.get (), start, end); + if (auto err = require (TOK_CLOSE_CURLY)) + return parser_result_t (std::move (err)); + return parser_result_t (std::move (obj)); + } + if (tok->id != TOK_STRING) + return error_at (tok->range, + "expected string for object key after '{'; got %s", + token_id_name[tok->id]); + while (true) + { + tok = m_lexer.peek (); + if (tok->id != TOK_STRING) + return error_at (tok->range, + "expected string for object key after ','; got %s", + token_id_name[tok->id]); + label_text key = label_text::take (xstrdup (tok->u.string)); + m_lexer.consume (); + + if (auto err = require (TOK_COLON)) + return parser_result_t (std::move (err)); + + parser_result_t r = parse_value (depth + 1); + if (r.m_err) + return r; + if (!r.m_val) + return parser_result_t (std::move (obj)); + + /* We don't enforce uniqueness for keys. */ + obj->set (key.get (), std::move (r.m_val)); + + location_map::point end = get_next_token_end (); + result> result + (require_one_of (TOK_COMMA, TOK_CLOSE_CURLY)); + if (result.m_err) + return parser_result_t (std::move (result.m_err)); + if (result.m_val == TOK_COMMA) + continue; + else + { + /* TOK_CLOSE_CURLY. */ + maybe_record_range (obj.get (), start, end); + return parser_result_t (std::move (obj)); + } + } +} + +/* Parse a JSON array. + (ECMA-404 section 7; RFC 7159 section 5). */ + +parser_result_t +parser::parse_array (int depth) +{ + location_map::point start = get_next_token_start (); + if (auto err = require (TOK_OPEN_SQUARE)) + return parser_result_t (std::move (err)); + + auto arr = ::make_unique (); + + const token *tok = m_lexer.peek (); + if (tok->id == TOK_CLOSE_SQUARE) + { + location_map::point end = get_next_token_end (); + maybe_record_range (arr.get (), start, end); + m_lexer.consume (); + return parser_result_t (std::move (arr)); + } + + while (true) + { + parser_result_t r = parse_value (depth + 1); + if (r.m_err) + return r; + + arr->append (std::move (r.m_val)); + + location_map::point end = get_next_token_end (); + result> result + (require_one_of (TOK_COMMA, TOK_CLOSE_SQUARE)); + if (result.m_err) + return parser_result_t (std::move (result.m_err)); + if (result.m_val == TOK_COMMA) + continue; + else + { + /* TOK_CLOSE_SQUARE. */ + maybe_record_range (arr.get (), start, end); + return parser_result_t (std::move (arr)); + } + } +} + +/* Get the start point of the next token. */ + +location_map::point +parser::get_next_token_start () +{ + const token *tok = m_lexer.peek (); + return tok->range.m_start; +} + +/* Get the end point of the next token. */ + +location_map::point +parser::get_next_token_end () +{ + const token *tok = m_lexer.peek (); + return tok->range.m_end; +} + +/* Require an EOF, or fail if there is surplus input. */ + +std::unique_ptr +parser::require_eof () +{ + return require (TOK_EOF); +} + +/* Consume the next token, issuing an error if it is not of kind TOK_ID. */ + +std::unique_ptr +parser::require (enum token_id tok_id) +{ + const token *tok = m_lexer.peek (); + if (tok->id != tok_id) + { + if (tok->id == TOK_ERROR) + return error_at (tok->range, + "expected %s; got bad token: %s", + token_id_name[tok_id], tok->u.string); + else + return error_at (tok->range, + "expected %s; got %s", token_id_name[tok_id], + token_id_name[tok->id]); + } + m_lexer.consume (); + return nullptr; +} + +/* Consume the next token, issuing an error if it is not of + kind TOK_ID_A or TOK_ID_B. + Return which kind it was. */ + +result> +parser::require_one_of (enum token_id tok_id_a, enum token_id tok_id_b) +{ + const token *tok = m_lexer.peek (); + if ((tok->id != tok_id_a) + && (tok->id != tok_id_b)) + { + if (tok->id == TOK_ERROR) + return error_at (tok->range, "expected %s or %s; got bad token: %s", + token_id_name[tok_id_a], token_id_name[tok_id_b], + tok->u.string); + else + return error_at (tok->range, "expected %s or %s; got %s", + token_id_name[tok_id_a], token_id_name[tok_id_b], + token_id_name[tok->id]); + } + enum token_id id = tok->id; + m_lexer.consume (); + return result> (id); +} + +/* Genarate a parsing error. */ + +std::unique_ptr +parser::error_at (const location_map::range &r, const char *fmt, ...) +{ + va_list ap; + va_start (ap, fmt); + char *formatted_msg = xvasprintf (fmt, ap); + va_end (ap); + + return ::make_unique (r, formatted_msg); +} + +/* Record that JV has range R within the input file. */ + +void +parser::maybe_record_range (json::value *jv, const location_map::range &r) +{ + if (m_loc_map) + m_loc_map->record_range_for_value (jv, r); +} + +/* Record that JV has range START to END within the input file. */ + +void +parser::maybe_record_range (json::value *jv, + const location_map::point &start, + const location_map::point &end) +{ + if (m_loc_map) + { + location_map::range r; + r.m_start = start; + r.m_end = end; + m_loc_map->record_range_for_value (jv, r); + } +} + +/* Attempt to parse the UTF-8 encoded buffer at UTF8_BUF + of the given LENGTH. + If ALLOW_COMMENTS is true, then allow C and C++ style-comments in the + buffer, as an extension to JSON, otherwise forbid them. + If successful, return an json::value in the result. + if there was a problem, return a json::error in the result. + If OUT_LOC_MAP is non-NULL, notify *OUT_LOC_MAP about + source locations of nodes seen during parsing. */ + +parser_result_t +json::parse_utf8_string (size_t length, + const char *utf8_buf, + bool allow_comments, + location_map *out_loc_map) +{ + parser p (out_loc_map, allow_comments); + if (auto err = p.add_utf8 (length, utf8_buf)) + return parser_result_t (std::move (err)); + parser_result_t r = p.parse_value (0); + if (r.m_err) + return r; + if (auto err = p.require_eof ()) + return parser_result_t (std::move (err)); + return r; +} + +/* Attempt to parse the nil-terminated UTF-8 encoded buffer at + UTF8_BUF. + If ALLOW_COMMENTS is true, then allow C and C++ style-comments in the + buffer, as an extension to JSON, otherwise forbid them. + If successful, return a non-NULL json::value *. + if there was a problem, return NULL and write an error + message to err_out, which must be deleted by the caller. + If OUT_LOC_MAP is non-NULL, notify *OUT_LOC_MAP about + source locations of nodes seen during parsing. */ + +json::parser_result_t +json::parse_utf8_string (const char *utf8, + bool allow_comments, + location_map *out_loc_map) +{ + return parse_utf8_string (strlen (utf8), utf8, allow_comments, + out_loc_map); +} + + +#if CHECKING_P + +namespace selftest { + +/* Selftests. */ + +#define ASSERT_PRINT_EQ(JV, FORMATTED, EXPECTED_JSON) \ + assert_print_eq (SELFTEST_LOCATION, JV, FORMATTED, EXPECTED_JSON) + +/* Implementation detail of ASSERT_RANGE_EQ. */ + +static void +assert_point_eq (const location &loc, + const location_map::point &actual_point, + size_t exp_unichar_idx, int exp_line, int exp_column) +{ + ASSERT_EQ_AT (loc, actual_point.m_unichar_idx, exp_unichar_idx); + ASSERT_EQ_AT (loc, actual_point.m_line, exp_line); + ASSERT_EQ_AT (loc, actual_point.m_column, exp_column); +} + +/* Implementation detail of ASSERT_RANGE_EQ. */ + +static void +assert_range_eq (const location &loc, + const location_map::range &actual_range, + /* Expected location. */ + size_t start_unichar_idx, int start_line, int start_column, + size_t end_unichar_idx, int end_line, int end_column) +{ + assert_point_eq (loc, actual_range.m_start, + start_unichar_idx, start_line, start_column); + assert_point_eq (loc, actual_range.m_end, + end_unichar_idx, end_line, end_column); +} + +/* Assert that ACTUAL_RANGE starts at + (START_UNICHAR_IDX, START_LINE, START_COLUMN) + and ends at (END_UNICHAR_IDX, END_LINE, END_COLUMN). */ + +#define ASSERT_RANGE_EQ(ACTUAL_RANGE, \ + START_UNICHAR_IDX, START_LINE, START_COLUMN, \ + END_UNICHAR_IDX, END_LINE, END_COLUMN) \ + assert_range_eq ((SELFTEST_LOCATION), (ACTUAL_RANGE), \ + (START_UNICHAR_IDX), (START_LINE), (START_COLUMN), \ + (END_UNICHAR_IDX), (END_LINE), (END_COLUMN)) + +/* Implementation detail of ASSERT_ERR_EQ. */ + +static void +assert_err_eq (const location &loc, + const json::error *actual_err, + /* Expected location. */ + size_t start_unichar_idx, int start_line, int start_column, + size_t end_unichar_idx, int end_line, int end_column, + const char *expected_msg) +{ + ASSERT_TRUE_AT (loc, actual_err); + const location_map::range &actual_range = actual_err->get_range (); + ASSERT_EQ_AT (loc, actual_range.m_start.m_unichar_idx, start_unichar_idx); + ASSERT_EQ_AT (loc, actual_range.m_start.m_line, start_line); + ASSERT_EQ_AT (loc, actual_range.m_start.m_column, start_column); + ASSERT_EQ_AT (loc, actual_range.m_end.m_unichar_idx, end_unichar_idx); + ASSERT_EQ_AT (loc, actual_range.m_end.m_line, end_line); + ASSERT_EQ_AT (loc, actual_range.m_end.m_column, end_column); + ASSERT_STREQ_AT (loc, actual_err->get_msg (), expected_msg); +} + +/* Assert that ACTUAL_ERR is a non-NULL json::error *, + with message EXPECTED_MSG, and that its location starts + at (START_UNICHAR_IDX, START_LINE, START_COLUMN) + and ends at (END_UNICHAR_IDX, END_LINE, END_COLUMN). */ + +#define ASSERT_ERR_EQ(ACTUAL_ERR, \ + START_UNICHAR_IDX, START_LINE, START_COLUMN, \ + END_UNICHAR_IDX, END_LINE, END_COLUMN, \ + EXPECTED_MSG) \ + assert_err_eq ((SELFTEST_LOCATION), (ACTUAL_ERR), \ + (START_UNICHAR_IDX), (START_LINE), (START_COLUMN), \ + (END_UNICHAR_IDX), (END_LINE), (END_COLUMN), \ + (EXPECTED_MSG)) + +/* Verify that the JSON lexer works as expected. */ + +static void +test_lexer () +{ + lexer l (false); + const char *str + /* 0 1 2 3 4 . */ + /* 01234567890123456789012345678901234567890123456789. */ + = (" 1066 -1 \n" + " -273.15 1e6\n" + " [ ] null true false { } \"foo\" \n"); + auto err = l.add_utf8 (strlen (str), str); + ASSERT_EQ (err, nullptr); + + /* Line 1. */ + { + const size_t line_offset = 0; + + /* Expect token: "1066" in columns 4-7. */ + { + const token *tok = l.peek (); + ASSERT_EQ (tok->id, TOK_INTEGER_NUMBER); + ASSERT_EQ (tok->u.integer_number, 1066); + ASSERT_RANGE_EQ (tok->range, + line_offset + 4, 1, 4, + line_offset + 7, 1, 7); + l.consume (); + } + /* Expect token: "-1" in columns 11-12. */ + { + const token *tok = l.peek (); + ASSERT_EQ (tok->id, TOK_INTEGER_NUMBER); + ASSERT_EQ (tok->u.integer_number, -1); + ASSERT_RANGE_EQ (tok->range, + line_offset + 11, 1, 11, + line_offset + 12, 1, 12); + l.consume (); + } + } + + /* Line 2. */ + { + const size_t line_offset = 16; + + /* Expect token: "-273.15" in columns 4-10. */ + { + const token *tok = l.peek (); + ASSERT_EQ (tok->id, TOK_FLOAT_NUMBER); + ASSERT_EQ (int(tok->u.float_number), int(-273.15)); + ASSERT_RANGE_EQ (tok->range, + line_offset + 4, 2, 4, + line_offset + 10, 2, 10); + l.consume (); + } + /* Expect token: "1e6" in columns 12-14. */ + { + const token *tok = l.peek (); + ASSERT_EQ (tok->id, TOK_INTEGER_NUMBER); + ASSERT_EQ (tok->u.integer_number, 1000000); + ASSERT_RANGE_EQ (tok->range, + line_offset + 12, 2, 12, + line_offset + 14, 2, 14); + l.consume (); + } + } + + /* Line 3. */ + { + const size_t line_offset = 32; + + /* Expect token: "[". */ + { + const token *tok = l.peek (); + ASSERT_EQ (tok->id, TOK_OPEN_SQUARE); + ASSERT_RANGE_EQ (tok->range, + line_offset + 2, 3, 2, + line_offset + 2, 3, 2); + l.consume (); + } + /* Expect token: "]". */ + { + const token *tok = l.peek (); + ASSERT_EQ (tok->id, TOK_CLOSE_SQUARE); + ASSERT_RANGE_EQ (tok->range, + line_offset + 6, 3, 6, + line_offset + 6, 3, 6); + l.consume (); + } + /* Expect token: "null". */ + { + const token *tok = l.peek (); + ASSERT_EQ (tok->id, TOK_NULL); + ASSERT_RANGE_EQ (tok->range, + line_offset + 8, 3, 8, + line_offset + 11, 3, 11); + l.consume (); + } + /* Expect token: "true". */ + { + const token *tok = l.peek (); + ASSERT_EQ (tok->id, TOK_TRUE); + ASSERT_RANGE_EQ (tok->range, + line_offset + 15, 3, 15, + line_offset + 18, 3, 18); + l.consume (); + } + /* Expect token: "false". */ + { + const token *tok = l.peek (); + ASSERT_EQ (tok->id, TOK_FALSE); + ASSERT_RANGE_EQ (tok->range, + line_offset + 21, 3, 21, + line_offset + 25, 3, 25); + l.consume (); + } + /* Expect token: "{". */ + { + const token *tok = l.peek (); + ASSERT_EQ (tok->id, TOK_OPEN_CURLY); + ASSERT_RANGE_EQ (tok->range, + line_offset + 28, 3, 28, + line_offset + 28, 3, 28); + l.consume (); + } + /* Expect token: "}". */ + { + const token *tok = l.peek (); + ASSERT_EQ (tok->id, TOK_CLOSE_CURLY); + ASSERT_RANGE_EQ (tok->range, + line_offset + 31, 3, 31, + line_offset + 31, 3, 31); + l.consume (); + } + /* Expect token: "\"foo\"". */ + { + const token *tok = l.peek (); + ASSERT_EQ (tok->id, TOK_STRING); + ASSERT_RANGE_EQ (tok->range, + line_offset + 34, 3, 34, + line_offset + 38, 3, 38); + l.consume (); + } + } +} + +/* Verify that the JSON lexer complains about single-line comments + when comments are disabled. */ + +static void +test_lexing_unsupported_single_line_comment () +{ + lexer l (false); + const char *str + /* 0 1 2 3 4 . */ + /* 01234567890123456789012345678901234567890123456789. */ + = (" 1066 // Hello world\n"); + auto err = l.add_utf8 (strlen (str), str); + ASSERT_EQ (err, nullptr); + + /* Line 1. */ + { + const size_t line_offset = 0; + const int line_1 = 1; + + /* Expect token: "1066" in columns 4-7. */ + { + const token *tok = l.peek (); + ASSERT_EQ (tok->id, TOK_INTEGER_NUMBER); + ASSERT_EQ (tok->u.integer_number, 1066); + ASSERT_RANGE_EQ (tok->range, + line_offset + 4, line_1, 4, + line_offset + 7, line_1, 7); + l.consume (); + } + + /* Expect error. */ + { + const token *tok = l.peek (); + ASSERT_EQ (tok->id, TOK_ERROR); + ASSERT_STREQ (tok->u.string, "unexpected character: '/'"); + ASSERT_RANGE_EQ (tok->range, + line_offset + 11, line_1, 11, + line_offset + 11, line_1, 11); + l.consume (); + } + } +} + +/* Verify that the JSON lexer complains about multiline comments + when comments are disabled. */ + +static void +test_lexing_unsupported_multiline_comment () +{ + lexer l (false); + const char *str + /* 0 1 2 3 4 . */ + /* 01234567890123456789012345678901234567890123456789. */ + = (" 1066 /* Hello world\n" + " continuation of comment\n" + " end of comment */ 42\n"); + auto err = l.add_utf8 (strlen (str), str); + ASSERT_EQ (err, nullptr); + + /* Line 1. */ + { + const size_t line_offset = 0; + const int line_1 = 1; + + /* Expect token: "1066" in line 1, columns 4-7. */ + { + const token *tok = l.peek (); + ASSERT_EQ (tok->id, TOK_INTEGER_NUMBER); + ASSERT_EQ (tok->u.integer_number, 1066); + ASSERT_RANGE_EQ (tok->range, + line_offset + 4, line_1, 4, + line_offset + 7, line_1, 7); + l.consume (); + } + + /* Expect error. */ + { + const token *tok = l.peek (); + ASSERT_EQ (tok->id, TOK_ERROR); + ASSERT_STREQ (tok->u.string, "unexpected character: '/'"); + ASSERT_RANGE_EQ (tok->range, + line_offset + 11, line_1, 11, + line_offset + 11, line_1, 11); + l.consume (); + } + } +} + +/* Verify that the JSON lexer handles single-line comments + when comments are enabled. */ + +static void +test_lexing_supported_single_line_comment () +{ + lexer l (true); + const char *str + /* 0 1 2 3 4 . */ + /* 01234567890123456789012345678901234567890123456789. */ + = (" 1066 // Hello world\n" + " 42 // etc\n"); + auto err = l.add_utf8 (strlen (str), str); + ASSERT_EQ (err, nullptr); + + const size_t line_1_offset = 0; + const size_t line_2_offset = 26; + const size_t line_3_offset = line_2_offset + 17; + + /* Expect token: "1066" in line 1, columns 4-7. */ + { + const int line_1 = 1; + const token *tok = l.peek (); + ASSERT_EQ (tok->id, TOK_INTEGER_NUMBER); + ASSERT_EQ (tok->u.integer_number, 1066); + ASSERT_RANGE_EQ (tok->range, + line_1_offset + 4, line_1, 4, + line_1_offset + 7, line_1, 7); + l.consume (); + } + + /* Expect token: "42" in line 2, columns 5-6. */ + { + const int line_2 = 2; + const token *tok = l.peek (); + ASSERT_EQ (tok->id, TOK_INTEGER_NUMBER); + ASSERT_EQ (tok->u.integer_number, 42); + ASSERT_RANGE_EQ (tok->range, + line_2_offset + 5, line_2, 5, + line_2_offset + 6, line_2, 6); + l.consume (); + } + + /* Expect EOF. */ + { + const int line_3 = 3; + const token *tok = l.peek (); + ASSERT_EQ (tok->id, TOK_EOF); + ASSERT_RANGE_EQ (tok->range, + line_3_offset + 0, line_3, 0, + line_3_offset + 0, line_3, 0); + l.consume (); + } +} + +/* Verify that the JSON lexer handles multiline comments + when comments are enabled. */ + +static void +test_lexing_supported_multiline_comment () +{ + lexer l (true); + const char *str + /* 0 1 2 3 4 . */ + /* 01234567890123456789012345678901234567890123456789. */ + = (" 1066 /* Hello world\n" + " continuation of comment\n" + " end of comment */ 42\n"); + auto err = l.add_utf8 (strlen (str), str); + ASSERT_EQ (err, nullptr); + + const size_t line_1_offset = 0; + const size_t line_2_offset = 26; + const size_t line_3_offset = line_2_offset + 25; + const size_t line_4_offset = line_3_offset + 23; + + /* Expect token: "1066" in line 1, columns 4-7. */ + { + const int line_1 = 1; + const token *tok = l.peek (); + ASSERT_EQ (tok->id, TOK_INTEGER_NUMBER); + ASSERT_EQ (tok->u.integer_number, 1066); + ASSERT_RANGE_EQ (tok->range, + line_1_offset + 4, line_1, 4, + line_1_offset + 7, line_1, 7); + l.consume (); + } + + /* Expect token: "42" in line 3, columns 20-21. */ + { + const int line_3 = 3; + const token *tok = l.peek (); + ASSERT_EQ (tok->id, TOK_INTEGER_NUMBER); + ASSERT_EQ (tok->u.integer_number, 42); + ASSERT_RANGE_EQ (tok->range, + line_3_offset + 20, line_3, 20, + line_3_offset + 21, line_3, 21); + l.consume (); + } + + /* Expect EOF. */ + { + const int line_4 = 4; + const token *tok = l.peek (); + ASSERT_EQ (tok->id, TOK_EOF); + ASSERT_RANGE_EQ (tok->range, + line_4_offset + 0, line_4, 0, + line_4_offset + 0, line_4, 0); + l.consume (); + } +} + +/* Helper class for writing JSON parsing testcases. + Attempts to parse a string in ctor, and captures the result (either + a json::value or a json::error), and a location map. */ + +struct parser_testcase +{ +public: + parser_testcase (const char *utf8_string, bool allow_comments = false) + : m_loc_map (), + m_result (parse_utf8_string (utf8_string, allow_comments, &m_loc_map)) + { + } + + const json::value *get_value () const { return m_result.m_val.get (); } + const json::error *get_error () const { return m_result.m_err.get (); } + + const location_map::range * + get_range_for_value (const json::value *jv) const + { + return m_loc_map.get_range_for_value (jv); + } + +private: + /* Concrete implementation of location_map for use in + JSON parsing selftests. */ + class test_location_map : public location_map + { + public: + void record_range_for_value (json::value *jv, const range &r) final override + { + m_map.put (jv, r); + } + + range *get_range_for_value (const json::value *jv) const + { + return const_cast &> (m_map) + .get (jv); + } + + private: + hash_map m_map; + }; + + test_location_map m_loc_map; + json::parser_result_t m_result; +}; + +/* Verify that parse_utf8_string works as expected. */ + +static void +test_parse_string () +{ + const int line_1 = 1; + + { + parser_testcase tc ("\"foo\""); + ASSERT_EQ (tc.get_error (), nullptr); + const json::value *jv = tc.get_value (); + ASSERT_EQ (jv->get_kind (), JSON_STRING); + ASSERT_STREQ (as_a (jv)->get_string (), "foo"); + ASSERT_PRINT_EQ (*jv, true, "\"foo\""); + auto range = tc.get_range_for_value (jv); + ASSERT_TRUE (range); + ASSERT_RANGE_EQ (*range, + 0, line_1, 0, + 4, line_1, 4); + } + + { + const char *contains_quotes = "\"before \\\"quoted\\\" after\""; + parser_testcase tc (contains_quotes); + ASSERT_EQ (tc.get_error (), nullptr); + const json::value *jv = tc.get_value (); + ASSERT_EQ (jv->get_kind (), JSON_STRING); + ASSERT_STREQ (as_a (jv)->get_string (), + "before \"quoted\" after"); + ASSERT_PRINT_EQ (*jv, true, contains_quotes); + auto range = tc.get_range_for_value (jv); + ASSERT_TRUE (range); + ASSERT_RANGE_EQ (*range, + 0, line_1, 0, + 24, line_1, 24); + } + + /* Test of non-ASCII input. This string is the Japanese word "mojibake", + written as C octal-escaped UTF-8. */ + const char *mojibake = (/* Opening quote. */ + "\"" + /* U+6587 CJK UNIFIED IDEOGRAPH-6587 + UTF-8: 0xE6 0x96 0x87 + C octal escaped UTF-8: \346\226\207. */ + "\346\226\207" + /* U+5B57 CJK UNIFIED IDEOGRAPH-5B57 + UTF-8: 0xE5 0xAD 0x97 + C octal escaped UTF-8: \345\255\227. */ + "\345\255\227" + /* U+5316 CJK UNIFIED IDEOGRAPH-5316 + UTF-8: 0xE5 0x8C 0x96 + C octal escaped UTF-8: \345\214\226. */ + "\345\214\226" + /* U+3051 HIRAGANA LETTER KE + UTF-8: 0xE3 0x81 0x91 + C octal escaped UTF-8: \343\201\221. */ + "\343\201\221" + /* Closing quote. */ + "\""); + { + parser_testcase tc (mojibake); + ASSERT_EQ (tc.get_error (), nullptr); + const json::value *jv = tc.get_value (); + ASSERT_EQ (jv->get_kind (), JSON_STRING); + /* Result of get_string should be UTF-8 encoded, without quotes. */ + ASSERT_STREQ (as_a (jv)->get_string (), + "\346\226\207" "\345\255\227" "\345\214\226" "\343\201\221"); + /* Result of dump should be UTF-8 encoded, with quotes. */ + ASSERT_PRINT_EQ (*jv, false, mojibake); + auto range = tc.get_range_for_value (jv); + ASSERT_TRUE (range); + ASSERT_RANGE_EQ (*range, + 0, line_1, 0, + 5, line_1, 5); + } + + /* Test of \u-escaped unicode. This is "mojibake" again, as above. */ + { + const char *escaped_unicode = "\"\\u6587\\u5b57\\u5316\\u3051\""; + parser_testcase tc (escaped_unicode); + ASSERT_EQ (tc.get_error (), nullptr); + const json::value *jv = tc.get_value (); + ASSERT_EQ (jv->get_kind (), JSON_STRING); + /* Result of get_string should be UTF-8 encoded, without quotes. */ + ASSERT_STREQ (as_a (jv)->get_string (), + "\346\226\207" "\345\255\227" "\345\214\226" "\343\201\221"); + /* Result of dump should be UTF-8 encoded, with quotes. */ + ASSERT_PRINT_EQ (*jv, false, mojibake); + auto range = tc.get_range_for_value (jv); + ASSERT_TRUE (range); + ASSERT_RANGE_EQ (*range, + 0, line_1, 0, + 25, line_1, 25); + } +} + +/* Verify that we can parse various kinds of JSON numbers. */ + +static void +test_parse_number () +{ + const int line_1 = 1; + + { + parser_testcase tc ("42"); + ASSERT_EQ (tc.get_error (), nullptr); + const json::value *jv = tc.get_value (); + ASSERT_EQ (jv->get_kind (), JSON_INTEGER); + ASSERT_EQ (as_a (jv)->get (), 42.0); + ASSERT_PRINT_EQ (*jv, true, "42"); + auto range = tc.get_range_for_value (jv); + ASSERT_TRUE (range); + ASSERT_RANGE_EQ (*range, + 0, line_1, 0, + 1, line_1, 1); + } + + /* Negative number. */ + { + parser_testcase tc ("-17"); + ASSERT_EQ (tc.get_error (), nullptr); + const json::value *jv = tc.get_value (); + ASSERT_EQ (jv->get_kind (), JSON_INTEGER); + ASSERT_EQ (as_a (jv)->get (), -17.0); + ASSERT_PRINT_EQ (*jv, true, "-17"); + auto range = tc.get_range_for_value (jv); + ASSERT_TRUE (range); + ASSERT_RANGE_EQ (*range, + 0, line_1, 0, + 2, line_1, 2); + } + + /* Decimal. */ + { + parser_testcase tc ("3.141"); + ASSERT_EQ (tc.get_error (), nullptr); + const json::value *jv = tc.get_value (); + ASSERT_EQ (JSON_FLOAT, jv->get_kind ()); + ASSERT_EQ (3.141, ((const json::float_number *)jv)->get ()); + ASSERT_PRINT_EQ (*jv, true, "3.141"); + auto range = tc.get_range_for_value (jv); + ASSERT_TRUE (range); + ASSERT_RANGE_EQ (*range, + 0, line_1, 0, + 4, line_1, 4); + } + + /* Exponents. */ + { + { + parser_testcase tc ("3.141e+0"); + ASSERT_EQ (tc.get_error (), nullptr); + const json::value *jv = tc.get_value (); + ASSERT_EQ (jv->get_kind (), JSON_FLOAT); + ASSERT_EQ (as_a (jv)->get (), 3.141); + ASSERT_PRINT_EQ (*jv, true, "3.141"); + auto range = tc.get_range_for_value (jv); + ASSERT_TRUE (range); + ASSERT_RANGE_EQ (*range, + 0, line_1, 0, + 7, line_1, 7); + } + { + parser_testcase tc ("42e2"); + ASSERT_EQ (tc.get_error (), nullptr); + const json::value *jv = tc.get_value (); + ASSERT_EQ (jv->get_kind (), JSON_INTEGER); + ASSERT_EQ (as_a (jv)->get (), 4200); + ASSERT_PRINT_EQ (*jv, true, "4200"); + auto range = tc.get_range_for_value (jv); + ASSERT_TRUE (range); + ASSERT_RANGE_EQ (*range, + 0, line_1, 0, + 3, line_1, 3); + } + { + parser_testcase tc ("42e-1"); + ASSERT_EQ (tc.get_error (), nullptr); + const json::value *jv = tc.get_value (); + ASSERT_EQ (jv->get_kind (), JSON_FLOAT); + ASSERT_EQ (as_a (jv)->get (), 4.2); + ASSERT_PRINT_EQ (*jv, true, "4.2"); + auto range = tc.get_range_for_value (jv); + ASSERT_TRUE (range); + ASSERT_RANGE_EQ (*range, + 0, line_1, 0, + 4, line_1, 4); + } + } +} + +/* Verify that JSON array parsing works. */ + +static void +test_parse_array () +{ + const int line_1 = 1; + + parser_testcase tc ("[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]"); + ASSERT_EQ (tc.get_error (), nullptr); + const json::value *jv = tc.get_value (); + ASSERT_EQ (jv->get_kind (), JSON_ARRAY); + const json::array *arr = as_a (jv); + ASSERT_EQ (arr->length (), 10); + auto range = tc.get_range_for_value (jv); + ASSERT_TRUE (range); + ASSERT_RANGE_EQ (*range, + 0, line_1, 0, + 29, line_1, 29); + for (int i = 0; i < 10; i++) + { + json::value *element = arr->get (i); + ASSERT_EQ (element->get_kind (), JSON_INTEGER); + ASSERT_EQ (as_a (element)->get (), i); + range = tc.get_range_for_value (element); + ASSERT_TRUE (range); + const int offset = 1 + (i * 3); + ASSERT_RANGE_EQ (*range, + offset, line_1, offset, + offset, line_1, offset); + } + ASSERT_PRINT_EQ (*jv, false, "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]"); +} + +/* Verify that JSON object parsing works. */ + +static void +test_parse_object () +{ + const int line_1 = 1; + std::unique_ptr err; + /* 0 1 2 3 . */ + /* 01 2345 678 9012 345 6789 0123456789012. */ + parser_testcase tc ("{\"foo\": \"bar\", \"baz\": [42, null]}"); + + ASSERT_EQ (tc.get_error (), nullptr); + const json::value *jv = tc.get_value (); + ASSERT_NE (jv, nullptr); + ASSERT_EQ (jv->get_kind (), JSON_OBJECT); + auto range = tc.get_range_for_value (jv); + ASSERT_TRUE (range); + ASSERT_RANGE_EQ (*range, + 0, line_1, 0, + 32, line_1, 32); + const json::object *jo = static_cast (jv); + + json::value *foo_value = jo->get ("foo"); + ASSERT_NE (foo_value, nullptr); + ASSERT_EQ (foo_value->get_kind (), JSON_STRING); + ASSERT_STREQ (as_a (foo_value)->get_string (), "bar"); + range = tc.get_range_for_value (foo_value); + ASSERT_TRUE (range); + ASSERT_RANGE_EQ (*range, + 8, line_1, 8, + 12, line_1, 12); + + json::value *baz_value = jo->get ("baz"); + ASSERT_NE (baz_value, nullptr); + ASSERT_EQ (baz_value->get_kind (), JSON_ARRAY); + range = tc.get_range_for_value (baz_value); + ASSERT_TRUE (range); + ASSERT_RANGE_EQ (*range, + 22, line_1, 22, + 31, line_1, 31); + + json::array *baz_array = as_a (baz_value); + ASSERT_EQ (baz_array->length (), 2); + + json::value *element0 = baz_array->get (0); + ASSERT_EQ (as_a (element0)->get (), 42); + range = tc.get_range_for_value (element0); + ASSERT_TRUE (range); + ASSERT_RANGE_EQ (*range, + 23, line_1, 23, + 24, line_1, 24); + + json::value *element1 = baz_array->get (1); + ASSERT_EQ (element1->get_kind (), JSON_NULL); + range = tc.get_range_for_value (element1); + ASSERT_TRUE (range); + ASSERT_RANGE_EQ (*range, + 27, line_1, 27, + 30, line_1, 30); +} + +/* Verify that the JSON literals "true", "false" and "null" are parsed + correctly. */ + +static void +test_parse_literals () +{ + const int line_1 = 1; + { + parser_testcase tc ("true"); + ASSERT_EQ (tc.get_error (), nullptr); + const json::value *jv = tc.get_value (); + ASSERT_NE (jv, nullptr); + ASSERT_EQ (jv->get_kind (), JSON_TRUE); + ASSERT_PRINT_EQ (*jv, false, "true"); + auto range = tc.get_range_for_value (jv); + ASSERT_TRUE (range); + ASSERT_RANGE_EQ (*range, + 0, line_1, 0, + 3, line_1, 3); + } + + { + parser_testcase tc ("false"); + ASSERT_EQ (tc.get_error (), nullptr); + const json::value *jv = tc.get_value (); + ASSERT_NE (jv, nullptr); + ASSERT_EQ (jv->get_kind (), JSON_FALSE); + ASSERT_PRINT_EQ (*jv, false, "false"); + auto range = tc.get_range_for_value (jv); + ASSERT_TRUE (range); + ASSERT_RANGE_EQ (*range, + 0, line_1, 0, + 4, line_1, 4); + } + + { + parser_testcase tc ("null"); + ASSERT_EQ (tc.get_error (), nullptr); + const json::value *jv = tc.get_value (); + ASSERT_NE (jv, nullptr); + ASSERT_EQ (jv->get_kind (), JSON_NULL); + ASSERT_PRINT_EQ (*jv, false, "null"); + auto range = tc.get_range_for_value (jv); + ASSERT_TRUE (range); + ASSERT_RANGE_EQ (*range, + 0, line_1, 0, + 3, line_1, 3); + } +} + +/* Verify that we can parse a simple JSON-RPC request. */ + +static void +test_parse_jsonrpc () +{ + std::unique_ptr err; + const char *request + /* 0 1 2 3 4. */ + /* 01 23456789 012 3456 789 0123456 789 012345678 90. */ + = ("{\"jsonrpc\": \"2.0\", \"method\": \"subtract\",\n" + /* 0 1 2 3 4. */ + /* 0 1234567 8901234567890 1234 56789012345678 90. */ + " \"params\": [42, 23], \"id\": 1}"); + const int line_1 = 1; + const int line_2 = 2; + const size_t line_2_offset = 41; + parser_testcase tc (request); + ASSERT_EQ (tc.get_error (), nullptr); + const json::value *jv = tc.get_value (); + ASSERT_NE (jv, nullptr); + auto range = tc.get_range_for_value (jv); + ASSERT_TRUE (range); + ASSERT_RANGE_EQ (*range, + 0, line_1, 0, + line_2_offset + 28, line_2, 28); +} + +/* Verify that we can parse an empty JSON object. */ + +static void +test_parse_empty_object () +{ + const int line_1 = 1; + std::unique_ptr err; + parser_testcase tc ("{}"); + ASSERT_EQ (tc.get_error (), nullptr); + const json::value *jv = tc.get_value (); + ASSERT_NE (jv, nullptr); + ASSERT_EQ (jv->get_kind (), JSON_OBJECT); + ASSERT_PRINT_EQ (*jv, true, "{}"); + auto range = tc.get_range_for_value (jv); + ASSERT_TRUE (range); + ASSERT_RANGE_EQ (*range, + 0, line_1, 0, + 1, line_1, 1); +} + +/* Verify that comment-parsing can be enabled or disabled. */ + +static void +test_parsing_comments () +{ + const char *str = ("// foo\n" + "/*...\n" + "...*/ 42 // bar\n" + "/* etc */\n"); + + /* Parsing with comment support disabled. */ + { + parser_testcase tc (str); + ASSERT_NE (tc.get_error (), nullptr); + ASSERT_STREQ (tc.get_error ()->get_msg (), + "invalid JSON token: unexpected character: '/'"); + ASSERT_EQ (tc.get_value (), nullptr); + } + + /* Parsing with comment support enabled. */ + { + parser_testcase tc (str, true); + ASSERT_EQ (tc.get_error (), nullptr); + const json::value *jv = tc.get_value (); + ASSERT_NE (jv, nullptr); + ASSERT_EQ (jv->get_kind (), JSON_INTEGER); + ASSERT_EQ (((const json::integer_number *)jv)->get (), 42); + } +} + +/* Verify that we can parse an empty JSON string. */ + +static void +test_error_empty_string () +{ + const int line_1 = 1; + parser_testcase tc (""); + ASSERT_ERR_EQ (tc.get_error (), + 0, line_1, 0, + 0, line_1, 0, + "expected a JSON value but got EOF"); + ASSERT_EQ (tc.get_value (), nullptr); +} + +/* Verify that JSON parsing gracefully handles an invalid token. */ + +static void +test_error_bad_token () +{ + const int line_1 = 1; + parser_testcase tc (" not valid "); + ASSERT_ERR_EQ (tc.get_error (), + 2, line_1, 2, + 2, line_1, 2, + "invalid JSON token: unexpected character: 'n'"); + ASSERT_EQ (tc.get_value (), nullptr); +} + +/* Verify that JSON parsing gracefully handles a missing comma + within an object. */ + +static void +test_error_object_with_missing_comma () +{ + const int line_1 = 1; + /* 0 1 2. */ + /* 01 2345 6789012 3456 7890. */ + const char *json = "{\"foo\" : 42 \"bar\""; + parser_testcase tc (json); + ASSERT_ERR_EQ (tc.get_error (), + 12, line_1, 12, + 16, line_1, 16, + "expected ',' or '}'; got string"); + ASSERT_EQ (tc.get_value (), nullptr); +} + +/* Verify that JSON parsing gracefully handles a missing comma + within an array. */ + +static void +test_error_array_with_missing_comma () +{ + const int line_1 = 1; + /* 01234567. */ + const char *json = "[0, 1 42]"; + parser_testcase tc (json); + ASSERT_ERR_EQ (tc.get_error (), + 6, line_1, 6, + 7, line_1, 7, + "expected ',' or ']'; got number"); + ASSERT_EQ (tc.get_value (), nullptr); +} + +/* Run all of the selftests within this file. */ + +void +json_parser_cc_tests () +{ + test_lexer (); + test_lexing_unsupported_single_line_comment (); + test_lexing_unsupported_multiline_comment (); + test_lexing_supported_single_line_comment (); + test_lexing_supported_multiline_comment (); + test_parse_string (); + test_parse_number (); + test_parse_array (); + test_parse_object (); + test_parse_literals (); + test_parse_jsonrpc (); + test_parse_empty_object (); + test_parsing_comments (); + test_error_empty_string (); + test_error_bad_token (); + test_error_object_with_missing_comma (); + test_error_array_with_missing_comma (); +} + +} // namespace selftest + +#endif /* #if CHECKING_P */ diff --git a/gcc/json-parsing.h b/gcc/json-parsing.h new file mode 100644 index 000000000000..3dbbf22402e4 --- /dev/null +++ b/gcc/json-parsing.h @@ -0,0 +1,113 @@ +/* JSON parsing + Copyright (C) 2017-2022 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 +. */ + +#ifndef GCC_JSON_PARSING_H +#define GCC_JSON_PARSING_H + +#include "json.h" + +namespace json +{ + +/* Declarations for parsing JSON to a json::value * tree. */ + +/* Abstract base class for recording what the locations of JSON values + were as they parsed. */ + +class location_map +{ +public: + /* A point within the JSON input file. */ + struct point + { + size_t m_unichar_idx; /* zero-based. */ + int m_line; /* one-based. */ + int m_column; /* zero-based unichar count. */ + }; + + /* A range of points within the JSON input file. + Both endpoints are part of the range. */ + struct range + { + point m_start; + point m_end; + }; + + virtual ~location_map () {} + virtual void record_range_for_value (json::value *jv, const range &r) = 0; + virtual void on_finished_parsing () {} +}; + +/* Class for recording an error within a JSON file. */ + +class error +{ +public: + error (const location_map::range &r, char *msg) + : m_range (r), m_msg (msg) + { + } + ~error () + { + free (m_msg); + } + + const location_map::range &get_range () const { return m_range; } + const char *get_msg () const { return m_msg; } + +private: + location_map::range m_range; + char *m_msg; +}; + +/* Class for the result of an operation: either a value or an error + (or both null for the case of "successful nullptr"). + The types must be default-constructible. */ + +template +struct result +{ + result (ValueType val) : m_val (std::move (val)), m_err () {} + result (ErrorType err) : m_val (), m_err (std::move (err)) {} + + ValueType m_val; + ErrorType m_err; +}; + +/* Typedef for the result of parsing JSON: ownership of either a + json::value * or of a json::error *. */ +typedef result, + std::unique_ptr> parser_result_t; + +/* Functions for parsing JSON buffers. */ + +extern parser_result_t +parse_utf8_string (size_t length, + const char *utf8_buf, + bool allow_comments, + location_map *out_loc_map); +extern parser_result_t +parse_utf8_string (const char *utf8, + bool allow_comments, + location_map *out_loc_map); + +} // namespace json + +#endif /* GCC_JSON_PARSING_H */ diff --git a/gcc/json.cc b/gcc/json.cc index 275ef486faf1..4e4d43a7d100 100644 --- a/gcc/json.cc +++ b/gcc/json.cc @@ -383,7 +383,7 @@ namespace selftest { /* Verify that JV->print () prints EXPECTED_JSON. */ -static void +void assert_print_eq (const location &loc, const json::value &jv, bool formatted, diff --git a/gcc/json.h b/gcc/json.h index 21f71fe1c4ab..a761384af775 100644 --- a/gcc/json.h +++ b/gcc/json.h @@ -36,8 +36,8 @@ along with GCC; see the file COPYING3. If not see and http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf and https://tools.ietf.org/html/rfc7159 - Supports creating a DOM-like tree of json::value *, and then dumping - json::value * to text. */ + Supports parsing text into a DOM-like tree of json::value *, directly + creating such trees, and dumping json::value * to text. */ /* TODO: `libcpp/mkdeps.cc` wants JSON writing support for p1689r5 output; extract this code and move to libiberty. */ @@ -175,6 +175,13 @@ class array : public value size_t size () const { return m_elements.length (); } value *operator[] (size_t i) const { return m_elements[i]; } + value **begin () { return m_elements.begin (); } + value **end () { return m_elements.end (); } + const value * const *begin () const { return m_elements.begin (); } + const value * const *end () const { return m_elements.end (); } + size_t length () const { return m_elements.length (); } + value *get (size_t idx) const { return m_elements[idx]; } + private: auto_vec m_elements; }; @@ -252,4 +259,115 @@ class literal : public value } // namespace json +template <> +template <> +inline bool +is_a_helper ::test (json::value *) +{ + return true; +} + +template <> +template <> +inline bool +is_a_helper ::test (const json::value *) +{ + return true; +} + +template <> +template <> +inline bool +is_a_helper ::test (json::value *jv) +{ + return jv->get_kind () == json::JSON_OBJECT; +} + +template <> +template <> +inline bool +is_a_helper ::test (const json::value *jv) +{ + return jv->get_kind () == json::JSON_OBJECT; +} + +template <> +template <> +inline bool +is_a_helper ::test (json::value *jv) +{ + return jv->get_kind () == json::JSON_ARRAY; +} + +template <> +template <> +inline bool +is_a_helper ::test (const json::value *jv) +{ + return jv->get_kind () == json::JSON_ARRAY; +} + +template <> +template <> +inline bool +is_a_helper ::test (json::value *jv) +{ + return jv->get_kind () == json::JSON_FLOAT; +} + +template <> +template <> +inline bool +is_a_helper ::test (const json::value *jv) +{ + return jv->get_kind () == json::JSON_FLOAT; +} + +template <> +template <> +inline bool +is_a_helper ::test (json::value *jv) +{ + return jv->get_kind () == json::JSON_INTEGER; +} + +template <> +template <> +inline bool +is_a_helper ::test (const json::value *jv) +{ + return jv->get_kind () == json::JSON_INTEGER; +} + +template <> +template <> +inline bool +is_a_helper ::test (json::value *jv) +{ + return jv->get_kind () == json::JSON_STRING; +} + +template <> +template <> +inline bool +is_a_helper ::test (const json::value *jv) +{ + return jv->get_kind () == json::JSON_STRING; +} + +#if CHECKING_P + +namespace selftest { + +class location; + +extern void assert_print_eq (const location &loc, + const json::value &jv, + bool formatted, + const char *expected_json); + +} // namespace selftest + +#endif /* #if CHECKING_P */ + #endif /* GCC_JSON_H */ diff --git a/gcc/selftest-run-tests.cc b/gcc/selftest-run-tests.cc index d6c88f864ba7..821f3fb7b25b 100644 --- a/gcc/selftest-run-tests.cc +++ b/gcc/selftest-run-tests.cc @@ -75,6 +75,7 @@ selftest::run_tests () opt_suggestions_cc_tests (); opts_cc_tests (); json_cc_tests (); + json_parser_cc_tests (); cgraph_cc_tests (); optinfo_emit_json_cc_tests (); ordered_hash_map_tests_cc_tests (); diff --git a/gcc/selftest.h b/gcc/selftest.h index 5afc9399c619..7a50d95df6b0 100644 --- a/gcc/selftest.h +++ b/gcc/selftest.h @@ -239,6 +239,7 @@ extern void hash_map_tests_cc_tests (); extern void hash_set_tests_cc_tests (); extern void input_cc_tests (); extern void json_cc_tests (); +extern void json_parser_cc_tests (); extern void optinfo_emit_json_cc_tests (); extern void opts_cc_tests (); extern void ordered_hash_map_tests_cc_tests (); From patchwork Thu Aug 15 18:11:56 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: David Malcolm X-Patchwork-Id: 1972877 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; dkim=pass (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=H4NvEtmN; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=gcc.gnu.org (client-ip=8.43.85.97; helo=server2.sourceware.org; envelope-from=gcc-patches-bounces~incoming=patchwork.ozlabs.org@gcc.gnu.org; receiver=patchwork.ozlabs.org) Received: from server2.sourceware.org (server2.sourceware.org [8.43.85.97]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (secp384r1) server-digest SHA384) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4WlCwg0vfxz1yYl for ; Fri, 16 Aug 2024 04:15:47 +1000 (AEST) Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 3C0303858428 for ; Thu, 15 Aug 2024 18:15:45 +0000 (GMT) X-Original-To: gcc-patches@gcc.gnu.org Delivered-To: gcc-patches@gcc.gnu.org Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by sourceware.org (Postfix) with ESMTP id F21EB3858428 for ; Thu, 15 Aug 2024 18:12:15 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org F21EB3858428 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 F21EB3858428 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=1723745545; cv=none; b=asB7BXGBAGEcJ0r+teHd3eEXVZk4WjYlVXAy92M8gfXCiL5MFrby4I3lNGYDkZtByNN+sVwJtLfjlA9BnVEF5g4TQkg2JFnX9Ad7fe4JzYvmp/hPM7JsxP27eQKvPJhiGzbCdOznJLRhVDlYzZV/MClg3THH4TeRZ4mX61+RHZA= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1723745545; c=relaxed/simple; bh=aoteVbwV3XWwXR6p1apeLWwbhEata+H4wRyXNHGsy88=; h=DKIM-Signature:From:To:Subject:Date:Message-Id:MIME-Version; b=rrwhWBjVSZIXxyQItufamPSd6uMi3MQH6XoWQpcpVz2Jh4NFh6XUfhEtyDdHy991QBb7vfZAKARAE9qfEWdSwWU52q+9vEovOLXKYaC73NsmnWchb5dQ3R2hxAncC46nf4IkyaI59rPAnw/49EPQ4iMBr8d8R5cKD43uElU3xpM= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1723745535; 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=GZMTwBom1TbYxuCmTVC/U/qsvidaiLG7sFYFoy0kH00=; b=H4NvEtmNT0AIXExl3Gi2GKezTMCiGP3ci6nN8IUuCo8HMaPa0JRLq3b3VsNbXk14hiNFwI yRHrol42wFn0EFIYFtBQ5yPMO2u2YDnQl+dSujJ3OVcOnTioaCHJiXphxo4OK5hY17NBoi 86DOF7dCo+XJVMJT1Rcrd1uwrdGVOc8= Received: from mx-prod-mc-05.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-172-DEbP812uNMWEZQjv-hr1_A-1; Thu, 15 Aug 2024 14:12:12 -0400 X-MC-Unique: DEbP812uNMWEZQjv-hr1_A-1 Received: from mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.17]) (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 mx-prod-mc-05.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 49B961955F3B for ; Thu, 15 Aug 2024 18:12:11 +0000 (UTC) Received: from t14s.localdomain.com (unknown [10.22.16.43]) by mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 6AB7D1955D44; Thu, 15 Aug 2024 18:12:09 +0000 (UTC) From: David Malcolm To: gcc-patches@gcc.gnu.org Cc: David Malcolm Subject: [PATCH 7/7] libdiagnostics: add a "sarif-replay" command-line tool [PR96032] Date: Thu, 15 Aug 2024 14:11:56 -0400 Message-Id: <20240815181156.1815075-8-dmalcolm@redhat.com> In-Reply-To: <20240815181156.1815075-1-dmalcolm@redhat.com> References: <20240815181156.1815075-1-dmalcolm@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.0 on 10.30.177.17 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com X-Spam-Status: No, score=-11.3 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, KAM_INFOUSMEBIZ, KAM_SHORT, RCVD_IN_DNSWL_NONE, RCVD_IN_MSPIKE_H4, 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 X-BeenThere: gcc-patches@gcc.gnu.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Gcc-patches mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: gcc-patches-bounces~incoming=patchwork.ozlabs.org@gcc.gnu.org This patch adds a new "sarif-replay" command-line tool for viewing .sarif files. It uses libdiagnostics to "replay" any diagnostics found in the .sarif files in text form as if they were GCC diagnostics. contrib/ChangeLog: PR other/96032 * regenerate-sarif-spec-index.py: New file. gcc/ChangeLog: PR other/96032 * Makefile.in (lang_checks): If libdiagnostics is enabled, add check-sarif-replay. (SARIF_REPLAY_OBJS): New. (ALL_HOST_OBJS): If libdiagnostics is enabled, add $(SARIF_REPLAY_OBJS). (sarif-replay): New. (install-libdiagnostics): Add sarif-replay to deps, and install it. * configure: Regenerate. * configure.ac (check_languages): If libdiagnostics is enabled, add check-sarif-replay. (LIBDIAGNOSTICS): If libdiagnostics is enabled, add sarif-replay. * doc/install.texi (--enable-libdiagnostics): Note that it also enables sarif-replay. * libsarifreplay.cc: New file. * libsarifreplay.h: New file. * sarif-replay.cc: New file. * sarif-spec-urls.def: New file. gcc/testsuite/ChangeLog: PR other/96032 * lib/gcc-dg.exp (gcc-dg-test-1): Add "replay-sarif". * lib/sarif-replay-dg.exp: New file. * lib/sarif-replay.exp: New file. * sarif-replay.dg/2.1.0-invalid/3.1-not-an-object.sarif: New test. * sarif-replay.dg/2.1.0-invalid/3.11.11-malformed-placeholder.sarif: New test. * sarif-replay.dg/2.1.0-invalid/3.11.11-missing-arguments-for-placeholders.sarif: New test. * sarif-replay.dg/2.1.0-invalid/3.11.11-not-enough-arguments-for-placeholders.sarif: New test. * sarif-replay.dg/2.1.0-invalid/3.13.2-no-version.sarif: New test. * sarif-replay.dg/2.1.0-invalid/3.13.2-version-not-a-string.sarif: New test. * sarif-replay.dg/2.1.0-invalid/3.13.4-bad-runs.sarif: New test. * sarif-replay.dg/2.1.0-invalid/3.13.4-no-runs.sarif: New test. * sarif-replay.dg/2.1.0-invalid/3.13.4-non-object-in-runs.sarif: New test. * sarif-replay.dg/2.1.0-invalid/3.27.10-bad-level.sarif: New test. * sarif-replay.dg/2.1.0-unhandled/3.27.10-none-level.sarif: New test. * sarif-replay.dg/2.1.0-valid/error-with-note.sarif: New test. * sarif-replay.dg/2.1.0-valid/escaped-braces.sarif: New test. * sarif-replay.dg/2.1.0-valid/null-runs.sarif: New test. * sarif-replay.dg/2.1.0-valid/signal-1.c.sarif: New test. * sarif-replay.dg/2.1.0-valid/spec-example-1.sarif: New test. * sarif-replay.dg/2.1.0-valid/spec-example-2.sarif: New test. * sarif-replay.dg/2.1.0-valid/spec-example-3.sarif: New test. * sarif-replay.dg/2.1.0-valid/spec-example-4.sarif: New test. * sarif-replay.dg/2.1.0-valid/tutorial-example.sarif: New test. * sarif-replay.dg/dg.exp: New script. * sarif-replay.dg/malformed-json/array-missing-comma.sarif: New test. * sarif-replay.dg/malformed-json/array-with-trailing-comma.sarif: New test. * sarif-replay.dg/malformed-json/bad-token.sarif: New test. * sarif-replay.dg/malformed-json/object-missing-comma.sarif: New test. * sarif-replay.dg/malformed-json/object-with-trailing-comma.sarif: New test. Signed-off-by: David Malcolm --- contrib/regenerate-sarif-spec-index.py | 60 + gcc/Makefile.in | 16 +- gcc/configure | 4 +- gcc/configure.ac | 4 +- gcc/doc/install.texi | 6 + gcc/libsarifreplay.cc | 1747 +++++++++++++++++ gcc/libsarifreplay.h | 59 + gcc/sarif-replay.cc | 239 +++ gcc/sarif-spec-urls.def | 496 +++++ gcc/testsuite/lib/gcc-dg.exp | 4 + gcc/testsuite/lib/sarif-replay-dg.exp | 90 + gcc/testsuite/lib/sarif-replay.exp | 204 ++ .../2.1.0-invalid/3.1-not-an-object.sarif | 6 + .../3.11.11-malformed-placeholder.sarif | 15 + ...1-missing-arguments-for-placeholders.sarif | 14 + ...ot-enough-arguments-for-placeholders.sarif | 14 + .../2.1.0-invalid/3.13.2-no-version.sarif | 6 + .../3.13.2-version-not-a-string.sarif | 6 + .../2.1.0-invalid/3.13.4-bad-runs.sarif | 7 + .../2.1.0-invalid/3.13.4-no-runs.sarif | 6 + .../3.13.4-non-object-in-runs.sarif | 7 + .../2.1.0-invalid/3.27.10-bad-level.sarif | 25 + .../2.1.0-unhandled/3.27.10-none-level.sarif | 25 + .../2.1.0-valid/error-with-note.sarif | 34 + .../2.1.0-valid/escaped-braces.sarif | 17 + .../2.1.0-valid/null-runs.sarif | 2 + .../2.1.0-valid/signal-1.c.sarif | 193 ++ .../2.1.0-valid/spec-example-1.sarif | 15 + .../2.1.0-valid/spec-example-2.sarif | 73 + .../2.1.0-valid/spec-example-3.sarif | 65 + .../2.1.0-valid/spec-example-4.sarif | 766 ++++++++ .../2.1.0-valid/tutorial-example.sarif | 117 ++ gcc/testsuite/sarif-replay.dg/dg.exp | 46 + .../malformed-json/array-missing-comma.sarif | 6 + .../array-with-trailing-comma.sarif | 6 + .../malformed-json/bad-token.sarif | 6 + .../malformed-json/object-missing-comma.sarif | 7 + .../object-with-trailing-comma.sarif | 6 + 38 files changed, 4412 insertions(+), 7 deletions(-) create mode 100644 contrib/regenerate-sarif-spec-index.py create mode 100644 gcc/libsarifreplay.cc create mode 100644 gcc/libsarifreplay.h create mode 100644 gcc/sarif-replay.cc create mode 100644 gcc/sarif-spec-urls.def create mode 100644 gcc/testsuite/lib/sarif-replay-dg.exp create mode 100644 gcc/testsuite/lib/sarif-replay.exp create mode 100644 gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.1-not-an-object.sarif create mode 100644 gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.11.11-malformed-placeholder.sarif create mode 100644 gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.11.11-missing-arguments-for-placeholders.sarif create mode 100644 gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.11.11-not-enough-arguments-for-placeholders.sarif create mode 100644 gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.13.2-no-version.sarif create mode 100644 gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.13.2-version-not-a-string.sarif create mode 100644 gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.13.4-bad-runs.sarif create mode 100644 gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.13.4-no-runs.sarif create mode 100644 gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.13.4-non-object-in-runs.sarif create mode 100644 gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.27.10-bad-level.sarif create mode 100644 gcc/testsuite/sarif-replay.dg/2.1.0-unhandled/3.27.10-none-level.sarif create mode 100644 gcc/testsuite/sarif-replay.dg/2.1.0-valid/error-with-note.sarif create mode 100644 gcc/testsuite/sarif-replay.dg/2.1.0-valid/escaped-braces.sarif create mode 100644 gcc/testsuite/sarif-replay.dg/2.1.0-valid/null-runs.sarif create mode 100644 gcc/testsuite/sarif-replay.dg/2.1.0-valid/signal-1.c.sarif create mode 100644 gcc/testsuite/sarif-replay.dg/2.1.0-valid/spec-example-1.sarif create mode 100644 gcc/testsuite/sarif-replay.dg/2.1.0-valid/spec-example-2.sarif create mode 100644 gcc/testsuite/sarif-replay.dg/2.1.0-valid/spec-example-3.sarif create mode 100644 gcc/testsuite/sarif-replay.dg/2.1.0-valid/spec-example-4.sarif create mode 100644 gcc/testsuite/sarif-replay.dg/2.1.0-valid/tutorial-example.sarif create mode 100644 gcc/testsuite/sarif-replay.dg/dg.exp create mode 100644 gcc/testsuite/sarif-replay.dg/malformed-json/array-missing-comma.sarif create mode 100644 gcc/testsuite/sarif-replay.dg/malformed-json/array-with-trailing-comma.sarif create mode 100644 gcc/testsuite/sarif-replay.dg/malformed-json/bad-token.sarif create mode 100644 gcc/testsuite/sarif-replay.dg/malformed-json/object-missing-comma.sarif create mode 100644 gcc/testsuite/sarif-replay.dg/malformed-json/object-with-trailing-comma.sarif diff --git a/contrib/regenerate-sarif-spec-index.py b/contrib/regenerate-sarif-spec-index.py new file mode 100644 index 000000000000..da9dfb59379d --- /dev/null +++ b/contrib/regenerate-sarif-spec-index.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 + +# Copyright (C) 2024 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 +# . + +# Unfortunately the SARIF 2.1.0 spec doesn't have memorable anchors +# for its subsections +# (filed as https://github.com/oasis-tcs/sarif-spec/issues/533) +# +# In the meantime, use this script to generate a table mapping subsections +# to anchors. + +from pprint import pprint +import re + +spec_url = 'https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/sarif-v2.1.0-errata01-os-complete.html' +filename_in = 'sarif-v2.1.0-errata01-os-complete.html' + +d = {} +with open(filename_in, encoding='windows-1252') as infile: + for line in infile: + m = re.match(r'

(.*) tmp-specs @@ -4159,7 +4167,9 @@ libdiagnostics.install-common: installdirs libdiagnostics.install-headers endif endif -install-libdiagnostics: libdiagnostics.install-common +install-libdiagnostics: libdiagnostics.install-common sarif-replay + -rm -f $(DESTDIR)$(bindir)/sarif-replay + -$(INSTALL_PROGRAM) sarif-replay $(DESTDIR)$(bindir)/sarif-replay # Install the info files. # $(INSTALL_DATA) might be a relative pathname, so we can't cd into srcdir diff --git a/gcc/configure b/gcc/configure index 5e2f4644c528..cafdf1300955 100755 --- a/gcc/configure +++ b/gcc/configure @@ -33793,7 +33793,7 @@ do check_languages="$check_languages check-$language" done if test x$enable_libdiagnostics = xyes; then - check_languages="$check_languages check-libdiagnostics" + check_languages="$check_languages check-libdiagnostics check-sarif-replay" fi selftest_languages= @@ -34241,7 +34241,7 @@ fi if test "$enable_libdiagnostics" = "yes"; then - LIBDIAGNOSTICS='libdiagnostics' + LIBDIAGNOSTICS='libdiagnostics sarif-replay' else LIBDIAGNOSTICS='' fi diff --git a/gcc/configure.ac b/gcc/configure.ac index 0edd1b92f5fb..16f91dc552dd 100644 --- a/gcc/configure.ac +++ b/gcc/configure.ac @@ -7372,7 +7372,7 @@ do check_languages="$check_languages check-$language" done if test x$enable_libdiagnostics = xyes; then - check_languages="$check_languages check-libdiagnostics" + check_languages="$check_languages check-libdiagnostics check-sarif-replay" fi selftest_languages= @@ -7612,7 +7612,7 @@ AC_ARG_ENABLE(libdiagnostics, AC_SUBST(enable_libdiagnostics) if test "$enable_libdiagnostics" = "yes"; then - LIBDIAGNOSTICS='libdiagnostics' + LIBDIAGNOSTICS='libdiagnostics sarif-replay' else LIBDIAGNOSTICS='' fi diff --git a/gcc/doc/install.texi b/gcc/doc/install.texi index f1d64833cc8a..3624b544e27b 100644 --- a/gcc/doc/install.texi +++ b/gcc/doc/install.texi @@ -1233,6 +1233,12 @@ GCC's diagnostics capabilities via a C API, and a C++ wrapper API adding This option requires @option{--enable-host-shared} on non-Windows hosts. +This option also enables @code{sarif-replay}, a command-line tool for +viewing @uref{https://sarif.info/,,SARIF files}. @code{sarif-replay} takes +one or more @code{.sarif} files as input and attempts to replay any +diagnostics within them to stderr (via @code{libdiagnostics}) in the style +of GCC's diagnostics. + @item --disable-gcov Specify that the run-time library used for coverage analysis and associated host tools should not be built. diff --git a/gcc/libsarifreplay.cc b/gcc/libsarifreplay.cc new file mode 100644 index 000000000000..e726e101c61b --- /dev/null +++ b/gcc/libsarifreplay.cc @@ -0,0 +1,1747 @@ +/* A library for re-emitting diagnostics saved in SARIF form + via libdiagnostics. + Copyright (C) 2022-2024 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 +. */ + +#include "config.h" +#define INCLUDE_VECTOR +#define INCLUDE_MAP +#define INCLUDE_MEMORY +#define INCLUDE_STRING +#include "system.h" +#include "coretypes.h" +#include "make-unique.h" +#include "libdiagnostics++.h" +#include "json-parsing.h" +#include "intl.h" +#include "sarif-spec-urls.def" +#include "libsarifreplay.h" +#include "label-text.h" + +namespace { + +/* Read the contents of PATH into memory. + Issue an error to MGR and return nullptr if there are any problems. */ + +static std::unique_ptr> +read_file (const char *path, libdiagnostics::manager &mgr) +{ + FILE *f_in = fopen (path, "r"); + if (!f_in) + { + char *errmsg = xstrerror (errno); + auto err (mgr.begin_diagnostic (DIAGNOSTIC_LEVEL_ERROR)); + err.finish ("cannot open %qs: %s", path, errmsg); + return nullptr; + } + + /* Read content, allocating a buffer for it. */ + auto result = ::make_unique> (); + char buf[4096]; + size_t iter_sz_in; + + while ( (iter_sz_in = fread (buf, 1, sizeof (buf), f_in)) ) + { + size_t old_total_sz = result->size (); + size_t new_total_sz = old_total_sz + iter_sz_in; + size_t old_alloc_sz = result->capacity (); + if (new_total_sz > old_alloc_sz) + { + size_t new_alloc_sz = std::max (old_alloc_sz * 2, new_total_sz); + result->reserve (new_alloc_sz); + } + gcc_assert (result->capacity () >= new_total_sz); + result->resize (new_total_sz); + memcpy (result->data () + old_total_sz, buf, iter_sz_in); + } + + if (!feof (f_in)) + { + char *errmsg = xstrerror (errno); + auto err (mgr.begin_diagnostic (DIAGNOSTIC_LEVEL_ERROR)); + err.finish ("error reading from %qs: %s", path, errmsg); + return nullptr; + } + + fclose (f_in); + + return result; +} + +static libdiagnostics::physical_location +make_physical_location (libdiagnostics::manager &mgr, + libdiagnostics::file f, + const json::location_map::point &point) +{ + /* json::location_map::point uses 0-based columns, + whereas libdiagnostics uses 1-based columns. */ + return mgr.new_location_from_file_line_column (f, + point.m_line, + point.m_column + 1); +} + +static libdiagnostics::physical_location +make_physical_location (libdiagnostics::manager &mgr, + libdiagnostics::file f, + const json::location_map::range &range) +{ + libdiagnostics::physical_location start + = make_physical_location (mgr, f, range.m_start); + libdiagnostics::physical_location end + = make_physical_location (mgr, f, range.m_end); + return mgr.new_location_from_range (start, start, end); +} + +enum class status +{ + ok, + err_reading_file, + err_malformed_json, + err_invalid_sarif, + err_unhandled_sarif +}; + +/* A reference to the SARIF specification. */ + +class spec_ref +{ +public: + spec_ref (const char *section) + : m_section (section) + {} + + virtual char *make_description () const + { + /* 'SECTION SIGN' (U+00A7). */ +#define SECTION_SIGN_UTF8 "\xC2\xA7" + return xasprintf ("SARIF v2.1.0 " SECTION_SIGN_UTF8 "%s", m_section); + } + + char *make_url () const + { + const char *anchor = get_anchor_for_section (m_section); + if (!anchor) + return nullptr; + return xasprintf ("%s#%s", sarif_spec_base_url, anchor); + } + +private: + static const char * + get_anchor_for_section (const char *section) + { + /* Linear search, but the array is only a few hundred entries . */ + for (size_t i = 0; i < ARRAY_SIZE (sarif_spec_anchor_arr); i++) + { + if (strcmp (sarif_spec_anchor_arr[i].m_ref, section) == 0) + return sarif_spec_anchor_arr[i].m_anchor; + } + return nullptr; + } + + /* e.g. "3.1" for section 3.1 of the spec. */ + const char *m_section; +}; + +/* A reference to the SARIF specification for a particular kind of object. */ + +class object_spec_ref : public spec_ref +{ +public: + object_spec_ref (const char *obj_name, const char *section) + : spec_ref (section), + m_obj_name (obj_name) + {} + + const char *get_obj_name () const { return m_obj_name; } + +private: + const char *m_obj_name; +}; + +/* A reference to the SARIF specification for a particular property + of a particular kind of object. */ + +class property_spec_ref : public object_spec_ref +{ +public: + property_spec_ref (const char *obj_name, + const char *property_name, + const char *section) + : object_spec_ref (obj_name, section), + m_property_name (property_name) + {} + + const char *get_property_name () const { return m_property_name; } + +private: + const char *m_property_name; +}; + +template +struct string_property_value +{ + const char *m_string; + ValueType m_value; +}; + +class sarif_replayer +{ +public: + sarif_replayer (libdiagnostics::manager &&output_manager, + libdiagnostics::manager &&control_manager) + : m_output_mgr (std::move (output_manager)), + m_control_mgr (std::move (control_manager)), + m_driver_obj (nullptr), + m_artifacts_arr (nullptr) + { + } + + enum status replay_file (const char *filename, + const replay_options &replay_opts); + +private: + class replayer_location_map : public json::location_map + { + public: + void record_range_for_value (json::value *jv, + const range &r) final override + { + m_map_jv_to_range[jv] = r; + } + + const json::location_map::range & + get_range_for_value (const json::value &jv) const + { + auto iter = m_map_jv_to_range.find (&jv); + gcc_assert (iter != m_map_jv_to_range.end ()); + return iter->second; + } + + private: + std::map m_map_jv_to_range; + }; + + enum status emit_sarif_as_diagnostics (const json::value &jv); + + label_text + make_plain_text_within_result_message (const json::object *tool_component_obj, + const json::object &message_obj, + const json::object *rule_obj); + + /* Handlers for specific parts of the SARIF spec. + Keep this in the same order as the spec. */ + + // "artifactLocation" object (§3.4) + enum status + handle_artifact_location_object (const json::object &artifact_loc, + libdiagnostics::file &out); + + // Message string lookup algorithm (§3.11.7) + const char * + lookup_plain_text_within_result_message (const json::object *tool_component_obj, + const json::object &message_obj, + const json::object *rule_obj); + + // "multiformatMessageString" object (§3.12). + const char * + get_plain_text_from_mfms (json::value &mfms_val, + const property_spec_ref &prop); + + // "run" object (§3.14) + enum status + handle_run_obj (const json::object &run_obj); + + // "tool" object (§3.18) + enum status + handle_tool_obj (const json::object &tool_obj); + + // "result" object (§3.27) + enum status + handle_result_obj (const json::object &result_obj, + const json::object &tool_obj); + json::result + get_level_from_level_str (const json::string &level_str); + + // "location" object (§3.28) + enum status + handle_location_object (const json::object &location_obj, + libdiagnostics::physical_location &out_physical_loc, + libdiagnostics::logical_location &out_logical_loc); + + // "physicalLocation" object (§3.29) + enum status + handle_physical_location_object (const json::object &phys_loc_obj, + libdiagnostics::physical_location &out); + + // "region" object (§3.30) + enum status + handle_region_object (const json::object ®ion_obj, + libdiagnostics::file file, + libdiagnostics::physical_location &out); + + // "logicalLocation" object (§3.33) + enum status + handle_logical_location_object (const json::object &logical_loc_obj, + libdiagnostics::logical_location &out); + + // "threadFlow" object (§3.37) + enum status + handle_thread_flow_object (const json::object &thread_flow_obj, + libdiagnostics::execution_path &out); + + // "threadFlowLocation" object (§3.38) + enum status + handle_thread_flow_location_object (const json::object &tflow_loc_obj, + libdiagnostics::execution_path &out); + + // reportingDescriptor lookup (§3.52.3) + const json::object * + lookup_rule_by_id_in_tool (const char *rule_id, + const json::object &tool_obj, + const json::object *&tool_component_obj); + + const json::object * + lookup_rule_by_id_in_component (const char *rule_id, + const json::object &tool_component_obj); + + /* Support functions. */ + + /* Report an error to m_control_mgr about JV violating REF, + and return status::err_invalid_sarif. */ + + enum status + report_invalid_sarif (const json::value &jv, + const spec_ref &ref, + const char *gmsgid, ...) + LIBDIAGNOSTICS_PARAM_GCC_FORMAT_STRING (4, 5) + { + va_list ap; + va_start (ap, gmsgid); + report_problem (jv, ref, gmsgid, &ap, DIAGNOSTIC_LEVEL_ERROR); + va_end (ap); + return status::err_invalid_sarif; + } + + /* Report a "sorry" to m_control_mgr inability to handle JV and REF, + and return status::err_unhandled_sarif. */ + + enum status + report_unhandled_sarif (const json::value &jv, + const spec_ref &ref, + const char *gmsgid, ...) + LIBDIAGNOSTICS_PARAM_GCC_FORMAT_STRING (4, 5) + { + va_list ap; + va_start (ap, gmsgid); + report_problem (jv, ref, gmsgid, &ap, DIAGNOSTIC_LEVEL_SORRY); + va_end (ap); + return status::err_unhandled_sarif; + } + + void + report_problem (const json::value &jv, + const spec_ref &ref, + const char *gmsgid, + va_list *args, + enum diagnostic_level level) + LIBDIAGNOSTICS_PARAM_GCC_FORMAT_STRING (4, 0) + { + auto diag (m_control_mgr.begin_diagnostic (level)); + + /* Add rule specifying the pertinent section of the specification. + There doesn't seem to be a systematic mapping from spec sections to + HTML anchors, so we can't provide URLs + (filed as https://github.com/oasis-tcs/sarif-spec/issues/533 ). */ + char *ref_desc = ref.make_description (); + char *ref_url = ref.make_url (); + diag.add_rule (ref_desc, ref_url); + free (ref_desc); + free (ref_url); + + auto loc_range + = make_physical_location (m_control_mgr, + m_loaded_file, + m_json_location_map.get_range_for_value (jv)); + diag.set_location (loc_range); + + diag.finish_va (gmsgid, args); + } + + /* Require OBJ to have at least one of OBJ_PROP1 or OBJ_PROP2. + If successful, result status::ok. + Otherwise, complain about OBJ_CONSTRAINTS and return + status::invalid_sarif. */ + enum status + report_invalid_sarif_at_least_one_of (const json::object &obj, + const object_spec_ref &obj_constraints, + const property_spec_ref &obj_prop_1, + const property_spec_ref &obj_prop_2) + { + return report_invalid_sarif + (obj, obj_constraints, + "expected SARIF %qs object to contain at least one of %qs or %qs", + obj_constraints.get_obj_name (), + obj_prop_1.get_property_name (), + obj_prop_2.get_property_name ()); + } + + /* Require VAL to be a json::object. + If successful, return it as an object. + Otherwise, complain using REF and return nullptr. */ + const json::object * + require_object (const json::value &val, const property_spec_ref &ref) + { + const json::object *obj = dyn_cast (&val); + if (!obj) + { + report_invalid_sarif (val, ref, "expected %s.%s to be an object", + ref.get_obj_name (), ref.get_property_name ()); + return nullptr; + } + return obj; + } + + /* Require VAL to be a json::string + If successful, return it as an string. + Otherwise, complain using REF and return nullptr. */ + const json::string * + require_string (const json::value &val, const property_spec_ref &ref) + { + const json::string *str = dyn_cast (&val); + if (!str) + { + report_invalid_sarif (val, ref, "expected %s.%s to be an string", + ref.get_obj_name (), ref.get_property_name ()); + return nullptr; + } + return str; + } + /* Look for an optional property within OBJ based on REF. */ + const json::value * + get_optional_property (const json::object &obj, + const property_spec_ref &ref) + { + return obj.get (ref.get_property_name ()); + } + + /* Look for a property of VAL based on REF. + If present, it must be of kind JsonType. + If found and valid, return the property's value. + If not found, silently return nullptr. + Otherwise, complain and return nullptr. */ + template + const JsonType * + get_optional_property (const json::object &obj, + const property_spec_ref &ref) + { + const json::value *property_val = get_optional_property (obj, ref); + if (!property_val) + return nullptr; + const JsonType *sub = dyn_cast (property_val); + if (!sub) + { + /* Property is wrong kind of value. */ + report_bad_property_kind (obj, ref, *property_val); + return nullptr; + } + return sub; + } + + /* Require VAL to be a json::object. + Look for a property of VAL based on REF, which must be of + kind JsonType. + If successful, return the property's value. + Otherwise, complain and return nullptr. */ + template + const JsonType * + get_required_property (const json::value &val, + const property_spec_ref &ref) + { + const json::object *obj = require_object (val, ref); + if (!obj) + return nullptr; + return get_required_property (*obj, ref); + } + + /* Look for a property of VAL based on REF, which must be of + kind JsonType. + If successful, return the property's value. + Otherwise, complain and return nullptr. */ + template + const JsonType * + get_required_property (const json::object &obj, + const property_spec_ref &ref) + { + const json::value *property_val = get_optional_property (obj, ref); + if (!property_val) + { + /* Property not present. */ + report_invalid_sarif (obj, ref, + "expected %s object to have a %qs property", + ref.get_obj_name (), ref.get_property_name ()); + return nullptr; + } + const JsonType *sub = dyn_cast (property_val); + if (!sub) + { + /* Property is wrong kind of value. */ + report_bad_property_kind (obj, ref, *property_val); + return nullptr; + } + return sub; + } + + template + void + report_bad_property_kind (const json::object &obj, + const property_spec_ref &ref, + const json::value &property_val); + + const json::object * + require_object_for_element (const json::value &jv, + const property_spec_ref &ref) + { + const json::object *obj = dyn_cast (&jv); + if (!obj) + { + report_invalid_sarif (jv, ref, + "expected element of %s.%s array to be an object", + ref.get_obj_name (), ref.get_property_name ()); + return nullptr; + } + return obj; + } + + template + json::result + get_value_from_json_string (const json::string &json_str, + const property_spec_ref &prop, + const string_property_value *value_arr, + size_t num_values); + + /* The manager to replay the SARIF files to. */ + libdiagnostics::manager m_output_mgr; + + /* The manager for reporting issues loading SARIF files. */ + libdiagnostics::manager m_control_mgr; + + /* The file within m_control_mgr representing the .sarif file. */ + libdiagnostics::file m_loaded_file; + + replayer_location_map m_json_location_map; + + const json::object *m_driver_obj; + const json::value *m_artifacts_arr; +}; + +static const char * +describe_kind (const json::value &val) +{ + switch (val.get_kind ()) + { + default: + gcc_unreachable (); + case json::JSON_OBJECT: + return _("JSON object"); + + case json::JSON_ARRAY: + return _("JSON array"); + + case json::JSON_INTEGER: + case json::JSON_FLOAT: + return _("JSON number"); + + case json::JSON_STRING: + return _("JSON string"); + + case json::JSON_TRUE: + case json::JSON_FALSE: + case json::JSON_NULL: + return _("JSON literal"); + } +} + +/* class sarif_replayer. */ + +template <> +void +sarif_replayer:: +report_bad_property_kind (const json::object &, + const property_spec_ref &, + const json::value &) +{ + gcc_unreachable (); +} + +template <> +void +sarif_replayer:: +report_bad_property_kind (const json::object &, + const property_spec_ref &ref, + const json::value &propval) +{ + report_invalid_sarif (propval, ref, "expected %s.%s to be a JSON integer; got %s", + ref.get_obj_name (), ref.get_property_name (), + describe_kind (propval)); +} + +template <> +void +sarif_replayer:: +report_bad_property_kind (const json::object &, + const property_spec_ref &ref, + const json::value &propval) +{ + report_invalid_sarif (propval, ref, "expected %s.%s to be a JSON string; got %s", + ref.get_obj_name (), ref.get_property_name (), + describe_kind (propval)); +} + +template <> +void +sarif_replayer:: +report_bad_property_kind (const json::object &, + const property_spec_ref &ref, + const json::value &propval) +{ + report_invalid_sarif (propval, ref, "expected %s.%s to be a JSON array; got %s", + ref.get_obj_name (), ref.get_property_name (), + describe_kind (propval)); +} + +template <> +void +sarif_replayer:: +report_bad_property_kind (const json::object &, + const property_spec_ref &ref, + const json::value &propval) +{ + report_invalid_sarif (propval, ref, "expected %s.%s to be a JSON object; got %s", + ref.get_obj_name (), ref.get_property_name (), + describe_kind (propval)); +} + +enum status +sarif_replayer::replay_file (const char *filename, + const replay_options &replay_opts) +{ + std::unique_ptr> buf = read_file (filename, m_control_mgr); + if (!buf) + return status::err_reading_file; + + /* Use "sarif" as the sourceLanguage for SARIF itself; see + https://github.com/oasis-tcs/sarif-spec/issues/654 */ + const char * const source_language = "sarif"; + m_loaded_file = m_control_mgr.new_file (filename, source_language); + + if (replay_opts.m_echo_file) + { + fprintf (stderr, "%s: (%li bytes)\n", + filename, (long)buf->size ()); + for (size_t i = 0; i < buf->size(); i++) + fputc ((*buf)[i], stderr); + } + + json::parser_result_t result + (json::parse_utf8_string (buf->size (), + (const char *)buf->data (), + replay_opts.m_json_comments, + &m_json_location_map)); + + if (auto json_err = result.m_err.get ()) + { + gcc_assert (!result.m_val.get ()); + auto file = m_control_mgr.new_file (filename, source_language); + auto loc_range = make_physical_location (m_control_mgr, + file, + json_err->get_range ()); + auto err (m_control_mgr.begin_diagnostic (DIAGNOSTIC_LEVEL_ERROR)); + err.set_location (loc_range); + err.finish ("%s", json_err->get_msg ()); + + return status::err_malformed_json; + } + + gcc_assert (result.m_val.get ()); + return emit_sarif_as_diagnostics (*result.m_val.get ()); +} + +#define PROP_sarifLog_version \ + property_spec_ref ("sarifLog", "version", "3.13.2") + +#define PROP_sarifLog_runs \ + property_spec_ref ("sarifLog", "runs", "3.13.4") + +enum status +sarif_replayer::emit_sarif_as_diagnostics (const json::value &jv) +{ + /* We expect a sarifLog object as the top-level value + (SARIF v2.1.0 section 3.13). */ + const json::object *toplev_obj = dyn_cast (&jv); + if (!toplev_obj) + return report_invalid_sarif + (jv, spec_ref ("3.1"), + "expected a sarifLog object as the top-level value"); + + /* sarifLog objects SHALL have a property named "version" + (SARIF v2.1.0 section 3.13.2) with a string value. */ + if (!get_required_property (*toplev_obj, + PROP_sarifLog_version)) + return status::err_invalid_sarif; + + /* sarifLog.runs must be null or be an array. */ + const property_spec_ref prop_runs (PROP_sarifLog_runs); + const json::value *runs + = get_required_property (*toplev_obj, prop_runs); + if (!runs) + return status::err_invalid_sarif; + + switch (runs->get_kind ()) + { + default: + return report_invalid_sarif (*runs, prop_runs, + "expected sarifLog.runs to be" + " % or an array"); + + case json::JSON_NULL: + /* Nothing to do. */ + break; + + case json::JSON_ARRAY: + { + const json::array &runs_arr = *as_a (runs); + for (auto element : runs_arr) + { + const json::object *run_obj + = require_object_for_element (*element, prop_runs); + if (!run_obj) + return status::err_invalid_sarif; + enum status s = handle_run_obj (*run_obj); + if (s != status::ok) + return s; + } + } + break; + } + + return status::ok; +} + +/* Process a run object (SARIF v2.1.0 section 3.14). */ + +enum status +sarif_replayer::handle_run_obj (const json::object &run_obj) +{ + const json::object *tool_obj + = get_required_property (run_obj, + property_spec_ref ("run", "tool", + "3.14.6")); + if (!tool_obj) + return status::err_invalid_sarif; + { + enum status err = handle_tool_obj (*tool_obj); + if (err != status::ok) + return err; + } + + m_driver_obj + = get_required_property (*tool_obj, + property_spec_ref ("tool", "driver", + "3.18.2")); + if (!m_driver_obj) + return status::err_invalid_sarif; + +#if 0 + m_artifacts_arr = get_optional_property + (run_obj, property_spec_ref ("run", "artifacts","3.14.15")); +#endif + + /* If present, run.results must be null or be an array. */ + const property_spec_ref prop_results ("run", "results", "3.14.23"); + if (const json::value *results = get_optional_property (run_obj, + prop_results)) + switch (results->get_kind ()) + { + default: + return report_invalid_sarif (*results, prop_results, + "expected run.results to be" + " % or an array"); + + case json::JSON_NULL: + /* Nothing to do. */ + break; + case json::JSON_ARRAY: + { + const json::array *results_arr = as_a (results); + for (auto element : *results_arr) + { + const json::object *result_obj + = require_object_for_element (*element, prop_results); + if (!result_obj) + return status::err_invalid_sarif; + enum status s = handle_result_obj (*result_obj, *tool_obj); + if (s != status::ok) + return s; + } + } + break; + } + + return status::ok; +} + +/* Process a tool object (SARIF v2.1.0 section 3.18). */ + +enum status +sarif_replayer::handle_tool_obj (const json::object &tool_obj) +{ + auto driver_obj + = get_required_property (tool_obj, + property_spec_ref ("tool", "driver", + "3.18.2")); + if (!driver_obj) + return status::err_invalid_sarif; + + const property_spec_ref name_prop ("toolComponent", "name", "3.19.8"); + if (auto name_jstr = get_optional_property (*driver_obj, + name_prop)) + m_output_mgr.set_tool_name (name_jstr->get_string ()); + + const property_spec_ref full_name_prop + ("toolComponent", "fullName", "3.19.9"); + if (auto name_jstr = get_optional_property (*driver_obj, + full_name_prop)) + m_output_mgr.set_full_name (name_jstr->get_string ()); + + const property_spec_ref version_prop ("toolComponent", "version", "3.19.13"); + if (auto name_jstr = get_optional_property (*driver_obj, + version_prop)) + m_output_mgr.set_version_string (name_jstr->get_string ()); + + const property_spec_ref + info_uri_prop ("toolComponent", "informationUri", "3.19.17"); + if (auto name_jstr = get_optional_property (*driver_obj, + info_uri_prop)) + m_output_mgr.set_version_url (name_jstr->get_string ()); + + return status::ok; +} + +/* Compare the value of JSON_STR to the values in VALUE_ARR. + If found, return it in the result's m_val. + Otherwise, complain using PROP and return status::invalid_sarif + in results's m_err. */ + +template +json::result +sarif_replayer:: +get_value_from_json_string (const json::string &json_str, + const property_spec_ref &prop, + const string_property_value *value_arr, + size_t num_values) +{ + const char *str = json_str.get_string (); + for (size_t i = 0; i < num_values; i++) + if (strcmp (str, value_arr[i].m_string) == 0) + return value_arr[i].m_value; + return report_invalid_sarif (json_str, prop, + "unrecognized value for %qs: %qs", + prop.get_property_name (), + str); +} + +const property_spec_ref prop_result_level ("result", "level", "3.27.10"); + +/* Handle a value for result's "level" property (§3.27.10). + Limitation: doesn't yet support "none". */ +json::result +sarif_replayer::get_level_from_level_str (const json::string &level_str) +{ + if (strcmp (level_str.get_string (), "none") == 0) + return report_unhandled_sarif (level_str, prop_result_level, + "unable to handle value for %qs: %qs", + prop_result_level.get_property_name (), + level_str.get_string ()); + + const string_property_value level_values[] + = { {"warning", + DIAGNOSTIC_LEVEL_WARNING}, + {"error", + DIAGNOSTIC_LEVEL_ERROR}, + {"note", + DIAGNOSTIC_LEVEL_NOTE} }; + return get_value_from_json_string + (level_str, + prop_result_level, + level_values, ARRAY_SIZE (level_values)); +} + +/* Process a result object (SARIF v2.1.0 section 3.27). + Known limitations: + - doesn't yet handle "ruleIndex" property (§3.27.6) + - doesn't yet handle "taxa" property (§3.27.8) + - handling of "level" property (§3.27.10) doesn't yet support the + full logic for when "level" is absent. + - doesn't yet handle "relatedLocations" property (§3.27.22) + - doesn't yet handle "fixes" property (§3.27.30) + - doesn't yet support multithreaded flows (§3.36.3) +*/ + +#define PROP_result_ruleId \ + property_spec_ref ("result", "ruleId", "3.27.5") + +#define PROP_result_message \ + property_spec_ref ("result", "message", "3.27.11") + +enum status +sarif_replayer::handle_result_obj (const json::object &result_obj, + const json::object &tool_obj) +{ + const json::object *rule_obj = nullptr; + const json::object *tool_component_obj = nullptr; + const json::string *rule_id + = get_optional_property (result_obj, PROP_result_ruleId); + if (rule_id) + { + rule_obj = lookup_rule_by_id_in_tool (rule_id->get_string (), + tool_obj, + tool_component_obj); + // TODO: error handling + } + + enum diagnostic_level level = DIAGNOSTIC_LEVEL_WARNING; + if (auto level_str + = get_optional_property (result_obj, + prop_result_level)) + { + auto result = get_level_from_level_str (*level_str); + if (result.m_err != status::ok) + return result.m_err; + level = result.m_val; + } + + // §3.27.11 "message" property + label_text text; + if (auto message_obj + = get_optional_property (result_obj, PROP_result_message)) + text = make_plain_text_within_result_message (nullptr, // TODO: tool_component_obj, + *message_obj, + rule_obj); + if (!text.get ()) + return status::err_invalid_sarif; + + // §3.27.12 "locations" property + libdiagnostics::physical_location physical_loc; + libdiagnostics::logical_location logical_loc; + const property_spec_ref locations_prop ("result", "locations", "3.27.12"); + const json::array *locations_arr + = get_required_property (result_obj, locations_prop); + if (!locations_arr) + return status::err_invalid_sarif; + if (locations_arr->length () > 0) + { + /* Only look at the first, if there's more than one. */ + // location objects (§3.28) + const json::object *location_obj + = require_object_for_element (*locations_arr->get (0), locations_prop); + if (!location_obj) + return status::err_invalid_sarif; + enum status s = handle_location_object (*location_obj, + physical_loc, + logical_loc); + if (s != status::ok) + return s; + } + + // §3.27.18 "codeFlows" property + libdiagnostics::execution_path path; + const property_spec_ref code_flows ("result", "codeFlows", "3.27.18"); + if (auto code_flows_arr = get_optional_property (result_obj, + code_flows)) + { + // TODO: what if more than one? + if (code_flows_arr->length () == 1) + { + const json::object *code_flow_obj + = require_object_for_element (*code_flows_arr->get (0), code_flows); + if (!code_flow_obj) + return status::err_invalid_sarif; + + const property_spec_ref prop_thread_flows + ("result", "threadFlows", "3.36.3"); + if (auto thread_flows_arr + = get_optional_property (*code_flow_obj, + prop_thread_flows)) + { + if (thread_flows_arr->length () == 1) + { + const json::object *thread_flow_obj + = require_object_for_element (*thread_flows_arr->get (0), + prop_thread_flows); + if (!thread_flow_obj) + return status::err_invalid_sarif; + handle_thread_flow_object (*thread_flow_obj, path); + } + } + } + } + + libdiagnostics::group g (m_output_mgr); + auto err (m_output_mgr.begin_diagnostic (level)); + if (rule_id) + err.add_rule (rule_id->get_string (), nullptr); + err.set_location (physical_loc); + err.set_logical_location (logical_loc); + if (path.m_inner) + err.take_execution_path (std::move (path)); + err.finish ("%s", text.get ()); + + // §3.27.22 relatedLocations property + const property_spec_ref prop_related_locations + ("result", "relatedLocations", "3.27.22"); + if (auto related_locations_arr + = get_optional_property (result_obj, + prop_related_locations)) + { + for (auto rel_loc : *related_locations_arr) + { + libdiagnostics::physical_location physical_loc; + libdiagnostics::logical_location logical_loc; + const json::object *location_obj + = require_object_for_element (*rel_loc, + prop_related_locations); + if (!location_obj) + return status::err_invalid_sarif; + enum status s = handle_location_object (*location_obj, + physical_loc, + logical_loc); + if (s != status::ok) + return s; + + // §3.28.5 message property + const property_spec_ref prop_message + ("location", "message", "3.28.5"); + if (auto message_obj + = get_optional_property (*location_obj, + prop_message)) + { + /* Treat related locations with a message as a "note". */ + label_text text + (make_plain_text_within_result_message + (tool_component_obj, + *message_obj, + rule_obj)); + if (!text.get ()) + return status::err_invalid_sarif; + auto note (m_output_mgr.begin_diagnostic (DIAGNOSTIC_LEVEL_NOTE)); + note.set_location (physical_loc); + note.set_logical_location (logical_loc); + note.finish ("%s", text.get ()); + } + } + } + + return status::ok; + +} + +/* If ITER_SRC starts with a placeholder as per §3.11.5, advance ITER_SRC + to immediately beyond the placeholder, write to *OUT_ARG_IDX, and + return true. + + Otherwise, leave ITER_SRC untouched and return false. */ + +static bool +maybe_consume_placeholder (const char *&iter_src, unsigned *out_arg_idx) +{ + if (*iter_src != '{') + return false; + const char *first_digit = iter_src + 1; + const char *iter_digit = first_digit; + while (char ch = *iter_digit) + switch (ch) + { + default: + return false; + + case '}': + if (iter_digit == first_digit) + { + /* No digits, we simply have "{}" which is not a placeholder + (and malformed: the braces should have been escaped). */ + return false; + } + *out_arg_idx = atoi (first_digit); + iter_src = iter_digit + 1; + return true; + + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + /* TODO: what about multiple leading zeroes? */ + iter_digit++; + continue; + } + return false; +} + +/* Lookup the plain text string within a result.message (§3.27.11), + and substitute for any placeholders (§3.11.5). + + Limitations: + - we don't yet support embedded links + + MESSAGE_OBJ is "theMessage" + RULE_OBJ is "theRule". */ + +label_text +sarif_replayer:: +make_plain_text_within_result_message (const json::object *tool_component_obj, + const json::object &message_obj, + const json::object *rule_obj) +{ + const char *original_text + = lookup_plain_text_within_result_message (tool_component_obj, + message_obj, + rule_obj); + if (!original_text) + return label_text::borrow (nullptr); + + /* Look up any arguments for substituting into placeholders. */ + const property_spec_ref arguments_prop ("message", "arguments", "3.11.11"); + const json::array *arguments + = get_optional_property (message_obj, arguments_prop); + + /* Duplicate original_text, substituting any placeholders. */ + std::string accum; + + const char *iter_src = original_text; + while (char ch = *iter_src) + { + unsigned arg_idx; + if (maybe_consume_placeholder (iter_src, &arg_idx)) + { + if (!arguments) + { + report_invalid_sarif + (message_obj, arguments_prop, + "message string contains placeholder %<{%i}%>" + " but message object has no %qs property", + (int)arg_idx, + arguments_prop.get_property_name ()); + return label_text::borrow (nullptr); + } + if (arg_idx >= arguments->length ()) + { + report_invalid_sarif + (message_obj, arguments_prop, + "not enough strings in %qs array for" + " placeholder %<{%i}%>", + arguments_prop.get_property_name (), + (int)arg_idx); + // TODO: might be nice to add a note showing the args + return label_text::borrow (nullptr); + } + auto replacement_jstr + = require_string (*arguments->get (arg_idx), arguments_prop); + if (!replacement_jstr) + return label_text::borrow (nullptr); + accum += replacement_jstr->get_string (); + } + else if (ch == '{' || ch == '}') + { + /* '{' and '}' are escaped by repeating them. */ + if (iter_src[1] == ch) + { + accum += ch; + iter_src += 2; + } + else + { + report_invalid_sarif (message_obj, arguments_prop, + "unescaped '%c' within message string", + ch); + return label_text::borrow (nullptr); + } + } + else + { + accum += ch; + iter_src++; + } + } + + return label_text::take (xstrdup (accum.c_str ())); +} + +/* Handle a value that should be a multiformatMessageString object (§3.12). + Complain using prop if MFMS_VAL is not an object. + Return get the "text" value (or nullptr, and complain). */ + +const char * +sarif_replayer::get_plain_text_from_mfms (json::value &mfms_val, + const property_spec_ref &prop) +{ + auto mfms_obj = require_object (mfms_val, prop); + if (!mfms_obj) + return nullptr; + + const property_spec_ref text_prop + ("multiformatMessageString", "text", "3.12.3"); + auto text_jstr = get_required_property (*mfms_obj, text_prop); + if (!text_jstr) + return nullptr; + return text_jstr->get_string (); +} + +#define PROP_message_text \ + property_spec_ref ("message", "text", "3.11.8") + +#define PROP_message_id \ + property_spec_ref ("message", "id", "3.11.10") + +/* Implement the message string lookup algorithm from + SARIF v2.1.0 section 3.11.7, for the case where theMessage + is the value of result.message (§3.27.11). + + MESSAGE_OBJ is "theMessage" + RULE_OBJ is "theRule". */ + +const char * +sarif_replayer:: +lookup_plain_text_within_result_message (const json::object *tool_component_obj, + const json::object &message_obj, + const json::object *rule_obj) +{ + // rule_obj can be NULL + + /* IF theMessage.text is present and the desired language is theRun.language THEN + Use the text or markdown property of theMessage as appropriate. */ + if (const json::string *str + = get_optional_property (message_obj, PROP_message_text)) + // TODO: check language + return str->get_string (); + + if (rule_obj) + if (auto message_id_jstr + = get_optional_property (message_obj, PROP_message_id)) + { + const char *message_id = message_id_jstr->get_string (); + const property_spec_ref message_strings + ("reportingDescriptor", "messageStrings", "3.49.11"); + if (auto message_strings_obj + = get_optional_property (*rule_obj, + message_strings)) + if (json::value *mfms = message_strings_obj->get (message_id)) + return get_plain_text_from_mfms (*mfms, message_strings); + + /* Look up by theMessage.id within theComponent.globalMessageStrings + (§3.19.22). */ + if (tool_component_obj) + { + const property_spec_ref prop_gms + ("toolComponent", "globalMessageStrings", "3.19.22"); + if (auto global_message_strings + = get_optional_property (*tool_component_obj, + prop_gms)) + if (auto mfms = global_message_strings->get (message_id)) + return get_plain_text_from_mfms (*mfms, prop_gms); + } + } + + /* Failure. */ + report_invalid_sarif (message_obj, spec_ref ("3.11.7"), + "could not find string for % object"); + return nullptr; +} + +/* Populate OUT for THREAD_FLOW_OBJ, a + SARIF threadFlow object (section 3.37). */ + +enum status +sarif_replayer::handle_thread_flow_object (const json::object &thread_flow_obj, + libdiagnostics::execution_path &out) +{ + const property_spec_ref locations ("threadFlow", "locations", "3.37.6"); + const json::array *locations_arr + = get_required_property (thread_flow_obj, locations); + if (!locations_arr) + return status::err_invalid_sarif; + + out = m_output_mgr.new_execution_path (); + for (auto location : *locations_arr) + { + /* threadFlowLocation object (§3.38). */ + const json::object *tflow_loc_obj + = require_object_for_element (*location, locations); + if (!tflow_loc_obj) + return status::err_invalid_sarif; + handle_thread_flow_location_object (*tflow_loc_obj, out); + } + + return status::ok; +} + +/* "threadFlowLocation" object (§3.38). + Attempt to add an event for TFLOW_LOC_OBJ to PATH. */ + +enum status +sarif_replayer:: +handle_thread_flow_location_object (const json::object &tflow_loc_obj, + libdiagnostics::execution_path &path) +{ + libdiagnostics::physical_location physical_loc; + libdiagnostics::logical_location logical_loc; + label_text message; + int stack_depth = 0; + + const property_spec_ref location_prop + ("threadFlowLocation", "location", "3.38.3"); + if (auto location_obj = get_optional_property (tflow_loc_obj, + location_prop)) + { + /* location object (§3.28). */ + enum status s + = handle_location_object (*location_obj, physical_loc, logical_loc); + if (s != status::ok) + return s; + + /* Get any message from here. */ + const property_spec_ref location_message + ("location", "message", "3.28.5"); + if (auto message_obj + = get_optional_property (*location_obj, + location_message)) + { + message = make_plain_text_within_result_message + (nullptr, + *message_obj, + nullptr/* TODO. */); + } + } + + // §3.38.8 "kinds" property + const property_spec_ref kinds ("threadFlowLocation", "kinds", "3.38.8"); + if (auto kinds_arr + = get_optional_property (tflow_loc_obj, kinds)) + { + std::vector kind_strs; + for (auto iter : *kinds_arr) + { + const json::string *kind_str = dyn_cast (iter); + if (!kind_str) + { + } + kind_strs.push_back (kind_str->get_string ()); + // TODO: handle meaning? + /* TOOD: probably just want to add sarif kinds to + the libdiagnostics event, and have libdiagnostics + turn that back into a "meaning". */ + } + } + + /* nestingLevel property (§3.38.10). */ + const property_spec_ref nesting_level + ("threadFlowLocation", "nestingLevel", "3.38.10"); + if (auto nesting_level_jv + = get_optional_property (tflow_loc_obj, + nesting_level)) + { + stack_depth = nesting_level_jv->get (); + if (stack_depth < 0) + { + return report_invalid_sarif (tflow_loc_obj, nesting_level, + "expected a non-negative integer"); + } + } + + if (message.get ()) + path.add_event (physical_loc, + logical_loc, + stack_depth, + "%s", message.get ()); + else + path.add_event (physical_loc, + logical_loc, + stack_depth, + ""); + + return status::ok; +} + +/* Handle LOCATION_OBJ, a "location" (§3.28). */ + +enum status +sarif_replayer:: +handle_location_object (const json::object &location_obj, + libdiagnostics::physical_location &out_physical_loc, + libdiagnostics::logical_location &out_logical_loc) +{ + // §3.28.3 "physicalLocation" property + { + const property_spec_ref physical_location_prop + ("location", "physicalLocation", "3.28.3"); + if (const json::object *phys_loc_obj + = get_optional_property (location_obj, + physical_location_prop)) + { + enum status s = handle_physical_location_object (*phys_loc_obj, + out_physical_loc); + if (s!= status::ok) + return s; + } + } + + // §3.28.4 "logicalLocations" property + { + const property_spec_ref logical_locations_prop + ("location", "logicalLocations", "3.28.4"); + if (const json::array *logical_loc_arr + = get_optional_property (location_obj, + logical_locations_prop)) + if (logical_loc_arr->length () > 0) + { + /* Only look at the first, if there's more than one. */ + const json::object *logical_loc_obj + = require_object_for_element (*logical_loc_arr->get (0), + logical_locations_prop); + if (!logical_loc_obj) + return status::err_invalid_sarif; + enum status s = handle_logical_location_object (*logical_loc_obj, + out_logical_loc); + if (s != status::ok) + return s; + } + } + + return status::ok; +} + +/* Handle PHYS_LOC_OBJ, a "physicalLocation" object (§3.29). + Limitations: + - we don't yet support the "contextRegion" property (§3.29.5) */ + +enum status +sarif_replayer:: +handle_physical_location_object (const json::object &phys_loc_obj, + libdiagnostics::physical_location &out) +{ + libdiagnostics::file artifact_file; + + // §3.29.3 "artifactLocation" property + const property_spec_ref artifact_location_prop + ("physicalLocation", "artifactLocation", "3.29.3"); + if (const json::object *artifact_loc_obj + = get_optional_property (phys_loc_obj, + artifact_location_prop)) + { + enum status s = handle_artifact_location_object (*artifact_loc_obj, + artifact_file); + if (s != status::ok) + return s; + } + + // §3.29.6 "address" property + const property_spec_ref artifact_address_prop + ("physicalLocation", "address", "3.29.6"); + + if (!artifact_file.m_inner) + { + const object_spec_ref constraints ("physicalLocation", "3.29.2"); + return report_invalid_sarif_at_least_one_of (phys_loc_obj, + constraints, + artifact_address_prop, + artifact_location_prop); + } + + //3.29.4 region property + const property_spec_ref region_prop ("physicalLocation", "region", "3.29.4"); + if (const json::object *region_obj + = get_optional_property (phys_loc_obj, region_prop)) + { + enum status s + = handle_region_object (*region_obj, artifact_file, out); + if (s != status::ok) + return s; + // TODO: + } + + return status::ok; +} + +/* Handle ARTIFACT_LOC, an "artifactLocation" object (§3.4). */ + +enum status +sarif_replayer::handle_artifact_location_object (const json::object &artifact_loc, + libdiagnostics::file &out) +{ + // §3.4.3 "uri" property + const property_spec_ref uri_prop ("artifactLocation", "uri", "3.4.3"); + auto uri = get_optional_property (artifact_loc, uri_prop); + + // §3.4.5 "index" property + const property_spec_ref index_prop ("artifactLocation", "index", "3.4.5"); + auto index = get_optional_property (artifact_loc, + index_prop); + if (uri == nullptr && index == nullptr) + { + object_spec_ref constraints ("artifactLocation", "3.4.2"); + return report_invalid_sarif_at_least_one_of (artifact_loc, + constraints, + uri_prop, + index_prop); + } + + if (uri) + { + // TODO: source language + out = m_output_mgr.new_file (uri->get_string (), nullptr); + return status::ok; + } + + return status::ok; +} + +/* Handle a "region" object (§3.30) within FILE, writing to OUT. */ + +enum status +sarif_replayer:: +handle_region_object (const json::object ®ion_obj, + libdiagnostics::file file, + libdiagnostics::physical_location &out) +{ + gcc_assert (file.m_inner); + + // §3.30.5 "startLine" property + const property_spec_ref start_line_prop ("region", "startLine", "3.30.5"); + libdiagnostics::physical_location start; + libdiagnostics::physical_location end; + if (auto start_line_jnum + = get_optional_property (region_obj, + start_line_prop)) + { + /* Text region defined by line/column properties. */ + const property_spec_ref start_column_prop + ("region", "startColumn", "3.30.6"); + if (auto start_column_jnum + = get_optional_property (region_obj, + start_column_prop)) + { + start = m_output_mgr.new_location_from_file_line_column + (file, start_line_jnum->get (), start_column_jnum->get ()); + } + else + start = m_output_mgr.new_location_from_file_and_line + (file, start_line_jnum->get ()); + + int end_line = start_line_jnum->get (); + const property_spec_ref end_line_prop ("region", "endLine", "3.30.7"); + if (auto end_line_jnum + = get_optional_property (region_obj, + end_line_prop)) + end_line = end_line_jnum->get (); + + const property_spec_ref end_column_prop ("region", "endColumn", "3.30.8"); + if (auto end_column_jnum + = get_optional_property (region_obj, + end_column_prop)) + { + /* SARIF's endColumn is 1 beyond the final column in the region, + whereas GCC's end columns are inclusive. */ + end = m_output_mgr.new_location_from_file_line_column + (file, end_line, end_column_jnum->get ()); + } + else + { + // missing "endColumn" means the whole of the rest of the row + end = m_output_mgr.new_location_from_file_and_line + (file, end_line); + } + + out = m_output_mgr.new_location_from_range (start, start, end); + } + + return status::ok; +} + +/* Handle a "logicalLocation" object (§3.33), using it to populate OUT. + Known limitations: + - doesn't yet handle "parentIndex" property (§3.33.8) +*/ + +enum status +sarif_replayer:: +handle_logical_location_object (const json::object &logical_loc_obj, + libdiagnostics::logical_location &out) +{ + const property_spec_ref name_prop ("logicalLocation", "name", "3.33.4"); + const char *short_name = nullptr; + if (auto name_jstr = get_optional_property (logical_loc_obj, + name_prop)) + short_name = name_jstr->get_string (); + + const property_spec_ref fqname_prop + ("logicalLocation", "fullyQualifiedName", "3.33.5"); + const char *fully_qualified_name = nullptr; + if (auto fully_qualified_name_jstr + = get_optional_property (logical_loc_obj, fqname_prop)) + fully_qualified_name = fully_qualified_name_jstr->get_string (); + + const property_spec_ref decorated_name_prop + ("logicalLocation", "decoratedName", "3.33.6"); + const char *decorated_name = nullptr; + if (auto decorated_name_jstr + = get_optional_property (logical_loc_obj, + decorated_name_prop)) + decorated_name = decorated_name_jstr->get_string (); + + // §3.33.7 "kind" property + const property_spec_ref kind_prop ("logicalLocation", "kind", "3.33.7"); + enum diagnostic_logical_location_kind_t kind + = DIAGNOSTIC_LOGICAL_LOCATION_KIND_FUNCTION; + if (auto kind_str = get_optional_property (logical_loc_obj, + kind_prop)) + { + const string_property_value + kind_values[] + = { + { "function", + DIAGNOSTIC_LOGICAL_LOCATION_KIND_FUNCTION }, + { "member", + DIAGNOSTIC_LOGICAL_LOCATION_KIND_MEMBER }, + { "module", + DIAGNOSTIC_LOGICAL_LOCATION_KIND_MODULE }, + { "namespace", + DIAGNOSTIC_LOGICAL_LOCATION_KIND_NAMESPACE }, + { "type", + DIAGNOSTIC_LOGICAL_LOCATION_KIND_TYPE }, + { "returnType", + DIAGNOSTIC_LOGICAL_LOCATION_KIND_RETURN_TYPE }, + { "parameter", + DIAGNOSTIC_LOGICAL_LOCATION_KIND_PARAMETER }, + { "variable", + DIAGNOSTIC_LOGICAL_LOCATION_KIND_VARIABLE } }; + auto result + = get_value_from_json_string + (*kind_str, kind_prop, kind_values, ARRAY_SIZE (kind_values)); + if (result.m_err != status::ok) + return result.m_err; + kind = result.m_val; + } + + libdiagnostics::logical_location parent; + out = m_output_mgr.new_logical_location (kind, + parent, + short_name, + fully_qualified_name, + decorated_name); + + return status::ok; +} + +// 3.52.3 reportingDescriptor lookup +// "For an example of the interaction between ruleId and rule.id, see §3.52.4." + +const json::object * +sarif_replayer:: +lookup_rule_by_id_in_tool (const char *rule_id, + const json::object &tool_obj, + const json::object *&tool_component_obj) +{ + auto driver_obj + = get_required_property (tool_obj, + property_spec_ref ("tool", "driver", + "3.18.2")); + if (!driver_obj) + return nullptr; + + if (auto rule_obj = lookup_rule_by_id_in_component (rule_id, *driver_obj)) + { + tool_component_obj = driver_obj; + return rule_obj; + } + + // TODO: also handle extensions + + return NULL; +} + +const json::object * +sarif_replayer:: +lookup_rule_by_id_in_component (const char *rule_id, + const json::object &tool_component_obj) +{ + const property_spec_ref rules ("toolComponent", "rules", "3.18.2"); + + auto rules_arr + = get_optional_property (tool_component_obj, rules); + if (!rules_arr) + return nullptr; + + for (auto element : *rules_arr) + { + const json::object *reporting_desc_obj + = require_object_for_element (*element, rules); + + /* reportingDescriptor objects (§3.49). */ + const property_spec_ref id ("reportingDescriptor", "id", "3.49.3"); + auto desc_id_jstr + = get_required_property (*reporting_desc_obj, id); + if (!desc_id_jstr) + return nullptr; + + if (!strcmp (rule_id, desc_id_jstr->get_string ())) + return reporting_desc_obj; + } + + /* Not found. */ + return nullptr; +} + +} // anonymous namespace + +/* Error-checking at the API boundary. */ + +#define FAIL_IF_NULL(PTR_ARG) \ + do { \ + GCC_DIAGNOSTIC_PUSH_IGNORED(-Wnonnull-compare) \ + if (!(PTR_ARG)) { \ + fprintf (stderr, "%s: %s must be non-NULL\n", \ + __func__, #PTR_ARG); \ + abort (); \ + } \ + GCC_DIAGNOSTIC_POP \ + } while (0) + +/* Public entrypoint. */ + +int +sarif_replay_path (const char *sarif_file, + diagnostic_manager *output_manager, + diagnostic_manager *control_manager, + const replay_options *options) +{ + FAIL_IF_NULL (sarif_file); + FAIL_IF_NULL (output_manager); + FAIL_IF_NULL (control_manager); + FAIL_IF_NULL (options); + + sarif_replayer r (libdiagnostics::manager (output_manager, false), + libdiagnostics::manager (control_manager, false)); + return (int)r.replay_file (sarif_file, *options); +} diff --git a/gcc/libsarifreplay.h b/gcc/libsarifreplay.h new file mode 100644 index 000000000000..4c6b255166d9 --- /dev/null +++ b/gcc/libsarifreplay.h @@ -0,0 +1,59 @@ +/* A pure C API for replaying SARIF as diagnostics. + Copyright (C) 2023-2024 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 +. */ + +#ifndef LIBSARIFREPLAY_H +#define LIBSARIFREPLAY_H + +#include "libdiagnostics.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +struct replay_options +{ + bool m_echo_file; + bool m_json_comments; + bool m_verbose; + enum diagnostic_colorize m_diagnostics_colorize; +}; + +/* Attempt to load a .sarif file from SARIF_FILE, and + replay the diagnostics to OUTPUT_MANAGER. + Report any problems to CONTROL_MANAGER (such as + file-not-found, malformed .sarif, etc). + If ALLOW_JSON_COMMENTS is true, then allow C/C++ style comments + in the file. + If ECHO_FILE, then dump the filename and contents to stderr. */ + +extern int +sarif_replay_path (const char *sarif_file, + diagnostic_manager *output_manager, + diagnostic_manager *control_manager, + const replay_options *options) + LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1) + LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (2) + LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (3) + LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (4); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* LIBSARIFREPLAY_H */ diff --git a/gcc/sarif-replay.cc b/gcc/sarif-replay.cc new file mode 100644 index 000000000000..e33457e1a67b --- /dev/null +++ b/gcc/sarif-replay.cc @@ -0,0 +1,239 @@ +/* A program for re-emitting diagnostics saved in SARIF form. + Copyright (C) 2022-2024 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 +. */ + +#include "config.h" +#define INCLUDE_VECTOR +#include "system.h" +#include "coretypes.h" +#include "version.h" +#include "intl.h" +#include "libdiagnostics++.h" +#include "libsarifreplay.h" + +static const char *progname; + +static void +set_defaults (replay_options &replay_opts) +{ + /* Defaults. */ + replay_opts.m_echo_file = false; + replay_opts.m_json_comments = false; + replay_opts.m_verbose = false; + replay_opts.m_diagnostics_colorize = DIAGNOSTIC_COLORIZE_IF_TTY; +} + +struct options +{ + options () + { + set_defaults (m_replay_opts); + } + + replay_options m_replay_opts; + std::vector m_sarif_filenames; +}; + +static void +print_version () +{ + printf (_("%s %s%s\n"), progname, pkgversion_string, + version_string); + printf ("Copyright %s 2024 Free Software Foundation, Inc.\n", + _("(C)")); + fputs (_("This is free software; see the source for copying conditions. There is NO\n\ +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\n"), + stdout); +} + +static const char *const usage_msg = ( +"sarif-replay [OPTIONS] FILE+\n" +"\n" +" \"Replay\" results from one or more .sarif files as if they were\n" +" GCC diagnostics\n" +"\n" +"Options:\n" +"\n" +" -fdiagnostics-color={never|always|auto}\n" +" Control colorization of diagnostics. Default: auto.\n" +"\n" +" -fjson-comments\n" +" Support C and C++ style comments in .sarif JSON files\n" +"\n" +" --verbose\n" +" Print notes about each .sarif file before and after replaying it.\n" +"\n" +" --echo-file\n" +" Print the filename and file contents to stderr before replaying it.\n" +"\n" +" -v, --version\n" +" Print version and exit.\n" +"\n" +" --usage\n" +" Print this message and exit.\n" +"\n"); + +static void +print_usage () +{ + fprintf (stderr, usage_msg); +} + +static bool +parse_options (int argc, char **argv, + options &opts, + libdiagnostics::text_sink control_text_sink) +{ + libdiagnostics::manager options_mgr; + options_mgr.set_tool_name ("sarif-replay"); + options_mgr.add_text_sink (stderr, DIAGNOSTIC_COLORIZE_NO/*IF_TTY*/); + + for (int i = 1; i < argc; ++i) + { + const char *option = argv[i]; + bool handled = false; + if (strcmp (option, "-fjson-comments") == 0) + { + opts.m_replay_opts.m_json_comments = true; + handled = true; + } + else if (strcmp (option, "--verbose") == 0) + { + opts.m_replay_opts.m_verbose = true; + handled = true; + } + else if (strcmp (option, "--usage") == 0) + { + print_usage (); + exit (0); + } + else if (strcmp (option, "--echo-file") == 0) + { + opts.m_replay_opts.m_echo_file = true; + handled = true; + } + else if (strcmp (option, "-fdiagnostics-color=never") == 0) + { + opts.m_replay_opts.m_diagnostics_colorize = DIAGNOSTIC_COLORIZE_NO; + control_text_sink.set_colorize + (opts.m_replay_opts.m_diagnostics_colorize); + handled = true; + } + else if (strcmp (option, "-fdiagnostics-color=always") == 0) + { + opts.m_replay_opts.m_diagnostics_colorize = DIAGNOSTIC_COLORIZE_YES; + control_text_sink.set_colorize + (opts.m_replay_opts.m_diagnostics_colorize); + handled = true; + } + else if (strcmp (option, "-fdiagnostics-color=auto") == 0) + { + opts.m_replay_opts.m_diagnostics_colorize + = DIAGNOSTIC_COLORIZE_IF_TTY; + control_text_sink.set_colorize + (opts.m_replay_opts.m_diagnostics_colorize); + handled = true; + } + else if (strcmp (option, "-v") == 0 + || strcmp (option, "--version") == 0) + { + print_version (); + exit (0); + } + + if (!handled) + { + if (option[0] == '-') + { + auto err = options_mgr.begin_diagnostic (DIAGNOSTIC_LEVEL_ERROR); + err.finish ("unrecognized option: %qs", option); + return false; + } + else + opts.m_sarif_filenames.push_back (option); + } + } + + if (opts.m_sarif_filenames.size () == 0) + { + auto err = options_mgr.begin_diagnostic (DIAGNOSTIC_LEVEL_ERROR); + err.finish ("need at least one .sarif file to dump"); + return false; + } + return true; +} + +static const char * +get_progname (const char *argv0) +{ + const char *p = argv0 + strlen (argv0); + while (p != argv0 && !IS_DIR_SEPARATOR (p[-1])) + --p; + return p; +} + +/* Entrypoint to sarif-replay command-line tool. */ + +int +main (int argc, char **argv) +{ + progname = get_progname (argv[0]); + xmalloc_set_program_name (progname); + + libdiagnostics::manager control_mgr; + + control_mgr.set_tool_name (progname); + + libdiagnostics::text_sink control_text_sink + = control_mgr.add_text_sink (stderr, DIAGNOSTIC_COLORIZE_IF_TTY); + + options opts; + if (!parse_options (argc, argv, opts, control_text_sink)) + { + print_usage (); + return -1; + } + + int failures = 0; + for (auto filename : opts.m_sarif_filenames) + { + if (opts.m_replay_opts.m_verbose) + { + auto note = control_mgr.begin_diagnostic (DIAGNOSTIC_LEVEL_NOTE); + note.finish ("about to replay %qs...", filename); + } + libdiagnostics::manager playback_mgr; + playback_mgr.add_text_sink (stderr, + opts.m_replay_opts.m_diagnostics_colorize); + + int result = sarif_replay_path (filename, + playback_mgr.m_inner, + control_mgr.m_inner, + &opts.m_replay_opts); + if (result) + ++failures; + + if (opts.m_replay_opts.m_verbose) + { + auto note = control_mgr.begin_diagnostic (DIAGNOSTIC_LEVEL_NOTE); + note.finish ("...finished replaying %qs", filename); + } + } + return failures; +} diff --git a/gcc/sarif-spec-urls.def b/gcc/sarif-spec-urls.def new file mode 100644 index 000000000000..2ffa4a84aaa1 --- /dev/null +++ b/gcc/sarif-spec-urls.def @@ -0,0 +1,496 @@ +/* Generated by regenerate-sarif-spec-index.py. */ + +static const char * const sarif_spec_base_url + = "https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/sarif-v2.1.0-errata01-os-complete.html"; + +static const struct ref_anchor +{ + const char *m_ref; + const char *m_anchor; +} sarif_spec_anchor_arr[] = { + { "1.1", "_Toc141790658" }, + { "1.2", "_Toc141790659" }, + { "1.3", "_Toc141790660" }, + { "1.4", "_Toc141790661" }, + { "1.5", "_Toc141790662" }, + { "2.1", "_Toc141790664" }, + { "2.2", "_Toc141790665" }, + { "2.3", "_Toc141790666" }, + { "2.4", "_Toc141790667" }, + { "2.5", "_Toc141790668" }, + { "3.1", "_Toc141790670" }, + { "3.2", "_Toc141790671" }, + { "3.3", "_Toc141790672" }, + { "3.3.1", "_Toc141790673" }, + { "3.3.2", "_Toc141790674" }, + { "3.3.3", "_Toc141790675" }, + { "3.3.4", "_Toc141790676" }, + { "3.4", "_Toc141790677" }, + { "3.4.1", "_Toc141790678" }, + { "3.4.2", "_Toc141790679" }, + { "3.4.3", "_Toc141790680" }, + { "3.4.4", "_Toc141790681" }, + { "3.4.5", "_Toc141790682" }, + { "3.4.6", "_Toc141790683" }, + { "3.5", "_Toc141790685" }, + { "3.5.1", "_Toc141790686" }, + { "3.5.2", "_Toc141790687" }, + { "3.5.3", "_Toc141790688" }, + { "3.5.4", "_Toc141790689" }, + { "3.5.4.1", "_Toc141790690" }, + { "3.6", "_Toc141790692" }, + { "3.7", "_Toc141790693" }, + { "3.7.1", "_Toc141790694" }, + { "3.7.2", "_Toc141790695" }, + { "3.7.4", "_Toc141790697" }, + { "3.8", "_Toc141790698" }, + { "3.8.1", "_Toc141790699" }, + { "3.8.2", "_Toc141790700" }, + { "3.8.2.1", "_Toc141790701" }, + { "3.8.2.2", "_Toc141790702" }, + { "3.9", "_Toc141790703" }, + { "3.10", "_Toc141790704" }, + { "3.10.1", "_Toc141790705" }, + { "3.10.2", "_Toc141790706" }, + { "3.10.3", "_Toc141790707" }, + { "3.11", "_Toc141790709" }, + { "3.11.1", "_Toc141790710" }, + { "3.11.2", "_Toc141790711" }, + { "3.11.3", "_Toc141790712" }, + { "3.11.4", "_Toc141790713" }, + { "3.11.4.1", "_Toc141790714" }, + { "3.11.4.2", "_Toc141790715" }, + { "3.11.5", "_Toc141790716" }, + { "3.11.6", "_Toc141790717" }, + { "3.11.7", "_Toc141790718" }, + { "3.11.8", "_Toc141790719" }, + { "3.11.9", "_Toc141790720" }, + { "3.11.10", "_Toc141790721" }, + { "3.11.11", "_Toc141790722" }, + { "3.12", "_Toc141790723" }, + { "3.12.1", "_Toc141790724" }, + { "3.12.3", "_Toc141790726" }, + { "3.12.4", "_Toc141790727" }, + { "3.13", "_Toc141790728" }, + { "3.13.1", "_Toc141790729" }, + { "3.13.2", "_Toc141790730" }, + { "3.13.3", "_Toc141790731" }, + { "3.13.4", "_Toc141790732" }, + { "3.14", "_Toc141790734" }, + { "3.14.1", "_Toc141790735" }, + { "3.14.3", "_Toc141790737" }, + { "3.14.4", "_Toc141790738" }, + { "3.14.5", "_Toc141790739" }, + { "3.14.6", "_Toc141790740" }, + { "3.14.7", "_Toc141790741" }, + { "3.14.8", "_Toc141790742" }, + { "3.14.9", "_Toc141790743" }, + { "3.14.10", "_Toc141790744" }, + { "3.14.11", "_Toc141790745" }, + { "3.14.12", "_Toc141790746" }, + { "3.14.14", "_Toc141790748" }, + { "3.14.15", "_Toc141790749" }, + { "3.14.16", "_Toc141790750" }, + { "3.14.17", "_Toc141790751" }, + { "3.14.18", "_Toc141790752" }, + { "3.14.19", "_Toc141790753" }, + { "3.14.20", "_Toc141790754" }, + { "3.14.21", "_Toc141790755" }, + { "3.14.22", "_Toc141790756" }, + { "3.14.23", "_Toc141790757" }, + { "3.14.24", "_Toc141790758" }, + { "3.14.26", "_Toc141790760" }, + { "3.14.27", "_Toc141790761" }, + { "3.14.28", "_Toc141790762" }, + { "3.15.1", "_Toc141790764" }, + { "3.15.2", "_Toc141790765" }, + { "3.15.3", "_Toc141790766" }, + { "3.16.1", "_Toc141790768" }, + { "3.16.2", "_Toc141790769" }, + { "3.16.3", "_Toc141790770" }, + { "3.16.4", "_Toc141790771" }, + { "3.16.5", "_Toc141790772" }, + { "3.17", "_Toc141790773" }, + { "3.17.1", "_Toc141790774" }, + { "3.17.2", "_Toc141790775" }, + { "3.17.3", "_Toc141790776" }, + { "3.17.4", "_Toc141790777" }, + { "3.17.5", "_Toc141790778" }, + { "3.18", "_Toc141790779" }, + { "3.18.1", "_Toc141790780" }, + { "3.18.2", "_Toc141790781" }, + { "3.18.3", "_Toc141790782" }, + { "3.19", "_Toc141790783" }, + { "3.19.1", "_Toc141790784" }, + { "3.19.2", "_Toc141790785" }, + { "3.19.3", "_Toc141790786" }, + { "3.19.4", "_Toc141790787" }, + { "3.19.5", "_Toc141790788" }, + { "3.19.6", "_Toc141790789" }, + { "3.19.7", "_Toc141790790" }, + { "3.19.8", "_Toc141790791" }, + { "3.19.9", "_Toc141790792" }, + { "3.19.10", "_Toc141790793" }, + { "3.19.11", "_Toc141790794" }, + { "3.19.12", "_Toc141790795" }, + { "3.19.13", "_Toc141790796" }, + { "3.19.15", "_Toc141790798" }, + { "3.19.16", "_Toc141790799" }, + { "3.19.17", "_Toc141790800" }, + { "3.19.18", "_Toc141790801" }, + { "3.19.19", "_Toc141790802" }, + { "3.19.20", "_Toc141790803" }, + { "3.19.21", "_Toc141790804" }, + { "3.19.22", "_Toc141790805" }, + { "3.19.23", "_Toc141790806" }, + { "3.19.24", "_Toc141790807" }, + { "3.19.25", "_Toc141790808" }, + { "3.19.26", "_Toc141790809" }, + { "3.19.27", "_Toc141790810" }, + { "3.19.28", "_Toc141790811" }, + { "3.19.29", "_Toc141790812" }, + { "3.19.30", "_Toc141790813" }, + { "3.19.33", "_Toc141790816" }, + { "3.20", "_Toc141790817" }, + { "3.20.1", "_Toc141790818" }, + { "3.20.2", "_Toc141790819" }, + { "3.20.3", "_Toc141790820" }, + { "3.20.4", "_Toc141790821" }, + { "3.20.7", "_Toc141790824" }, + { "3.20.8", "_Toc141790825" }, + { "3.20.9", "_Toc141790826" }, + { "3.20.10", "_Toc141790827" }, + { "3.20.11", "_Toc141790828" }, + { "3.20.12", "_Toc141790829" }, + { "3.20.14", "_Toc141790831" }, + { "3.20.15", "_Toc141790832" }, + { "3.20.16", "_Toc141790833" }, + { "3.20.17", "_Toc141790834" }, + { "3.20.18", "_Toc141790835" }, + { "3.20.19", "_Toc141790836" }, + { "3.20.20", "_Toc141790837" }, + { "3.21", "_Toc141790841" }, + { "3.21.1", "_Toc141790842" }, + { "3.21.2", "_Toc141790843" }, + { "3.21.3", "_Toc141790844" }, + { "3.21.4", "_Toc141790845" }, + { "3.21.5", "_Toc141790846" }, + { "3.22", "_Toc141790847" }, + { "3.22.1", "_Toc141790848" }, + { "3.22.2", "_Toc141790849" }, + { "3.22.3", "_Toc141790850" }, + { "3.22.4", "_Toc141790851" }, + { "3.23", "_Toc141790852" }, + { "3.23.1", "_Toc141790853" }, + { "3.23.2", "_Toc141790854" }, + { "3.23.3", "_Toc141790855" }, + { "3.23.4", "_Toc141790856" }, + { "3.23.5", "_Toc141790857" }, + { "3.23.6", "_Toc141790858" }, + { "3.23.7", "_Toc141790859" }, + { "3.23.8", "_Toc141790860" }, + { "3.24", "_Toc141790861" }, + { "3.24.1", "_Toc141790862" }, + { "3.24.2", "_Toc141790863" }, + { "3.24.3", "_Toc141790864" }, + { "3.24.4", "_Toc141790865" }, + { "3.24.5", "_Toc141790866" }, + { "3.24.6", "_Toc141790867" }, + { "3.24.7", "_Toc141790868" }, + { "3.24.8", "_Toc141790869" }, + { "3.24.9", "_Toc141790870" }, + { "3.24.10", "_Toc141790871" }, + { "3.24.10.1", "_Toc141790872" }, + { "3.24.11", "_Toc141790874" }, + { "3.24.12", "_Toc141790875" }, + { "3.24.13", "_Toc141790876" }, + { "3.25", "_Toc141790877" }, + { "3.25.1", "_Toc141790878" }, + { "3.25.2", "_Toc141790879" }, + { "3.26", "_Toc141790880" }, + { "3.26.1", "_Toc141790881" }, + { "3.26.2", "_Toc141790882" }, + { "3.26.3", "_Toc141790883" }, + { "3.26.4", "_Toc141790884" }, + { "3.26.5", "_Toc141790885" }, + { "3.26.6", "_Toc141790886" }, + { "3.26.7", "_Toc141790887" }, + { "3.27", "_Toc141790888" }, + { "3.27.1", "_Toc141790889" }, + { "3.27.3", "_Toc141790891" }, + { "3.27.4", "_Toc141790892" }, + { "3.27.5", "_Toc141790893" }, + { "3.27.6", "_Toc141790894" }, + { "3.27.7", "_Toc141790895" }, + { "3.27.8", "_Toc141790896" }, + { "3.27.9", "_Toc141790897" }, + { "3.27.10", "_Toc141790898" }, + { "3.27.11", "_Toc141790899" }, + { "3.27.12", "_Toc141790900" }, + { "3.27.13", "_Toc141790901" }, + { "3.27.14", "_Toc141790902" }, + { "3.27.15", "_Toc141790903" }, + { "3.27.16", "_Toc141790904" }, + { "3.27.17", "_Toc141790905" }, + { "3.27.18", "_Toc141790906" }, + { "3.27.19", "_Toc141790907" }, + { "3.27.20", "_Toc141790908" }, + { "3.27.21", "_Toc141790909" }, + { "3.27.22", "_Toc141790910" }, + { "3.27.23", "_Toc141790911" }, + { "3.27.24", "_Toc141790912" }, + { "3.27.25", "_Toc141790913" }, + { "3.27.26", "_Toc141790914" }, + { "3.27.27", "_Toc141790915" }, + { "3.27.28", "_Toc141790916" }, + { "3.27.29", "_Toc141790917" }, + { "3.27.30", "_Toc141790918" }, + { "3.27.31", "_Toc141790919" }, + { "3.28", "_Toc141790920" }, + { "3.28.1", "_Toc141790921" }, + { "3.28.2", "_Toc141790922" }, + { "3.28.3", "_Toc141790923" }, + { "3.28.4", "_Toc141790924" }, + { "3.28.5", "_Toc141790925" }, + { "3.28.6", "_Toc141790926" }, + { "3.28.7", "_Toc141790927" }, + { "3.29", "_Toc141790928" }, + { "3.29.1", "_Toc141790929" }, + { "3.29.2", "_Toc141790930" }, + { "3.29.3", "_Toc141790931" }, + { "3.29.4", "_Toc141790932" }, + { "3.29.5", "_Toc141790933" }, + { "3.29.6", "_Toc141790934" }, + { "3.30", "_Toc141790935" }, + { "3.30.1", "_Toc141790936" }, + { "3.30.2", "_Toc141790937" }, + { "3.30.3", "_Toc141790938" }, + { "3.30.5", "_Toc141790940" }, + { "3.30.6", "_Toc141790941" }, + { "3.30.7", "_Toc141790942" }, + { "3.30.8", "_Toc141790943" }, + { "3.30.9", "_Toc141790944" }, + { "3.30.10", "_Toc141790945" }, + { "3.30.11", "_Toc141790946" }, + { "3.30.12", "_Toc141790947" }, + { "3.30.13", "_Toc141790948" }, + { "3.30.14", "_Toc141790949" }, + { "3.30.15", "_Toc141790950" }, + { "3.31", "_Toc141790951" }, + { "3.31.1", "_Toc141790952" }, + { "3.31.3", "_Toc141790954" }, + { "3.32", "_Toc141790955" }, + { "3.32.1", "_Toc141790956" }, + { "3.32.2", "_Toc141790957" }, + { "3.32.3", "_Toc141790958" }, + { "3.32.4", "_Toc141790959" }, + { "3.32.5", "_Toc141790960" }, + { "3.32.6", "_Toc141790961" }, + { "3.32.7", "_Toc141790962" }, + { "3.32.8", "_Toc141790963" }, + { "3.32.9", "_Toc141790964" }, + { "3.32.10", "_Toc141790965" }, + { "3.32.11", "_Toc141790966" }, + { "3.32.12", "_Toc141790967" }, + { "3.32.13", "_Toc141790968" }, + { "3.33", "_Toc141790969" }, + { "3.33.1", "_Toc141790970" }, + { "3.33.2", "_Toc141790971" }, + { "3.33.3", "_Toc141790972" }, + { "3.33.4", "_Toc141790973" }, + { "3.33.5", "_Toc141790974" }, + { "3.33.6", "_Toc141790975" }, + { "3.33.7", "_Toc141790976" }, + { "3.33.8", "_Toc141790977" }, + { "3.34", "_Toc141790978" }, + { "3.34.1", "_Toc141790979" }, + { "3.34.2", "_Toc141790980" }, + { "3.34.3", "_Toc141790981" }, + { "3.34.4", "_Toc141790982" }, + { "3.35", "_Toc141790983" }, + { "3.35.1", "_Toc141790984" }, + { "3.35.2", "_Toc141790985" }, + { "3.35.3", "_Toc141790986" }, + { "3.35.4", "_Toc141790987" }, + { "3.35.5", "_Toc141790988" }, + { "3.35.6", "_Toc141790989" }, + { "3.36", "_Toc141790990" }, + { "3.36.1", "_Toc141790991" }, + { "3.36.2", "_Toc141790992" }, + { "3.36.3", "_Toc141790993" }, + { "3.37", "_Toc141790994" }, + { "3.37.1", "_Toc141790995" }, + { "3.37.2", "_Toc141790996" }, + { "3.37.3", "_Toc141790997" }, + { "3.37.4", "_Toc141790998" }, + { "3.37.5", "_Toc141790999" }, + { "3.37.6", "_Toc141791000" }, + { "3.38", "_Toc141791001" }, + { "3.38.1", "_Toc141791002" }, + { "3.38.2", "_Toc141791003" }, + { "3.38.3", "_Toc141791004" }, + { "3.38.4", "_Toc141791005" }, + { "3.38.5", "_Toc141791006" }, + { "3.38.6", "_Toc141791007" }, + { "3.38.7", "_Toc141791008" }, + { "3.38.8", "_Toc141791009" }, + { "3.38.9", "_Toc141791010" }, + { "3.38.10", "_Toc141791011" }, + { "3.38.11", "_Toc141791012" }, + { "3.38.12", "_Toc141791013" }, + { "3.38.13", "_Toc141791014" }, + { "3.38.14", "_Toc141791015" }, + { "3.39", "_Toc141791016" }, + { "3.39.1", "_Toc141791017" }, + { "3.39.2", "_Toc141791018" }, + { "3.39.3", "_Toc141791019" }, + { "3.39.4", "_Toc141791020" }, + { "3.40", "_Toc141791021" }, + { "3.40.1", "_Toc141791022" }, + { "3.40.2", "_Toc141791023" }, + { "3.40.3", "_Toc141791024" }, + { "3.40.4", "_Toc141791025" }, + { "3.40.5", "_Toc141791026" }, + { "3.41", "_Toc141791027" }, + { "3.41.1", "_Toc141791028" }, + { "3.41.2", "_Toc141791029" }, + { "3.41.3", "_Toc141791030" }, + { "3.41.4", "_Toc141791031" }, + { "3.41.5", "_Toc141791032" }, + { "3.42", "_Toc141791033" }, + { "3.42.1", "_Toc141791034" }, + { "3.42.2", "_Toc141791035" }, + { "3.42.3", "_Toc141791036" }, + { "3.42.4", "_Toc141791037" }, + { "3.42.5", "_Toc141791038" }, + { "3.42.6", "_Toc141791039" }, + { "3.42.7", "_Toc141791040" }, + { "3.42.8", "_Toc141791041" }, + { "3.43", "_Toc141791042" }, + { "3.43.1", "_Toc141791043" }, + { "3.43.2", "_Toc141791044" }, + { "3.43.3", "_Toc141791045" }, + { "3.43.4", "_Toc141791046" }, + { "3.43.5", "_Toc141791047" }, + { "3.44", "_Toc141791048" }, + { "3.44.1", "_Toc141791049" }, + { "3.44.2", "_Toc141791050" }, + { "3.44.3", "_Toc141791051" }, + { "3.45", "_Toc141791052" }, + { "3.45.1", "_Toc141791053" }, + { "3.45.2", "_Toc141791054" }, + { "3.45.3", "_Toc141791055" }, + { "3.45.4", "_Toc141791056" }, + { "3.45.5", "_Toc141791057" }, + { "3.46", "_Toc141791058" }, + { "3.46.1", "_Toc141791059" }, + { "3.46.2", "_Toc141791060" }, + { "3.46.3", "_Toc141791061" }, + { "3.46.4", "_Toc141791062" }, + { "3.46.5", "_Toc141791063" }, + { "3.46.6", "_Toc141791064" }, + { "3.46.7", "_Toc141791065" }, + { "3.46.8", "_Toc141791066" }, + { "3.46.9", "_Toc141791067" }, + { "3.47", "_Toc141791068" }, + { "3.47.1", "_Toc141791069" }, + { "3.47.2", "_Toc141791070" }, + { "3.47.3", "_Toc141791071" }, + { "3.47.4", "_Toc141791072" }, + { "3.47.5", "_Toc141791073" }, + { "3.47.6", "_Toc141791074" }, + { "3.47.7", "_Toc141791075" }, + { "3.47.8", "_Toc141791076" }, + { "3.47.9", "_Toc141791077" }, + { "3.48", "_Toc141791078" }, + { "3.48.1", "_Toc141791079" }, + { "3.48.2", "_Toc141791080" }, + { "3.48.3", "_Toc141791081" }, + { "3.48.4", "_Toc141791082" }, + { "3.48.5", "_Toc141791083" }, + { "3.48.6", "_Toc141791084" }, + { "3.48.7", "_Toc141791085" }, + { "3.49", "_Toc141791086" }, + { "3.49.1", "_Toc141791087" }, + { "3.49.2", "_Toc141791088" }, + { "3.49.3", "_Toc141791089" }, + { "3.49.4", "_Toc141791090" }, + { "3.49.5", "_Toc141791091" }, + { "3.49.6", "_Toc141791092" }, + { "3.49.7", "_Toc141791093" }, + { "3.49.8", "_Toc141791094" }, + { "3.49.9", "_Toc141791095" }, + { "3.49.10", "_Toc141791096" }, + { "3.49.11", "_Toc141791097" }, + { "3.49.12", "_Toc141791098" }, + { "3.49.13", "_Toc141791099" }, + { "3.49.14", "_Toc141791100" }, + { "3.49.15", "_Toc141791101" }, + { "3.50", "_Toc141791102" }, + { "3.50.1", "_Toc141791103" }, + { "3.50.2", "_Toc141791104" }, + { "3.50.3", "_Toc141791105" }, + { "3.50.4", "_Toc141791106" }, + { "3.50.5", "_Toc141791107" }, + { "3.51", "_Toc141791108" }, + { "3.51.1", "_Toc141791109" }, + { "3.51.2", "_Toc141791110" }, + { "3.51.3", "_Toc141791111" }, + { "3.52.1", "_Toc141791113" }, + { "3.52.2", "_Toc141791114" }, + { "3.52.3", "_Toc141791115" }, + { "3.52.4", "_Toc141791116" }, + { "3.52.5", "_Toc141791117" }, + { "3.52.6", "_Toc141791118" }, + { "3.52.7", "_Toc141791119" }, + { "3.53.1", "_Toc141791121" }, + { "3.53.2", "_Toc141791122" }, + { "3.53.3", "_Toc141791123" }, + { "3.53.4", "_Toc141791124" }, + { "3.54", "_Toc141791125" }, + { "3.54.1", "_Toc141791126" }, + { "3.54.2", "_Toc141791127" }, + { "3.54.3", "_Toc141791128" }, + { "3.54.4", "_Toc141791129" }, + { "3.54.5", "_Toc141791130" }, + { "3.55", "_Toc141791131" }, + { "3.55.1", "_Toc141791132" }, + { "3.55.2", "_Toc141791133" }, + { "3.55.3", "_Toc141791134" }, + { "3.56", "_Toc141791135" }, + { "3.56.1", "_Toc141791136" }, + { "3.56.2", "_Toc141791137" }, + { "3.56.3", "_Toc141791138" }, + { "3.57", "_Toc141791139" }, + { "3.57.1", "_Toc141791140" }, + { "3.57.2", "_Toc141791141" }, + { "3.57.3", "_Toc141791142" }, + { "3.57.4", "_Toc141791143" }, + { "3.58", "_Toc141791144" }, + { "3.58.1", "_Toc141791145" }, + { "3.58.2", "_Toc141791146" }, + { "3.58.3", "_Toc141791147" }, + { "3.58.4", "_Toc141791148" }, + { "3.58.5", "_Toc141791149" }, + { "3.58.6", "_Toc141791150" }, + { "3.58.7", "_Toc141791151" }, + { "3.58.8", "_Toc141791152" }, + { "3.58.9", "_Toc141791153" }, + { "3.59", "_Toc141791154" }, + { "3.59.1", "_Toc141791155" }, + { "3.59.2", "_Toc141791156" }, + { "3.59.3", "_Toc141791157" }, + { "3.59.4", "_Toc141791158" }, + { "3.59.5", "_Toc141791159" }, + { "4.1", "_Toc141791161" }, + { "4.3", "_Toc141791163" }, + { "4.3.1", "_Toc141791164" }, + { "4.3.2", "_Toc141791165" }, + { "4.3.3", "_Toc141791166" }, + { "4.3.4", "_Toc141791167" }, + { "4.3.5", "_Toc141791168" }, + { "4.3.6", "_Toc141791169" }, + { "5.1", "_Toc141791171" }, + { "5.5", "_Toc141791175" }, + { "5.8", "_Toc141791178" }, +}; \ No newline at end of file diff --git a/gcc/testsuite/lib/gcc-dg.exp b/gcc/testsuite/lib/gcc-dg.exp index 33b7e5a1e2c2..25dbaad1cbbc 100644 --- a/gcc/testsuite/lib/gcc-dg.exp +++ b/gcc/testsuite/lib/gcc-dg.exp @@ -273,6 +273,10 @@ proc gcc-dg-test-1 { target_compile prog do_what extra_tool_flags } { # created or not. If it was, dg.exp will try to run it. catch { remote_file build delete $output_file } } + "replay-sarif" { + set compile_type "none" + set output_file "" + } default { perror "$do_what: not a valid dg-do keyword" return "" diff --git a/gcc/testsuite/lib/sarif-replay-dg.exp b/gcc/testsuite/lib/sarif-replay-dg.exp new file mode 100644 index 000000000000..55084005c637 --- /dev/null +++ b/gcc/testsuite/lib/sarif-replay-dg.exp @@ -0,0 +1,90 @@ +# Copyright (C) 2012-2024 Free Software Foundation, Inc. + +# This program 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 of the License, or +# (at your option) any later version. +# +# This program 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 +# . + +load_lib gcc-dg.exp + +# Define sarif-replay callbacks for dg.exp. + +proc sarif-replay-dg-test { prog do_what extra_tool_flags } { + upvar dg-do-what dg-do-what + + set result \ + [gcc-dg-test-1 sarif-replay_target_compile $prog "replay-sarif" $extra_tool_flags] + + set comp_output [lindex $result 0] + set output_file [lindex $result 1] + + return [list $comp_output $output_file] +} + +proc sarif-replay-dg-prune { system text } { + return [gcc-dg-prune $system $text] +} + +# Utility routines. + +# +# Modified dg-runtest that can cycle through a list of optimization options +# as c-torture does. +# + +proc sarif-replay-dg-runtest { testcases flags default-extra-flags } { + global runtests + + foreach test $testcases { + # If we're only testing specific files and this isn't one of + # them, skip it. + if ![runtest_file_p $runtests $test] { + continue + } + + # Use TORTURE_OPTIONS to cycle through an option list. + if [torture-options-exist] then { + global torture_with_loops + set option_list $torture_with_loops + } else { + set option_list { "" } + } + + set nshort [file tail [file dirname $test]]/[file tail $test] + + foreach flags_t $option_list { + verbose "Testing $nshort, $flags $flags_t" 1 + dg-test $test "$flags $flags_t" ${default-extra-flags} + } + } +} + +# +# sarif-replay_load -- wrapper around default sarif-replay_load to handle tests that +# require program arguments passed to them. +# + +if { [info procs sarif-replay_load] != [list] \ + && [info procs prev_sarif-replay_load] == [list] } { + rename sarif-replay_load prev_sarif-replay_load + + proc sarif-replay_load { program args } { + global SARIF_REPLAY_EXECUTE_ARGS + if [info exists SARIF_REPLAY_EXECUTE_ARGS] then { + set args [concat "{$SARIF_REPLAY_EXECUTE_ARGS}"] + } + #print "Running: $program [lindex $args 0]" + set result [eval [list prev_sarif-replay_load $program] $args ] + return $result + } +} + diff --git a/gcc/testsuite/lib/sarif-replay.exp b/gcc/testsuite/lib/sarif-replay.exp new file mode 100644 index 000000000000..e757ad8ecfc3 --- /dev/null +++ b/gcc/testsuite/lib/sarif-replay.exp @@ -0,0 +1,204 @@ +# Copyright (C) 2012-2024 Free Software Foundation, Inc. + +# This program 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 of the License, or +# (at your option) any later version. +# +# This program 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 +# . + +# +# sarif-replay support library routines +# + +load_lib prune.exp +load_lib gcc-defs.exp +load_lib timeout.exp +load_lib target-libpath.exp + +# +# SARIF_REPLAY_UNDER_TEST is the compiler under test. +# + +set sarif-replay_compile_options "" + + +# +# sarif-replay_version -- extract and print the version number of the compiler +# + +proc sarif-replay_version { } { + global SARIF_REPLAY_UNDER_TEST + + sarif-replay_init + + # ignore any arguments after the command + set compiler [lindex $SARIF_REPLAY_UNDER_TEST 0] + + # verify that the compiler exists + if { [is_remote host] || [which $compiler] != 0 } then { + set tmp [remote_exec host "$compiler -v"] + set status [lindex $tmp 0] + set output [lindex $tmp 1] + regexp " version \[^\n\r\]*" $output version + if { $status == 0 && [info exists version] } then { + if [is_remote host] { + clone_output "$compiler $version\n" + } else { + clone_output "[which $compiler] $version\n" + } + } else { + clone_output "Couldn't determine version of [which $compiler]\n" + } + } else { + # compiler does not exist (this should have already been detected) + warning "$compiler does not exist" + } +} + +# +# sarif-replay_include_flags -- include flags for the gcc tree structure +# + +proc sarif-replay_include_flags { paths } { + global srcdir + global SARIF-REPLAY_INCLUDE_CXX_FLAGS + global TESTING_IN_BUILD_TREE + + set flags "" + + if { [is_remote host] || ![info exists TESTING_IN_BUILD_TREE] } { + return "${flags}" + } + + if [info exists SARIF-REPLAY_INCLUDE_CXX_FLAGS] { + set include_cxx_flags $SARIF-REPLAY_INCLUDE_CXX_FLAGS + } else { + set include_cxx_flags 0 + } + + set gccpath ${paths} + + if { $gccpath != "" } { + if [file exists "${gccpath}/libphobos/libdruntime"] { + append flags "-I${gccpath}/libphobos/libdruntime " + } + } + append flags "-I${srcdir}/../../libphobos/libdruntime " + append flags "-I${srcdir}/../../libphobos/src " + + # For the tests that mix C++ and D, need to know where headers are located. + if $include_cxx_flags { + set odir [lookfor_file ${gccpath} libstdc++-v3] + if { ${odir} != "" && [file exists ${odir}/scripts/testsuite_flags] } { + set cxxflags [exec sh ${odir}/scripts/testsuite_flags --build-includes] + set idx [lsearch $cxxflags "-nostdinc++"] + append flags [lreplace $cxxflags $idx $idx] + } + } + + return "$flags" +} + +# +# sarif-replay_init -- called at the start of each subdir of tests +# + +proc sarif-replay_init { args } { + global sarif-replay_initialized + global base_dir + global tmpdir + global libdir + global gluefile wrap_flags + global TOOL_EXECUTABLE + global SARIF_REPLAY_UNDER_TEST + global TESTING_IN_BUILD_TREE + global gcc_warning_prefix + global gcc_error_prefix + + # We set LC_ALL and LANG to C so that we get the same error messages as expected. + setenv LC_ALL C + setenv LANG C + + if ![info exists SARIF_REPLAY_UNDER_TEST] then { + if [info exists TOOL_EXECUTABLE] { + set SARIF_REPLAY_UNDER_TEST $TOOL_EXECUTABLE + } else { + if { [is_remote host] || ! [info exists TESTING_IN_BUILD_TREE] } { + set SARIF_REPLAY_UNDER_TEST [transform sarif-replay] + } else { + set SARIF_REPLAY_UNDER_TEST [findfile $base_dir/../../sarif-replay "$base_dir/../../sarif-replay" [findfile $base_dir/sarif-replay "$base_dir/sarif-replay" [transform sarif-replay]]] + } + } + } + + verbose "SARIF_REPLAY_UNDER_TEST: $SARIF_REPLAY_UNDER_TEST" 2 + + if ![is_remote host] { + if { [which $SARIF_REPLAY_UNDER_TEST] == 0 } then { + perror "SARIF_REPLAY_UNDER_TEST ($SARIF_REPLAY_UNDER_TEST) does not exist" + exit 1 + } + } + if ![info exists tmpdir] { + set tmpdir "/tmp" + } + + if [info exists gluefile] { + unset gluefile + } + + set gcc_warning_prefix "warning:" + set gcc_error_prefix "(fatal )?error:" + + verbose "sarif-replay is initialized" 3 +} + +# +# sarif-replay_target_compile -- compile a source file +# + +proc sarif-replay_target_compile { source dest type options } { + global tmpdir + global gluefile wrap_flags + global SARIF_REPLAY_UNDER_TEST + global TOOL_OPTIONS + global TEST_ALWAYS_FLAGS + + if { [target_info needs_status_wrapper] != "" && [info exists gluefile] } { + lappend options "libs=${gluefile}" + lappend options "ldflags=${wrap_flags}" + } + + set always_sarif_replay_flags "" + + # FIXME: strip out -fdiagnostics-plain-output from TEST_ALWAYS_FLAGS + # rather than hacking this out: + # + # TEST_ALWAYS_FLAGS are flags that should be passed to every + # compilation. They are passed first to allow individual + # tests to override them. + ## if [info exists TEST_ALWAYS_FLAGS] { + ## lappend always_sarif_replay_flags "additional_flags=$TEST_ALWAYS_FLAGS" + ## } + + if [info exists TOOL_OPTIONS] { + lappend always_sarif_replay_flags "additional_flags=$TOOL_OPTIONS" + } + + verbose "always_sarif_replay_flags set to: $always_sarif_replay_flags" + + lappend options "timeout=[timeout_value]" + lappend options "compiler=$SARIF_REPLAY_UNDER_TEST" + + set options [concat "$always_sarif_replay_flags" $options] + set options [dg-additional-files-options $options $source $dest $type] + return [target_compile $source $dest $type $options] +} diff --git a/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.1-not-an-object.sarif b/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.1-not-an-object.sarif new file mode 100644 index 000000000000..4743bad3ba3c --- /dev/null +++ b/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.1-not-an-object.sarif @@ -0,0 +1,6 @@ +[ null ] // { dg-error "expected a sarifLog object as the top-level value \\\[SARIF v2.1.0 §3.1\\\]" } + +/* { dg-begin-multiline-output "" } + 1 | [ null ] + | ^~~~~~~~ + { dg-end-multiline-output "" } */ diff --git a/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.11.11-malformed-placeholder.sarif b/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.11.11-malformed-placeholder.sarif new file mode 100644 index 000000000000..72da185de0dc --- /dev/null +++ b/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.11.11-malformed-placeholder.sarif @@ -0,0 +1,15 @@ +{ + "version": "2.1.0", + "runs": [{ + "tool": { "driver": { "name": "example" } }, + "results": [ + { "message": { "text" : "before {} after" }, /* { dg-error "unescaped '\\\{' within message string \\\[SARIF v2.1.0 §3.11.11\\\]" } */ + "locations": [] } + ] + }] +} + +/* { dg-begin-multiline-output "" } + 6 | { "message": { "text" : "before {} after" }, + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + { dg-end-multiline-output "" } */ diff --git a/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.11.11-missing-arguments-for-placeholders.sarif b/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.11.11-missing-arguments-for-placeholders.sarif new file mode 100644 index 000000000000..c4354d4ef23d --- /dev/null +++ b/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.11.11-missing-arguments-for-placeholders.sarif @@ -0,0 +1,14 @@ +{ + "version": "2.1.0", + "runs": [{ + "tool": { "driver": { "name": "example" } }, + "results": [ + { "message": { "text" : "the {0} {1} fox jumps over the {2} dog" } } /* { dg-error "message string contains placeholder '\\{0\\}' but message object has no 'arguments' property \\\[SARIF v2.1.0 §3.11.11\\\]" } */ + ] + }] +} + +/* { dg-begin-multiline-output "" } + 6 | { "message": { "text" : "the {0} {1} fox jumps over the {2} dog" } } + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + { dg-end-multiline-output "" } */ diff --git a/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.11.11-not-enough-arguments-for-placeholders.sarif b/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.11.11-not-enough-arguments-for-placeholders.sarif new file mode 100644 index 000000000000..e3eb53411101 --- /dev/null +++ b/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.11.11-not-enough-arguments-for-placeholders.sarif @@ -0,0 +1,14 @@ +{ + "version": "2.1.0", + "runs": [{ + "tool": { "driver": { "name": "example" } }, + "results": [ + { "message": { "text" : "the {0} {1} fox jumps over the {2} dog", "arguments": ["quick", "brown"] } } /* { dg-error "not enough strings in 'arguments' array for placeholder '\\{2\\}' \\\[SARIF v2.1.0 §3.11.11\\\]" } */ + ] + }] +} + +/* { dg-begin-multiline-output "" } + 6 | { "message": { "text" : "the {0} {1} fox jumps over the {2} dog", "arguments": ["quick", "brown"] } } + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + { dg-end-multiline-output "" } */ diff --git a/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.13.2-no-version.sarif b/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.13.2-no-version.sarif new file mode 100644 index 000000000000..771bd9c0c05d --- /dev/null +++ b/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.13.2-no-version.sarif @@ -0,0 +1,6 @@ +{ } // { dg-error "expected sarifLog object to have a 'version' property \\\[SARIF v2.1.0 §3.13.2\\\]" } + +/* { dg-begin-multiline-output "" } + 1 | { } + | ^~~ + { dg-end-multiline-output "" } */ diff --git a/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.13.2-version-not-a-string.sarif b/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.13.2-version-not-a-string.sarif new file mode 100644 index 000000000000..87bfd0de7898 --- /dev/null +++ b/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.13.2-version-not-a-string.sarif @@ -0,0 +1,6 @@ +{ "version" : 42 } // { dg-error "15: expected sarifLog.version to be a JSON string; got JSON number \\\[SARIF v2.1.0 §3.13.2\\\]" } + +/* { dg-begin-multiline-output "" } + 1 | { "version" : 42 } + | ^~ + { dg-end-multiline-output "" } */ diff --git a/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.13.4-bad-runs.sarif b/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.13.4-bad-runs.sarif new file mode 100644 index 000000000000..c5a26f055164 --- /dev/null +++ b/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.13.4-bad-runs.sarif @@ -0,0 +1,7 @@ +{ "version": "2.1.0", + "runs": 42 } // { dg-error "expected sarifLog.runs to be 'null' or an array \\\[SARIF v2.1.0 §3.13.4\\\]" } + +/* { dg-begin-multiline-output "" } + 2 | "runs": 42 } + | ^~ + { dg-end-multiline-output "" } */ diff --git a/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.13.4-no-runs.sarif b/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.13.4-no-runs.sarif new file mode 100644 index 000000000000..f142321642c3 --- /dev/null +++ b/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.13.4-no-runs.sarif @@ -0,0 +1,6 @@ +{ "version": "2.1.0" } // { dg-error "expected sarifLog object to have a 'runs' property \\\[SARIF v2.1.0 §3.13.4\\\]" } + +/* { dg-begin-multiline-output "" } + 1 | { "version": "2.1.0" } + | ^~~~~~~~~~~~~~~~~~~~~~ + { dg-end-multiline-output "" } */ diff --git a/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.13.4-non-object-in-runs.sarif b/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.13.4-non-object-in-runs.sarif new file mode 100644 index 000000000000..4eeaaaa7b242 --- /dev/null +++ b/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.13.4-non-object-in-runs.sarif @@ -0,0 +1,7 @@ +{ "version": "2.1.0", + "runs" : [42] } // { dg-error "expected element of sarifLog.runs array to be an object \\\[SARIF v2.1.0 §3.13.4\\\]" } + +/* { dg-begin-multiline-output "" } + 2 | "runs" : [42] } + | ^~ + { dg-end-multiline-output "" } */ diff --git a/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.27.10-bad-level.sarif b/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.27.10-bad-level.sarif new file mode 100644 index 000000000000..b5c3fbe55c37 --- /dev/null +++ b/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.27.10-bad-level.sarif @@ -0,0 +1,25 @@ +{ + "version": "2.1.0", + "runs": [ + { + "tool": { + "driver": { + "name": "foo" + } + }, + "results": [ + { + "level": "mostly harmless", /* { dg-error "unrecognized value for 'level': 'mostly harmless' \\\[SARIF v2.1.0 §3.27.10\\\]" } */ + "message": { + "text": "bar" + } + } + ] + } + ] +} + +/* { dg-begin-multiline-output "" } + 12 | "level": "mostly harmless", + | ^~~~~~~~~~~~~~~~~ + { dg-end-multiline-output "" } */ diff --git a/gcc/testsuite/sarif-replay.dg/2.1.0-unhandled/3.27.10-none-level.sarif b/gcc/testsuite/sarif-replay.dg/2.1.0-unhandled/3.27.10-none-level.sarif new file mode 100644 index 000000000000..7f5867739ffb --- /dev/null +++ b/gcc/testsuite/sarif-replay.dg/2.1.0-unhandled/3.27.10-none-level.sarif @@ -0,0 +1,25 @@ +{ + "version": "2.1.0", + "runs": [ + { + "tool": { + "driver": { + "name": "foo" + } + }, + "results": [ + { + "level": "none", /* { dg-message "sorry, unimplemented: unable to handle value for 'level': 'none' \\\[SARIF v2.1.0 §3.27.10\\\]" } */ + "message": { + "text": "bar" + } + } + ] + } + ] +} + +/* { dg-begin-multiline-output "" } + 12 | "level": "none", + | ^~~~~~ + { dg-end-multiline-output "" } */ diff --git a/gcc/testsuite/sarif-replay.dg/2.1.0-valid/error-with-note.sarif b/gcc/testsuite/sarif-replay.dg/2.1.0-valid/error-with-note.sarif new file mode 100644 index 000000000000..98df315cae4a --- /dev/null +++ b/gcc/testsuite/sarif-replay.dg/2.1.0-valid/error-with-note.sarif @@ -0,0 +1,34 @@ +{"$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json", + "version": "2.1.0", + "runs": [{"tool": {"driver": {"name": "FooScanner", + "rules": []}}, + "artifacts": [{"location": {"uri": "/this/does/not/exist/test.bas"}, + "sourceLanguage": "basic", + "contents": {"text": "label: PRINT \"hello world!\"\n GOTO label\n"}, + "roles": ["analysisTarget"]}], + "results": [{"ruleId": "error", + "level": "error", + "message": {"text": "'GOTO' is considered harmful"}, + "locations": [{"physicalLocation": {"artifactLocation": {"uri": "/this/does/not/exist/test.bas"}, + "region": {"startLine": 2, + "startColumn": 8, + "endColumn": 19}, + "contextRegion": {"startLine": 2, + "snippet": {"text": " GOTO label\n"}}}}], + "relatedLocations": [{"physicalLocation": {"artifactLocation": {"uri": "/this/does/not/exist/test.bas"}, + "region": {"startLine": 1, + "startColumn": 1, + "endColumn": 6}, + "contextRegion": {"startLine": 1, + "snippet": {"text": "PRINT \"hello world!\";\n"}}}, + "message": {"text": "this is the target of the 'GOTO'"}}]}]}]} + +/* { dg-begin-multiline-output "" } +/this/does/not/exist/test.bas:2:8: error: 'GOTO' is considered harmful + { dg-end-multiline-output "" } */ +/* { dg-begin-multiline-output "" } +/this/does/not/exist/test.bas:1:1: note: this is the target of the 'GOTO' + { dg-end-multiline-output "" } */ + +// TODO: quote the source +// TODO: trailing [error] diff --git a/gcc/testsuite/sarif-replay.dg/2.1.0-valid/escaped-braces.sarif b/gcc/testsuite/sarif-replay.dg/2.1.0-valid/escaped-braces.sarif new file mode 100644 index 000000000000..cbb90511e361 --- /dev/null +++ b/gcc/testsuite/sarif-replay.dg/2.1.0-valid/escaped-braces.sarif @@ -0,0 +1,17 @@ +{ + "version": "2.1.0", + "runs": [{ + "tool": { "driver": { "name": "example" } }, + "results": [ + { "message": { "text" : "before open '{{' after open" }, + "locations": []}, + { "message": { "text" : "before close '}}' after close" }, + "locations": []} + ] + }] +} + +/* { dg-begin-multiline-output "" } +example: warning: before open '{' after open +example: warning: before close '}' after close + { dg-end-multiline-output "" } */ diff --git a/gcc/testsuite/sarif-replay.dg/2.1.0-valid/null-runs.sarif b/gcc/testsuite/sarif-replay.dg/2.1.0-valid/null-runs.sarif new file mode 100644 index 000000000000..5fc630eecb35 --- /dev/null +++ b/gcc/testsuite/sarif-replay.dg/2.1.0-valid/null-runs.sarif @@ -0,0 +1,2 @@ +{ "version": "2.1.0", + "runs": null } diff --git a/gcc/testsuite/sarif-replay.dg/2.1.0-valid/signal-1.c.sarif b/gcc/testsuite/sarif-replay.dg/2.1.0-valid/signal-1.c.sarif new file mode 100644 index 000000000000..6b76bc0bc594 --- /dev/null +++ b/gcc/testsuite/sarif-replay.dg/2.1.0-valid/signal-1.c.sarif @@ -0,0 +1,193 @@ +/* Test a replay of a .sarif file generated from GCC testsuite. + + The dg directives were stripped out from the generated .sarif + to avoid confusing DejaGnu for this test. */ + +{"$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json", + "version": "2.1.0", + "runs": [{"tool": {"driver": {"name": "GNU C17", + "fullName": "GNU C17 (GCC) version 15.0.0 20240709 (experimental) (x86_64-pc-linux-gnu)", + "version": "15.0.0 20240709 (experimental)", + "informationUri": "https://gcc.gnu.org/gcc-15/", + "rules": [{"id": "-Wanalyzer-unsafe-call-within-signal-handler", + "helpUri": "https://gcc.gnu.org/onlinedocs/gcc/Static-Analyzer-Options.html#index-Wanalyzer-unsafe-call-within-signal-handler"}]}}, + "taxonomies": [{"name": "CWE", + "version": "4.7", + "organization": "MITRE", + "shortDescription": {"text": "The MITRE Common Weakness Enumeration"}, + "taxa": [{"id": "479", + "helpUri": "https://cwe.mitre.org/data/definitions/479.html"}]}], + "invocations": [{"executionSuccessful": true, + "toolExecutionNotifications": []}], + "originalUriBaseIds": {"PWD": {"uri": "file:///home/david/coding/gcc-newgit-more-taint/build/gcc/"}}, + "artifacts": [{"location": {"uri": "../../src/gcc/testsuite/gcc.dg/analyzer/signal-1.c", + "uriBaseId": "PWD"}, + "sourceLanguage": "c", + "contents": {"text": "/* Example of a bad call within a signal handler.\n 'handler' calls 'custom_logger' which calls 'fprintf', and 'fprintf' is\n not allowed from a signal handler. */\n\n\n#include \n#include \n\nextern void body_of_program(void);\n\nvoid custom_logger(const char *msg)\n{\n fprintf(stderr, \"LOG: %s\", msg);\n}\n\nstatic void handler(int signum)\n{\n custom_logger(\"got signal\");\n}\n\nint main(int argc, const char *argv)\n{\n custom_logger(\"started\");\n\n signal(SIGINT, handler);\n\n body_of_program();\n\n custom_logger(\"stopped\");\n\n return 0;\n}\n"}, + "roles": ["analysisTarget", + "tracedFile"]}], + "results": [{"ruleId": "-Wanalyzer-unsafe-call-within-signal-handler", + "taxa": [{"id": "479", + "toolComponent": {"name": "cwe"}}], + "properties": {"gcc/analyzer/saved_diagnostic/sm": "signal", + "gcc/analyzer/saved_diagnostic/enode": 57, + "gcc/analyzer/saved_diagnostic/snode": 11, + "gcc/analyzer/saved_diagnostic/state": "in_signal_handler", + "gcc/analyzer/saved_diagnostic/idx": 0}, + "level": "warning", + "message": {"text": "call to ‘fprintf’ from within signal handler"}, + "locations": [{"physicalLocation": {"artifactLocation": {"uri": "../../src/gcc/testsuite/gcc.dg/analyzer/signal-1.c", + "uriBaseId": "PWD"}, + "region": {"startLine": 13, + "startColumn": 3, + "endColumn": 34}, + "contextRegion": {"startLine": 13, + "snippet": {"text": " fprintf(stderr, \"LOG: %s\", msg);\n"}}}, + "logicalLocations": [{"name": "custom_logger", + "fullyQualifiedName": "custom_logger", + "decoratedName": "custom_logger", + "kind": "function"}]}], + "codeFlows": [{"threadFlows": [{"id": "main", + "locations": [{"properties": {"gcc/analyzer/checker_event/emission_id": "(1)", + "gcc/analyzer/checker_event/kind": "EK_FUNCTION_ENTRY"}, + "location": {"physicalLocation": {"artifactLocation": {"uri": "../../src/gcc/testsuite/gcc.dg/analyzer/signal-1.c", + "uriBaseId": "PWD"}, + "region": {"startLine": 21, + "startColumn": 5, + "endColumn": 9}, + "contextRegion": {"startLine": 21, + "snippet": {"text": "int main(int argc, const char *argv)\n"}}}, + "logicalLocations": [{"name": "main", + "fullyQualifiedName": "main", + "decoratedName": "main", + "kind": "function"}], + "message": {"text": "entry to ‘main’"}}, + "kinds": ["enter", + "function"], + "nestingLevel": 1, + "executionOrder": 1}, + {"properties": {"gcc/analyzer/checker_event/emission_id": "(2)", + "gcc/analyzer/checker_event/kind": "EK_STATE_CHANGE"}, + "location": {"physicalLocation": {"artifactLocation": {"uri": "../../src/gcc/testsuite/gcc.dg/analyzer/signal-1.c", + "uriBaseId": "PWD"}, + "region": {"startLine": 25, + "startColumn": 3, + "endColumn": 26}, + "contextRegion": {"startLine": 25, + "snippet": {"text": " signal(SIGINT, handler);\n"}}}, + "logicalLocations": [{"name": "main", + "fullyQualifiedName": "main", + "decoratedName": "main", + "kind": "function"}], + "message": {"text": "registering ‘handler’ as signal handler"}}, + "nestingLevel": 1, + "executionOrder": 2}, + {"properties": {"gcc/analyzer/checker_event/emission_id": "(3)", + "gcc/analyzer/checker_event/kind": "EK_CUSTOM"}, + "location": {"message": {"text": "later on, when the signal is delivered to the process"}}, + "nestingLevel": 0, + "executionOrder": 3}, + {"properties": {"gcc/analyzer/checker_event/emission_id": "(4)", + "gcc/analyzer/checker_event/kind": "EK_FUNCTION_ENTRY"}, + "location": {"physicalLocation": {"artifactLocation": {"uri": "../../src/gcc/testsuite/gcc.dg/analyzer/signal-1.c", + "uriBaseId": "PWD"}, + "region": {"startLine": 16, + "startColumn": 13, + "endColumn": 20}, + "contextRegion": {"startLine": 16, + "snippet": {"text": "static void handler(int signum)\n"}}}, + "logicalLocations": [{"name": "handler", + "fullyQualifiedName": "handler", + "decoratedName": "handler", + "kind": "function"}], + "message": {"text": "entry to ‘handler’"}}, + "kinds": ["enter", + "function"], + "nestingLevel": 1, + "executionOrder": 4}, + {"properties": {"gcc/analyzer/checker_event/emission_id": "(5)", + "gcc/analyzer/checker_event/kind": "EK_CALL_EDGE", + "gcc/analyzer/superedge_event/superedge": {"kind": "SUPEREDGE_CALL", + "src_idx": 7, + "dst_idx": 10, + "desc": "call"}}, + "location": {"physicalLocation": {"artifactLocation": {"uri": "../../src/gcc/testsuite/gcc.dg/analyzer/signal-1.c", + "uriBaseId": "PWD"}, + "region": {"startLine": 18, + "startColumn": 3, + "endColumn": 30}, + "contextRegion": {"startLine": 18, + "snippet": {"text": " custom_logger(\"got signal\");\n"}}}, + "logicalLocations": [{"name": "handler", + "fullyQualifiedName": "handler", + "decoratedName": "handler", + "kind": "function"}], + "message": {"text": "calling ‘custom_logger’ from ‘handler’"}}, + "kinds": ["call", + "function"], + "nestingLevel": 1, + "executionOrder": 5}, + {"properties": {"gcc/analyzer/checker_event/emission_id": "(6)", + "gcc/analyzer/checker_event/kind": "EK_FUNCTION_ENTRY"}, + "location": {"physicalLocation": {"artifactLocation": {"uri": "../../src/gcc/testsuite/gcc.dg/analyzer/signal-1.c", + "uriBaseId": "PWD"}, + "region": {"startLine": 11, + "startColumn": 6, + "endColumn": 19}, + "contextRegion": {"startLine": 11, + "snippet": {"text": "void custom_logger(const char *msg)\n"}}}, + "logicalLocations": [{"name": "custom_logger", + "fullyQualifiedName": "custom_logger", + "decoratedName": "custom_logger", + "kind": "function"}], + "message": {"text": "entry to ‘custom_logger’"}}, + "kinds": ["enter", + "function"], + "nestingLevel": 2, + "executionOrder": 6}, + {"properties": {"gcc/analyzer/checker_event/emission_id": "(7)", + "gcc/analyzer/checker_event/kind": "EK_WARNING"}, + "location": {"physicalLocation": {"artifactLocation": {"uri": "../../src/gcc/testsuite/gcc.dg/analyzer/signal-1.c", + "uriBaseId": "PWD"}, + "region": {"startLine": 13, + "startColumn": 3, + "endColumn": 34}, + "contextRegion": {"startLine": 13, + "snippet": {"text": " fprintf(stderr, \"LOG: %s\", msg);\n"}}}, + "logicalLocations": [{"name": "custom_logger", + "fullyQualifiedName": "custom_logger", + "decoratedName": "custom_logger", + "kind": "function"}], + "message": {"text": "call to ‘fprintf’ from within signal handler"}}, + "kinds": ["danger"], + "nestingLevel": 2, + "executionOrder": 7}]}]}]}]}]} + +// TODO: replay the source code +// TODO: show the CWE +/* { dg-begin-multiline-output "" } +../../src/gcc/testsuite/gcc.dg/analyzer/signal-1.c:13:3: warning: call to ‘fprintf’ from within signal handler [-Wanalyzer-unsafe-call-within-signal-handler] + 'main': event 1 + | + | + 'main': event 2 + | + | + event 3 + | + |GNU C17: + | (3): later on, when the signal is delivered to the process + | + +--> 'handler': event 4 + | + | + 'handler': event 5 + | + | + +--> 'custom_logger': event 6 + | + | + 'custom_logger': event 7 + | + | + { dg-end-multiline-output "" } */ diff --git a/gcc/testsuite/sarif-replay.dg/2.1.0-valid/spec-example-1.sarif b/gcc/testsuite/sarif-replay.dg/2.1.0-valid/spec-example-1.sarif new file mode 100644 index 000000000000..97f409f4aa45 --- /dev/null +++ b/gcc/testsuite/sarif-replay.dg/2.1.0-valid/spec-example-1.sarif @@ -0,0 +1,15 @@ +// Taken from SARIF v2.1.0, Appendix K.1: "Minimal valid SARIF log file" +{ + "version": "2.1.0", + "runs": [ + { + "tool": { + "driver": { + "name": "CodeScanner" + } + }, + "results": [ + ] + } + ] +} diff --git a/gcc/testsuite/sarif-replay.dg/2.1.0-valid/spec-example-2.sarif b/gcc/testsuite/sarif-replay.dg/2.1.0-valid/spec-example-2.sarif new file mode 100644 index 000000000000..dff53cd8c849 --- /dev/null +++ b/gcc/testsuite/sarif-replay.dg/2.1.0-valid/spec-example-2.sarif @@ -0,0 +1,73 @@ +/* Taken from SARIF v2.1.0, Appendix K.2: "Minimal recommended SARIF log + file with source information". */ + +{ + "version": "2.1.0", + "runs": [ + { + "tool": { + "driver": { + "name": "CodeScanner", + "rules": [ + { + "id": "C2001", + "fullDescription": { + "text": "A variable was used without being initialized. This can result in runtime errors such as null reference exceptions." + }, + "messageStrings": { + "default": { + "text": "Variable \"{0}\" was used without being initialized." + } + } + } + ] + } + }, + "artifacts": [ + { + "location": { + "uri": "src/collections/list.cpp", + "uriBaseId": "SRCROOT" + }, + "sourceLanguage": "c" + } + ], + "results": [ + { + "ruleId": "C2001", + "ruleIndex": 0, + "message": { + "id": "default", + "arguments": [ + "count" + ] + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/collections/list.cpp", + "uriBaseId": "SRCROOT", + "index": 0 + }, + "region": { + "startLine": 15 + } + }, + "logicalLocations": [ + { + "fullyQualifiedName": "collections::list::add" + } + ] + } + ] + } + ] + } + ] +} + +/* { dg-begin-multiline-output "" } +In function 'collections::list::add': +src/collections/list.cpp:15: warning: Variable "count" was used without being initialized. [C2001] + { dg-end-multiline-output "" } */ diff --git a/gcc/testsuite/sarif-replay.dg/2.1.0-valid/spec-example-3.sarif b/gcc/testsuite/sarif-replay.dg/2.1.0-valid/spec-example-3.sarif new file mode 100644 index 000000000000..5e306528e403 --- /dev/null +++ b/gcc/testsuite/sarif-replay.dg/2.1.0-valid/spec-example-3.sarif @@ -0,0 +1,65 @@ +/* Taken from SARIF v2.1.0, Appendix K.3: "Minimal recommended SARIF log + file without source information". */ + +{ + "version": "2.1.0", + "runs": [ + { + "tool": { + "driver": { + "name": "BinaryScanner" + } + }, + "artifact": [ + { + "location": { + "uri": "bin/example", + "uriBaseId": "BINROOT" + } + } + ], + "logicalLocations": [ + { + "name": "Example", + "kind": "namespace" + }, + { + "name": "Worker", + "fullyQualifiedName": "Example.Worker", + "kind": "type", + "parentIndex": 0 + }, + { + "name": "DoWork", + "fullyQualifiedName": "Example.Worker.DoWork", + "kind": "function", + "parentIndex": 1 + } + ], + "results": [ + { + "ruleId": "B6412", + "message": { + "text": "The insecure method \"Crypto.Sha1.Encrypt\" should not be used." + }, + "level": "warning", + "locations": [ + { + "logicalLocations": [ + { + "fullyQualifiedName": "Example.Worker.DoWork", + "index": 2 + } + ] + } + ] + } + ] + } + ] +} + +/* { dg-begin-multiline-output "" } +In function 'Example.Worker.DoWork': +BinaryScanner: warning: The insecure method "Crypto.Sha1.Encrypt" should not be used. [B6412] + { dg-end-multiline-output "" } */ diff --git a/gcc/testsuite/sarif-replay.dg/2.1.0-valid/spec-example-4.sarif b/gcc/testsuite/sarif-replay.dg/2.1.0-valid/spec-example-4.sarif new file mode 100644 index 000000000000..84d3f887cefd --- /dev/null +++ b/gcc/testsuite/sarif-replay.dg/2.1.0-valid/spec-example-4.sarif @@ -0,0 +1,766 @@ +/* Taken from SARIF v2.1.0, Appendix K.4: "Comprehensive SARIF file". */ + +{ + "version": "2.1.0", + "$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json", + "runs": [ + { + "automationId": { + "guid": "BC650830-A9FE-44CB-8818-AD6C387279A0", + "id": "Nightly code scan/2018-10-08" + }, + "baselineGuid": "0A106451-C9B1-4309-A7EE-06988B95F723", + "runAggregates": [ + { + "id": "Build/14.0.1.2/Release/20160716-13:22:18", + "correlationGuid": "26F138B6-6014-4D3D-B174-6E1ACE9439F3" + } + ], + "tool": { + "driver": { + "name": "CodeScanner", + "fullName": "CodeScanner 1.1 for Microsoft Windows (R) (en-US)", + "version": "2.1", + "semanticVersion": "2.1.0", + "dottedQuadFileVersion": "2.1.0.0", + "releaseDateUtc": "2019-03-17", + "organization": "Example Corporation", + "product": "Code Scanner", + "productSuite": "Code Quality Tools", + "shortDescription": { + "text": "A scanner for code." + }, + "fullDescription": { + "text": "A really great scanner for all your code." + }, + "properties": { + "copyright": "Copyright (c) 2017 by Example Corporation." + }, + "globalMessageStrings": { + "variableDeclared": { + "text": "Variable \"{0}\" was declared here.", + "markdown": " Variable `{0}` was declared here." + } + }, + "rules": [ + { + "id": "C2001", + "deprecatedIds": [ + "CA2000" + ], + "defaultConfiguration": { + "level": "error", + "rank": 95 + }, + "shortDescription": { + "text": "A variable was used without being initialized." + }, + "fullDescription": { + "text": "A variable was used without being initialized. This can result in runtime errors such as null reference exceptions." + }, + "messageStrings": { + "default": { + "text": "Variable \"{0}\" was used without being initialized. It was declared [here]({1}).", + "markdown": "Variable `{0}` was used without being initialized. It was declared [here]({1})." + } + } + } + ], + "notifications": [ + { + "id": "start", + "shortDescription": { + "text": "The run started." + }, + "messageStrings": { + "default": { + "text": "Run started." + } + } + }, + { + "id": "end", + "shortDescription": { + "text": "The run ended." + }, + "messageStrings": { + "default": { + "text": "Run ended." + } + } + } + ], + "language": "en-US" + }, + "extensions": [ + { + "name": "CodeScanner Security Rules", + "version": "3.1", + "rules": [ + { + "id": "S0001", + "defaultConfiguration": { + "level": "error" + }, + "shortDescription": { + "text": "Do not use weak cryptographic algorithms." + }, + "messageStrings": { + "default": { + "text": "The cryptographic algorithm '{0}' should not be used." + } + } + } + ] + } + ] + }, + "language": "en-US", + "versionControlProvenance": [ + { + "repositoryUri": "https://github.com/example-corp/browser", + "revisionId": "5da53fbb2a0aaa12d648b73984acc9aac2e11c2a", + "mappedTo": { + "uriBaseId": "PROJECTROOT" + } + } + ], + "originalUriBaseIds": { + "PROJECTROOT": { + "uri": "file://build.example.com/work/" + }, + "SRCROOT": { + "uri": " src/", + "uriBaseId": "PROJECTROOT" + }, + "BINROOT": { + "uri": " bin/", + "uriBaseId": "PROJECTROOT" + } + }, + "invocations": [ + { + "commandLine": "CodeScanner @build/collections.rsp", + "responseFiles": [ + { + "uri": "build/collections.rsp", + "uriBaseId": "SRCROOT", + "index": 0 + } + ], + "startTimeUtc": "2016-07-16T14:18:25Z", + "endTimeUtc": "2016-07-16T14:19:01Z", + "machine": "BLD01", + "account": "buildAgent", + "processId": 1218, + "fileName": "/bin/tools/CodeScanner", + "workingDirectory": { + "uri": "file:///home/buildAgent/src" + }, + "environmentVariables": { + "PATH": "/usr/local/bin:/bin:/bin/tools:/home/buildAgent/bin", + "HOME": "/home/buildAgent", + "TZ": "EST" + }, + "toolConfigurationNotifications": [ + { + "descriptor": { + "id": "UnknownRule" + }, + "associatedRule": { + "ruleId": "ABC0001" + }, + "level": "warning", + "message": { + "text": "Could not disable rule \"ABC0001\" because there is no rule with that id." + } + } + ], + "toolExecutionNotifications": [ + { + "descriptor": { + "id": "CTN0001" + }, + "level": "note", + "message": { + "text": "Run started." + } + }, + { + "descriptor": { + "id": "CTN9999" + }, + "associatedRule": { + "id": "C2001", + "index": 0 + }, + "level": "error", + "message": { + "text": "Exception evaluating rule \"C2001\". Rule disabled; run continues." + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "crypto/hash.cpp", + "uriBaseId": "SRCROOT", + "index": 4 + } + } + } + ], + "threadId": 52, + "timeUtc": "2016-07-16T14:18:43.119Z", + "exception": { + "kind": "ExecutionEngine.RuleFailureException", + "message": "Unhandled exception during rule evaluation.", + "stack": { + "frames": [ + { + "location": { + "message": { + "text": "Exception thrown" + }, + "logicalLocations": [ + { + "fullyQualifiedName": + "Rules.SecureHashAlgorithmRule.Evaluate" + } + ], + "physicalLocation": { + "address": { + "offset": 4244988 + } + } + }, + "module": "RuleLibrary", + "threadId": 52 + }, + { + "location": { + "logicalLocations": [ + { + "fullyQualifiedName": + "ExecutionEngine.Engine.EvaluateRule" + } + ], + "physicalLocation": { + "address": { + "offset": 4245514 + } + } + }, + "module": "ExecutionEngine", + "threadId": 52 + } + ] + }, + "innerExceptions": [ + { + "kind": "System.ArgumentException", + "message": "length is < 0" + } + ] + } + }, + { + "descriptor": { + "id": "CTN0002" + }, + "level": "note", + "message": { + "text": "Run ended." + } + } + ], + "exitCode": 0, + "executionSuccessful": true + } + ], + "artifacts": [ + { + "location": { + "uri": "build/collections.rsp", + "uriBaseId": "SRCROOT" + }, + "mimeType": "text/plain", + "length": 81, + "contents": { + "text": "-input src/collections/*.cpp -log out/collections.sarif -rules all -disable C9999" + } + }, + { + "location": { + "uri": "application/main.cpp", + "uriBaseId": "SRCROOT" + }, + "sourceLanguage": "cplusplus", + "length": 1742, + "hashes": { + "sha-256": "cc8e6a99f3eff00adc649fee132ba80fe333ea5a" + } + }, + { + "location": { + "uri": "collections/list.cpp", + "uriBaseId": "SRCROOT" + }, + "sourceLanguage": "cplusplus", + "length": 980, + "hashes": { + "sha-256": "b13ce2678a8807ba0765ab94a0ecd394f869bc81" + } + }, + { + "location": { + "uri": "collections/list.h", + "uriBaseId": "SRCROOT" + }, + "sourceLanguage": "cplusplus", + "length": 24656, + "hashes": { + "sha-256": "849be119aaba4e9f88921a99e3036fb6c2a8144a" + } + }, + { + "location": { + "uri": "crypto/hash.cpp", + "uriBaseId": "SRCROOT" + }, + "sourceLanguage": "cplusplus", + "length": 1424, + "hashes": { + "sha-256": "3ffe2b77dz255cdf95f97d986d7a6ad8f287eaed" + } + }, + { + "location": { + "uri": "app.zip", + "uriBaseId": "BINROOT" + }, + "mimeType": "application/zip", + "length": 310450, + "hashes": { + "sha-256": "df18a5e74b6b46ddaa23ad7271ee2b7c5731cbe1" + } + }, + { + "location": { + "uri": "/docs/intro.docx" + }, + "mimeType": + "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + "parentIndex": 5, + "offset": 17522, + "length": 4050 + } + ], + "logicalLocations": [ + { + "name": "add", + "fullyQualifiedName": "collections::list::add", + "decoratedName": "?add@list@collections@@QAEXH@Z", + "kind": "function", + "parentIndex": 1 + }, + { + "name": "list", + "fullyQualifiedName": "collections::list", + "kind": "type", + "parentIndex": 2 + }, + { + "name": "collections", + "kind": "namespace" + }, + { + "name": "add_core", + "fullyQualfiedName": "collections::list::add_core", + "decoratedName": "?add_core@list@collections@@QAEXH@Z", + "kind": "function", + "parentIndex": 1 + }, + { + "fullyQualifiedName": "main", + "kind": "function" + } + ], + "results": [ + { + "ruleId": "C2001", + "ruleIndex": 0, + "kind": "fail", + "level": "error", + "message": { + "id": "default", + "arguments": [ + "ptr", + "0" + ] + }, + "suppressions": [ + { + "kind": "external", + "status": "accepted" + } + ], + "baselineState": "unchanged", + "rank": 95, + "analysisTarget": { + "uri": "collections/list.cpp", + "uriBaseId": "SRCROOT", + "index": 2 + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "collections/list.h", + "uriBaseId": "SRCROOT", + "index": 3 + }, + "region": { + "startLine": 15, + "startColumn": 9, + "endLine": 15, + "endColumn": 10, + "charLength": 1, + "charOffset": 254, + "snippet": { + "text": "add_core(ptr, offset, val);\n return;" + } + } + }, + "logicalLocations": [ + { + "fullyQualifiedName": "collections::list::add", + "index": 0 + } + ] + } + ], + "relatedLocations": [ + { + "id": 0, + "message": { + "id": "variableDeclared", + "arguments": [ + "ptr" + ] + }, + "physicalLocation": { + "artifactLocation": { + "uri": "collections/list.h", + "uriBaseId": "SRCROOT", + "index": 3 + }, + "region": { + "startLine": 8, + "startColumn": 5 + } + }, + "logicalLocations": [ + { + "fullyQualifiedName": "collections::list::add", + "index": 0 + } + ] + } + ], + "codeFlows": [ + { + "message": { + "text": "Path from declaration to usage" + }, + + "threadFlows": [ + { + "id": "thread-52", + "locations": [ + { + "importance": "essential", + "location": { + "message": { + "text": "Variable \"ptr\" declared.", + "markdown": "Variable `ptr` declared." + }, + "physicalLocation": { + "artifactLocation": { + "uri":"collections/list.h", + "uriBaseId": "SRCROOT", + "index": 3 + }, + "region": { + "startLine": 15, + "snippet": { + "text": "int *ptr;" + } + } + }, + "logicalLocations": [ + { + "fullyQualifiedName": "collections::list::add", + "index": 0 + } + ] + }, + "module": "platform" + }, + { + "state": { + "y": { + "text": "2" + }, + "z": { + "text": "4" + }, + "y + z": { + "text": "6" + }, + "q": { + "text": "7" + } + }, + "importance": "unimportant", + "location": { + "physicalLocation": { + "artifactLocation": { + "uri":"collections/list.h", + "uriBaseId": "SRCROOT", + "index": 3 + }, + "region": { + "startLine": 15, + "snippet": { + "text": "offset = (y + z) * q + 1;" + } + } + }, + "logicalLocations": [ + { + "fullyQualifiedName": "collections::list::add", + "index": 0 + } + ], + "annotations": [ + { + "startLine": 15, + "startColumn": 13, + "endColumn": 19, + "message": { + "text": "(y + z) = 42", + "markdown": "`(y + z) = 42`" + } + } + ] + }, + "module": "platform" + }, + { + "importance": "essential", + "location": { + "message": { + "text": "Uninitialized variable \"ptr\" passed to method \"add_core\".", + "markdown": "Uninitialized variable `ptr` passed to method `add_core`." + }, + "physicalLocation": { + "artifactLocation": { + "uri":"collections/list.h", + "uriBaseId": "SRCROOT", + "index": 3 + }, + "region": { + "startLine": 25, + "snippet": { + "text": "add_core(ptr, offset, val)" + } + } + }, + "logicalLocations": [ + { + "fullyQualifiedName": "collections::list::add", + "index": 0 + } + ] + }, + "module": "platform" + } + ] + } + ] + } + ], + "stacks": [ + { + "message": { + "text": "Call stack resulting from usage of uninitialized variable." + }, + "frames": [ + { + "location": { + "message": { + "text": "Exception thrown." + }, + "physicalLocation": { + "artifactLocation": { + "uri": "collections/list.h", + "uriBaseId": "SRCROOT", + "index": 3 + }, + "region": { + "startLine": 110, + "startColumn": 15 + }, + "address": { + "offset": 4229178 + } + }, + "logicalLocations": [ + { + "fullyQualifiedName": "collections::list::add_core", + "index": 0 + } + ] + }, + "module": "platform", + "threadId": 52, + "parameters": [ "null", "0", "14" ] + }, + { + "location": { + "physicalLocation": { + "artifactLocation": { + "uri": "collections/list.h", + "uriBaseId": "SRCROOT", + "index": 3 + }, + "region": { + "startLine": 43, + "startColumn": 15 + }, + "address": { + "offset": 4229268 + } + }, + "logicalLocations": [ + { + "fullyQualifiedName": "collections::list::add", + "index": 0 + } + ] + }, + "module": "platform", + "threadId": 52, + "parameters": [ "14" ] + }, + { + "location": { + "physicalLocation": { + "artifactLocation": { + "uri": "application/main.cpp", + "uriBaseId": "SRCROOT", + "index": 1 + }, + "region": { + "startLine": 28, + "startColumn": 9 + }, + "address": { + "offset": 4229836 + } + }, + "logicalLocations": [ + { + "fullyQualifiedName": "main", + "index": 4 + } + ] + }, + "module": "application", + "threadId": 52 + } + ] + } + ], + "addresses": [ + { + "baseAddress": 4194304, + "fullyQualifiedName": "collections.dll", + "kind": "module", + "section": ".text" + }, + { + "offset": 100, + "fullyQualifiedName": "collections.dll!collections::list::add", + "kind": "function", + "parentIndex": 0 + }, + { + "offset": 22, + "fullyQualifiedName": "collections.dll!collections::list::add+0x16", + "parentIndex": 1 + } + ], + "fixes": [ + { + "description": { + "text": "Initialize the variable to null" + }, + "artifactChanges": [ + { + "artifactLocation": { + "uri": "collections/list.h", + "uriBaseId": "SRCROOT", + "index": 3 + }, + "replacements": [ + { + "deletedRegion": { + "startLine": 42 + }, + "insertedContent": { + "text": "A different line\n" + } + } + ] + } + ] + } + ], + "hostedViewerUri": + "https://www.example.com/viewer/3918d370-c636-40d8-bf23-8c176043a2df", + "workItemUris": [ + "https://github.com/example/project/issues/42", + "https://github.com/example/project/issues/54" + ], + "provenance": { + "firstDetectionTimeUtc": "2016-07-15T14:20:42Z", + "firstDetectionRunGuid": "8F62D8A0-C14F-4516-9959-1A663BA6FB99", + "lastDetectionTimeUtc": "2016-07-16T14:20:42Z", + "lastDetectionRunGuid": "BC650830-A9FE-44CB-8818-AD6C387279A0", + "invocationIndex": 0 + } + } + ] + } + ] +} + +/* { dg-begin-multiline-output "" } +In function 'collections::list::add': +collections/list.h:15:9: error: Variable "ptr" was used without being initialized. It was declared [here](0). [C2001] + event 1 + | + | + event 2 + | + | + event 3 + | + | + { dg-end-multiline-output "" } */ +/* { dg-begin-multiline-output "" } +collections/list.h:8:5: note: Variable "ptr" was declared here. + { dg-end-multiline-output "" } */ + +// TODO: what's up with the events? diff --git a/gcc/testsuite/sarif-replay.dg/2.1.0-valid/tutorial-example.sarif b/gcc/testsuite/sarif-replay.dg/2.1.0-valid/tutorial-example.sarif new file mode 100644 index 000000000000..6a1252072d16 --- /dev/null +++ b/gcc/testsuite/sarif-replay.dg/2.1.0-valid/tutorial-example.sarif @@ -0,0 +1,117 @@ +/* Adapted from https://github.com/microsoft/sarif-tutorials + samples/bad-eval-with-code-flow.sarif. + which is licensed under the Creative Commons Attribution 4.0 International Public License + and/or the MIT License. */ + +{ + "version": "2.1.0", + "runs": [ + { + "tool": { + "driver": { + "name": "PythonScanner" + } + }, + "results": [ + { + "ruleId": "PY2335", + "message": { + "text": "Use of tainted variable 'raw_input' in the insecure function 'eval'." + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "bad-eval-with-code-flow.py" + }, + "region": { + "startLine": 10 + } + } + } + ], + "codeFlows": [ + { + "message": { + "text": "Tracing the path from user input to insecure usage." + }, + "threadFlows": [ + { + "locations": [ + { + "location": { + "physicalLocation": { + "artifactLocation": { + "uri": "bad-eval-with-code-flow.py" + }, + "region": { + "startLine": 5 + } + } + }, + "state": { + "expr": { + "text": "undef" + } + }, + "nestingLevel": 0 + }, + { + "location": { + "physicalLocation": { + "artifactLocation": { + "uri": "bad-eval-with-code-flow.py" + }, + "region": { + "startLine": 6 + } + } + }, + "state": { + "expr": { + "text": "42" + } + }, + "nestingLevel": 0 + }, + { + "location": { + "physicalLocation": { + "artifactLocation": { + "uri": "bad-eval-with-code-flow.py" + }, + "region": { + "startLine": 10 + } + } + }, + "state": { + "raw_input": { + "text": "42" + } + }, + "nestingLevel": 1 + } + ] + } + ] + } + ] + } + ] + } + ] +} + +/* { dg-begin-multiline-output "" } +bad-eval-with-code-flow.py:10: warning: Use of tainted variable 'raw_input' in the insecure function 'eval'. [PY2335] + events 1-2 + | + | + +--> event 3 + | + | + { dg-end-multiline-output "" } */ + +// TODO: show path even when we can't find the source +// TODO: show path when we can find the source \ No newline at end of file diff --git a/gcc/testsuite/sarif-replay.dg/dg.exp b/gcc/testsuite/sarif-replay.dg/dg.exp new file mode 100644 index 000000000000..cf9c82d9ae2b --- /dev/null +++ b/gcc/testsuite/sarif-replay.dg/dg.exp @@ -0,0 +1,46 @@ +# Copyright (C) 2023-2024 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 +# . + +# GCC testsuite that uses the `dg.exp' driver. + +# Load support procs. +load_lib sarif-replay-dg.exp + +global DEFAULT_SARIF_REPLAY_FLAGS +if [info exists DEFAULT_SARIF_REPLAY_FLAGS] then { + set save_default_sarif_replay_flags $DEFAULT_SARIF_REPLAY_FLAGS +} + +# If a testcase doesn't have special options, use these. +set DEFAULT_SARIF_REPLAY_FLAGS "-fjson-comments -fdiagnostics-color=never" + +# Initialize `dg'. +dg-init + +# Main loop. +sarif-replay-dg-runtest [lsort \ + [glob -nocomplain $srcdir/$subdir/*/*.sarif ] ] "" $DEFAULT_SARIF_REPLAY_FLAGS + +# All done. +dg-finish + +if [info exists save_default_sarif_replay_flags] { + set DEFAULT_SARIF_REPLAY_FLAGS $save_default_sarif_replay_flags +} else { + unset DEFAULT_SARIF_REPLAY_FLAGS +} diff --git a/gcc/testsuite/sarif-replay.dg/malformed-json/array-missing-comma.sarif b/gcc/testsuite/sarif-replay.dg/malformed-json/array-missing-comma.sarif new file mode 100644 index 000000000000..0d64aeb73de1 --- /dev/null +++ b/gcc/testsuite/sarif-replay.dg/malformed-json/array-missing-comma.sarif @@ -0,0 +1,6 @@ +["foo", "bar" "baz"] /* { dg-error "expected ',' or '\]'; got string" } */ + +/* { dg-begin-multiline-output "" } + 1 | ["foo", "bar" "baz"] + | ^~~~~ + { dg-end-multiline-output "" } */ diff --git a/gcc/testsuite/sarif-replay.dg/malformed-json/array-with-trailing-comma.sarif b/gcc/testsuite/sarif-replay.dg/malformed-json/array-with-trailing-comma.sarif new file mode 100644 index 000000000000..05b74a81efcc --- /dev/null +++ b/gcc/testsuite/sarif-replay.dg/malformed-json/array-with-trailing-comma.sarif @@ -0,0 +1,6 @@ +[ 0, 1, 2, ] /* { dg-error "expected a JSON value but got '\\\]'" } */ + +{ dg-begin-multiline-output "" } + 1 | [ 0, 1, 2, ] + | ^ +{ dg-end-multiline-output "" } diff --git a/gcc/testsuite/sarif-replay.dg/malformed-json/bad-token.sarif b/gcc/testsuite/sarif-replay.dg/malformed-json/bad-token.sarif new file mode 100644 index 000000000000..7756eef1add3 --- /dev/null +++ b/gcc/testsuite/sarif-replay.dg/malformed-json/bad-token.sarif @@ -0,0 +1,6 @@ + not a valid JSON file // { dg-error "invalid JSON token: unexpected character: 'n'" } + +{ dg-begin-multiline-output "" } + 1 | not a valid JSON file + | ^ +{ dg-end-multiline-output "" } diff --git a/gcc/testsuite/sarif-replay.dg/malformed-json/object-missing-comma.sarif b/gcc/testsuite/sarif-replay.dg/malformed-json/object-missing-comma.sarif new file mode 100644 index 000000000000..9d2bf9476b16 --- /dev/null +++ b/gcc/testsuite/sarif-replay.dg/malformed-json/object-missing-comma.sarif @@ -0,0 +1,7 @@ +{ "foo": "bar" + "baz": 42 } // { dg-error "expected ',' or '\}'; got string" } + +{ dg-begin-multiline-output "" } + 2 | "baz": 42 } + | ^~~~~ +{ dg-end-multiline-output "" } diff --git a/gcc/testsuite/sarif-replay.dg/malformed-json/object-with-trailing-comma.sarif b/gcc/testsuite/sarif-replay.dg/malformed-json/object-with-trailing-comma.sarif new file mode 100644 index 000000000000..e1aae9b350c9 --- /dev/null +++ b/gcc/testsuite/sarif-replay.dg/malformed-json/object-with-trailing-comma.sarif @@ -0,0 +1,6 @@ +{ "foo": "bar", } /* { dg-error "expected string for object key after ','; got '\\\}'" } */ + +{ dg-begin-multiline-output "" } + 1 | { "foo": "bar", } + | ^ +{ dg-end-multiline-output "" }