aboutsummaryrefslogtreecommitdiff
path: root/gcc
diff options
context:
space:
mode:
authorDavid Malcolm <dmalcolm@redhat.com>2022-11-15 13:53:42 -0500
committerDavid Malcolm <dmalcolm@redhat.com>2022-11-15 13:53:42 -0500
commit86a90006864840c2e222d46ead551850caba184b (patch)
treedbd40ec5eead9974edb997d246a79036eed69d31 /gcc
parentd8aba860b34203621586df8c5a6756b18c2a0c32 (diff)
downloadgcc-86a90006864840c2e222d46ead551850caba184b.zip
gcc-86a90006864840c2e222d46ead551850caba184b.tar.gz
gcc-86a90006864840c2e222d46ead551850caba184b.tar.bz2
analyzer: add warnings relating to sockets [PR106140]
This patch generalizes the analyzer's file descriptor state machine so that it tracks the states of sockets. It adds two new warnings relating to misuses of socket APIs: * -Wanalyzer-fd-phase-mismatch (e.g. calling 'accept' on a socket before calling 'listen' on it) * -Wanalyzer-fd-type-mismatch (e.g. using a stream socket operation on a datagram socket) gcc/analyzer/ChangeLog: PR analyzer/106140 * analyzer-language.cc (on_finish_translation_unit): Stash named constants "SOCK_STREAM" and "SOCK_DGRAM". * analyzer.opt (Wanalyzer-fd-phase-mismatch): New. (Wanalyzer-fd-type-mismatch): New. * engine.cc (impl_region_model_context::get_state_map_by_name): Add "out_sm_context" param. Allow out_sm_idx to be NULL. * exploded-graph.h (impl_region_model_context::get_state_map_by_name): Add "out_sm_context" param. * region-model-impl-calls.cc (region_model::impl_call_accept): New. (region_model::impl_call_bind): New. (region_model::impl_call_connect): New. (region_model::impl_call_listen): New. (region_model::impl_call_socket): New. * region-model.cc (region_model::on_call_pre): Special-case "bind". (region_model::on_call_post): Special-case "accept", "bind", "connect", "listen", and "socket". * region-model.h (region_model::impl_call_accept): New decl. (region_model::impl_call_bind): New decl. (region_model::impl_call_connect): New decl. (region_model::impl_call_listen): New decl. (region_model::impl_call_socket): New decl. (region_model::on_socket): New decl. (region_model::on_bind): New decl. (region_model::on_listen): New decl. (region_model::on_accept): New decl. (region_model::on_connect): New decl. (region_model::add_constraint): Make public. (region_model::check_for_poison): Make public. (region_model_context::get_state_map_by_name): Add out_sm_context param. (region_model_context::get_fd_map): Likewise. (region_model_context::get_malloc_map): Likewise. (region_model_context::get_taint_map): Likewise. (noop_region_model_context::get_state_map_by_name): Likewise. (region_model_context_decorator::get_state_map_by_name): Likewise. * sm-fd.cc: Include "analyzer/supergraph.h" and "analyzer/analyzer-language.h". (enum expected_phase): New enum. (fd_state_machine::m_new_datagram_socket): New. (fd_state_machine::m_new_stream_socket): New. (fd_state_machine::m_new_unknown_socket): New. (fd_state_machine::m_bound_datagram_socket): New. (fd_state_machine::m_bound_stream_socket): New. (fd_state_machine::m_bound_unknown_socket): New. (fd_state_machine::m_listening_stream_socket): New. (fd_state_machine::m_m_connected_stream_socket): New. (fd_state_machine::m_SOCK_STREAM): New. (fd_state_machine::m_SOCK_DGRAM): New. (fd_diagnostic::describe_state_change): Handle socket states. (fd_diagnostic::get_meaning_for_state_change): Likewise. (class fd_phase_mismatch): New. (enum expected_type): New enum. (class fd_type_mismatch): New. (fd_state_machine::fd_state_machine): Initialize new states and stashed named constants. (fd_state_machine::is_socket_fd_p): New. (fd_state_machine::is_datagram_socket_fd_p): New. (fd_state_machine::is_stream_socket_fd_p): New. (fd_state_machine::on_close): Handle the socket states. (fd_state_machine::check_for_open_fd): Complain about fncalls on sockets in the wrong phase. Support socket FDs. (add_constraint_ge_zero): New. (fd_state_machine::get_state_for_socket_type): New. (fd_state_machine::on_socket): New. (fd_state_machine::check_for_socket_fd): New. (fd_state_machine::check_for_new_socket_fd): New. (fd_state_machine::on_bind): New. (fd_state_machine::on_listen): New. (fd_state_machine::on_accept): New. (fd_state_machine::on_connect): New. (fd_state_machine::can_purge_p): Don't purge socket values. (get_fd_state): New. (region_model::mark_as_valid_fd): Use get_fd_state. (region_model::on_socket): New. (region_model::on_bind): New. (region_model::on_listen): New. (region_model::on_accept): New. (region_model::on_connect): New. * sm-fd.dot: Update to reflect sm-fd.cc changes. gcc/ChangeLog: PR analyzer/106140 * doc/invoke.texi (Static Analyzer Options): Add -Wanalyzer-fd-phase-mismatch and -Wanalyzer-fd-type-mismatch. Add "socket", "bind", "listen", "accept", and "connect" to the list of functions known to the analyzer. gcc/testsuite/ChangeLog: PR analyzer/106140 * gcc.dg/analyzer/fd-accept.c: New test. * gcc.dg/analyzer/fd-bind.c: New test. * gcc.dg/analyzer/fd-connect.c: New test. * gcc.dg/analyzer/fd-datagram-socket.c: New test. * gcc.dg/analyzer/fd-glibc-byte-stream-connection-server.c: New test. * gcc.dg/analyzer/fd-glibc-byte-stream-socket.c: New test. * gcc.dg/analyzer/fd-glibc-datagram-client.c: New test. * gcc.dg/analyzer/fd-glibc-datagram-socket.c: New test. * gcc.dg/analyzer/fd-glibc-make_named_socket.h: New test. * gcc.dg/analyzer/fd-listen.c: New test. * gcc.dg/analyzer/fd-manpage-getaddrinfo-client.c: New test. * gcc.dg/analyzer/fd-mappage-getaddrinfo-server.c: New test. * gcc.dg/analyzer/fd-socket-meaning.c: New test. * gcc.dg/analyzer/fd-socket-misuse.c: New test. * gcc.dg/analyzer/fd-stream-socket-active-open.c: New test. * gcc.dg/analyzer/fd-stream-socket-passive-open.c: New test. * gcc.dg/analyzer/fd-stream-socket.c: New test. * gcc.dg/analyzer/fd-symbolic-socket.c: New test. * gcc.dg/analyzer/pr104369-1.c: Add -Wno-analyzer-too-complex and -Wno-analyzer-fd-leak to options. * gcc.dg/analyzer/pr104369-2.c: Add -Wno-analyzer-fd-leak to options. Signed-off-by: David Malcolm <dmalcolm@redhat.com>
Diffstat (limited to 'gcc')
-rw-r--r--gcc/analyzer/analyzer-language.cc2
-rw-r--r--gcc/analyzer/analyzer.opt8
-rw-r--r--gcc/analyzer/engine.cc60
-rw-r--r--gcc/analyzer/exploded-graph.h10
-rw-r--r--gcc/analyzer/region-model-impl-calls.cc150
-rw-r--r--gcc/analyzer/region-model.cc30
-rw-r--r--gcc/analyzer/region-model.h57
-rw-r--r--gcc/analyzer/sm-fd.cc1110
-rw-r--r--gcc/analyzer/sm-fd.dot64
-rw-r--r--gcc/doc/invoke.texi32
-rw-r--r--gcc/testsuite/gcc.dg/analyzer/fd-accept.c69
-rw-r--r--gcc/testsuite/gcc.dg/analyzer/fd-bind.c74
-rw-r--r--gcc/testsuite/gcc.dg/analyzer/fd-connect.c46
-rw-r--r--gcc/testsuite/gcc.dg/analyzer/fd-datagram-socket.c108
-rw-r--r--gcc/testsuite/gcc.dg/analyzer/fd-glibc-byte-stream-connection-server.c133
-rw-r--r--gcc/testsuite/gcc.dg/analyzer/fd-glibc-byte-stream-socket.c62
-rw-r--r--gcc/testsuite/gcc.dg/analyzer/fd-glibc-datagram-client.c56
-rw-r--r--gcc/testsuite/gcc.dg/analyzer/fd-glibc-datagram-socket.c52
-rw-r--r--gcc/testsuite/gcc.dg/analyzer/fd-glibc-make_named_socket.h47
-rw-r--r--gcc/testsuite/gcc.dg/analyzer/fd-listen.c63
-rw-r--r--gcc/testsuite/gcc.dg/analyzer/fd-manpage-getaddrinfo-client.c122
-rw-r--r--gcc/testsuite/gcc.dg/analyzer/fd-mappage-getaddrinfo-server.c119
-rw-r--r--gcc/testsuite/gcc.dg/analyzer/fd-socket-meaning.c21
-rw-r--r--gcc/testsuite/gcc.dg/analyzer/fd-socket-misuse.c98
-rw-r--r--gcc/testsuite/gcc.dg/analyzer/fd-stream-socket-active-open.c74
-rw-r--r--gcc/testsuite/gcc.dg/analyzer/fd-stream-socket-passive-open.c197
-rw-r--r--gcc/testsuite/gcc.dg/analyzer/fd-stream-socket.c98
-rw-r--r--gcc/testsuite/gcc.dg/analyzer/fd-symbolic-socket.c98
-rw-r--r--gcc/testsuite/gcc.dg/analyzer/pr104369-1.c4
-rw-r--r--gcc/testsuite/gcc.dg/analyzer/pr104369-2.c3
30 files changed, 3007 insertions, 60 deletions
diff --git a/gcc/analyzer/analyzer-language.cc b/gcc/analyzer/analyzer-language.cc
index ba4352b..0629b68 100644
--- a/gcc/analyzer/analyzer-language.cc
+++ b/gcc/analyzer/analyzer-language.cc
@@ -72,6 +72,8 @@ on_finish_translation_unit (const translation_unit &tu)
maybe_stash_named_constant (tu, "O_ACCMODE");
maybe_stash_named_constant (tu, "O_RDONLY");
maybe_stash_named_constant (tu, "O_WRONLY");
+ maybe_stash_named_constant (tu, "SOCK_STREAM");
+ maybe_stash_named_constant (tu, "SOCK_DGRAM");
}
/* Lookup NAME in the named constants stashed when the frontend TU finished.
diff --git a/gcc/analyzer/analyzer.opt b/gcc/analyzer/analyzer.opt
index 4944869..c4e7906 100644
--- a/gcc/analyzer/analyzer.opt
+++ b/gcc/analyzer/analyzer.opt
@@ -90,6 +90,14 @@ Wanalyzer-fd-leak
Common Var(warn_analyzer_fd_leak) Init(1) Warning
Warn about code paths in which a file descriptor is not closed.
+Wanalyzer-fd-phase-mismatch
+Common Var(warn_analyzer_fd_phase_mismatch) Init(1) Warning
+Warn about code paths in which an operation is attempted in the wrong phase of a file descriptor's lifetime.
+
+Wanalyzer-fd-type-mismatch
+Common Var(warn_analyzer_fd_type_mismatch) Init(1) Warning
+Warn about code paths in which an operation is attempted on the wrong type of file descriptor.
+
Wanalyzer-fd-use-after-close
Common Var(warn_analyzer_fd_use_after_close) Init(1) Warning
Warn about code paths in which a read or write is performed on a closed file descriptor.
diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc
index 891be7c..3ef411c 100644
--- a/gcc/analyzer/engine.cc
+++ b/gcc/analyzer/engine.cc
@@ -206,25 +206,6 @@ impl_region_model_context::terminate_path ()
return m_path_ctxt->terminate_path ();
}
-bool
-impl_region_model_context::get_state_map_by_name (const char *name,
- sm_state_map **out_smap,
- const state_machine **out_sm,
- unsigned *out_sm_idx)
-{
- if (!m_new_state)
- return false;
-
- unsigned sm_idx;
- if (!m_ext_state.get_sm_idx_by_name (name, &sm_idx))
- return false;
-
- *out_smap = m_new_state->m_checker_states[sm_idx];
- *out_sm = &m_ext_state.get_sm (sm_idx);
- *out_sm_idx = sm_idx;
- return true;
-}
-
/* struct setjmp_record. */
int
@@ -527,6 +508,47 @@ public:
bool m_unknown_side_effects;
};
+bool
+impl_region_model_context::
+get_state_map_by_name (const char *name,
+ sm_state_map **out_smap,
+ const state_machine **out_sm,
+ unsigned *out_sm_idx,
+ std::unique_ptr<sm_context> *out_sm_context)
+{
+ if (!m_new_state)
+ return false;
+
+ unsigned sm_idx;
+ if (!m_ext_state.get_sm_idx_by_name (name, &sm_idx))
+ return false;
+
+ const state_machine *sm = &m_ext_state.get_sm (sm_idx);
+ sm_state_map *new_smap = m_new_state->m_checker_states[sm_idx];
+
+ *out_smap = new_smap;
+ *out_sm = sm;
+ if (out_sm_idx)
+ *out_sm_idx = sm_idx;
+ if (out_sm_context)
+ {
+ const sm_state_map *old_smap = m_old_state->m_checker_states[sm_idx];
+ *out_sm_context
+ = make_unique<impl_sm_context> (*m_eg,
+ sm_idx,
+ *sm,
+ m_enode_for_diag,
+ m_old_state,
+ m_new_state,
+ old_smap,
+ new_smap,
+ m_path_ctxt,
+ m_stmt_finder,
+ false);
+ }
+ return true;
+}
+
/* Subclass of stmt_finder for finding the best stmt to report the leak at,
given the emission path. */
diff --git a/gcc/analyzer/exploded-graph.h b/gcc/analyzer/exploded-graph.h
index a4cbc8f..86644c1 100644
--- a/gcc/analyzer/exploded-graph.h
+++ b/gcc/analyzer/exploded-graph.h
@@ -98,10 +98,12 @@ class impl_region_model_context : public region_model_context
{
return &m_ext_state;
}
- bool get_state_map_by_name (const char *name,
- sm_state_map **out_smap,
- const state_machine **out_sm,
- unsigned *out_sm_idx) override;
+ bool
+ get_state_map_by_name (const char *name,
+ sm_state_map **out_smap,
+ const state_machine **out_sm,
+ unsigned *out_sm_idx,
+ std::unique_ptr<sm_context> *out_sm_context) override;
const gimple *get_stmt () const override { return m_stmt; }
diff --git a/gcc/analyzer/region-model-impl-calls.cc b/gcc/analyzer/region-model-impl-calls.cc
index a7134ed..99597e0 100644
--- a/gcc/analyzer/region-model-impl-calls.cc
+++ b/gcc/analyzer/region-model-impl-calls.cc
@@ -407,6 +407,66 @@ region_model::impl_call_analyzer_get_unknown_ptr (const call_details &cd)
cd.maybe_set_lhs (ptr_sval);
}
+/* Handle the on_call_post part of "accept". */
+
+void
+region_model::impl_call_accept (const call_details &cd)
+{
+ class outcome_of_accept : public succeed_or_fail_call_info
+ {
+ public:
+ outcome_of_accept (const call_details &cd, bool success)
+ : succeed_or_fail_call_info (cd, success)
+ {}
+
+ bool update_model (region_model *model,
+ const exploded_edge *,
+ region_model_context *ctxt) const final override
+ {
+ const call_details cd (get_call_details (model, ctxt));
+ return cd.get_model ()->on_accept (cd, m_success);
+ }
+ };
+
+ /* Body of region_model::impl_call_accept. */
+ if (cd.get_ctxt ())
+ {
+ cd.get_ctxt ()->bifurcate (make_unique<outcome_of_accept> (cd, false));
+ cd.get_ctxt ()->bifurcate (make_unique<outcome_of_accept> (cd, true));
+ cd.get_ctxt ()->terminate_path ();
+ }
+}
+
+/* Handle the on_call_post part of "bind". */
+
+void
+region_model::impl_call_bind (const call_details &cd)
+{
+ class outcome_of_bind : public succeed_or_fail_call_info
+ {
+ public:
+ outcome_of_bind (const call_details &cd, bool success)
+ : succeed_or_fail_call_info (cd, success)
+ {}
+
+ bool update_model (region_model *model,
+ const exploded_edge *,
+ region_model_context *ctxt) const final override
+ {
+ const call_details cd (get_call_details (model, ctxt));
+ return cd.get_model ()->on_bind (cd, m_success);
+ }
+ };
+
+ /* Body of region_model::impl_call_bind. */
+ if (cd.get_ctxt ())
+ {
+ cd.get_ctxt ()->bifurcate (make_unique<outcome_of_bind> (cd, false));
+ cd.get_ctxt ()->bifurcate (make_unique<outcome_of_bind> (cd, true));
+ cd.get_ctxt ()->terminate_path ();
+ }
+}
+
/* Handle the on_call_pre part of "__builtin_expect" etc. */
void
@@ -441,6 +501,36 @@ region_model::impl_call_calloc (const call_details &cd)
}
}
+/* Handle the on_call_post part of "connect". */
+
+void
+region_model::impl_call_connect (const call_details &cd)
+{
+ class outcome_of_connect : public succeed_or_fail_call_info
+ {
+ public:
+ outcome_of_connect (const call_details &cd, bool success)
+ : succeed_or_fail_call_info (cd, success)
+ {}
+
+ bool update_model (region_model *model,
+ const exploded_edge *,
+ region_model_context *ctxt) const final override
+ {
+ const call_details cd (get_call_details (model, ctxt));
+ return cd.get_model ()->on_connect (cd, m_success);
+ }
+ };
+
+ /* Body of region_model::impl_call_connect. */
+ if (cd.get_ctxt ())
+ {
+ cd.get_ctxt ()->bifurcate (make_unique<outcome_of_connect> (cd, false));
+ cd.get_ctxt ()->bifurcate (make_unique<outcome_of_connect> (cd, true));
+ cd.get_ctxt ()->terminate_path ();
+ }
+}
+
/* Handle the on_call_pre part of "__errno_location". */
void
@@ -543,6 +633,36 @@ region_model::impl_call_free (const call_details &cd)
}
}
+/* Handle the on_call_post part of "listen". */
+
+void
+region_model::impl_call_listen (const call_details &cd)
+{
+ class outcome_of_listen : public succeed_or_fail_call_info
+ {
+ public:
+ outcome_of_listen (const call_details &cd, bool success)
+ : succeed_or_fail_call_info (cd, success)
+ {}
+
+ bool update_model (region_model *model,
+ const exploded_edge *,
+ region_model_context *ctxt) const final override
+ {
+ const call_details cd (get_call_details (model, ctxt));
+ return cd.get_model ()->on_listen (cd, m_success);
+ }
+ };
+
+ /* Body of region_model::impl_call_listen. */
+ if (cd.get_ctxt ())
+ {
+ cd.get_ctxt ()->bifurcate (make_unique<outcome_of_listen> (cd, false));
+ cd.get_ctxt ()->bifurcate (make_unique<outcome_of_listen> (cd, true));
+ cd.get_ctxt ()->terminate_path ();
+ }
+}
+
/* Handle the on_call_pre part of "malloc". */
void
@@ -1055,6 +1175,36 @@ region_model::impl_call_realloc (const call_details &cd)
}
}
+/* Handle the on_call_post part of "socket". */
+
+void
+region_model::impl_call_socket (const call_details &cd)
+{
+ class outcome_of_socket : public succeed_or_fail_call_info
+ {
+ public:
+ outcome_of_socket (const call_details &cd, bool success)
+ : succeed_or_fail_call_info (cd, success)
+ {}
+
+ bool update_model (region_model *model,
+ const exploded_edge *,
+ region_model_context *ctxt) const final override
+ {
+ const call_details cd (get_call_details (model, ctxt));
+ return cd.get_model ()->on_socket (cd, m_success);
+ }
+ };
+
+ /* Body of region_model::impl_call_socket. */
+ if (cd.get_ctxt ())
+ {
+ cd.get_ctxt ()->bifurcate (make_unique<outcome_of_socket> (cd, false));
+ cd.get_ctxt ()->bifurcate (make_unique<outcome_of_socket> (cd, true));
+ cd.get_ctxt ()->terminate_path ();
+ }
+}
+
/* Handle the on_call_post part of "strchr" and "__builtin_strchr". */
void
diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc
index 5bae3cf..5f1dd01 100644
--- a/gcc/analyzer/region-model.cc
+++ b/gcc/analyzer/region-model.cc
@@ -2293,6 +2293,11 @@ region_model::on_call_pre (const gcall *call, region_model_context *ctxt,
impl_call_realloc (cd);
return false;
}
+ else if (is_named_call_p (callee_fndecl, "bind", call, 3))
+ {
+ /* Handle in "on_call_post". */
+ return false;
+ }
else if (is_named_call_p (callee_fndecl, "__errno_location", call, 0))
{
impl_call_errno_location (cd);
@@ -2422,12 +2427,37 @@ region_model::on_call_post (const gcall *call,
impl_call_operator_delete (cd);
return;
}
+ else if (is_named_call_p (callee_fndecl, "accept", call, 3))
+ {
+ impl_call_accept (cd);
+ return;
+ }
+ else if (is_named_call_p (callee_fndecl, "bind", call, 3))
+ {
+ impl_call_bind (cd);
+ return;
+ }
+ else if (is_named_call_p (callee_fndecl, "connect", call, 3))
+ {
+ impl_call_connect (cd);
+ return;
+ }
+ else if (is_named_call_p (callee_fndecl, "listen", call, 2))
+ {
+ impl_call_listen (cd);
+ return;
+ }
else if (is_pipe_call_p (callee_fndecl, "pipe", call, 1)
|| is_pipe_call_p (callee_fndecl, "pipe2", call, 2))
{
impl_call_pipe (cd);
return;
}
+ else if (is_named_call_p (callee_fndecl, "socket", call, 3))
+ {
+ impl_call_socket (cd);
+ return;
+ }
else if (is_named_call_p (callee_fndecl, "strchr", call, 2)
&& POINTER_TYPE_P (cd.get_arg_type (0)))
{
diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h
index bd81e6b..1e72c55 100644
--- a/gcc/analyzer/region-model.h
+++ b/gcc/analyzer/region-model.h
@@ -338,6 +338,7 @@ class region_model
void purge_state_involving (const svalue *sval, region_model_context *ctxt);
/* Specific handling for on_call_pre. */
+ void impl_call_accept (const call_details &cd);
void impl_call_alloca (const call_details &cd);
void impl_call_analyzer_describe (const gcall *call,
region_model_context *ctxt);
@@ -349,20 +350,24 @@ class region_model
void impl_call_analyzer_eval (const gcall *call,
region_model_context *ctxt);
void impl_call_analyzer_get_unknown_ptr (const call_details &cd);
+ void impl_call_bind (const call_details &cd);
void impl_call_builtin_expect (const call_details &cd);
void impl_call_calloc (const call_details &cd);
+ void impl_call_connect (const call_details &cd);
void impl_call_errno_location (const call_details &cd);
bool impl_call_error (const call_details &cd, unsigned min_args,
bool *out_terminate_path);
void impl_call_fgets (const call_details &cd);
void impl_call_fread (const call_details &cd);
void impl_call_free (const call_details &cd);
+ void impl_call_listen (const call_details &cd);
void impl_call_malloc (const call_details &cd);
void impl_call_memcpy (const call_details &cd);
void impl_call_memset (const call_details &cd);
void impl_call_pipe (const call_details &cd);
void impl_call_putenv (const call_details &cd);
void impl_call_realloc (const call_details &cd);
+ void impl_call_socket (const call_details &cd);
void impl_call_strchr (const call_details &cd);
void impl_call_strcpy (const call_details &cd);
void impl_call_strlen (const call_details &cd);
@@ -548,6 +553,11 @@ class region_model
/* Implemented in sm-fd.cc */
void mark_as_valid_fd (const svalue *sval, region_model_context *ctxt);
+ bool on_socket (const call_details &cd, bool successful);
+ bool on_bind (const call_details &cd, bool successful);
+ bool on_listen (const call_details &cd, bool successful);
+ bool on_accept (const call_details &cd, bool successful);
+ bool on_connect (const call_details &cd, bool successful);
/* Implemented in sm-malloc.cc */
void on_realloc_with_move (const call_details &cd,
@@ -558,7 +568,16 @@ class region_model
void mark_as_tainted (const svalue *sval,
region_model_context *ctxt);
- private:
+ bool add_constraint (const svalue *lhs,
+ enum tree_code op,
+ const svalue *rhs,
+ region_model_context *ctxt);
+
+ const svalue *check_for_poison (const svalue *sval,
+ tree expr,
+ region_model_context *ctxt) const;
+
+private:
const region *get_lvalue_1 (path_var pv, region_model_context *ctxt) const;
const svalue *get_rvalue_1 (path_var pv, region_model_context *ctxt) const;
@@ -571,10 +590,6 @@ class region_model
const known_function *get_known_function (tree fndecl) const;
- bool add_constraint (const svalue *lhs,
- enum tree_code op,
- const svalue *rhs,
- region_model_context *ctxt);
bool add_constraints_from_binop (const svalue *outer_lhs,
enum tree_code outer_op,
const svalue *outer_rhs,
@@ -605,9 +620,6 @@ class region_model
bool called_from_main_p () const;
const svalue *get_initial_value_for_global (const region *reg) const;
- const svalue *check_for_poison (const svalue *sval,
- tree expr,
- region_model_context *ctxt) const;
const region * get_region_for_poisoned_expr (tree expr) const;
void check_dynamic_size_for_taint (enum memory_space mem_space,
@@ -744,30 +756,33 @@ class region_model_context
/* Hook for clients to access the a specific state machine in
any underlying program_state. */
- virtual bool get_state_map_by_name (const char *name,
- sm_state_map **out_smap,
- const state_machine **out_sm,
- unsigned *out_sm_idx) = 0;
+ virtual bool
+ get_state_map_by_name (const char *name,
+ sm_state_map **out_smap,
+ const state_machine **out_sm,
+ unsigned *out_sm_idx,
+ std::unique_ptr<sm_context> *out_sm_context) = 0;
/* Precanned ways for clients to access specific state machines. */
bool get_fd_map (sm_state_map **out_smap,
const state_machine **out_sm,
- unsigned *out_sm_idx)
+ unsigned *out_sm_idx,
+ std::unique_ptr<sm_context> *out_sm_context)
{
return get_state_map_by_name ("file-descriptor", out_smap, out_sm,
- out_sm_idx);
+ out_sm_idx, out_sm_context);
}
bool get_malloc_map (sm_state_map **out_smap,
const state_machine **out_sm,
unsigned *out_sm_idx)
{
- return get_state_map_by_name ("malloc", out_smap, out_sm, out_sm_idx);
+ return get_state_map_by_name ("malloc", out_smap, out_sm, out_sm_idx, NULL);
}
bool get_taint_map (sm_state_map **out_smap,
const state_machine **out_sm,
unsigned *out_sm_idx)
{
- return get_state_map_by_name ("taint", out_smap, out_sm, out_sm_idx);
+ return get_state_map_by_name ("taint", out_smap, out_sm, out_sm_idx, NULL);
}
/* Get the current statement, if any. */
@@ -819,7 +834,8 @@ public:
bool get_state_map_by_name (const char *,
sm_state_map **,
const state_machine **,
- unsigned *) override
+ unsigned *,
+ std::unique_ptr<sm_context> *) override
{
return false;
}
@@ -946,9 +962,12 @@ class region_model_context_decorator : public region_model_context
bool get_state_map_by_name (const char *name,
sm_state_map **out_smap,
const state_machine **out_sm,
- unsigned *out_sm_idx) override
+ unsigned *out_sm_idx,
+ std::unique_ptr<sm_context> *out_sm_context)
+ override
{
- return m_inner->get_state_map_by_name (name, out_smap, out_sm, out_sm_idx);
+ return m_inner->get_state_map_by_name (name, out_smap, out_sm, out_sm_idx,
+ out_sm_context);
}
const gimple *get_stmt () const override
diff --git a/gcc/analyzer/sm-fd.cc b/gcc/analyzer/sm-fd.cc
index 370115d..d0b5871 100644
--- a/gcc/analyzer/sm-fd.cc
+++ b/gcc/analyzer/sm-fd.cc
@@ -45,6 +45,8 @@ along with GCC; see the file COPYING3. If not see
#include "analyzer/region-model.h"
#include "bitmap.h"
#include "analyzer/program-state.h"
+#include "analyzer/supergraph.h"
+#include "analyzer/analyzer-language.h"
#if ENABLE_ANALYZER
@@ -76,6 +78,17 @@ enum dup
DUP_3
};
+/* Enum for use by -Wanalyzer-fd-phase-mismatch. */
+
+enum expected_phase
+{
+ EXPECTED_PHASE_CAN_TRANSFER, /* can "read"/"write". */
+ EXPECTED_PHASE_CAN_BIND,
+ EXPECTED_PHASE_CAN_LISTEN,
+ EXPECTED_PHASE_CAN_ACCEPT,
+ EXPECTED_PHASE_CAN_CONNECT
+};
+
class fd_state_machine : public state_machine
{
public:
@@ -116,6 +129,9 @@ public:
bool is_unchecked_fd_p (state_t s) const;
bool is_valid_fd_p (state_t s) const;
+ bool is_socket_fd_p (state_t s) const;
+ bool is_datagram_socket_fd_p (state_t s) const;
+ bool is_stream_socket_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;
@@ -130,6 +146,27 @@ public:
const svalue *fd_sval,
const extrinsic_state &ext_state) const;
+ bool on_socket (const call_details &cd,
+ bool successful,
+ sm_context *sm_ctxt,
+ const extrinsic_state &ext_state) const;
+ bool on_bind (const call_details &cd,
+ bool successful,
+ sm_context *sm_ctxt,
+ const extrinsic_state &ext_state) const;
+ bool on_listen (const call_details &cd,
+ bool successful,
+ sm_context *sm_ctxt,
+ const extrinsic_state &ext_state) const;
+ bool on_accept (const call_details &cd,
+ bool successful,
+ sm_context *sm_ctxt,
+ const extrinsic_state &ext_state) const;
+ bool on_connect (const call_details &cd,
+ bool successful,
+ sm_context *sm_ctxt,
+ const extrinsic_state &ext_state) const;
+
/* State for a constant file descriptor (>= 0) */
state_t m_constant_fd;
@@ -156,6 +193,29 @@ public:
/* State for a file descriptor that has been closed. */
state_t m_closed;
+ /* States for FDs relating to socket APIs. */
+
+ /* Result of successful "socket" with SOCK_DGRAM. */
+ state_t m_new_datagram_socket;
+ /* Result of successful "socket" with SOCK_STREAM. */
+ state_t m_new_stream_socket;
+ /* Result of successful "socket" with unknown type. */
+ state_t m_new_unknown_socket;
+
+ /* The above after a successful call to "bind". */
+ state_t m_bound_datagram_socket;
+ state_t m_bound_stream_socket;
+ state_t m_bound_unknown_socket;
+
+ /* A bound socket after a successful call to "listen" (stream or unknown). */
+ state_t m_listening_stream_socket;
+
+ /* (i) the new FD as a result of a succesful call to "accept" on a
+ listening socket (via a passive open), or
+ (ii) an active socket after a successful call to "connect"
+ (via an active open). */
+ state_t m_connected_stream_socket;
+
/* State for a file descriptor that we do not want to track anymore . */
state_t m_stop;
@@ -163,6 +223,8 @@ public:
tree m_O_ACCMODE;
tree m_O_RDONLY;
tree m_O_WRONLY;
+ tree m_SOCK_STREAM;
+ tree m_SOCK_DGRAM;
private:
void on_open (sm_context *sm_ctxt, const supernode *node, const gimple *stmt,
@@ -195,6 +257,23 @@ private:
void check_for_dup (sm_context *sm_ctxt, const supernode *node,
const gimple *stmt, const gcall *call, const tree callee_fndecl,
enum dup kind) const;
+
+ state_t get_state_for_socket_type (const svalue *socket_type_sval) const;
+
+ bool check_for_socket_fd (const call_details &cd,
+ bool successful,
+ sm_context *sm_ctxt,
+ const svalue *fd_sval,
+ const supernode *node,
+ state_t old_state,
+ bool *complained = NULL) const;
+ bool check_for_new_socket_fd (const call_details &cd,
+ bool successful,
+ sm_context *sm_ctxt,
+ const svalue *fd_sval,
+ const supernode *node,
+ state_t old_state,
+ enum expected_phase expected_phase) const;
};
/* Base diagnostic class relative to fd_state_machine. */
@@ -214,9 +293,7 @@ public:
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)
- || m_sm.is_valid_fd_p (change.m_new_state)))
+ if (change.m_old_state == m_sm.get_start_state ())
{
if (change.m_new_state == m_sm.m_unchecked_read_write
|| change.m_new_state == m_sm.m_valid_read_write)
@@ -229,8 +306,32 @@ public:
if (change.m_new_state == m_sm.m_unchecked_write_only
|| change.m_new_state == m_sm.m_valid_write_only)
return change.formatted_print ("opened here as write-only");
+
+ if (change.m_new_state == m_sm.m_new_datagram_socket)
+ return change.formatted_print ("datagram socket created here");
+
+ if (change.m_new_state == m_sm.m_new_stream_socket)
+ return change.formatted_print ("stream socket created here");
+
+ if (change.m_new_state == m_sm.m_new_unknown_socket
+ || change.m_new_state == m_sm.m_connected_stream_socket)
+ return change.formatted_print ("socket created here");
}
+ if (change.m_new_state == m_sm.m_bound_datagram_socket)
+ return change.formatted_print ("datagram socket bound here");
+
+ if (change.m_new_state == m_sm.m_bound_stream_socket)
+ return change.formatted_print ("stream socket bound here");
+
+ if (change.m_new_state == m_sm.m_bound_unknown_socket
+ || change.m_new_state == m_sm.m_connected_stream_socket)
+ return change.formatted_print ("socket bound here");
+
+ if (change.m_new_state == m_sm.m_listening_stream_socket)
+ return change.formatted_print
+ ("stream socket marked as passive here via %qs", "listen");
+
if (change.m_new_state == m_sm.m_closed)
return change.formatted_print ("closed here");
@@ -263,7 +364,10 @@ public:
const evdesc::state_change &change) const final override
{
if (change.m_old_state == m_sm.get_start_state ()
- && (m_sm.is_unchecked_fd_p (change.m_new_state)))
+ && (m_sm.is_unchecked_fd_p (change.m_new_state)
+ || change.m_new_state == m_sm.m_new_datagram_socket
+ || change.m_new_state == m_sm.m_new_stream_socket
+ || change.m_new_state == m_sm.m_new_unknown_socket))
return diagnostic_event::meaning (diagnostic_event::VERB_acquire,
diagnostic_event::NOUN_resource);
if (change.m_new_state == m_sm.m_closed)
@@ -680,6 +784,289 @@ private:
diagnostic_event_id_t m_first_open_event;
};
+/* Concrete pending_diagnostic subclass for -Wanalyzer-fd-phase-mismatch. */
+
+class fd_phase_mismatch : public fd_param_diagnostic
+{
+public:
+ fd_phase_mismatch (const fd_state_machine &sm, tree arg,
+ const tree callee_fndecl,
+ state_machine::state_t actual_state,
+ enum expected_phase expected_phase)
+ : fd_param_diagnostic (sm, arg, callee_fndecl),
+ m_actual_state (actual_state),
+ m_expected_phase (expected_phase)
+ {
+ gcc_assert (m_sm.is_socket_fd_p (actual_state));
+ switch (expected_phase)
+ {
+ case EXPECTED_PHASE_CAN_TRANSFER:
+ gcc_assert (actual_state == m_sm.m_new_stream_socket
+ || actual_state == m_sm.m_bound_stream_socket
+ || actual_state == m_sm.m_listening_stream_socket);
+ break;
+ case EXPECTED_PHASE_CAN_BIND:
+ gcc_assert (actual_state == m_sm.m_bound_datagram_socket
+ || actual_state == m_sm.m_bound_stream_socket
+ || actual_state == m_sm.m_bound_unknown_socket
+ || actual_state == m_sm.m_connected_stream_socket
+ || actual_state == m_sm.m_listening_stream_socket);
+ break;
+ case EXPECTED_PHASE_CAN_LISTEN:
+ gcc_assert (actual_state == m_sm.m_new_stream_socket
+ || actual_state == m_sm.m_new_unknown_socket
+ || actual_state == m_sm.m_connected_stream_socket);
+ break;
+ case EXPECTED_PHASE_CAN_ACCEPT:
+ gcc_assert (actual_state == m_sm.m_new_stream_socket
+ || actual_state == m_sm.m_new_unknown_socket
+ || actual_state == m_sm.m_bound_stream_socket
+ || actual_state == m_sm.m_bound_unknown_socket
+ || actual_state == m_sm.m_connected_stream_socket);
+ break;
+ case EXPECTED_PHASE_CAN_CONNECT:
+ gcc_assert (actual_state == m_sm.m_bound_datagram_socket
+ || actual_state == m_sm.m_bound_stream_socket
+ || actual_state == m_sm.m_bound_unknown_socket
+ || actual_state == m_sm.m_listening_stream_socket
+ || actual_state == m_sm.m_connected_stream_socket);
+ break;
+ }
+ }
+
+ const char *
+ get_kind () const final override
+ {
+ return "fd_phase_mismatch";
+ }
+
+ bool
+ subclass_equal_p (const pending_diagnostic &base_other) const final override
+ {
+ const fd_phase_mismatch &sub_other = (const fd_phase_mismatch &)base_other;
+ if (!fd_param_diagnostic ::subclass_equal_p (sub_other))
+ return false;
+ return (m_actual_state == sub_other.m_actual_state
+ && m_expected_phase == sub_other.m_expected_phase);
+ }
+
+ int
+ get_controlling_option () const final override
+ {
+ return OPT_Wanalyzer_fd_phase_mismatch;
+ }
+
+ bool
+ emit (rich_location *rich_loc) final override
+ {
+ /* CWE-666: Operation on Resource in Wrong Phase of Lifetime. */
+ diagnostic_metadata m;
+ m.add_cwe (666);
+ return warning_at (rich_loc, get_controlling_option (),
+ "%qE on file descriptor %qE in wrong phase",
+ m_callee_fndecl, m_arg);
+ }
+
+ label_text
+ describe_final_event (const evdesc::final_event &ev) final override
+ {
+ switch (m_expected_phase)
+ {
+ case EXPECTED_PHASE_CAN_TRANSFER:
+ {
+ if (m_actual_state == m_sm.m_new_stream_socket)
+ return ev.formatted_print
+ ("%qE expects a stream socket to be connected via %qs"
+ " but %qE has not yet been bound",
+ m_callee_fndecl, "accept", m_arg);
+ if (m_actual_state == m_sm.m_bound_stream_socket)
+ return ev.formatted_print
+ ("%qE expects a stream socket to be connected via %qs"
+ " but %qE is not yet listening",
+ m_callee_fndecl, "accept", m_arg);
+ if (m_actual_state == m_sm.m_listening_stream_socket)
+ return ev.formatted_print
+ ("%qE expects a stream socket to be connected via"
+ " the return value of %qs"
+ " but %qE is listening; wrong file descriptor?",
+ m_callee_fndecl, "accept", m_arg);
+ }
+ break;
+ case EXPECTED_PHASE_CAN_BIND:
+ {
+ if (m_actual_state == m_sm.m_bound_datagram_socket
+ || m_actual_state == m_sm.m_bound_stream_socket
+ || m_actual_state == m_sm.m_bound_unknown_socket)
+ return ev.formatted_print
+ ("%qE expects a new socket file descriptor"
+ " but %qE has already been bound",
+ m_callee_fndecl, m_arg);
+ if (m_actual_state == m_sm.m_connected_stream_socket)
+ return ev.formatted_print
+ ("%qE expects a new socket file descriptor"
+ " but %qE is already connected",
+ m_callee_fndecl, m_arg);
+ if (m_actual_state == m_sm.m_listening_stream_socket)
+ return ev.formatted_print
+ ("%qE expects a new socket file descriptor"
+ " but %qE is already listening",
+ m_callee_fndecl, m_arg);
+ }
+ break;
+ case EXPECTED_PHASE_CAN_LISTEN:
+ {
+ if (m_actual_state == m_sm.m_new_stream_socket
+ || m_actual_state == m_sm.m_new_unknown_socket)
+ return ev.formatted_print
+ ("%qE expects a bound stream socket file descriptor"
+ " but %qE has not yet been bound",
+ m_callee_fndecl, m_arg);
+ if (m_actual_state == m_sm.m_connected_stream_socket)
+ return ev.formatted_print
+ ("%qE expects a bound stream socket file descriptor"
+ " but %qE is connected",
+ m_callee_fndecl, m_arg);
+ }
+ break;
+ case EXPECTED_PHASE_CAN_ACCEPT:
+ {
+ if (m_actual_state == m_sm.m_new_stream_socket
+ || m_actual_state == m_sm.m_new_unknown_socket)
+ return ev.formatted_print
+ ("%qE expects a listening stream socket file descriptor"
+ " but %qE has not yet been bound",
+ m_callee_fndecl, m_arg);
+ if (m_actual_state == m_sm.m_bound_stream_socket
+ || m_actual_state == m_sm.m_bound_unknown_socket)
+ return ev.formatted_print
+ ("%qE expects a listening stream socket file descriptor"
+ " whereas %qE is bound but not yet listening",
+ m_callee_fndecl, m_arg);
+ if (m_actual_state == m_sm.m_connected_stream_socket)
+ return ev.formatted_print
+ ("%qE expects a listening stream socket file descriptor"
+ " but %qE is connected",
+ m_callee_fndecl, m_arg);
+ }
+ break;
+ case EXPECTED_PHASE_CAN_CONNECT:
+ {
+ if (m_actual_state == m_sm.m_bound_datagram_socket
+ || m_actual_state == m_sm.m_bound_stream_socket
+ || m_actual_state == m_sm.m_bound_unknown_socket)
+ return ev.formatted_print
+ ("%qE expects a new socket file descriptor but %qE is bound",
+ m_callee_fndecl, m_arg);
+ else
+ return ev.formatted_print
+ ("%qE expects a new socket file descriptor", m_callee_fndecl);
+ }
+ break;
+ }
+ gcc_unreachable ();
+ }
+
+private:
+ state_machine::state_t m_actual_state;
+ enum expected_phase m_expected_phase;
+};
+
+/* Enum for use by -Wanalyzer-fd-type-mismatch. */
+
+enum expected_type
+{
+ EXPECTED_TYPE_SOCKET,
+ EXPECTED_TYPE_STREAM_SOCKET
+};
+
+/* Concrete pending_diagnostic subclass for -Wanalyzer-fd-type-mismatch. */
+
+class fd_type_mismatch : public fd_param_diagnostic
+{
+public:
+ fd_type_mismatch (const fd_state_machine &sm, tree arg,
+ const tree callee_fndecl,
+ state_machine::state_t actual_state,
+ enum expected_type expected_type)
+ : fd_param_diagnostic (sm, arg, callee_fndecl),
+ m_actual_state (actual_state),
+ m_expected_type (expected_type)
+ {
+ }
+
+ const char *
+ get_kind () const final override
+ {
+ return "fd_type_mismatch";
+ }
+
+ bool
+ subclass_equal_p (const pending_diagnostic &base_other) const final override
+ {
+ const fd_type_mismatch &sub_other = (const fd_type_mismatch &)base_other;
+ if (!fd_param_diagnostic ::subclass_equal_p (sub_other))
+ return false;
+ return (m_actual_state == sub_other.m_actual_state
+ && m_expected_type == sub_other.m_expected_type);
+ }
+
+ int
+ get_controlling_option () const final override
+ {
+ return OPT_Wanalyzer_fd_type_mismatch;
+ }
+
+ bool
+ emit (rich_location *rich_loc) final override
+ {
+ switch (m_expected_type)
+ {
+ default:
+ gcc_unreachable ();
+ case EXPECTED_TYPE_SOCKET:
+ return warning_at (rich_loc, get_controlling_option (),
+ "%qE on non-socket file descriptor %qE",
+ m_callee_fndecl, m_arg);
+ case EXPECTED_TYPE_STREAM_SOCKET:
+ if (m_sm.is_datagram_socket_fd_p (m_actual_state))
+ return warning_at (rich_loc, get_controlling_option (),
+ "%qE on datagram socket file descriptor %qE",
+ m_callee_fndecl, m_arg);
+ else
+ return warning_at (rich_loc, get_controlling_option (),
+ "%qE on non-stream-socket file descriptor %qE",
+ m_callee_fndecl, m_arg);
+ }
+ }
+
+ label_text
+ describe_final_event (const evdesc::final_event &ev) final override
+ {
+ switch (m_expected_type)
+ {
+ default:
+ break;
+ gcc_unreachable ();
+ case EXPECTED_TYPE_SOCKET:
+ case EXPECTED_TYPE_STREAM_SOCKET:
+ if (!m_sm.is_socket_fd_p (m_actual_state))
+ return ev.formatted_print ("%qE expects a socket file descriptor"
+ " but %qE is not a socket",
+ m_callee_fndecl, m_arg);
+ }
+ gcc_assert (m_expected_type == EXPECTED_TYPE_STREAM_SOCKET);
+ gcc_assert (m_sm.is_datagram_socket_fd_p (m_actual_state));
+ return ev.formatted_print
+ ("%qE expects a stream socket file descriptor"
+ " but %qE is a datagram socket",
+ m_callee_fndecl, m_arg);
+ }
+
+private:
+ state_machine::state_t m_actual_state;
+ enum expected_type m_expected_type;
+};
+
fd_state_machine::fd_state_machine (logger *logger)
: state_machine ("file-descriptor", logger),
m_constant_fd (add_state ("fd-constant")),
@@ -691,10 +1078,20 @@ fd_state_machine::fd_state_machine (logger *logger)
m_valid_write_only (add_state ("fd-valid-write-only")),
m_invalid (add_state ("fd-invalid")),
m_closed (add_state ("fd-closed")),
+ m_new_datagram_socket (add_state ("fd-new-datagram-socket")),
+ m_new_stream_socket (add_state ("fd-new-stream-socket")),
+ m_new_unknown_socket (add_state ("fd-new-unknown-socket")),
+ m_bound_datagram_socket (add_state ("fd-bound-datagram-socket")),
+ m_bound_stream_socket (add_state ("fd-bound-stream-socket")),
+ m_bound_unknown_socket (add_state ("fd-bound-unknown-socket")),
+ m_listening_stream_socket (add_state ("fd-listening-stream-socket")),
+ m_connected_stream_socket (add_state ("fd-connected-stream-socket")),
m_stop (add_state ("fd-stop")),
m_O_ACCMODE (get_stashed_constant_by_name ("O_ACCMODE")),
m_O_RDONLY (get_stashed_constant_by_name ("O_RDONLY")),
- m_O_WRONLY (get_stashed_constant_by_name ("O_WRONLY"))
+ m_O_WRONLY (get_stashed_constant_by_name ("O_WRONLY")),
+ m_SOCK_STREAM (get_stashed_constant_by_name ("SOCK_STREAM")),
+ m_SOCK_DGRAM (get_stashed_constant_by_name ("SOCK_DGRAM"))
{
}
@@ -714,6 +1111,39 @@ fd_state_machine::is_valid_fd_p (state_t s) const
|| s == m_valid_write_only);
}
+bool
+fd_state_machine::is_socket_fd_p (state_t s) const
+{
+ return (s == m_new_datagram_socket
+ || s == m_new_stream_socket
+ || s == m_new_unknown_socket
+ || s == m_bound_datagram_socket
+ || s == m_bound_stream_socket
+ || s == m_bound_unknown_socket
+ || s == m_listening_stream_socket
+ || s == m_connected_stream_socket);
+}
+
+bool
+fd_state_machine::is_datagram_socket_fd_p (state_t s) const
+{
+ return (s == m_new_datagram_socket
+ || s == m_new_unknown_socket
+ || s == m_bound_datagram_socket
+ || s == m_bound_unknown_socket);
+}
+
+bool
+fd_state_machine::is_stream_socket_fd_p (state_t s) const
+{
+ return (s == m_new_stream_socket
+ || s == m_new_unknown_socket
+ || s == m_bound_stream_socket
+ || s == m_bound_unknown_socket
+ || s == m_listening_stream_socket
+ || s == m_connected_stream_socket);
+}
+
enum access_mode
fd_state_machine::get_access_mode_from_flag (int flag) const
{
@@ -1079,6 +1509,14 @@ fd_state_machine::on_close (sm_context *sm_ctxt, const supernode *node,
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);
+ sm_ctxt->on_transition (node, stmt, arg, m_new_datagram_socket, m_closed);
+ sm_ctxt->on_transition (node, stmt, arg, m_new_stream_socket, m_closed);
+ sm_ctxt->on_transition (node, stmt, arg, m_new_unknown_socket, m_closed);
+ sm_ctxt->on_transition (node, stmt, arg, m_bound_datagram_socket, m_closed);
+ sm_ctxt->on_transition (node, stmt, arg, m_bound_stream_socket, m_closed);
+ sm_ctxt->on_transition (node, stmt, arg, m_bound_unknown_socket, m_closed);
+ sm_ctxt->on_transition (node, stmt, arg, m_listening_stream_socket, m_closed);
+ sm_ctxt->on_transition (node, stmt, arg, m_connected_stream_socket, m_closed);
if (is_closed_fd_p (state))
{
@@ -1121,7 +1559,22 @@ fd_state_machine::check_for_open_fd (
else
{
- if (!(is_valid_fd_p (state) || state == m_start || state == m_stop))
+ if (state == m_new_stream_socket
+ || state == m_bound_stream_socket
+ || state == m_listening_stream_socket)
+ /* Complain about fncall on socket in wrong phase. */
+ sm_ctxt->warn
+ (node, stmt, arg,
+ make_unique<fd_phase_mismatch> (*this, diag_arg,
+ callee_fndecl,
+ state,
+ EXPECTED_PHASE_CAN_TRANSFER));
+ else if (!(is_valid_fd_p (state)
+ || state == m_new_datagram_socket
+ || state == m_bound_unknown_socket
+ || state == m_connected_stream_socket
+ || state == m_start
+ || state == m_stop))
{
if (!is_constant_fd_p (state))
sm_ctxt->warn (
@@ -1157,6 +1610,529 @@ fd_state_machine::check_for_open_fd (
}
}
+static bool
+add_constraint_ge_zero (region_model *model,
+ const svalue *fd_sval,
+ region_model_context *ctxt)
+{
+ const svalue *zero
+ = model->get_manager ()->get_or_create_int_cst (integer_type_node, 0);
+ return model->add_constraint (fd_sval, GE_EXPR, zero, ctxt);
+}
+
+/* Get the state for a new socket type based on SOCKET_TYPE_SVAL,
+ a SOCK_* value. */
+
+state_machine::state_t
+fd_state_machine::
+get_state_for_socket_type (const svalue *socket_type_sval) const
+{
+ if (tree socket_type_cst = socket_type_sval->maybe_get_constant ())
+ {
+ /* Attempt to use SOCK_* constants stashed from the frontend. */
+ if (tree_int_cst_equal (socket_type_cst, m_SOCK_STREAM))
+ return m_new_stream_socket;
+ if (tree_int_cst_equal (socket_type_cst, m_SOCK_DGRAM))
+ return m_new_datagram_socket;
+ }
+
+ /* Unrecognized constant, or a symbolic "type" value. */
+ return m_new_unknown_socket;
+}
+
+/* Update the model and fd state for an outcome of a call to "socket",
+ where SUCCESSFUL indicate which of the two outcomes.
+ Return true if the outcome is feasible, or false to reject it. */
+
+bool
+fd_state_machine::on_socket (const call_details &cd,
+ bool successful,
+ sm_context *sm_ctxt,
+ const extrinsic_state &ext_state) const
+{
+ const gcall *stmt = cd.get_call_stmt ();
+ engine *eng = ext_state.get_engine ();
+ const supergraph *sg = eng->get_supergraph ();
+ const supernode *node = sg->get_supernode_for_stmt (stmt);
+ region_model *model = cd.get_model ();
+
+ if (successful)
+ {
+ if (gimple_call_lhs (stmt))
+ {
+ conjured_purge p (model, cd.get_ctxt ());
+ region_model_manager *mgr = model->get_manager ();
+ const svalue *new_fd
+ = mgr->get_or_create_conjured_svalue (integer_type_node,
+ stmt,
+ cd.get_lhs_region (),
+ p);
+ if (!add_constraint_ge_zero (model, new_fd, cd.get_ctxt ()))
+ return false;
+
+ const svalue *socket_type_sval = cd.get_arg_svalue (1);
+ state_machine::state_t new_state
+ = get_state_for_socket_type (socket_type_sval);
+ sm_ctxt->on_transition (node, stmt, new_fd, m_start, new_state);
+ model->set_value (cd.get_lhs_region (), new_fd, cd.get_ctxt ());
+ }
+ else
+ sm_ctxt->warn (node, stmt, NULL_TREE,
+ make_unique<fd_leak> (*this, NULL_TREE));
+ }
+ else
+ {
+ /* Return -1; set errno. */
+ model->update_for_int_cst_return (cd, -1, true);
+ model->set_errno (cd);
+ }
+
+ return true;
+}
+
+/* Check that FD_SVAL is usable by socket APIs.
+ Complain if it has been closed, if it is a non-socket,
+ or is invalid.
+ If COMPLAINED is non-NULL and a problem is found,
+ write *COMPLAINED = true.
+
+ If SUCCESSFUL is true, attempt to add the constraint that FD_SVAL >= 0.
+ Return true if this outcome is feasible. */
+
+bool
+fd_state_machine::check_for_socket_fd (const call_details &cd,
+ bool successful,
+ sm_context *sm_ctxt,
+ const svalue *fd_sval,
+ const supernode *node,
+ state_t old_state,
+ bool *complained) const
+{
+ const gcall *stmt = cd.get_call_stmt ();
+
+ if (is_closed_fd_p (old_state))
+ {
+ tree diag_arg = sm_ctxt->get_diagnostic_tree (fd_sval);
+ sm_ctxt->warn
+ (node, stmt, fd_sval,
+ make_unique<fd_use_after_close> (*this, diag_arg,
+ cd.get_fndecl_for_call ()));
+ if (complained)
+ *complained = true;
+ if (successful)
+ return false;
+ }
+ else if (is_unchecked_fd_p (old_state) || is_valid_fd_p (old_state))
+ {
+ /* Complain about non-socket. */
+ tree diag_arg = sm_ctxt->get_diagnostic_tree (fd_sval);
+ sm_ctxt->warn
+ (node, stmt, fd_sval,
+ make_unique<fd_type_mismatch> (*this, diag_arg,
+ cd.get_fndecl_for_call (),
+ old_state,
+ EXPECTED_TYPE_SOCKET));
+ if (complained)
+ *complained = true;
+ if (successful)
+ return false;
+ }
+ else if (old_state == m_invalid)
+ {
+ tree diag_arg = sm_ctxt->get_diagnostic_tree (fd_sval);
+ sm_ctxt->warn
+ (node, stmt, fd_sval,
+ make_unique<fd_use_without_check> (*this, diag_arg,
+ cd.get_fndecl_for_call ()));
+ if (complained)
+ *complained = true;
+ if (successful)
+ return false;
+ }
+
+ if (successful)
+ if (!add_constraint_ge_zero (cd.get_model (), fd_sval, cd.get_ctxt ()))
+ return false;
+
+ return true;
+}
+
+/* For use by "bind" and "connect".
+ As per fd_state_machine::check_for_socket_fd above,
+ but also complain if we don't have a new socket, and check that
+ we can read up to the size bytes from the address. */
+
+bool
+fd_state_machine::check_for_new_socket_fd (const call_details &cd,
+ bool successful,
+ sm_context *sm_ctxt,
+ const svalue *fd_sval,
+ const supernode *node,
+ state_t old_state,
+ enum expected_phase expected_phase)
+ const
+{
+ bool complained = false;
+
+ /* Check address and len. */
+ const svalue *address_sval = cd.get_arg_svalue (1);
+ const svalue *len_sval = cd.get_arg_svalue (2);
+
+ /* Check that we can read the given number of bytes from the
+ address. */
+ region_model *model = cd.get_model ();
+ const region *address_reg
+ = model->deref_rvalue (address_sval, cd.get_arg_tree (1),
+ cd.get_ctxt ());
+ const region *sized_address_reg
+ = model->get_manager ()->get_sized_region (address_reg,
+ NULL_TREE,
+ len_sval);
+ model->get_store_value (sized_address_reg, cd.get_ctxt ());
+
+ if (!check_for_socket_fd (cd, successful, sm_ctxt,
+ fd_sval, node, old_state, &complained))
+ return false;
+ else if (!complained
+ && !(old_state == m_new_stream_socket
+ || old_state == m_new_datagram_socket
+ || old_state == m_new_unknown_socket
+ || old_state == m_start
+ || old_state == m_stop))
+ {
+ /* Complain about "bind" or "connect" in wrong phase. */
+ tree diag_arg = sm_ctxt->get_diagnostic_tree (fd_sval);
+ sm_ctxt->warn
+ (node, cd.get_call_stmt (), fd_sval,
+ make_unique<fd_phase_mismatch> (*this, diag_arg,
+ cd.get_fndecl_for_call (),
+ old_state,
+ expected_phase));
+ if (successful)
+ return false;
+ }
+ else if (!successful)
+ {
+ /* If we were in the start state, assume we had a new socket. */
+ if (old_state == m_start)
+ sm_ctxt->set_next_state (cd.get_call_stmt (), fd_sval,
+ m_new_unknown_socket);
+ }
+
+ /* Passing NULL as the address will lead to failure. */
+ if (successful)
+ if (address_sval->all_zeroes_p ())
+ return false;
+
+ return true;
+}
+
+/* Update the model and fd state for an outcome of a call to "bind",
+ where SUCCESSFUL indicate which of the two outcomes.
+ Return true if the outcome is feasible, or false to reject it. */
+
+bool
+fd_state_machine::on_bind (const call_details &cd,
+ bool successful,
+ sm_context *sm_ctxt,
+ const extrinsic_state &ext_state) const
+{
+ const gcall *stmt = cd.get_call_stmt ();
+ engine *eng = ext_state.get_engine ();
+ const supergraph *sg = eng->get_supergraph ();
+ const supernode *node = sg->get_supernode_for_stmt (stmt);
+ const svalue *fd_sval = cd.get_arg_svalue (0);
+ region_model *model = cd.get_model ();
+ state_t old_state = sm_ctxt->get_state (stmt, fd_sval);
+
+ if (!check_for_new_socket_fd (cd, successful, sm_ctxt,
+ fd_sval, node, old_state,
+ EXPECTED_PHASE_CAN_BIND))
+ return false;
+
+ if (successful)
+ {
+ state_t next_state = NULL;
+ if (old_state == m_new_stream_socket)
+ next_state = m_bound_stream_socket;
+ else if (old_state == m_new_datagram_socket)
+ next_state = m_bound_datagram_socket;
+ else if (old_state == m_new_unknown_socket)
+ next_state = m_bound_unknown_socket;
+ else if (old_state == m_start)
+ next_state = m_bound_unknown_socket;
+ else if (old_state == m_stop)
+ next_state = m_stop;
+ else
+ gcc_unreachable ();
+ sm_ctxt->set_next_state (cd.get_call_stmt (), fd_sval, next_state);
+ model->update_for_zero_return (cd, true);
+ }
+ else
+ {
+ /* Return -1; set errno. */
+ model->update_for_int_cst_return (cd, -1, true);
+ model->set_errno (cd);
+ }
+
+ return true;
+}
+
+/* Update the model and fd state for an outcome of a call to "listen",
+ where SUCCESSFUL indicate which of the two outcomes.
+ Return true if the outcome is feasible, or false to reject it. */
+
+bool
+fd_state_machine::on_listen (const call_details &cd,
+ bool successful,
+ sm_context *sm_ctxt,
+ const extrinsic_state &ext_state) const
+{
+ const gcall *stmt = cd.get_call_stmt ();
+ engine *eng = ext_state.get_engine ();
+ const supergraph *sg = eng->get_supergraph ();
+ const supernode *node = sg->get_supernode_for_stmt (cd.get_call_stmt ());
+ const svalue *fd_sval = cd.get_arg_svalue (0);
+ region_model *model = cd.get_model ();
+ state_t old_state = sm_ctxt->get_state (stmt, fd_sval);
+
+ /* We expect a stream socket that's had "bind" called on it. */
+ if (!check_for_socket_fd (cd, successful, sm_ctxt, fd_sval, node, old_state))
+ return false;
+ if (!(old_state == m_start
+ || old_state == m_stop
+ || old_state == m_bound_stream_socket
+ || old_state == m_bound_unknown_socket
+ /* Assume it's OK to call "listen" more than once. */
+ || old_state == m_listening_stream_socket))
+ {
+ /* Complain about fncall on wrong type or in wrong phase. */
+ tree diag_arg = sm_ctxt->get_diagnostic_tree (fd_sval);
+ if (is_stream_socket_fd_p (old_state))
+ sm_ctxt->warn
+ (node, stmt, fd_sval,
+ make_unique<fd_phase_mismatch> (*this, diag_arg,
+ cd.get_fndecl_for_call (),
+ old_state,
+ EXPECTED_PHASE_CAN_LISTEN));
+ else
+ sm_ctxt->warn
+ (node, stmt, fd_sval,
+ make_unique<fd_type_mismatch> (*this, diag_arg,
+ cd.get_fndecl_for_call (),
+ old_state,
+ EXPECTED_TYPE_STREAM_SOCKET));
+ if (successful)
+ return false;
+ }
+
+ if (successful)
+ {
+ model->update_for_zero_return (cd, true);
+ sm_ctxt->set_next_state (cd.get_call_stmt (), fd_sval,
+ m_listening_stream_socket);
+ }
+ else
+ {
+ /* Return -1; set errno. */
+ model->update_for_int_cst_return (cd, -1, true);
+ model->set_errno (cd);
+ if (old_state == m_start)
+ sm_ctxt->set_next_state (cd.get_call_stmt (), fd_sval,
+ m_bound_stream_socket);
+ }
+
+ return true;
+}
+
+/* Update the model and fd state for an outcome of a call to "accept",
+ where SUCCESSFUL indicate which of the two outcomes.
+ Return true if the outcome is feasible, or false to reject it. */
+
+bool
+fd_state_machine::on_accept (const call_details &cd,
+ bool successful,
+ sm_context *sm_ctxt,
+ const extrinsic_state &ext_state) const
+{
+ const gcall *stmt = cd.get_call_stmt ();
+ engine *eng = ext_state.get_engine ();
+ const supergraph *sg = eng->get_supergraph ();
+ const supernode *node = sg->get_supernode_for_stmt (stmt);
+ const svalue *fd_sval = cd.get_arg_svalue (0);
+ const svalue *address_sval = cd.get_arg_svalue (1);
+ const svalue *len_ptr_sval = cd.get_arg_svalue (2);
+ region_model *model = cd.get_model ();
+ state_t old_state = sm_ctxt->get_state (stmt, fd_sval);
+
+ if (!address_sval->all_zeroes_p ())
+ {
+ region_model_manager *mgr = model->get_manager ();
+
+ /* We might have a union of various pointer types, rather than a
+ pointer type; cast to (void *) before dereferencing. */
+ address_sval = mgr->get_or_create_cast (ptr_type_node, address_sval);
+
+ const region *address_reg
+ = model->deref_rvalue (address_sval, cd.get_arg_tree (1),
+ cd.get_ctxt ());
+ const region *len_reg
+ = model->deref_rvalue (len_ptr_sval, cd.get_arg_tree (2),
+ cd.get_ctxt ());
+ const svalue *old_len_sval
+ = model->get_store_value (len_reg, cd.get_ctxt ());
+ tree len_ptr = cd.get_arg_tree (2);
+ tree star_len_ptr = build2 (MEM_REF, TREE_TYPE (TREE_TYPE (len_ptr)),
+ len_ptr,
+ build_int_cst (TREE_TYPE (len_ptr), 0));
+ old_len_sval = model->check_for_poison (old_len_sval,
+ star_len_ptr,
+ cd.get_ctxt ());
+ if (successful)
+ {
+ conjured_purge p (model, cd.get_ctxt ());
+ const region *old_sized_address_reg
+ = mgr->get_sized_region (address_reg,
+ NULL_TREE,
+ old_len_sval);
+ const svalue *new_addr_sval
+ = mgr->get_or_create_conjured_svalue (NULL_TREE,
+ stmt,
+ old_sized_address_reg,
+ p);
+ model->set_value (old_sized_address_reg, new_addr_sval,
+ cd.get_ctxt ());
+ const svalue *new_addr_len
+ = mgr->get_or_create_conjured_svalue (NULL_TREE,
+ stmt,
+ len_reg,
+ p);
+ model->set_value (len_reg, new_addr_len, cd.get_ctxt ());
+ }
+ }
+
+ /* We expect a stream socket in the "listening" state. */
+ if (!check_for_socket_fd (cd, successful, sm_ctxt, fd_sval, node, old_state))
+ return false;
+
+ if (old_state == m_start)
+ /* If we were in the start state, assume we had the expected state. */
+ sm_ctxt->set_next_state (cd.get_call_stmt (), fd_sval,
+ m_listening_stream_socket);
+ else if (old_state == m_stop)
+ {
+ /* No further complaints. */
+ }
+ else if (old_state != m_listening_stream_socket)
+ {
+ /* Complain about fncall on wrong type or in wrong phase. */
+ tree diag_arg = sm_ctxt->get_diagnostic_tree (fd_sval);
+ if (is_stream_socket_fd_p (old_state))
+ sm_ctxt->warn
+ (node, stmt, fd_sval,
+ make_unique<fd_phase_mismatch> (*this, diag_arg,
+ cd.get_fndecl_for_call (),
+ old_state,
+ EXPECTED_PHASE_CAN_ACCEPT));
+ else
+ sm_ctxt->warn
+ (node, stmt, fd_sval,
+ make_unique<fd_type_mismatch> (*this, diag_arg,
+ cd.get_fndecl_for_call (),
+ old_state,
+ EXPECTED_TYPE_STREAM_SOCKET));
+ if (successful)
+ return false;
+ }
+
+ if (successful)
+ {
+ /* Return new conjured FD in "connected" state. */
+ if (gimple_call_lhs (stmt))
+ {
+ conjured_purge p (model, cd.get_ctxt ());
+ region_model_manager *mgr = model->get_manager ();
+ const svalue *new_fd
+ = mgr->get_or_create_conjured_svalue (integer_type_node,
+ stmt,
+ cd.get_lhs_region (),
+ p);
+ if (!add_constraint_ge_zero (model, new_fd, cd.get_ctxt ()))
+ return false;
+ sm_ctxt->on_transition (node, stmt, new_fd,
+ m_start, m_connected_stream_socket);
+ model->set_value (cd.get_lhs_region (), new_fd, cd.get_ctxt ());
+ }
+ else
+ sm_ctxt->warn (node, stmt, NULL_TREE,
+ make_unique<fd_leak> (*this, NULL_TREE));
+ }
+ else
+ {
+ /* Return -1; set errno. */
+ model->update_for_int_cst_return (cd, -1, true);
+ model->set_errno (cd);
+ }
+
+ return true;
+}
+
+/* Update the model and fd state for an outcome of a call to "connect",
+ where SUCCESSFUL indicate which of the two outcomes.
+ Return true if the outcome is feasible, or false to reject it. */
+
+bool
+fd_state_machine::on_connect (const call_details &cd,
+ bool successful,
+ sm_context *sm_ctxt,
+ const extrinsic_state &ext_state) const
+{
+ const gcall *stmt = cd.get_call_stmt ();
+ engine *eng = ext_state.get_engine ();
+ const supergraph *sg = eng->get_supergraph ();
+ const supernode *node = sg->get_supernode_for_stmt (stmt);
+ const svalue *fd_sval = cd.get_arg_svalue (0);
+ region_model *model = cd.get_model ();
+ state_t old_state = sm_ctxt->get_state (stmt, fd_sval);
+
+ if (!check_for_new_socket_fd (cd, successful, sm_ctxt,
+ fd_sval, node, old_state,
+ EXPECTED_PHASE_CAN_CONNECT))
+ return false;
+
+ if (successful)
+ {
+ model->update_for_zero_return (cd, true);
+ state_t next_state = NULL;
+ if (old_state == m_new_stream_socket)
+ next_state = m_connected_stream_socket;
+ else if (old_state == m_new_datagram_socket)
+ /* It's legal to call connect on a datagram socket, potentially
+ more than once. We don't transition states for this. */
+ next_state = m_new_datagram_socket;
+ else if (old_state == m_new_unknown_socket)
+ next_state = m_stop;
+ else if (old_state == m_start)
+ next_state = m_stop;
+ else if (old_state == m_stop)
+ next_state = m_stop;
+ else
+ gcc_unreachable ();
+ sm_ctxt->set_next_state (cd.get_call_stmt (), fd_sval, next_state);
+ }
+ else
+ {
+ /* Return -1; set errno. */
+ model->update_for_int_cst_return (cd, -1, true);
+ model->set_errno (cd);
+ /* TODO: perhaps transition to a failed state, since the
+ portable way to handle a failed "connect" is to close
+ the socket and try again with a new socket. */
+ }
+
+ return true;
+}
+
void
fd_state_machine::on_condition (sm_context *sm_ctxt, const supernode *node,
const gimple *stmt, const svalue *lhs,
@@ -1215,7 +2191,9 @@ fd_state_machine::make_invalid_transitions_on_condition (
bool
fd_state_machine::can_purge_p (state_t s) const
{
- if (is_unchecked_fd_p (s) || is_valid_fd_p (s))
+ if (is_unchecked_fd_p (s)
+ || is_valid_fd_p (s)
+ || is_socket_fd_p (s))
return false;
else
return true;
@@ -1234,30 +2212,130 @@ make_fd_state_machine (logger *logger)
return new fd_state_machine (logger);
}
+static bool
+get_fd_state (region_model_context *ctxt,
+ sm_state_map **out_smap,
+ const fd_state_machine **out_sm,
+ unsigned *out_sm_idx,
+ std::unique_ptr<sm_context> *out_sm_context)
+{
+ if (!ctxt)
+ return false;
+
+ const state_machine *sm;
+ if (!ctxt->get_fd_map (out_smap, &sm, out_sm_idx, out_sm_context))
+ return false;
+
+ gcc_assert (sm);
+
+ *out_sm = (const fd_state_machine *)sm;
+ return true;
+}
+
/* Specialcase hook for handling pipe, for use by
region_model::impl_call_pipe::success::update_model. */
void
region_model::mark_as_valid_fd (const svalue *sval, region_model_context *ctxt)
{
- if (!ctxt)
+ sm_state_map *smap;
+ const fd_state_machine *fd_sm;
+ if (!get_fd_state (ctxt, &smap, &fd_sm, NULL, NULL))
return;
const extrinsic_state *ext_state = ctxt->get_ext_state ();
if (!ext_state)
return;
+ fd_sm->mark_as_valid_fd (this, smap, sval, *ext_state);
+}
+/* Specialcase hook for handling "socket", for use by
+ region_model::impl_call_socket::outcome_of_socket::update_model. */
+
+bool
+region_model::on_socket (const call_details &cd, bool successful)
+{
sm_state_map *smap;
- const state_machine *sm;
- unsigned sm_idx;
- if (!ctxt->get_fd_map (&smap, &sm, &sm_idx))
- return;
+ const fd_state_machine *fd_sm;
+ std::unique_ptr<sm_context> sm_ctxt;
+ if (!get_fd_state (cd.get_ctxt (), &smap, &fd_sm, NULL, &sm_ctxt))
+ return true;
+ const extrinsic_state *ext_state = cd.get_ctxt ()->get_ext_state ();
+ if (!ext_state)
+ return true;
- gcc_assert (smap);
- gcc_assert (sm);
+ return fd_sm->on_socket (cd, successful, sm_ctxt.get (), *ext_state);
+}
+
+/* Specialcase hook for handling "bind", for use by
+ region_model::impl_call_bind::outcome_of_bind::update_model. */
+
+bool
+region_model::on_bind (const call_details &cd, bool successful)
+{
+ sm_state_map *smap;
+ const fd_state_machine *fd_sm;
+ std::unique_ptr<sm_context> sm_ctxt;
+ if (!get_fd_state (cd.get_ctxt (), &smap, &fd_sm, NULL, &sm_ctxt))
+ return true;
+ const extrinsic_state *ext_state = cd.get_ctxt ()->get_ext_state ();
+ if (!ext_state)
+ return true;
+
+ return fd_sm->on_bind (cd, successful, sm_ctxt.get (), *ext_state);
+}
+
+/* Specialcase hook for handling "listen", for use by
+ region_model::impl_call_listen::outcome_of_listen::update_model. */
+
+bool
+region_model::on_listen (const call_details &cd, bool successful)
+{
+ sm_state_map *smap;
+ const fd_state_machine *fd_sm;
+ std::unique_ptr<sm_context> sm_ctxt;
+ if (!get_fd_state (cd.get_ctxt (), &smap, &fd_sm, NULL, &sm_ctxt))
+ return true;
+ const extrinsic_state *ext_state = cd.get_ctxt ()->get_ext_state ();
+ if (!ext_state)
+ return true;
+
+ return fd_sm->on_listen (cd, successful, sm_ctxt.get (), *ext_state);
+}
+
+/* Specialcase hook for handling "accept", for use by
+ region_model::impl_call_accept::outcome_of_accept::update_model. */
+
+bool
+region_model::on_accept (const call_details &cd, bool successful)
+{
+ sm_state_map *smap;
+ const fd_state_machine *fd_sm;
+ std::unique_ptr<sm_context> sm_ctxt;
+ if (!get_fd_state (cd.get_ctxt (), &smap, &fd_sm, NULL, &sm_ctxt))
+ return true;
+ const extrinsic_state *ext_state = cd.get_ctxt ()->get_ext_state ();
+ if (!ext_state)
+ return true;
+
+ return fd_sm->on_accept (cd, successful, sm_ctxt.get (), *ext_state);
+}
- const fd_state_machine &fd_sm = (const fd_state_machine &)*sm;
+/* Specialcase hook for handling "connect", for use by
+ region_model::impl_call_connect::outcome_of_connect::update_model. */
+
+bool
+region_model::on_connect (const call_details &cd, bool successful)
+{
+ sm_state_map *smap;
+ const fd_state_machine *fd_sm;
+ std::unique_ptr<sm_context> sm_ctxt;
+ if (!get_fd_state (cd.get_ctxt (), &smap, &fd_sm, NULL, &sm_ctxt))
+ return true;
+ const extrinsic_state *ext_state = cd.get_ctxt ()->get_ext_state ();
+ if (!ext_state)
+ return true;
- fd_sm.mark_as_valid_fd (this, smap, sval, *ext_state);
+ return fd_sm->on_connect (cd, successful, sm_ctxt.get (), *ext_state);
}
} // namespace ana
diff --git a/gcc/analyzer/sm-fd.dot b/gcc/analyzer/sm-fd.dot
index 5c9984a..da925b0 100644
--- a/gcc/analyzer/sm-fd.dot
+++ b/gcc/analyzer/sm-fd.dot
@@ -46,6 +46,29 @@ digraph "fd" {
/* State for a file descriptor that has been closed. */
closed;
+ /* States for FDs relating to socket APIs. */
+
+ /* Result of successful "socket" with SOCK_DGRAM. */
+ new_datagram_socket;
+ /* Result of successful "socket" with SOCK_STREAM. */
+ new_stream_socket;
+ /* Result of successful "socket" with unknown type. */
+ new_unknown_socket;
+
+ /* The above after a successful call to "bind". */
+ bound_datagram_socket;
+ bound_stream_socket;
+ bound_unknown_socket;
+
+ /* A bound socket after a successful call to "listen" (stream or unknown). */
+ listening_stream_socket;
+
+ /* (i) the new FD as a result of a succesful call to "accept" on a
+ listening socket (via a passive open), or
+ (ii) an active socket after a successful call to "connect"
+ (via an active open). */
+ connected_stream_socket;
+
/* State for a file descriptor that we do not want to track anymore . */
stop;
@@ -68,6 +91,14 @@ digraph "fd" {
valid_read_only -> closed [label="on 'close(X);'"];
valid_write_only -> closed [label="on 'close(X);'"];
constant_fd -> closed [label="on 'close(X);'"];
+ new_datagram_socket -> closed [label="on 'close(X);'"];
+ new_stream_socket -> closed [label="on 'close(X);'"];
+ new_unknown_socket -> closed [label="on 'close(X);'"];
+ bound_datagram_socket -> closed [label="on 'close(X);'"];
+ bound_stream_socket -> closed [label="on 'close(X);'"];
+ bound_unknown_socket -> closed [label="on 'close(X);'"];
+ listening_stream_socket -> closed [label="on 'close(X);'"];
+ connected_stream_socket -> closed [label="on 'close(X);'"];
closed -> stop [label="on 'close(X);':\nWarn('double close')"];
/* On "read". */
@@ -91,6 +122,31 @@ digraph "fd" {
/* On "pipe". */
start -> valid_read_write [label="when 'pipe()' succeeds"];
+ /* On "socket". */
+ start -> new_datagram_socket [label="when 'socket(..., SOCK_DGRAM, ...)' succeeds"];
+ start -> new_stream_socket [label="when 'socket(..., SOCK_STREAM, ...)' succeeds"];
+ start -> new_unknown_socket [label="when 'socket(..., ..., ...)' succeeds"];
+
+ /* On "bind". */
+ start -> bound_unknown_socket [label="when 'bind(X, ...)' succeeds"];
+ new_stream_socket -> bound_stream_socket [label="when 'bind(X, ...)' succeeds"];
+ new_datagram_socket -> bound_datagram_socket [label="when 'bind(X, ...)' succeeds"];
+ new_unknown_socket -> bound_unknown_socket [label="when 'bind(X, ...)' succeeds"];
+
+ /* On "listen". */
+ start -> listening_stream_socket [label="when 'listen(X, ...)' succeeds"];
+ bound_stream_socket -> listening_stream_socket [label="when 'listen(X, ...)' succeeds"];
+ bound_unknown_socket -> listening_stream_socket [label="when 'listen(X, ...)' succeeds"];
+
+ /* On "accept". */
+ start -> connected_stream_socket [label="when 'accept(OTHER, ...)' succeeds on a listening_stream_socket"];
+
+ /* On "connect". */
+ new_stream_socket -> connected_stream_socket [label="when 'connect(X, ...)' succeeds"];
+ new_datagram_socket -> new_datagram_socket [label="when 'connect(X, ...)' succeeds"];
+ new_unknown_socket -> stop [label="when 'connect(X, ...)' succeeds"];
+ start -> stop [label="when 'connect(X, ...)' succeeds"];
+
/* on_condition. */
unchecked_read_write -> valid_read_write [label="on 'X >= 0'"];
unchecked_read_only -> valid_read_only [label="on 'X >= 0'"];
@@ -106,4 +162,12 @@ digraph "fd" {
valid_read_write -> stop [label="on leak:\nWarn('leak')"];
valid_read_only -> stop [label="on leak:\nWarn('leak')"];
valid_write_only -> stop [label="on leak:\nWarn('leak')"];
+ new_datagram_socket -> stop [label="on leak:\nWarn('leak')"];
+ new_stream_socket -> stop [label="on leak:\nWarn('leak')"];
+ new_unknown_socket -> stop [label="on leak:\nWarn('leak')"];
+ bound_datagram_socket -> stop [label="on leak:\nWarn('leak')"];
+ bound_stream_socket -> stop [label="on leak:\nWarn('leak')"];
+ bound_unknown_socket -> stop [label="on leak:\nWarn('leak')"];
+ listening_stream_socket -> stop [label="on leak:\nWarn('leak')"];
+ connected_stream_socket -> stop [label="on leak:\nWarn('leak')"];
}
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 9d73394..057439a 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -453,6 +453,8 @@ Objective-C and Objective-C++ Dialects}.
-Wno-analyzer-fd-access-mode-mismatch @gol
-Wno-analyzer-fd-double-close @gol
-Wno-analyzer-fd-leak @gol
+-Wno-analyzer-fd-phase-mismatch @gol
+-Wno-analyzer-fd-type-mismatch @gol
-Wno-analyzer-fd-use-after-close @gol
-Wno-analyzer-fd-use-without-check @gol
-Wno-analyzer-file-leak @gol
@@ -9899,6 +9901,8 @@ Enabling this option effectively enables the following warnings:
-Wanalyzer-fd-access-mode-mismatch @gol
-Wanalyzer-fd-double-close @gol
-Wanalyzer-fd-leak @gol
+-Wanalyzer-fd-phase-mismatch @gol
+-Wanalyzer-fd-type-mismatch @gol
-Wanalyzer-fd-use-after-close @gol
-Wanalyzer-fd-use-without-check @gol
-Wanalyzer-file-leak @gol
@@ -10077,6 +10081,33 @@ open file descriptor is leaked.
See @uref{https://cwe.mitre.org/data/definitions/775.html, CWE-775: Missing Release of File Descriptor or Handle after Effective Lifetime}.
+@item -Wno-analyzer-fd-phase-mismatch
+@opindex Wanalyzer-fd-phase-mismatch
+@opindex Wno-analyzer-fd-phase-mismatch
+This warning requires @option{-fanalyzer}, which enables it; use
+@option{-Wno-analyzer-fd-phase-mismatch}
+to disable it.
+
+This diagnostic warns for paths through code in which an operation is
+attempted in the wrong phase of a file descriptor's lifetime.
+For example, it will warn on attempts to call @code{accept} on a stream
+socket that has not yet had @code{listen} successfully called on it.
+
+See @uref{https://cwe.mitre.org/data/definitions/666.html, CWE-666: Operation on Resource in Wrong Phase of Lifetime}.
+
+@item -Wno-analyzer-fd-type-mismatch
+@opindex Wanalyzer-fd-type-mismatch
+@opindex Wno-analyzer-fd-type-mismatch
+This warning requires @option{-fanalyzer}, which enables it; use
+@option{-Wno-analyzer-fd-type-mismatch}
+to disable it.
+
+This diagnostic warns for paths through code in which an
+operation is attempted on the wrong type of file descriptor.
+For example, it will warn on attempts to use socket operations
+on a file descriptor obtained via @code{open}, or when attempting
+to use a stream socket operation on a datagram socket.
+
@item -Wno-analyzer-fd-use-after-close
@opindex Wanalyzer-fd-use-after-close
@opindex Wno-analyzer-fd-use-after-close
@@ -10616,6 +10647,7 @@ of the following functions for working with file descriptors:
@item @code{pipe}, and @code{pipe2}
@item @code{read}
@item @code{write}
+@item @code{socket}, @code{bind}, @code{listen}, @code{accept}, and @code{connect}
@end itemize
of the following functions for working with @code{<stdio.h>} streams:
diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-accept.c b/gcc/testsuite/gcc.dg/analyzer/fd-accept.c
new file mode 100644
index 0000000..36cc7af
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/fd-accept.c
@@ -0,0 +1,69 @@
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+#include <errno.h>
+#include "analyzer-decls.h"
+
+int test_accept (int fd, struct sockaddr *addr, socklen_t *addrlen)
+{
+ return accept (fd, addr, addrlen);
+}
+
+void test_accept_leak_no_lhs (int fd, struct sockaddr *addr, socklen_t *addrlen)
+{
+ accept (fd, addr, addrlen); /* { dg-warning "leak of file descriptor" } */
+}
+
+void test_accept_leak_with_lhs (int fd, struct sockaddr *addr, socklen_t *addrlen)
+{
+ int newfd = accept (fd, addr, addrlen); /* { dg-message "socket created here" } */
+} /* { dg-warning "leak of file descriptor 'newfd'" } */
+
+int test_accept_null_addr (int fd)
+{
+ return accept (fd, NULL, 0);
+}
+
+int test_accept_uninit_addrlen (int fd)
+{
+ struct sockaddr_storage addr;
+ socklen_t addr_len;
+ return accept (fd, (struct sockaddr *)&addr, &addr_len); /* { dg-warning "use of uninitialized value 'addr_len'" } */
+}
+
+int test_accept_writes_to_addr_and_len (int fd)
+{
+ struct sockaddr_storage addr;
+ socklen_t addr_len = sizeof (addr);
+ __analyzer_eval (addr_len == sizeof (addr)); /* { dg-warning "TRUE" } */
+ int newfd = accept (fd, (struct sockaddr *)&addr, &addr_len);
+ if (newfd == -1)
+ return newfd;
+ /* Check that the analyzer considers addr and addr_len to
+ have been written to. */
+ __analyzer_eval (((char *)&addr)[0]); /* { dg-warning "UNKNOWN" } */
+ __analyzer_eval (addr_len == sizeof (addr)); /* { dg-warning "UNKNOWN" } */
+ return newfd;
+}
+
+void test_accept_on_new_datagram_socket (void)
+{
+ int fd = socket (AF_UNIX, SOCK_DGRAM, 0);
+ if (fd == -1)
+ return;
+ accept (fd, NULL, NULL); /* { dg-message "'accept' on datagram socket file descriptor 'fd' \\\[-Wanalyzer-fd-type-mismatch\\\]" "warning" } */
+ /* { dg-message "'accept' expects a stream socket file descriptor but 'fd' is a datagram socket" "final event" { target *-*-* } .-1 } */
+ close (fd);
+}
+
+int test_accept_on_accept (int fd_a)
+{
+ int fd_b = accept (fd_a, NULL, 0);
+ if (fd_b == -1)
+ return -1;
+
+ int fd_c = accept (fd_b, NULL, 0); /* { dg-warning "'accept' on file descriptor 'fd_b' in wrong phase \\\[-Wanalyzer-fd-phase-mismatch\\\]" "warning" } */
+ /* { dg-message "'accept' expects a listening stream socket file descriptor but 'fd_b' is connected" "final event" { target *-*-* } .-1 } */
+
+ return fd_b;
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-bind.c b/gcc/testsuite/gcc.dg/analyzer/fd-bind.c
new file mode 100644
index 0000000..6f91bc4
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/fd-bind.c
@@ -0,0 +1,74 @@
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+#include <errno.h>
+#include "analyzer-decls.h"
+
+void test_bind (int fd, const char *sockname)
+{
+ struct sockaddr_un addr;
+ memset (&addr, 0, sizeof (addr));
+ addr.sun_family = AF_UNIX;
+ strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1);
+ if (bind (fd, (struct sockaddr *)&addr, sizeof (addr)) == -1)
+ __analyzer_dump_path (); /* { dg-message "path" } */
+ else
+ __analyzer_dump_path (); /* { dg-message "path" } */
+}
+
+void test_null_bind (int fd)
+{
+ errno = 0;
+ int result = bind (fd, NULL, 0);
+ __analyzer_eval (result == -1); /* { dg-warning "TRUE" } */
+ __analyzer_eval (errno > 0); /* { dg-warning "TRUE" } */
+}
+
+void test_double_bind (int fd, const char *sockname)
+{
+ struct sockaddr_un addr;
+ memset (&addr, 0, sizeof (addr));
+ addr.sun_family = AF_UNIX;
+ strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1);
+ bind (fd, (struct sockaddr *)&addr, sizeof (addr));
+ bind (fd, (struct sockaddr *)&addr, sizeof (addr)); /* { dg-warning "'bind' on file descriptor 'fd' in wrong phase \\\[-Wanalyzer-fd-phase-mismatch\\\]" "warning" } */
+ /* { dg-message "'bind' expects a new socket file descriptor but 'fd' has already been bound" "final event" { target *-*-* } .-1 } */
+}
+
+int test_uninit_addr (int fd, const char *sockname)
+{
+ struct sockaddr_un addr;
+ return bind (fd, (struct sockaddr *)&addr, sizeof (addr));
+ // TODO: complain about uninit addr.
+}
+
+void test_bind_after_connect (int fd, const char *sockname,
+ const struct sockaddr *caddr, socklen_t caddrlen)
+{
+ if (connect (fd, caddr, caddrlen) == -1)
+ return;
+
+ struct sockaddr_un addr;
+ memset (&addr, 0, sizeof (addr));
+ addr.sun_family = AF_UNIX;
+ strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1);
+ bind (fd, (struct sockaddr *)&addr, sizeof (addr));
+ /* TODO: we don't warn for this; after the plain "connect" we're
+ in the stop state. */
+}
+
+void test_bind_after_accept (int fd, const char *sockname)
+{
+ int afd = accept (fd, NULL, NULL);
+ if (afd == -1)
+ return;
+
+ struct sockaddr_un addr;
+ memset (&addr, 0, sizeof (addr));
+ addr.sun_family = AF_UNIX;
+ strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1);
+ bind (afd, (struct sockaddr *)&addr, sizeof (addr)); /* { dg-warning "'bind' on file descriptor 'afd' in wrong phase \\\[-Wanalyzer-fd-phase-mismatch\\\]" "warning" } */
+ /* { dg-message "'bind' expects a new socket file descriptor but 'afd' is already connected" "final event" { target *-*-* } .-1 } */
+
+ close (afd);
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-connect.c b/gcc/testsuite/gcc.dg/analyzer/fd-connect.c
new file mode 100644
index 0000000..1ab54d0
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/fd-connect.c
@@ -0,0 +1,46 @@
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+#include <errno.h>
+#include "analyzer-decls.h"
+
+int test_connect (int sockfd, const struct sockaddr *addr,
+ socklen_t addrlen)
+{
+ return connect (sockfd, addr, addrlen);
+}
+
+void test_null_connect (int fd)
+{
+ errno = 0;
+ int result = connect (fd, NULL, 0);
+ __analyzer_eval (result == -1); /* { dg-warning "TRUE" } */
+ __analyzer_eval (errno > 0); /* { dg-warning "TRUE" } */
+}
+
+int test_uninit_addr (int fd, const char *sockname)
+{
+ struct sockaddr_un addr;
+ return connect (fd, (struct sockaddr *)&addr, sizeof (addr));
+ // TODO: complain about uninit addr.
+}
+
+void test_connect_after_bind (const char *sockname,
+ const struct sockaddr *baddr, socklen_t baddrlen,
+ const struct sockaddr *caddr, socklen_t caddrlen)
+{
+ int fd = socket (AF_UNIX, SOCK_STREAM, 0); /* { dg-message "stream socket created here" } */
+ if (fd == -1)
+ return;
+
+ if (bind (fd, baddr, baddrlen) == -1)
+ {
+ close (fd);
+ return;
+ }
+
+ connect (fd, caddr, caddrlen); /* { dg-warning "'connect' on file descriptor 'fd' in wrong phase" "warning" } */
+ /* { dg-message "'connect' expects a new socket file descriptor but 'fd' is bound" "final event" { target *-*-* } .-1 } */
+
+ close (fd);
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-datagram-socket.c b/gcc/testsuite/gcc.dg/analyzer/fd-datagram-socket.c
new file mode 100644
index 0000000..045bdfa
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/fd-datagram-socket.c
@@ -0,0 +1,108 @@
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+#include <errno.h>
+#include "analyzer-decls.h"
+
+void test_leak_socket (void)
+{
+ int fd = socket (AF_UNIX, SOCK_DGRAM, 0); /* { dg-message "datagram socket created here" } */
+} /* { dg-warning "leak of file descriptor 'fd'" } */
+
+void test_leak_socket_no_lhs (void)
+{
+ socket (AF_UNIX, SOCK_DGRAM, 0); /* { dg-warning "leak of file descriptor" } */
+}
+
+void test_close_unchecked_socket (void)
+{
+ int fd = socket (AF_UNIX, SOCK_DGRAM, 0);
+ close (fd);
+}
+
+void test_close_checked_socket (void)
+{
+ int fd = socket (AF_UNIX, SOCK_DGRAM, 0);
+ if (fd == -1)
+ return;
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-new-datagram-socket'" } */
+ close (fd);
+}
+
+void test_leak_checked_socket (void)
+{
+ int fd = socket (AF_UNIX, SOCK_DGRAM, 0); /* { dg-message "datagram socket created here" } */
+ if (fd == -1) /* { dg-warning "leak of file descriptor 'fd'" } */
+ return;
+ // TODO: strange location for leak message
+}
+
+void test_bind (const char *sockname)
+{
+ struct sockaddr_un addr;
+ int fd = socket (AF_UNIX, SOCK_DGRAM, 0);
+ if (fd == -1)
+ return;
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-new-datagram-socket'" } */
+ memset (&addr, 0, sizeof (addr));
+ addr.sun_family = AF_UNIX;
+ strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1);
+ bind (fd, (struct sockaddr *)&addr, sizeof (addr));
+ close (fd);
+}
+
+void test_bind_on_unchecked_socket (const char *sockname)
+{
+ struct sockaddr_un addr;
+ int fd = socket (AF_UNIX, SOCK_DGRAM, 0); /* { dg-message "when 'socket' fails" } */
+ memset (&addr, 0, sizeof (addr));
+ addr.sun_family = AF_UNIX;
+ strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1);
+ bind (fd, (struct sockaddr *)&addr, sizeof (addr)); /* { dg-warning "'bind' on possibly invalid file descriptor 'fd'" } */
+ close (fd);
+}
+
+void test_leak_of_bound_socket (const char *sockname)
+{
+ struct sockaddr_un addr;
+ int fd = socket (AF_UNIX, SOCK_DGRAM, 0); /* { dg-message "datagram socket created here" } */
+ if (fd == -1)
+ return;
+ memset (&addr, 0, sizeof (addr));
+ addr.sun_family = AF_UNIX;
+ strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1);
+ bind (fd, (struct sockaddr *)&addr, sizeof (addr)); /* { dg-warning "leak of file descriptor 'fd'" } */
+}
+
+void test_listen_on_datagram_socket_without_bind (void)
+{
+ int fd = socket (AF_UNIX, SOCK_DGRAM, 0); /* { dg-message "datagram socket created here" } */
+ if (fd == -1)
+ return;
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-new-datagram-socket'" } */
+ listen (fd, 5); /* { dg-warning "'listen' on datagram socket file descriptor 'fd' \\\[-Wanalyzer-fd-type-mismatch\\\]" "warning" } */
+ /* { dg-message "'listen' expects a stream socket file descriptor but 'fd' is a datagram socket" "final event" { target *-*-* } .-1 } */
+ close (fd);
+}
+
+void test_listen_on_datagram_socket_with_bind (const char *sockname)
+{
+ int fd = socket (AF_UNIX, SOCK_DGRAM, 0); /* { dg-message "datagram socket created here" } */
+ if (fd == -1)
+ return;
+
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-new-datagram-socket'" } */
+
+ struct sockaddr_un addr;
+ memset (&addr, 0, sizeof (addr));
+ addr.sun_family = AF_UNIX;
+ strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1);
+ if (bind (fd, (struct sockaddr *)&addr, sizeof (addr)) == -1) /* { dg message "datagram socket bound here" } */
+ {
+ close (fd);
+ return;
+ }
+ listen (fd, 5); /* { dg-warning "'listen' on datagram socket file descriptor 'fd' \\\[-Wanalyzer-fd-type-mismatch\\\]" "warning" } */
+ /* { dg-message "'listen' expects a stream socket file descriptor but 'fd' is a datagram socket" "final event" { target *-*-* } .-1 } */
+ close (fd);
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-glibc-byte-stream-connection-server.c b/gcc/testsuite/gcc.dg/analyzer/fd-glibc-byte-stream-connection-server.c
new file mode 100644
index 0000000..1ff9028
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/fd-glibc-byte-stream-connection-server.c
@@ -0,0 +1,133 @@
+/* Example from glibc manual (16.9.7). */
+/* { dg-additional-options "-Wno-analyzer-too-complex" } */
+
+#include <stdio.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+
+#define PORT 5555
+#define MAXMSG 512
+
+int
+make_socket (uint16_t port)
+{
+ int sock;
+ struct sockaddr_in name;
+
+ /* Create the socket. */
+ sock = socket (PF_INET, SOCK_STREAM, 0);
+ if (sock < 0)
+ {
+ perror ("socket");
+ exit (EXIT_FAILURE);
+ }
+
+ /* Give the socket a name. */
+ name.sin_family = AF_INET;
+ name.sin_port = htons (port);
+ name.sin_addr.s_addr = htonl (INADDR_ANY);
+ if (bind (sock, (struct sockaddr *) &name, sizeof (name)) < 0)
+ {
+ perror ("bind");
+ exit (EXIT_FAILURE);
+ }
+
+ return sock;
+}
+
+int
+read_from_client (int filedes)
+{
+ char buffer[MAXMSG];
+ int nbytes;
+
+ nbytes = read (filedes, buffer, MAXMSG);
+ if (nbytes < 0)
+ {
+ /* Read error. */
+ perror ("read");
+ exit (EXIT_FAILURE);
+ }
+ else if (nbytes == 0)
+ /* End-of-file. */
+ return -1;
+ else
+ {
+ /* Data read. */
+ fprintf (stderr, "Server: got message: `%s'\n", buffer);
+ return 0;
+ }
+}
+
+int
+main (void)
+{
+ int sock;
+ fd_set active_fd_set, read_fd_set;
+ int i;
+ struct sockaddr_in clientname;
+ socklen_t size;
+
+ /* Create the socket and set it up to accept connections. */
+ sock = make_socket (PORT);
+ if (listen (sock, 1) < 0)
+ {
+ perror ("listen");
+ exit (EXIT_FAILURE);
+ }
+
+ /* Initialize the set of active sockets. */
+ FD_ZERO (&active_fd_set);
+ FD_SET (sock, &active_fd_set);
+
+ while (1)
+ {
+ /* Block until input arrives on one or more active sockets. */
+ read_fd_set = active_fd_set;
+ if (select (FD_SETSIZE, &read_fd_set, NULL, NULL, NULL) < 0)
+ {
+ perror ("select");
+ exit (EXIT_FAILURE);
+ }
+
+ /* Service all the sockets with input pending. */
+ for (i = 0; i < FD_SETSIZE; ++i)
+ if (FD_ISSET (i, &read_fd_set))
+ {
+ if (i == sock)
+ {
+ /* Connection request on original socket. */
+ int new;
+ size = sizeof (clientname);
+ new = accept (sock,
+ (struct sockaddr *) &clientname,
+ &size);
+ if (new < 0)
+ {
+ perror ("accept");
+ exit (EXIT_FAILURE);
+ }
+ fprintf (stderr,
+ "Server: connect from host %s, port %hd.\n",
+ inet_ntoa (clientname.sin_addr),
+ ntohs (clientname.sin_port));
+ FD_SET (new, &active_fd_set);
+ }
+ else
+ {
+ /* Data arriving on an already-connected socket. */
+ if (read_from_client (i) < 0)
+ {
+ close (i);
+ FD_CLR (i, &active_fd_set);
+ }
+ }
+ }
+ }
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-glibc-byte-stream-socket.c b/gcc/testsuite/gcc.dg/analyzer/fd-glibc-byte-stream-socket.c
new file mode 100644
index 0000000..f96da81
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/fd-glibc-byte-stream-socket.c
@@ -0,0 +1,62 @@
+/* Example from glibc manual (16.9.6). */
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netdb.h>
+
+#define PORT 5555
+#define MESSAGE "Yow!!! Are we having fun yet?!?"
+#define SERVERHOST "www.gnu.org"
+
+void
+write_to_server (int filedes)
+{
+ int nbytes;
+
+ nbytes = write (filedes, MESSAGE, strlen (MESSAGE) + 1);
+ if (nbytes < 0)
+ {
+ perror ("write");
+ exit (EXIT_FAILURE);
+ }
+}
+
+
+int
+main (void)
+{
+ extern void init_sockaddr (struct sockaddr_in *name,
+ const char *hostname,
+ uint16_t port);
+ int sock;
+ struct sockaddr_in servername;
+
+ /* Create the socket. */
+ sock = socket (PF_INET, SOCK_STREAM, 0);
+ if (sock < 0)
+ {
+ perror ("socket (client)");
+ exit (EXIT_FAILURE);
+ }
+
+ /* Connect to the server. */
+ init_sockaddr (&servername, SERVERHOST, PORT);
+ if (0 > connect (sock,
+ (struct sockaddr *) &servername,
+ sizeof (servername)))
+ {
+ perror ("connect (client)");
+ exit (EXIT_FAILURE);
+ }
+
+ /* Send data to the server. */
+ write_to_server (sock);
+ close (sock);
+ exit (EXIT_SUCCESS);
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-glibc-datagram-client.c b/gcc/testsuite/gcc.dg/analyzer/fd-glibc-datagram-client.c
new file mode 100644
index 0000000..888c751
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/fd-glibc-datagram-client.c
@@ -0,0 +1,56 @@
+/* Example from the glibc manual (16.10.4). */
+
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include "fd-glibc-make_named_socket.h"
+
+#define SERVER "/tmp/serversocket"
+#define CLIENT "/tmp/mysocket"
+#define MAXMSG 512
+#define MESSAGE "Yow!!! Are we having fun yet?!?"
+
+int
+main (void)
+{
+ int sock;
+ char message[MAXMSG];
+ struct sockaddr_un name;
+ size_t size;
+ int nbytes;
+
+ /* Make the socket. */
+ sock = make_named_socket (CLIENT);
+
+ /* Initialize the server socket address. */
+ name.sun_family = AF_LOCAL;
+ strcpy (name.sun_path, SERVER);
+ size = strlen (name.sun_path) + sizeof (name.sun_family);
+
+ /* Send the datagram. */
+ nbytes = sendto (sock, MESSAGE, strlen (MESSAGE) + 1, 0,
+ (struct sockaddr *) & name, size);
+ if (nbytes < 0)
+ {
+ perror ("sendto (client)");
+ exit (EXIT_FAILURE);
+ }
+
+ /* Wait for a reply. */
+ nbytes = recvfrom (sock, message, MAXMSG, 0, NULL, 0);
+ if (nbytes < 0)
+ {
+ perror ("recfrom (client)");
+ exit (EXIT_FAILURE);
+ }
+
+ /* Print a diagnostic message. */
+ fprintf (stderr, "Client: got message: %s\n", message);
+
+ /* Clean up. */
+ remove (CLIENT);
+ close (sock);
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-glibc-datagram-socket.c b/gcc/testsuite/gcc.dg/analyzer/fd-glibc-datagram-socket.c
new file mode 100644
index 0000000..b8b6876
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/fd-glibc-datagram-socket.c
@@ -0,0 +1,52 @@
+/* Example from glibc manual (16.10.3). */
+
+#include <stdio.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+#include "fd-glibc-make_named_socket.h"
+
+#define SERVER "/tmp/serversocket"
+#define MAXMSG 512
+
+int
+main (void)
+{
+ int sock;
+ char message[MAXMSG];
+ struct sockaddr_un name;
+ socklen_t size;
+ int nbytes;
+
+ /* Remove the filename first, it’s ok if the call fails */
+ unlink (SERVER);
+
+ /* Make the socket, then loop endlessly. */
+ sock = make_named_socket (SERVER);
+ while (1)
+ {
+ /* Wait for a datagram. */
+ size = sizeof (name);
+ nbytes = recvfrom (sock, message, MAXMSG, 0,
+ (struct sockaddr *) & name, &size);
+ if (nbytes < 0)
+ {
+ perror ("recfrom (server)");
+ exit (EXIT_FAILURE);
+ }
+
+ /* Give a diagnostic message. */
+ fprintf (stderr, "Server: got message: %s\n", message);
+
+ /* Bounce the message back to the sender. */
+ nbytes = sendto (sock, message, nbytes, 0,
+ (struct sockaddr *) & name, size);
+ if (nbytes < 0)
+ {
+ perror ("sendto (server)");
+ exit (EXIT_FAILURE);
+ }
+ }
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-glibc-make_named_socket.h b/gcc/testsuite/gcc.dg/analyzer/fd-glibc-make_named_socket.h
new file mode 100644
index 0000000..bdb6de0
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/fd-glibc-make_named_socket.h
@@ -0,0 +1,47 @@
+/* Example of Local-Namespace Sockets from the glibc manual (16.5.3). */
+
+#include <stddef.h>
+#include <stdio.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+int
+make_named_socket (const char *filename)
+{
+ struct sockaddr_un name;
+ int sock;
+ size_t size;
+
+ /* Create the socket. */
+ sock = socket (PF_LOCAL, SOCK_DGRAM, 0);
+ if (sock < 0)
+ {
+ perror ("socket");
+ exit (EXIT_FAILURE);
+ }
+
+ /* Bind a name to the socket. */
+ name.sun_family = AF_LOCAL;
+ strncpy (name.sun_path, filename, sizeof (name.sun_path));
+ name.sun_path[sizeof (name.sun_path) - 1] = '\0';
+
+ /* The size of the address is
+ the offset of the start of the filename,
+ plus its length (not including the terminating null byte).
+ Alternatively you can just do:
+ size = SUN_LEN (&name);
+ */
+ size = (offsetof (struct sockaddr_un, sun_path)
+ + strlen (name.sun_path));
+
+ if (bind (sock, (struct sockaddr *) &name, size) < 0)
+ {
+ perror ("bind");
+ exit (EXIT_FAILURE);
+ }
+
+ return sock;
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-listen.c b/gcc/testsuite/gcc.dg/analyzer/fd-listen.c
new file mode 100644
index 0000000..1f54a8f
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/fd-listen.c
@@ -0,0 +1,63 @@
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+#include <errno.h>
+#include "analyzer-decls.h"
+
+int test_listen (int fd, int backlog)
+{
+ return listen (fd, backlog);
+}
+
+/* Some systems seem to allow repeated calls to listen. */
+
+void test_double_listen (int fd, int backlog)
+{
+ listen (fd, backlog);
+ listen (fd, backlog);
+}
+
+void test_listen_before_bind (int fd, const char *sockname)
+{
+ if (listen (fd, 5) == -1) /* { dg-message "stream socket marked as passive here via 'listen'" } */
+ return;
+
+ struct sockaddr_un addr;
+ memset (&addr, 0, sizeof (addr));
+ addr.sun_family = AF_UNIX;
+ strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1);
+ bind (fd, (struct sockaddr *)&addr, sizeof (addr)); /* { dg-warning "'bind' on file descriptor 'fd' in wrong phase" "warning" } */
+ /* { dg-message "'bind' expects a new socket file descriptor but 'fd' is already listening" "final event" { target *-*-* } .-1 } */
+}
+
+void test_listen_on_unchecked_bind (int fd, const char *sockname)
+{
+ struct sockaddr_un addr;
+ memset (&addr, 0, sizeof (addr));
+ addr.sun_family = AF_UNIX;
+ strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1);
+ bind (fd, (struct sockaddr *)&addr, sizeof (addr)); /* { dg-message "when 'bind' fails" } */
+ listen (fd, 5); /* { dg-warning "'listen' on file descriptor 'fd' in wrong phase" "warning" } */
+ /* { dg-message "'listen' expects a bound stream socket file descriptor but 'fd' has not yet been bound" "final event" { target *-*-* } .-1 } */
+ close (fd);
+}
+
+void test_listen_on_new_datagram_socket (void)
+{
+ int fd = socket (AF_UNIX, SOCK_DGRAM, 0);
+ if (fd == -1)
+ return;
+ listen (fd, 5); /* { dg-message "'listen' on datagram socket file descriptor 'fd' \\\[-Wanalyzer-fd-type-mismatch\\\]" "warning" } */
+ /* { dg-message "'listen' expects a stream socket file descriptor but 'fd' is a datagram socket" "final event" { target *-*-* } .-1 } */
+ close (fd);
+}
+
+void test_listed_on_connected_socket (int fd)
+{
+ int afd = accept (fd, NULL, 0);
+ if (afd == -1)
+ return;
+ listen (afd, 5); /* { dg-warning "'listen' on file descriptor 'afd' in wrong phase" "warning" } */
+ /* { dg-message "'listen' expects a bound stream socket file descriptor but 'afd' is connected" "final event" { target *-*-* } .-1 } */
+ close (afd);
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-manpage-getaddrinfo-client.c b/gcc/testsuite/gcc.dg/analyzer/fd-manpage-getaddrinfo-client.c
new file mode 100644
index 0000000..d9c3ff0
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/fd-manpage-getaddrinfo-client.c
@@ -0,0 +1,122 @@
+/* Example from getaddrinfo.3 manpage, which has this license:
+
+Copyright (c) 2007, 2008 Michael Kerrisk <mtk.manpages@gmail.com>
+and Copyright (c) 2006 Ulrich Drepper <drepper@redhat.com>
+A few pieces of an earlier version remain:
+Copyright 2000, Sam Varshavchik <mrsam@courier-mta.com>
+
+Permission is granted to make and distribute verbatim copies of this
+manual provided the copyright notice and this permission notice are
+preserved on all copies.
+
+Permission is granted to copy and distribute modified versions of this
+manual under the conditions for verbatim copying, provided that the
+entire resulting derived work is distributed under the terms of a
+permission notice identical to this one.
+
+Since the Linux kernel and libraries are constantly changing, this
+manual page may be incorrect or out-of-date. The author(s) assume no
+responsibility for errors or omissions, or for damages resulting from
+the use of the information contained herein. The author(s) may not
+have taken the same level of care in the production of this manual,
+which is licensed free of charge, as they might when working
+professionally.
+
+Formatted or processed versions of this manual, if unaccompanied by
+the source, must acknowledge the copyright and authors of this work.
+*/
+
+/* { dg-additional-options "-Wno-analyzer-too-complex" } */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+
+#define BUF_SIZE 500
+
+int
+main(int argc, char *argv[])
+{
+ struct addrinfo hints;
+ struct addrinfo *result, *rp;
+ int sfd, s;
+ size_t len;
+ ssize_t nread;
+ char buf[BUF_SIZE];
+
+ if (argc < 3) {
+ fprintf(stderr, "Usage: %s host port msg...\n", argv[0]);
+ exit(EXIT_FAILURE);
+ }
+
+ /* Obtain address(es) matching host/port. */
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */
+ hints.ai_socktype = SOCK_DGRAM; /* Datagram socket */
+ hints.ai_flags = 0;
+ hints.ai_protocol = 0; /* Any protocol */
+
+ s = getaddrinfo(argv[1], argv[2], &hints, &result);
+ if (s != 0) {
+ fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s));
+ exit(EXIT_FAILURE);
+ }
+
+ /* getaddrinfo() returns a list of address structures.
+ Try each address until we successfully connect(2).
+ If socket(2) (or connect(2)) fails, we (close the socket
+ and) try the next address. */
+
+ for (rp = result; rp != NULL; rp = rp->ai_next) {
+ sfd = socket(rp->ai_family, rp->ai_socktype,
+ rp->ai_protocol);
+ if (sfd == -1)
+ continue;
+
+ if (connect(sfd, rp->ai_addr, rp->ai_addrlen) != -1)
+ break; /* Success */
+
+ close(sfd);
+ }
+
+ freeaddrinfo(result); /* No longer needed */
+
+ if (rp == NULL) { /* No address succeeded */
+ fprintf(stderr, "Could not connect\n");
+ exit(EXIT_FAILURE);
+ }
+
+ /* Send remaining command-line arguments as separate
+ datagrams, and read responses from server. */
+
+ for (int j = 3; j < argc; j++) {
+ len = strlen(argv[j]) + 1;
+ /* +1 for terminating null byte */
+
+ if (len > BUF_SIZE) {
+ fprintf(stderr,
+ "Ignoring long message in argument %d\n", j);
+ continue;
+ }
+
+ if (write(sfd, argv[j], len) != len) {
+ fprintf(stderr, "partial/failed write\n");
+ exit(EXIT_FAILURE);
+ }
+
+ nread = read(sfd, buf, BUF_SIZE);
+ if (nread == -1) {
+ perror("read");
+ exit(EXIT_FAILURE);
+ }
+
+ printf("Received %zd bytes: %s\n", nread, buf);
+ }
+
+ exit(EXIT_SUCCESS);
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-mappage-getaddrinfo-server.c b/gcc/testsuite/gcc.dg/analyzer/fd-mappage-getaddrinfo-server.c
new file mode 100644
index 0000000..66398e8
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/fd-mappage-getaddrinfo-server.c
@@ -0,0 +1,119 @@
+/* Example from getaddrinfo.3 manpage, which has this license:
+
+Copyright (c) 2007, 2008 Michael Kerrisk <mtk.manpages@gmail.com>
+and Copyright (c) 2006 Ulrich Drepper <drepper@redhat.com>
+A few pieces of an earlier version remain:
+Copyright 2000, Sam Varshavchik <mrsam@courier-mta.com>
+
+Permission is granted to make and distribute verbatim copies of this
+manual provided the copyright notice and this permission notice are
+preserved on all copies.
+
+Permission is granted to copy and distribute modified versions of this
+manual under the conditions for verbatim copying, provided that the
+entire resulting derived work is distributed under the terms of a
+permission notice identical to this one.
+
+Since the Linux kernel and libraries are constantly changing, this
+manual page may be incorrect or out-of-date. The author(s) assume no
+responsibility for errors or omissions, or for damages resulting from
+the use of the information contained herein. The author(s) may not
+have taken the same level of care in the production of this manual,
+which is licensed free of charge, as they might when working
+professionally.
+
+Formatted or processed versions of this manual, if unaccompanied by
+the source, must acknowledge the copyright and authors of this work.
+*/
+
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <netdb.h>
+
+#define BUF_SIZE 500
+
+int
+main(int argc, char *argv[])
+{
+ struct addrinfo hints;
+ struct addrinfo *result, *rp;
+ int sfd, s;
+ struct sockaddr_storage peer_addr;
+ socklen_t peer_addr_len;
+ ssize_t nread;
+ char buf[BUF_SIZE];
+
+ if (argc != 2) {
+ fprintf(stderr, "Usage: %s port\n", argv[0]);
+ exit(EXIT_FAILURE);
+ }
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */
+ hints.ai_socktype = SOCK_DGRAM; /* Datagram socket */
+ hints.ai_flags = AI_PASSIVE; /* For wildcard IP address */
+ hints.ai_protocol = 0; /* Any protocol */
+ hints.ai_canonname = NULL;
+ hints.ai_addr = NULL;
+ hints.ai_next = NULL;
+
+ s = getaddrinfo(NULL, argv[1], &hints, &result);
+ if (s != 0) {
+ fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s));
+ exit(EXIT_FAILURE);
+ }
+
+ /* getaddrinfo() returns a list of address structures.
+ Try each address until we successfully bind(2).
+ If socket(2) (or bind(2)) fails, we (close the socket
+ and) try the next address. */
+
+ for (rp = result; rp != NULL; rp = rp->ai_next) {
+ sfd = socket(rp->ai_family, rp->ai_socktype,
+ rp->ai_protocol);
+ if (sfd == -1)
+ continue;
+
+ if (bind(sfd, rp->ai_addr, rp->ai_addrlen) == 0)
+ break; /* Success */
+
+ close(sfd);
+ }
+
+ freeaddrinfo(result); /* No longer needed */
+
+ if (rp == NULL) { /* No address succeeded */
+ fprintf(stderr, "Could not bind\n");
+ exit(EXIT_FAILURE);
+ }
+
+ /* Read datagrams and echo them back to sender. */
+
+ for (;;) {
+ peer_addr_len = sizeof(peer_addr);
+ nread = recvfrom(sfd, buf, BUF_SIZE, 0,
+ (struct sockaddr *) &peer_addr, &peer_addr_len);
+ if (nread == -1)
+ continue; /* Ignore failed request */
+
+ char host[NI_MAXHOST], service[NI_MAXSERV];
+
+ s = getnameinfo((struct sockaddr *) &peer_addr,
+ peer_addr_len, host, NI_MAXHOST,
+ service, NI_MAXSERV, NI_NUMERICSERV);
+ if (s == 0)
+ printf("Received %zd bytes from %s:%s\n",
+ nread, host, service);
+ else
+ fprintf(stderr, "getnameinfo: %s\n", gai_strerror(s));
+
+ if (sendto(sfd, buf, nread, 0,
+ (struct sockaddr *) &peer_addr,
+ peer_addr_len) != nread)
+ fprintf(stderr, "Error sending response\n");
+ }
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-socket-meaning.c b/gcc/testsuite/gcc.dg/analyzer/fd-socket-meaning.c
new file mode 100644
index 0000000..5bfb57f
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/fd-socket-meaning.c
@@ -0,0 +1,21 @@
+/* { dg-additional-options "-fanalyzer-verbose-state-changes" } */
+
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+#include <errno.h>
+
+void test_leak_unchecked_stream_socket (void)
+{
+ int fd = socket (AF_UNIX, SOCK_STREAM, 0); /* { dg-message "meaning: \\{verb: 'acquire', noun: 'resource'\\}" } */
+} /* { dg-warning "leak of file descriptor 'fd'" } */
+
+void test_leak_unchecked_datagram_socket (void)
+{
+ int fd = socket (AF_UNIX, SOCK_DGRAM, 0); /* { dg-message "meaning: \\{verb: 'acquire', noun: 'resource'\\}" } */
+} /* { dg-warning "leak of file descriptor 'fd'" } */
+
+void test_leak_unchecked_socket (int type)
+{
+ int fd = socket (AF_UNIX, type, 0); /* { dg-message "meaning: \\{verb: 'acquire', noun: 'resource'\\}" } */
+} /* { dg-warning "leak of file descriptor 'fd'" } */
diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-socket-misuse.c b/gcc/testsuite/gcc.dg/analyzer/fd-socket-misuse.c
new file mode 100644
index 0000000..4ff08d5
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/fd-socket-misuse.c
@@ -0,0 +1,98 @@
+/* Various operations done on sockets in the wrong phase. */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+#include <errno.h>
+#include "analyzer-decls.h"
+
+void test_read_on_new_socket (void *buf)
+{
+ int fd = socket (AF_UNIX, SOCK_STREAM, 0); /* { dg-message "stream socket created here" } */
+ if (fd == -1)
+ return;
+ read (fd, buf, 1); /* { dg-warning "'read' on file descriptor 'fd' in wrong phase \\\[-Wanalyzer-fd-phase-mismatch\\\]" "warning" } */
+ /* { dg-message "'read' expects a stream socket to be connected via 'accept' but 'fd' has not yet been bound" "final event" { target *-*-* } .-1 } */
+ close (fd);
+}
+
+void test_read_on_bound_socket (int fd, const char *sockname, void *buf)
+{
+ struct sockaddr_un addr;
+ memset (&addr, 0, sizeof (addr));
+ addr.sun_family = AF_UNIX;
+ strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1);
+ if (bind (fd, (struct sockaddr *)&addr, sizeof (addr)) == -1)
+ return;
+ /* This could be a datagram socket, so we shouldn't complain here. */
+ read (fd, buf, 1);
+}
+
+void test_read_on_listening_socket (int fd, void *buf)
+{
+ if (listen (fd, 5) == -1) /* { dg-message "stream socket marked as passive here via 'listen'" } */
+ return;
+ read (fd, buf, 1); /* { dg-message "'read' on file descriptor 'fd' in wrong phase" "warning" } */
+ /* { dg-message "'read' expects a stream socket to be connected via the return value of 'accept' but 'fd' is listening; wrong file descriptor\\\?" "final event" { target *-*-* } .-1 } */
+}
+
+void test_bind_on_non_socket (const char *filename, const char *sockname)
+{
+ int fd = open (filename, O_RDONLY);
+ if (fd == -1)
+ return;
+
+ struct sockaddr_un addr;
+ memset (&addr, 0, sizeof (addr));
+ addr.sun_family = AF_UNIX;
+ strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1);
+ int result = bind (fd, (struct sockaddr *)&addr, sizeof (addr)); /* { dg-warning "'bind' on non-socket file descriptor 'fd' \\\[-Wanalyzer-fd-type-mismatch\\\]" "warning" } */
+ /* { dg-message "'bind' expects a socket file descriptor but 'fd' is not a socket" "final event" { target *-*-* } .-1 } */
+ __analyzer_eval (result == -1); /* { dg-warning "TRUE" } */
+
+ close (fd);
+}
+
+void test_passive_open_read_on_wrong_socket (int sfd)
+{
+ int cfd = accept (sfd, NULL, NULL);
+ write (sfd, "hello", 6); /* { dg-warning "'write' on file descriptor 'sfd' in wrong phase" "warning" } */
+ /* { dg-message "'write' expects a stream socket to be connected via the return value of 'accept' but 'sfd' is listening; wrong file descriptor\\\?" "final event" { target *-*-* } .-1 } */
+ close (cfd);
+}
+
+void test_listen_on_new_stream_socket (void)
+{
+ int fd = socket (AF_UNIX, SOCK_STREAM, 0);
+ if (fd == -1)
+ return;
+ listen (fd, 5); /* { dg-message "'listen' on file descriptor 'fd' in wrong phase" "warning" } */
+ /* { dg-message "'listen' expects a bound stream socket file descriptor but 'fd' has not yet been bound" "final event" { target *-*-* } .-1 } */
+ close (fd);
+}
+
+void test_accept_on_new_stream_socket (void)
+{
+ int fd = socket (AF_UNIX, SOCK_STREAM, 0);
+ if (fd == -1)
+ return;
+ accept (fd, NULL, NULL); /* { dg-message "'accept' on file descriptor 'fd' in wrong phase" "warning" } */
+ /* { dg-message "'accept' expects a listening stream socket file descriptor but 'fd' has not yet been bound" "final event" { target *-*-* } .-1 } */
+ close (fd);
+}
+
+void test_listen_before_bind (int fd, const char *sockname)
+{
+ if (listen (fd, 5) == -1) /* { dg-message "stream socket marked as passive here via 'listen'" } */
+ return;
+
+ struct sockaddr_un addr;
+ memset (&addr, 0, sizeof (addr));
+ addr.sun_family = AF_UNIX;
+ strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1);
+ bind (fd, (struct sockaddr *)&addr, sizeof (addr)); /* { dg-warning "'bind' on file descriptor 'fd' in wrong phase" "warning" } */
+ /* { dg-message "'bind' expects a new socket file descriptor but 'fd' is already listening" "final event" { target *-*-* } .-1 } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-stream-socket-active-open.c b/gcc/testsuite/gcc.dg/analyzer/fd-stream-socket-active-open.c
new file mode 100644
index 0000000..7fde0ef
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/fd-stream-socket-active-open.c
@@ -0,0 +1,74 @@
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+#include <errno.h>
+#include "analyzer-decls.h"
+
+void test_active_open_from_scratch (const char *sockname, void *buf)
+{
+ errno = 0;
+ int fd = socket (AF_UNIX, SOCK_STREAM, 0);
+ if (fd == -1)
+ {
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-invalid'" } */
+ __analyzer_eval (errno > 0); /* { dg-warning "TRUE" } */
+ return;
+ }
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-new-stream-socket'" } */
+ __analyzer_eval (errno == 0); /* { dg-warning "TRUE" } */
+
+ struct sockaddr_un addr;
+ memset (&addr, 0, sizeof (addr));
+ addr.sun_family = AF_UNIX;
+ strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1);
+
+ errno = 0;
+ if (connect (fd, (struct sockaddr *)&addr, sizeof (addr)) == -1)
+ {
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-new-stream-socket'" } */
+ __analyzer_eval (errno > 0); /* { dg-warning "TRUE" } */
+ close (fd);
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-closed'" } */
+ return;
+ }
+
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-connected-stream-socket'" } */
+ __analyzer_eval (errno == 0); /* { dg-warning "TRUE" } */
+ __analyzer_eval (fd >= 0); /* { dg-warning "TRUE" } */
+
+ write (fd, "hello", 6);
+ read (fd, buf, 100);
+
+ close (fd);
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-closed'" } */
+}
+
+void test_active_open_from_connect (int fd, const char *sockname, void *buf)
+{
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'start'" } */
+
+ struct sockaddr_un addr;
+ memset (&addr, 0, sizeof (addr));
+ addr.sun_family = AF_UNIX;
+ strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1);
+
+ errno = 0;
+ if (connect (fd, (struct sockaddr *)&addr, sizeof (addr)) == -1)
+ {
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-new-unknown-socket'" } */
+ __analyzer_eval (errno > 0); /* { dg-warning "TRUE" } */
+ close (fd);
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-closed'" } */
+ return;
+ }
+
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-stop'" } */
+ __analyzer_eval (errno == 0); /* { dg-warning "TRUE" } */
+ __analyzer_eval (fd >= 0); /* { dg-warning "TRUE" } */
+
+ write (fd, "hello", 6);
+ read (fd, buf, 100);
+
+ close (fd);
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-stop'" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-stream-socket-passive-open.c b/gcc/testsuite/gcc.dg/analyzer/fd-stream-socket-passive-open.c
new file mode 100644
index 0000000..c31e5b5
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/fd-stream-socket-passive-open.c
@@ -0,0 +1,197 @@
+/* Verify the various states when performing a passive open,
+ either from scratch, or when various phases are assumed to already
+ be done. */
+
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+#include <errno.h>
+#include "analyzer-decls.h"
+
+void test_passive_open_from_scratch (const char *sockname, void *buf)
+{
+ struct sockaddr_un addr;
+ int afd;
+ errno = 0;
+ int fd = socket (AF_UNIX, SOCK_STREAM, 0);
+ if (fd == -1)
+ {
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-invalid'" } */
+ __analyzer_eval (errno > 0); /* { dg-warning "TRUE" } */
+ return;
+ }
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-new-stream-socket'" } */
+ __analyzer_eval (errno == 0); /* { dg-warning "TRUE" } */
+ memset (&addr, 0, sizeof (addr));
+ addr.sun_family = AF_UNIX;
+ strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1);
+ errno = 0;
+ if (bind (fd, (struct sockaddr *)&addr, sizeof (addr)) == -1)
+ {
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-new-stream-socket'" } */
+ __analyzer_eval (errno > 0); /* { dg-warning "TRUE" } */
+ close (fd);
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-closed'" } */
+ return;
+ }
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-bound-stream-socket'" } */
+ __analyzer_eval (errno == 0); /* { dg-warning "TRUE" } */
+ __analyzer_eval (fd >= 0); /* { dg-warning "TRUE" } */
+ if (listen (fd, 5) == -1)
+ {
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-bound-stream-socket'" } */
+ __analyzer_eval (errno > 0); /* { dg-warning "TRUE" } */
+ close (fd);
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-closed'" } */
+ return;
+ }
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-listening-stream-socket'" } */
+ __analyzer_eval (errno == 0); /* { dg-warning "TRUE" } */
+ __analyzer_eval (fd >= 0); /* { dg-warning "TRUE" } */
+ afd = accept (fd, NULL, NULL);
+ if (afd == -1)
+ {
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-listening-stream-socket'" } */
+ __analyzer_eval (errno > 0); /* { dg-warning "TRUE" } */
+ close (fd);
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-closed'" } */
+ return;
+ }
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-listening-stream-socket'" } */
+ __analyzer_dump_state ("file-descriptor", afd); /* { dg-warning "state: 'fd-connected-stream-socket'" } */
+ __analyzer_eval (errno == 0); /* { dg-warning "TRUE" } */
+ __analyzer_eval (fd >= 0); /* { dg-warning "TRUE" } */
+ __analyzer_eval (afd >= 0); /* { dg-warning "TRUE" } */
+
+ write (afd, "hello", 6);
+ read (afd, buf, 100);
+
+ close (afd);
+ close (fd);
+ __analyzer_dump_state ("file-descriptor", afd); /* { dg-warning "state: 'fd-closed'" } */
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-closed'" } */
+}
+
+void test_passive_open_from_bind (int fd, const char *sockname, void *buf)
+{
+ struct sockaddr_un addr;
+ int afd;
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'start'" } */
+ memset (&addr, 0, sizeof (addr));
+ addr.sun_family = AF_UNIX;
+ strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1);
+ errno = 0;
+ if (bind (fd, (struct sockaddr *)&addr, sizeof (addr)) == -1)
+ {
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-new-unknown-socket'" } */
+ __analyzer_eval (errno > 0); /* { dg-warning "TRUE" } */
+ close (fd);
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-closed'" } */
+ return;
+ }
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-bound-unknown-socket'" } */
+ __analyzer_eval (errno == 0); /* { dg-warning "TRUE" } */
+ __analyzer_eval (fd >= 0); /* { dg-warning "TRUE" } */
+ if (listen (fd, 5) == -1)
+ {
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-bound-unknown-socket'" } */
+ __analyzer_eval (errno > 0); /* { dg-warning "TRUE" } */
+ close (fd);
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-closed'" } */
+ return;
+ }
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-listening-stream-socket'" } */
+ __analyzer_eval (errno == 0); /* { dg-warning "TRUE" } */
+ __analyzer_eval (fd >= 0); /* { dg-warning "TRUE" } */
+ afd = accept (fd, NULL, NULL);
+ if (afd == -1)
+ {
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-listening-stream-socket'" } */
+ __analyzer_eval (errno > 0); /* { dg-warning "TRUE" } */
+ close (fd);
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-closed'" } */
+ return;
+ }
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-listening-stream-socket'" } */
+ __analyzer_dump_state ("file-descriptor", afd); /* { dg-warning "state: 'fd-connected-stream-socket'" } */
+ __analyzer_eval (errno == 0); /* { dg-warning "TRUE" } */
+ __analyzer_eval (fd >= 0); /* { dg-warning "TRUE" } */
+ __analyzer_eval (afd >= 0); /* { dg-warning "TRUE" } */
+
+ write (afd, "hello", 6);
+ read (afd, buf, 100);
+
+ close (afd);
+ close (fd);
+ __analyzer_dump_state ("file-descriptor", afd); /* { dg-warning "state: 'fd-closed'" } */
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-closed'" } */
+}
+
+void test_passive_open_from_listen (int fd, void *buf)
+{
+ int afd;
+ errno = 0;
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'start'" } */
+ if (listen (fd, 5) == -1)
+ {
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-bound-stream-socket'" } */
+ __analyzer_eval (errno > 0); /* { dg-warning "TRUE" } */
+ close (fd);
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-closed'" } */
+ return;
+ }
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-listening-stream-socket'" } */
+ __analyzer_eval (errno == 0); /* { dg-warning "TRUE" } */
+ __analyzer_eval (fd >= 0); /* { dg-warning "TRUE" } */
+ afd = accept (fd, NULL, NULL);
+ if (afd == -1)
+ {
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-listening-stream-socket'" } */
+ __analyzer_eval (errno > 0); /* { dg-warning "TRUE" } */
+ close (fd);
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-closed'" } */
+ return;
+ }
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-listening-stream-socket'" } */
+ __analyzer_dump_state ("file-descriptor", afd); /* { dg-warning "state: 'fd-connected-stream-socket'" } */
+ __analyzer_eval (errno == 0); /* { dg-warning "TRUE" } */
+ __analyzer_eval (fd >= 0); /* { dg-warning "TRUE" } */
+ __analyzer_eval (afd >= 0); /* { dg-warning "TRUE" } */
+
+ write (afd, "hello", 6);
+ read (afd, buf, 100);
+
+ close (afd);
+ close (fd);
+ __analyzer_dump_state ("file-descriptor", afd); /* { dg-warning "state: 'fd-closed'" } */
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-closed'" } */
+}
+
+void test_passive_open_from_accept (int fd, void *buf)
+{
+ int afd;
+ errno = 0;
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'start'" } */
+ afd = accept (fd, NULL, NULL);
+ if (afd == -1)
+ {
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-listening-stream-socket'" } */
+ __analyzer_eval (errno > 0); /* { dg-warning "TRUE" } */
+ close (fd);
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-closed'" } */
+ return;
+ }
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-listening-stream-socket'" } */
+ __analyzer_dump_state ("file-descriptor", afd); /* { dg-warning "state: 'fd-connected-stream-socket'" } */
+ __analyzer_eval (errno == 0); /* { dg-warning "TRUE" } */
+ __analyzer_eval (fd >= 0); /* { dg-warning "TRUE" } */
+ __analyzer_eval (afd >= 0); /* { dg-warning "TRUE" } */
+
+ write (afd, "hello", 6);
+ read (afd, buf, 100);
+
+ close (afd);
+ close (fd);
+ __analyzer_dump_state ("file-descriptor", afd); /* { dg-warning "state: 'fd-closed'" } */
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-closed'" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-stream-socket.c b/gcc/testsuite/gcc.dg/analyzer/fd-stream-socket.c
new file mode 100644
index 0000000..3a292d0
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/fd-stream-socket.c
@@ -0,0 +1,98 @@
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+#include <errno.h>
+#include "analyzer-decls.h"
+
+void test_leak_socket (void)
+{
+ int fd = socket (AF_UNIX, SOCK_STREAM, 0); /* { dg-message "stream socket created here" } */
+} /* { dg-warning "leak of file descriptor 'fd'" } */
+
+void test_leak_socket_no_lhs (void)
+{
+ socket (AF_UNIX, SOCK_STREAM, 0); /* { dg-warning "leak of file descriptor" } */
+}
+
+void test_close_unchecked_socket (void)
+{
+ int fd = socket (AF_UNIX, SOCK_STREAM, 0);
+ close (fd);
+}
+
+void test_close_checked_socket (void)
+{
+ int fd = socket (AF_UNIX, SOCK_STREAM, 0);
+ if (fd == -1)
+ return;
+ close (fd);
+}
+
+void test_leak_checked_socket (void)
+{
+ int fd = socket (AF_UNIX, SOCK_STREAM, 0); /* { dg-message "stream socket created here" } */
+ if (fd == -1) /* { dg-warning "leak of file descriptor 'fd'" } */
+ return;
+ // TODO: strange location for leak message
+}
+
+void test_bind_on_checked_socket (const char *sockname)
+{
+ struct sockaddr_un addr;
+ int fd = socket (AF_UNIX, SOCK_STREAM, 0);
+ if (fd == -1)
+ return;
+ memset (&addr, 0, sizeof (addr));
+ addr.sun_family = AF_UNIX;
+ strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1);
+ bind (fd, (struct sockaddr *)&addr, sizeof (addr));
+ close (fd);
+}
+
+void test_bind_on_unchecked_socket (const char *sockname)
+{
+ struct sockaddr_un addr;
+ int fd = socket (AF_UNIX, SOCK_STREAM, 0); /* { dg-message "when 'socket' fails" } */
+ memset (&addr, 0, sizeof (addr));
+ addr.sun_family = AF_UNIX;
+ strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1);
+ bind (fd, (struct sockaddr *)&addr, sizeof (addr)); /* { dg-warning "'bind' on possibly invalid file descriptor 'fd'" } */
+ close (fd);
+}
+
+void test_leak_of_bound_socket (const char *sockname)
+{
+ struct sockaddr_un addr;
+ int fd = socket (AF_UNIX, SOCK_STREAM, 0); /* { dg-message "stream socket created here" } */
+ if (fd == -1)
+ return;
+ memset (&addr, 0, sizeof (addr));
+ addr.sun_family = AF_UNIX;
+ strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1);
+ bind (fd, (struct sockaddr *)&addr, sizeof (addr)); /* { dg-warning "leak of file descriptor 'fd'" } */
+}
+
+void test_listen_without_bind (void)
+{
+ int fd = socket (AF_UNIX, SOCK_STREAM, 0); /* { dg-message "stream socket created here" } */
+ if (fd == -1)
+ return;
+ listen (fd, 5); /* { dg-warning "'listen' on file descriptor 'fd' in wrong phase" "warning" } */
+ /* { dg-message "'listen' expects a bound stream socket file descriptor but 'fd' has not yet been bound" "final event" { target *-*-* } .-1 } */
+ close (fd);
+}
+
+void test_listen_on_unchecked_bind (const char *sockname)
+{
+ struct sockaddr_un addr;
+ int fd = socket (AF_UNIX, SOCK_STREAM, 0); /* { dg-message "stream socket created here" } */
+ if (fd == -1)
+ return;
+ memset (&addr, 0, sizeof (addr));
+ addr.sun_family = AF_UNIX;
+ strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1);
+ bind (fd, (struct sockaddr *)&addr, sizeof (addr)); /* { dg-message "when 'bind' fails" } */
+ listen (fd, 5); /* { dg-warning "'listen' on file descriptor 'fd' in wrong phase" "warning" } */
+ /* { dg-message "'listen' expects a bound stream socket file descriptor but 'fd' has not yet been bound" "final event" { target *-*-* } .-1 } */
+ close (fd);
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-symbolic-socket.c b/gcc/testsuite/gcc.dg/analyzer/fd-symbolic-socket.c
new file mode 100644
index 0000000..83400c1
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/fd-symbolic-socket.c
@@ -0,0 +1,98 @@
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+#include <errno.h>
+#include "analyzer-decls.h"
+
+void test_leak_socket (int type)
+{
+ int fd = socket (AF_UNIX, type, 0); /* { dg-message "socket created here" } */
+} /* { dg-warning "leak of file descriptor 'fd'" } */
+
+void test_leak_socket_no_lhs (int type)
+{
+ socket (AF_UNIX, type, 0); /* { dg-warning "leak of file descriptor" } */
+}
+
+void test_close_unchecked_socket (int type)
+{
+ int fd = socket (AF_UNIX, type, 0);
+ close (fd);
+}
+
+void test_close_checked_socket (int type)
+{
+ int fd = socket (AF_UNIX, type, 0);
+ if (fd == -1)
+ return;
+ close (fd);
+}
+
+void test_leak_checked_socket (int type)
+{
+ int fd = socket (AF_UNIX, type, 0); /* { dg-message "socket created here" } */
+ if (fd == -1) /* { dg-warning "leak of file descriptor 'fd'" } */
+ return;
+ // TODO: strange location for leak message
+}
+
+void test_bind_on_checked_socket (int type, const char *sockname)
+{
+ struct sockaddr_un addr;
+ int fd = socket (AF_UNIX, type, 0);
+ if (fd == -1)
+ return;
+ memset (&addr, 0, sizeof (addr));
+ addr.sun_family = AF_UNIX;
+ strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1);
+ bind (fd, (struct sockaddr *)&addr, sizeof (addr));
+ close (fd);
+}
+
+void test_bind_on_unchecked_socket (int type, const char *sockname)
+{
+ struct sockaddr_un addr;
+ int fd = socket (AF_UNIX, type, 0); /* { dg-message "when 'socket' fails" } */
+ memset (&addr, 0, sizeof (addr));
+ addr.sun_family = AF_UNIX;
+ strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1);
+ bind (fd, (struct sockaddr *)&addr, sizeof (addr)); /* { dg-warning "'bind' on possibly invalid file descriptor 'fd'" } */
+ close (fd);
+}
+
+void test_leak_of_bound_socket (int type, const char *sockname)
+{
+ struct sockaddr_un addr;
+ int fd = socket (AF_UNIX, type, 0); /* { dg-message "socket created here" } */
+ if (fd == -1)
+ return;
+ memset (&addr, 0, sizeof (addr));
+ addr.sun_family = AF_UNIX;
+ strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1);
+ bind (fd, (struct sockaddr *)&addr, sizeof (addr)); /* { dg-warning "leak of file descriptor 'fd'" } */
+}
+
+void test_listen_without_bind (int type)
+{
+ int fd = socket (AF_UNIX, type, 0);
+ if (fd == -1)
+ return;
+ listen (fd, 5); /* { dg-warning "'listen' on file descriptor 'fd' in wrong phase" } */
+ /* { dg-message "'listen' expects a bound stream socket file descriptor but 'fd' has not yet been bound" "msg" { target *-*-* } .-1 } */
+ close (fd);
+}
+
+void test_listen_on_unchecked_bind (int type, const char *sockname)
+{
+ struct sockaddr_un addr;
+ int fd = socket (AF_UNIX, type, 0);
+ if (fd == -1)
+ return;
+ memset (&addr, 0, sizeof (addr));
+ addr.sun_family = AF_UNIX;
+ strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1);
+ bind (fd, (struct sockaddr *)&addr, sizeof (addr)); /* { dg-message "when 'bind' fails" } */
+ listen (fd, 5); /* { dg-warning "'listen' on file descriptor 'fd' in wrong phase" "warning" } */
+ /* { dg-message "'listen' expects a bound stream socket file descriptor but 'fd' has not yet been bound" "msg" { target *-*-* } .-1 } */
+ close (fd);
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/pr104369-1.c b/gcc/testsuite/gcc.dg/analyzer/pr104369-1.c
index d3b3241..c05137b 100644
--- a/gcc/testsuite/gcc.dg/analyzer/pr104369-1.c
+++ b/gcc/testsuite/gcc.dg/analyzer/pr104369-1.c
@@ -1,5 +1,5 @@
-/* { dg-additional-options "--param analyzer-max-enodes-per-program-point=10" } */
-// TODO: remove need for this option
+/* { dg-additional-options "-Wno-analyzer-too-complex -Wno-analyzer-fd-leak" } */
+// TODO: remove need for these options
typedef __SIZE_TYPE__ size_t;
#define NULL ((void *)0)
diff --git a/gcc/testsuite/gcc.dg/analyzer/pr104369-2.c b/gcc/testsuite/gcc.dg/analyzer/pr104369-2.c
index 57dc9ca..93d9987 100644
--- a/gcc/testsuite/gcc.dg/analyzer/pr104369-2.c
+++ b/gcc/testsuite/gcc.dg/analyzer/pr104369-2.c
@@ -1,3 +1,6 @@
+/* { dg-additional-options "-Wno-analyzer-fd-leak" } */
+// TODO: remove need for this option
+
typedef __SIZE_TYPE__ size_t;
#define NULL ((void *)0)
#define POLLIN 0x001