From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mail-pf1-x434.google.com (mail-pf1-x434.google.com [IPv6:2607:f8b0:4864:20::434]) by sourceware.org (Postfix) with ESMTPS id E40B2386DC6C for ; Sat, 2 Jul 2022 14:05:12 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.1 sourceware.org E40B2386DC6C Received: by mail-pf1-x434.google.com with SMTP id k9so5002563pfg.5 for ; Sat, 02 Jul 2022 07:05:12 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:mime-version:references:in-reply-to:from:date :message-id:subject:to; bh=P9+ojmi5jbKbaFvy+cDqG1Gd29VfzoZQk0E8VXYQTeg=; b=JYcl+IwKvTeLGjwLMmfk7/Myo6T8F1Co2KBvlLk22CqybA4sOwg11EP9jEn0pPoaab WpRbUr2CsDJHrTkCkb+9q6pr1G03GfoLeOW/fBLhqiK/Fch9rpHkYCfKJwRk2GZyEtQJ H9mf6/TwJJ1EBLZwlD3PEexknqZV8zOUY+lw8yRWJGJPtp3VzJ2nVNjM0OUJILVuqQPU q0ENE+soQd65FTuouPrgTZnUthmNS00q3uBJrUIfbd3LRLmagcx+gXMAjQAkkGUXBxyj /SdpayfI4ojKr7G6OA+GWDsiBNIUzQQ/WO0oOoBMS0I6BS9Rqo06Hvr/Hgn7FnjZiXty zjFA== X-Gm-Message-State: AJIora/pZ/zWEb6xi5sUSqKuQyryMXrEZV8uNL0kxa4TIKKBSN+LiaLn Ea/nF4Spq7X+YMBT5zCx8k7K8ZoTRk593TAYGk0lDex0p6ozMg== X-Google-Smtp-Source: AGRyM1se2OZojPqM3QYIymKggPDpPT2eCcAkIm1sL0KNqne+HZSfQc7gmtxi4wh2Hx2fPKFrYmSHYjZj/q7VVTOwd8o= X-Received: by 2002:a05:6a00:1693:b0:528:3c0:5825 with SMTP id k19-20020a056a00169300b0052803c05825mr17140572pfc.21.1656770711285; Sat, 02 Jul 2022 07:05:11 -0700 (PDT) MIME-Version: 1.0 References: In-Reply-To: From: Mir Immad Date: Sat, 2 Jul 2022 19:35:00 +0530 Message-ID: Subject: Re: [PATCH] PR 106003 To: gcc@gcc.gnu.org, David Malcolm X-Spam-Status: No, score=-7.2 required=5.0 tests=BAYES_00, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, FREEMAIL_ENVFROM_END_DIGIT, FREEMAIL_FROM, GIT_PATCH_0, HTML_MESSAGE, KAM_NUMSUBJECT, KAM_SHORT, RCVD_IN_DNSWL_NONE, SPF_HELO_NONE, SPF_PASS, SUBJ_ALL_CAPS, 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 Content-Type: text/plain; charset="UTF-8" X-Content-Filtered-By: Mailman/MimeDel 2.1.29 X-BeenThere: gcc@gcc.gnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Gcc mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Sat, 02 Jul 2022 14:05:21 -0000 Hi everyone, This is a patch for PR 106003 https://gcc.gnu.org/bugzilla/show_bug.cgi?id=106003; tested on x86_64. On Sat, Jul 2, 2022 at 7:34 PM Mir Immad wrote: > From 62b7b7736975172f03b30783436fbc9217324223 Mon Sep 17 00:00:00 2001 > From: mir > Date: Sat, 2 Jul 2022 15:04:37 +0530 > Subject: [PATCH] analyzer: implement five new warnings for misuse of POSIX > file descriptor APIs [PR106003]. > > This patch adds a new state machine to the analyzer for checking usage of > POSIX file descriptor > APIs with five new warnings. > > It adds: > - check for FD leaks (CWE 775). > - check for double "close" of a FD (CWE-1341). > - check for read/write of a closed file descriptor. > - check whether a file descriptor was used without being checked for > validity. > - check for read/write of a descriptor opened for just writing/reading. > > gcc/ChangeLog: > PR analyzer/106003 > * Makefile.in (ANALYZER_OBJS): Add sm-fd.o. > * doc/invoke.texi: Add -Wanalyzer-fd-double-close, -Wanalyzer-fd-leak, > -Wanalyzer-fd-access-mode-mismatch, -Wanalyzer-fd-use-without-check, > -Wanalyzer-fd-use-after-close. > > gcc/analyzer/ChangeLog: > PR analyzer/106003 > * analyzer.opt (Wanalyzer-fd-leak): New option. > (Wanalyzer-fd-access-mode-mismatch): New option. > (Wanalyzer-fd-use-without-check): New option. > (Wanalyzer-fd-double-close): New option. > (Wanalyzer-fd-use-after-close): New option. > * sm.h (make_fd_state_machine): New decl. > * sm.cc (make_checkers): Call make_fd_state_machine. > * sm-fd.cc: New file. > > gcc/testsuite/ChangeLog: > PR analyzer/106003 > * gcc.dg/analyzer/fd-1.c: New test. > * gcc.dg/analyzer/fd-2.c: New test. > * gcc.dg/analyzer/fd-3.c: New test. > * gcc.dg/analyzer/fd-4.c: New test. > --- > gcc/Makefile.in | 1 + > gcc/analyzer/analyzer.opt | 20 + > gcc/analyzer/sm-fd.cc | 847 +++++++++++++++++++++++++++ > gcc/analyzer/sm.cc | 1 + > gcc/analyzer/sm.h | 1 + > gcc/doc/invoke.texi | 55 ++ > gcc/testsuite/gcc.dg/analyzer/fd-1.c | 39 ++ > gcc/testsuite/gcc.dg/analyzer/fd-2.c | 49 ++ > gcc/testsuite/gcc.dg/analyzer/fd-3.c | 85 +++ > gcc/testsuite/gcc.dg/analyzer/fd-4.c | 62 ++ > 10 files changed, 1160 insertions(+) > create mode 100644 gcc/analyzer/sm-fd.cc > create mode 100644 gcc/testsuite/gcc.dg/analyzer/fd-1.c > create mode 100644 gcc/testsuite/gcc.dg/analyzer/fd-2.c > create mode 100644 gcc/testsuite/gcc.dg/analyzer/fd-3.c > create mode 100644 gcc/testsuite/gcc.dg/analyzer/fd-4.c > > diff --git a/gcc/Makefile.in b/gcc/Makefile.in > index b6dcc45a58a..04631f737ea 100644 > --- a/gcc/Makefile.in > +++ b/gcc/Makefile.in > @@ -1269,6 +1269,7 @@ ANALYZER_OBJS = \ > analyzer/region-model-reachability.o \ > analyzer/sm.o \ > analyzer/sm-file.o \ > + analyzer/sm-fd.o \ > analyzer/sm-malloc.o \ > analyzer/sm-pattern-test.o \ > analyzer/sm-sensitive.o \ > diff --git a/gcc/analyzer/analyzer.opt b/gcc/analyzer/analyzer.opt > index 23dfc797cea..479990b0e5e 100644 > --- a/gcc/analyzer/analyzer.opt > +++ b/gcc/analyzer/analyzer.opt > @@ -66,6 +66,26 @@ Wanalyzer-exposure-through-output-file > Common Var(warn_analyzer_exposure_through_output_file) Init(1) Warning > Warn about code paths in which sensitive data is written to a file. > > +Wanalyzer-fd-access-mode-mismatch > +Common Var(warn_analyzer_fd_mode_mismatch) Init(1) Warning > +Warn about code paths in which read on a write-only file descriptor is > attempted, or vice versa. > + > +Wanalyzer-fd-double-close > +Common Var(warn_analyzer_fd_double_close) Init(1) Warning > +Warn about code paths in which a file descriptor can be closed more than > once. > + > +Wanalyzer-fd-leak > +Common Var(warn_analyzer_fd_leak) Init(1) Warning > +Warn about code paths in which a file descriptor is not closed. > + > +Wanalyzer-fd-use-after-close > +Common Var(warn_analyzer_fd_use_after_close) Init(1) Warning > +Warn about code paths in which a read or write is performed on a closed > file descriptor. > + > +Wanalyzer-fd-use-without-check > +Common Var(warn_analyzer_fd_use_without_check) Init(1) Warning > +Warn about code paths in which a file descriptor is used without being > checked for validity. > + > Wanalyzer-file-leak > Common Var(warn_analyzer_file_leak) Init(1) Warning > Warn about code paths in which a stdio FILE is not closed. > diff --git a/gcc/analyzer/sm-fd.cc b/gcc/analyzer/sm-fd.cc > new file mode 100644 > index 00000000000..4058ac53308 > --- /dev/null > +++ b/gcc/analyzer/sm-fd.cc > @@ -0,0 +1,847 @@ > +/* A state machine for detecting misuses of POSIX file descriptor APIs. > + Copyright (C) 2019-2022 Free Software Foundation, Inc. > + Contributed by Immad Mir . > + > +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" > +#include "system.h" > +#include "coretypes.h" > +#include "tree.h" > +#include "function.h" > +#include "basic-block.h" > +#include "gimple.h" > +#include "options.h" > +#include "diagnostic-path.h" > +#include "diagnostic-metadata.h" > +#include "function.h" > +#include "json.h" > +#include "analyzer/analyzer.h" > +#include "diagnostic-event-id.h" > +#include "analyzer/analyzer-logging.h" > +#include "analyzer/sm.h" > +#include "analyzer/pending-diagnostic.h" > +#include "analyzer/function-set.h" > +#include "analyzer/analyzer-selftests.h" > +#include "tristate.h" > +#include "selftest.h" > +#include "analyzer/call-string.h" > +#include "analyzer/program-point.h" > +#include "analyzer/store.h" > +#include "analyzer/region-model.h" > + > +#if ENABLE_ANALYZER > + > +namespace ana { > + > +namespace { > + > +/* An enum for distinguishing between three different access modes. */ > + > +enum access_mode > +{ > + READ_WRITE, > + READ_ONLY, > + WRITE_ONLY > +}; > + > +class fd_state_machine : public state_machine > +{ > +public: > + fd_state_machine (logger *logger); > + > + bool > + inherited_state_p () const final override > + { > + return false; > + } > + > + state_machine::state_t > + get_default_state (const svalue *sval) const final override > + { > + if (tree cst = sval->maybe_get_constant ()) > + { > + if (TREE_CODE (cst) == INTEGER_CST) > + { > + int val = TREE_INT_CST_LOW (cst); > + if (val >= 0) > + return m_constant_fd; > + else > + return m_invalid; > + } > + } > + return m_start; > + } > + > + bool on_stmt (sm_context *sm_ctxt, const supernode *node, > + const gimple *stmt) const final override; > + > + void on_condition (sm_context *sm_ctxt, const supernode *node, > + const gimple *stmt, const svalue *lhs, const > tree_code op, > + const svalue *rhs) const final override; > + > + bool can_purge_p (state_t s) const final override; > + pending_diagnostic *on_leak (tree var) const final override; > + > + bool is_unchecked_fd_p (state_t s) const; > + bool is_valid_fd_p (state_t s) const; > + bool is_closed_fd_p (state_t s) const; > + bool is_constant_fd_p (state_t s) const; > + bool is_readonly_fd_p (state_t s) const; > + bool is_writeonly_fd_p (state_t s) const; > + enum access_mode get_access_mode_from_flag (int flag) const; > + > + /* State for a constant file descriptor (>= 0) */ > + state_t m_constant_fd; > + > + /* States representing a file descriptor that hasn't yet been > + checked for validity after opening, for three different > + access modes. */ > + state_t m_unchecked_read_write; > + > + state_t m_unchecked_read_only; > + > + state_t m_unchecked_write_only; > + > + /* States for representing a file descriptor that is known to be valid > (>= > + 0), for three different access modes.*/ > + state_t m_valid_read_write; > + > + state_t m_valid_read_only; > + > + state_t m_valid_write_only; > + > + /* State for a file descriptor that is known to be invalid (< 0). */ > + state_t m_invalid; > + > + /* State for a file descriptor that has been closed.*/ > + state_t m_closed; > + > + /* State for a file descriptor that we do not want to track anymore . */ > + state_t m_stop; > + > +private: > + void on_open (sm_context *sm_ctxt, const supernode *node, const gimple > *stmt, > + const gcall *call) const; > + void on_close (sm_context *sm_ctxt, const supernode *node, const gimple > *stmt, > + const gcall *call) const; > + void on_read (sm_context *sm_ctxt, const supernode *node, const gimple > *stmt, > + const gcall *call, const tree callee_fndecl) const; > + void on_write (sm_context *sm_ctxt, const supernode *node, const gimple > *stmt, > + const gcall *call, const tree callee_fndecl) const; > + void check_for_open_fd (sm_context *sm_ctxt, const supernode *node, > + const gimple *stmt, const gcall *call, > + const tree callee_fndecl, > + enum access_direction access_fn) const; > + > + void make_valid_transitions_on_condition (sm_context *sm_ctxt, > + const supernode *node, > + const gimple *stmt, > + const svalue *lhs) const; > + void make_invalid_transitions_on_condition (sm_context *sm_ctxt, > + const supernode *node, > + const gimple *stmt, > + const svalue *lhs) const; > +}; > + > +/* Base diagnostic class relative to fd_state_machine. */ > +class fd_diagnostic : public pending_diagnostic > +{ > +public: > + fd_diagnostic (const fd_state_machine &sm, tree arg) : m_sm (sm), m_arg > (arg) > + { > + } > + > + bool > + subclass_equal_p (const pending_diagnostic &base_other) const override > + { > + return same_tree_p (m_arg, ((const fd_diagnostic &)base_other).m_arg); > + } > + > + label_text > + describe_state_change (const evdesc::state_change &change) override > + { > + if (change.m_old_state == m_sm.get_start_state () > + && m_sm.is_unchecked_fd_p (change.m_new_state)) > + { > + if (change.m_new_state == m_sm.m_unchecked_read_write) > + return change.formatted_print ("opened here as read-write"); > + > + if (change.m_new_state == m_sm.m_unchecked_read_only) > + return change.formatted_print ("opened here as read-only"); > + > + if (change.m_new_state == m_sm.m_unchecked_write_only) > + return change.formatted_print ("opened here as write-only"); > + } > + > + if (change.m_new_state == m_sm.m_closed) > + return change.formatted_print ("closed here"); > + > + if (m_sm.is_unchecked_fd_p (change.m_old_state) > + && m_sm.is_valid_fd_p (change.m_new_state)) > + { > + if (change.m_expr) > + return change.formatted_print ( > + "assuming %qE is a valid file descriptor (>= 0)", > change.m_expr); > + else > + return change.formatted_print ("assuming a valid file > descriptor"); > + } > + > + if (m_sm.is_unchecked_fd_p (change.m_old_state) > + && change.m_new_state == m_sm.m_invalid) > + { > + if (change.m_expr) > + return change.formatted_print ( > + "assuming %qE is an invalid file descriptor (< 0)", > + change.m_expr); > + else > + return change.formatted_print ("assuming an invalid file > descriptor"); > + } > + > + return label_text (); > + } > + > +protected: > + const fd_state_machine &m_sm; > + tree m_arg; > +}; > + > +class fd_leak : public fd_diagnostic > +{ > +public: > + fd_leak (const fd_state_machine &sm, tree arg) : fd_diagnostic (sm, > arg) {} > + > + const char * > + get_kind () const final override > + { > + return "fd_leak"; > + } > + > + int > + get_controlling_option () const final override > + { > + return OPT_Wanalyzer_fd_leak; > + } > + > + bool > + emit (rich_location *rich_loc) final override > + { > + /*CWE-775: Missing Release of File Descriptor or Handle after > Effective > + Lifetime > + */ > + diagnostic_metadata m; > + m.add_cwe (775); > + if (m_arg) > + return warning_meta (rich_loc, m, get_controlling_option (), > + "leak of file descriptor %qE", m_arg); > + else > + return warning_meta (rich_loc, m, get_controlling_option (), > + "leak of file descriptor"); > + } > + > + label_text > + describe_state_change (const evdesc::state_change &change) final > override > + { > + if (m_sm.is_unchecked_fd_p (change.m_new_state)) > + { > + m_open_event = change.m_event_id; > + return label_text::borrow ("opened here"); > + } > + > + return fd_diagnostic::describe_state_change (change); > + } > + > + label_text > + describe_final_event (const evdesc::final_event &ev) final override > + { > + if (m_open_event.known_p ()) > + { > + if (ev.m_expr) > + return ev.formatted_print ("%qE leaks here; was opened at %@", > + ev.m_expr, &m_open_event); > + else > + return ev.formatted_print ("leaks here; was opened at %@", > + &m_open_event); > + } > + else > + { > + if (ev.m_expr) > + return ev.formatted_print ("%qE leaks here", ev.m_expr); > + else > + return ev.formatted_print ("leaks here"); > + } > + } > + > +private: > + diagnostic_event_id_t m_open_event; > +}; > + > +class fd_access_mode_mismatch : public fd_diagnostic > +{ > +public: > + fd_access_mode_mismatch (const fd_state_machine &sm, tree arg, > + enum access_direction fd_dir, > + const tree callee_fndecl) > + : fd_diagnostic (sm, arg), m_fd_dir (fd_dir), > + m_callee_fndecl (callee_fndecl) > + > + { > + } > + > + const char * > + get_kind () const final override > + { > + return "fd_access_mode_mismatch"; > + } > + > + int > + get_controlling_option () const final override > + { > + return OPT_Wanalyzer_fd_access_mode_mismatch; > + } > + > + bool > + emit (rich_location *rich_loc) final override > + { > + switch (m_fd_dir) > + { > + case DIR_READ: > + return warning_at (rich_loc, get_controlling_option (), > + "%qE on % file descriptor %qE", > + m_callee_fndecl, m_arg); > + case DIR_WRITE: > + return warning_at (rich_loc, get_controlling_option (), > + "%qE on % file descriptor %qE", > + m_callee_fndecl, m_arg); > + default: > + gcc_unreachable (); > + } > + } > + > + bool > + subclass_equal_p (const pending_diagnostic &base_other) const override > + { > + const fd_access_mode_mismatch &sub_other > + = (const fd_access_mode_mismatch &)base_other; > + return (same_tree_p (m_arg, sub_other.m_arg) > + && m_callee_fndecl == sub_other.m_callee_fndecl > + && m_fd_dir == sub_other.m_fd_dir); > + } > + > + label_text > + describe_final_event (const evdesc::final_event &ev) final override > + { > + switch (m_fd_dir) > + { > + case DIR_READ: > + return ev.formatted_print ("%qE on % file descriptor > %qE", > + m_callee_fndecl, m_arg); > + case DIR_WRITE: > + return ev.formatted_print ("%qE on % file descriptor > %qE", > + m_callee_fndecl, m_arg); > + default: > + gcc_unreachable (); > + } > + } > + > +private: > + enum access_direction m_fd_dir; > + const tree m_callee_fndecl; > +}; > + > +class double_close : public fd_diagnostic > +{ > +public: > + double_close (const fd_state_machine &sm, tree arg) : fd_diagnostic > (sm, arg) > + { > + } > + > + const char * > + get_kind () const final override > + { > + return "double_close"; > + } > + > + int > + get_controlling_option () const final override > + { > + return OPT_Wanalyzer_fd_double_close; > + } > + bool > + emit (rich_location *rich_loc) final override > + { > + diagnostic_metadata m; > + // CWE-1341: Multiple Releases of Same Resource or Handle > + m.add_cwe (1341); > + return warning_meta (rich_loc, m, get_controlling_option (), > + "double % of file descriptor %qE", > m_arg); > + } > + > + label_text > + describe_state_change (const evdesc::state_change &change) override > + { > + if (m_sm.is_unchecked_fd_p (change.m_new_state)) > + return label_text::borrow ("opened here"); > + > + if (change.m_new_state == m_sm.m_closed) > + { > + m_first_close_event = change.m_event_id; > + return change.formatted_print ("first %qs here", "close"); > + } > + return fd_diagnostic::describe_state_change (change); > + } > + > + label_text > + describe_final_event (const evdesc::final_event &ev) final override > + { > + if (m_first_close_event.known_p ()) > + return ev.formatted_print ("second %qs here; first %qs was at %@", > + "close", "close", &m_first_close_event); > + return ev.formatted_print ("second %qs here", "close"); > + } > + > +private: > + diagnostic_event_id_t m_first_close_event; > +}; > + > +class fd_use_after_close : public fd_diagnostic > +{ > +public: > + fd_use_after_close (const fd_state_machine &sm, tree arg, > + const tree callee_fndecl) > + : fd_diagnostic (sm, arg), m_callee_fndecl (callee_fndecl) > + { > + } > + > + const char * > + get_kind () const final override > + { > + return "fd_use_after_close"; > + } > + > + int > + get_controlling_option () const final override > + { > + return OPT_Wanalyzer_fd_use_after_close; > + } > + > + bool > + emit (rich_location *rich_loc) final override > + { > + return warning_at (rich_loc, get_controlling_option (), > + "%qE on closed file descriptor %qE", > m_callee_fndecl, > + m_arg); > + } > + > + label_text > + describe_state_change (const evdesc::state_change &change) override > + { > + if (m_sm.is_unchecked_fd_p (change.m_new_state)) > + return label_text::borrow ("opened here"); > + > + if (change.m_new_state == m_sm.m_closed) > + return change.formatted_print ("closed here"); > + > + return fd_diagnostic::describe_state_change (change); > + } > + > + label_text > + describe_final_event (const evdesc::final_event &ev) final override > + { > + return ev.formatted_print ("%qE on closed file descriptor %qE here", > + m_callee_fndecl, m_arg); > + } > + > +private: > + const tree m_callee_fndecl; > +}; > + > +class unchecked_use_of_fd : public fd_diagnostic > +{ > +public: > + unchecked_use_of_fd (const fd_state_machine &sm, tree arg, > + const tree callee_fndecl) > + : fd_diagnostic (sm, arg), m_callee_fndecl (callee_fndecl) > + { > + } > + > + const char * > + get_kind () const final override > + { > + return "unchecked_use_of_fd"; > + } > + > + int > + get_controlling_option () const final override > + { > + return OPT_Wanalyzer_fd_use_without_check; > + } > + > + bool > + emit (rich_location *rich_loc) final override > + { > + return warning_at (rich_loc, get_controlling_option (), > + "%qE on possibly invalid file descriptor %qE", > + m_callee_fndecl, m_arg); > + } > + > + bool > + subclass_equal_p (const pending_diagnostic &base_other) const override > + { > + const unchecked_use_of_fd &sub_other > + = (const unchecked_use_of_fd &)base_other; > + return (same_tree_p (m_arg, sub_other.m_arg) > + && m_callee_fndecl == sub_other.m_callee_fndecl); > + } > + > + label_text > + describe_state_change (const evdesc::state_change &change) override > + { > + if (m_sm.is_unchecked_fd_p (change.m_new_state)) > + { > + m_first_open_event = change.m_event_id; > + return label_text::borrow ("opened here"); > + } > + > + return fd_diagnostic::describe_state_change (change); > + } > + > + label_text > + describe_final_event (const evdesc::final_event &ev) final override > + { > + if (m_first_open_event.known_p ()) > + return ev.formatted_print ( > + "%qE could be invalid: unchecked value from %@", m_arg, > + &m_first_open_event); > + else > + return ev.formatted_print ("%qE could be invalid", m_arg); > + } > + > +private: > + diagnostic_event_id_t m_first_open_event; > + const tree m_callee_fndecl; > +}; > + > +fd_state_machine::fd_state_machine (logger *logger) > + : state_machine ("file-descriptor", logger), > + m_constant_fd (add_state ("fd-constant")), > + m_unchecked_read_write (add_state ("fd-unchecked-read-write")), > + m_unchecked_read_only (add_state ("fd-unchecked-read-only")), > + m_unchecked_write_only (add_state ("fd-unchecked-write-only")), > + m_invalid (add_state ("fd-invalid")), > + m_valid_read_write (add_state ("fd-valid-read-write")), > + m_valid_read_only (add_state ("fd-valid-read-only")), > + m_valid_write_only (add_state ("fd-valid-write-only")), > + m_closed (add_state ("fd-closed")), m_stop (add_state ("fd-stop")) > +{ > +} > + > +bool > +fd_state_machine::is_unchecked_fd_p (state_t s) const > +{ > + return (s == m_unchecked_read_write > + || s == m_unchecked_read_only > + || s == m_unchecked_write_only); > +} > + > +bool > +fd_state_machine::is_valid_fd_p (state_t s) const > +{ > + return (s == m_valid_read_write > + || s == m_valid_read_only > + || s == m_valid_write_only); > +} > + > +enum access_mode > +fd_state_machine::get_access_mode_from_flag (int flag) const > +{ > + /* FIXME: this code assumes the access modes on the host and > + target are the same, which in practice might not be the case. */ > + > + if ((flag & O_ACCMODE) == O_RDONLY) > + { > + return READ_ONLY; > + } > + else if ((flag & O_ACCMODE) == O_WRONLY) > + { > + return WRITE_ONLY; > + } > + return READ_WRITE; > +} > + > +bool > +fd_state_machine::is_readonly_fd_p (state_t state) const > +{ > + return (state == m_unchecked_read_only || state == m_valid_read_only); > +} > + > +bool > +fd_state_machine::is_writeonly_fd_p (state_t state) const > +{ > + return (state == m_unchecked_write_only || state == m_valid_write_only); > +} > + > +bool > +fd_state_machine::is_closed_fd_p (state_t state) const > +{ > + return (state == m_closed); > +} > + > +bool > +fd_state_machine::is_constant_fd_p (state_t state) const > +{ > + return (state == m_constant_fd); > +} > + > +bool > +fd_state_machine::on_stmt (sm_context *sm_ctxt, const supernode *node, > + const gimple *stmt) const > +{ > + if (const gcall *call = dyn_cast (stmt)) > + if (tree callee_fndecl = sm_ctxt->get_fndecl_for_call (call)) > + { > + if (is_named_call_p (callee_fndecl, "open", call, 2)) > + { > + on_open (sm_ctxt, node, stmt, call); > + return true; > + } // "open" > + > + if (is_named_call_p (callee_fndecl, "close", call, 1)) > + { > + on_close (sm_ctxt, node, stmt, call); > + return true; > + } // "close" > + > + if (is_named_call_p (callee_fndecl, "write", call, 3)) > + { > + on_write (sm_ctxt, node, stmt, call, callee_fndecl); > + return true; > + } // "write" > + > + if (is_named_call_p (callee_fndecl, "read", call, 3)) > + { > + on_read (sm_ctxt, node, stmt, call, callee_fndecl); > + return true; > + } // "read" > + } > + > + return false; > +} > + > +void > +fd_state_machine::on_open (sm_context *sm_ctxt, const supernode *node, > + const gimple *stmt, const gcall *call) const > +{ > + tree lhs = gimple_call_lhs (call); > + if (lhs) > + { > + tree arg = gimple_call_arg (call, 1); > + if (TREE_CODE (arg) == INTEGER_CST) > + { > + int flag = TREE_INT_CST_LOW (arg); > + enum access_mode mode = get_access_mode_from_flag (flag); > + > + switch (mode) > + { > + case READ_ONLY: > + sm_ctxt->on_transition (node, stmt, lhs, m_start, > + m_unchecked_read_only); > + break; > + case WRITE_ONLY: > + sm_ctxt->on_transition (node, stmt, lhs, m_start, > + m_unchecked_write_only); > + break; > + default: > + sm_ctxt->on_transition (node, stmt, lhs, m_start, > + m_unchecked_read_write); > + } > + } > + } > + else > + { > + sm_ctxt->warn (node, stmt, NULL_TREE, new fd_leak (*this, > NULL_TREE)); > + } > +} > + > +void > +fd_state_machine::on_close (sm_context *sm_ctxt, const supernode *node, > + const gimple *stmt, const gcall *call) const > +{ > + tree arg = gimple_call_arg (call, 0); > + state_t state = sm_ctxt->get_state (stmt, arg); > + tree diag_arg = sm_ctxt->get_diagnostic_tree (arg); > + > + sm_ctxt->on_transition (node, stmt, arg, m_start, m_closed); > + sm_ctxt->on_transition (node, stmt, arg, m_unchecked_read_write, > m_closed); > + sm_ctxt->on_transition (node, stmt, arg, m_unchecked_read_only, > m_closed); > + sm_ctxt->on_transition (node, stmt, arg, m_unchecked_write_only, > m_closed); > + sm_ctxt->on_transition (node, stmt, arg, m_valid_read_write, m_closed); > + sm_ctxt->on_transition (node, stmt, arg, m_valid_read_only, m_closed); > + sm_ctxt->on_transition (node, stmt, arg, m_valid_write_only, m_closed); > + sm_ctxt->on_transition (node, stmt, arg, m_constant_fd, m_closed); > + > + if (is_closed_fd_p (state)) > + { > + sm_ctxt->warn (node, stmt, arg, new double_close (*this, diag_arg)); > + sm_ctxt->set_next_state (stmt, arg, m_stop); > + } > +} > +void > +fd_state_machine::on_read (sm_context *sm_ctxt, const supernode *node, > + const gimple *stmt, const gcall *call, > + const tree callee_fndecl) const > +{ > + check_for_open_fd (sm_ctxt, node, stmt, call, callee_fndecl, DIR_READ); > +} > +void > +fd_state_machine::on_write (sm_context *sm_ctxt, const supernode *node, > + const gimple *stmt, const gcall *call, > + const tree callee_fndecl) const > +{ > + check_for_open_fd (sm_ctxt, node, stmt, call, callee_fndecl, DIR_WRITE); > +} > + > +void > +fd_state_machine::check_for_open_fd ( > + sm_context *sm_ctxt, const supernode *node, const gimple *stmt, > + const gcall *call, const tree callee_fndecl, > + enum access_direction callee_fndecl_dir) const > +{ > + tree arg = gimple_call_arg (call, 0); > + tree diag_arg = sm_ctxt->get_diagnostic_tree (arg); > + state_t state = sm_ctxt->get_state (stmt, arg); > + > + if (is_closed_fd_p (state)) > + { > + sm_ctxt->warn (node, stmt, arg, > + new fd_use_after_close (*this, diag_arg, > callee_fndecl)); > + } > + > + else > + { > + if (!(is_valid_fd_p (state) || (state == m_stop))) > + { > + if (!is_constant_fd_p (state)) > + sm_ctxt->warn ( > + node, stmt, arg, > + new unchecked_use_of_fd (*this, diag_arg, callee_fndecl)); > + } > + switch (callee_fndecl_dir) > + { > + case DIR_READ: > + if (is_writeonly_fd_p (state)) > + { > + tree diag_arg = sm_ctxt->get_diagnostic_tree (arg); > + sm_ctxt->warn (node, stmt, arg, > + new fd_access_mode_mismatch ( > + *this, diag_arg, DIR_WRITE, > callee_fndecl)); > + } > + > + break; > + case DIR_WRITE: > + > + if (is_readonly_fd_p (state)) > + { > + tree diag_arg = sm_ctxt->get_diagnostic_tree (arg); > + sm_ctxt->warn (node, stmt, arg, > + new fd_access_mode_mismatch ( > + *this, diag_arg, DIR_READ, > callee_fndecl)); > + } > + break; > + } > + } > +} > + > +void > +fd_state_machine::on_condition (sm_context *sm_ctxt, const supernode > *node, > + const gimple *stmt, const svalue *lhs, > + enum tree_code op, const svalue *rhs) > const > +{ > + if (tree cst = rhs->maybe_get_constant ()) > + { > + if (TREE_CODE (cst) == INTEGER_CST) > + { > + int val = TREE_INT_CST_LOW (cst); > + if (val == -1) > + { > + if (op == NE_EXPR) > + make_valid_transitions_on_condition (sm_ctxt, node, stmt, > lhs); > + > + else if (op == EQ_EXPR) > + make_invalid_transitions_on_condition (sm_ctxt, node, > stmt, > + lhs); > + } > + } > + } > + > + if (rhs->all_zeroes_p ()) > + { > + if (op == GE_EXPR) > + make_valid_transitions_on_condition (sm_ctxt, node, stmt, lhs); > + else if (op == LT_EXPR) > + make_invalid_transitions_on_condition (sm_ctxt, node, stmt, lhs); > + } > +} > + > +void > +fd_state_machine::make_valid_transitions_on_condition (sm_context > *sm_ctxt, > + const supernode > *node, > + const gimple *stmt, > + const svalue *lhs) > const > +{ > + sm_ctxt->on_transition (node, stmt, lhs, m_unchecked_read_write, > + m_valid_read_write); > + sm_ctxt->on_transition (node, stmt, lhs, m_unchecked_read_only, > + m_valid_read_only); > + sm_ctxt->on_transition (node, stmt, lhs, m_unchecked_write_only, > + m_valid_write_only); > +} > + > +void > +fd_state_machine::make_invalid_transitions_on_condition ( > + sm_context *sm_ctxt, const supernode *node, const gimple *stmt, > + const svalue *lhs) const > +{ > + sm_ctxt->on_transition (node, stmt, lhs, m_unchecked_read_write, > m_invalid); > + sm_ctxt->on_transition (node, stmt, lhs, m_unchecked_read_only, > m_invalid); > + sm_ctxt->on_transition (node, stmt, lhs, m_unchecked_write_only, > m_invalid); > +} > + > +bool > +fd_state_machine::can_purge_p (state_t s) const > +{ > + if (is_unchecked_fd_p (s) || is_valid_fd_p (s)) > + return false; > + else > + return true; > +} > + > +pending_diagnostic * > +fd_state_machine::on_leak (tree var) const > +{ > + return new fd_leak (*this, var); > +} > +} // namespace > + > +state_machine * > +make_fd_state_machine (logger *logger) > +{ > + return new fd_state_machine (logger); > +} > +} // namespace ana > + > +#endif // ENABLE_ANALYZER > \ No newline at end of file > diff --git a/gcc/analyzer/sm.cc b/gcc/analyzer/sm.cc > index 622cb0b7ab3..24c20b894cd 100644 > --- a/gcc/analyzer/sm.cc > +++ b/gcc/analyzer/sm.cc > @@ -167,6 +167,7 @@ make_checkers (auto_delete_vec &out, > logger *logger) > { > out.safe_push (make_malloc_state_machine (logger)); > out.safe_push (make_fileptr_state_machine (logger)); > + out.safe_push (make_fd_state_machine (logger)); > /* The "taint" checker must be explicitly enabled (as it currently > leads to state explosions that stop the other checkers working). */ > if (flag_analyzer_checker) > diff --git a/gcc/analyzer/sm.h b/gcc/analyzer/sm.h > index 4cc54531c56..e80ef1fac37 100644 > --- a/gcc/analyzer/sm.h > +++ b/gcc/analyzer/sm.h > @@ -301,6 +301,7 @@ extern state_machine *make_sensitive_state_machine > (logger *logger); > extern state_machine *make_signal_state_machine (logger *logger); > extern state_machine *make_pattern_test_state_machine (logger *logger); > extern state_machine *make_va_list_state_machine (logger *logger); > +extern state_machine *make_fd_state_machine (logger *logger); > > } // namespace ana > > diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi > index 174bc09e5cf..b4a46dddfbb 100644 > --- a/gcc/doc/invoke.texi > +++ b/gcc/doc/invoke.texi > @@ -9709,6 +9709,11 @@ Enabling this option effectively enables the > following warnings: > -Wanalyzer-double-fclose @gol > -Wanalyzer-double-free @gol > -Wanalyzer-exposure-through-output-file @gol > +-Wanalyzer-fd-access-mode-mismatch @gol > +-Wanalyzer-fd-double-close @gol > +-Wanalyzer-fd-leak @gol > +-Wanalyzer-fd-use-after-close @gol > +-Wanalyzer-fd-use-without-check @gol > -Wanalyzer-file-leak @gol > -Wanalyzer-free-of-non-heap @gol > -Wanalyzer-malloc-leak @gol > @@ -9783,6 +9788,56 @@ This diagnostic warns for paths through the code in > which a > security-sensitive value is written to an output file > (such as writing a password to a log file). > > +@item -Wno-analyzer-fd-access-mode-mismatch > +@opindex Wanalyzer-fd-access-mode-mismatch > +@opindex Wno-analyzer-fd-access-mode-mismatch > +This warning requires @option{-fanalyzer}, which enables it; use > +@option{-Wno-analyzer-fd-access-mode-mismatch} > +to disable it. > + > +This diagnostic warns for paths through code in which a > +@code{read} on a write-only file descriptor is attempted, or vice versa > + > +@item -Wno-analyzer-fd-double-close > +@opindex Wanalyzer-fd-double-close > +@opindex Wno-analyzer-fd-double-close > +This warning requires @option{-fanalyzer}, which enables it; use > +@option{-Wno-analyzer-fd-double-close} > +to disable it. > + > +This diagnostic warns for paths through code in which a > +file descriptor can be closed more than once. > + > +@item -Wno-analyzer-fd-leak > +@opindex Wanalyzer-fd-leak > +@opindex Wno-analyzer-fd-leak > +This warning requires @option{-fanalyzer}, which enables it; use > +@option{-Wno-analyzer-fd-leak} > +to disable it. > + > +This diagnostic warns for paths through code in which an > +open file descriptor is leaked. > + > +@item -Wno-analyzer-fd-use-after-close > +@opindex Wanalyzer-fd-use-after-close > +@opindex Wno-analyzer-fd-use-after-close > +This warning requires @option{-fanalyzer}, which enables it; use > +@option{-Wno-analyzer-fd-use-after-close} > +to disable it. > + > +This diagnostic warns for paths through code in which a > +read or write is called on a closed file descriptor. > + > +@item -Wno-analyzer-fd-use-without-check > +@opindex Wanalyzer-fd-use-without-check > +@opindex Wno-analyzer-fd-use-without-check > +This warning requires @option{-fanalyzer}, which enables it; use > +@option{-Wno-analyzer-fd-use-without-check} > +to disable it. > + > +This diagnostic warns for paths through code in which a > +file descriptor is used without being checked for validity. > + > @item -Wno-analyzer-file-leak > @opindex Wanalyzer-file-leak > @opindex Wno-analyzer-file-leak > diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-1.c > b/gcc/testsuite/gcc.dg/analyzer/fd-1.c > new file mode 100644 > index 00000000000..8a72e63833c > --- /dev/null > +++ b/gcc/testsuite/gcc.dg/analyzer/fd-1.c > @@ -0,0 +1,39 @@ > +int open(const char *, int mode); > +#define O_RDONLY 0 > +#define O_WRONLY 1 > +#define O_RDWR 2 > + > +void > +test_1 (const char *path) > +{ > + int fd = open (path, O_RDONLY); /* { dg-message "\\(1\\) opened here" } > */ > + return; /* { dg-warning "leak of file descriptor 'fd' \\\[CWE-775\\\]" > "warning" } */ > + /* { dg-message "\\(2\\) 'fd' leaks here; was opened at \\(1\\)" "event" > { target *-*-* } .-1 } */ > +} > + > +void > +test_2 (const char *path) > +{ > + int fd = open (path, O_RDWR); /* { dg-message "\\(1\\) opened here" } */ > + if (fd >= 0) /* { dg-message "\\(2\\) assuming 'fd' is a valid file > descriptor" "event1" } */ > + /* { dg-message "\\(3\\) following 'true' branch \\(when 'fd >= > 0'\\)..." "event2" { target *-*-* } .-1 } */ > + { > + return; /* { dg-warning "leak of file descriptor 'fd' > \\\[CWE-775\\\]" "warning" } */ > + /* { dg-message "\\(4\\) ...to here" "event1" { target *-*-* } .-1 } > */ > + /* { dg-message "\\(5\\) 'fd' leaks here; was opened at \\(1\\)" > "event2" { target *-*-* } .-2 } */ > + } > +} > + > +void > +test_3 (const char *path) > +{ > + int fd = open (path, O_WRONLY); /* { dg-message "\\(1\\) opened here" } > */ > + return; /* { dg-warning "leak of file descriptor 'fd' \\\[CWE-775\\\]" > "warning" } */ > +} > + > +void test_4 (const char *path) > +{ > + open(path, O_RDONLY); /* { dg-warning "leak of file descriptor > \\\[CWE-775\\\]" } */ > + /* { dg-message "\\(1\\) leaks here" "" { target *-*-* } .-1 } */ > +} > + > diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-2.c > b/gcc/testsuite/gcc.dg/analyzer/fd-2.c > new file mode 100644 > index 00000000000..96ccf2f7ba8 > --- /dev/null > +++ b/gcc/testsuite/gcc.dg/analyzer/fd-2.c > @@ -0,0 +1,49 @@ > +int open(const char *, int mode); > +void close(int fd); > +#define O_RDONLY 0 > +#define O_WRONLY 1 > +#define O_RDWR 2 > +#define STDIN 0 > + > +void > +test_1 (const char *path) > +{ > + int fd = open (path, O_RDWR); /* { dg-message "\\(1\\) opened here" } > */ > + close (fd); /* { dg-message "\\(2\\) first 'close' here" "event1" } */ > + close (fd); /* { dg-warning "double 'close' of file descriptor 'fd' > \\\[CWE-1341\\\]" "warning" } */ > + /* { dg-message "\\(3\\) second 'close' here; first 'close' was at > \\(2\\)" "event2" { target *-*-* } .-1 } */ > +} > + > +void > +test_2 (const char *path) > +{ > + int fd = open (path, O_RDWR); /* { dg-message "\\(1\\) opened here" } > */ > + if (fd < 0) /* { dg-message "\\(2\\) assuming 'fd' is a valid file > descriptor \\(>= 0\\)" "event1" } */ > + /* { dg-message "\\(3\\) following 'false' branch \\(when 'fd >= > 0'\\)..." "event2" { target *-*-* } .-1 } */ > + return; > + close (fd); /* { dg-message "\\(4\\) ...to here" "event1" } */ > + /* { dg-message "\\(5\\) first 'close' here" "event2" { target *-*-* > } .-1 } */ > + close (fd); /* { dg-warning "double 'close' of file descriptor 'fd' > \\\[CWE-1341\\\]" "warning" } */ > + /* {dg-message "\\(6\\) second 'close' here; first was at \\(5\\)" "" > { target *-*-* } .-1 } */ > +} > + > +void > +test_3 () > +{ > + /* FD 0 is stdin at the entry to "main" and thus read-only, but we > have no > + guarantees here that it hasn't been closed and then reopened for > + writing, so we can't issue a warning */ > + > + int fd = STDIN; > + close(fd); /* { dg-message "\\(1\\) first 'close' here" } */ > + close(fd); /* { dg-warning "double 'close' of file descriptor 'fd' > \\\[CWE-1341\\\]" "warning" } */ > + /* { dg-message "\\(2\\) second 'close' here; first 'close' was at > \\(1\\)" "event2" { target *-*-* } .-1 } */ > +} > + > +void > +test_4 () > +{ > + int fd = -1; > + close(fd); > + close(fd); > +} > \ No newline at end of file > diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-3.c > b/gcc/testsuite/gcc.dg/analyzer/fd-3.c > new file mode 100644 > index 00000000000..40fc8af27b5 > --- /dev/null > +++ b/gcc/testsuite/gcc.dg/analyzer/fd-3.c > @@ -0,0 +1,85 @@ > +int open(const char *, int mode); > +void close(int fd); > +int write (int fd, void *buf, int nbytes); > +int read (int fd, void *buf, int nbytes); > +int some_condition(); > + > +#define O_RDONLY 0 > +#define O_WRONLY 1 > +#define O_RDWR 2 > +#define STDIN 0 > +#define O_NOATIME 262144 > + > +void > +test_1 (const char *path, void *buf) > +{ > + int fd = open (path, O_RDWR); /* { dg-message "\\(1\\) opened here" } > */ > + write (fd, buf, 1); /* { dg-message "\\(2\\) 'fd' could be invalid: > unchecked value from \\(1\\)" } */ > + /* { dg-warning "'write' on possibly invalid file descriptor 'fd'" > "warning" { target *-*-* } .-1 } */ > + close(fd); > +} > + > +void > +test_2 (const char *path, void *buf) > +{ > + int fd = open (path, O_RDWR); /* { dg-message "\\(1\\) opened here" } > */ > + read (fd, buf, 1); /* { dg-message "\\(2\\) 'fd' could be invalid: > unchecked value from \\(1\\)" } */ > + /* { dg-warning "'read' on possibly invalid file descriptor 'fd'" > "warning" { target *-*-* } .-1 } */ > + close (fd); > +} > + > +void > +test_3 (void *buf) > +{ > + int fd = -1; > + read (fd, buf, 1); /* { dg-warning "'read' on possibly invalid file > descriptor 'fd'" } */ > + /* { dg-message "\\(1\\) 'fd' could be invalid" "" { target *-*-* } > .-1 } */ > +} > + > +void > +test_4 (void *buf) > +{ > + int fd = STDIN; > + read (fd, buf, 1); > + close(fd); > +} > + > +void > +test_5 (char *path, void *buf) > +{ > + int flags = O_RDONLY; > + if (some_condition()) > + flags |= O_NOATIME; > + int fd = open (path, flags); > + read (fd, buf, 1); /* { dg-warning "'read' on possibly invalid file > descriptor 'fd'" } */ > + /* { dg-message "\\(1\\) 'fd' could be invalid" "" { target *-*-* } > .-1 } */ > + close (fd); > +} > + > + > +void > +test_6 (char *path, void *buf) > +{ > + int fd = open (path, O_RDONLY); > + if (fd != -1) > + { > + read (fd, buf, 1); > + } > + close (fd); > +} > + > + > +void > +test_7 (char *path, void *buf) > +{ > + int fd = open (path, O_RDWR); /* { dg-message "\\(1\\) opened here" } > */ > + if (fd != -1) /* { dg-message "\\(2\\) assuming 'fd' is an invalid > file descriptor \\(< 0\\)" } */ > + { > + read (fd, buf, 1); > + } else > + { > + write (fd, buf, 1); /* { dg-warning "'write' on possibly invalid > file descriptor 'fd'" } */ > + > + } > + close(fd); > +} > \ No newline at end of file > diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-4.c > b/gcc/testsuite/gcc.dg/analyzer/fd-4.c > new file mode 100644 > index 00000000000..a973704f403 > --- /dev/null > +++ b/gcc/testsuite/gcc.dg/analyzer/fd-4.c > @@ -0,0 +1,62 @@ > +int open(const char *, int mode); > +void close(int fd); > +int write (int fd, void *buf, int nbytes); > +int read (int fd, void *buf, int nbytes); > + > +#define O_RDONLY 0 > +#define O_WRONLY 1 > +#define O_RDWR 2 > + > + > +void > +test_1 (const char *path, void *buf) > +{ > + int fd = open (path, O_RDONLY); /* { dg-message "opened here as > read-only" } */ > + if (fd >= 0) /* { dg-message "assuming 'fd' is a valid file > descriptor \\(>= 0\\)" "event1" } */ > + /* { dg-message "following 'true' branch \\(when 'fd >= 0'\\)..." > "event2" { target *-*-* } .-1 } */ > + { > + write (fd, buf, 1); /* { dg-warning "'write' on 'read-only' file > descriptor 'fd'" "warning" } */ > + /* { dg-message "\\(4\\) ...to here" "event1" { target *-*-* } > .-1 } */ > + /* { dg-message "\\(5\\) 'write' on 'read-only' file descriptor > 'fd'" "event2" { target *-*-* } .-2 } */ > + close (fd); > + } > +} > + > +void > +test_2 (const char *path, void *buf) > +{ > + int fd = open (path, O_WRONLY); /* { dg-message "opened here as > write-only" } */ > + if (fd >= 0) /* { dg-message "assuming 'fd' is a valid file > descriptor \\(>= 0\\)" "event1" } */ > + /* { dg-message "following 'true' branch \\(when 'fd >= 0'\\)..." > "event2" { target *-*-* } .-1 } */ > + { > + read (fd, buf, 1); /* { dg-warning "'read' on 'write-only' file > descriptor 'fd'" "warning" } */ > + /* { dg-message "\\(4\\) ...to here" "event1" { target *-*-* } > .-1 } */ > + /* { dg-message "\\(5\\) 'read' on 'write-only' file descriptor > 'fd'" "event2" { target *-*-* } .-2 } */ > + close (fd); > + } > +} > + > + > +void > +test_3 (const char *path, void *buf) > +{ > + int fd = open (path, O_RDWR); /* { dg-message "\\(1\\) opened here" } > */ > + if (fd >= 0) > + { > + close(fd); /* {dg-message "\\(2\\) closed here"} */ > + read(fd, buf, 1); /* { dg-warning "'read' on closed file > descriptor 'fd'" } */ > + /* {dg-message "\\(3\\) 'read' on closed file descriptor 'fd' > here" "" {target *-*-*} .-1 } */ > + } > +} > + > +void > +test_4 (const char *path, void *buf) > +{ > + int fd = open (path, O_RDWR); /* { dg-message "\\(1\\) opened here" } > */ > + if (fd >= 0) > + { > + close(fd); /* {dg-message "\\(2\\) closed here"} */ > + write(fd, buf, 1); /* { dg-warning "'write' on closed file > descriptor 'fd'" } */ > + /* {dg-message "\\(3\\) 'write' on closed file descriptor 'fd' > here" "" {target *-*-*} .-1 } */ > + } > +} > -- > 2.25.1 > > >