analyzer: fix coding style in sm-fd.cc
Commit Message
Hi.
First, thanks Mir for your contribution. The following patch addresses
coding style issues I let you know in:
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=106003#c3
Most notably, I converted Windows endlines to Unix style, replace 8 spaces with tabs
and removed trailing whitespaces.
Please consider using our script that verifies that:
git show f8e6e2c046e1015697356ee7079fb39e0cb6add5 > diff && ./contrib/check_GNU_style.py diff
Ready to be installed?
Thanks,
Martin
gcc/analyzer/ChangeLog:
* sm-fd.cc: Run dos2unix and fix coding style issues.
---
gcc/analyzer/sm-fd.cc | 2114 ++++++++++++++++++++---------------------
1 file changed, 1057 insertions(+), 1057 deletions(-)
Comments
On Mon, 2022-07-25 at 08:44 +0200, Martin Liška wrote:
> Hi.
>
> First, thanks Mir for your contribution. The following patch
> addresses
> coding style issues I let you know in:
> https://gcc.gnu.org/bugzilla/show_bug.cgi?id=106003#c3
>
> Most notably, I converted Windows endlines to Unix style, replace 8
> spaces with tabs
> and removed trailing whitespaces.
>
> Please consider using our script that verifies that:
> git show f8e6e2c046e1015697356ee7079fb39e0cb6add5 > diff &&
> ./contrib/check_GNU_style.py diff
>
> Ready to be installed?
Yes, thanks
Dave
> Thanks,
> Martin
>
>
> gcc/analyzer/ChangeLog:
>
> * sm-fd.cc: Run dos2unix and fix coding style issues.
> ---
> gcc/analyzer/sm-fd.cc | 2114 ++++++++++++++++++++-------------------
> --
> 1 file changed, 1057 insertions(+), 1057 deletions(-)
>
> diff --git a/gcc/analyzer/sm-fd.cc b/gcc/analyzer/sm-fd.cc
> index c3dac48509e..56b0063ba42 100644
> --- a/gcc/analyzer/sm-fd.cc
> +++ b/gcc/analyzer/sm-fd.cc
> @@ -1,1057 +1,1057 @@
> -/* A state machine for detecting misuses of POSIX file descriptor
> APIs.
> - Copyright (C) 2019-2022 Free Software Foundation, Inc.
> - Contributed by Immad Mir <mir@sourceware.org>.
> -
> -This file is part of GCC.
> -
> -GCC is free software; you can redistribute it and/or modify it
> -under the terms of the GNU General Public License as published by
> -the Free Software Foundation; either version 3, or (at your option)
> -any later version.
> -
> -GCC is distributed in the hope that it will be useful, but
> -WITHOUT ANY WARRANTY; without even the implied warranty of
> -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> -General Public License for more details.
> -
> -You should have received a copy of the GNU General Public License
> -along with GCC; see the file COPYING3. If not see
> -<http://www.gnu.org/licenses/>. */
> -
> -#include "config.h"
> -#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 "stringpool.h"
> -#include "attribs.h"
> -#include "analyzer/call-string.h"
> -#include "analyzer/program-point.h"
> -#include "analyzer/store.h"
> -#include "analyzer/region-model.h"
> -#include "bitmap.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
> -};
> -
> -enum access_directions
> -{
> - DIRS_READ_WRITE,
> - DIRS_READ,
> - DIRS_WRITE
> -};
> -
> -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_directions 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;
> - void check_for_fd_attrs (sm_context *sm_ctxt, const supernode
> *node,
> - const gimple *stmt, const gcall *call,
> - const tree callee_fndecl, const char
> *attr_name,
> - access_directions fd_attr_access_dir)
> 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_param_diagnostic : public fd_diagnostic
> -{
> -public:
> - fd_param_diagnostic (const fd_state_machine &sm, tree arg, tree
> callee_fndecl,
> - const char *attr_name, int arg_idx)
> - : fd_diagnostic (sm, arg), m_callee_fndecl (callee_fndecl),
> - m_attr_name (attr_name), m_arg_idx (arg_idx)
> - {
> - }
> -
> - fd_param_diagnostic (const fd_state_machine &sm, tree arg, tree
> callee_fndecl)
> - : fd_diagnostic (sm, arg), m_callee_fndecl (callee_fndecl),
> - m_attr_name (NULL), m_arg_idx (-1)
> - {
> - }
> -
> - bool
> - subclass_equal_p (const pending_diagnostic &base_other) const
> override
> - {
> - const fd_param_diagnostic &sub_other
> - = (const fd_param_diagnostic &)base_other;
> - return (same_tree_p (m_arg, sub_other.m_arg)
> - && same_tree_p (m_callee_fndecl,
> sub_other.m_callee_fndecl)
> - && m_arg_idx == sub_other.m_arg_idx
> - && ((m_attr_name)
> - ? (strcmp (m_attr_name, sub_other.m_attr_name)
> == 0)
> - : true));
> - }
> -
> - void
> - inform_filedescriptor_attribute (access_directions fd_dir)
> - {
> -
> - if (m_attr_name)
> - switch (fd_dir)
> - {
> - case DIRS_READ_WRITE:
> - inform (DECL_SOURCE_LOCATION (m_callee_fndecl),
> - "argument %d of %qD must be an open file
> descriptor, due to "
> - "%<__attribute__((%s(%d)))%>",
> - m_arg_idx + 1, m_callee_fndecl, m_attr_name,
> m_arg_idx + 1);
> - break;
> - case DIRS_WRITE:
> - inform (DECL_SOURCE_LOCATION (m_callee_fndecl),
> - "argument %d of %qD must be a readable file
> descriptor, due "
> - "to %<__attribute__((%s(%d)))%>",
> - m_arg_idx + 1, m_callee_fndecl, m_attr_name,
> m_arg_idx + 1);
> - break;
> - case DIRS_READ:
> - inform (DECL_SOURCE_LOCATION (m_callee_fndecl),
> - "argument %d of %qD must be a writable file
> descriptor, due "
> - "to %<__attribute__((%s(%d)))%>",
> - m_arg_idx + 1, m_callee_fndecl, m_attr_name,
> m_arg_idx + 1);
> - break;
> - }
> - }
> -
> -protected:
> - tree m_callee_fndecl;
> - const char *m_attr_name;
> - /* ARG_IDX is 0-based. */
> - int m_arg_idx;
> -};
> -
> -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_param_diagnostic
> -{
> -public:
> - fd_access_mode_mismatch (const fd_state_machine &sm, tree arg,
> - enum access_directions fd_dir,
> - const tree callee_fndecl, const char
> *attr_name,
> - int arg_idx)
> - : fd_param_diagnostic (sm, arg, callee_fndecl, attr_name,
> arg_idx),
> - m_fd_dir (fd_dir)
> -
> - {
> - }
> -
> - fd_access_mode_mismatch (const fd_state_machine &sm, tree arg,
> - enum access_directions fd_dir,
> - const tree callee_fndecl)
> - : fd_param_diagnostic (sm, arg, callee_fndecl), m_fd_dir
> (fd_dir)
> - {
> - }
> -
> - 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
> - {
> - bool warned;
> - switch (m_fd_dir)
> - {
> - case DIRS_READ:
> - warned = warning_at (rich_loc, get_controlling_option (),
> - "%qE on read-only file descriptor %qE",
> - m_callee_fndecl, m_arg);
> - break;
> - case DIRS_WRITE:
> - warned = warning_at (rich_loc, get_controlling_option (),
> - "%qE on write-only file descriptor %qE",
> - m_callee_fndecl, m_arg);
> - break;
> - default:
> - gcc_unreachable ();
> - }
> - if (warned)
> - inform_filedescriptor_attribute (m_fd_dir);
> - return warned;
> - }
> -
> - label_text
> - describe_final_event (const evdesc::final_event &ev) final
> override
> - {
> - switch (m_fd_dir)
> - {
> - case DIRS_READ:
> - return ev.formatted_print ("%qE on read-only file descriptor
> %qE",
> - m_callee_fndecl, m_arg);
> - case DIRS_WRITE:
> - return ev.formatted_print ("%qE on write-only file
> descriptor %qE",
> - m_callee_fndecl, m_arg);
> - default:
> - gcc_unreachable ();
> - }
> - }
> -
> -private:
> - enum access_directions m_fd_dir;
> -};
> -
> -class fd_double_close : public fd_diagnostic
> -{
> -public:
> - fd_double_close (const fd_state_machine &sm, tree arg) :
> fd_diagnostic (sm, arg)
> - {
> - }
> -
> - const char *
> - get_kind () const final override
> - {
> - return "fd_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 %<close%> 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_param_diagnostic
> -{
> -public:
> - fd_use_after_close (const fd_state_machine &sm, tree arg,
> - const tree callee_fndecl, const char
> *attr_name,
> - int arg_idx)
> - : fd_param_diagnostic (sm, arg, callee_fndecl, attr_name,
> arg_idx)
> - {
> - }
> -
> - fd_use_after_close (const fd_state_machine &sm, tree arg,
> - const tree callee_fndecl)
> - : fd_param_diagnostic (sm, arg, 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
> - {
> - bool warned;
> - warned = warning_at (rich_loc, get_controlling_option (),
> - "%qE on closed file descriptor %qE",
> m_callee_fndecl,
> - m_arg);
> - if (warned)
> - inform_filedescriptor_attribute (DIRS_READ_WRITE);
> - return warned;
> - }
> -
> - 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 ("closed here");
> - }
> -
> - 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 (
> - "%qE on closed file descriptor %qE; %qs was at %@",
> m_callee_fndecl,
> - m_arg, "close", &m_first_close_event);
> - else
> - return ev.formatted_print ("%qE on closed file descriptor
> %qE",
> - m_callee_fndecl, m_arg);
> - }
> -
> -private:
> - diagnostic_event_id_t m_first_close_event;
> -};
> -
> -class fd_use_without_check : public fd_param_diagnostic
> -{
> -public:
> - fd_use_without_check (const fd_state_machine &sm, tree arg,
> - const tree callee_fndecl, const char
> *attr_name,
> - int arg_idx)
> - : fd_param_diagnostic (sm, arg, callee_fndecl, attr_name,
> arg_idx)
> - {
> - }
> -
> - fd_use_without_check (const fd_state_machine &sm, tree arg,
> - const tree callee_fndecl)
> - : fd_param_diagnostic (sm, arg, callee_fndecl)
> - {
> - }
> -
> - const char *
> - get_kind () const final override
> - {
> - return "fd_use_without_check";
> - }
> -
> - int
> - get_controlling_option () const final override
> - {
> - return OPT_Wanalyzer_fd_use_without_check;
> - }
> -
> - bool
> - emit (rich_location *rich_loc) final override
> - {
> - bool warned;
> - warned = warning_at (rich_loc, get_controlling_option (),
> - "%qE on possibly invalid file descriptor
> %qE",
> - m_callee_fndecl, m_arg);
> - if (warned)
> - inform_filedescriptor_attribute (DIRS_READ_WRITE);
> - return warned;
> - }
> -
> - 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;
> -};
> -
> -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_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_invalid (add_state ("fd-invalid")),
> - 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<const gcall *> (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"
> -
> -
> - {
> - // Handle __attribute__((fd_arg))
> -
> - check_for_fd_attrs (sm_ctxt, node, stmt, call,
> callee_fndecl,
> - "fd_arg", DIRS_READ_WRITE);
> -
> - // Handle __attribute__((fd_arg_read))
> -
> - check_for_fd_attrs (sm_ctxt, node, stmt, call,
> callee_fndecl,
> - "fd_arg_read", DIRS_READ);
> -
> - // Handle __attribute__((fd_arg_write))
> -
> - check_for_fd_attrs (sm_ctxt, node, stmt, call,
> callee_fndecl,
> - "fd_arg_write", DIRS_WRITE);
> - }
> - }
> -
> - return false;
> -}
> -
> -void
> -fd_state_machine::check_for_fd_attrs (
> - sm_context *sm_ctxt, const supernode *node, const gimple *stmt,
> - const gcall *call, const tree callee_fndecl, const char
> *attr_name,
> - access_directions fd_attr_access_dir) const
> -{
> -
> - tree attrs = TYPE_ATTRIBUTES (TREE_TYPE (callee_fndecl));
> - attrs = lookup_attribute (attr_name, attrs);
> - if (!attrs)
> - return;
> -
> - if (!TREE_VALUE (attrs))
> - return;
> -
> - auto_bitmap argmap;
> -
> - for (tree idx = TREE_VALUE (attrs); idx; idx = TREE_CHAIN (idx))
> - {
> - unsigned int val = TREE_INT_CST_LOW (TREE_VALUE (idx)) - 1;
> - bitmap_set_bit (argmap, val);
> - }
> - if (bitmap_empty_p (argmap))
> - return;
> -
> - for (unsigned arg_idx = 0; arg_idx < gimple_call_num_args (call);
> arg_idx++)
> - {
> - tree arg = gimple_call_arg (call, arg_idx);
> - tree diag_arg = sm_ctxt->get_diagnostic_tree (arg);
> - state_t state = sm_ctxt->get_state (stmt, arg);
> - bool bit_set = bitmap_bit_p (argmap, arg_idx);
> - if (TREE_CODE (TREE_TYPE (arg)) != INTEGER_TYPE)
> - continue;
> - if (bit_set) // Check if arg_idx is marked by any of the file
> descriptor
> - // attributes
> - {
> -
> - if (is_closed_fd_p (state))
> - {
> -
> - sm_ctxt->warn (node, stmt, arg,
> - new fd_use_after_close (*this,
> diag_arg,
> - callee_fndecl,
> attr_name,
> - arg_idx));
> - continue;
> - }
> -
> - if (!(is_valid_fd_p (state) || (state == m_stop)))
> - {
> - if (!is_constant_fd_p (state))
> - sm_ctxt->warn (node, stmt, arg,
> - new fd_use_without_check (*this,
> diag_arg,
> -
> callee_fndecl, attr_name,
> - arg_idx));
> - }
> -
> - switch (fd_attr_access_dir)
> - {
> - case DIRS_READ_WRITE:
> - break;
> - case DIRS_READ:
> -
> - if (is_writeonly_fd_p (state))
> - {
> - sm_ctxt->warn (
> - node, stmt, arg,
> - new fd_access_mode_mismatch (*this, diag_arg,
> DIRS_WRITE,
> - callee_fndecl,
> attr_name, arg_idx));
> - }
> -
> - break;
> - case DIRS_WRITE:
> -
> - if (is_readonly_fd_p (state))
> - {
> - sm_ctxt->warn (
> - node, stmt, arg,
> - new fd_access_mode_mismatch (*this, diag_arg,
> DIRS_READ,
> - callee_fndecl,
> attr_name, arg_idx));
> - }
> -
> - break;
> - }
> - }
> - }
> -}
> -
> -
> -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 fd_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,
> DIRS_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,
> DIRS_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_directions 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 fd_use_without_check (*this, diag_arg,
> callee_fndecl));
> - }
> - switch (callee_fndecl_dir)
> - {
> - case DIRS_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, DIRS_WRITE,
> callee_fndecl));
> - }
> -
> - break;
> - case DIRS_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, DIRS_READ,
> callee_fndecl));
> - }
> - break;
> - default:
> - gcc_unreachable ();
> - }
> - }
> -}
> -
> -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
> +/* A state machine for detecting misuses of POSIX file descriptor
> APIs.
> + Copyright (C) 2019-2022 Free Software Foundation, Inc.
> + Contributed by Immad Mir <mir@sourceware.org>.
> +
> +This file is part of GCC.
> +
> +GCC is free software; you can redistribute it and/or modify it
> +under the terms of the GNU General Public License as published by
> +the Free Software Foundation; either version 3, or (at your option)
> +any later version.
> +
> +GCC is distributed in the hope that it will be useful, but
> +WITHOUT ANY WARRANTY; without even the implied warranty of
> +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> +General Public License for more details.
> +
> +You should have received a copy of the GNU General Public License
> +along with GCC; see the file COPYING3. If not see
> +<http://www.gnu.org/licenses/>. */
> +
> +#include "config.h"
> +#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 "stringpool.h"
> +#include "attribs.h"
> +#include "analyzer/call-string.h"
> +#include "analyzer/program-point.h"
> +#include "analyzer/store.h"
> +#include "analyzer/region-model.h"
> +#include "bitmap.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
> +};
> +
> +enum access_directions
> +{
> + DIRS_READ_WRITE,
> + DIRS_READ,
> + DIRS_WRITE
> +};
> +
> +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_directions 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;
> + void check_for_fd_attrs (sm_context *sm_ctxt, const supernode
> *node,
> + const gimple *stmt, const gcall *call,
> + const tree callee_fndecl, const char
> *attr_name,
> + access_directions fd_attr_access_dir)
> 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_param_diagnostic : public fd_diagnostic
> +{
> +public:
> + fd_param_diagnostic (const fd_state_machine &sm, tree arg, tree
> callee_fndecl,
> + const char *attr_name, int arg_idx)
> + : fd_diagnostic (sm, arg), m_callee_fndecl (callee_fndecl),
> + m_attr_name (attr_name), m_arg_idx (arg_idx)
> + {
> + }
> +
> + fd_param_diagnostic (const fd_state_machine &sm, tree arg, tree
> callee_fndecl)
> + : fd_diagnostic (sm, arg), m_callee_fndecl (callee_fndecl),
> + m_attr_name (NULL), m_arg_idx (-1)
> + {
> + }
> +
> + bool
> + subclass_equal_p (const pending_diagnostic &base_other) const
> override
> + {
> + const fd_param_diagnostic &sub_other
> + = (const fd_param_diagnostic &)base_other;
> + return (same_tree_p (m_arg, sub_other.m_arg)
> + && same_tree_p (m_callee_fndecl,
> sub_other.m_callee_fndecl)
> + && m_arg_idx == sub_other.m_arg_idx
> + && ((m_attr_name)
> + ? (strcmp (m_attr_name, sub_other.m_attr_name) ==
> 0)
> + : true));
> + }
> +
> + void
> + inform_filedescriptor_attribute (access_directions fd_dir)
> + {
> +
> + if (m_attr_name)
> + switch (fd_dir)
> + {
> + case DIRS_READ_WRITE:
> + inform (DECL_SOURCE_LOCATION (m_callee_fndecl),
> + "argument %d of %qD must be an open file
> descriptor, due to "
> + "%<__attribute__((%s(%d)))%>",
> + m_arg_idx + 1, m_callee_fndecl, m_attr_name,
> m_arg_idx + 1);
> + break;
> + case DIRS_WRITE:
> + inform (DECL_SOURCE_LOCATION (m_callee_fndecl),
> + "argument %d of %qD must be a readable file
> descriptor, due "
> + "to %<__attribute__((%s(%d)))%>",
> + m_arg_idx + 1, m_callee_fndecl, m_attr_name,
> m_arg_idx + 1);
> + break;
> + case DIRS_READ:
> + inform (DECL_SOURCE_LOCATION (m_callee_fndecl),
> + "argument %d of %qD must be a writable file
> descriptor, due "
> + "to %<__attribute__((%s(%d)))%>",
> + m_arg_idx + 1, m_callee_fndecl, m_attr_name,
> m_arg_idx + 1);
> + break;
> + }
> + }
> +
> +protected:
> + tree m_callee_fndecl;
> + const char *m_attr_name;
> + /* ARG_IDX is 0-based. */
> + int m_arg_idx;
> +};
> +
> +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_param_diagnostic
> +{
> +public:
> + fd_access_mode_mismatch (const fd_state_machine &sm, tree arg,
> + enum access_directions fd_dir,
> + const tree callee_fndecl, const char
> *attr_name,
> + int arg_idx)
> + : fd_param_diagnostic (sm, arg, callee_fndecl, attr_name,
> arg_idx),
> + m_fd_dir (fd_dir)
> +
> + {
> + }
> +
> + fd_access_mode_mismatch (const fd_state_machine &sm, tree arg,
> + enum access_directions fd_dir,
> + const tree callee_fndecl)
> + : fd_param_diagnostic (sm, arg, callee_fndecl), m_fd_dir
> (fd_dir)
> + {
> + }
> +
> + 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
> + {
> + bool warned;
> + switch (m_fd_dir)
> + {
> + case DIRS_READ:
> + warned = warning_at (rich_loc, get_controlling_option (),
> + "%qE on read-only file descriptor %qE",
> + m_callee_fndecl, m_arg);
> + break;
> + case DIRS_WRITE:
> + warned = warning_at (rich_loc, get_controlling_option (),
> + "%qE on write-only file descriptor %qE",
> + m_callee_fndecl, m_arg);
> + break;
> + default:
> + gcc_unreachable ();
> + }
> + if (warned)
> + inform_filedescriptor_attribute (m_fd_dir);
> + return warned;
> + }
> +
> + label_text
> + describe_final_event (const evdesc::final_event &ev) final
> override
> + {
> + switch (m_fd_dir)
> + {
> + case DIRS_READ:
> + return ev.formatted_print ("%qE on read-only file descriptor
> %qE",
> + m_callee_fndecl, m_arg);
> + case DIRS_WRITE:
> + return ev.formatted_print ("%qE on write-only file descriptor
> %qE",
> + m_callee_fndecl, m_arg);
> + default:
> + gcc_unreachable ();
> + }
> + }
> +
> +private:
> + enum access_directions m_fd_dir;
> +};
> +
> +class fd_double_close : public fd_diagnostic
> +{
> +public:
> + fd_double_close (const fd_state_machine &sm, tree arg) :
> fd_diagnostic (sm, arg)
> + {
> + }
> +
> + const char *
> + get_kind () const final override
> + {
> + return "fd_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 %<close%> 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_param_diagnostic
> +{
> +public:
> + fd_use_after_close (const fd_state_machine &sm, tree arg,
> + const tree callee_fndecl, const char
> *attr_name,
> + int arg_idx)
> + : fd_param_diagnostic (sm, arg, callee_fndecl, attr_name,
> arg_idx)
> + {
> + }
> +
> + fd_use_after_close (const fd_state_machine &sm, tree arg,
> + const tree callee_fndecl)
> + : fd_param_diagnostic (sm, arg, 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
> + {
> + bool warned;
> + warned = warning_at (rich_loc, get_controlling_option (),
> + "%qE on closed file descriptor %qE",
> m_callee_fndecl,
> + m_arg);
> + if (warned)
> + inform_filedescriptor_attribute (DIRS_READ_WRITE);
> + return warned;
> + }
> +
> + 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 ("closed here");
> + }
> +
> + 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 (
> + "%qE on closed file descriptor %qE; %qs was at %@",
> m_callee_fndecl,
> + m_arg, "close", &m_first_close_event);
> + else
> + return ev.formatted_print ("%qE on closed file descriptor
> %qE",
> + m_callee_fndecl, m_arg);
> + }
> +
> +private:
> + diagnostic_event_id_t m_first_close_event;
> +};
> +
> +class fd_use_without_check : public fd_param_diagnostic
> +{
> +public:
> + fd_use_without_check (const fd_state_machine &sm, tree arg,
> + const tree callee_fndecl, const char
> *attr_name,
> + int arg_idx)
> + : fd_param_diagnostic (sm, arg, callee_fndecl, attr_name,
> arg_idx)
> + {
> + }
> +
> + fd_use_without_check (const fd_state_machine &sm, tree arg,
> + const tree callee_fndecl)
> + : fd_param_diagnostic (sm, arg, callee_fndecl)
> + {
> + }
> +
> + const char *
> + get_kind () const final override
> + {
> + return "fd_use_without_check";
> + }
> +
> + int
> + get_controlling_option () const final override
> + {
> + return OPT_Wanalyzer_fd_use_without_check;
> + }
> +
> + bool
> + emit (rich_location *rich_loc) final override
> + {
> + bool warned;
> + warned = warning_at (rich_loc, get_controlling_option (),
> + "%qE on possibly invalid file descriptor
> %qE",
> + m_callee_fndecl, m_arg);
> + if (warned)
> + inform_filedescriptor_attribute (DIRS_READ_WRITE);
> + return warned;
> + }
> +
> + 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;
> +};
> +
> +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_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_invalid (add_state ("fd-invalid")),
> + 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<const gcall *> (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"
> +
> +
> + {
> + // Handle __attribute__((fd_arg))
> +
> + check_for_fd_attrs (sm_ctxt, node, stmt, call,
> callee_fndecl,
> + "fd_arg", DIRS_READ_WRITE);
> +
> + // Handle __attribute__((fd_arg_read))
> +
> + check_for_fd_attrs (sm_ctxt, node, stmt, call,
> callee_fndecl,
> + "fd_arg_read", DIRS_READ);
> +
> + // Handle __attribute__((fd_arg_write))
> +
> + check_for_fd_attrs (sm_ctxt, node, stmt, call,
> callee_fndecl,
> + "fd_arg_write", DIRS_WRITE);
> + }
> + }
> +
> + return false;
> +}
> +
> +void
> +fd_state_machine::check_for_fd_attrs (
> + sm_context *sm_ctxt, const supernode *node, const gimple *stmt,
> + const gcall *call, const tree callee_fndecl, const char
> *attr_name,
> + access_directions fd_attr_access_dir) const
> +{
> +
> + tree attrs = TYPE_ATTRIBUTES (TREE_TYPE (callee_fndecl));
> + attrs = lookup_attribute (attr_name, attrs);
> + if (!attrs)
> + return;
> +
> + if (!TREE_VALUE (attrs))
> + return;
> +
> + auto_bitmap argmap;
> +
> + for (tree idx = TREE_VALUE (attrs); idx; idx = TREE_CHAIN (idx))
> + {
> + unsigned int val = TREE_INT_CST_LOW (TREE_VALUE (idx)) - 1;
> + bitmap_set_bit (argmap, val);
> + }
> + if (bitmap_empty_p (argmap))
> + return;
> +
> + for (unsigned arg_idx = 0; arg_idx < gimple_call_num_args (call);
> arg_idx++)
> + {
> + tree arg = gimple_call_arg (call, arg_idx);
> + tree diag_arg = sm_ctxt->get_diagnostic_tree (arg);
> + state_t state = sm_ctxt->get_state (stmt, arg);
> + bool bit_set = bitmap_bit_p (argmap, arg_idx);
> + if (TREE_CODE (TREE_TYPE (arg)) != INTEGER_TYPE)
> + continue;
> + if (bit_set) // Check if arg_idx is marked by any of the file
> descriptor
> + // attributes
> + {
> +
> + if (is_closed_fd_p (state))
> + {
> +
> + sm_ctxt->warn (node, stmt, arg,
> + new fd_use_after_close (*this, diag_arg,
> + callee_fndecl,
> attr_name,
> + arg_idx));
> + continue;
> + }
> +
> + if (!(is_valid_fd_p (state) || (state == m_stop)))
> + {
> + if (!is_constant_fd_p (state))
> + sm_ctxt->warn (node, stmt, arg,
> + new fd_use_without_check (*this,
> diag_arg,
> + callee_fndecl
> , attr_name,
> + arg_idx));
> + }
> +
> + switch (fd_attr_access_dir)
> + {
> + case DIRS_READ_WRITE:
> + break;
> + case DIRS_READ:
> +
> + if (is_writeonly_fd_p (state))
> + {
> + sm_ctxt->warn (
> + node, stmt, arg,
> + new fd_access_mode_mismatch (*this, diag_arg,
> DIRS_WRITE,
> + callee_fndecl,
> attr_name, arg_idx));
> + }
> +
> + break;
> + case DIRS_WRITE:
> +
> + if (is_readonly_fd_p (state))
> + {
> + sm_ctxt->warn (
> + node, stmt, arg,
> + new fd_access_mode_mismatch (*this, diag_arg,
> DIRS_READ,
> + callee_fndecl,
> attr_name, arg_idx));
> + }
> +
> + break;
> + }
> + }
> + }
> +}
> +
> +
> +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 fd_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,
> DIRS_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,
> DIRS_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_directions 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 fd_use_without_check (*this, diag_arg,
> callee_fndecl));
> + }
> + switch (callee_fndecl_dir)
> + {
> + case DIRS_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, DIRS_WRITE,
> callee_fndecl));
> + }
> +
> + break;
> + case DIRS_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, DIRS_READ,
> callee_fndecl));
> + }
> + break;
> + default:
> + gcc_unreachable ();
> + }
> + }
> +}
> +
> +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
Thanks for this. I’ll keep this in mind for future patches.
Immad.
Sent from Mail<https://go.microsoft.com/fwlink/?LinkId=550986> for Windows 10
From: Martin Liška<mailto:mliska@suse.cz>
Sent: 25 July 2022 12:14 PM
To: gcc-patches@gcc.gnu.org<mailto:gcc-patches@gcc.gnu.org>
Cc: mirimmad@outlook.com<mailto:mirimmad@outlook.com>; David Malcolm<mailto:dmalcolm@redhat.com>
Subject: [PATCH] analyzer: fix coding style in sm-fd.cc
Hi.
First, thanks Mir for your contribution. The following patch addresses
coding style issues I let you know in:
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=106003#c3
Most notably, I converted Windows endlines to Unix style, replace 8 spaces with tabs
and removed trailing whitespaces.
Please consider using our script that verifies that:
git show f8e6e2c046e1015697356ee7079fb39e0cb6add5 > diff && ./contrib/check_GNU_style.py diff
Ready to be installed?
Thanks,
Martin
gcc/analyzer/ChangeLog:
* sm-fd.cc: Run dos2unix and fix coding style issues.
---
gcc/analyzer/sm-fd.cc | 2114 ++++++++++++++++++++---------------------
1 file changed, 1057 insertions(+), 1057 deletions(-)
diff --git a/gcc/analyzer/sm-fd.cc b/gcc/analyzer/sm-fd.cc
index c3dac48509e..56b0063ba42 100644
--- a/gcc/analyzer/sm-fd.cc
+++ b/gcc/analyzer/sm-fd.cc
@@ -1,1057 +1,1057 @@
-/* A state machine for detecting misuses of POSIX file descriptor APIs.
- Copyright (C) 2019-2022 Free Software Foundation, Inc.
- Contributed by Immad Mir <mir@sourceware.org>.
-
-This file is part of GCC.
-
-GCC is free software; you can redistribute it and/or modify it
-under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 3, or (at your option)
-any later version.
-
-GCC is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with GCC; see the file COPYING3. If not see
-<http://www.gnu.org/licenses/>. */
-
-#include "config.h"
-#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 "stringpool.h"
-#include "attribs.h"
-#include "analyzer/call-string.h"
-#include "analyzer/program-point.h"
-#include "analyzer/store.h"
-#include "analyzer/region-model.h"
-#include "bitmap.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
-};
-
-enum access_directions
-{
- DIRS_READ_WRITE,
- DIRS_READ,
- DIRS_WRITE
-};
-
-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_directions 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;
- void check_for_fd_attrs (sm_context *sm_ctxt, const supernode *node,
- const gimple *stmt, const gcall *call,
- const tree callee_fndecl, const char *attr_name,
- access_directions fd_attr_access_dir) 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_param_diagnostic : public fd_diagnostic
-{
-public:
- fd_param_diagnostic (const fd_state_machine &sm, tree arg, tree callee_fndecl,
- const char *attr_name, int arg_idx)
- : fd_diagnostic (sm, arg), m_callee_fndecl (callee_fndecl),
- m_attr_name (attr_name), m_arg_idx (arg_idx)
- {
- }
-
- fd_param_diagnostic (const fd_state_machine &sm, tree arg, tree callee_fndecl)
- : fd_diagnostic (sm, arg), m_callee_fndecl (callee_fndecl),
- m_attr_name (NULL), m_arg_idx (-1)
- {
- }
-
- bool
- subclass_equal_p (const pending_diagnostic &base_other) const override
- {
- const fd_param_diagnostic &sub_other
- = (const fd_param_diagnostic &)base_other;
- return (same_tree_p (m_arg, sub_other.m_arg)
- && same_tree_p (m_callee_fndecl, sub_other.m_callee_fndecl)
- && m_arg_idx == sub_other.m_arg_idx
- && ((m_attr_name)
- ? (strcmp (m_attr_name, sub_other.m_attr_name) == 0)
- : true));
- }
-
- void
- inform_filedescriptor_attribute (access_directions fd_dir)
- {
-
- if (m_attr_name)
- switch (fd_dir)
- {
- case DIRS_READ_WRITE:
- inform (DECL_SOURCE_LOCATION (m_callee_fndecl),
- "argument %d of %qD must be an open file descriptor, due to "
- "%<__attribute__((%s(%d)))%>",
- m_arg_idx + 1, m_callee_fndecl, m_attr_name, m_arg_idx + 1);
- break;
- case DIRS_WRITE:
- inform (DECL_SOURCE_LOCATION (m_callee_fndecl),
- "argument %d of %qD must be a readable file descriptor, due "
- "to %<__attribute__((%s(%d)))%>",
- m_arg_idx + 1, m_callee_fndecl, m_attr_name, m_arg_idx + 1);
- break;
- case DIRS_READ:
- inform (DECL_SOURCE_LOCATION (m_callee_fndecl),
- "argument %d of %qD must be a writable file descriptor, due "
- "to %<__attribute__((%s(%d)))%>",
- m_arg_idx + 1, m_callee_fndecl, m_attr_name, m_arg_idx + 1);
- break;
- }
- }
-
-protected:
- tree m_callee_fndecl;
- const char *m_attr_name;
- /* ARG_IDX is 0-based. */
- int m_arg_idx;
-};
-
-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_param_diagnostic
-{
-public:
- fd_access_mode_mismatch (const fd_state_machine &sm, tree arg,
- enum access_directions fd_dir,
- const tree callee_fndecl, const char *attr_name,
- int arg_idx)
- : fd_param_diagnostic (sm, arg, callee_fndecl, attr_name, arg_idx),
- m_fd_dir (fd_dir)
-
- {
- }
-
- fd_access_mode_mismatch (const fd_state_machine &sm, tree arg,
- enum access_directions fd_dir,
- const tree callee_fndecl)
- : fd_param_diagnostic (sm, arg, callee_fndecl), m_fd_dir (fd_dir)
- {
- }
-
- 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
- {
- bool warned;
- switch (m_fd_dir)
- {
- case DIRS_READ:
- warned = warning_at (rich_loc, get_controlling_option (),
- "%qE on read-only file descriptor %qE",
- m_callee_fndecl, m_arg);
- break;
- case DIRS_WRITE:
- warned = warning_at (rich_loc, get_controlling_option (),
- "%qE on write-only file descriptor %qE",
- m_callee_fndecl, m_arg);
- break;
- default:
- gcc_unreachable ();
- }
- if (warned)
- inform_filedescriptor_attribute (m_fd_dir);
- return warned;
- }
-
- label_text
- describe_final_event (const evdesc::final_event &ev) final override
- {
- switch (m_fd_dir)
- {
- case DIRS_READ:
- return ev.formatted_print ("%qE on read-only file descriptor %qE",
- m_callee_fndecl, m_arg);
- case DIRS_WRITE:
- return ev.formatted_print ("%qE on write-only file descriptor %qE",
- m_callee_fndecl, m_arg);
- default:
- gcc_unreachable ();
- }
- }
-
-private:
- enum access_directions m_fd_dir;
-};
-
-class fd_double_close : public fd_diagnostic
-{
-public:
- fd_double_close (const fd_state_machine &sm, tree arg) : fd_diagnostic (sm, arg)
- {
- }
-
- const char *
- get_kind () const final override
- {
- return "fd_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 %<close%> 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_param_diagnostic
-{
-public:
- fd_use_after_close (const fd_state_machine &sm, tree arg,
- const tree callee_fndecl, const char *attr_name,
- int arg_idx)
- : fd_param_diagnostic (sm, arg, callee_fndecl, attr_name, arg_idx)
- {
- }
-
- fd_use_after_close (const fd_state_machine &sm, tree arg,
- const tree callee_fndecl)
- : fd_param_diagnostic (sm, arg, 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
- {
- bool warned;
- warned = warning_at (rich_loc, get_controlling_option (),
- "%qE on closed file descriptor %qE", m_callee_fndecl,
- m_arg);
- if (warned)
- inform_filedescriptor_attribute (DIRS_READ_WRITE);
- return warned;
- }
-
- 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 ("closed here");
- }
-
- 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 (
- "%qE on closed file descriptor %qE; %qs was at %@", m_callee_fndecl,
- m_arg, "close", &m_first_close_event);
- else
- return ev.formatted_print ("%qE on closed file descriptor %qE",
- m_callee_fndecl, m_arg);
- }
-
-private:
- diagnostic_event_id_t m_first_close_event;
-};
-
-class fd_use_without_check : public fd_param_diagnostic
-{
-public:
- fd_use_without_check (const fd_state_machine &sm, tree arg,
- const tree callee_fndecl, const char *attr_name,
- int arg_idx)
- : fd_param_diagnostic (sm, arg, callee_fndecl, attr_name, arg_idx)
- {
- }
-
- fd_use_without_check (const fd_state_machine &sm, tree arg,
- const tree callee_fndecl)
- : fd_param_diagnostic (sm, arg, callee_fndecl)
- {
- }
-
- const char *
- get_kind () const final override
- {
- return "fd_use_without_check";
- }
-
- int
- get_controlling_option () const final override
- {
- return OPT_Wanalyzer_fd_use_without_check;
- }
-
- bool
- emit (rich_location *rich_loc) final override
- {
- bool warned;
- warned = warning_at (rich_loc, get_controlling_option (),
- "%qE on possibly invalid file descriptor %qE",
- m_callee_fndecl, m_arg);
- if (warned)
- inform_filedescriptor_attribute (DIRS_READ_WRITE);
- return warned;
- }
-
- 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;
-};
-
-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_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_invalid (add_state ("fd-invalid")),
- 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<const gcall *> (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"
-
-
- {
- // Handle __attribute__((fd_arg))
-
- check_for_fd_attrs (sm_ctxt, node, stmt, call, callee_fndecl,
- "fd_arg", DIRS_READ_WRITE);
-
- // Handle __attribute__((fd_arg_read))
-
- check_for_fd_attrs (sm_ctxt, node, stmt, call, callee_fndecl,
- "fd_arg_read", DIRS_READ);
-
- // Handle __attribute__((fd_arg_write))
-
- check_for_fd_attrs (sm_ctxt, node, stmt, call, callee_fndecl,
- "fd_arg_write", DIRS_WRITE);
- }
- }
-
- return false;
-}
-
-void
-fd_state_machine::check_for_fd_attrs (
- sm_context *sm_ctxt, const supernode *node, const gimple *stmt,
- const gcall *call, const tree callee_fndecl, const char *attr_name,
- access_directions fd_attr_access_dir) const
-{
-
- tree attrs = TYPE_ATTRIBUTES (TREE_TYPE (callee_fndecl));
- attrs = lookup_attribute (attr_name, attrs);
- if (!attrs)
- return;
-
- if (!TREE_VALUE (attrs))
- return;
-
- auto_bitmap argmap;
-
- for (tree idx = TREE_VALUE (attrs); idx; idx = TREE_CHAIN (idx))
- {
- unsigned int val = TREE_INT_CST_LOW (TREE_VALUE (idx)) - 1;
- bitmap_set_bit (argmap, val);
- }
- if (bitmap_empty_p (argmap))
- return;
-
- for (unsigned arg_idx = 0; arg_idx < gimple_call_num_args (call); arg_idx++)
- {
- tree arg = gimple_call_arg (call, arg_idx);
- tree diag_arg = sm_ctxt->get_diagnostic_tree (arg);
- state_t state = sm_ctxt->get_state (stmt, arg);
- bool bit_set = bitmap_bit_p (argmap, arg_idx);
- if (TREE_CODE (TREE_TYPE (arg)) != INTEGER_TYPE)
- continue;
- if (bit_set) // Check if arg_idx is marked by any of the file descriptor
- // attributes
- {
-
- if (is_closed_fd_p (state))
- {
-
- sm_ctxt->warn (node, stmt, arg,
- new fd_use_after_close (*this, diag_arg,
- callee_fndecl, attr_name,
- arg_idx));
- continue;
- }
-
- if (!(is_valid_fd_p (state) || (state == m_stop)))
- {
- if (!is_constant_fd_p (state))
- sm_ctxt->warn (node, stmt, arg,
- new fd_use_without_check (*this, diag_arg,
- callee_fndecl, attr_name,
- arg_idx));
- }
-
- switch (fd_attr_access_dir)
- {
- case DIRS_READ_WRITE:
- break;
- case DIRS_READ:
-
- if (is_writeonly_fd_p (state))
- {
- sm_ctxt->warn (
- node, stmt, arg,
- new fd_access_mode_mismatch (*this, diag_arg, DIRS_WRITE,
- callee_fndecl, attr_name, arg_idx));
- }
-
- break;
- case DIRS_WRITE:
-
- if (is_readonly_fd_p (state))
- {
- sm_ctxt->warn (
- node, stmt, arg,
- new fd_access_mode_mismatch (*this, diag_arg, DIRS_READ,
- callee_fndecl, attr_name, arg_idx));
- }
-
- break;
- }
- }
- }
-}
-
-
-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 fd_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, DIRS_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, DIRS_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_directions 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 fd_use_without_check (*this, diag_arg, callee_fndecl));
- }
- switch (callee_fndecl_dir)
- {
- case DIRS_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, DIRS_WRITE, callee_fndecl));
- }
-
- break;
- case DIRS_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, DIRS_READ, callee_fndecl));
- }
- break;
- default:
- gcc_unreachable ();
- }
- }
-}
-
-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
+/* A state machine for detecting misuses of POSIX file descriptor APIs.
+ Copyright (C) 2019-2022 Free Software Foundation, Inc.
+ Contributed by Immad Mir <mir@sourceware.org>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3, or (at your option)
+any later version.
+
+GCC is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#include "config.h"
+#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 "stringpool.h"
+#include "attribs.h"
+#include "analyzer/call-string.h"
+#include "analyzer/program-point.h"
+#include "analyzer/store.h"
+#include "analyzer/region-model.h"
+#include "bitmap.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
+};
+
+enum access_directions
+{
+ DIRS_READ_WRITE,
+ DIRS_READ,
+ DIRS_WRITE
+};
+
+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_directions 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;
+ void check_for_fd_attrs (sm_context *sm_ctxt, const supernode *node,
+ const gimple *stmt, const gcall *call,
+ const tree callee_fndecl, const char *attr_name,
+ access_directions fd_attr_access_dir) 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_param_diagnostic : public fd_diagnostic
+{
+public:
+ fd_param_diagnostic (const fd_state_machine &sm, tree arg, tree callee_fndecl,
+ const char *attr_name, int arg_idx)
+ : fd_diagnostic (sm, arg), m_callee_fndecl (callee_fndecl),
+ m_attr_name (attr_name), m_arg_idx (arg_idx)
+ {
+ }
+
+ fd_param_diagnostic (const fd_state_machine &sm, tree arg, tree callee_fndecl)
+ : fd_diagnostic (sm, arg), m_callee_fndecl (callee_fndecl),
+ m_attr_name (NULL), m_arg_idx (-1)
+ {
+ }
+
+ bool
+ subclass_equal_p (const pending_diagnostic &base_other) const override
+ {
+ const fd_param_diagnostic &sub_other
+ = (const fd_param_diagnostic &)base_other;
+ return (same_tree_p (m_arg, sub_other.m_arg)
+ && same_tree_p (m_callee_fndecl, sub_other.m_callee_fndecl)
+ && m_arg_idx == sub_other.m_arg_idx
+ && ((m_attr_name)
+ ? (strcmp (m_attr_name, sub_other.m_attr_name) == 0)
+ : true));
+ }
+
+ void
+ inform_filedescriptor_attribute (access_directions fd_dir)
+ {
+
+ if (m_attr_name)
+ switch (fd_dir)
+ {
+ case DIRS_READ_WRITE:
+ inform (DECL_SOURCE_LOCATION (m_callee_fndecl),
+ "argument %d of %qD must be an open file descriptor, due to "
+ "%<__attribute__((%s(%d)))%>",
+ m_arg_idx + 1, m_callee_fndecl, m_attr_name, m_arg_idx + 1);
+ break;
+ case DIRS_WRITE:
+ inform (DECL_SOURCE_LOCATION (m_callee_fndecl),
+ "argument %d of %qD must be a readable file descriptor, due "
+ "to %<__attribute__((%s(%d)))%>",
+ m_arg_idx + 1, m_callee_fndecl, m_attr_name, m_arg_idx + 1);
+ break;
+ case DIRS_READ:
+ inform (DECL_SOURCE_LOCATION (m_callee_fndecl),
+ "argument %d of %qD must be a writable file descriptor, due "
+ "to %<__attribute__((%s(%d)))%>",
+ m_arg_idx + 1, m_callee_fndecl, m_attr_name, m_arg_idx + 1);
+ break;
+ }
+ }
+
+protected:
+ tree m_callee_fndecl;
+ const char *m_attr_name;
+ /* ARG_IDX is 0-based. */
+ int m_arg_idx;
+};
+
+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_param_diagnostic
+{
+public:
+ fd_access_mode_mismatch (const fd_state_machine &sm, tree arg,
+ enum access_directions fd_dir,
+ const tree callee_fndecl, const char *attr_name,
+ int arg_idx)
+ : fd_param_diagnostic (sm, arg, callee_fndecl, attr_name, arg_idx),
+ m_fd_dir (fd_dir)
+
+ {
+ }
+
+ fd_access_mode_mismatch (const fd_state_machine &sm, tree arg,
+ enum access_directions fd_dir,
+ const tree callee_fndecl)
+ : fd_param_diagnostic (sm, arg, callee_fndecl), m_fd_dir (fd_dir)
+ {
+ }
+
+ 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
+ {
+ bool warned;
+ switch (m_fd_dir)
+ {
+ case DIRS_READ:
+ warned = warning_at (rich_loc, get_controlling_option (),
+ "%qE on read-only file descriptor %qE",
+ m_callee_fndecl, m_arg);
+ break;
+ case DIRS_WRITE:
+ warned = warning_at (rich_loc, get_controlling_option (),
+ "%qE on write-only file descriptor %qE",
+ m_callee_fndecl, m_arg);
+ break;
+ default:
+ gcc_unreachable ();
+ }
+ if (warned)
+ inform_filedescriptor_attribute (m_fd_dir);
+ return warned;
+ }
+
+ label_text
+ describe_final_event (const evdesc::final_event &ev) final override
+ {
+ switch (m_fd_dir)
+ {
+ case DIRS_READ:
+ return ev.formatted_print ("%qE on read-only file descriptor %qE",
+ m_callee_fndecl, m_arg);
+ case DIRS_WRITE:
+ return ev.formatted_print ("%qE on write-only file descriptor %qE",
+ m_callee_fndecl, m_arg);
+ default:
+ gcc_unreachable ();
+ }
+ }
+
+private:
+ enum access_directions m_fd_dir;
+};
+
+class fd_double_close : public fd_diagnostic
+{
+public:
+ fd_double_close (const fd_state_machine &sm, tree arg) : fd_diagnostic (sm, arg)
+ {
+ }
+
+ const char *
+ get_kind () const final override
+ {
+ return "fd_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 %<close%> 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_param_diagnostic
+{
+public:
+ fd_use_after_close (const fd_state_machine &sm, tree arg,
+ const tree callee_fndecl, const char *attr_name,
+ int arg_idx)
+ : fd_param_diagnostic (sm, arg, callee_fndecl, attr_name, arg_idx)
+ {
+ }
+
+ fd_use_after_close (const fd_state_machine &sm, tree arg,
+ const tree callee_fndecl)
+ : fd_param_diagnostic (sm, arg, 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
+ {
+ bool warned;
+ warned = warning_at (rich_loc, get_controlling_option (),
+ "%qE on closed file descriptor %qE", m_callee_fndecl,
+ m_arg);
+ if (warned)
+ inform_filedescriptor_attribute (DIRS_READ_WRITE);
+ return warned;
+ }
+
+ 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 ("closed here");
+ }
+
+ 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 (
+ "%qE on closed file descriptor %qE; %qs was at %@", m_callee_fndecl,
+ m_arg, "close", &m_first_close_event);
+ else
+ return ev.formatted_print ("%qE on closed file descriptor %qE",
+ m_callee_fndecl, m_arg);
+ }
+
+private:
+ diagnostic_event_id_t m_first_close_event;
+};
+
+class fd_use_without_check : public fd_param_diagnostic
+{
+public:
+ fd_use_without_check (const fd_state_machine &sm, tree arg,
+ const tree callee_fndecl, const char *attr_name,
+ int arg_idx)
+ : fd_param_diagnostic (sm, arg, callee_fndecl, attr_name, arg_idx)
+ {
+ }
+
+ fd_use_without_check (const fd_state_machine &sm, tree arg,
+ const tree callee_fndecl)
+ : fd_param_diagnostic (sm, arg, callee_fndecl)
+ {
+ }
+
+ const char *
+ get_kind () const final override
+ {
+ return "fd_use_without_check";
+ }
+
+ int
+ get_controlling_option () const final override
+ {
+ return OPT_Wanalyzer_fd_use_without_check;
+ }
+
+ bool
+ emit (rich_location *rich_loc) final override
+ {
+ bool warned;
+ warned = warning_at (rich_loc, get_controlling_option (),
+ "%qE on possibly invalid file descriptor %qE",
+ m_callee_fndecl, m_arg);
+ if (warned)
+ inform_filedescriptor_attribute (DIRS_READ_WRITE);
+ return warned;
+ }
+
+ 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;
+};
+
+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_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_invalid (add_state ("fd-invalid")),
+ 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<const gcall *> (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"
+
+
+ {
+ // Handle __attribute__((fd_arg))
+
+ check_for_fd_attrs (sm_ctxt, node, stmt, call, callee_fndecl,
+ "fd_arg", DIRS_READ_WRITE);
+
+ // Handle __attribute__((fd_arg_read))
+
+ check_for_fd_attrs (sm_ctxt, node, stmt, call, callee_fndecl,
+ "fd_arg_read", DIRS_READ);
+
+ // Handle __attribute__((fd_arg_write))
+
+ check_for_fd_attrs (sm_ctxt, node, stmt, call, callee_fndecl,
+ "fd_arg_write", DIRS_WRITE);
+ }
+ }
+
+ return false;
+}
+
+void
+fd_state_machine::check_for_fd_attrs (
+ sm_context *sm_ctxt, const supernode *node, const gimple *stmt,
+ const gcall *call, const tree callee_fndecl, const char *attr_name,
+ access_directions fd_attr_access_dir) const
+{
+
+ tree attrs = TYPE_ATTRIBUTES (TREE_TYPE (callee_fndecl));
+ attrs = lookup_attribute (attr_name, attrs);
+ if (!attrs)
+ return;
+
+ if (!TREE_VALUE (attrs))
+ return;
+
+ auto_bitmap argmap;
+
+ for (tree idx = TREE_VALUE (attrs); idx; idx = TREE_CHAIN (idx))
+ {
+ unsigned int val = TREE_INT_CST_LOW (TREE_VALUE (idx)) - 1;
+ bitmap_set_bit (argmap, val);
+ }
+ if (bitmap_empty_p (argmap))
+ return;
+
+ for (unsigned arg_idx = 0; arg_idx < gimple_call_num_args (call); arg_idx++)
+ {
+ tree arg = gimple_call_arg (call, arg_idx);
+ tree diag_arg = sm_ctxt->get_diagnostic_tree (arg);
+ state_t state = sm_ctxt->get_state (stmt, arg);
+ bool bit_set = bitmap_bit_p (argmap, arg_idx);
+ if (TREE_CODE (TREE_TYPE (arg)) != INTEGER_TYPE)
+ continue;
+ if (bit_set) // Check if arg_idx is marked by any of the file descriptor
+ // attributes
+ {
+
+ if (is_closed_fd_p (state))
+ {
+
+ sm_ctxt->warn (node, stmt, arg,
+ new fd_use_after_close (*this, diag_arg,
+ callee_fndecl, attr_name,
+ arg_idx));
+ continue;
+ }
+
+ if (!(is_valid_fd_p (state) || (state == m_stop)))
+ {
+ if (!is_constant_fd_p (state))
+ sm_ctxt->warn (node, stmt, arg,
+ new fd_use_without_check (*this, diag_arg,
+ callee_fndecl, attr_name,
+ arg_idx));
+ }
+
+ switch (fd_attr_access_dir)
+ {
+ case DIRS_READ_WRITE:
+ break;
+ case DIRS_READ:
+
+ if (is_writeonly_fd_p (state))
+ {
+ sm_ctxt->warn (
+ node, stmt, arg,
+ new fd_access_mode_mismatch (*this, diag_arg, DIRS_WRITE,
+ callee_fndecl, attr_name, arg_idx));
+ }
+
+ break;
+ case DIRS_WRITE:
+
+ if (is_readonly_fd_p (state))
+ {
+ sm_ctxt->warn (
+ node, stmt, arg,
+ new fd_access_mode_mismatch (*this, diag_arg, DIRS_READ,
+ callee_fndecl, attr_name, arg_idx));
+ }
+
+ break;
+ }
+ }
+ }
+}
+
+
+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 fd_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, DIRS_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, DIRS_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_directions 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 fd_use_without_check (*this, diag_arg, callee_fndecl));
+ }
+ switch (callee_fndecl_dir)
+ {
+ case DIRS_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, DIRS_WRITE, callee_fndecl));
+ }
+
+ break;
+ case DIRS_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, DIRS_READ, callee_fndecl));
+ }
+ break;
+ default:
+ gcc_unreachable ();
+ }
+ }
+}
+
+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
--
2.37.1
@@ -1,1057 +1,1057 @@
-/* A state machine for detecting misuses of POSIX file descriptor APIs.
- Copyright (C) 2019-2022 Free Software Foundation, Inc.
- Contributed by Immad Mir <mir@sourceware.org>.
-
-This file is part of GCC.
-
-GCC is free software; you can redistribute it and/or modify it
-under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 3, or (at your option)
-any later version.
-
-GCC is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with GCC; see the file COPYING3. If not see
-<http://www.gnu.org/licenses/>. */
-
-#include "config.h"
-#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 "stringpool.h"
-#include "attribs.h"
-#include "analyzer/call-string.h"
-#include "analyzer/program-point.h"
-#include "analyzer/store.h"
-#include "analyzer/region-model.h"
-#include "bitmap.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
-};
-
-enum access_directions
-{
- DIRS_READ_WRITE,
- DIRS_READ,
- DIRS_WRITE
-};
-
-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_directions 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;
- void check_for_fd_attrs (sm_context *sm_ctxt, const supernode *node,
- const gimple *stmt, const gcall *call,
- const tree callee_fndecl, const char *attr_name,
- access_directions fd_attr_access_dir) 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_param_diagnostic : public fd_diagnostic
-{
-public:
- fd_param_diagnostic (const fd_state_machine &sm, tree arg, tree callee_fndecl,
- const char *attr_name, int arg_idx)
- : fd_diagnostic (sm, arg), m_callee_fndecl (callee_fndecl),
- m_attr_name (attr_name), m_arg_idx (arg_idx)
- {
- }
-
- fd_param_diagnostic (const fd_state_machine &sm, tree arg, tree callee_fndecl)
- : fd_diagnostic (sm, arg), m_callee_fndecl (callee_fndecl),
- m_attr_name (NULL), m_arg_idx (-1)
- {
- }
-
- bool
- subclass_equal_p (const pending_diagnostic &base_other) const override
- {
- const fd_param_diagnostic &sub_other
- = (const fd_param_diagnostic &)base_other;
- return (same_tree_p (m_arg, sub_other.m_arg)
- && same_tree_p (m_callee_fndecl, sub_other.m_callee_fndecl)
- && m_arg_idx == sub_other.m_arg_idx
- && ((m_attr_name)
- ? (strcmp (m_attr_name, sub_other.m_attr_name) == 0)
- : true));
- }
-
- void
- inform_filedescriptor_attribute (access_directions fd_dir)
- {
-
- if (m_attr_name)
- switch (fd_dir)
- {
- case DIRS_READ_WRITE:
- inform (DECL_SOURCE_LOCATION (m_callee_fndecl),
- "argument %d of %qD must be an open file descriptor, due to "
- "%<__attribute__((%s(%d)))%>",
- m_arg_idx + 1, m_callee_fndecl, m_attr_name, m_arg_idx + 1);
- break;
- case DIRS_WRITE:
- inform (DECL_SOURCE_LOCATION (m_callee_fndecl),
- "argument %d of %qD must be a readable file descriptor, due "
- "to %<__attribute__((%s(%d)))%>",
- m_arg_idx + 1, m_callee_fndecl, m_attr_name, m_arg_idx + 1);
- break;
- case DIRS_READ:
- inform (DECL_SOURCE_LOCATION (m_callee_fndecl),
- "argument %d of %qD must be a writable file descriptor, due "
- "to %<__attribute__((%s(%d)))%>",
- m_arg_idx + 1, m_callee_fndecl, m_attr_name, m_arg_idx + 1);
- break;
- }
- }
-
-protected:
- tree m_callee_fndecl;
- const char *m_attr_name;
- /* ARG_IDX is 0-based. */
- int m_arg_idx;
-};
-
-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_param_diagnostic
-{
-public:
- fd_access_mode_mismatch (const fd_state_machine &sm, tree arg,
- enum access_directions fd_dir,
- const tree callee_fndecl, const char *attr_name,
- int arg_idx)
- : fd_param_diagnostic (sm, arg, callee_fndecl, attr_name, arg_idx),
- m_fd_dir (fd_dir)
-
- {
- }
-
- fd_access_mode_mismatch (const fd_state_machine &sm, tree arg,
- enum access_directions fd_dir,
- const tree callee_fndecl)
- : fd_param_diagnostic (sm, arg, callee_fndecl), m_fd_dir (fd_dir)
- {
- }
-
- 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
- {
- bool warned;
- switch (m_fd_dir)
- {
- case DIRS_READ:
- warned = warning_at (rich_loc, get_controlling_option (),
- "%qE on read-only file descriptor %qE",
- m_callee_fndecl, m_arg);
- break;
- case DIRS_WRITE:
- warned = warning_at (rich_loc, get_controlling_option (),
- "%qE on write-only file descriptor %qE",
- m_callee_fndecl, m_arg);
- break;
- default:
- gcc_unreachable ();
- }
- if (warned)
- inform_filedescriptor_attribute (m_fd_dir);
- return warned;
- }
-
- label_text
- describe_final_event (const evdesc::final_event &ev) final override
- {
- switch (m_fd_dir)
- {
- case DIRS_READ:
- return ev.formatted_print ("%qE on read-only file descriptor %qE",
- m_callee_fndecl, m_arg);
- case DIRS_WRITE:
- return ev.formatted_print ("%qE on write-only file descriptor %qE",
- m_callee_fndecl, m_arg);
- default:
- gcc_unreachable ();
- }
- }
-
-private:
- enum access_directions m_fd_dir;
-};
-
-class fd_double_close : public fd_diagnostic
-{
-public:
- fd_double_close (const fd_state_machine &sm, tree arg) : fd_diagnostic (sm, arg)
- {
- }
-
- const char *
- get_kind () const final override
- {
- return "fd_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 %<close%> 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_param_diagnostic
-{
-public:
- fd_use_after_close (const fd_state_machine &sm, tree arg,
- const tree callee_fndecl, const char *attr_name,
- int arg_idx)
- : fd_param_diagnostic (sm, arg, callee_fndecl, attr_name, arg_idx)
- {
- }
-
- fd_use_after_close (const fd_state_machine &sm, tree arg,
- const tree callee_fndecl)
- : fd_param_diagnostic (sm, arg, 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
- {
- bool warned;
- warned = warning_at (rich_loc, get_controlling_option (),
- "%qE on closed file descriptor %qE", m_callee_fndecl,
- m_arg);
- if (warned)
- inform_filedescriptor_attribute (DIRS_READ_WRITE);
- return warned;
- }
-
- 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 ("closed here");
- }
-
- 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 (
- "%qE on closed file descriptor %qE; %qs was at %@", m_callee_fndecl,
- m_arg, "close", &m_first_close_event);
- else
- return ev.formatted_print ("%qE on closed file descriptor %qE",
- m_callee_fndecl, m_arg);
- }
-
-private:
- diagnostic_event_id_t m_first_close_event;
-};
-
-class fd_use_without_check : public fd_param_diagnostic
-{
-public:
- fd_use_without_check (const fd_state_machine &sm, tree arg,
- const tree callee_fndecl, const char *attr_name,
- int arg_idx)
- : fd_param_diagnostic (sm, arg, callee_fndecl, attr_name, arg_idx)
- {
- }
-
- fd_use_without_check (const fd_state_machine &sm, tree arg,
- const tree callee_fndecl)
- : fd_param_diagnostic (sm, arg, callee_fndecl)
- {
- }
-
- const char *
- get_kind () const final override
- {
- return "fd_use_without_check";
- }
-
- int
- get_controlling_option () const final override
- {
- return OPT_Wanalyzer_fd_use_without_check;
- }
-
- bool
- emit (rich_location *rich_loc) final override
- {
- bool warned;
- warned = warning_at (rich_loc, get_controlling_option (),
- "%qE on possibly invalid file descriptor %qE",
- m_callee_fndecl, m_arg);
- if (warned)
- inform_filedescriptor_attribute (DIRS_READ_WRITE);
- return warned;
- }
-
- 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;
-};
-
-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_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_invalid (add_state ("fd-invalid")),
- 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<const gcall *> (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"
-
-
- {
- // Handle __attribute__((fd_arg))
-
- check_for_fd_attrs (sm_ctxt, node, stmt, call, callee_fndecl,
- "fd_arg", DIRS_READ_WRITE);
-
- // Handle __attribute__((fd_arg_read))
-
- check_for_fd_attrs (sm_ctxt, node, stmt, call, callee_fndecl,
- "fd_arg_read", DIRS_READ);
-
- // Handle __attribute__((fd_arg_write))
-
- check_for_fd_attrs (sm_ctxt, node, stmt, call, callee_fndecl,
- "fd_arg_write", DIRS_WRITE);
- }
- }
-
- return false;
-}
-
-void
-fd_state_machine::check_for_fd_attrs (
- sm_context *sm_ctxt, const supernode *node, const gimple *stmt,
- const gcall *call, const tree callee_fndecl, const char *attr_name,
- access_directions fd_attr_access_dir) const
-{
-
- tree attrs = TYPE_ATTRIBUTES (TREE_TYPE (callee_fndecl));
- attrs = lookup_attribute (attr_name, attrs);
- if (!attrs)
- return;
-
- if (!TREE_VALUE (attrs))
- return;
-
- auto_bitmap argmap;
-
- for (tree idx = TREE_VALUE (attrs); idx; idx = TREE_CHAIN (idx))
- {
- unsigned int val = TREE_INT_CST_LOW (TREE_VALUE (idx)) - 1;
- bitmap_set_bit (argmap, val);
- }
- if (bitmap_empty_p (argmap))
- return;
-
- for (unsigned arg_idx = 0; arg_idx < gimple_call_num_args (call); arg_idx++)
- {
- tree arg = gimple_call_arg (call, arg_idx);
- tree diag_arg = sm_ctxt->get_diagnostic_tree (arg);
- state_t state = sm_ctxt->get_state (stmt, arg);
- bool bit_set = bitmap_bit_p (argmap, arg_idx);
- if (TREE_CODE (TREE_TYPE (arg)) != INTEGER_TYPE)
- continue;
- if (bit_set) // Check if arg_idx is marked by any of the file descriptor
- // attributes
- {
-
- if (is_closed_fd_p (state))
- {
-
- sm_ctxt->warn (node, stmt, arg,
- new fd_use_after_close (*this, diag_arg,
- callee_fndecl, attr_name,
- arg_idx));
- continue;
- }
-
- if (!(is_valid_fd_p (state) || (state == m_stop)))
- {
- if (!is_constant_fd_p (state))
- sm_ctxt->warn (node, stmt, arg,
- new fd_use_without_check (*this, diag_arg,
- callee_fndecl, attr_name,
- arg_idx));
- }
-
- switch (fd_attr_access_dir)
- {
- case DIRS_READ_WRITE:
- break;
- case DIRS_READ:
-
- if (is_writeonly_fd_p (state))
- {
- sm_ctxt->warn (
- node, stmt, arg,
- new fd_access_mode_mismatch (*this, diag_arg, DIRS_WRITE,
- callee_fndecl, attr_name, arg_idx));
- }
-
- break;
- case DIRS_WRITE:
-
- if (is_readonly_fd_p (state))
- {
- sm_ctxt->warn (
- node, stmt, arg,
- new fd_access_mode_mismatch (*this, diag_arg, DIRS_READ,
- callee_fndecl, attr_name, arg_idx));
- }
-
- break;
- }
- }
- }
-}
-
-
-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 fd_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, DIRS_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, DIRS_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_directions 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 fd_use_without_check (*this, diag_arg, callee_fndecl));
- }
- switch (callee_fndecl_dir)
- {
- case DIRS_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, DIRS_WRITE, callee_fndecl));
- }
-
- break;
- case DIRS_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, DIRS_READ, callee_fndecl));
- }
- break;
- default:
- gcc_unreachable ();
- }
- }
-}
-
-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
+/* A state machine for detecting misuses of POSIX file descriptor APIs.
+ Copyright (C) 2019-2022 Free Software Foundation, Inc.
+ Contributed by Immad Mir <mir@sourceware.org>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3, or (at your option)
+any later version.
+
+GCC is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#include "config.h"
+#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 "stringpool.h"
+#include "attribs.h"
+#include "analyzer/call-string.h"
+#include "analyzer/program-point.h"
+#include "analyzer/store.h"
+#include "analyzer/region-model.h"
+#include "bitmap.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
+};
+
+enum access_directions
+{
+ DIRS_READ_WRITE,
+ DIRS_READ,
+ DIRS_WRITE
+};
+
+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_directions 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;
+ void check_for_fd_attrs (sm_context *sm_ctxt, const supernode *node,
+ const gimple *stmt, const gcall *call,
+ const tree callee_fndecl, const char *attr_name,
+ access_directions fd_attr_access_dir) 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_param_diagnostic : public fd_diagnostic
+{
+public:
+ fd_param_diagnostic (const fd_state_machine &sm, tree arg, tree callee_fndecl,
+ const char *attr_name, int arg_idx)
+ : fd_diagnostic (sm, arg), m_callee_fndecl (callee_fndecl),
+ m_attr_name (attr_name), m_arg_idx (arg_idx)
+ {
+ }
+
+ fd_param_diagnostic (const fd_state_machine &sm, tree arg, tree callee_fndecl)
+ : fd_diagnostic (sm, arg), m_callee_fndecl (callee_fndecl),
+ m_attr_name (NULL), m_arg_idx (-1)
+ {
+ }
+
+ bool
+ subclass_equal_p (const pending_diagnostic &base_other) const override
+ {
+ const fd_param_diagnostic &sub_other
+ = (const fd_param_diagnostic &)base_other;
+ return (same_tree_p (m_arg, sub_other.m_arg)
+ && same_tree_p (m_callee_fndecl, sub_other.m_callee_fndecl)
+ && m_arg_idx == sub_other.m_arg_idx
+ && ((m_attr_name)
+ ? (strcmp (m_attr_name, sub_other.m_attr_name) == 0)
+ : true));
+ }
+
+ void
+ inform_filedescriptor_attribute (access_directions fd_dir)
+ {
+
+ if (m_attr_name)
+ switch (fd_dir)
+ {
+ case DIRS_READ_WRITE:
+ inform (DECL_SOURCE_LOCATION (m_callee_fndecl),
+ "argument %d of %qD must be an open file descriptor, due to "
+ "%<__attribute__((%s(%d)))%>",
+ m_arg_idx + 1, m_callee_fndecl, m_attr_name, m_arg_idx + 1);
+ break;
+ case DIRS_WRITE:
+ inform (DECL_SOURCE_LOCATION (m_callee_fndecl),
+ "argument %d of %qD must be a readable file descriptor, due "
+ "to %<__attribute__((%s(%d)))%>",
+ m_arg_idx + 1, m_callee_fndecl, m_attr_name, m_arg_idx + 1);
+ break;
+ case DIRS_READ:
+ inform (DECL_SOURCE_LOCATION (m_callee_fndecl),
+ "argument %d of %qD must be a writable file descriptor, due "
+ "to %<__attribute__((%s(%d)))%>",
+ m_arg_idx + 1, m_callee_fndecl, m_attr_name, m_arg_idx + 1);
+ break;
+ }
+ }
+
+protected:
+ tree m_callee_fndecl;
+ const char *m_attr_name;
+ /* ARG_IDX is 0-based. */
+ int m_arg_idx;
+};
+
+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_param_diagnostic
+{
+public:
+ fd_access_mode_mismatch (const fd_state_machine &sm, tree arg,
+ enum access_directions fd_dir,
+ const tree callee_fndecl, const char *attr_name,
+ int arg_idx)
+ : fd_param_diagnostic (sm, arg, callee_fndecl, attr_name, arg_idx),
+ m_fd_dir (fd_dir)
+
+ {
+ }
+
+ fd_access_mode_mismatch (const fd_state_machine &sm, tree arg,
+ enum access_directions fd_dir,
+ const tree callee_fndecl)
+ : fd_param_diagnostic (sm, arg, callee_fndecl), m_fd_dir (fd_dir)
+ {
+ }
+
+ 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
+ {
+ bool warned;
+ switch (m_fd_dir)
+ {
+ case DIRS_READ:
+ warned = warning_at (rich_loc, get_controlling_option (),
+ "%qE on read-only file descriptor %qE",
+ m_callee_fndecl, m_arg);
+ break;
+ case DIRS_WRITE:
+ warned = warning_at (rich_loc, get_controlling_option (),
+ "%qE on write-only file descriptor %qE",
+ m_callee_fndecl, m_arg);
+ break;
+ default:
+ gcc_unreachable ();
+ }
+ if (warned)
+ inform_filedescriptor_attribute (m_fd_dir);
+ return warned;
+ }
+
+ label_text
+ describe_final_event (const evdesc::final_event &ev) final override
+ {
+ switch (m_fd_dir)
+ {
+ case DIRS_READ:
+ return ev.formatted_print ("%qE on read-only file descriptor %qE",
+ m_callee_fndecl, m_arg);
+ case DIRS_WRITE:
+ return ev.formatted_print ("%qE on write-only file descriptor %qE",
+ m_callee_fndecl, m_arg);
+ default:
+ gcc_unreachable ();
+ }
+ }
+
+private:
+ enum access_directions m_fd_dir;
+};
+
+class fd_double_close : public fd_diagnostic
+{
+public:
+ fd_double_close (const fd_state_machine &sm, tree arg) : fd_diagnostic (sm, arg)
+ {
+ }
+
+ const char *
+ get_kind () const final override
+ {
+ return "fd_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 %<close%> 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_param_diagnostic
+{
+public:
+ fd_use_after_close (const fd_state_machine &sm, tree arg,
+ const tree callee_fndecl, const char *attr_name,
+ int arg_idx)
+ : fd_param_diagnostic (sm, arg, callee_fndecl, attr_name, arg_idx)
+ {
+ }
+
+ fd_use_after_close (const fd_state_machine &sm, tree arg,
+ const tree callee_fndecl)
+ : fd_param_diagnostic (sm, arg, 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
+ {
+ bool warned;
+ warned = warning_at (rich_loc, get_controlling_option (),
+ "%qE on closed file descriptor %qE", m_callee_fndecl,
+ m_arg);
+ if (warned)
+ inform_filedescriptor_attribute (DIRS_READ_WRITE);
+ return warned;
+ }
+
+ 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 ("closed here");
+ }
+
+ 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 (
+ "%qE on closed file descriptor %qE; %qs was at %@", m_callee_fndecl,
+ m_arg, "close", &m_first_close_event);
+ else
+ return ev.formatted_print ("%qE on closed file descriptor %qE",
+ m_callee_fndecl, m_arg);
+ }
+
+private:
+ diagnostic_event_id_t m_first_close_event;
+};
+
+class fd_use_without_check : public fd_param_diagnostic
+{
+public:
+ fd_use_without_check (const fd_state_machine &sm, tree arg,
+ const tree callee_fndecl, const char *attr_name,
+ int arg_idx)
+ : fd_param_diagnostic (sm, arg, callee_fndecl, attr_name, arg_idx)
+ {
+ }
+
+ fd_use_without_check (const fd_state_machine &sm, tree arg,
+ const tree callee_fndecl)
+ : fd_param_diagnostic (sm, arg, callee_fndecl)
+ {
+ }
+
+ const char *
+ get_kind () const final override
+ {
+ return "fd_use_without_check";
+ }
+
+ int
+ get_controlling_option () const final override
+ {
+ return OPT_Wanalyzer_fd_use_without_check;
+ }
+
+ bool
+ emit (rich_location *rich_loc) final override
+ {
+ bool warned;
+ warned = warning_at (rich_loc, get_controlling_option (),
+ "%qE on possibly invalid file descriptor %qE",
+ m_callee_fndecl, m_arg);
+ if (warned)
+ inform_filedescriptor_attribute (DIRS_READ_WRITE);
+ return warned;
+ }
+
+ 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;
+};
+
+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_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_invalid (add_state ("fd-invalid")),
+ 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<const gcall *> (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"
+
+
+ {
+ // Handle __attribute__((fd_arg))
+
+ check_for_fd_attrs (sm_ctxt, node, stmt, call, callee_fndecl,
+ "fd_arg", DIRS_READ_WRITE);
+
+ // Handle __attribute__((fd_arg_read))
+
+ check_for_fd_attrs (sm_ctxt, node, stmt, call, callee_fndecl,
+ "fd_arg_read", DIRS_READ);
+
+ // Handle __attribute__((fd_arg_write))
+
+ check_for_fd_attrs (sm_ctxt, node, stmt, call, callee_fndecl,
+ "fd_arg_write", DIRS_WRITE);
+ }
+ }
+
+ return false;
+}
+
+void
+fd_state_machine::check_for_fd_attrs (
+ sm_context *sm_ctxt, const supernode *node, const gimple *stmt,
+ const gcall *call, const tree callee_fndecl, const char *attr_name,
+ access_directions fd_attr_access_dir) const
+{
+
+ tree attrs = TYPE_ATTRIBUTES (TREE_TYPE (callee_fndecl));
+ attrs = lookup_attribute (attr_name, attrs);
+ if (!attrs)
+ return;
+
+ if (!TREE_VALUE (attrs))
+ return;
+
+ auto_bitmap argmap;
+
+ for (tree idx = TREE_VALUE (attrs); idx; idx = TREE_CHAIN (idx))
+ {
+ unsigned int val = TREE_INT_CST_LOW (TREE_VALUE (idx)) - 1;
+ bitmap_set_bit (argmap, val);
+ }
+ if (bitmap_empty_p (argmap))
+ return;
+
+ for (unsigned arg_idx = 0; arg_idx < gimple_call_num_args (call); arg_idx++)
+ {
+ tree arg = gimple_call_arg (call, arg_idx);
+ tree diag_arg = sm_ctxt->get_diagnostic_tree (arg);
+ state_t state = sm_ctxt->get_state (stmt, arg);
+ bool bit_set = bitmap_bit_p (argmap, arg_idx);
+ if (TREE_CODE (TREE_TYPE (arg)) != INTEGER_TYPE)
+ continue;
+ if (bit_set) // Check if arg_idx is marked by any of the file descriptor
+ // attributes
+ {
+
+ if (is_closed_fd_p (state))
+ {
+
+ sm_ctxt->warn (node, stmt, arg,
+ new fd_use_after_close (*this, diag_arg,
+ callee_fndecl, attr_name,
+ arg_idx));
+ continue;
+ }
+
+ if (!(is_valid_fd_p (state) || (state == m_stop)))
+ {
+ if (!is_constant_fd_p (state))
+ sm_ctxt->warn (node, stmt, arg,
+ new fd_use_without_check (*this, diag_arg,
+ callee_fndecl, attr_name,
+ arg_idx));
+ }
+
+ switch (fd_attr_access_dir)
+ {
+ case DIRS_READ_WRITE:
+ break;
+ case DIRS_READ:
+
+ if (is_writeonly_fd_p (state))
+ {
+ sm_ctxt->warn (
+ node, stmt, arg,
+ new fd_access_mode_mismatch (*this, diag_arg, DIRS_WRITE,
+ callee_fndecl, attr_name, arg_idx));
+ }
+
+ break;
+ case DIRS_WRITE:
+
+ if (is_readonly_fd_p (state))
+ {
+ sm_ctxt->warn (
+ node, stmt, arg,
+ new fd_access_mode_mismatch (*this, diag_arg, DIRS_READ,
+ callee_fndecl, attr_name, arg_idx));
+ }
+
+ break;
+ }
+ }
+ }
+}
+
+
+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 fd_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, DIRS_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, DIRS_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_directions 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 fd_use_without_check (*this, diag_arg, callee_fndecl));
+ }
+ switch (callee_fndecl_dir)
+ {
+ case DIRS_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, DIRS_WRITE, callee_fndecl));
+ }
+
+ break;
+ case DIRS_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, DIRS_READ, callee_fndecl));
+ }
+ break;
+ default:
+ gcc_unreachable ();
+ }
+ }
+}
+
+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