public inbox for gcc-patches@gcc.gnu.org
 help / color / mirror / Atom feed
* [PATCH] c, analyzer: support named constants in analyzer [PR106302]
@ 2022-10-31 19:07 David Malcolm
  2022-11-08  3:02 ` [PATCH] analyzer: add warnings relating to sockets [PR106140] David Malcolm
  2022-11-08  3:06 ` PING: Re: [PATCH] c, analyzer: support named constants in analyzer [PR106302] David Malcolm
  0 siblings, 2 replies; 12+ messages in thread
From: David Malcolm @ 2022-10-31 19:07 UTC (permalink / raw)
  To: gcc-patches, Joseph Myers; +Cc: Marek Polacek, David Malcolm

The analyzer's file-descriptor state machine tracks the access mode of
opened files, so that it can emit -Wanalyzer-fd-access-mode-mismatch.

To do this, its symbolic execution needs to "know" the values of the
constants "O_RDONLY", "O_WRONLY", and "O_ACCMODE".  Currently
analyzer/sm-fd.cc simply uses these values directly from the build-time
header files, but these are the values on the host, not those from the
target, which could be different (PR analyzer/106302).

In an earlier discussion of this issue:
  https://gcc.gnu.org/pipermail/gcc/2022-June/238954.html
we talked about adding a target hook for this.

However, I've also been experimenting with extending the fd state
machine to track sockets (PR analyzer/106140).  For this, it's useful to
"know" the values of the constants "SOCK_STREAM" and "SOCK_DGRAM".
Unfortunately, these seem to have many arbitrary differences from target
to target.

For example: Linux/glibc general has SOCK_STREAM == 1, SOCK_DGRAM == 2,
as does AIX, but annoyingly, e.g. Linux on MIPS has them the other way
around.

It seems to me that as the analyzer grows more ambitious modeling of the
behavior of APIs (perhaps via plugins) it's more likely that the
analyzer will need to know the values of named constants, which might
not even exist on the host.

For example, at LPC it was suggested to me that -fanalyzer could check
rules about memory management inside the Linux kernel (probably via a
plugin), but doing so involves a bunch of GFP_* flags (see PR 107472).

So rather than trying to capture all this knowledge in a target hook,
this patch attempts to get at named constant values from the user's
source code.

The patch adds an interface for frontends to call into the analyzer as
the translation unit finishes.  The analyzer can then call back into the
frontend to ask about the values of the named constants it cares about
whilst the frontend's data structures are still around.

The patch implements this for the C frontend, which looks up the names
by looking for named CONST_DECLs (which handles enum values).  Failing
that, it attempts to look up the values of macros but only the simplest
cases are supported (a non-traditional macro with a single CPP_NUMBER
token).  It does this by building a buffer containing the macro
definition and rerunning a lexer on it.

The analyzer gracefully handles the cases where named values aren't
found (such as anything more complicated than described above).

The patch ports the analyzer to use this mechanism for "O_RDONLY",
"O_WRONLY", and "O_ACCMODE".  I have successfully tested my socket patch
to also use this for "SOCK_STREAM" and "SOCK_DGRAM", so the technique
seems to work.

Successfully bootstrapped & regrtested on x86_64-pc-linux-gnu.

Are the C frontend parts OK for trunk?

Thanks
Dave

gcc/ChangeLog:
	PR analyzer/106302
	* Makefile.in (ANALYZER_OBJS): Add analyzer/analyzer-language.o.
	(GTFILES): Add analyzer/analyzer-language.cc.

gcc/analyzer/ChangeLog:
	PR analyzer/106302
	* analyzer-language.cc: New file.
	* analyzer-language.h: New file.
	* analyzer.h (get_stashed_constant_by_name): New decl.
	(log_stashed_constants): New decl.
	* engine.cc (impl_run_checkers): Call log_stashed_constants.
	* region-model-impl-calls.cc
	(region_model::impl_call_analyzer_dump_named_constant): New.
	* region-model.cc (region_model::on_stmt_pre): Handle
	__analyzer_dump_named_constant.
	* region-model.h
	(region_model::impl_call_analyzer_dump_named_constant): New decl.
	* sm-fd.cc (fd_state_machine::m_O_ACCMODE): New.
	(fd_state_machine::m_O_RDONLY): New.
	(fd_state_machine::m_O_WRONLY): New.
	(fd_state_machine::fd_state_machine): Initialize the new fields.
	(fd_state_machine::get_access_mode_from_flag): Use the new fields,
	rather than using the host values.

gcc/c/ChangeLog:
	PR analyzer/106302
	* c-parser.cc: Include "analyzer/analyzer-language.h" and "toplev.h".
	(class ana::c_translation_unit): New.
	(c_parser_translation_unit): Call ana::on_finish_translation_unit.

gcc/ChangeLog:
	* doc/analyzer.texi: Document __analyzer_dump_named_constant.

gcc/testsuite/ChangeLog:
	* gcc.dg/analyzer/analyzer-decls.h
	(__analyzer_dump_named_constant): New decl.
	* gcc.dg/analyzer/fd-4.c (void): Likewise.
	(O_ACCMODE): Define.
	* gcc.dg/analyzer/fd-access-mode-enum.c: New test, based on .
	* gcc.dg/analyzer/fd-5.c: ...this.  Rename to...
	* gcc.dg/analyzer/fd-access-mode-macros.c: ...this.
	(O_ACCMODE): Define.
	* gcc.dg/analyzer/fd-access-mode-target-headers.c: New test, also
	based on fd-5.c.
	(test_sm_fd_constants): New.
	* gcc.dg/analyzer/fd-dup-1.c (O_ACCMODE): Define.
	* gcc.dg/analyzer/named-constants-via-enum.c: New test.
	* gcc.dg/analyzer/named-constants-via-macros-2.c: New test.
	* gcc.dg/analyzer/named-constants-via-macros.c: New test.

Signed-off-by: David Malcolm <dmalcolm@redhat.com>
---
 gcc/Makefile.in                               |   2 +
 gcc/analyzer/analyzer-language.cc             | 110 ++++++++++++++++++
 gcc/analyzer/analyzer-language.h              |  48 ++++++++
 gcc/analyzer/analyzer.h                       |   3 +
 gcc/analyzer/engine.cc                        |   1 +
 gcc/analyzer/region-model-impl-calls.cc       |  28 +++++
 gcc/analyzer/region-model.cc                  |   4 +
 gcc/analyzer/region-model.h                   |   2 +
 gcc/analyzer/sm-fd.cc                         |  30 +++--
 gcc/c/c-parser.cc                             |  91 +++++++++++++++
 gcc/doc/analyzer.texi                         |  17 +++
 .../gcc.dg/analyzer/analyzer-decls.h          |   3 +
 gcc/testsuite/gcc.dg/analyzer/fd-4.c          |   1 +
 .../gcc.dg/analyzer/fd-access-mode-enum.c     |  60 ++++++++++
 .../{fd-5.c => fd-access-mode-macros.c}       |   1 +
 .../analyzer/fd-access-mode-target-headers.c  |  56 +++++++++
 gcc/testsuite/gcc.dg/analyzer/fd-dup-1.c      |   1 +
 .../analyzer/named-constants-via-enum.c       |  20 ++++
 .../analyzer/named-constants-via-macros-2.c   |  15 +++
 .../analyzer/named-constants-via-macros.c     |  19 +++
 20 files changed, 502 insertions(+), 10 deletions(-)
 create mode 100644 gcc/analyzer/analyzer-language.cc
 create mode 100644 gcc/analyzer/analyzer-language.h
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/fd-access-mode-enum.c
 rename gcc/testsuite/gcc.dg/analyzer/{fd-5.c => fd-access-mode-macros.c} (98%)
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/fd-access-mode-target-headers.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/named-constants-via-enum.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/named-constants-via-macros-2.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/named-constants-via-macros.c

diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index f672e6ea549..4539934cb03 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -1249,6 +1249,7 @@ C_COMMON_OBJS = c-family/c-common.o c-family/c-cppbuiltin.o c-family/c-dump.o \
 ANALYZER_OBJS = \
 	analyzer/analysis-plan.o \
 	analyzer/analyzer.o \
+	analyzer/analyzer-language.o \
 	analyzer/analyzer-logging.o \
 	analyzer/analyzer-pass.o \
 	analyzer/analyzer-selftests.o \
@@ -2741,6 +2742,7 @@ GTFILES = $(CPPLIB_H) $(srcdir)/input.h $(srcdir)/coretypes.h \
   $(srcdir)/internal-fn.h \
   $(srcdir)/calls.cc \
   $(srcdir)/omp-general.h \
+  $(srcdir)/analyzer/analyzer-language.cc \
   @all_gtfiles@
 
 # Compute the list of GT header files from the corresponding C sources,
diff --git a/gcc/analyzer/analyzer-language.cc b/gcc/analyzer/analyzer-language.cc
new file mode 100644
index 00000000000..ba4352b729a
--- /dev/null
+++ b/gcc/analyzer/analyzer-language.cc
@@ -0,0 +1,110 @@
+/* Interface between analyzer and frontends.
+   Copyright (C) 2022 Free Software Foundation, Inc.
+   Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3, or (at your option)
+any later version.
+
+GCC is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+#include "config.h"
+#define INCLUDE_MEMORY
+#include "system.h"
+#include "coretypes.h"
+#include "tree.h"
+#include "stringpool.h"
+#include "analyzer/analyzer.h"
+#include "analyzer/analyzer-language.h"
+#include "analyzer/analyzer-logging.h"
+
+/* Map from identifier to INTEGER_CST.  */
+static GTY (()) hash_map <tree, tree> *analyzer_stashed_constants;
+
+#if ENABLE_ANALYZER
+
+namespace ana {
+
+/* Call into TU to try to find a value for NAME.
+   If found, stash its value within analyzer_stashed_constants.  */
+
+static void
+maybe_stash_named_constant (const translation_unit &tu, const char *name)
+{
+  if (!analyzer_stashed_constants)
+    analyzer_stashed_constants = hash_map<tree, tree>::create_ggc ();
+
+  tree id = get_identifier (name);
+  if (tree t = tu.lookup_constant_by_id (id))
+    {
+      gcc_assert (TREE_CODE (t) == INTEGER_CST);
+      analyzer_stashed_constants->put (id, t);
+    }
+}
+
+/* Hook for frontend to call into analyzer when TU finishes.
+   This exists so that the analyzer can stash named constant values from
+   header files (e.g. macros and enums) for later use when modeling the
+   behaviors of APIs.
+
+   By doing it this way, the analyzer can use the precise values for those
+   constants from the user's headers, rather than attempting to model them
+   as properties of the target.  */
+
+void
+on_finish_translation_unit (const translation_unit &tu)
+{
+  /* Bail if the analyzer isn't enabled.  */
+  if (!flag_analyzer)
+    return;
+
+  /* Stash named constants for use by sm-fd.cc  */
+  maybe_stash_named_constant (tu, "O_ACCMODE");
+  maybe_stash_named_constant (tu, "O_RDONLY");
+  maybe_stash_named_constant (tu, "O_WRONLY");
+}
+
+/* Lookup NAME in the named constants stashed when the frontend TU finished.
+   Return either an INTEGER_CST, or NULL_TREE.  */
+
+tree
+get_stashed_constant_by_name (const char *name)
+{
+  if (!analyzer_stashed_constants)
+    return NULL_TREE;
+  tree id = get_identifier (name);
+  if (tree *slot = analyzer_stashed_constants->get (id))
+    {
+      gcc_assert (TREE_CODE (*slot) == INTEGER_CST);
+      return *slot;
+    }
+  return NULL_TREE;
+}
+
+/* Log all stashed named constants to LOGGER.  */
+
+void
+log_stashed_constants (logger *logger)
+{
+  gcc_assert (logger);
+  LOG_SCOPE (logger);
+  if (analyzer_stashed_constants)
+    for (auto iter : *analyzer_stashed_constants)
+      logger->log ("%qE: %qE", iter.first, iter.second);
+}
+
+} // namespace ana
+
+#endif /* #if ENABLE_ANALYZER */
+
+#include "gt-analyzer-language.h"
diff --git a/gcc/analyzer/analyzer-language.h b/gcc/analyzer/analyzer-language.h
new file mode 100644
index 00000000000..33c4dd60623
--- /dev/null
+++ b/gcc/analyzer/analyzer-language.h
@@ -0,0 +1,48 @@
+/* Interface between analyzer and frontends.
+   Copyright (C) 2022 Free Software Foundation, Inc.
+   Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3, or (at your option)
+any later version.
+
+GCC is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+#ifndef GCC_ANALYZER_LANGUAGE_H
+#define GCC_ANALYZER_LANGUAGE_H
+
+#if ENABLE_ANALYZER
+
+namespace ana {
+
+/* Abstract base class for representing a specific TU
+   to the analyzer.  */
+
+class translation_unit
+{
+ public:
+  /* Attempt to look up an  value for identifier ID (e.g. in the headers that
+     have been seen).  If it is defined and an integer (e.g. either as a
+     macro or enum), return the INTEGER_CST value, otherwise return NULL.  */
+  virtual tree lookup_constant_by_id (tree id) const = 0;
+};
+
+/* Analyzer hook for frontends to call at the end of the TU.  */
+
+void on_finish_translation_unit (const translation_unit &tu);
+
+} // namespace ana
+
+#endif /* #if ENABLE_ANALYZER */
+
+#endif /* GCC_ANALYZER_LANGUAGE_H */
diff --git a/gcc/analyzer/analyzer.h b/gcc/analyzer/analyzer.h
index a2d79e4a59f..e0c2ef77405 100644
--- a/gcc/analyzer/analyzer.h
+++ b/gcc/analyzer/analyzer.h
@@ -312,6 +312,9 @@ public:
   virtual bool terminate_path_p () const = 0;
 };
 
+extern tree get_stashed_constant_by_name (const char *name);
+extern void log_stashed_constants (logger *logger);
+
 } // namespace ana
 
 extern bool is_special_named_call_p (const gcall *call, const char *funcname,
diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc
index 52978dd0d37..40ee14c1eae 100644
--- a/gcc/analyzer/engine.cc
+++ b/gcc/analyzer/engine.cc
@@ -6001,6 +6001,7 @@ impl_run_checkers (logger *logger)
       logger->log ("BITS_BIG_ENDIAN: %i", BITS_BIG_ENDIAN ? 1 : 0);
       logger->log ("BYTES_BIG_ENDIAN: %i", BYTES_BIG_ENDIAN ? 1 : 0);
       logger->log ("WORDS_BIG_ENDIAN: %i", WORDS_BIG_ENDIAN ? 1 : 0);
+      log_stashed_constants (logger);
     }
 
   /* If using LTO, ensure that the cgraph nodes have function bodies.  */
diff --git a/gcc/analyzer/region-model-impl-calls.cc b/gcc/analyzer/region-model-impl-calls.cc
index 52c4205cbeb..3e06f9eacd2 100644
--- a/gcc/analyzer/region-model-impl-calls.cc
+++ b/gcc/analyzer/region-model-impl-calls.cc
@@ -350,6 +350,34 @@ region_model::impl_call_analyzer_dump_escaped (const gcall *call)
 	      pp_formatted_text (&pp));
 }
 
+/* Handle a call to "__analyzer_dump_named_constant".
+
+   Look up the given name, and emit a warning describing the
+   state of the corresponding stashed value.
+
+   This is for use when debugging, and for DejaGnu tests.  */
+
+void
+region_model::
+impl_call_analyzer_dump_named_constant (const gcall *call,
+					region_model_context *ctxt)
+{
+  call_details cd (call, this, ctxt);
+  const char *name = cd.get_arg_string_literal (0);
+  if (!name)
+    {
+      error_at (call->location, "cannot determine name");
+      return;
+    }
+  tree value = get_stashed_constant_by_name (name);
+  if (value)
+    warning_at (call->location, 0, "named constant %qs has value %qE",
+		name, value);
+  else
+    warning_at (call->location, 0, "named constant %qs has unknown value",
+		name);
+}
+
 /* Handle a call to "__analyzer_eval" by evaluating the input
    and dumping as a dummy warning, so that test cases can use
    dg-warning to validate the result (and so unexpected warnings will
diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc
index 7c44fc9e253..d33be9c9905 100644
--- a/gcc/analyzer/region-model.cc
+++ b/gcc/analyzer/region-model.cc
@@ -1224,6 +1224,10 @@ region_model::on_stmt_pre (const gimple *stmt,
 	  impl_call_analyzer_dump_capacity (call, ctxt);
 	else if (is_special_named_call_p (call, "__analyzer_dump_escaped", 0))
 	  impl_call_analyzer_dump_escaped (call);
+	else if (is_special_named_call_p (call,
+					  "__analyzer_dump_named_constant",
+					  1))
+	  impl_call_analyzer_dump_named_constant (call, ctxt);
 	else if (is_special_named_call_p (call, "__analyzer_dump_path", 0))
 	  {
 	    /* Handle the builtin "__analyzer_dump_path" by queuing a
diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h
index 19e8043daa4..16429703cef 100644
--- a/gcc/analyzer/region-model.h
+++ b/gcc/analyzer/region-model.h
@@ -343,6 +343,8 @@ class region_model
   void impl_call_analyzer_dump_capacity (const gcall *call,
 					 region_model_context *ctxt);
   void impl_call_analyzer_dump_escaped (const gcall *call);
+  void impl_call_analyzer_dump_named_constant (const gcall *call,
+					       region_model_context *ctxt);
   void impl_call_analyzer_eval (const gcall *call,
 				region_model_context *ctxt);
   void impl_call_analyzer_get_unknown_ptr (const call_details &cd);
diff --git a/gcc/analyzer/sm-fd.cc b/gcc/analyzer/sm-fd.cc
index ae846cd6ec8..f3a5582c4b8 100644
--- a/gcc/analyzer/sm-fd.cc
+++ b/gcc/analyzer/sm-fd.cc
@@ -157,6 +157,11 @@ public:
   /* State for a file descriptor that we do not want to track anymore . */
   state_t m_stop;
 
+  /* Stashed constant values from the frontend.  These could be NULL.  */
+  tree m_O_ACCMODE;
+  tree m_O_RDONLY;
+  tree m_O_WRONLY;
+
 private:
   void on_open (sm_context *sm_ctxt, const supernode *node, const gimple *stmt,
 		const gcall *call) const;
@@ -684,7 +689,10 @@ 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_stop (add_state ("fd-stop"))
+      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"))
 {
 }
 
@@ -707,16 +715,18 @@ fd_state_machine::is_valid_fd_p (state_t s) const
 enum access_mode
 fd_state_machine::get_access_mode_from_flag (int flag) const
 {
-  /* FIXME: this code assumes the access modes on the host and
-     target are the same, which in practice might not be the case.  */
-
-  if ((flag & O_ACCMODE) == O_RDONLY)
-    {
-      return READ_ONLY;
-    }
-  else if ((flag & O_ACCMODE) == O_WRONLY)
+  if (m_O_ACCMODE && TREE_CODE (m_O_ACCMODE) == INTEGER_CST)
     {
-      return WRITE_ONLY;
+      const unsigned HOST_WIDE_INT mask_val = TREE_INT_CST_LOW (m_O_ACCMODE);
+      const unsigned HOST_WIDE_INT masked_flag = flag & mask_val;
+
+      if (m_O_RDONLY && TREE_CODE (m_O_RDONLY) == INTEGER_CST)
+	if (masked_flag == TREE_INT_CST_LOW (m_O_RDONLY))
+	  return READ_ONLY;
+
+      if (m_O_WRONLY && TREE_CODE (m_O_WRONLY) == INTEGER_CST)
+	if (masked_flag == TREE_INT_CST_LOW (m_O_WRONLY))
+	  return WRITE_ONLY;
     }
   return READ_WRITE;
 }
diff --git a/gcc/c/c-parser.cc b/gcc/c/c-parser.cc
index ca533c9c667..9e67afffa13 100644
--- a/gcc/c/c-parser.cc
+++ b/gcc/c/c-parser.cc
@@ -72,6 +72,8 @@ along with GCC; see the file COPYING3.  If not see
 #include "memmodel.h"
 #include "c-family/known-headers.h"
 #include "bitmap.h"
+#include "analyzer/analyzer-language.h"
+#include "toplev.h"
 
 /* We need to walk over decls with incomplete struct/union/enum types
    after parsing the whole translation unit.
@@ -1662,6 +1664,87 @@ static bool c_parser_objc_diagnose_bad_element_prefix
   (c_parser *, struct c_declspecs *);
 static location_t c_parser_parse_rtl_body (c_parser *, char *);
 
+#if ENABLE_ANALYZER
+
+namespace ana {
+
+/* Concrete implementation of ana::translation_unit for the C frontend.  */
+
+class c_translation_unit : public translation_unit
+{
+public:
+  /* Implementation of translation_unit::lookup_constant_by_id for use by the
+     analyzer to look up named constants in the user's source code.  */
+  tree lookup_constant_by_id (tree id) const final override
+  {
+    /* Consider decls.  */
+    if (tree decl = lookup_name (id))
+      if (TREE_CODE (decl) == CONST_DECL)
+	if (tree value = DECL_INITIAL (decl))
+	  if (TREE_CODE (value) == INTEGER_CST)
+	    return value;
+
+    /* Consider macros.  */
+    cpp_hashnode *hashnode = C_CPP_HASHNODE (id);
+    if (cpp_macro_p (hashnode))
+      if (tree value = consider_macro (hashnode->value.macro))
+	return value;
+
+    return NULL_TREE;
+  }
+
+private:
+  /* Attempt to get an INTEGER_CST from MACRO.
+     Only handle the simplest cases: where MACRO's definition is a single
+     token containing a number, by lexing the number again.
+     This will handle e.g.
+       #define NAME 42
+     and other bases but not negative numbers, parentheses or e.g.
+       #define NAME 1 << 7
+     as doing so would require a parser.  */
+  tree consider_macro (cpp_macro *macro) const
+  {
+    if (macro->paramc > 0)
+      return NULL_TREE;
+    if (macro->kind == cmk_traditional)
+      return NULL_TREE;
+    if (macro->count != 1)
+      return NULL_TREE;
+    const cpp_token &tok = macro->exp.tokens[0];
+    if (tok.type != CPP_NUMBER)
+      return NULL_TREE;
+
+    cpp_reader *old_parse_in = parse_in;
+    parse_in = cpp_create_reader (c_dialect_cxx () ? CLK_GNUCXX: CLK_GNUC89,
+				  ident_hash, line_table);
+
+    pretty_printer pp;
+    pp_string (&pp, (const char *)tok.val.str.text);
+    pp_newline (&pp);
+    cpp_push_buffer (parse_in,
+		     (const unsigned char *)pp_formatted_text (&pp),
+		     strlen (pp_formatted_text (&pp)),
+		     0);
+
+    tree value;
+    location_t loc;
+    unsigned char cpp_flags;
+    c_lex_with_flags (&value, &loc, &cpp_flags, 0);
+
+    cpp_destroy (parse_in);
+    parse_in = old_parse_in;
+
+    if (value && TREE_CODE (value) == INTEGER_CST)
+      return value;
+
+    return NULL_TREE;
+  }
+};
+
+} // namespace ana
+
+#endif /* #if ENABLE_ANALYZER */
+
 /* Parse a translation unit (C90 6.7, C99 6.9, C11 6.9).
 
    translation-unit:
@@ -1722,6 +1805,14 @@ c_parser_translation_unit (c_parser *parser)
 	       "#pragma omp begin assumes", "#pragma omp end assumes");
       current_omp_begin_assumes = 0;
     }
+
+#if ENABLE_ANALYZER
+  if (flag_analyzer)
+    {
+      ana::c_translation_unit tu;
+      ana::on_finish_translation_unit (tu);
+    }
+#endif
 }
 
 /* Parse an external declaration (C90 6.7, C99 6.9, C11 6.9).
diff --git a/gcc/doc/analyzer.texi b/gcc/doc/analyzer.texi
index ec49f951435..d61b55cec5a 100644
--- a/gcc/doc/analyzer.texi
+++ b/gcc/doc/analyzer.texi
@@ -524,6 +524,23 @@ With a non-zero argument
 
 it will also dump all of the states within the ``processed'' nodes.
 
+The builtin @code{__analyzer_dump_named_constant} will emit a warning
+during analysis describing what is known about the value of a given
+named constant, for parts of the analyzer that interact with target
+headers.
+
+For example:
+
+@smallexample
+__analyzer_dump_named_constant ("O_RDONLY");
+@end smallexample
+
+might emit the warning:
+
+@smallexample
+warning: named constant 'O_RDONLY' has value '1'
+@end smallexample
+
 @smallexample
    __analyzer_dump_region_model ();
 @end smallexample
diff --git a/gcc/testsuite/gcc.dg/analyzer/analyzer-decls.h b/gcc/testsuite/gcc.dg/analyzer/analyzer-decls.h
index 4478d740b58..d9a32ed9370 100644
--- a/gcc/testsuite/gcc.dg/analyzer/analyzer-decls.h
+++ b/gcc/testsuite/gcc.dg/analyzer/analyzer-decls.h
@@ -31,6 +31,9 @@ extern void __analyzer_dump_escaped (void);
    will also dump all of the states within those nodes.  */
 extern void __analyzer_dump_exploded_nodes (int);
 
+/* Emit a warning describing what is known about the value of NAME.  */
+extern void __analyzer_dump_named_constant (const char *name);
+
 /* Emit a placeholder "note" diagnostic with a path to this call site,
    if the analyzer finds a feasible path to it.  */
 extern void __analyzer_dump_path (void);
diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-4.c b/gcc/testsuite/gcc.dg/analyzer/fd-4.c
index 842a26b4364..994bad84342 100644
--- a/gcc/testsuite/gcc.dg/analyzer/fd-4.c
+++ b/gcc/testsuite/gcc.dg/analyzer/fd-4.c
@@ -8,6 +8,7 @@ void close(int fd);
 int write (int fd, void *buf, int nbytes);
 int read (int fd, void *buf, int nbytes);
 
+#define O_ACCMODE 0xf
 #define O_RDONLY 0
 #define O_WRONLY 1
 #define O_RDWR 2
diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-access-mode-enum.c b/gcc/testsuite/gcc.dg/analyzer/fd-access-mode-enum.c
new file mode 100644
index 00000000000..5226569c437
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/fd-access-mode-enum.c
@@ -0,0 +1,60 @@
+int open(const char *, int mode);
+void close(int fd);
+int write (int fd, void *buf, int nbytes);
+int read (int fd, void *buf, int nbytes);
+
+/* Example of these flags as an enum, and with
+   non-standard values for them.  */
+
+enum {
+      O_RDONLY  = 0x10,
+      O_WRONLY  = 0x20,
+      O_RDWR    = 0x40,
+
+      O_ACCMODE = 0xf0
+};
+
+void f (int fd) __attribute__((fd_arg(1))); /* { dg-message "argument 1 of 'f' must be an open file descriptor, due to '__attribute__\\(\\(fd_arg\\(1\\)\\)\\)'" } */
+
+void
+test_1 (const char *path)
+{
+    int fd = open (path, O_RDWR);
+    close(fd);
+    f(fd); /* { dg-warning "'f' on closed file descriptor 'fd'" } */
+      /* { dg-message "\\(3\\) 'f' on closed file descriptor 'fd'; 'close' was at \\(2\\)" "" { target *-*-* } .-1 } */
+}
+
+void g (int fd) __attribute__((fd_arg_read(1))); /* { dg-message "argument 1 of 'g' must be a readable file descriptor, due to '__attribute__\\(\\(fd_arg_read\\(1\\)\\)\\)'" } */
+
+void
+test_2 (const char *path)
+{
+  int fd = open (path, O_WRONLY);
+  if (fd != -1)
+  {
+    g (fd); /* { dg-warning "'g' on write-only file descriptor 'fd'" } */
+  }
+  close (fd);
+}
+
+void h (int fd) __attribute__((fd_arg_write(1))); /* { dg-message "argument 1 of 'h' must be a writable file descriptor, due to '__attribute__\\(\\(fd_arg_write\\(1\\)\\)\\)'" } */
+void
+test_3 (const char *path)
+{
+  int fd = open (path, O_RDONLY);
+  if (fd != -1)
+  {
+    h (fd); /* { dg-warning "'h' on read-only file descriptor 'fd'" } */
+  }
+  close(fd);
+}
+
+void ff (int fd) __attribute__((fd_arg(1))); /* { dg-message "argument 1 of 'ff' must be an open file descriptor, due to '__attribute__\\(\\(fd_arg\\(1\\)\\)\\)'" } */
+
+void test_4 (const char *path)
+{
+  int fd = open (path, O_RDWR);
+  ff (fd); /* { dg-warning "'ff' on possibly invalid file descriptor 'fd'" } */
+  close(fd);
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-5.c b/gcc/testsuite/gcc.dg/analyzer/fd-access-mode-macros.c
similarity index 98%
rename from gcc/testsuite/gcc.dg/analyzer/fd-5.c
rename to gcc/testsuite/gcc.dg/analyzer/fd-access-mode-macros.c
index c18b2adcbe5..f9a6931a5db 100644
--- a/gcc/testsuite/gcc.dg/analyzer/fd-5.c
+++ b/gcc/testsuite/gcc.dg/analyzer/fd-access-mode-macros.c
@@ -6,6 +6,7 @@ int read (int fd, void *buf, int nbytes);
 #define O_RDONLY 0
 #define O_WRONLY 1
 #define O_RDWR 2
+#define O_ACCMODE 0x3
 
 void f (int fd) __attribute__((fd_arg(1))); /* { dg-message "argument 1 of 'f' must be an open file descriptor, due to '__attribute__\\(\\(fd_arg\\(1\\)\\)\\)'" } */
 
diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-access-mode-target-headers.c b/gcc/testsuite/gcc.dg/analyzer/fd-access-mode-target-headers.c
new file mode 100644
index 00000000000..b76eb667d50
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/fd-access-mode-target-headers.c
@@ -0,0 +1,56 @@
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include "analyzer-decls.h"
+
+void f (int fd) __attribute__((fd_arg(1))); /* { dg-message "argument 1 of 'f' must be an open file descriptor, due to '__attribute__\\(\\(fd_arg\\(1\\)\\)\\)'" } */
+
+void
+test_1 (const char *path)
+{
+    int fd = open (path, O_RDWR);
+    close(fd);
+    f(fd); /* { dg-warning "'f' on closed file descriptor 'fd'" } */
+      /* { dg-message "\\(3\\) 'f' on closed file descriptor 'fd'; 'close' was at \\(2\\)" "" { target *-*-* } .-1 } */
+}
+
+void g (int fd) __attribute__((fd_arg_read(1))); /* { dg-message "argument 1 of 'g' must be a readable file descriptor, due to '__attribute__\\(\\(fd_arg_read\\(1\\)\\)\\)'" } */
+
+void
+test_2 (const char *path)
+{
+  int fd = open (path, O_WRONLY);
+  if (fd != -1)
+  {
+    g (fd); /* { dg-warning "'g' on write-only file descriptor 'fd'" } */
+  }
+  close (fd);
+}
+
+void h (int fd) __attribute__((fd_arg_write(1))); /* { dg-message "argument 1 of 'h' must be a writable file descriptor, due to '__attribute__\\(\\(fd_arg_write\\(1\\)\\)\\)'" } */
+void
+test_3 (const char *path)
+{
+  int fd = open (path, O_RDONLY);
+  if (fd != -1)
+  {
+    h (fd); /* { dg-warning "'h' on read-only file descriptor 'fd'" } */
+  }
+  close(fd);
+}
+
+void ff (int fd) __attribute__((fd_arg(1))); /* { dg-message "argument 1 of 'ff' must be an open file descriptor, due to '__attribute__\\(\\(fd_arg\\(1\\)\\)\\)'" } */
+
+void test_4 (const char *path)
+{
+  int fd = open (path, O_RDWR);
+  ff (fd); /* { dg-warning "'ff' on possibly invalid file descriptor 'fd'" } */
+  close(fd);
+}
+
+void test_sm_fd_constants (void)
+{
+  __analyzer_dump_named_constant ("O_ACCMODE"); /* { dg-warning "named constant 'O_ACCMODE' has value '\[0-9\]+'" } */
+  __analyzer_dump_named_constant ("O_RDONLY"); /* { dg-warning "named constant 'O_RDONLY' has value '\[0-9\]+'" } */
+  __analyzer_dump_named_constant ("O_WRONLY"); /* { dg-warning "named constant 'O_WRONLY' has value '\[0-9\]+'" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-dup-1.c b/gcc/testsuite/gcc.dg/analyzer/fd-dup-1.c
index b4f43e7f0ef..bb58e9d9d5e 100644
--- a/gcc/testsuite/gcc.dg/analyzer/fd-dup-1.c
+++ b/gcc/testsuite/gcc.dg/analyzer/fd-dup-1.c
@@ -8,6 +8,7 @@ int read (int fd, void *buf, int nbytes);
 #define O_RDONLY 0
 #define O_WRONLY 1
 #define O_RDWR 2
+#define O_ACCMODE 3
 
 void test_1 (const char *path)
 {
diff --git a/gcc/testsuite/gcc.dg/analyzer/named-constants-via-enum.c b/gcc/testsuite/gcc.dg/analyzer/named-constants-via-enum.c
new file mode 100644
index 00000000000..e6b77b8dd18
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/named-constants-via-enum.c
@@ -0,0 +1,20 @@
+#include "analyzer-decls.h"
+
+/* Various constants used by the fd state machine.  */
+enum {
+      O_ACCMODE = 42,
+      O_RDONLY  = 0x1,
+      O_WRONLY  = 010
+};
+
+void test_sm_fd_constants (void)
+{
+  __analyzer_dump_named_constant ("O_ACCMODE"); /* { dg-warning "named constant 'O_ACCMODE' has value '42'" } */
+  __analyzer_dump_named_constant ("O_RDONLY"); /* { dg-warning "named constant 'O_RDONLY' has value '1'" } */
+  __analyzer_dump_named_constant ("O_WRONLY"); /* { dg-warning "named constant 'O_WRONLY' has value '8'" } */
+}
+
+void test_unknown (void)
+{
+  __analyzer_dump_named_constant ("UNKNOWN"); /* { dg-warning "named constant 'UNKNOWN' has unknown value" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/named-constants-via-macros-2.c b/gcc/testsuite/gcc.dg/analyzer/named-constants-via-macros-2.c
new file mode 100644
index 00000000000..9c019e7c5ef
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/named-constants-via-macros-2.c
@@ -0,0 +1,15 @@
+#include "analyzer-decls.h"
+
+/* Various constants used by the fd state machine, as macros
+   that can't be handled.  */
+
+#define O_ACCMODE (
+#define O_RDONLY  "foo"
+#define O_WRONLY  int
+
+void test_sm_fd_constants (void)
+{
+  __analyzer_dump_named_constant ("O_ACCMODE"); /* { dg-warning "named constant 'O_ACCMODE' has unknown value" } */
+  __analyzer_dump_named_constant ("O_RDONLY"); /* { dg-warning "named constant 'O_RDONLY' has unknown value" } */
+  __analyzer_dump_named_constant ("O_WRONLY"); /* { dg-warning "named constant 'O_WRONLY' has unknown value" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/named-constants-via-macros.c b/gcc/testsuite/gcc.dg/analyzer/named-constants-via-macros.c
new file mode 100644
index 00000000000..2022f98e5b6
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/named-constants-via-macros.c
@@ -0,0 +1,19 @@
+#include "analyzer-decls.h"
+
+/* Various constants used by the fd state machine.  */
+
+#define O_ACCMODE 42
+#define O_RDONLY  0x1
+#define O_WRONLY  010
+
+void test_sm_fd_constants (void)
+{
+  __analyzer_dump_named_constant ("O_ACCMODE"); /* { dg-warning "named constant 'O_ACCMODE' has value '42'" } */
+  __analyzer_dump_named_constant ("O_RDONLY"); /* { dg-warning "named constant 'O_RDONLY' has value '1'" } */
+  __analyzer_dump_named_constant ("O_WRONLY"); /* { dg-warning "named constant 'O_WRONLY' has value '8'" } */
+}
+
+void test_unknown (void)
+{
+  __analyzer_dump_named_constant ("UNKNOWN"); /* { dg-warning "named constant 'UNKNOWN' has unknown value" } */
+}
-- 
2.26.3


^ permalink raw reply	[flat|nested] 12+ messages in thread

* [PATCH] analyzer: add warnings relating to sockets [PR106140]
  2022-10-31 19:07 [PATCH] c, analyzer: support named constants in analyzer [PR106302] David Malcolm
@ 2022-11-08  3:02 ` David Malcolm
  2022-11-12  3:27   ` [PATCH v2] " David Malcolm
  2022-11-08  3:06 ` PING: Re: [PATCH] c, analyzer: support named constants in analyzer [PR106302] David Malcolm
  1 sibling, 1 reply; 12+ messages in thread
From: David Malcolm @ 2022-11-08  3:02 UTC (permalink / raw)
  To: gcc-patches; +Cc: David Malcolm

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)

Successfully bootstrapped & regrtested on x86_64-pc-linux-gnu.

This patch depends on the named constants patch here:
  [PATCH] c, analyzer: support named constants in analyzer [PR106302]
    https://gcc.gnu.org/pipermail/gcc-patches/2022-October/604739.html

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.

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>
---
 gcc/analyzer/analyzer-language.cc             |    2 +
 gcc/analyzer/analyzer.opt                     |    8 +
 gcc/analyzer/engine.cc                        |   60 +-
 gcc/analyzer/exploded-graph.h                 |   10 +-
 gcc/analyzer/region-model-impl-calls.cc       |  150 +++
 gcc/analyzer/region-model.cc                  |   30 +
 gcc/analyzer/region-model.h                   |   57 +-
 gcc/analyzer/sm-fd.cc                         | 1110 ++++++++++++++++-
 gcc/analyzer/sm-fd.dot                        |   64 +
 gcc/doc/invoke.texi                           |   32 +
 gcc/testsuite/gcc.dg/analyzer/fd-accept.c     |   69 +
 gcc/testsuite/gcc.dg/analyzer/fd-bind.c       |   74 ++
 gcc/testsuite/gcc.dg/analyzer/fd-connect.c    |   46 +
 .../gcc.dg/analyzer/fd-datagram-socket.c      |  108 ++
 .../fd-glibc-byte-stream-connection-server.c  |  133 ++
 .../analyzer/fd-glibc-byte-stream-socket.c    |   62 +
 .../analyzer/fd-glibc-datagram-client.c       |   56 +
 .../analyzer/fd-glibc-datagram-socket.c       |   52 +
 .../analyzer/fd-glibc-make_named_socket.h     |   47 +
 gcc/testsuite/gcc.dg/analyzer/fd-listen.c     |   63 +
 .../analyzer/fd-manpage-getaddrinfo-client.c  |  122 ++
 .../analyzer/fd-mappage-getaddrinfo-server.c  |  119 ++
 .../gcc.dg/analyzer/fd-socket-meaning.c       |   21 +
 .../gcc.dg/analyzer/fd-socket-misuse.c        |   98 ++
 .../analyzer/fd-stream-socket-active-open.c   |   74 ++
 .../analyzer/fd-stream-socket-passive-open.c  |  197 +++
 .../gcc.dg/analyzer/fd-stream-socket.c        |   98 ++
 .../gcc.dg/analyzer/fd-symbolic-socket.c      |   98 ++
 gcc/testsuite/gcc.dg/analyzer/pr104369-1.c    |    4 +-
 gcc/testsuite/gcc.dg/analyzer/pr104369-2.c    |    3 +
 30 files changed, 3007 insertions(+), 60 deletions(-)
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/fd-accept.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/fd-bind.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/fd-connect.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/fd-datagram-socket.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/fd-glibc-byte-stream-connection-server.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/fd-glibc-byte-stream-socket.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/fd-glibc-datagram-client.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/fd-glibc-datagram-socket.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/fd-glibc-make_named_socket.h
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/fd-listen.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/fd-manpage-getaddrinfo-client.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/fd-mappage-getaddrinfo-server.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/fd-socket-meaning.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/fd-socket-misuse.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/fd-stream-socket-active-open.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/fd-stream-socket-passive-open.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/fd-stream-socket.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/fd-symbolic-socket.c

diff --git a/gcc/analyzer/analyzer-language.cc b/gcc/analyzer/analyzer-language.cc
index ba4352b729a..0629b681e7c 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 dbab3b82deb..bdf3ea65d00 100644
--- a/gcc/analyzer/analyzer.opt
+++ b/gcc/analyzer/analyzer.opt
@@ -86,6 +86,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 18df44017aa..290ee73c8e3 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 8e3c160189c..c9baad8e036 100644
--- a/gcc/analyzer/exploded-graph.h
+++ b/gcc/analyzer/exploded-graph.h
@@ -96,10 +96,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 e8ca71b66ed..17aaaec7210 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 aa8085b72b8..8951e88ed05 100644
--- a/gcc/analyzer/region-model.cc
+++ b/gcc/analyzer/region-model.cc
@@ -2292,6 +2292,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);
@@ -2421,12 +2426,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 2a0e16873b3..ae4dd6eb4fb 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);
@@ -551,6 +556,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,
@@ -561,7 +571,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;
 
@@ -574,10 +593,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,
@@ -608,9 +623,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.  */
@@ -818,7 +833,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;
   }
@@ -940,9 +956,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 370115d56bf..d0b587143d0 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 175daae44ae..0b2b31996ec 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 94a2e20cfc1..cf82f6d338b 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
@@ -10052,6 +10056,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
@@ -10504,6 +10535,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 00000000000..36cc7af7184
--- /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 00000000000..6f91bc4b794
--- /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 00000000000..1ab54d01f36
--- /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 00000000000..045bdfa32d3
--- /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 00000000000..1ff902894af
--- /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 00000000000..f96da8101cc
--- /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 00000000000..888c751e88d
--- /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 00000000000..b8b68768c34
--- /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 00000000000..bdb6de0ae15
--- /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 00000000000..1f54a8f2953
--- /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 00000000000..d9c3ff05de8
--- /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 00000000000..66398e834cc
--- /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 00000000000..5bfb57f68fb
--- /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 00000000000..4ff08d5ec19
--- /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 00000000000..7fde0ef6285
--- /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 00000000000..c31e5b5eefb
--- /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 00000000000..3a292d0e2d2
--- /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 00000000000..83400c18f50
--- /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 d3b32418a9e..c05137bb219 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 57dc9caf3e9..93d9987d0ba 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
-- 
2.26.3


^ permalink raw reply	[flat|nested] 12+ messages in thread

* PING: Re: [PATCH] c, analyzer: support named constants in analyzer [PR106302]
  2022-10-31 19:07 [PATCH] c, analyzer: support named constants in analyzer [PR106302] David Malcolm
  2022-11-08  3:02 ` [PATCH] analyzer: add warnings relating to sockets [PR106140] David Malcolm
@ 2022-11-08  3:06 ` David Malcolm
  2022-11-12  3:23   ` [PATCH v2] " David Malcolm
  1 sibling, 1 reply; 12+ messages in thread
From: David Malcolm @ 2022-11-08  3:06 UTC (permalink / raw)
  To: gcc-patches, Joseph Myers; +Cc: Marek Polacek

I'd like to "ping" the C frontend parts of this patch:
  https://gcc.gnu.org/pipermail/gcc-patches/2022-October/604739.html

FWIW I've just posted the socket patch that I referred to, here:
  https://gcc.gnu.org/pipermail/gcc-patches/2022-November/605281.html
which depends on this patch.

Thanks
Dave

On Mon, 2022-10-31 at 15:07 -0400, David Malcolm wrote:
> The analyzer's file-descriptor state machine tracks the access mode
> of
> opened files, so that it can emit -Wanalyzer-fd-access-mode-mismatch.
> 
> To do this, its symbolic execution needs to "know" the values of the
> constants "O_RDONLY", "O_WRONLY", and "O_ACCMODE".  Currently
> analyzer/sm-fd.cc simply uses these values directly from the build-
> time
> header files, but these are the values on the host, not those from
> the
> target, which could be different (PR analyzer/106302).
> 
> In an earlier discussion of this issue:
>   https://gcc.gnu.org/pipermail/gcc/2022-June/238954.html
> we talked about adding a target hook for this.
> 
> However, I've also been experimenting with extending the fd state
> machine to track sockets (PR analyzer/106140).  For this, it's useful
> to
> "know" the values of the constants "SOCK_STREAM" and "SOCK_DGRAM".
> Unfortunately, these seem to have many arbitrary differences from
> target
> to target.
> 
> For example: Linux/glibc general has SOCK_STREAM == 1, SOCK_DGRAM ==
> 2,
> as does AIX, but annoyingly, e.g. Linux on MIPS has them the other
> way
> around.
> 
> It seems to me that as the analyzer grows more ambitious modeling of
> the
> behavior of APIs (perhaps via plugins) it's more likely that the
> analyzer will need to know the values of named constants, which might
> not even exist on the host.
> 
> For example, at LPC it was suggested to me that -fanalyzer could
> check
> rules about memory management inside the Linux kernel (probably via a
> plugin), but doing so involves a bunch of GFP_* flags (see PR
> 107472).
> 
> So rather than trying to capture all this knowledge in a target hook,
> this patch attempts to get at named constant values from the user's
> source code.
> 
> The patch adds an interface for frontends to call into the analyzer
> as
> the translation unit finishes.  The analyzer can then call back into
> the
> frontend to ask about the values of the named constants it cares
> about
> whilst the frontend's data structures are still around.
> 
> The patch implements this for the C frontend, which looks up the
> names
> by looking for named CONST_DECLs (which handles enum values). 
> Failing
> that, it attempts to look up the values of macros but only the
> simplest
> cases are supported (a non-traditional macro with a single CPP_NUMBER
> token).  It does this by building a buffer containing the macro
> definition and rerunning a lexer on it.
> 
> The analyzer gracefully handles the cases where named values aren't
> found (such as anything more complicated than described above).
> 
> The patch ports the analyzer to use this mechanism for "O_RDONLY",
> "O_WRONLY", and "O_ACCMODE".  I have successfully tested my socket
> patch
> to also use this for "SOCK_STREAM" and "SOCK_DGRAM", so the technique
> seems to work.
> 
> Successfully bootstrapped & regrtested on x86_64-pc-linux-gnu.
> 
> Are the C frontend parts OK for trunk?
> 
> Thanks
> Dave
> 
> gcc/ChangeLog:
>         PR analyzer/106302
>         * Makefile.in (ANALYZER_OBJS): Add analyzer/analyzer-
> language.o.
>         (GTFILES): Add analyzer/analyzer-language.cc.
> 
> gcc/analyzer/ChangeLog:
>         PR analyzer/106302
>         * analyzer-language.cc: New file.
>         * analyzer-language.h: New file.
>         * analyzer.h (get_stashed_constant_by_name): New decl.
>         (log_stashed_constants): New decl.
>         * engine.cc (impl_run_checkers): Call log_stashed_constants.
>         * region-model-impl-calls.cc
>         (region_model::impl_call_analyzer_dump_named_constant): New.
>         * region-model.cc (region_model::on_stmt_pre): Handle
>         __analyzer_dump_named_constant.
>         * region-model.h
>         (region_model::impl_call_analyzer_dump_named_constant): New
> decl.
>         * sm-fd.cc (fd_state_machine::m_O_ACCMODE): New.
>         (fd_state_machine::m_O_RDONLY): New.
>         (fd_state_machine::m_O_WRONLY): New.
>         (fd_state_machine::fd_state_machine): Initialize the new
> fields.
>         (fd_state_machine::get_access_mode_from_flag): Use the new
> fields,
>         rather than using the host values.
> 
> gcc/c/ChangeLog:
>         PR analyzer/106302
>         * c-parser.cc: Include "analyzer/analyzer-language.h" and
> "toplev.h".
>         (class ana::c_translation_unit): New.
>         (c_parser_translation_unit): Call
> ana::on_finish_translation_unit.
> 
> gcc/ChangeLog:
>         * doc/analyzer.texi: Document __analyzer_dump_named_constant.
> 
> gcc/testsuite/ChangeLog:
>         * gcc.dg/analyzer/analyzer-decls.h
>         (__analyzer_dump_named_constant): New decl.
>         * gcc.dg/analyzer/fd-4.c (void): Likewise.
>         (O_ACCMODE): Define.
>         * gcc.dg/analyzer/fd-access-mode-enum.c: New test, based on .
>         * gcc.dg/analyzer/fd-5.c: ...this.  Rename to...
>         * gcc.dg/analyzer/fd-access-mode-macros.c: ...this.
>         (O_ACCMODE): Define.
>         * gcc.dg/analyzer/fd-access-mode-target-headers.c: New test,
> also
>         based on fd-5.c.
>         (test_sm_fd_constants): New.
>         * gcc.dg/analyzer/fd-dup-1.c (O_ACCMODE): Define.
>         * gcc.dg/analyzer/named-constants-via-enum.c: New test.
>         * gcc.dg/analyzer/named-constants-via-macros-2.c: New test.
>         * gcc.dg/analyzer/named-constants-via-macros.c: New test.
> 
> Signed-off-by: David Malcolm <dmalcolm@redhat.com>
> ---
>  gcc/Makefile.in                               |   2 +
>  gcc/analyzer/analyzer-language.cc             | 110
> ++++++++++++++++++
>  gcc/analyzer/analyzer-language.h              |  48 ++++++++
>  gcc/analyzer/analyzer.h                       |   3 +
>  gcc/analyzer/engine.cc                        |   1 +
>  gcc/analyzer/region-model-impl-calls.cc       |  28 +++++
>  gcc/analyzer/region-model.cc                  |   4 +
>  gcc/analyzer/region-model.h                   |   2 +
>  gcc/analyzer/sm-fd.cc                         |  30 +++--
>  gcc/c/c-parser.cc                             |  91 +++++++++++++++
>  gcc/doc/analyzer.texi                         |  17 +++
>  .../gcc.dg/analyzer/analyzer-decls.h          |   3 +
>  gcc/testsuite/gcc.dg/analyzer/fd-4.c          |   1 +
>  .../gcc.dg/analyzer/fd-access-mode-enum.c     |  60 ++++++++++
>  .../{fd-5.c => fd-access-mode-macros.c}       |   1 +
>  .../analyzer/fd-access-mode-target-headers.c  |  56 +++++++++
>  gcc/testsuite/gcc.dg/analyzer/fd-dup-1.c      |   1 +
>  .../analyzer/named-constants-via-enum.c       |  20 ++++
>  .../analyzer/named-constants-via-macros-2.c   |  15 +++
>  .../analyzer/named-constants-via-macros.c     |  19 +++
>  20 files changed, 502 insertions(+), 10 deletions(-)
>  create mode 100644 gcc/analyzer/analyzer-language.cc
>  create mode 100644 gcc/analyzer/analyzer-language.h
>  create mode 100644 gcc/testsuite/gcc.dg/analyzer/fd-access-mode-
> enum.c
>  rename gcc/testsuite/gcc.dg/analyzer/{fd-5.c => fd-access-mode-
> macros.c} (98%)
>  create mode 100644 gcc/testsuite/gcc.dg/analyzer/fd-access-mode-
> target-headers.c
>  create mode 100644 gcc/testsuite/gcc.dg/analyzer/named-constants-
> via-enum.c
>  create mode 100644 gcc/testsuite/gcc.dg/analyzer/named-constants-
> via-macros-2.c
>  create mode 100644 gcc/testsuite/gcc.dg/analyzer/named-constants-
> via-macros.c
> 
> diff --git a/gcc/Makefile.in b/gcc/Makefile.in
> index f672e6ea549..4539934cb03 100644
> --- a/gcc/Makefile.in
> +++ b/gcc/Makefile.in
> @@ -1249,6 +1249,7 @@ C_COMMON_OBJS = c-family/c-common.o c-family/c-
> cppbuiltin.o c-family/c-dump.o \
>  ANALYZER_OBJS = \
>         analyzer/analysis-plan.o \
>         analyzer/analyzer.o \
> +       analyzer/analyzer-language.o \
>         analyzer/analyzer-logging.o \
>         analyzer/analyzer-pass.o \
>         analyzer/analyzer-selftests.o \
> @@ -2741,6 +2742,7 @@ GTFILES = $(CPPLIB_H) $(srcdir)/input.h
> $(srcdir)/coretypes.h \
>    $(srcdir)/internal-fn.h \
>    $(srcdir)/calls.cc \
>    $(srcdir)/omp-general.h \
> +  $(srcdir)/analyzer/analyzer-language.cc \
>    @all_gtfiles@
>  
>  # Compute the list of GT header files from the corresponding C
> sources,
> diff --git a/gcc/analyzer/analyzer-language.cc
> b/gcc/analyzer/analyzer-language.cc
> new file mode 100644
> index 00000000000..ba4352b729a
> --- /dev/null
> +++ b/gcc/analyzer/analyzer-language.cc
> @@ -0,0 +1,110 @@
> +/* Interface between analyzer and frontends.
> +   Copyright (C) 2022 Free Software Foundation, Inc.
> +   Contributed by David Malcolm <dmalcolm@redhat.com>.
> +
> +This file is part of GCC.
> +
> +GCC is free software; you can redistribute it and/or modify it
> +under the terms of the GNU General Public License as published by
> +the Free Software Foundation; either version 3, or (at your option)
> +any later version.
> +
> +GCC is distributed in the hope that it will be useful, but
> +WITHOUT ANY WARRANTY; without even the implied warranty of
> +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +General Public License for more details.
> +
> +You should have received a copy of the GNU General Public License
> +along with GCC; see the file COPYING3.  If not see
> +<http://www.gnu.org/licenses/>.  */
> +
> +#include "config.h"
> +#define INCLUDE_MEMORY
> +#include "system.h"
> +#include "coretypes.h"
> +#include "tree.h"
> +#include "stringpool.h"
> +#include "analyzer/analyzer.h"
> +#include "analyzer/analyzer-language.h"
> +#include "analyzer/analyzer-logging.h"
> +
> +/* Map from identifier to INTEGER_CST.  */
> +static GTY (()) hash_map <tree, tree> *analyzer_stashed_constants;
> +
> +#if ENABLE_ANALYZER
> +
> +namespace ana {
> +
> +/* Call into TU to try to find a value for NAME.
> +   If found, stash its value within analyzer_stashed_constants.  */
> +
> +static void
> +maybe_stash_named_constant (const translation_unit &tu, const char
> *name)
> +{
> +  if (!analyzer_stashed_constants)
> +    analyzer_stashed_constants = hash_map<tree, tree>::create_ggc
> ();
> +
> +  tree id = get_identifier (name);
> +  if (tree t = tu.lookup_constant_by_id (id))
> +    {
> +      gcc_assert (TREE_CODE (t) == INTEGER_CST);
> +      analyzer_stashed_constants->put (id, t);
> +    }
> +}
> +
> +/* Hook for frontend to call into analyzer when TU finishes.
> +   This exists so that the analyzer can stash named constant values
> from
> +   header files (e.g. macros and enums) for later use when modeling
> the
> +   behaviors of APIs.
> +
> +   By doing it this way, the analyzer can use the precise values for
> those
> +   constants from the user's headers, rather than attempting to
> model them
> +   as properties of the target.  */
> +
> +void
> +on_finish_translation_unit (const translation_unit &tu)
> +{
> +  /* Bail if the analyzer isn't enabled.  */
> +  if (!flag_analyzer)
> +    return;
> +
> +  /* Stash named constants for use by sm-fd.cc  */
> +  maybe_stash_named_constant (tu, "O_ACCMODE");
> +  maybe_stash_named_constant (tu, "O_RDONLY");
> +  maybe_stash_named_constant (tu, "O_WRONLY");
> +}
> +
> +/* Lookup NAME in the named constants stashed when the frontend TU
> finished.
> +   Return either an INTEGER_CST, or NULL_TREE.  */
> +
> +tree
> +get_stashed_constant_by_name (const char *name)
> +{
> +  if (!analyzer_stashed_constants)
> +    return NULL_TREE;
> +  tree id = get_identifier (name);
> +  if (tree *slot = analyzer_stashed_constants->get (id))
> +    {
> +      gcc_assert (TREE_CODE (*slot) == INTEGER_CST);
> +      return *slot;
> +    }
> +  return NULL_TREE;
> +}
> +
> +/* Log all stashed named constants to LOGGER.  */
> +
> +void
> +log_stashed_constants (logger *logger)
> +{
> +  gcc_assert (logger);
> +  LOG_SCOPE (logger);
> +  if (analyzer_stashed_constants)
> +    for (auto iter : *analyzer_stashed_constants)
> +      logger->log ("%qE: %qE", iter.first, iter.second);
> +}
> +
> +} // namespace ana
> +
> +#endif /* #if ENABLE_ANALYZER */
> +
> +#include "gt-analyzer-language.h"
> diff --git a/gcc/analyzer/analyzer-language.h
> b/gcc/analyzer/analyzer-language.h
> new file mode 100644
> index 00000000000..33c4dd60623
> --- /dev/null
> +++ b/gcc/analyzer/analyzer-language.h
> @@ -0,0 +1,48 @@
> +/* Interface between analyzer and frontends.
> +   Copyright (C) 2022 Free Software Foundation, Inc.
> +   Contributed by David Malcolm <dmalcolm@redhat.com>.
> +
> +This file is part of GCC.
> +
> +GCC is free software; you can redistribute it and/or modify it
> +under the terms of the GNU General Public License as published by
> +the Free Software Foundation; either version 3, or (at your option)
> +any later version.
> +
> +GCC is distributed in the hope that it will be useful, but
> +WITHOUT ANY WARRANTY; without even the implied warranty of
> +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +General Public License for more details.
> +
> +You should have received a copy of the GNU General Public License
> +along with GCC; see the file COPYING3.  If not see
> +<http://www.gnu.org/licenses/>.  */
> +
> +#ifndef GCC_ANALYZER_LANGUAGE_H
> +#define GCC_ANALYZER_LANGUAGE_H
> +
> +#if ENABLE_ANALYZER
> +
> +namespace ana {
> +
> +/* Abstract base class for representing a specific TU
> +   to the analyzer.  */
> +
> +class translation_unit
> +{
> + public:
> +  /* Attempt to look up an  value for identifier ID (e.g. in the
> headers that
> +     have been seen).  If it is defined and an integer (e.g. either
> as a
> +     macro or enum), return the INTEGER_CST value, otherwise return
> NULL.  */
> +  virtual tree lookup_constant_by_id (tree id) const = 0;
> +};
> +
> +/* Analyzer hook for frontends to call at the end of the TU.  */
> +
> +void on_finish_translation_unit (const translation_unit &tu);
> +
> +} // namespace ana
> +
> +#endif /* #if ENABLE_ANALYZER */
> +
> +#endif /* GCC_ANALYZER_LANGUAGE_H */
> diff --git a/gcc/analyzer/analyzer.h b/gcc/analyzer/analyzer.h
> index a2d79e4a59f..e0c2ef77405 100644
> --- a/gcc/analyzer/analyzer.h
> +++ b/gcc/analyzer/analyzer.h
> @@ -312,6 +312,9 @@ public:
>    virtual bool terminate_path_p () const = 0;
>  };
>  
> +extern tree get_stashed_constant_by_name (const char *name);
> +extern void log_stashed_constants (logger *logger);
> +
>  } // namespace ana
>  
>  extern bool is_special_named_call_p (const gcall *call, const char
> *funcname,
> diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc
> index 52978dd0d37..40ee14c1eae 100644
> --- a/gcc/analyzer/engine.cc
> +++ b/gcc/analyzer/engine.cc
> @@ -6001,6 +6001,7 @@ impl_run_checkers (logger *logger)
>        logger->log ("BITS_BIG_ENDIAN: %i", BITS_BIG_ENDIAN ? 1 : 0);
>        logger->log ("BYTES_BIG_ENDIAN: %i", BYTES_BIG_ENDIAN ? 1 :
> 0);
>        logger->log ("WORDS_BIG_ENDIAN: %i", WORDS_BIG_ENDIAN ? 1 :
> 0);
> +      log_stashed_constants (logger);
>      }
>  
>    /* If using LTO, ensure that the cgraph nodes have function
> bodies.  */
> diff --git a/gcc/analyzer/region-model-impl-calls.cc
> b/gcc/analyzer/region-model-impl-calls.cc
> index 52c4205cbeb..3e06f9eacd2 100644
> --- a/gcc/analyzer/region-model-impl-calls.cc
> +++ b/gcc/analyzer/region-model-impl-calls.cc
> @@ -350,6 +350,34 @@ region_model::impl_call_analyzer_dump_escaped
> (const gcall *call)
>               pp_formatted_text (&pp));
>  }
>  
> +/* Handle a call to "__analyzer_dump_named_constant".
> +
> +   Look up the given name, and emit a warning describing the
> +   state of the corresponding stashed value.
> +
> +   This is for use when debugging, and for DejaGnu tests.  */
> +
> +void
> +region_model::
> +impl_call_analyzer_dump_named_constant (const gcall *call,
> +                                       region_model_context *ctxt)
> +{
> +  call_details cd (call, this, ctxt);
> +  const char *name = cd.get_arg_string_literal (0);
> +  if (!name)
> +    {
> +      error_at (call->location, "cannot determine name");
> +      return;
> +    }
> +  tree value = get_stashed_constant_by_name (name);
> +  if (value)
> +    warning_at (call->location, 0, "named constant %qs has value
> %qE",
> +               name, value);
> +  else
> +    warning_at (call->location, 0, "named constant %qs has unknown
> value",
> +               name);
> +}
> +
>  /* Handle a call to "__analyzer_eval" by evaluating the input
>     and dumping as a dummy warning, so that test cases can use
>     dg-warning to validate the result (and so unexpected warnings
> will
> diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-
> model.cc
> index 7c44fc9e253..d33be9c9905 100644
> --- a/gcc/analyzer/region-model.cc
> +++ b/gcc/analyzer/region-model.cc
> @@ -1224,6 +1224,10 @@ region_model::on_stmt_pre (const gimple *stmt,
>           impl_call_analyzer_dump_capacity (call, ctxt);
>         else if (is_special_named_call_p (call,
> "__analyzer_dump_escaped", 0))
>           impl_call_analyzer_dump_escaped (call);
> +       else if (is_special_named_call_p (call,
> +                                        
> "__analyzer_dump_named_constant",
> +                                         1))
> +         impl_call_analyzer_dump_named_constant (call, ctxt);
>         else if (is_special_named_call_p (call,
> "__analyzer_dump_path", 0))
>           {
>             /* Handle the builtin "__analyzer_dump_path" by queuing a
> diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-
> model.h
> index 19e8043daa4..16429703cef 100644
> --- a/gcc/analyzer/region-model.h
> +++ b/gcc/analyzer/region-model.h
> @@ -343,6 +343,8 @@ class region_model
>    void impl_call_analyzer_dump_capacity (const gcall *call,
>                                          region_model_context *ctxt);
>    void impl_call_analyzer_dump_escaped (const gcall *call);
> +  void impl_call_analyzer_dump_named_constant (const gcall *call,
> +                                              region_model_context
> *ctxt);
>    void impl_call_analyzer_eval (const gcall *call,
>                                 region_model_context *ctxt);
>    void impl_call_analyzer_get_unknown_ptr (const call_details &cd);
> diff --git a/gcc/analyzer/sm-fd.cc b/gcc/analyzer/sm-fd.cc
> index ae846cd6ec8..f3a5582c4b8 100644
> --- a/gcc/analyzer/sm-fd.cc
> +++ b/gcc/analyzer/sm-fd.cc
> @@ -157,6 +157,11 @@ public:
>    /* State for a file descriptor that we do not want to track
> anymore . */
>    state_t m_stop;
>  
> +  /* Stashed constant values from the frontend.  These could be
> NULL.  */
> +  tree m_O_ACCMODE;
> +  tree m_O_RDONLY;
> +  tree m_O_WRONLY;
> +
>  private:
>    void on_open (sm_context *sm_ctxt, const supernode *node, const
> gimple *stmt,
>                 const gcall *call) const;
> @@ -684,7 +689,10 @@ 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_stop (add_state ("fd-stop"))
> +      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"))
>  {
>  }
>  
> @@ -707,16 +715,18 @@ fd_state_machine::is_valid_fd_p (state_t s)
> const
>  enum access_mode
>  fd_state_machine::get_access_mode_from_flag (int flag) const
>  {
> -  /* FIXME: this code assumes the access modes on the host and
> -     target are the same, which in practice might not be the case. 
> */
> -
> -  if ((flag & O_ACCMODE) == O_RDONLY)
> -    {
> -      return READ_ONLY;
> -    }
> -  else if ((flag & O_ACCMODE) == O_WRONLY)
> +  if (m_O_ACCMODE && TREE_CODE (m_O_ACCMODE) == INTEGER_CST)
>      {
> -      return WRITE_ONLY;
> +      const unsigned HOST_WIDE_INT mask_val = TREE_INT_CST_LOW
> (m_O_ACCMODE);
> +      const unsigned HOST_WIDE_INT masked_flag = flag & mask_val;
> +
> +      if (m_O_RDONLY && TREE_CODE (m_O_RDONLY) == INTEGER_CST)
> +       if (masked_flag == TREE_INT_CST_LOW (m_O_RDONLY))
> +         return READ_ONLY;
> +
> +      if (m_O_WRONLY && TREE_CODE (m_O_WRONLY) == INTEGER_CST)
> +       if (masked_flag == TREE_INT_CST_LOW (m_O_WRONLY))
> +         return WRITE_ONLY;
>      }
>    return READ_WRITE;
>  }
> diff --git a/gcc/c/c-parser.cc b/gcc/c/c-parser.cc
> index ca533c9c667..9e67afffa13 100644
> --- a/gcc/c/c-parser.cc
> +++ b/gcc/c/c-parser.cc
> @@ -72,6 +72,8 @@ along with GCC; see the file COPYING3.  If not see
>  #include "memmodel.h"
>  #include "c-family/known-headers.h"
>  #include "bitmap.h"
> +#include "analyzer/analyzer-language.h"
> +#include "toplev.h"
>  
>  /* We need to walk over decls with incomplete struct/union/enum
> types
>     after parsing the whole translation unit.
> @@ -1662,6 +1664,87 @@ static bool
> c_parser_objc_diagnose_bad_element_prefix
>    (c_parser *, struct c_declspecs *);
>  static location_t c_parser_parse_rtl_body (c_parser *, char *);
>  
> +#if ENABLE_ANALYZER
> +
> +namespace ana {
> +
> +/* Concrete implementation of ana::translation_unit for the C
> frontend.  */
> +
> +class c_translation_unit : public translation_unit
> +{
> +public:
> +  /* Implementation of translation_unit::lookup_constant_by_id for
> use by the
> +     analyzer to look up named constants in the user's source code. 
> */
> +  tree lookup_constant_by_id (tree id) const final override
> +  {
> +    /* Consider decls.  */
> +    if (tree decl = lookup_name (id))
> +      if (TREE_CODE (decl) == CONST_DECL)
> +       if (tree value = DECL_INITIAL (decl))
> +         if (TREE_CODE (value) == INTEGER_CST)
> +           return value;
> +
> +    /* Consider macros.  */
> +    cpp_hashnode *hashnode = C_CPP_HASHNODE (id);
> +    if (cpp_macro_p (hashnode))
> +      if (tree value = consider_macro (hashnode->value.macro))
> +       return value;
> +
> +    return NULL_TREE;
> +  }
> +
> +private:
> +  /* Attempt to get an INTEGER_CST from MACRO.
> +     Only handle the simplest cases: where MACRO's definition is a
> single
> +     token containing a number, by lexing the number again.
> +     This will handle e.g.
> +       #define NAME 42
> +     and other bases but not negative numbers, parentheses or e.g.
> +       #define NAME 1 << 7
> +     as doing so would require a parser.  */
> +  tree consider_macro (cpp_macro *macro) const
> +  {
> +    if (macro->paramc > 0)
> +      return NULL_TREE;
> +    if (macro->kind == cmk_traditional)
> +      return NULL_TREE;
> +    if (macro->count != 1)
> +      return NULL_TREE;
> +    const cpp_token &tok = macro->exp.tokens[0];
> +    if (tok.type != CPP_NUMBER)
> +      return NULL_TREE;
> +
> +    cpp_reader *old_parse_in = parse_in;
> +    parse_in = cpp_create_reader (c_dialect_cxx () ? CLK_GNUCXX:
> CLK_GNUC89,
> +                                 ident_hash, line_table);
> +
> +    pretty_printer pp;
> +    pp_string (&pp, (const char *)tok.val.str.text);
> +    pp_newline (&pp);
> +    cpp_push_buffer (parse_in,
> +                    (const unsigned char *)pp_formatted_text (&pp),
> +                    strlen (pp_formatted_text (&pp)),
> +                    0);
> +
> +    tree value;
> +    location_t loc;
> +    unsigned char cpp_flags;
> +    c_lex_with_flags (&value, &loc, &cpp_flags, 0);
> +
> +    cpp_destroy (parse_in);
> +    parse_in = old_parse_in;
> +
> +    if (value && TREE_CODE (value) == INTEGER_CST)
> +      return value;
> +
> +    return NULL_TREE;
> +  }
> +};
> +
> +} // namespace ana
> +
> +#endif /* #if ENABLE_ANALYZER */
> +
>  /* Parse a translation unit (C90 6.7, C99 6.9, C11 6.9).
>  
>     translation-unit:
> @@ -1722,6 +1805,14 @@ c_parser_translation_unit (c_parser *parser)
>                "#pragma omp begin assumes", "#pragma omp end
> assumes");
>        current_omp_begin_assumes = 0;
>      }
> +
> +#if ENABLE_ANALYZER
> +  if (flag_analyzer)
> +    {
> +      ana::c_translation_unit tu;
> +      ana::on_finish_translation_unit (tu);
> +    }
> +#endif
>  }
>  
>  /* Parse an external declaration (C90 6.7, C99 6.9, C11 6.9).
> diff --git a/gcc/doc/analyzer.texi b/gcc/doc/analyzer.texi
> index ec49f951435..d61b55cec5a 100644
> --- a/gcc/doc/analyzer.texi
> +++ b/gcc/doc/analyzer.texi
> @@ -524,6 +524,23 @@ With a non-zero argument
>  
>  it will also dump all of the states within the ``processed'' nodes.
>  
> +The builtin @code{__analyzer_dump_named_constant} will emit a
> warning
> +during analysis describing what is known about the value of a given
> +named constant, for parts of the analyzer that interact with target
> +headers.
> +
> +For example:
> +
> +@smallexample
> +__analyzer_dump_named_constant ("O_RDONLY");
> +@end smallexample
> +
> +might emit the warning:
> +
> +@smallexample
> +warning: named constant 'O_RDONLY' has value '1'
> +@end smallexample
> +
>  @smallexample
>     __analyzer_dump_region_model ();
>  @end smallexample
> diff --git a/gcc/testsuite/gcc.dg/analyzer/analyzer-decls.h
> b/gcc/testsuite/gcc.dg/analyzer/analyzer-decls.h
> index 4478d740b58..d9a32ed9370 100644
> --- a/gcc/testsuite/gcc.dg/analyzer/analyzer-decls.h
> +++ b/gcc/testsuite/gcc.dg/analyzer/analyzer-decls.h
> @@ -31,6 +31,9 @@ extern void __analyzer_dump_escaped (void);
>     will also dump all of the states within those nodes.  */
>  extern void __analyzer_dump_exploded_nodes (int);
>  
> +/* Emit a warning describing what is known about the value of NAME. 
> */
> +extern void __analyzer_dump_named_constant (const char *name);
> +
>  /* Emit a placeholder "note" diagnostic with a path to this call
> site,
>     if the analyzer finds a feasible path to it.  */
>  extern void __analyzer_dump_path (void);
> diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-4.c
> b/gcc/testsuite/gcc.dg/analyzer/fd-4.c
> index 842a26b4364..994bad84342 100644
> --- a/gcc/testsuite/gcc.dg/analyzer/fd-4.c
> +++ b/gcc/testsuite/gcc.dg/analyzer/fd-4.c
> @@ -8,6 +8,7 @@ void close(int fd);
>  int write (int fd, void *buf, int nbytes);
>  int read (int fd, void *buf, int nbytes);
>  
> +#define O_ACCMODE 0xf
>  #define O_RDONLY 0
>  #define O_WRONLY 1
>  #define O_RDWR 2
> diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-access-mode-enum.c
> b/gcc/testsuite/gcc.dg/analyzer/fd-access-mode-enum.c
> new file mode 100644
> index 00000000000..5226569c437
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/analyzer/fd-access-mode-enum.c
> @@ -0,0 +1,60 @@
> +int open(const char *, int mode);
> +void close(int fd);
> +int write (int fd, void *buf, int nbytes);
> +int read (int fd, void *buf, int nbytes);
> +
> +/* Example of these flags as an enum, and with
> +   non-standard values for them.  */
> +
> +enum {
> +      O_RDONLY  = 0x10,
> +      O_WRONLY  = 0x20,
> +      O_RDWR    = 0x40,
> +
> +      O_ACCMODE = 0xf0
> +};
> +
> +void f (int fd) __attribute__((fd_arg(1))); /* { dg-message
> "argument 1 of 'f' must be an open file descriptor, due to
> '__attribute__\\(\\(fd_arg\\(1\\)\\)\\)'" } */
> +
> +void
> +test_1 (const char *path)
> +{
> +    int fd = open (path, O_RDWR);
> +    close(fd);
> +    f(fd); /* { dg-warning "'f' on closed file descriptor 'fd'" } */
> +      /* { dg-message "\\(3\\) 'f' on closed file descriptor 'fd';
> 'close' was at \\(2\\)" "" { target *-*-* } .-1 } */
> +}
> +
> +void g (int fd) __attribute__((fd_arg_read(1))); /* { dg-message
> "argument 1 of 'g' must be a readable file descriptor, due to
> '__attribute__\\(\\(fd_arg_read\\(1\\)\\)\\)'" } */
> +
> +void
> +test_2 (const char *path)
> +{
> +  int fd = open (path, O_WRONLY);
> +  if (fd != -1)
> +  {
> +    g (fd); /* { dg-warning "'g' on write-only file descriptor 'fd'"
> } */
> +  }
> +  close (fd);
> +}
> +
> +void h (int fd) __attribute__((fd_arg_write(1))); /* { dg-message
> "argument 1 of 'h' must be a writable file descriptor, due to
> '__attribute__\\(\\(fd_arg_write\\(1\\)\\)\\)'" } */
> +void
> +test_3 (const char *path)
> +{
> +  int fd = open (path, O_RDONLY);
> +  if (fd != -1)
> +  {
> +    h (fd); /* { dg-warning "'h' on read-only file descriptor 'fd'"
> } */
> +  }
> +  close(fd);
> +}
> +
> +void ff (int fd) __attribute__((fd_arg(1))); /* { dg-message
> "argument 1 of 'ff' must be an open file descriptor, due to
> '__attribute__\\(\\(fd_arg\\(1\\)\\)\\)'" } */
> +
> +void test_4 (const char *path)
> +{
> +  int fd = open (path, O_RDWR);
> +  ff (fd); /* { dg-warning "'ff' on possibly invalid file descriptor
> 'fd'" } */
> +  close(fd);
> +}
> diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-5.c
> b/gcc/testsuite/gcc.dg/analyzer/fd-access-mode-macros.c
> similarity index 98%
> rename from gcc/testsuite/gcc.dg/analyzer/fd-5.c
> rename to gcc/testsuite/gcc.dg/analyzer/fd-access-mode-macros.c
> index c18b2adcbe5..f9a6931a5db 100644
> --- a/gcc/testsuite/gcc.dg/analyzer/fd-5.c
> +++ b/gcc/testsuite/gcc.dg/analyzer/fd-access-mode-macros.c
> @@ -6,6 +6,7 @@ int read (int fd, void *buf, int nbytes);
>  #define O_RDONLY 0
>  #define O_WRONLY 1
>  #define O_RDWR 2
> +#define O_ACCMODE 0x3
>  
>  void f (int fd) __attribute__((fd_arg(1))); /* { dg-message
> "argument 1 of 'f' must be an open file descriptor, due to
> '__attribute__\\(\\(fd_arg\\(1\\)\\)\\)'" } */
>  
> diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-access-mode-target-
> headers.c b/gcc/testsuite/gcc.dg/analyzer/fd-access-mode-target-
> headers.c
> new file mode 100644
> index 00000000000..b76eb667d50
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/analyzer/fd-access-mode-target-headers.c
> @@ -0,0 +1,56 @@
> +#include <sys/stat.h>
> +#include <fcntl.h>
> +#include <unistd.h>
> +#include "analyzer-decls.h"
> +
> +void f (int fd) __attribute__((fd_arg(1))); /* { dg-message
> "argument 1 of 'f' must be an open file descriptor, due to
> '__attribute__\\(\\(fd_arg\\(1\\)\\)\\)'" } */
> +
> +void
> +test_1 (const char *path)
> +{
> +    int fd = open (path, O_RDWR);
> +    close(fd);
> +    f(fd); /* { dg-warning "'f' on closed file descriptor 'fd'" } */
> +      /* { dg-message "\\(3\\) 'f' on closed file descriptor 'fd';
> 'close' was at \\(2\\)" "" { target *-*-* } .-1 } */
> +}
> +
> +void g (int fd) __attribute__((fd_arg_read(1))); /* { dg-message
> "argument 1 of 'g' must be a readable file descriptor, due to
> '__attribute__\\(\\(fd_arg_read\\(1\\)\\)\\)'" } */
> +
> +void
> +test_2 (const char *path)
> +{
> +  int fd = open (path, O_WRONLY);
> +  if (fd != -1)
> +  {
> +    g (fd); /* { dg-warning "'g' on write-only file descriptor 'fd'"
> } */
> +  }
> +  close (fd);
> +}
> +
> +void h (int fd) __attribute__((fd_arg_write(1))); /* { dg-message
> "argument 1 of 'h' must be a writable file descriptor, due to
> '__attribute__\\(\\(fd_arg_write\\(1\\)\\)\\)'" } */
> +void
> +test_3 (const char *path)
> +{
> +  int fd = open (path, O_RDONLY);
> +  if (fd != -1)
> +  {
> +    h (fd); /* { dg-warning "'h' on read-only file descriptor 'fd'"
> } */
> +  }
> +  close(fd);
> +}
> +
> +void ff (int fd) __attribute__((fd_arg(1))); /* { dg-message
> "argument 1 of 'ff' must be an open file descriptor, due to
> '__attribute__\\(\\(fd_arg\\(1\\)\\)\\)'" } */
> +
> +void test_4 (const char *path)
> +{
> +  int fd = open (path, O_RDWR);
> +  ff (fd); /* { dg-warning "'ff' on possibly invalid file descriptor
> 'fd'" } */
> +  close(fd);
> +}
> +
> +void test_sm_fd_constants (void)
> +{
> +  __analyzer_dump_named_constant ("O_ACCMODE"); /* { dg-warning
> "named constant 'O_ACCMODE' has value '\[0-9\]+'" } */
> +  __analyzer_dump_named_constant ("O_RDONLY"); /* { dg-warning
> "named constant 'O_RDONLY' has value '\[0-9\]+'" } */
> +  __analyzer_dump_named_constant ("O_WRONLY"); /* { dg-warning
> "named constant 'O_WRONLY' has value '\[0-9\]+'" } */
> +}
> diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-dup-1.c
> b/gcc/testsuite/gcc.dg/analyzer/fd-dup-1.c
> index b4f43e7f0ef..bb58e9d9d5e 100644
> --- a/gcc/testsuite/gcc.dg/analyzer/fd-dup-1.c
> +++ b/gcc/testsuite/gcc.dg/analyzer/fd-dup-1.c
> @@ -8,6 +8,7 @@ int read (int fd, void *buf, int nbytes);
>  #define O_RDONLY 0
>  #define O_WRONLY 1
>  #define O_RDWR 2
> +#define O_ACCMODE 3
>  
>  void test_1 (const char *path)
>  {
> diff --git a/gcc/testsuite/gcc.dg/analyzer/named-constants-via-enum.c
> b/gcc/testsuite/gcc.dg/analyzer/named-constants-via-enum.c
> new file mode 100644
> index 00000000000..e6b77b8dd18
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/analyzer/named-constants-via-enum.c
> @@ -0,0 +1,20 @@
> +#include "analyzer-decls.h"
> +
> +/* Various constants used by the fd state machine.  */
> +enum {
> +      O_ACCMODE = 42,
> +      O_RDONLY  = 0x1,
> +      O_WRONLY  = 010
> +};
> +
> +void test_sm_fd_constants (void)
> +{
> +  __analyzer_dump_named_constant ("O_ACCMODE"); /* { dg-warning
> "named constant 'O_ACCMODE' has value '42'" } */
> +  __analyzer_dump_named_constant ("O_RDONLY"); /* { dg-warning
> "named constant 'O_RDONLY' has value '1'" } */
> +  __analyzer_dump_named_constant ("O_WRONLY"); /* { dg-warning
> "named constant 'O_WRONLY' has value '8'" } */
> +}
> +
> +void test_unknown (void)
> +{
> +  __analyzer_dump_named_constant ("UNKNOWN"); /* { dg-warning "named
> constant 'UNKNOWN' has unknown value" } */
> +}
> diff --git a/gcc/testsuite/gcc.dg/analyzer/named-constants-via-
> macros-2.c b/gcc/testsuite/gcc.dg/analyzer/named-constants-via-
> macros-2.c
> new file mode 100644
> index 00000000000..9c019e7c5ef
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/analyzer/named-constants-via-macros-2.c
> @@ -0,0 +1,15 @@
> +#include "analyzer-decls.h"
> +
> +/* Various constants used by the fd state machine, as macros
> +   that can't be handled.  */
> +
> +#define O_ACCMODE (
> +#define O_RDONLY  "foo"
> +#define O_WRONLY  int
> +
> +void test_sm_fd_constants (void)
> +{
> +  __analyzer_dump_named_constant ("O_ACCMODE"); /* { dg-warning
> "named constant 'O_ACCMODE' has unknown value" } */
> +  __analyzer_dump_named_constant ("O_RDONLY"); /* { dg-warning
> "named constant 'O_RDONLY' has unknown value" } */
> +  __analyzer_dump_named_constant ("O_WRONLY"); /* { dg-warning
> "named constant 'O_WRONLY' has unknown value" } */
> +}
> diff --git a/gcc/testsuite/gcc.dg/analyzer/named-constants-via-
> macros.c b/gcc/testsuite/gcc.dg/analyzer/named-constants-via-macros.c
> new file mode 100644
> index 00000000000..2022f98e5b6
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/analyzer/named-constants-via-macros.c
> @@ -0,0 +1,19 @@
> +#include "analyzer-decls.h"
> +
> +/* Various constants used by the fd state machine.  */
> +
> +#define O_ACCMODE 42
> +#define O_RDONLY  0x1
> +#define O_WRONLY  010
> +
> +void test_sm_fd_constants (void)
> +{
> +  __analyzer_dump_named_constant ("O_ACCMODE"); /* { dg-warning
> "named constant 'O_ACCMODE' has value '42'" } */
> +  __analyzer_dump_named_constant ("O_RDONLY"); /* { dg-warning
> "named constant 'O_RDONLY' has value '1'" } */
> +  __analyzer_dump_named_constant ("O_WRONLY"); /* { dg-warning
> "named constant 'O_WRONLY' has value '8'" } */
> +}
> +
> +void test_unknown (void)
> +{
> +  __analyzer_dump_named_constant ("UNKNOWN"); /* { dg-warning "named
> constant 'UNKNOWN' has unknown value" } */
> +}


^ permalink raw reply	[flat|nested] 12+ messages in thread

* [PATCH v2] c, analyzer: support named constants in analyzer [PR106302]
  2022-11-08  3:06 ` PING: Re: [PATCH] c, analyzer: support named constants in analyzer [PR106302] David Malcolm
@ 2022-11-12  3:23   ` David Malcolm
  2022-11-12  3:32     ` David Malcolm
  2022-11-14 20:42     ` Marek Polacek
  0 siblings, 2 replies; 12+ messages in thread
From: David Malcolm @ 2022-11-12  3:23 UTC (permalink / raw)
  To: gcc-patches, Joseph Myers; +Cc: Marek Polacek, David Malcolm

Changes since v1: ported the doc changes from texinfo to sphinx

Successfully bootstrapped & regrtested on x86_64-pc-linux-gnu.

Are the C frontend parts OK for trunk?  (I can self-approve the
analyzer parts)

Thanks
Dave


The analyzer's file-descriptor state machine tracks the access mode of
opened files, so that it can emit -Wanalyzer-fd-access-mode-mismatch.

To do this, its symbolic execution needs to "know" the values of the
constants "O_RDONLY", "O_WRONLY", and "O_ACCMODE".  Currently
analyzer/sm-fd.cc simply uses these values directly from the build-time
header files, but these are the values on the host, not those from the
target, which could be different (PR analyzer/106302).

In an earlier discussion of this issue:
  https://gcc.gnu.org/pipermail/gcc/2022-June/238954.html
we talked about adding a target hook for this.

However, I've also been experimenting with extending the fd state
machine to track sockets (PR analyzer/106140).  For this, it's useful to
"know" the values of the constants "SOCK_STREAM" and "SOCK_DGRAM".
Unfortunately, these seem to have many arbitrary differences from target
to target.

For example: Linux/glibc general has SOCK_STREAM == 1, SOCK_DGRAM == 2,
as does AIX, but annoyingly, e.g. Linux on MIPS has them the other way
around.

It seems to me that as the analyzer grows more ambitious modeling of the
behavior of APIs (perhaps via plugins) it's more likely that the
analyzer will need to know the values of named constants, which might
not even exist on the host.

For example, at LPC it was suggested to me that -fanalyzer could check
rules about memory management inside the Linux kernel (probably via a
plugin), but doing so involves a bunch of GFP_* flags (see PR 107472).

So rather than trying to capture all this knowledge in a target hook,
this patch attempts to get at named constant values from the user's
source code.

The patch adds an interface for frontends to call into the analyzer as
the translation unit finishes.  The analyzer can then call back into the
frontend to ask about the values of the named constants it cares about
whilst the frontend's data structures are still around.

The patch implements this for the C frontend, which looks up the names
by looking for named CONST_DECLs (which handles enum values).  Failing
that, it attempts to look up the values of macros but only the simplest
cases are supported (a non-traditional macro with a single CPP_NUMBER
token).  It does this by building a buffer containing the macro
definition and rerunning a lexer on it.

The analyzer gracefully handles the cases where named values aren't
found (such as anything more complicated than described above).

The patch ports the analyzer to use this mechanism for "O_RDONLY",
"O_WRONLY", and "O_ACCMODE".  I have successfully tested my socket patch
to also use this for "SOCK_STREAM" and "SOCK_DGRAM", so the technique
seems to work.

gcc/ChangeLog:
	PR analyzer/106302
	* Makefile.in (ANALYZER_OBJS): Add analyzer/analyzer-language.o.
	(GTFILES): Add analyzer/analyzer-language.cc.
	* doc/gccint/debugging-the-analyzer.rst: Document
	__analyzer_dump_named_constant.

gcc/analyzer/ChangeLog:
	PR analyzer/106302
	* analyzer-language.cc: New file.
	* analyzer-language.h: New file.
	* analyzer.h (get_stashed_constant_by_name): New decl.
	(log_stashed_constants): New decl.
	* engine.cc (impl_run_checkers): Call log_stashed_constants.
	* region-model-impl-calls.cc
	(region_model::impl_call_analyzer_dump_named_constant): New.
	* region-model.cc (region_model::on_stmt_pre): Handle
	__analyzer_dump_named_constant.
	* region-model.h
	(region_model::impl_call_analyzer_dump_named_constant): New decl.
	* sm-fd.cc (fd_state_machine::m_O_ACCMODE): New.
	(fd_state_machine::m_O_RDONLY): New.
	(fd_state_machine::m_O_WRONLY): New.
	(fd_state_machine::fd_state_machine): Initialize the new fields.
	(fd_state_machine::get_access_mode_from_flag): Use the new fields,
	rather than using the host values.

gcc/c/ChangeLog:
	PR analyzer/106302
	* c-parser.cc: Include "analyzer/analyzer-language.h" and "toplev.h".
	(class ana::c_translation_unit): New.
	(c_parser_translation_unit): Call ana::on_finish_translation_unit.

gcc/testsuite/ChangeLog:
	* gcc.dg/analyzer/analyzer-decls.h
	(__analyzer_dump_named_constant): New decl.
	* gcc.dg/analyzer/fd-4.c (void): Likewise.
	(O_ACCMODE): Define.
	* gcc.dg/analyzer/fd-access-mode-enum.c: New test, based on .
	* gcc.dg/analyzer/fd-5.c: ...this.  Rename to...
	* gcc.dg/analyzer/fd-access-mode-macros.c: ...this.
	(O_ACCMODE): Define.
	* gcc.dg/analyzer/fd-access-mode-target-headers.c: New test, also
	based on fd-5.c.
	(test_sm_fd_constants): New.
	* gcc.dg/analyzer/fd-dup-1.c (O_ACCMODE): Define.
	* gcc.dg/analyzer/named-constants-via-enum.c: New test.
	* gcc.dg/analyzer/named-constants-via-macros-2.c: New test.
	* gcc.dg/analyzer/named-constants-via-macros.c: New test.

Signed-off-by: David Malcolm <dmalcolm@redhat.com>
---
 gcc/Makefile.in                               |   2 +
 gcc/analyzer/analyzer-language.cc             | 110 ++++++++++++++++++
 gcc/analyzer/analyzer-language.h              |  48 ++++++++
 gcc/analyzer/analyzer.h                       |   3 +
 gcc/analyzer/engine.cc                        |   1 +
 gcc/analyzer/region-model-impl-calls.cc       |  28 +++++
 gcc/analyzer/region-model.cc                  |   4 +
 gcc/analyzer/region-model.h                   |   2 +
 gcc/analyzer/sm-fd.cc                         |  30 +++--
 gcc/c/c-parser.cc                             |  91 +++++++++++++++
 gcc/doc/gccint/debugging-the-analyzer.rst     |  17 +++
 .../gcc.dg/analyzer/analyzer-decls.h          |   3 +
 gcc/testsuite/gcc.dg/analyzer/fd-4.c          |   1 +
 .../gcc.dg/analyzer/fd-access-mode-enum.c     |  60 ++++++++++
 .../{fd-5.c => fd-access-mode-macros.c}       |   1 +
 .../analyzer/fd-access-mode-target-headers.c  |  56 +++++++++
 gcc/testsuite/gcc.dg/analyzer/fd-dup-1.c      |   1 +
 .../analyzer/named-constants-via-enum.c       |  20 ++++
 .../analyzer/named-constants-via-macros-2.c   |  15 +++
 .../analyzer/named-constants-via-macros.c     |  19 +++
 20 files changed, 502 insertions(+), 10 deletions(-)
 create mode 100644 gcc/analyzer/analyzer-language.cc
 create mode 100644 gcc/analyzer/analyzer-language.h
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/fd-access-mode-enum.c
 rename gcc/testsuite/gcc.dg/analyzer/{fd-5.c => fd-access-mode-macros.c} (98%)
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/fd-access-mode-target-headers.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/named-constants-via-enum.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/named-constants-via-macros-2.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/named-constants-via-macros.c

diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index 246a85a1677..684caedc2df 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -1236,6 +1236,7 @@ C_COMMON_OBJS = c-family/c-common.o c-family/c-cppbuiltin.o c-family/c-dump.o \
 ANALYZER_OBJS = \
 	analyzer/analysis-plan.o \
 	analyzer/analyzer.o \
+	analyzer/analyzer-language.o \
 	analyzer/analyzer-logging.o \
 	analyzer/analyzer-pass.o \
 	analyzer/analyzer-selftests.o \
@@ -2709,6 +2710,7 @@ GTFILES = $(CPPLIB_H) $(srcdir)/input.h $(srcdir)/coretypes.h \
   $(srcdir)/internal-fn.h \
   $(srcdir)/calls.cc \
   $(srcdir)/omp-general.h \
+  $(srcdir)/analyzer/analyzer-language.cc \
   @all_gtfiles@
 
 # Compute the list of GT header files from the corresponding C sources,
diff --git a/gcc/analyzer/analyzer-language.cc b/gcc/analyzer/analyzer-language.cc
new file mode 100644
index 00000000000..ba4352b729a
--- /dev/null
+++ b/gcc/analyzer/analyzer-language.cc
@@ -0,0 +1,110 @@
+/* Interface between analyzer and frontends.
+   Copyright (C) 2022 Free Software Foundation, Inc.
+   Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3, or (at your option)
+any later version.
+
+GCC is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+#include "config.h"
+#define INCLUDE_MEMORY
+#include "system.h"
+#include "coretypes.h"
+#include "tree.h"
+#include "stringpool.h"
+#include "analyzer/analyzer.h"
+#include "analyzer/analyzer-language.h"
+#include "analyzer/analyzer-logging.h"
+
+/* Map from identifier to INTEGER_CST.  */
+static GTY (()) hash_map <tree, tree> *analyzer_stashed_constants;
+
+#if ENABLE_ANALYZER
+
+namespace ana {
+
+/* Call into TU to try to find a value for NAME.
+   If found, stash its value within analyzer_stashed_constants.  */
+
+static void
+maybe_stash_named_constant (const translation_unit &tu, const char *name)
+{
+  if (!analyzer_stashed_constants)
+    analyzer_stashed_constants = hash_map<tree, tree>::create_ggc ();
+
+  tree id = get_identifier (name);
+  if (tree t = tu.lookup_constant_by_id (id))
+    {
+      gcc_assert (TREE_CODE (t) == INTEGER_CST);
+      analyzer_stashed_constants->put (id, t);
+    }
+}
+
+/* Hook for frontend to call into analyzer when TU finishes.
+   This exists so that the analyzer can stash named constant values from
+   header files (e.g. macros and enums) for later use when modeling the
+   behaviors of APIs.
+
+   By doing it this way, the analyzer can use the precise values for those
+   constants from the user's headers, rather than attempting to model them
+   as properties of the target.  */
+
+void
+on_finish_translation_unit (const translation_unit &tu)
+{
+  /* Bail if the analyzer isn't enabled.  */
+  if (!flag_analyzer)
+    return;
+
+  /* Stash named constants for use by sm-fd.cc  */
+  maybe_stash_named_constant (tu, "O_ACCMODE");
+  maybe_stash_named_constant (tu, "O_RDONLY");
+  maybe_stash_named_constant (tu, "O_WRONLY");
+}
+
+/* Lookup NAME in the named constants stashed when the frontend TU finished.
+   Return either an INTEGER_CST, or NULL_TREE.  */
+
+tree
+get_stashed_constant_by_name (const char *name)
+{
+  if (!analyzer_stashed_constants)
+    return NULL_TREE;
+  tree id = get_identifier (name);
+  if (tree *slot = analyzer_stashed_constants->get (id))
+    {
+      gcc_assert (TREE_CODE (*slot) == INTEGER_CST);
+      return *slot;
+    }
+  return NULL_TREE;
+}
+
+/* Log all stashed named constants to LOGGER.  */
+
+void
+log_stashed_constants (logger *logger)
+{
+  gcc_assert (logger);
+  LOG_SCOPE (logger);
+  if (analyzer_stashed_constants)
+    for (auto iter : *analyzer_stashed_constants)
+      logger->log ("%qE: %qE", iter.first, iter.second);
+}
+
+} // namespace ana
+
+#endif /* #if ENABLE_ANALYZER */
+
+#include "gt-analyzer-language.h"
diff --git a/gcc/analyzer/analyzer-language.h b/gcc/analyzer/analyzer-language.h
new file mode 100644
index 00000000000..33c4dd60623
--- /dev/null
+++ b/gcc/analyzer/analyzer-language.h
@@ -0,0 +1,48 @@
+/* Interface between analyzer and frontends.
+   Copyright (C) 2022 Free Software Foundation, Inc.
+   Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3, or (at your option)
+any later version.
+
+GCC is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+#ifndef GCC_ANALYZER_LANGUAGE_H
+#define GCC_ANALYZER_LANGUAGE_H
+
+#if ENABLE_ANALYZER
+
+namespace ana {
+
+/* Abstract base class for representing a specific TU
+   to the analyzer.  */
+
+class translation_unit
+{
+ public:
+  /* Attempt to look up an  value for identifier ID (e.g. in the headers that
+     have been seen).  If it is defined and an integer (e.g. either as a
+     macro or enum), return the INTEGER_CST value, otherwise return NULL.  */
+  virtual tree lookup_constant_by_id (tree id) const = 0;
+};
+
+/* Analyzer hook for frontends to call at the end of the TU.  */
+
+void on_finish_translation_unit (const translation_unit &tu);
+
+} // namespace ana
+
+#endif /* #if ENABLE_ANALYZER */
+
+#endif /* GCC_ANALYZER_LANGUAGE_H */
diff --git a/gcc/analyzer/analyzer.h b/gcc/analyzer/analyzer.h
index c0041c35d1a..9cf8d98fabe 100644
--- a/gcc/analyzer/analyzer.h
+++ b/gcc/analyzer/analyzer.h
@@ -311,6 +311,9 @@ public:
   virtual bool terminate_path_p () const = 0;
 };
 
+extern tree get_stashed_constant_by_name (const char *name);
+extern void log_stashed_constants (logger *logger);
+
 } // namespace ana
 
 extern bool is_special_named_call_p (const gcall *call, const char *funcname,
diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc
index d0595ef0d07..891be7c5c90 100644
--- a/gcc/analyzer/engine.cc
+++ b/gcc/analyzer/engine.cc
@@ -6010,6 +6010,7 @@ impl_run_checkers (logger *logger)
       logger->log ("BITS_BIG_ENDIAN: %i", BITS_BIG_ENDIAN ? 1 : 0);
       logger->log ("BYTES_BIG_ENDIAN: %i", BYTES_BIG_ENDIAN ? 1 : 0);
       logger->log ("WORDS_BIG_ENDIAN: %i", WORDS_BIG_ENDIAN ? 1 : 0);
+      log_stashed_constants (logger);
     }
 
   /* If using LTO, ensure that the cgraph nodes have function bodies.  */
diff --git a/gcc/analyzer/region-model-impl-calls.cc b/gcc/analyzer/region-model-impl-calls.cc
index 9ef31f6ab05..a7134ed90bb 100644
--- a/gcc/analyzer/region-model-impl-calls.cc
+++ b/gcc/analyzer/region-model-impl-calls.cc
@@ -352,6 +352,34 @@ region_model::impl_call_analyzer_dump_escaped (const gcall *call)
 	      pp_formatted_text (&pp));
 }
 
+/* Handle a call to "__analyzer_dump_named_constant".
+
+   Look up the given name, and emit a warning describing the
+   state of the corresponding stashed value.
+
+   This is for use when debugging, and for DejaGnu tests.  */
+
+void
+region_model::
+impl_call_analyzer_dump_named_constant (const gcall *call,
+					region_model_context *ctxt)
+{
+  call_details cd (call, this, ctxt);
+  const char *name = cd.get_arg_string_literal (0);
+  if (!name)
+    {
+      error_at (call->location, "cannot determine name");
+      return;
+    }
+  tree value = get_stashed_constant_by_name (name);
+  if (value)
+    warning_at (call->location, 0, "named constant %qs has value %qE",
+		name, value);
+  else
+    warning_at (call->location, 0, "named constant %qs has unknown value",
+		name);
+}
+
 /* Handle a call to "__analyzer_eval" by evaluating the input
    and dumping as a dummy warning, so that test cases can use
    dg-warning to validate the result (and so unexpected warnings will
diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc
index b91434d7db4..5bae3cf5cd4 100644
--- a/gcc/analyzer/region-model.cc
+++ b/gcc/analyzer/region-model.cc
@@ -1229,6 +1229,10 @@ region_model::on_stmt_pre (const gimple *stmt,
 	  impl_call_analyzer_dump_capacity (call, ctxt);
 	else if (is_special_named_call_p (call, "__analyzer_dump_escaped", 0))
 	  impl_call_analyzer_dump_escaped (call);
+	else if (is_special_named_call_p (call,
+					  "__analyzer_dump_named_constant",
+					  1))
+	  impl_call_analyzer_dump_named_constant (call, ctxt);
 	else if (is_special_named_call_p (call, "__analyzer_dump_path", 0))
 	  {
 	    /* Handle the builtin "__analyzer_dump_path" by queuing a
diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h
index 50790596726..bd81e6b6b9d 100644
--- a/gcc/analyzer/region-model.h
+++ b/gcc/analyzer/region-model.h
@@ -344,6 +344,8 @@ class region_model
   void impl_call_analyzer_dump_capacity (const gcall *call,
 					 region_model_context *ctxt);
   void impl_call_analyzer_dump_escaped (const gcall *call);
+  void impl_call_analyzer_dump_named_constant (const gcall *call,
+					       region_model_context *ctxt);
   void impl_call_analyzer_eval (const gcall *call,
 				region_model_context *ctxt);
   void impl_call_analyzer_get_unknown_ptr (const call_details &cd);
diff --git a/gcc/analyzer/sm-fd.cc b/gcc/analyzer/sm-fd.cc
index da0e92b5113..370115d56bf 100644
--- a/gcc/analyzer/sm-fd.cc
+++ b/gcc/analyzer/sm-fd.cc
@@ -159,6 +159,11 @@ public:
   /* State for a file descriptor that we do not want to track anymore . */
   state_t m_stop;
 
+  /* Stashed constant values from the frontend.  These could be NULL.  */
+  tree m_O_ACCMODE;
+  tree m_O_RDONLY;
+  tree m_O_WRONLY;
+
 private:
   void on_open (sm_context *sm_ctxt, const supernode *node, const gimple *stmt,
 		const gcall *call) const;
@@ -686,7 +691,10 @@ 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_stop (add_state ("fd-stop"))
+      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"))
 {
 }
 
@@ -709,16 +717,18 @@ fd_state_machine::is_valid_fd_p (state_t s) const
 enum access_mode
 fd_state_machine::get_access_mode_from_flag (int flag) const
 {
-  /* FIXME: this code assumes the access modes on the host and
-     target are the same, which in practice might not be the case.  */
-
-  if ((flag & O_ACCMODE) == O_RDONLY)
-    {
-      return READ_ONLY;
-    }
-  else if ((flag & O_ACCMODE) == O_WRONLY)
+  if (m_O_ACCMODE && TREE_CODE (m_O_ACCMODE) == INTEGER_CST)
     {
-      return WRITE_ONLY;
+      const unsigned HOST_WIDE_INT mask_val = TREE_INT_CST_LOW (m_O_ACCMODE);
+      const unsigned HOST_WIDE_INT masked_flag = flag & mask_val;
+
+      if (m_O_RDONLY && TREE_CODE (m_O_RDONLY) == INTEGER_CST)
+	if (masked_flag == TREE_INT_CST_LOW (m_O_RDONLY))
+	  return READ_ONLY;
+
+      if (m_O_WRONLY && TREE_CODE (m_O_WRONLY) == INTEGER_CST)
+	if (masked_flag == TREE_INT_CST_LOW (m_O_WRONLY))
+	  return WRITE_ONLY;
     }
   return READ_WRITE;
 }
diff --git a/gcc/c/c-parser.cc b/gcc/c/c-parser.cc
index d70697b1d63..efe19fbe70b 100644
--- a/gcc/c/c-parser.cc
+++ b/gcc/c/c-parser.cc
@@ -72,6 +72,8 @@ along with GCC; see the file COPYING3.  If not see
 #include "memmodel.h"
 #include "c-family/known-headers.h"
 #include "bitmap.h"
+#include "analyzer/analyzer-language.h"
+#include "toplev.h"
 
 /* We need to walk over decls with incomplete struct/union/enum types
    after parsing the whole translation unit.
@@ -1662,6 +1664,87 @@ static bool c_parser_objc_diagnose_bad_element_prefix
   (c_parser *, struct c_declspecs *);
 static location_t c_parser_parse_rtl_body (c_parser *, char *);
 
+#if ENABLE_ANALYZER
+
+namespace ana {
+
+/* Concrete implementation of ana::translation_unit for the C frontend.  */
+
+class c_translation_unit : public translation_unit
+{
+public:
+  /* Implementation of translation_unit::lookup_constant_by_id for use by the
+     analyzer to look up named constants in the user's source code.  */
+  tree lookup_constant_by_id (tree id) const final override
+  {
+    /* Consider decls.  */
+    if (tree decl = lookup_name (id))
+      if (TREE_CODE (decl) == CONST_DECL)
+	if (tree value = DECL_INITIAL (decl))
+	  if (TREE_CODE (value) == INTEGER_CST)
+	    return value;
+
+    /* Consider macros.  */
+    cpp_hashnode *hashnode = C_CPP_HASHNODE (id);
+    if (cpp_macro_p (hashnode))
+      if (tree value = consider_macro (hashnode->value.macro))
+	return value;
+
+    return NULL_TREE;
+  }
+
+private:
+  /* Attempt to get an INTEGER_CST from MACRO.
+     Only handle the simplest cases: where MACRO's definition is a single
+     token containing a number, by lexing the number again.
+     This will handle e.g.
+       #define NAME 42
+     and other bases but not negative numbers, parentheses or e.g.
+       #define NAME 1 << 7
+     as doing so would require a parser.  */
+  tree consider_macro (cpp_macro *macro) const
+  {
+    if (macro->paramc > 0)
+      return NULL_TREE;
+    if (macro->kind == cmk_traditional)
+      return NULL_TREE;
+    if (macro->count != 1)
+      return NULL_TREE;
+    const cpp_token &tok = macro->exp.tokens[0];
+    if (tok.type != CPP_NUMBER)
+      return NULL_TREE;
+
+    cpp_reader *old_parse_in = parse_in;
+    parse_in = cpp_create_reader (c_dialect_cxx () ? CLK_GNUCXX: CLK_GNUC89,
+				  ident_hash, line_table);
+
+    pretty_printer pp;
+    pp_string (&pp, (const char *)tok.val.str.text);
+    pp_newline (&pp);
+    cpp_push_buffer (parse_in,
+		     (const unsigned char *)pp_formatted_text (&pp),
+		     strlen (pp_formatted_text (&pp)),
+		     0);
+
+    tree value;
+    location_t loc;
+    unsigned char cpp_flags;
+    c_lex_with_flags (&value, &loc, &cpp_flags, 0);
+
+    cpp_destroy (parse_in);
+    parse_in = old_parse_in;
+
+    if (value && TREE_CODE (value) == INTEGER_CST)
+      return value;
+
+    return NULL_TREE;
+  }
+};
+
+} // namespace ana
+
+#endif /* #if ENABLE_ANALYZER */
+
 /* Parse a translation unit (C90 6.7, C99 6.9, C11 6.9).
 
    translation-unit:
@@ -1722,6 +1805,14 @@ c_parser_translation_unit (c_parser *parser)
 	       "#pragma omp begin assumes", "#pragma omp end assumes");
       current_omp_begin_assumes = 0;
     }
+
+#if ENABLE_ANALYZER
+  if (flag_analyzer)
+    {
+      ana::c_translation_unit tu;
+      ana::on_finish_translation_unit (tu);
+    }
+#endif
 }
 
 /* Parse an external declaration (C90 6.7, C99 6.9, C11 6.9).
diff --git a/gcc/doc/gccint/debugging-the-analyzer.rst b/gcc/doc/gccint/debugging-the-analyzer.rst
index 4a09b39e166..099acf295b9 100644
--- a/gcc/doc/gccint/debugging-the-analyzer.rst
+++ b/gcc/doc/gccint/debugging-the-analyzer.rst
@@ -90,6 +90,23 @@ With a non-zero argument
 
 it will also dump all of the states within the 'processed' nodes.
 
+The builtin ``__analyzer_dump_named_constant`` will emit a warning
+during analysis describing what is known about the value of a given
+named constant, for parts of the analyzer that interact with target
+headers.
+
+For example:
+
+.. code-block:: c
+
+   __analyzer_dump_named_constant ("O_RDONLY");
+
+might emit the warning:
+
+.. code-block::
+
+   warning: named constant 'O_RDONLY' has value '1'
+
 .. code-block:: c++
 
      __analyzer_dump_region_model ();
diff --git a/gcc/testsuite/gcc.dg/analyzer/analyzer-decls.h b/gcc/testsuite/gcc.dg/analyzer/analyzer-decls.h
index 4478d740b58..d9a32ed9370 100644
--- a/gcc/testsuite/gcc.dg/analyzer/analyzer-decls.h
+++ b/gcc/testsuite/gcc.dg/analyzer/analyzer-decls.h
@@ -31,6 +31,9 @@ extern void __analyzer_dump_escaped (void);
    will also dump all of the states within those nodes.  */
 extern void __analyzer_dump_exploded_nodes (int);
 
+/* Emit a warning describing what is known about the value of NAME.  */
+extern void __analyzer_dump_named_constant (const char *name);
+
 /* Emit a placeholder "note" diagnostic with a path to this call site,
    if the analyzer finds a feasible path to it.  */
 extern void __analyzer_dump_path (void);
diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-4.c b/gcc/testsuite/gcc.dg/analyzer/fd-4.c
index 842a26b4364..994bad84342 100644
--- a/gcc/testsuite/gcc.dg/analyzer/fd-4.c
+++ b/gcc/testsuite/gcc.dg/analyzer/fd-4.c
@@ -8,6 +8,7 @@ void close(int fd);
 int write (int fd, void *buf, int nbytes);
 int read (int fd, void *buf, int nbytes);
 
+#define O_ACCMODE 0xf
 #define O_RDONLY 0
 #define O_WRONLY 1
 #define O_RDWR 2
diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-access-mode-enum.c b/gcc/testsuite/gcc.dg/analyzer/fd-access-mode-enum.c
new file mode 100644
index 00000000000..5226569c437
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/fd-access-mode-enum.c
@@ -0,0 +1,60 @@
+int open(const char *, int mode);
+void close(int fd);
+int write (int fd, void *buf, int nbytes);
+int read (int fd, void *buf, int nbytes);
+
+/* Example of these flags as an enum, and with
+   non-standard values for them.  */
+
+enum {
+      O_RDONLY  = 0x10,
+      O_WRONLY  = 0x20,
+      O_RDWR    = 0x40,
+
+      O_ACCMODE = 0xf0
+};
+
+void f (int fd) __attribute__((fd_arg(1))); /* { dg-message "argument 1 of 'f' must be an open file descriptor, due to '__attribute__\\(\\(fd_arg\\(1\\)\\)\\)'" } */
+
+void
+test_1 (const char *path)
+{
+    int fd = open (path, O_RDWR);
+    close(fd);
+    f(fd); /* { dg-warning "'f' on closed file descriptor 'fd'" } */
+      /* { dg-message "\\(3\\) 'f' on closed file descriptor 'fd'; 'close' was at \\(2\\)" "" { target *-*-* } .-1 } */
+}
+
+void g (int fd) __attribute__((fd_arg_read(1))); /* { dg-message "argument 1 of 'g' must be a readable file descriptor, due to '__attribute__\\(\\(fd_arg_read\\(1\\)\\)\\)'" } */
+
+void
+test_2 (const char *path)
+{
+  int fd = open (path, O_WRONLY);
+  if (fd != -1)
+  {
+    g (fd); /* { dg-warning "'g' on write-only file descriptor 'fd'" } */
+  }
+  close (fd);
+}
+
+void h (int fd) __attribute__((fd_arg_write(1))); /* { dg-message "argument 1 of 'h' must be a writable file descriptor, due to '__attribute__\\(\\(fd_arg_write\\(1\\)\\)\\)'" } */
+void
+test_3 (const char *path)
+{
+  int fd = open (path, O_RDONLY);
+  if (fd != -1)
+  {
+    h (fd); /* { dg-warning "'h' on read-only file descriptor 'fd'" } */
+  }
+  close(fd);
+}
+
+void ff (int fd) __attribute__((fd_arg(1))); /* { dg-message "argument 1 of 'ff' must be an open file descriptor, due to '__attribute__\\(\\(fd_arg\\(1\\)\\)\\)'" } */
+
+void test_4 (const char *path)
+{
+  int fd = open (path, O_RDWR);
+  ff (fd); /* { dg-warning "'ff' on possibly invalid file descriptor 'fd'" } */
+  close(fd);
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-5.c b/gcc/testsuite/gcc.dg/analyzer/fd-access-mode-macros.c
similarity index 98%
rename from gcc/testsuite/gcc.dg/analyzer/fd-5.c
rename to gcc/testsuite/gcc.dg/analyzer/fd-access-mode-macros.c
index c18b2adcbe5..f9a6931a5db 100644
--- a/gcc/testsuite/gcc.dg/analyzer/fd-5.c
+++ b/gcc/testsuite/gcc.dg/analyzer/fd-access-mode-macros.c
@@ -6,6 +6,7 @@ int read (int fd, void *buf, int nbytes);
 #define O_RDONLY 0
 #define O_WRONLY 1
 #define O_RDWR 2
+#define O_ACCMODE 0x3
 
 void f (int fd) __attribute__((fd_arg(1))); /* { dg-message "argument 1 of 'f' must be an open file descriptor, due to '__attribute__\\(\\(fd_arg\\(1\\)\\)\\)'" } */
 
diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-access-mode-target-headers.c b/gcc/testsuite/gcc.dg/analyzer/fd-access-mode-target-headers.c
new file mode 100644
index 00000000000..b76eb667d50
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/fd-access-mode-target-headers.c
@@ -0,0 +1,56 @@
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include "analyzer-decls.h"
+
+void f (int fd) __attribute__((fd_arg(1))); /* { dg-message "argument 1 of 'f' must be an open file descriptor, due to '__attribute__\\(\\(fd_arg\\(1\\)\\)\\)'" } */
+
+void
+test_1 (const char *path)
+{
+    int fd = open (path, O_RDWR);
+    close(fd);
+    f(fd); /* { dg-warning "'f' on closed file descriptor 'fd'" } */
+      /* { dg-message "\\(3\\) 'f' on closed file descriptor 'fd'; 'close' was at \\(2\\)" "" { target *-*-* } .-1 } */
+}
+
+void g (int fd) __attribute__((fd_arg_read(1))); /* { dg-message "argument 1 of 'g' must be a readable file descriptor, due to '__attribute__\\(\\(fd_arg_read\\(1\\)\\)\\)'" } */
+
+void
+test_2 (const char *path)
+{
+  int fd = open (path, O_WRONLY);
+  if (fd != -1)
+  {
+    g (fd); /* { dg-warning "'g' on write-only file descriptor 'fd'" } */
+  }
+  close (fd);
+}
+
+void h (int fd) __attribute__((fd_arg_write(1))); /* { dg-message "argument 1 of 'h' must be a writable file descriptor, due to '__attribute__\\(\\(fd_arg_write\\(1\\)\\)\\)'" } */
+void
+test_3 (const char *path)
+{
+  int fd = open (path, O_RDONLY);
+  if (fd != -1)
+  {
+    h (fd); /* { dg-warning "'h' on read-only file descriptor 'fd'" } */
+  }
+  close(fd);
+}
+
+void ff (int fd) __attribute__((fd_arg(1))); /* { dg-message "argument 1 of 'ff' must be an open file descriptor, due to '__attribute__\\(\\(fd_arg\\(1\\)\\)\\)'" } */
+
+void test_4 (const char *path)
+{
+  int fd = open (path, O_RDWR);
+  ff (fd); /* { dg-warning "'ff' on possibly invalid file descriptor 'fd'" } */
+  close(fd);
+}
+
+void test_sm_fd_constants (void)
+{
+  __analyzer_dump_named_constant ("O_ACCMODE"); /* { dg-warning "named constant 'O_ACCMODE' has value '\[0-9\]+'" } */
+  __analyzer_dump_named_constant ("O_RDONLY"); /* { dg-warning "named constant 'O_RDONLY' has value '\[0-9\]+'" } */
+  __analyzer_dump_named_constant ("O_WRONLY"); /* { dg-warning "named constant 'O_WRONLY' has value '\[0-9\]+'" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-dup-1.c b/gcc/testsuite/gcc.dg/analyzer/fd-dup-1.c
index b4f43e7f0ef..bb58e9d9d5e 100644
--- a/gcc/testsuite/gcc.dg/analyzer/fd-dup-1.c
+++ b/gcc/testsuite/gcc.dg/analyzer/fd-dup-1.c
@@ -8,6 +8,7 @@ int read (int fd, void *buf, int nbytes);
 #define O_RDONLY 0
 #define O_WRONLY 1
 #define O_RDWR 2
+#define O_ACCMODE 3
 
 void test_1 (const char *path)
 {
diff --git a/gcc/testsuite/gcc.dg/analyzer/named-constants-via-enum.c b/gcc/testsuite/gcc.dg/analyzer/named-constants-via-enum.c
new file mode 100644
index 00000000000..e6b77b8dd18
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/named-constants-via-enum.c
@@ -0,0 +1,20 @@
+#include "analyzer-decls.h"
+
+/* Various constants used by the fd state machine.  */
+enum {
+      O_ACCMODE = 42,
+      O_RDONLY  = 0x1,
+      O_WRONLY  = 010
+};
+
+void test_sm_fd_constants (void)
+{
+  __analyzer_dump_named_constant ("O_ACCMODE"); /* { dg-warning "named constant 'O_ACCMODE' has value '42'" } */
+  __analyzer_dump_named_constant ("O_RDONLY"); /* { dg-warning "named constant 'O_RDONLY' has value '1'" } */
+  __analyzer_dump_named_constant ("O_WRONLY"); /* { dg-warning "named constant 'O_WRONLY' has value '8'" } */
+}
+
+void test_unknown (void)
+{
+  __analyzer_dump_named_constant ("UNKNOWN"); /* { dg-warning "named constant 'UNKNOWN' has unknown value" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/named-constants-via-macros-2.c b/gcc/testsuite/gcc.dg/analyzer/named-constants-via-macros-2.c
new file mode 100644
index 00000000000..9c019e7c5ef
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/named-constants-via-macros-2.c
@@ -0,0 +1,15 @@
+#include "analyzer-decls.h"
+
+/* Various constants used by the fd state machine, as macros
+   that can't be handled.  */
+
+#define O_ACCMODE (
+#define O_RDONLY  "foo"
+#define O_WRONLY  int
+
+void test_sm_fd_constants (void)
+{
+  __analyzer_dump_named_constant ("O_ACCMODE"); /* { dg-warning "named constant 'O_ACCMODE' has unknown value" } */
+  __analyzer_dump_named_constant ("O_RDONLY"); /* { dg-warning "named constant 'O_RDONLY' has unknown value" } */
+  __analyzer_dump_named_constant ("O_WRONLY"); /* { dg-warning "named constant 'O_WRONLY' has unknown value" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/named-constants-via-macros.c b/gcc/testsuite/gcc.dg/analyzer/named-constants-via-macros.c
new file mode 100644
index 00000000000..2022f98e5b6
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/named-constants-via-macros.c
@@ -0,0 +1,19 @@
+#include "analyzer-decls.h"
+
+/* Various constants used by the fd state machine.  */
+
+#define O_ACCMODE 42
+#define O_RDONLY  0x1
+#define O_WRONLY  010
+
+void test_sm_fd_constants (void)
+{
+  __analyzer_dump_named_constant ("O_ACCMODE"); /* { dg-warning "named constant 'O_ACCMODE' has value '42'" } */
+  __analyzer_dump_named_constant ("O_RDONLY"); /* { dg-warning "named constant 'O_RDONLY' has value '1'" } */
+  __analyzer_dump_named_constant ("O_WRONLY"); /* { dg-warning "named constant 'O_WRONLY' has value '8'" } */
+}
+
+void test_unknown (void)
+{
+  __analyzer_dump_named_constant ("UNKNOWN"); /* { dg-warning "named constant 'UNKNOWN' has unknown value" } */
+}
-- 
2.26.3


^ permalink raw reply	[flat|nested] 12+ messages in thread

* [PATCH v2] analyzer: add warnings relating to sockets [PR106140]
  2022-11-08  3:02 ` [PATCH] analyzer: add warnings relating to sockets [PR106140] David Malcolm
@ 2022-11-12  3:27   ` David Malcolm
  2022-11-15 19:02     ` David Malcolm
  0 siblings, 1 reply; 12+ messages in thread
From: David Malcolm @ 2022-11-12  3:27 UTC (permalink / raw)
  To: gcc-patches; +Cc: David Malcolm

Changed in v2: ported doc changes from texinfo to sphinx

Successfully bootstrapped & regrtested on x86_64-pc-linux-gnu.

I can self-approve this patch, but it depends on the named constants
patch here:
  * [PATCH v2] c, analyzer: support named constants in analyzer [PR106302]
    * https://gcc.gnu.org/pipermail/gcc-patches/2022-November/605835.html
which requires review of the C frontend changes.


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/gcc/gcc-command-options/option-summary.rst:
	Add -Wanalyzer-fd-phase-mismatch and -Wanalyzer-fd-type-mismatch.
	* doc/gcc/gcc-command-options/options-that-control-static-analysis.rst:
	Likewise.

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>
---
 gcc/analyzer/analyzer-language.cc             |    2 +
 gcc/analyzer/analyzer.opt                     |    8 +
 gcc/analyzer/engine.cc                        |   60 +-
 gcc/analyzer/exploded-graph.h                 |   10 +-
 gcc/analyzer/region-model-impl-calls.cc       |  150 +++
 gcc/analyzer/region-model.cc                  |   30 +
 gcc/analyzer/region-model.h                   |   57 +-
 gcc/analyzer/sm-fd.cc                         | 1110 ++++++++++++++++-
 gcc/analyzer/sm-fd.dot                        |   64 +
 .../gcc-command-options/option-summary.rst    |    2 +
 .../options-that-control-static-analysis.rst  |   38 +
 gcc/testsuite/gcc.dg/analyzer/fd-accept.c     |   69 +
 gcc/testsuite/gcc.dg/analyzer/fd-bind.c       |   74 ++
 gcc/testsuite/gcc.dg/analyzer/fd-connect.c    |   46 +
 .../gcc.dg/analyzer/fd-datagram-socket.c      |  108 ++
 .../fd-glibc-byte-stream-connection-server.c  |  133 ++
 .../analyzer/fd-glibc-byte-stream-socket.c    |   62 +
 .../analyzer/fd-glibc-datagram-client.c       |   56 +
 .../analyzer/fd-glibc-datagram-socket.c       |   52 +
 .../analyzer/fd-glibc-make_named_socket.h     |   47 +
 gcc/testsuite/gcc.dg/analyzer/fd-listen.c     |   63 +
 .../analyzer/fd-manpage-getaddrinfo-client.c  |  122 ++
 .../analyzer/fd-mappage-getaddrinfo-server.c  |  119 ++
 .../gcc.dg/analyzer/fd-socket-meaning.c       |   21 +
 .../gcc.dg/analyzer/fd-socket-misuse.c        |   98 ++
 .../analyzer/fd-stream-socket-active-open.c   |   74 ++
 .../analyzer/fd-stream-socket-passive-open.c  |  197 +++
 .../gcc.dg/analyzer/fd-stream-socket.c        |   98 ++
 .../gcc.dg/analyzer/fd-symbolic-socket.c      |   98 ++
 gcc/testsuite/gcc.dg/analyzer/pr104369-1.c    |    4 +-
 gcc/testsuite/gcc.dg/analyzer/pr104369-2.c    |    3 +
 31 files changed, 3015 insertions(+), 60 deletions(-)
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/fd-accept.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/fd-bind.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/fd-connect.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/fd-datagram-socket.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/fd-glibc-byte-stream-connection-server.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/fd-glibc-byte-stream-socket.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/fd-glibc-datagram-client.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/fd-glibc-datagram-socket.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/fd-glibc-make_named_socket.h
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/fd-listen.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/fd-manpage-getaddrinfo-client.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/fd-mappage-getaddrinfo-server.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/fd-socket-meaning.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/fd-socket-misuse.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/fd-stream-socket-active-open.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/fd-stream-socket-passive-open.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/fd-stream-socket.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/fd-symbolic-socket.c

diff --git a/gcc/analyzer/analyzer-language.cc b/gcc/analyzer/analyzer-language.cc
index ba4352b729a..0629b681e7c 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 518a5d422ff..95f345687d6 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 891be7c5c90..3ef411cae93 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 a4cbc8f688a..86644c10835 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 a7134ed90bb..99597e0667a 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 5bae3cf5cd4..5f1dd0112d1 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 bd81e6b6b9d..1e72c551dfa 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 370115d56bf..d0b587143d0 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 175daae44ae..0b2b31996ec 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/gcc/gcc-command-options/option-summary.rst b/gcc/doc/gcc/gcc-command-options/option-summary.rst
index d068f98feac..f99ce2346c0 100644
--- a/gcc/doc/gcc/gcc-command-options/option-summary.rst
+++ b/gcc/doc/gcc/gcc-command-options/option-summary.rst
@@ -291,6 +291,8 @@ in the following sections.
   :option:`-Wno-analyzer-fd-access-mode-mismatch` |gol|
   :option:`-Wno-analyzer-fd-double-close` |gol|
   :option:`-Wno-analyzer-fd-leak` |gol|
+  :option:`-Wno-analyzer-fd-phase-mismatch` |gol|
+  :option:`-Wno-analyzer-fd-type-mismatch` |gol|
   :option:`-Wno-analyzer-fd-use-after-close` |gol|
   :option:`-Wno-analyzer-fd-use-without-check` |gol|
   :option:`-Wno-analyzer-file-leak` |gol|
diff --git a/gcc/doc/gcc/gcc-command-options/options-that-control-static-analysis.rst b/gcc/doc/gcc/gcc-command-options/options-that-control-static-analysis.rst
index 32a626c16a9..9b081f93fce 100644
--- a/gcc/doc/gcc/gcc-command-options/options-that-control-static-analysis.rst
+++ b/gcc/doc/gcc/gcc-command-options/options-that-control-static-analysis.rst
@@ -27,6 +27,8 @@ Options That Control Static Analysis
   :option:`-Wanalyzer-fd-access-mode-mismatch` |gol|
   :option:`-Wanalyzer-fd-double-close` |gol|
   :option:`-Wanalyzer-fd-leak` |gol|
+  :option:`-Wanalyzer-fd-phase-mismatch` |gol|
+  :option:`-Wanalyzer-fd-type-mismatch` |gol|
   :option:`-Wanalyzer-fd-use-after-close` |gol|
   :option:`-Wanalyzer-fd-use-without-check` |gol|
   :option:`-Wanalyzer-file-leak` |gol|
@@ -228,6 +230,39 @@ Options That Control Static Analysis
 
   Default setting; overrides :option:`-Wno-analyzer-fd-leak`.
 
+.. option:: -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 ``accept`` on a stream
+  socket that has not yet had ``listen`` successfully called on it.
+
+  See `CWE-666: Operation on Resource in Wrong Phase of Lifetime <https://cwe.mitre.org/data/definitions/666.html>`_.
+
+.. option:: -Wanalyzer-fd-phase-mismatch
+
+  Default setting; overrides :option:`-Wno-analyzer-fd-phase-mismatch`.
+
+.. option:: -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 ``open``, or when attempting
+  to use a stream socket operation on a datagram socket.
+
+.. option:: -Wanalyzer-fd-type-mismatch
+
+  Default setting; overrides :option:`-Wno-analyzer-fd-type-mismatch`.
+
 .. option:: -Wno-analyzer-fd-use-after-close
 
   This warning requires :option:`-fanalyzer`, which enables it; use
@@ -798,6 +833,7 @@ of the following functions for working with file descriptors:
 * ``pipe`` and ``pipe2``
 * ``read``
 * ``write``
+* ``socket``, ``bind``, ``listen``, ``accept`` and ``connect``
 
 of the following functions for working with ``<stdio.h>`` streams:
 
@@ -888,6 +924,8 @@ The following options control the analyzer.
     :option:`-Wanalyzer-fd-access-mode-mismatch`  |gol|
     :option:`-Wanalyzer-fd-double-close`  |gol|
     :option:`-Wanalyzer-fd-leak`  |gol|
+    :option:`-Wanalyzer-fd-phase-mismatch` |gol|
+    :option:`-Wanalyzer-fd-type-mismatch` |gol|
     :option:`-Wanalyzer-fd-use-after-close`  |gol|
     :option:`-Wanalyzer-fd-use-without-check`  |gol|
     :option:`-Wanalyzer-file-leak`  |gol|
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 00000000000..36cc7af7184
--- /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 00000000000..6f91bc4b794
--- /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 00000000000..1ab54d01f36
--- /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 00000000000..045bdfa32d3
--- /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 00000000000..1ff902894af
--- /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 00000000000..f96da8101cc
--- /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 00000000000..888c751e88d
--- /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 00000000000..b8b68768c34
--- /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 00000000000..bdb6de0ae15
--- /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 00000000000..1f54a8f2953
--- /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 00000000000..d9c3ff05de8
--- /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 00000000000..66398e834cc
--- /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 00000000000..5bfb57f68fb
--- /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 00000000000..4ff08d5ec19
--- /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 00000000000..7fde0ef6285
--- /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 00000000000..c31e5b5eefb
--- /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 00000000000..3a292d0e2d2
--- /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 00000000000..83400c18f50
--- /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 d3b32418a9e..c05137bb219 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 57dc9caf3e9..93d9987d0ba 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
-- 
2.26.3


^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH v2] c, analyzer: support named constants in analyzer [PR106302]
  2022-11-12  3:23   ` [PATCH v2] " David Malcolm
@ 2022-11-12  3:32     ` David Malcolm
  2022-11-14 20:42     ` Marek Polacek
  1 sibling, 0 replies; 12+ messages in thread
From: David Malcolm @ 2022-11-12  3:32 UTC (permalink / raw)
  To: gcc-patches, Joseph Myers; +Cc: Marek Polacek

On Fri, 2022-11-11 at 22:23 -0500, David Malcolm wrote:
> Changes since v1: ported the doc changes from texinfo to sphinx
> 
> Successfully bootstrapped & regrtested on x86_64-pc-linux-gnu.
> 
> Are the C frontend parts OK for trunk?  (I can self-approve the
> analyzer parts)

...and FWIW, the followup patch that uses this to add checking of
sockets to -fanalyzer is here:

 [PATCH v2] analyzer: add warnings relating to sockets [PR106140]
    https://gcc.gnu.org/pipermail/gcc-patches/2022-November/605836.html

Thanks
Dave

> 
> Thanks
> Dave
> 
> 
> The analyzer's file-descriptor state machine tracks the access mode
> of
> opened files, so that it can emit -Wanalyzer-fd-access-mode-mismatch.
> 
> To do this, its symbolic execution needs to "know" the values of the
> constants "O_RDONLY", "O_WRONLY", and "O_ACCMODE".  Currently
> analyzer/sm-fd.cc simply uses these values directly from the build-
> time
> header files, but these are the values on the host, not those from
> the
> target, which could be different (PR analyzer/106302).
> 
> In an earlier discussion of this issue:
>   https://gcc.gnu.org/pipermail/gcc/2022-June/238954.html
> we talked about adding a target hook for this.
> 
> However, I've also been experimenting with extending the fd state
> machine to track sockets (PR analyzer/106140).  For this, it's useful
> to
> "know" the values of the constants "SOCK_STREAM" and "SOCK_DGRAM".
> Unfortunately, these seem to have many arbitrary differences from
> target
> to target.
> 
> For example: Linux/glibc general has SOCK_STREAM == 1, SOCK_DGRAM ==
> 2,
> as does AIX, but annoyingly, e.g. Linux on MIPS has them the other
> way
> around.
> 
> It seems to me that as the analyzer grows more ambitious modeling of
> the
> behavior of APIs (perhaps via plugins) it's more likely that the
> analyzer will need to know the values of named constants, which might
> not even exist on the host.
> 
> For example, at LPC it was suggested to me that -fanalyzer could
> check
> rules about memory management inside the Linux kernel (probably via a
> plugin), but doing so involves a bunch of GFP_* flags (see PR
> 107472).
> 
> So rather than trying to capture all this knowledge in a target hook,
> this patch attempts to get at named constant values from the user's
> source code.
> 
> The patch adds an interface for frontends to call into the analyzer
> as
> the translation unit finishes.  The analyzer can then call back into
> the
> frontend to ask about the values of the named constants it cares
> about
> whilst the frontend's data structures are still around.
> 
> The patch implements this for the C frontend, which looks up the
> names
> by looking for named CONST_DECLs (which handles enum values). 
> Failing
> that, it attempts to look up the values of macros but only the
> simplest
> cases are supported (a non-traditional macro with a single CPP_NUMBER
> token).  It does this by building a buffer containing the macro
> definition and rerunning a lexer on it.
> 
> The analyzer gracefully handles the cases where named values aren't
> found (such as anything more complicated than described above).
> 
> The patch ports the analyzer to use this mechanism for "O_RDONLY",
> "O_WRONLY", and "O_ACCMODE".  I have successfully tested my socket
> patch
> to also use this for "SOCK_STREAM" and "SOCK_DGRAM", so the technique
> seems to work.
> 
> gcc/ChangeLog:
>         PR analyzer/106302
>         * Makefile.in (ANALYZER_OBJS): Add analyzer/analyzer-
> language.o.
>         (GTFILES): Add analyzer/analyzer-language.cc.
>         * doc/gccint/debugging-the-analyzer.rst: Document
>         __analyzer_dump_named_constant.
> 
> gcc/analyzer/ChangeLog:
>         PR analyzer/106302
>         * analyzer-language.cc: New file.
>         * analyzer-language.h: New file.
>         * analyzer.h (get_stashed_constant_by_name): New decl.
>         (log_stashed_constants): New decl.
>         * engine.cc (impl_run_checkers): Call log_stashed_constants.
>         * region-model-impl-calls.cc
>         (region_model::impl_call_analyzer_dump_named_constant): New.
>         * region-model.cc (region_model::on_stmt_pre): Handle
>         __analyzer_dump_named_constant.
>         * region-model.h
>         (region_model::impl_call_analyzer_dump_named_constant): New
> decl.
>         * sm-fd.cc (fd_state_machine::m_O_ACCMODE): New.
>         (fd_state_machine::m_O_RDONLY): New.
>         (fd_state_machine::m_O_WRONLY): New.
>         (fd_state_machine::fd_state_machine): Initialize the new
> fields.
>         (fd_state_machine::get_access_mode_from_flag): Use the new
> fields,
>         rather than using the host values.
> 
> gcc/c/ChangeLog:
>         PR analyzer/106302
>         * c-parser.cc: Include "analyzer/analyzer-language.h" and
> "toplev.h".
>         (class ana::c_translation_unit): New.
>         (c_parser_translation_unit): Call
> ana::on_finish_translation_unit.
> 
> gcc/testsuite/ChangeLog:
>         * gcc.dg/analyzer/analyzer-decls.h
>         (__analyzer_dump_named_constant): New decl.
>         * gcc.dg/analyzer/fd-4.c (void): Likewise.
>         (O_ACCMODE): Define.
>         * gcc.dg/analyzer/fd-access-mode-enum.c: New test, based on .
>         * gcc.dg/analyzer/fd-5.c: ...this.  Rename to...
>         * gcc.dg/analyzer/fd-access-mode-macros.c: ...this.
>         (O_ACCMODE): Define.
>         * gcc.dg/analyzer/fd-access-mode-target-headers.c: New test,
> also
>         based on fd-5.c.
>         (test_sm_fd_constants): New.
>         * gcc.dg/analyzer/fd-dup-1.c (O_ACCMODE): Define.
>         * gcc.dg/analyzer/named-constants-via-enum.c: New test.
>         * gcc.dg/analyzer/named-constants-via-macros-2.c: New test.
>         * gcc.dg/analyzer/named-constants-via-macros.c: New test.
> 
> Signed-off-by: David Malcolm <dmalcolm@redhat.com>
> ---
>  gcc/Makefile.in                               |   2 +
>  gcc/analyzer/analyzer-language.cc             | 110
> ++++++++++++++++++
>  gcc/analyzer/analyzer-language.h              |  48 ++++++++
>  gcc/analyzer/analyzer.h                       |   3 +
>  gcc/analyzer/engine.cc                        |   1 +
>  gcc/analyzer/region-model-impl-calls.cc       |  28 +++++
>  gcc/analyzer/region-model.cc                  |   4 +
>  gcc/analyzer/region-model.h                   |   2 +
>  gcc/analyzer/sm-fd.cc                         |  30 +++--
>  gcc/c/c-parser.cc                             |  91 +++++++++++++++
>  gcc/doc/gccint/debugging-the-analyzer.rst     |  17 +++
>  .../gcc.dg/analyzer/analyzer-decls.h          |   3 +
>  gcc/testsuite/gcc.dg/analyzer/fd-4.c          |   1 +
>  .../gcc.dg/analyzer/fd-access-mode-enum.c     |  60 ++++++++++
>  .../{fd-5.c => fd-access-mode-macros.c}       |   1 +
>  .../analyzer/fd-access-mode-target-headers.c  |  56 +++++++++
>  gcc/testsuite/gcc.dg/analyzer/fd-dup-1.c      |   1 +
>  .../analyzer/named-constants-via-enum.c       |  20 ++++
>  .../analyzer/named-constants-via-macros-2.c   |  15 +++
>  .../analyzer/named-constants-via-macros.c     |  19 +++
>  20 files changed, 502 insertions(+), 10 deletions(-)
>  create mode 100644 gcc/analyzer/analyzer-language.cc
>  create mode 100644 gcc/analyzer/analyzer-language.h
>  create mode 100644 gcc/testsuite/gcc.dg/analyzer/fd-access-mode-
> enum.c
>  rename gcc/testsuite/gcc.dg/analyzer/{fd-5.c => fd-access-mode-
> macros.c} (98%)
>  create mode 100644 gcc/testsuite/gcc.dg/analyzer/fd-access-mode-
> target-headers.c
>  create mode 100644 gcc/testsuite/gcc.dg/analyzer/named-constants-
> via-enum.c
>  create mode 100644 gcc/testsuite/gcc.dg/analyzer/named-constants-
> via-macros-2.c
>  create mode 100644 gcc/testsuite/gcc.dg/analyzer/named-constants-
> via-macros.c
> 
> diff --git a/gcc/Makefile.in b/gcc/Makefile.in
> index 246a85a1677..684caedc2df 100644
> --- a/gcc/Makefile.in
> +++ b/gcc/Makefile.in
> @@ -1236,6 +1236,7 @@ C_COMMON_OBJS = c-family/c-common.o c-family/c-
> cppbuiltin.o c-family/c-dump.o \
>  ANALYZER_OBJS = \
>         analyzer/analysis-plan.o \
>         analyzer/analyzer.o \
> +       analyzer/analyzer-language.o \
>         analyzer/analyzer-logging.o \
>         analyzer/analyzer-pass.o \
>         analyzer/analyzer-selftests.o \
> @@ -2709,6 +2710,7 @@ GTFILES = $(CPPLIB_H) $(srcdir)/input.h
> $(srcdir)/coretypes.h \
>    $(srcdir)/internal-fn.h \
>    $(srcdir)/calls.cc \
>    $(srcdir)/omp-general.h \
> +  $(srcdir)/analyzer/analyzer-language.cc \
>    @all_gtfiles@
>  
>  # Compute the list of GT header files from the corresponding C
> sources,
> diff --git a/gcc/analyzer/analyzer-language.cc
> b/gcc/analyzer/analyzer-language.cc
> new file mode 100644
> index 00000000000..ba4352b729a
> --- /dev/null
> +++ b/gcc/analyzer/analyzer-language.cc
> @@ -0,0 +1,110 @@
> +/* Interface between analyzer and frontends.
> +   Copyright (C) 2022 Free Software Foundation, Inc.
> +   Contributed by David Malcolm <dmalcolm@redhat.com>.
> +
> +This file is part of GCC.
> +
> +GCC is free software; you can redistribute it and/or modify it
> +under the terms of the GNU General Public License as published by
> +the Free Software Foundation; either version 3, or (at your option)
> +any later version.
> +
> +GCC is distributed in the hope that it will be useful, but
> +WITHOUT ANY WARRANTY; without even the implied warranty of
> +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +General Public License for more details.
> +
> +You should have received a copy of the GNU General Public License
> +along with GCC; see the file COPYING3.  If not see
> +<http://www.gnu.org/licenses/>.  */
> +
> +#include "config.h"
> +#define INCLUDE_MEMORY
> +#include "system.h"
> +#include "coretypes.h"
> +#include "tree.h"
> +#include "stringpool.h"
> +#include "analyzer/analyzer.h"
> +#include "analyzer/analyzer-language.h"
> +#include "analyzer/analyzer-logging.h"
> +
> +/* Map from identifier to INTEGER_CST.  */
> +static GTY (()) hash_map <tree, tree> *analyzer_stashed_constants;
> +
> +#if ENABLE_ANALYZER
> +
> +namespace ana {
> +
> +/* Call into TU to try to find a value for NAME.
> +   If found, stash its value within analyzer_stashed_constants.  */
> +
> +static void
> +maybe_stash_named_constant (const translation_unit &tu, const char
> *name)
> +{
> +  if (!analyzer_stashed_constants)
> +    analyzer_stashed_constants = hash_map<tree, tree>::create_ggc
> ();
> +
> +  tree id = get_identifier (name);
> +  if (tree t = tu.lookup_constant_by_id (id))
> +    {
> +      gcc_assert (TREE_CODE (t) == INTEGER_CST);
> +      analyzer_stashed_constants->put (id, t);
> +    }
> +}
> +
> +/* Hook for frontend to call into analyzer when TU finishes.
> +   This exists so that the analyzer can stash named constant values
> from
> +   header files (e.g. macros and enums) for later use when modeling
> the
> +   behaviors of APIs.
> +
> +   By doing it this way, the analyzer can use the precise values for
> those
> +   constants from the user's headers, rather than attempting to
> model them
> +   as properties of the target.  */
> +
> +void
> +on_finish_translation_unit (const translation_unit &tu)
> +{
> +  /* Bail if the analyzer isn't enabled.  */
> +  if (!flag_analyzer)
> +    return;
> +
> +  /* Stash named constants for use by sm-fd.cc  */
> +  maybe_stash_named_constant (tu, "O_ACCMODE");
> +  maybe_stash_named_constant (tu, "O_RDONLY");
> +  maybe_stash_named_constant (tu, "O_WRONLY");
> +}
> +
> +/* Lookup NAME in the named constants stashed when the frontend TU
> finished.
> +   Return either an INTEGER_CST, or NULL_TREE.  */
> +
> +tree
> +get_stashed_constant_by_name (const char *name)
> +{
> +  if (!analyzer_stashed_constants)
> +    return NULL_TREE;
> +  tree id = get_identifier (name);
> +  if (tree *slot = analyzer_stashed_constants->get (id))
> +    {
> +      gcc_assert (TREE_CODE (*slot) == INTEGER_CST);
> +      return *slot;
> +    }
> +  return NULL_TREE;
> +}
> +
> +/* Log all stashed named constants to LOGGER.  */
> +
> +void
> +log_stashed_constants (logger *logger)
> +{
> +  gcc_assert (logger);
> +  LOG_SCOPE (logger);
> +  if (analyzer_stashed_constants)
> +    for (auto iter : *analyzer_stashed_constants)
> +      logger->log ("%qE: %qE", iter.first, iter.second);
> +}
> +
> +} // namespace ana
> +
> +#endif /* #if ENABLE_ANALYZER */
> +
> +#include "gt-analyzer-language.h"
> diff --git a/gcc/analyzer/analyzer-language.h
> b/gcc/analyzer/analyzer-language.h
> new file mode 100644
> index 00000000000..33c4dd60623
> --- /dev/null
> +++ b/gcc/analyzer/analyzer-language.h
> @@ -0,0 +1,48 @@
> +/* Interface between analyzer and frontends.
> +   Copyright (C) 2022 Free Software Foundation, Inc.
> +   Contributed by David Malcolm <dmalcolm@redhat.com>.
> +
> +This file is part of GCC.
> +
> +GCC is free software; you can redistribute it and/or modify it
> +under the terms of the GNU General Public License as published by
> +the Free Software Foundation; either version 3, or (at your option)
> +any later version.
> +
> +GCC is distributed in the hope that it will be useful, but
> +WITHOUT ANY WARRANTY; without even the implied warranty of
> +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +General Public License for more details.
> +
> +You should have received a copy of the GNU General Public License
> +along with GCC; see the file COPYING3.  If not see
> +<http://www.gnu.org/licenses/>.  */
> +
> +#ifndef GCC_ANALYZER_LANGUAGE_H
> +#define GCC_ANALYZER_LANGUAGE_H
> +
> +#if ENABLE_ANALYZER
> +
> +namespace ana {
> +
> +/* Abstract base class for representing a specific TU
> +   to the analyzer.  */
> +
> +class translation_unit
> +{
> + public:
> +  /* Attempt to look up an  value for identifier ID (e.g. in the
> headers that
> +     have been seen).  If it is defined and an integer (e.g. either
> as a
> +     macro or enum), return the INTEGER_CST value, otherwise return
> NULL.  */
> +  virtual tree lookup_constant_by_id (tree id) const = 0;
> +};
> +
> +/* Analyzer hook for frontends to call at the end of the TU.  */
> +
> +void on_finish_translation_unit (const translation_unit &tu);
> +
> +} // namespace ana
> +
> +#endif /* #if ENABLE_ANALYZER */
> +
> +#endif /* GCC_ANALYZER_LANGUAGE_H */
> diff --git a/gcc/analyzer/analyzer.h b/gcc/analyzer/analyzer.h
> index c0041c35d1a..9cf8d98fabe 100644
> --- a/gcc/analyzer/analyzer.h
> +++ b/gcc/analyzer/analyzer.h
> @@ -311,6 +311,9 @@ public:
>    virtual bool terminate_path_p () const = 0;
>  };
>  
> +extern tree get_stashed_constant_by_name (const char *name);
> +extern void log_stashed_constants (logger *logger);
> +
>  } // namespace ana
>  
>  extern bool is_special_named_call_p (const gcall *call, const char
> *funcname,
> diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc
> index d0595ef0d07..891be7c5c90 100644
> --- a/gcc/analyzer/engine.cc
> +++ b/gcc/analyzer/engine.cc
> @@ -6010,6 +6010,7 @@ impl_run_checkers (logger *logger)
>        logger->log ("BITS_BIG_ENDIAN: %i", BITS_BIG_ENDIAN ? 1 : 0);
>        logger->log ("BYTES_BIG_ENDIAN: %i", BYTES_BIG_ENDIAN ? 1 :
> 0);
>        logger->log ("WORDS_BIG_ENDIAN: %i", WORDS_BIG_ENDIAN ? 1 :
> 0);
> +      log_stashed_constants (logger);
>      }
>  
>    /* If using LTO, ensure that the cgraph nodes have function
> bodies.  */
> diff --git a/gcc/analyzer/region-model-impl-calls.cc
> b/gcc/analyzer/region-model-impl-calls.cc
> index 9ef31f6ab05..a7134ed90bb 100644
> --- a/gcc/analyzer/region-model-impl-calls.cc
> +++ b/gcc/analyzer/region-model-impl-calls.cc
> @@ -352,6 +352,34 @@ region_model::impl_call_analyzer_dump_escaped
> (const gcall *call)
>               pp_formatted_text (&pp));
>  }
>  
> +/* Handle a call to "__analyzer_dump_named_constant".
> +
> +   Look up the given name, and emit a warning describing the
> +   state of the corresponding stashed value.
> +
> +   This is for use when debugging, and for DejaGnu tests.  */
> +
> +void
> +region_model::
> +impl_call_analyzer_dump_named_constant (const gcall *call,
> +                                       region_model_context *ctxt)
> +{
> +  call_details cd (call, this, ctxt);
> +  const char *name = cd.get_arg_string_literal (0);
> +  if (!name)
> +    {
> +      error_at (call->location, "cannot determine name");
> +      return;
> +    }
> +  tree value = get_stashed_constant_by_name (name);
> +  if (value)
> +    warning_at (call->location, 0, "named constant %qs has value
> %qE",
> +               name, value);
> +  else
> +    warning_at (call->location, 0, "named constant %qs has unknown
> value",
> +               name);
> +}
> +
>  /* Handle a call to "__analyzer_eval" by evaluating the input
>     and dumping as a dummy warning, so that test cases can use
>     dg-warning to validate the result (and so unexpected warnings
> will
> diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-
> model.cc
> index b91434d7db4..5bae3cf5cd4 100644
> --- a/gcc/analyzer/region-model.cc
> +++ b/gcc/analyzer/region-model.cc
> @@ -1229,6 +1229,10 @@ region_model::on_stmt_pre (const gimple *stmt,
>           impl_call_analyzer_dump_capacity (call, ctxt);
>         else if (is_special_named_call_p (call,
> "__analyzer_dump_escaped", 0))
>           impl_call_analyzer_dump_escaped (call);
> +       else if (is_special_named_call_p (call,
> +                                        
> "__analyzer_dump_named_constant",
> +                                         1))
> +         impl_call_analyzer_dump_named_constant (call, ctxt);
>         else if (is_special_named_call_p (call,
> "__analyzer_dump_path", 0))
>           {
>             /* Handle the builtin "__analyzer_dump_path" by queuing a
> diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-
> model.h
> index 50790596726..bd81e6b6b9d 100644
> --- a/gcc/analyzer/region-model.h
> +++ b/gcc/analyzer/region-model.h
> @@ -344,6 +344,8 @@ class region_model
>    void impl_call_analyzer_dump_capacity (const gcall *call,
>                                          region_model_context *ctxt);
>    void impl_call_analyzer_dump_escaped (const gcall *call);
> +  void impl_call_analyzer_dump_named_constant (const gcall *call,
> +                                              region_model_context
> *ctxt);
>    void impl_call_analyzer_eval (const gcall *call,
>                                 region_model_context *ctxt);
>    void impl_call_analyzer_get_unknown_ptr (const call_details &cd);
> diff --git a/gcc/analyzer/sm-fd.cc b/gcc/analyzer/sm-fd.cc
> index da0e92b5113..370115d56bf 100644
> --- a/gcc/analyzer/sm-fd.cc
> +++ b/gcc/analyzer/sm-fd.cc
> @@ -159,6 +159,11 @@ public:
>    /* State for a file descriptor that we do not want to track
> anymore . */
>    state_t m_stop;
>  
> +  /* Stashed constant values from the frontend.  These could be
> NULL.  */
> +  tree m_O_ACCMODE;
> +  tree m_O_RDONLY;
> +  tree m_O_WRONLY;
> +
>  private:
>    void on_open (sm_context *sm_ctxt, const supernode *node, const
> gimple *stmt,
>                 const gcall *call) const;
> @@ -686,7 +691,10 @@ 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_stop (add_state ("fd-stop"))
> +      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"))
>  {
>  }
>  
> @@ -709,16 +717,18 @@ fd_state_machine::is_valid_fd_p (state_t s)
> const
>  enum access_mode
>  fd_state_machine::get_access_mode_from_flag (int flag) const
>  {
> -  /* FIXME: this code assumes the access modes on the host and
> -     target are the same, which in practice might not be the case. 
> */
> -
> -  if ((flag & O_ACCMODE) == O_RDONLY)
> -    {
> -      return READ_ONLY;
> -    }
> -  else if ((flag & O_ACCMODE) == O_WRONLY)
> +  if (m_O_ACCMODE && TREE_CODE (m_O_ACCMODE) == INTEGER_CST)
>      {
> -      return WRITE_ONLY;
> +      const unsigned HOST_WIDE_INT mask_val = TREE_INT_CST_LOW
> (m_O_ACCMODE);
> +      const unsigned HOST_WIDE_INT masked_flag = flag & mask_val;
> +
> +      if (m_O_RDONLY && TREE_CODE (m_O_RDONLY) == INTEGER_CST)
> +       if (masked_flag == TREE_INT_CST_LOW (m_O_RDONLY))
> +         return READ_ONLY;
> +
> +      if (m_O_WRONLY && TREE_CODE (m_O_WRONLY) == INTEGER_CST)
> +       if (masked_flag == TREE_INT_CST_LOW (m_O_WRONLY))
> +         return WRITE_ONLY;
>      }
>    return READ_WRITE;
>  }
> diff --git a/gcc/c/c-parser.cc b/gcc/c/c-parser.cc
> index d70697b1d63..efe19fbe70b 100644
> --- a/gcc/c/c-parser.cc
> +++ b/gcc/c/c-parser.cc
> @@ -72,6 +72,8 @@ along with GCC; see the file COPYING3.  If not see
>  #include "memmodel.h"
>  #include "c-family/known-headers.h"
>  #include "bitmap.h"
> +#include "analyzer/analyzer-language.h"
> +#include "toplev.h"
>  
>  /* We need to walk over decls with incomplete struct/union/enum
> types
>     after parsing the whole translation unit.
> @@ -1662,6 +1664,87 @@ static bool
> c_parser_objc_diagnose_bad_element_prefix
>    (c_parser *, struct c_declspecs *);
>  static location_t c_parser_parse_rtl_body (c_parser *, char *);
>  
> +#if ENABLE_ANALYZER
> +
> +namespace ana {
> +
> +/* Concrete implementation of ana::translation_unit for the C
> frontend.  */
> +
> +class c_translation_unit : public translation_unit
> +{
> +public:
> +  /* Implementation of translation_unit::lookup_constant_by_id for
> use by the
> +     analyzer to look up named constants in the user's source code. 
> */
> +  tree lookup_constant_by_id (tree id) const final override
> +  {
> +    /* Consider decls.  */
> +    if (tree decl = lookup_name (id))
> +      if (TREE_CODE (decl) == CONST_DECL)
> +       if (tree value = DECL_INITIAL (decl))
> +         if (TREE_CODE (value) == INTEGER_CST)
> +           return value;
> +
> +    /* Consider macros.  */
> +    cpp_hashnode *hashnode = C_CPP_HASHNODE (id);
> +    if (cpp_macro_p (hashnode))
> +      if (tree value = consider_macro (hashnode->value.macro))
> +       return value;
> +
> +    return NULL_TREE;
> +  }
> +
> +private:
> +  /* Attempt to get an INTEGER_CST from MACRO.
> +     Only handle the simplest cases: where MACRO's definition is a
> single
> +     token containing a number, by lexing the number again.
> +     This will handle e.g.
> +       #define NAME 42
> +     and other bases but not negative numbers, parentheses or e.g.
> +       #define NAME 1 << 7
> +     as doing so would require a parser.  */
> +  tree consider_macro (cpp_macro *macro) const
> +  {
> +    if (macro->paramc > 0)
> +      return NULL_TREE;
> +    if (macro->kind == cmk_traditional)
> +      return NULL_TREE;
> +    if (macro->count != 1)
> +      return NULL_TREE;
> +    const cpp_token &tok = macro->exp.tokens[0];
> +    if (tok.type != CPP_NUMBER)
> +      return NULL_TREE;
> +
> +    cpp_reader *old_parse_in = parse_in;
> +    parse_in = cpp_create_reader (c_dialect_cxx () ? CLK_GNUCXX:
> CLK_GNUC89,
> +                                 ident_hash, line_table);
> +
> +    pretty_printer pp;
> +    pp_string (&pp, (const char *)tok.val.str.text);
> +    pp_newline (&pp);
> +    cpp_push_buffer (parse_in,
> +                    (const unsigned char *)pp_formatted_text (&pp),
> +                    strlen (pp_formatted_text (&pp)),
> +                    0);
> +
> +    tree value;
> +    location_t loc;
> +    unsigned char cpp_flags;
> +    c_lex_with_flags (&value, &loc, &cpp_flags, 0);
> +
> +    cpp_destroy (parse_in);
> +    parse_in = old_parse_in;
> +
> +    if (value && TREE_CODE (value) == INTEGER_CST)
> +      return value;
> +
> +    return NULL_TREE;
> +  }
> +};
> +
> +} // namespace ana
> +
> +#endif /* #if ENABLE_ANALYZER */
> +
>  /* Parse a translation unit (C90 6.7, C99 6.9, C11 6.9).
>  
>     translation-unit:
> @@ -1722,6 +1805,14 @@ c_parser_translation_unit (c_parser *parser)
>                "#pragma omp begin assumes", "#pragma omp end
> assumes");
>        current_omp_begin_assumes = 0;
>      }
> +
> +#if ENABLE_ANALYZER
> +  if (flag_analyzer)
> +    {
> +      ana::c_translation_unit tu;
> +      ana::on_finish_translation_unit (tu);
> +    }
> +#endif
>  }
>  
>  /* Parse an external declaration (C90 6.7, C99 6.9, C11 6.9).
> diff --git a/gcc/doc/gccint/debugging-the-analyzer.rst
> b/gcc/doc/gccint/debugging-the-analyzer.rst
> index 4a09b39e166..099acf295b9 100644
> --- a/gcc/doc/gccint/debugging-the-analyzer.rst
> +++ b/gcc/doc/gccint/debugging-the-analyzer.rst
> @@ -90,6 +90,23 @@ With a non-zero argument
>  
>  it will also dump all of the states within the 'processed' nodes.
>  
> +The builtin ``__analyzer_dump_named_constant`` will emit a warning
> +during analysis describing what is known about the value of a given
> +named constant, for parts of the analyzer that interact with target
> +headers.
> +
> +For example:
> +
> +.. code-block:: c
> +
> +   __analyzer_dump_named_constant ("O_RDONLY");
> +
> +might emit the warning:
> +
> +.. code-block::
> +
> +   warning: named constant 'O_RDONLY' has value '1'
> +
>  .. code-block:: c++
>  
>       __analyzer_dump_region_model ();
> diff --git a/gcc/testsuite/gcc.dg/analyzer/analyzer-decls.h
> b/gcc/testsuite/gcc.dg/analyzer/analyzer-decls.h
> index 4478d740b58..d9a32ed9370 100644
> --- a/gcc/testsuite/gcc.dg/analyzer/analyzer-decls.h
> +++ b/gcc/testsuite/gcc.dg/analyzer/analyzer-decls.h
> @@ -31,6 +31,9 @@ extern void __analyzer_dump_escaped (void);
>     will also dump all of the states within those nodes.  */
>  extern void __analyzer_dump_exploded_nodes (int);
>  
> +/* Emit a warning describing what is known about the value of NAME. 
> */
> +extern void __analyzer_dump_named_constant (const char *name);
> +
>  /* Emit a placeholder "note" diagnostic with a path to this call
> site,
>     if the analyzer finds a feasible path to it.  */
>  extern void __analyzer_dump_path (void);
> diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-4.c
> b/gcc/testsuite/gcc.dg/analyzer/fd-4.c
> index 842a26b4364..994bad84342 100644
> --- a/gcc/testsuite/gcc.dg/analyzer/fd-4.c
> +++ b/gcc/testsuite/gcc.dg/analyzer/fd-4.c
> @@ -8,6 +8,7 @@ void close(int fd);
>  int write (int fd, void *buf, int nbytes);
>  int read (int fd, void *buf, int nbytes);
>  
> +#define O_ACCMODE 0xf
>  #define O_RDONLY 0
>  #define O_WRONLY 1
>  #define O_RDWR 2
> diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-access-mode-enum.c
> b/gcc/testsuite/gcc.dg/analyzer/fd-access-mode-enum.c
> new file mode 100644
> index 00000000000..5226569c437
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/analyzer/fd-access-mode-enum.c
> @@ -0,0 +1,60 @@
> +int open(const char *, int mode);
> +void close(int fd);
> +int write (int fd, void *buf, int nbytes);
> +int read (int fd, void *buf, int nbytes);
> +
> +/* Example of these flags as an enum, and with
> +   non-standard values for them.  */
> +
> +enum {
> +      O_RDONLY  = 0x10,
> +      O_WRONLY  = 0x20,
> +      O_RDWR    = 0x40,
> +
> +      O_ACCMODE = 0xf0
> +};
> +
> +void f (int fd) __attribute__((fd_arg(1))); /* { dg-message
> "argument 1 of 'f' must be an open file descriptor, due to
> '__attribute__\\(\\(fd_arg\\(1\\)\\)\\)'" } */
> +
> +void
> +test_1 (const char *path)
> +{
> +    int fd = open (path, O_RDWR);
> +    close(fd);
> +    f(fd); /* { dg-warning "'f' on closed file descriptor 'fd'" } */
> +      /* { dg-message "\\(3\\) 'f' on closed file descriptor 'fd';
> 'close' was at \\(2\\)" "" { target *-*-* } .-1 } */
> +}
> +
> +void g (int fd) __attribute__((fd_arg_read(1))); /* { dg-message
> "argument 1 of 'g' must be a readable file descriptor, due to
> '__attribute__\\(\\(fd_arg_read\\(1\\)\\)\\)'" } */
> +
> +void
> +test_2 (const char *path)
> +{
> +  int fd = open (path, O_WRONLY);
> +  if (fd != -1)
> +  {
> +    g (fd); /* { dg-warning "'g' on write-only file descriptor 'fd'"
> } */
> +  }
> +  close (fd);
> +}
> +
> +void h (int fd) __attribute__((fd_arg_write(1))); /* { dg-message
> "argument 1 of 'h' must be a writable file descriptor, due to
> '__attribute__\\(\\(fd_arg_write\\(1\\)\\)\\)'" } */
> +void
> +test_3 (const char *path)
> +{
> +  int fd = open (path, O_RDONLY);
> +  if (fd != -1)
> +  {
> +    h (fd); /* { dg-warning "'h' on read-only file descriptor 'fd'"
> } */
> +  }
> +  close(fd);
> +}
> +
> +void ff (int fd) __attribute__((fd_arg(1))); /* { dg-message
> "argument 1 of 'ff' must be an open file descriptor, due to
> '__attribute__\\(\\(fd_arg\\(1\\)\\)\\)'" } */
> +
> +void test_4 (const char *path)
> +{
> +  int fd = open (path, O_RDWR);
> +  ff (fd); /* { dg-warning "'ff' on possibly invalid file descriptor
> 'fd'" } */
> +  close(fd);
> +}
> diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-5.c
> b/gcc/testsuite/gcc.dg/analyzer/fd-access-mode-macros.c
> similarity index 98%
> rename from gcc/testsuite/gcc.dg/analyzer/fd-5.c
> rename to gcc/testsuite/gcc.dg/analyzer/fd-access-mode-macros.c
> index c18b2adcbe5..f9a6931a5db 100644
> --- a/gcc/testsuite/gcc.dg/analyzer/fd-5.c
> +++ b/gcc/testsuite/gcc.dg/analyzer/fd-access-mode-macros.c
> @@ -6,6 +6,7 @@ int read (int fd, void *buf, int nbytes);
>  #define O_RDONLY 0
>  #define O_WRONLY 1
>  #define O_RDWR 2
> +#define O_ACCMODE 0x3
>  
>  void f (int fd) __attribute__((fd_arg(1))); /* { dg-message
> "argument 1 of 'f' must be an open file descriptor, due to
> '__attribute__\\(\\(fd_arg\\(1\\)\\)\\)'" } */
>  
> diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-access-mode-target-
> headers.c b/gcc/testsuite/gcc.dg/analyzer/fd-access-mode-target-
> headers.c
> new file mode 100644
> index 00000000000..b76eb667d50
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/analyzer/fd-access-mode-target-headers.c
> @@ -0,0 +1,56 @@
> +#include <sys/stat.h>
> +#include <fcntl.h>
> +#include <unistd.h>
> +#include "analyzer-decls.h"
> +
> +void f (int fd) __attribute__((fd_arg(1))); /* { dg-message
> "argument 1 of 'f' must be an open file descriptor, due to
> '__attribute__\\(\\(fd_arg\\(1\\)\\)\\)'" } */
> +
> +void
> +test_1 (const char *path)
> +{
> +    int fd = open (path, O_RDWR);
> +    close(fd);
> +    f(fd); /* { dg-warning "'f' on closed file descriptor 'fd'" } */
> +      /* { dg-message "\\(3\\) 'f' on closed file descriptor 'fd';
> 'close' was at \\(2\\)" "" { target *-*-* } .-1 } */
> +}
> +
> +void g (int fd) __attribute__((fd_arg_read(1))); /* { dg-message
> "argument 1 of 'g' must be a readable file descriptor, due to
> '__attribute__\\(\\(fd_arg_read\\(1\\)\\)\\)'" } */
> +
> +void
> +test_2 (const char *path)
> +{
> +  int fd = open (path, O_WRONLY);
> +  if (fd != -1)
> +  {
> +    g (fd); /* { dg-warning "'g' on write-only file descriptor 'fd'"
> } */
> +  }
> +  close (fd);
> +}
> +
> +void h (int fd) __attribute__((fd_arg_write(1))); /* { dg-message
> "argument 1 of 'h' must be a writable file descriptor, due to
> '__attribute__\\(\\(fd_arg_write\\(1\\)\\)\\)'" } */
> +void
> +test_3 (const char *path)
> +{
> +  int fd = open (path, O_RDONLY);
> +  if (fd != -1)
> +  {
> +    h (fd); /* { dg-warning "'h' on read-only file descriptor 'fd'"
> } */
> +  }
> +  close(fd);
> +}
> +
> +void ff (int fd) __attribute__((fd_arg(1))); /* { dg-message
> "argument 1 of 'ff' must be an open file descriptor, due to
> '__attribute__\\(\\(fd_arg\\(1\\)\\)\\)'" } */
> +
> +void test_4 (const char *path)
> +{
> +  int fd = open (path, O_RDWR);
> +  ff (fd); /* { dg-warning "'ff' on possibly invalid file descriptor
> 'fd'" } */
> +  close(fd);
> +}
> +
> +void test_sm_fd_constants (void)
> +{
> +  __analyzer_dump_named_constant ("O_ACCMODE"); /* { dg-warning
> "named constant 'O_ACCMODE' has value '\[0-9\]+'" } */
> +  __analyzer_dump_named_constant ("O_RDONLY"); /* { dg-warning
> "named constant 'O_RDONLY' has value '\[0-9\]+'" } */
> +  __analyzer_dump_named_constant ("O_WRONLY"); /* { dg-warning
> "named constant 'O_WRONLY' has value '\[0-9\]+'" } */
> +}
> diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-dup-1.c
> b/gcc/testsuite/gcc.dg/analyzer/fd-dup-1.c
> index b4f43e7f0ef..bb58e9d9d5e 100644
> --- a/gcc/testsuite/gcc.dg/analyzer/fd-dup-1.c
> +++ b/gcc/testsuite/gcc.dg/analyzer/fd-dup-1.c
> @@ -8,6 +8,7 @@ int read (int fd, void *buf, int nbytes);
>  #define O_RDONLY 0
>  #define O_WRONLY 1
>  #define O_RDWR 2
> +#define O_ACCMODE 3
>  
>  void test_1 (const char *path)
>  {
> diff --git a/gcc/testsuite/gcc.dg/analyzer/named-constants-via-enum.c
> b/gcc/testsuite/gcc.dg/analyzer/named-constants-via-enum.c
> new file mode 100644
> index 00000000000..e6b77b8dd18
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/analyzer/named-constants-via-enum.c
> @@ -0,0 +1,20 @@
> +#include "analyzer-decls.h"
> +
> +/* Various constants used by the fd state machine.  */
> +enum {
> +      O_ACCMODE = 42,
> +      O_RDONLY  = 0x1,
> +      O_WRONLY  = 010
> +};
> +
> +void test_sm_fd_constants (void)
> +{
> +  __analyzer_dump_named_constant ("O_ACCMODE"); /* { dg-warning
> "named constant 'O_ACCMODE' has value '42'" } */
> +  __analyzer_dump_named_constant ("O_RDONLY"); /* { dg-warning
> "named constant 'O_RDONLY' has value '1'" } */
> +  __analyzer_dump_named_constant ("O_WRONLY"); /* { dg-warning
> "named constant 'O_WRONLY' has value '8'" } */
> +}
> +
> +void test_unknown (void)
> +{
> +  __analyzer_dump_named_constant ("UNKNOWN"); /* { dg-warning "named
> constant 'UNKNOWN' has unknown value" } */
> +}
> diff --git a/gcc/testsuite/gcc.dg/analyzer/named-constants-via-
> macros-2.c b/gcc/testsuite/gcc.dg/analyzer/named-constants-via-
> macros-2.c
> new file mode 100644
> index 00000000000..9c019e7c5ef
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/analyzer/named-constants-via-macros-2.c
> @@ -0,0 +1,15 @@
> +#include "analyzer-decls.h"
> +
> +/* Various constants used by the fd state machine, as macros
> +   that can't be handled.  */
> +
> +#define O_ACCMODE (
> +#define O_RDONLY  "foo"
> +#define O_WRONLY  int
> +
> +void test_sm_fd_constants (void)
> +{
> +  __analyzer_dump_named_constant ("O_ACCMODE"); /* { dg-warning
> "named constant 'O_ACCMODE' has unknown value" } */
> +  __analyzer_dump_named_constant ("O_RDONLY"); /* { dg-warning
> "named constant 'O_RDONLY' has unknown value" } */
> +  __analyzer_dump_named_constant ("O_WRONLY"); /* { dg-warning
> "named constant 'O_WRONLY' has unknown value" } */
> +}
> diff --git a/gcc/testsuite/gcc.dg/analyzer/named-constants-via-
> macros.c b/gcc/testsuite/gcc.dg/analyzer/named-constants-via-macros.c
> new file mode 100644
> index 00000000000..2022f98e5b6
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/analyzer/named-constants-via-macros.c
> @@ -0,0 +1,19 @@
> +#include "analyzer-decls.h"
> +
> +/* Various constants used by the fd state machine.  */
> +
> +#define O_ACCMODE 42
> +#define O_RDONLY  0x1
> +#define O_WRONLY  010
> +
> +void test_sm_fd_constants (void)
> +{
> +  __analyzer_dump_named_constant ("O_ACCMODE"); /* { dg-warning
> "named constant 'O_ACCMODE' has value '42'" } */
> +  __analyzer_dump_named_constant ("O_RDONLY"); /* { dg-warning
> "named constant 'O_RDONLY' has value '1'" } */
> +  __analyzer_dump_named_constant ("O_WRONLY"); /* { dg-warning
> "named constant 'O_WRONLY' has value '8'" } */
> +}
> +
> +void test_unknown (void)
> +{
> +  __analyzer_dump_named_constant ("UNKNOWN"); /* { dg-warning "named
> constant 'UNKNOWN' has unknown value" } */
> +}


^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH v2] c, analyzer: support named constants in analyzer [PR106302]
  2022-11-12  3:23   ` [PATCH v2] " David Malcolm
  2022-11-12  3:32     ` David Malcolm
@ 2022-11-14 20:42     ` Marek Polacek
  2022-11-15 18:35       ` [PATCH v3] " David Malcolm
  1 sibling, 1 reply; 12+ messages in thread
From: Marek Polacek @ 2022-11-14 20:42 UTC (permalink / raw)
  To: David Malcolm; +Cc: gcc-patches, Joseph Myers

On Fri, Nov 11, 2022 at 10:23:10PM -0500, David Malcolm wrote:
> Changes since v1: ported the doc changes from texinfo to sphinx
> 
> Successfully bootstrapped & regrtested on x86_64-pc-linux-gnu.
> 
> Are the C frontend parts OK for trunk?  (I can self-approve the
> analyzer parts)

Sorry for the delay.
 
> The patch adds an interface for frontends to call into the analyzer as
> the translation unit finishes.  The analyzer can then call back into the
> frontend to ask about the values of the named constants it cares about
> whilst the frontend's data structures are still around.
> 
> The patch implements this for the C frontend, which looks up the names
> by looking for named CONST_DECLs (which handles enum values).  Failing
> that, it attempts to look up the values of macros but only the simplest
> cases are supported (a non-traditional macro with a single CPP_NUMBER
> token).  It does this by building a buffer containing the macro
> definition and rerunning a lexer on it.
> 
> The analyzer gracefully handles the cases where named values aren't
> found (such as anything more complicated than described above).
> 
> The patch ports the analyzer to use this mechanism for "O_RDONLY",
> "O_WRONLY", and "O_ACCMODE".  I have successfully tested my socket patch
> to also use this for "SOCK_STREAM" and "SOCK_DGRAM", so the technique
> seems to work.

So this works well for code like

enum __socket_type {
    SOCK_STREAM = 1,

#define SOCK_STREAM SOCK_STREAM
};

?

> diff --git a/gcc/c/c-parser.cc b/gcc/c/c-parser.cc
> index d70697b1d63..efe19fbe70b 100644
> --- a/gcc/c/c-parser.cc
> +++ b/gcc/c/c-parser.cc
> @@ -72,6 +72,8 @@ along with GCC; see the file COPYING3.  If not see
>  #include "memmodel.h"
>  #include "c-family/known-headers.h"
>  #include "bitmap.h"
> +#include "analyzer/analyzer-language.h"
> +#include "toplev.h"
>  
>  /* We need to walk over decls with incomplete struct/union/enum types
>     after parsing the whole translation unit.
> @@ -1662,6 +1664,87 @@ static bool c_parser_objc_diagnose_bad_element_prefix
>    (c_parser *, struct c_declspecs *);
>  static location_t c_parser_parse_rtl_body (c_parser *, char *);
>  
> +#if ENABLE_ANALYZER
> +
> +namespace ana {
> +
> +/* Concrete implementation of ana::translation_unit for the C frontend.  */
> +
> +class c_translation_unit : public translation_unit
> +{
> +public:
> +  /* Implementation of translation_unit::lookup_constant_by_id for use by the
> +     analyzer to look up named constants in the user's source code.  */
> +  tree lookup_constant_by_id (tree id) const final override
> +  {
> +    /* Consider decls.  */
> +    if (tree decl = lookup_name (id))
> +      if (TREE_CODE (decl) == CONST_DECL)
> +	if (tree value = DECL_INITIAL (decl))
> +	  if (TREE_CODE (value) == INTEGER_CST)
> +	    return value;
> +
> +    /* Consider macros.  */
> +    cpp_hashnode *hashnode = C_CPP_HASHNODE (id);
> +    if (cpp_macro_p (hashnode))
> +      if (tree value = consider_macro (hashnode->value.macro))
> +	return value;
> +
> +    return NULL_TREE;
> +  }
> +
> +private:
> +  /* Attempt to get an INTEGER_CST from MACRO.
> +     Only handle the simplest cases: where MACRO's definition is a single
> +     token containing a number, by lexing the number again.
> +     This will handle e.g.
> +       #define NAME 42
> +     and other bases but not negative numbers, parentheses or e.g.
> +       #define NAME 1 << 7
> +     as doing so would require a parser.  */
> +  tree consider_macro (cpp_macro *macro) const
> +  {
> +    if (macro->paramc > 0)
> +      return NULL_TREE;
> +    if (macro->kind == cmk_traditional)

Do you really want to handle cmk_assert?  I'd say you want

  if (macro->kind != cmk_macro)

> +      return NULL_TREE;
> +    if (macro->count != 1)
> +      return NULL_TREE;
> +    const cpp_token &tok = macro->exp.tokens[0];
> +    if (tok.type != CPP_NUMBER)
> +      return NULL_TREE;
> +
> +    cpp_reader *old_parse_in = parse_in;
> +    parse_in = cpp_create_reader (c_dialect_cxx () ? CLK_GNUCXX: CLK_GNUC89,
> +				  ident_hash, line_table);

Why not always CLK_GNUC89 since we're in the C FE?

> +
> +    pretty_printer pp;
> +    pp_string (&pp, (const char *)tok.val.str.text);

A space after ')'.

> +    pp_newline (&pp);
> +    cpp_push_buffer (parse_in,
> +		     (const unsigned char *)pp_formatted_text (&pp),

Likewise.

> +		     strlen (pp_formatted_text (&pp)),
> +		     0);
> +
> +    tree value;
> +    location_t loc;
> +    unsigned char cpp_flags;
> +    c_lex_with_flags (&value, &loc, &cpp_flags, 0);
> +
> +    cpp_destroy (parse_in);
> +    parse_in = old_parse_in;
> +
> +    if (value && TREE_CODE (value) == INTEGER_CST)
> +      return value;
> +
> +    return NULL_TREE;
> +  }
> +};
> +
> +} // namespace ana
> +
> +#endif /* #if ENABLE_ANALYZER */
> +
>  /* Parse a translation unit (C90 6.7, C99 6.9, C11 6.9).
>  
>     translation-unit:
> @@ -1722,6 +1805,14 @@ c_parser_translation_unit (c_parser *parser)
>  	       "#pragma omp begin assumes", "#pragma omp end assumes");
>        current_omp_begin_assumes = 0;
>      }
> +
> +#if ENABLE_ANALYZER
> +  if (flag_analyzer)
> +    {
> +      ana::c_translation_unit tu;
> +      ana::on_finish_translation_unit (tu);
> +    }
> +#endif
>  }

Marek


^ permalink raw reply	[flat|nested] 12+ messages in thread

* [PATCH v3] c, analyzer: support named constants in analyzer [PR106302]
  2022-11-14 20:42     ` Marek Polacek
@ 2022-11-15 18:35       ` David Malcolm
  2022-11-15 18:39         ` Marek Polacek
  0 siblings, 1 reply; 12+ messages in thread
From: David Malcolm @ 2022-11-15 18:35 UTC (permalink / raw)
  To: Marek Polacek; +Cc: gcc-patches, Joseph Myers

On Mon, 2022-11-14 at 15:42 -0500, Marek Polacek wrote:
> On Fri, Nov 11, 2022 at 10:23:10PM -0500, David Malcolm wrote:
> > Changes since v1: ported the doc changes from texinfo to sphinx
> > 
> > Successfully bootstrapped & regrtested on x86_64-pc-linux-gnu.
> > 
> > Are the C frontend parts OK for trunk?  (I can self-approve the
> > analyzer parts)
> 
> Sorry for the delay.
>  
> > The patch adds an interface for frontends to call into the analyzer
> > as
> > the translation unit finishes.  The analyzer can then call back
> > into the
> > frontend to ask about the values of the named constants it cares
> > about
> > whilst the frontend's data structures are still around.
> > 
> > The patch implements this for the C frontend, which looks up the
> > names
> > by looking for named CONST_DECLs (which handles enum values). 
> > Failing
> > that, it attempts to look up the values of macros but only the
> > simplest
> > cases are supported (a non-traditional macro with a single
> > CPP_NUMBER
> > token).  It does this by building a buffer containing the macro
> > definition and rerunning a lexer on it.
> > 
> > The analyzer gracefully handles the cases where named values aren't
> > found (such as anything more complicated than described above).
> > 
> > The patch ports the analyzer to use this mechanism for "O_RDONLY",
> > "O_WRONLY", and "O_ACCMODE".  I have successfully tested my socket
> > patch
> > to also use this for "SOCK_STREAM" and "SOCK_DGRAM", so the
> > technique
> > seems to work.
> 
> So this works well for code like
> 
> enum __socket_type {
>     SOCK_STREAM = 1,
> 
> #define SOCK_STREAM SOCK_STREAM
> };
> 
> ?

Yes: c_translation_unit::lookup_constant_by_id does the "lookup_name"
first, and this finds the CONST_DECL, so it doesn't need to look at
macros for this case.

I've added a testcase for this in the v3 patch (gcc.dg/analyzer/named-
constants-via-enum-and-macro.c)

> 
> > diff --git a/gcc/c/c-parser.cc b/gcc/c/c-parser.cc
> > index d70697b1d63..efe19fbe70b 100644
> > --- a/gcc/c/c-parser.cc
> > +++ b/gcc/c/c-parser.cc
> > @@ -72,6 +72,8 @@ along with GCC; see the file COPYING3.  If not
> > see
> >  #include "memmodel.h"
> >  #include "c-family/known-headers.h"
> >  #include "bitmap.h"
> > +#include "analyzer/analyzer-language.h"
> > +#include "toplev.h"
> >  
> >  /* We need to walk over decls with incomplete struct/union/enum
> > types
> >     after parsing the whole translation unit.
> > @@ -1662,6 +1664,87 @@ static bool
> > c_parser_objc_diagnose_bad_element_prefix
> >    (c_parser *, struct c_declspecs *);
> >  static location_t c_parser_parse_rtl_body (c_parser *, char *);
> >  
> > +#if ENABLE_ANALYZER
> > +
> > +namespace ana {
> > +
> > +/* Concrete implementation of ana::translation_unit for the C
> > frontend.  */
> > +
> > +class c_translation_unit : public translation_unit
> > +{
> > +public:
> > +  /* Implementation of translation_unit::lookup_constant_by_id for
> > use by the
> > +     analyzer to look up named constants in the user's source
> > code.  */
> > +  tree lookup_constant_by_id (tree id) const final override
> > +  {
> > +    /* Consider decls.  */
> > +    if (tree decl = lookup_name (id))
> > +      if (TREE_CODE (decl) == CONST_DECL)
> > +       if (tree value = DECL_INITIAL (decl))
> > +         if (TREE_CODE (value) == INTEGER_CST)
> > +           return value;
> > +
> > +    /* Consider macros.  */
> > +    cpp_hashnode *hashnode = C_CPP_HASHNODE (id);
> > +    if (cpp_macro_p (hashnode))
> > +      if (tree value = consider_macro (hashnode->value.macro))
> > +       return value;
> > +
> > +    return NULL_TREE;
> > +  }
> > +
> > +private:
> > +  /* Attempt to get an INTEGER_CST from MACRO.
> > +     Only handle the simplest cases: where MACRO's definition is a
> > single
> > +     token containing a number, by lexing the number again.
> > +     This will handle e.g.
> > +       #define NAME 42
> > +     and other bases but not negative numbers, parentheses or e.g.
> > +       #define NAME 1 << 7
> > +     as doing so would require a parser.  */
> > +  tree consider_macro (cpp_macro *macro) const
> > +  {
> > +    if (macro->paramc > 0)
> > +      return NULL_TREE;
> > +    if (macro->kind == cmk_traditional)
> 
> Do you really want to handle cmk_assert?  I'd say you want
> 
>   if (macro->kind != cmk_macro)

Thanks; fixed in the v3 patch.

> 
> > +      return NULL_TREE;
> > +    if (macro->count != 1)
> > +      return NULL_TREE;
> > +    const cpp_token &tok = macro->exp.tokens[0];
> > +    if (tok.type != CPP_NUMBER)
> > +      return NULL_TREE;
> > +
> > +    cpp_reader *old_parse_in = parse_in;
> > +    parse_in = cpp_create_reader (c_dialect_cxx () ? CLK_GNUCXX:
> > CLK_GNUC89,
> > +                                 ident_hash, line_table);
> 
> Why not always CLK_GNUC89 since we're in the C FE?

Fixed (I was copying and pasting from a selftest in input.c, IIRC).

> 
> > +
> > +    pretty_printer pp;
> > +    pp_string (&pp, (const char *)tok.val.str.text);
> 
> A space after ')'.

Fixed.

> 
> > +    pp_newline (&pp);
> > +    cpp_push_buffer (parse_in,
> > +                    (const unsigned char *)pp_formatted_text
> > (&pp),
> 
> Likewise.

Fixed.

> 
> > +                    strlen (pp_formatted_text (&pp)),
> > +                    0);
> > +
> > +    tree value;
> > +    location_t loc;
> > +    unsigned char cpp_flags;
> > +    c_lex_with_flags (&value, &loc, &cpp_flags, 0);
> > +
> > +    cpp_destroy (parse_in);
> > +    parse_in = old_parse_in;
> > +
> > +    if (value && TREE_CODE (value) == INTEGER_CST)
> > +      return value;
> > +
> > +    return NULL_TREE;
> > +  }
> > +};
> > +
> > +} // namespace ana
> > +
> > +#endif /* #if ENABLE_ANALYZER */
> > +
> >  /* Parse a translation unit (C90 6.7, C99 6.9, C11 6.9).
> >  
> >     translation-unit:
> > @@ -1722,6 +1805,14 @@ c_parser_translation_unit (c_parser *parser)
> >                "#pragma omp begin assumes", "#pragma omp end
> > assumes");
> >        current_omp_begin_assumes = 0;
> >      }
> > +
> > +#if ENABLE_ANALYZER
> > +  if (flag_analyzer)
> > +    {
> > +      ana::c_translation_unit tu;
> > +      ana::on_finish_translation_unit (tu);
> > +    }
> > +#endif
> >  }
> 
> Marek

Thanks for the review.  Here's a v3 version of the patch.

Successfully bootstrapped & regrtested on x86_64-pc-linux-gnu.

Are the C FE parts OK for trunk?

Thanks!
Dave


Changed in v3:
* ported the doc changes back to texinfo from sphinx
* added testcase: named-constants-via-enum-and-macro.c
* fixes issues noted in review

Changed in v2: ported the doc changes from texinfo to sphinx


The analyzer's file-descriptor state machine tracks the access mode of
opened files, so that it can emit -Wanalyzer-fd-access-mode-mismatch.

To do this, its symbolic execution needs to "know" the values of the
constants "O_RDONLY", "O_WRONLY", and "O_ACCMODE".  Currently
analyzer/sm-fd.cc simply uses these values directly from the build-time
header files, but these are the values on the host, not those from the
target, which could be different (PR analyzer/106302).

In an earlier discussion of this issue:
  https://gcc.gnu.org/pipermail/gcc/2022-June/238954.html
we talked about adding a target hook for this.

However, I've also been experimenting with extending the fd state
machine to track sockets (PR analyzer/106140).  For this, it's useful to
"know" the values of the constants "SOCK_STREAM" and "SOCK_DGRAM".
Unfortunately, these seem to have many arbitrary differences from target
to target.

For example: Linux/glibc general has SOCK_STREAM == 1, SOCK_DGRAM == 2,
as does AIX, but annoyingly, e.g. Linux on MIPS has them the other way
around.

It seems to me that as the analyzer grows more ambitious modeling of the
behavior of APIs (perhaps via plugins) it's more likely that the
analyzer will need to know the values of named constants, which might
not even exist on the host.

For example, at LPC it was suggested to me that -fanalyzer could check
rules about memory management inside the Linux kernel (probably via a
plugin), but doing so involves a bunch of GFP_* flags (see PR 107472).

So rather than trying to capture all this knowledge in a target hook,
this patch attempts to get at named constant values from the user's
source code.

The patch adds an interface for frontends to call into the analyzer as
the translation unit finishes.  The analyzer can then call back into the
frontend to ask about the values of the named constants it cares about
whilst the frontend's data structures are still around.

The patch implements this for the C frontend, which looks up the names
by looking for named CONST_DECLs (which handles enum values).  Failing
that, it attempts to look up the values of macros but only the simplest
cases are supported (a non-traditional macro with a single CPP_NUMBER
token).  It does this by building a buffer containing the macro
definition and rerunning a lexer on it.

The analyzer gracefully handles the cases where named values aren't
found (such as anything more complicated than described above).

The patch ports the analyzer to use this mechanism for "O_RDONLY",
"O_WRONLY", and "O_ACCMODE".  I have successfully tested my socket patch
to also use this for "SOCK_STREAM" and "SOCK_DGRAM", so the technique
seems to work.

gcc/ChangeLog:
	PR analyzer/106302
	* Makefile.in (ANALYZER_OBJS): Add analyzer/analyzer-language.o.
	(GTFILES): Add analyzer/analyzer-language.cc.
	* doc/analyzer.texi: Document __analyzer_dump_named_constant.

gcc/analyzer/ChangeLog:
	PR analyzer/106302
	* analyzer-language.cc: New file.
	* analyzer-language.h: New file.
	* analyzer.h (get_stashed_constant_by_name): New decl.
	(log_stashed_constants): New decl.
	* engine.cc (impl_run_checkers): Call log_stashed_constants.
	* region-model-impl-calls.cc
	(region_model::impl_call_analyzer_dump_named_constant): New.
	* region-model.cc (region_model::on_stmt_pre): Handle
	__analyzer_dump_named_constant.
	* region-model.h
	(region_model::impl_call_analyzer_dump_named_constant): New decl.
	* sm-fd.cc (fd_state_machine::m_O_ACCMODE): New.
	(fd_state_machine::m_O_RDONLY): New.
	(fd_state_machine::m_O_WRONLY): New.
	(fd_state_machine::fd_state_machine): Initialize the new fields.
	(fd_state_machine::get_access_mode_from_flag): Use the new fields,
	rather than using the host values.

gcc/c/ChangeLog:
	PR analyzer/106302
	* c-parser.cc: Include "analyzer/analyzer-language.h" and "toplev.h".
	(class ana::c_translation_unit): New.
	(c_parser_translation_unit): Call ana::on_finish_translation_unit.

gcc/testsuite/ChangeLog:
	* gcc.dg/analyzer/analyzer-decls.h
	(__analyzer_dump_named_constant): New decl.
	* gcc.dg/analyzer/fd-4.c (void): Likewise.
	(O_ACCMODE): Define.
	* gcc.dg/analyzer/fd-access-mode-enum.c: New test, based on .
	* gcc.dg/analyzer/fd-5.c: ...this.  Rename to...
	* gcc.dg/analyzer/fd-access-mode-macros.c: ...this.
	(O_ACCMODE): Define.
	* gcc.dg/analyzer/fd-access-mode-target-headers.c: New test, also
	based on fd-5.c.
	(test_sm_fd_constants): New.
	* gcc.dg/analyzer/fd-dup-1.c (O_ACCMODE): Define.
	* gcc.dg/analyzer/named-constants-via-enum.c: New test.
	* gcc.dg/analyzer/named-constants-via-enum-and-macro.c: New test.
	* gcc.dg/analyzer/named-constants-via-macros-2.c: New test.
	* gcc.dg/analyzer/named-constants-via-macros.c: New test.

Signed-off-by: David Malcolm <dmalcolm@redhat.com>
---
 gcc/Makefile.in                               |   2 +
 gcc/analyzer/analyzer-language.cc             | 110 ++++++++++++++++++
 gcc/analyzer/analyzer-language.h              |  48 ++++++++
 gcc/analyzer/analyzer.h                       |   3 +
 gcc/analyzer/engine.cc                        |   1 +
 gcc/analyzer/region-model-impl-calls.cc       |  28 +++++
 gcc/analyzer/region-model.cc                  |   4 +
 gcc/analyzer/region-model.h                   |   2 +
 gcc/analyzer/sm-fd.cc                         |  30 +++--
 gcc/c/c-parser.cc                             |  90 ++++++++++++++
 gcc/doc/analyzer.texi                         |  17 +++
 .../gcc.dg/analyzer/analyzer-decls.h          |   3 +
 gcc/testsuite/gcc.dg/analyzer/fd-4.c          |   1 +
 .../gcc.dg/analyzer/fd-access-mode-enum.c     |  60 ++++++++++
 .../{fd-5.c => fd-access-mode-macros.c}       |   1 +
 .../analyzer/fd-access-mode-target-headers.c  |  56 +++++++++
 gcc/testsuite/gcc.dg/analyzer/fd-dup-1.c      |   1 +
 .../named-constants-via-enum-and-macro.c      |  12 ++
 .../analyzer/named-constants-via-enum.c       |  20 ++++
 .../analyzer/named-constants-via-macros-2.c   |  15 +++
 .../analyzer/named-constants-via-macros.c     |  19 +++
 21 files changed, 513 insertions(+), 10 deletions(-)
 create mode 100644 gcc/analyzer/analyzer-language.cc
 create mode 100644 gcc/analyzer/analyzer-language.h
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/fd-access-mode-enum.c
 rename gcc/testsuite/gcc.dg/analyzer/{fd-5.c => fd-access-mode-macros.c} (98%)
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/fd-access-mode-target-headers.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/named-constants-via-enum-and-macro.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/named-constants-via-enum.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/named-constants-via-macros-2.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/named-constants-via-macros.c

diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index 3491ff66045..41b3fe7851f 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -1249,6 +1249,7 @@ C_COMMON_OBJS = c-family/c-common.o c-family/c-cppbuiltin.o c-family/c-dump.o \
 ANALYZER_OBJS = \
 	analyzer/analysis-plan.o \
 	analyzer/analyzer.o \
+	analyzer/analyzer-language.o \
 	analyzer/analyzer-logging.o \
 	analyzer/analyzer-pass.o \
 	analyzer/analyzer-selftests.o \
@@ -2742,6 +2743,7 @@ GTFILES = $(CPPLIB_H) $(srcdir)/input.h $(srcdir)/coretypes.h \
   $(srcdir)/internal-fn.h \
   $(srcdir)/calls.cc \
   $(srcdir)/omp-general.h \
+  $(srcdir)/analyzer/analyzer-language.cc \
   @all_gtfiles@
 
 # Compute the list of GT header files from the corresponding C sources,
diff --git a/gcc/analyzer/analyzer-language.cc b/gcc/analyzer/analyzer-language.cc
new file mode 100644
index 00000000000..ba4352b729a
--- /dev/null
+++ b/gcc/analyzer/analyzer-language.cc
@@ -0,0 +1,110 @@
+/* Interface between analyzer and frontends.
+   Copyright (C) 2022 Free Software Foundation, Inc.
+   Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3, or (at your option)
+any later version.
+
+GCC is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+#include "config.h"
+#define INCLUDE_MEMORY
+#include "system.h"
+#include "coretypes.h"
+#include "tree.h"
+#include "stringpool.h"
+#include "analyzer/analyzer.h"
+#include "analyzer/analyzer-language.h"
+#include "analyzer/analyzer-logging.h"
+
+/* Map from identifier to INTEGER_CST.  */
+static GTY (()) hash_map <tree, tree> *analyzer_stashed_constants;
+
+#if ENABLE_ANALYZER
+
+namespace ana {
+
+/* Call into TU to try to find a value for NAME.
+   If found, stash its value within analyzer_stashed_constants.  */
+
+static void
+maybe_stash_named_constant (const translation_unit &tu, const char *name)
+{
+  if (!analyzer_stashed_constants)
+    analyzer_stashed_constants = hash_map<tree, tree>::create_ggc ();
+
+  tree id = get_identifier (name);
+  if (tree t = tu.lookup_constant_by_id (id))
+    {
+      gcc_assert (TREE_CODE (t) == INTEGER_CST);
+      analyzer_stashed_constants->put (id, t);
+    }
+}
+
+/* Hook for frontend to call into analyzer when TU finishes.
+   This exists so that the analyzer can stash named constant values from
+   header files (e.g. macros and enums) for later use when modeling the
+   behaviors of APIs.
+
+   By doing it this way, the analyzer can use the precise values for those
+   constants from the user's headers, rather than attempting to model them
+   as properties of the target.  */
+
+void
+on_finish_translation_unit (const translation_unit &tu)
+{
+  /* Bail if the analyzer isn't enabled.  */
+  if (!flag_analyzer)
+    return;
+
+  /* Stash named constants for use by sm-fd.cc  */
+  maybe_stash_named_constant (tu, "O_ACCMODE");
+  maybe_stash_named_constant (tu, "O_RDONLY");
+  maybe_stash_named_constant (tu, "O_WRONLY");
+}
+
+/* Lookup NAME in the named constants stashed when the frontend TU finished.
+   Return either an INTEGER_CST, or NULL_TREE.  */
+
+tree
+get_stashed_constant_by_name (const char *name)
+{
+  if (!analyzer_stashed_constants)
+    return NULL_TREE;
+  tree id = get_identifier (name);
+  if (tree *slot = analyzer_stashed_constants->get (id))
+    {
+      gcc_assert (TREE_CODE (*slot) == INTEGER_CST);
+      return *slot;
+    }
+  return NULL_TREE;
+}
+
+/* Log all stashed named constants to LOGGER.  */
+
+void
+log_stashed_constants (logger *logger)
+{
+  gcc_assert (logger);
+  LOG_SCOPE (logger);
+  if (analyzer_stashed_constants)
+    for (auto iter : *analyzer_stashed_constants)
+      logger->log ("%qE: %qE", iter.first, iter.second);
+}
+
+} // namespace ana
+
+#endif /* #if ENABLE_ANALYZER */
+
+#include "gt-analyzer-language.h"
diff --git a/gcc/analyzer/analyzer-language.h b/gcc/analyzer/analyzer-language.h
new file mode 100644
index 00000000000..33c4dd60623
--- /dev/null
+++ b/gcc/analyzer/analyzer-language.h
@@ -0,0 +1,48 @@
+/* Interface between analyzer and frontends.
+   Copyright (C) 2022 Free Software Foundation, Inc.
+   Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3, or (at your option)
+any later version.
+
+GCC is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+#ifndef GCC_ANALYZER_LANGUAGE_H
+#define GCC_ANALYZER_LANGUAGE_H
+
+#if ENABLE_ANALYZER
+
+namespace ana {
+
+/* Abstract base class for representing a specific TU
+   to the analyzer.  */
+
+class translation_unit
+{
+ public:
+  /* Attempt to look up an  value for identifier ID (e.g. in the headers that
+     have been seen).  If it is defined and an integer (e.g. either as a
+     macro or enum), return the INTEGER_CST value, otherwise return NULL.  */
+  virtual tree lookup_constant_by_id (tree id) const = 0;
+};
+
+/* Analyzer hook for frontends to call at the end of the TU.  */
+
+void on_finish_translation_unit (const translation_unit &tu);
+
+} // namespace ana
+
+#endif /* #if ENABLE_ANALYZER */
+
+#endif /* GCC_ANALYZER_LANGUAGE_H */
diff --git a/gcc/analyzer/analyzer.h b/gcc/analyzer/analyzer.h
index c0041c35d1a..9cf8d98fabe 100644
--- a/gcc/analyzer/analyzer.h
+++ b/gcc/analyzer/analyzer.h
@@ -311,6 +311,9 @@ public:
   virtual bool terminate_path_p () const = 0;
 };
 
+extern tree get_stashed_constant_by_name (const char *name);
+extern void log_stashed_constants (logger *logger);
+
 } // namespace ana
 
 extern bool is_special_named_call_p (const gcall *call, const char *funcname,
diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc
index d0595ef0d07..891be7c5c90 100644
--- a/gcc/analyzer/engine.cc
+++ b/gcc/analyzer/engine.cc
@@ -6010,6 +6010,7 @@ impl_run_checkers (logger *logger)
       logger->log ("BITS_BIG_ENDIAN: %i", BITS_BIG_ENDIAN ? 1 : 0);
       logger->log ("BYTES_BIG_ENDIAN: %i", BYTES_BIG_ENDIAN ? 1 : 0);
       logger->log ("WORDS_BIG_ENDIAN: %i", WORDS_BIG_ENDIAN ? 1 : 0);
+      log_stashed_constants (logger);
     }
 
   /* If using LTO, ensure that the cgraph nodes have function bodies.  */
diff --git a/gcc/analyzer/region-model-impl-calls.cc b/gcc/analyzer/region-model-impl-calls.cc
index 9ef31f6ab05..a7134ed90bb 100644
--- a/gcc/analyzer/region-model-impl-calls.cc
+++ b/gcc/analyzer/region-model-impl-calls.cc
@@ -352,6 +352,34 @@ region_model::impl_call_analyzer_dump_escaped (const gcall *call)
 	      pp_formatted_text (&pp));
 }
 
+/* Handle a call to "__analyzer_dump_named_constant".
+
+   Look up the given name, and emit a warning describing the
+   state of the corresponding stashed value.
+
+   This is for use when debugging, and for DejaGnu tests.  */
+
+void
+region_model::
+impl_call_analyzer_dump_named_constant (const gcall *call,
+					region_model_context *ctxt)
+{
+  call_details cd (call, this, ctxt);
+  const char *name = cd.get_arg_string_literal (0);
+  if (!name)
+    {
+      error_at (call->location, "cannot determine name");
+      return;
+    }
+  tree value = get_stashed_constant_by_name (name);
+  if (value)
+    warning_at (call->location, 0, "named constant %qs has value %qE",
+		name, value);
+  else
+    warning_at (call->location, 0, "named constant %qs has unknown value",
+		name);
+}
+
 /* Handle a call to "__analyzer_eval" by evaluating the input
    and dumping as a dummy warning, so that test cases can use
    dg-warning to validate the result (and so unexpected warnings will
diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc
index b91434d7db4..5bae3cf5cd4 100644
--- a/gcc/analyzer/region-model.cc
+++ b/gcc/analyzer/region-model.cc
@@ -1229,6 +1229,10 @@ region_model::on_stmt_pre (const gimple *stmt,
 	  impl_call_analyzer_dump_capacity (call, ctxt);
 	else if (is_special_named_call_p (call, "__analyzer_dump_escaped", 0))
 	  impl_call_analyzer_dump_escaped (call);
+	else if (is_special_named_call_p (call,
+					  "__analyzer_dump_named_constant",
+					  1))
+	  impl_call_analyzer_dump_named_constant (call, ctxt);
 	else if (is_special_named_call_p (call, "__analyzer_dump_path", 0))
 	  {
 	    /* Handle the builtin "__analyzer_dump_path" by queuing a
diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h
index 50790596726..bd81e6b6b9d 100644
--- a/gcc/analyzer/region-model.h
+++ b/gcc/analyzer/region-model.h
@@ -344,6 +344,8 @@ class region_model
   void impl_call_analyzer_dump_capacity (const gcall *call,
 					 region_model_context *ctxt);
   void impl_call_analyzer_dump_escaped (const gcall *call);
+  void impl_call_analyzer_dump_named_constant (const gcall *call,
+					       region_model_context *ctxt);
   void impl_call_analyzer_eval (const gcall *call,
 				region_model_context *ctxt);
   void impl_call_analyzer_get_unknown_ptr (const call_details &cd);
diff --git a/gcc/analyzer/sm-fd.cc b/gcc/analyzer/sm-fd.cc
index da0e92b5113..370115d56bf 100644
--- a/gcc/analyzer/sm-fd.cc
+++ b/gcc/analyzer/sm-fd.cc
@@ -159,6 +159,11 @@ public:
   /* State for a file descriptor that we do not want to track anymore . */
   state_t m_stop;
 
+  /* Stashed constant values from the frontend.  These could be NULL.  */
+  tree m_O_ACCMODE;
+  tree m_O_RDONLY;
+  tree m_O_WRONLY;
+
 private:
   void on_open (sm_context *sm_ctxt, const supernode *node, const gimple *stmt,
 		const gcall *call) const;
@@ -686,7 +691,10 @@ 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_stop (add_state ("fd-stop"))
+      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"))
 {
 }
 
@@ -709,16 +717,18 @@ fd_state_machine::is_valid_fd_p (state_t s) const
 enum access_mode
 fd_state_machine::get_access_mode_from_flag (int flag) const
 {
-  /* FIXME: this code assumes the access modes on the host and
-     target are the same, which in practice might not be the case.  */
-
-  if ((flag & O_ACCMODE) == O_RDONLY)
-    {
-      return READ_ONLY;
-    }
-  else if ((flag & O_ACCMODE) == O_WRONLY)
+  if (m_O_ACCMODE && TREE_CODE (m_O_ACCMODE) == INTEGER_CST)
     {
-      return WRITE_ONLY;
+      const unsigned HOST_WIDE_INT mask_val = TREE_INT_CST_LOW (m_O_ACCMODE);
+      const unsigned HOST_WIDE_INT masked_flag = flag & mask_val;
+
+      if (m_O_RDONLY && TREE_CODE (m_O_RDONLY) == INTEGER_CST)
+	if (masked_flag == TREE_INT_CST_LOW (m_O_RDONLY))
+	  return READ_ONLY;
+
+      if (m_O_WRONLY && TREE_CODE (m_O_WRONLY) == INTEGER_CST)
+	if (masked_flag == TREE_INT_CST_LOW (m_O_WRONLY))
+	  return WRITE_ONLY;
     }
   return READ_WRITE;
 }
diff --git a/gcc/c/c-parser.cc b/gcc/c/c-parser.cc
index 1d144bba24d..f3c79996fb0 100644
--- a/gcc/c/c-parser.cc
+++ b/gcc/c/c-parser.cc
@@ -72,6 +72,8 @@ along with GCC; see the file COPYING3.  If not see
 #include "memmodel.h"
 #include "c-family/known-headers.h"
 #include "bitmap.h"
+#include "analyzer/analyzer-language.h"
+#include "toplev.h"
 
 /* We need to walk over decls with incomplete struct/union/enum types
    after parsing the whole translation unit.
@@ -1664,6 +1666,86 @@ static bool c_parser_objc_diagnose_bad_element_prefix
   (c_parser *, struct c_declspecs *);
 static location_t c_parser_parse_rtl_body (c_parser *, char *);
 
+#if ENABLE_ANALYZER
+
+namespace ana {
+
+/* Concrete implementation of ana::translation_unit for the C frontend.  */
+
+class c_translation_unit : public translation_unit
+{
+public:
+  /* Implementation of translation_unit::lookup_constant_by_id for use by the
+     analyzer to look up named constants in the user's source code.  */
+  tree lookup_constant_by_id (tree id) const final override
+  {
+    /* Consider decls.  */
+    if (tree decl = lookup_name (id))
+      if (TREE_CODE (decl) == CONST_DECL)
+	if (tree value = DECL_INITIAL (decl))
+	  if (TREE_CODE (value) == INTEGER_CST)
+	    return value;
+
+    /* Consider macros.  */
+    cpp_hashnode *hashnode = C_CPP_HASHNODE (id);
+    if (cpp_macro_p (hashnode))
+      if (tree value = consider_macro (hashnode->value.macro))
+	return value;
+
+    return NULL_TREE;
+  }
+
+private:
+  /* Attempt to get an INTEGER_CST from MACRO.
+     Only handle the simplest cases: where MACRO's definition is a single
+     token containing a number, by lexing the number again.
+     This will handle e.g.
+       #define NAME 42
+     and other bases but not negative numbers, parentheses or e.g.
+       #define NAME 1 << 7
+     as doing so would require a parser.  */
+  tree consider_macro (cpp_macro *macro) const
+  {
+    if (macro->paramc > 0)
+      return NULL_TREE;
+    if (macro->kind != cmk_macro)
+      return NULL_TREE;
+    if (macro->count != 1)
+      return NULL_TREE;
+    const cpp_token &tok = macro->exp.tokens[0];
+    if (tok.type != CPP_NUMBER)
+      return NULL_TREE;
+
+    cpp_reader *old_parse_in = parse_in;
+    parse_in = cpp_create_reader (CLK_GNUC89, ident_hash, line_table);
+
+    pretty_printer pp;
+    pp_string (&pp, (const char *) tok.val.str.text);
+    pp_newline (&pp);
+    cpp_push_buffer (parse_in,
+		     (const unsigned char *) pp_formatted_text (&pp),
+		     strlen (pp_formatted_text (&pp)),
+		     0);
+
+    tree value;
+    location_t loc;
+    unsigned char cpp_flags;
+    c_lex_with_flags (&value, &loc, &cpp_flags, 0);
+
+    cpp_destroy (parse_in);
+    parse_in = old_parse_in;
+
+    if (value && TREE_CODE (value) == INTEGER_CST)
+      return value;
+
+    return NULL_TREE;
+  }
+};
+
+} // namespace ana
+
+#endif /* #if ENABLE_ANALYZER */
+
 /* Parse a translation unit (C90 6.7, C99 6.9, C11 6.9).
 
    translation-unit:
@@ -1724,6 +1806,14 @@ c_parser_translation_unit (c_parser *parser)
 	       "#pragma omp begin assumes", "#pragma omp end assumes");
       current_omp_begin_assumes = 0;
     }
+
+#if ENABLE_ANALYZER
+  if (flag_analyzer)
+    {
+      ana::c_translation_unit tu;
+      ana::on_finish_translation_unit (tu);
+    }
+#endif
 }
 
 /* Parse an external declaration (C90 6.7, C99 6.9, C11 6.9).
diff --git a/gcc/doc/analyzer.texi b/gcc/doc/analyzer.texi
index ec49f951435..d61b55cec5a 100644
--- a/gcc/doc/analyzer.texi
+++ b/gcc/doc/analyzer.texi
@@ -524,6 +524,23 @@ With a non-zero argument
 
 it will also dump all of the states within the ``processed'' nodes.
 
+The builtin @code{__analyzer_dump_named_constant} will emit a warning
+during analysis describing what is known about the value of a given
+named constant, for parts of the analyzer that interact with target
+headers.
+
+For example:
+
+@smallexample
+__analyzer_dump_named_constant ("O_RDONLY");
+@end smallexample
+
+might emit the warning:
+
+@smallexample
+warning: named constant 'O_RDONLY' has value '1'
+@end smallexample
+
 @smallexample
    __analyzer_dump_region_model ();
 @end smallexample
diff --git a/gcc/testsuite/gcc.dg/analyzer/analyzer-decls.h b/gcc/testsuite/gcc.dg/analyzer/analyzer-decls.h
index 4478d740b58..d9a32ed9370 100644
--- a/gcc/testsuite/gcc.dg/analyzer/analyzer-decls.h
+++ b/gcc/testsuite/gcc.dg/analyzer/analyzer-decls.h
@@ -31,6 +31,9 @@ extern void __analyzer_dump_escaped (void);
    will also dump all of the states within those nodes.  */
 extern void __analyzer_dump_exploded_nodes (int);
 
+/* Emit a warning describing what is known about the value of NAME.  */
+extern void __analyzer_dump_named_constant (const char *name);
+
 /* Emit a placeholder "note" diagnostic with a path to this call site,
    if the analyzer finds a feasible path to it.  */
 extern void __analyzer_dump_path (void);
diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-4.c b/gcc/testsuite/gcc.dg/analyzer/fd-4.c
index 842a26b4364..994bad84342 100644
--- a/gcc/testsuite/gcc.dg/analyzer/fd-4.c
+++ b/gcc/testsuite/gcc.dg/analyzer/fd-4.c
@@ -8,6 +8,7 @@ void close(int fd);
 int write (int fd, void *buf, int nbytes);
 int read (int fd, void *buf, int nbytes);
 
+#define O_ACCMODE 0xf
 #define O_RDONLY 0
 #define O_WRONLY 1
 #define O_RDWR 2
diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-access-mode-enum.c b/gcc/testsuite/gcc.dg/analyzer/fd-access-mode-enum.c
new file mode 100644
index 00000000000..5226569c437
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/fd-access-mode-enum.c
@@ -0,0 +1,60 @@
+int open(const char *, int mode);
+void close(int fd);
+int write (int fd, void *buf, int nbytes);
+int read (int fd, void *buf, int nbytes);
+
+/* Example of these flags as an enum, and with
+   non-standard values for them.  */
+
+enum {
+      O_RDONLY  = 0x10,
+      O_WRONLY  = 0x20,
+      O_RDWR    = 0x40,
+
+      O_ACCMODE = 0xf0
+};
+
+void f (int fd) __attribute__((fd_arg(1))); /* { dg-message "argument 1 of 'f' must be an open file descriptor, due to '__attribute__\\(\\(fd_arg\\(1\\)\\)\\)'" } */
+
+void
+test_1 (const char *path)
+{
+    int fd = open (path, O_RDWR);
+    close(fd);
+    f(fd); /* { dg-warning "'f' on closed file descriptor 'fd'" } */
+      /* { dg-message "\\(3\\) 'f' on closed file descriptor 'fd'; 'close' was at \\(2\\)" "" { target *-*-* } .-1 } */
+}
+
+void g (int fd) __attribute__((fd_arg_read(1))); /* { dg-message "argument 1 of 'g' must be a readable file descriptor, due to '__attribute__\\(\\(fd_arg_read\\(1\\)\\)\\)'" } */
+
+void
+test_2 (const char *path)
+{
+  int fd = open (path, O_WRONLY);
+  if (fd != -1)
+  {
+    g (fd); /* { dg-warning "'g' on write-only file descriptor 'fd'" } */
+  }
+  close (fd);
+}
+
+void h (int fd) __attribute__((fd_arg_write(1))); /* { dg-message "argument 1 of 'h' must be a writable file descriptor, due to '__attribute__\\(\\(fd_arg_write\\(1\\)\\)\\)'" } */
+void
+test_3 (const char *path)
+{
+  int fd = open (path, O_RDONLY);
+  if (fd != -1)
+  {
+    h (fd); /* { dg-warning "'h' on read-only file descriptor 'fd'" } */
+  }
+  close(fd);
+}
+
+void ff (int fd) __attribute__((fd_arg(1))); /* { dg-message "argument 1 of 'ff' must be an open file descriptor, due to '__attribute__\\(\\(fd_arg\\(1\\)\\)\\)'" } */
+
+void test_4 (const char *path)
+{
+  int fd = open (path, O_RDWR);
+  ff (fd); /* { dg-warning "'ff' on possibly invalid file descriptor 'fd'" } */
+  close(fd);
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-5.c b/gcc/testsuite/gcc.dg/analyzer/fd-access-mode-macros.c
similarity index 98%
rename from gcc/testsuite/gcc.dg/analyzer/fd-5.c
rename to gcc/testsuite/gcc.dg/analyzer/fd-access-mode-macros.c
index c18b2adcbe5..f9a6931a5db 100644
--- a/gcc/testsuite/gcc.dg/analyzer/fd-5.c
+++ b/gcc/testsuite/gcc.dg/analyzer/fd-access-mode-macros.c
@@ -6,6 +6,7 @@ int read (int fd, void *buf, int nbytes);
 #define O_RDONLY 0
 #define O_WRONLY 1
 #define O_RDWR 2
+#define O_ACCMODE 0x3
 
 void f (int fd) __attribute__((fd_arg(1))); /* { dg-message "argument 1 of 'f' must be an open file descriptor, due to '__attribute__\\(\\(fd_arg\\(1\\)\\)\\)'" } */
 
diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-access-mode-target-headers.c b/gcc/testsuite/gcc.dg/analyzer/fd-access-mode-target-headers.c
new file mode 100644
index 00000000000..b76eb667d50
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/fd-access-mode-target-headers.c
@@ -0,0 +1,56 @@
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include "analyzer-decls.h"
+
+void f (int fd) __attribute__((fd_arg(1))); /* { dg-message "argument 1 of 'f' must be an open file descriptor, due to '__attribute__\\(\\(fd_arg\\(1\\)\\)\\)'" } */
+
+void
+test_1 (const char *path)
+{
+    int fd = open (path, O_RDWR);
+    close(fd);
+    f(fd); /* { dg-warning "'f' on closed file descriptor 'fd'" } */
+      /* { dg-message "\\(3\\) 'f' on closed file descriptor 'fd'; 'close' was at \\(2\\)" "" { target *-*-* } .-1 } */
+}
+
+void g (int fd) __attribute__((fd_arg_read(1))); /* { dg-message "argument 1 of 'g' must be a readable file descriptor, due to '__attribute__\\(\\(fd_arg_read\\(1\\)\\)\\)'" } */
+
+void
+test_2 (const char *path)
+{
+  int fd = open (path, O_WRONLY);
+  if (fd != -1)
+  {
+    g (fd); /* { dg-warning "'g' on write-only file descriptor 'fd'" } */
+  }
+  close (fd);
+}
+
+void h (int fd) __attribute__((fd_arg_write(1))); /* { dg-message "argument 1 of 'h' must be a writable file descriptor, due to '__attribute__\\(\\(fd_arg_write\\(1\\)\\)\\)'" } */
+void
+test_3 (const char *path)
+{
+  int fd = open (path, O_RDONLY);
+  if (fd != -1)
+  {
+    h (fd); /* { dg-warning "'h' on read-only file descriptor 'fd'" } */
+  }
+  close(fd);
+}
+
+void ff (int fd) __attribute__((fd_arg(1))); /* { dg-message "argument 1 of 'ff' must be an open file descriptor, due to '__attribute__\\(\\(fd_arg\\(1\\)\\)\\)'" } */
+
+void test_4 (const char *path)
+{
+  int fd = open (path, O_RDWR);
+  ff (fd); /* { dg-warning "'ff' on possibly invalid file descriptor 'fd'" } */
+  close(fd);
+}
+
+void test_sm_fd_constants (void)
+{
+  __analyzer_dump_named_constant ("O_ACCMODE"); /* { dg-warning "named constant 'O_ACCMODE' has value '\[0-9\]+'" } */
+  __analyzer_dump_named_constant ("O_RDONLY"); /* { dg-warning "named constant 'O_RDONLY' has value '\[0-9\]+'" } */
+  __analyzer_dump_named_constant ("O_WRONLY"); /* { dg-warning "named constant 'O_WRONLY' has value '\[0-9\]+'" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-dup-1.c b/gcc/testsuite/gcc.dg/analyzer/fd-dup-1.c
index b4f43e7f0ef..bb58e9d9d5e 100644
--- a/gcc/testsuite/gcc.dg/analyzer/fd-dup-1.c
+++ b/gcc/testsuite/gcc.dg/analyzer/fd-dup-1.c
@@ -8,6 +8,7 @@ int read (int fd, void *buf, int nbytes);
 #define O_RDONLY 0
 #define O_WRONLY 1
 #define O_RDWR 2
+#define O_ACCMODE 3
 
 void test_1 (const char *path)
 {
diff --git a/gcc/testsuite/gcc.dg/analyzer/named-constants-via-enum-and-macro.c b/gcc/testsuite/gcc.dg/analyzer/named-constants-via-enum-and-macro.c
new file mode 100644
index 00000000000..9e6cbeeb036
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/named-constants-via-enum-and-macro.c
@@ -0,0 +1,12 @@
+#include "analyzer-decls.h"
+
+enum __foo {
+     O_ACCMODE = 1,
+ 
+#define O_ACCMODE O_ACCMODE
+};
+
+void test_sm_fd_constants (void)
+{
+  __analyzer_dump_named_constant ("O_ACCMODE"); /* { dg-warning "named constant 'O_ACCMODE' has value '1'" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/named-constants-via-enum.c b/gcc/testsuite/gcc.dg/analyzer/named-constants-via-enum.c
new file mode 100644
index 00000000000..e6b77b8dd18
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/named-constants-via-enum.c
@@ -0,0 +1,20 @@
+#include "analyzer-decls.h"
+
+/* Various constants used by the fd state machine.  */
+enum {
+      O_ACCMODE = 42,
+      O_RDONLY  = 0x1,
+      O_WRONLY  = 010
+};
+
+void test_sm_fd_constants (void)
+{
+  __analyzer_dump_named_constant ("O_ACCMODE"); /* { dg-warning "named constant 'O_ACCMODE' has value '42'" } */
+  __analyzer_dump_named_constant ("O_RDONLY"); /* { dg-warning "named constant 'O_RDONLY' has value '1'" } */
+  __analyzer_dump_named_constant ("O_WRONLY"); /* { dg-warning "named constant 'O_WRONLY' has value '8'" } */
+}
+
+void test_unknown (void)
+{
+  __analyzer_dump_named_constant ("UNKNOWN"); /* { dg-warning "named constant 'UNKNOWN' has unknown value" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/named-constants-via-macros-2.c b/gcc/testsuite/gcc.dg/analyzer/named-constants-via-macros-2.c
new file mode 100644
index 00000000000..9c019e7c5ef
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/named-constants-via-macros-2.c
@@ -0,0 +1,15 @@
+#include "analyzer-decls.h"
+
+/* Various constants used by the fd state machine, as macros
+   that can't be handled.  */
+
+#define O_ACCMODE (
+#define O_RDONLY  "foo"
+#define O_WRONLY  int
+
+void test_sm_fd_constants (void)
+{
+  __analyzer_dump_named_constant ("O_ACCMODE"); /* { dg-warning "named constant 'O_ACCMODE' has unknown value" } */
+  __analyzer_dump_named_constant ("O_RDONLY"); /* { dg-warning "named constant 'O_RDONLY' has unknown value" } */
+  __analyzer_dump_named_constant ("O_WRONLY"); /* { dg-warning "named constant 'O_WRONLY' has unknown value" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/named-constants-via-macros.c b/gcc/testsuite/gcc.dg/analyzer/named-constants-via-macros.c
new file mode 100644
index 00000000000..2022f98e5b6
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/named-constants-via-macros.c
@@ -0,0 +1,19 @@
+#include "analyzer-decls.h"
+
+/* Various constants used by the fd state machine.  */
+
+#define O_ACCMODE 42
+#define O_RDONLY  0x1
+#define O_WRONLY  010
+
+void test_sm_fd_constants (void)
+{
+  __analyzer_dump_named_constant ("O_ACCMODE"); /* { dg-warning "named constant 'O_ACCMODE' has value '42'" } */
+  __analyzer_dump_named_constant ("O_RDONLY"); /* { dg-warning "named constant 'O_RDONLY' has value '1'" } */
+  __analyzer_dump_named_constant ("O_WRONLY"); /* { dg-warning "named constant 'O_WRONLY' has value '8'" } */
+}
+
+void test_unknown (void)
+{
+  __analyzer_dump_named_constant ("UNKNOWN"); /* { dg-warning "named constant 'UNKNOWN' has unknown value" } */
+}
-- 
2.26.3



^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH v3] c, analyzer: support named constants in analyzer [PR106302]
  2022-11-15 18:35       ` [PATCH v3] " David Malcolm
@ 2022-11-15 18:39         ` Marek Polacek
  2022-11-17  3:05           ` [PATCH] c: fix ICE with -fanalyzer and -Wunused-macros [PR107711] David Malcolm
  0 siblings, 1 reply; 12+ messages in thread
From: Marek Polacek @ 2022-11-15 18:39 UTC (permalink / raw)
  To: David Malcolm; +Cc: gcc-patches, Joseph Myers

On Tue, Nov 15, 2022 at 01:35:05PM -0500, David Malcolm wrote:
> On Mon, 2022-11-14 at 15:42 -0500, Marek Polacek wrote:
> > On Fri, Nov 11, 2022 at 10:23:10PM -0500, David Malcolm wrote:
> > > Changes since v1: ported the doc changes from texinfo to sphinx
> > > 
> > > Successfully bootstrapped & regrtested on x86_64-pc-linux-gnu.
> > > 
> > > Are the C frontend parts OK for trunk?  (I can self-approve the
> > > analyzer parts)
> > 
> > Sorry for the delay.
> >  
> > > The patch adds an interface for frontends to call into the analyzer
> > > as
> > > the translation unit finishes.  The analyzer can then call back
> > > into the
> > > frontend to ask about the values of the named constants it cares
> > > about
> > > whilst the frontend's data structures are still around.
> > > 
> > > The patch implements this for the C frontend, which looks up the
> > > names
> > > by looking for named CONST_DECLs (which handles enum values). 
> > > Failing
> > > that, it attempts to look up the values of macros but only the
> > > simplest
> > > cases are supported (a non-traditional macro with a single
> > > CPP_NUMBER
> > > token).  It does this by building a buffer containing the macro
> > > definition and rerunning a lexer on it.
> > > 
> > > The analyzer gracefully handles the cases where named values aren't
> > > found (such as anything more complicated than described above).
> > > 
> > > The patch ports the analyzer to use this mechanism for "O_RDONLY",
> > > "O_WRONLY", and "O_ACCMODE".  I have successfully tested my socket
> > > patch
> > > to also use this for "SOCK_STREAM" and "SOCK_DGRAM", so the
> > > technique
> > > seems to work.
> > 
> > So this works well for code like
> > 
> > enum __socket_type {
> >     SOCK_STREAM = 1,
> > 
> > #define SOCK_STREAM SOCK_STREAM
> > };
> > 
> > ?
> 
> Yes: c_translation_unit::lookup_constant_by_id does the "lookup_name"
> first, and this finds the CONST_DECL, so it doesn't need to look at
> macros for this case.

Ah, nice.
 
> I've added a testcase for this in the v3 patch (gcc.dg/analyzer/named-
> constants-via-enum-and-macro.c)

Thanks.

> Thanks for the review.  Here's a v3 version of the patch.
> 
> Successfully bootstrapped & regrtested on x86_64-pc-linux-gnu.
> 
> Are the C FE parts OK for trunk?

Ok, thanks.
 

Marek


^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH v2] analyzer: add warnings relating to sockets [PR106140]
  2022-11-12  3:27   ` [PATCH v2] " David Malcolm
@ 2022-11-15 19:02     ` David Malcolm
  0 siblings, 0 replies; 12+ messages in thread
From: David Malcolm @ 2022-11-15 19:02 UTC (permalink / raw)
  To: gcc-patches

On Fri, 2022-11-11 at 22:27 -0500, David Malcolm wrote:
> Changed in v2: ported doc changes from texinfo to sphinx
> 
> Successfully bootstrapped & regrtested on x86_64-pc-linux-gnu.
> 
> I can self-approve this patch, but it depends on the named constants
> patch here:
>   * [PATCH v2] c, analyzer: support named constants in analyzer
> [PR106302]
>     *
> https://gcc.gnu.org/pipermail/gcc-patches/2022-November/605835.html
> which requires review of the C frontend changes.

Marek approved v3 of the named constants patch (thanks!), so I've now
committed both that, and this (ported back to texinfo from sphinx) to
trunk, as:
  r13-4073-gd8aba860b34203
and
  r13-4074-g86a90006864840
respectively.

Dave


^ permalink raw reply	[flat|nested] 12+ messages in thread

* [PATCH] c: fix ICE with -fanalyzer and -Wunused-macros [PR107711]
  2022-11-15 18:39         ` Marek Polacek
@ 2022-11-17  3:05           ` David Malcolm
  2022-11-17 15:30             ` Marek Polacek
  0 siblings, 1 reply; 12+ messages in thread
From: David Malcolm @ 2022-11-17  3:05 UTC (permalink / raw)
  To: gcc-patches, Marek Polacek; +Cc: Joseph Myers, David Malcolm

PR analyzer/107711 reports an ICE since r13-4073-gd8aba860b34203 with
the combination of -fanalyzer and -Wunused-macros.

The issue is that in c_translation_unit::consider_macro's call to
cpp_create_reader I was passing "ident_hash" for use by the the new
reader, but that takes ownership of that hash_table, so that ident_hash
erroneously gets freed when c_translation_unit::consider_macro calls
cpp_destroy, leading to a use-after-free in -Wunused-macros, where:

(gdb) p pfile->hash_table->pfile == pfile
$23 = false

and it's instead pointing at the freed reader from consider_macro,
leading to a use-after-free ICE.

Fixed thusly.

Successfully bootstrapped & regrtested on x86_64-pc-linux-gnu.

OK for trunk?

Thanks
Dave


gcc/c/ChangeLog:
	PR analyzer/107711
	* c-parser.cc (ana::c_translation_unit::consider_macro): Pass NULL
	to cpp_create_reader, rather than ident_hash, so that the new
	reader gets its own hash table.

gcc/testsuite/ChangeLog:
	PR analyzer/107711
	* gcc.dg/analyzer/named-constants-Wunused-macros.c: New test.

Signed-off-by: David Malcolm <dmalcolm@redhat.com>
---
 gcc/c/c-parser.cc                             |  2 +-
 .../analyzer/named-constants-Wunused-macros.c | 19 +++++++++++++++++++
 2 files changed, 20 insertions(+), 1 deletion(-)
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/named-constants-Wunused-macros.c

diff --git a/gcc/c/c-parser.cc b/gcc/c/c-parser.cc
index f3c79996fb0..1bbb39f9b08 100644
--- a/gcc/c/c-parser.cc
+++ b/gcc/c/c-parser.cc
@@ -1717,7 +1717,7 @@ private:
       return NULL_TREE;
 
     cpp_reader *old_parse_in = parse_in;
-    parse_in = cpp_create_reader (CLK_GNUC89, ident_hash, line_table);
+    parse_in = cpp_create_reader (CLK_GNUC89, NULL, line_table);
 
     pretty_printer pp;
     pp_string (&pp, (const char *) tok.val.str.text);
diff --git a/gcc/testsuite/gcc.dg/analyzer/named-constants-Wunused-macros.c b/gcc/testsuite/gcc.dg/analyzer/named-constants-Wunused-macros.c
new file mode 100644
index 00000000000..0b31cc42d78
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/named-constants-Wunused-macros.c
@@ -0,0 +1,19 @@
+/* Regression test for interaction of named constants in -fanalyzer with
+   -Wunused-macros (PR analyzer/107711).  */
+
+/* { dg-additional-options "-Wunused-macros" } */
+
+#include "analyzer-decls.h"
+
+/* Various constants used by the fd state machine.  */
+
+#define O_ACCMODE 42   /* { dg-warning "-: macro \"O_ACCMODE\" is not used" } */
+#define O_RDONLY  0x1  /* { dg-warning "-: macro \"O_RDONLY\" is not used" } */
+#define O_WRONLY  010  /* { dg-warning "-: macro \"O_WRONLY\" is not used" } */
+
+void test_sm_fd_constants (void)
+{
+  __analyzer_dump_named_constant ("O_ACCMODE"); /* { dg-warning "named constant 'O_ACCMODE' has value '42'" } */
+  __analyzer_dump_named_constant ("O_RDONLY"); /* { dg-warning "named constant 'O_RDONLY' has value '1'" } */
+  __analyzer_dump_named_constant ("O_WRONLY"); /* { dg-warning "named constant 'O_WRONLY' has value '8'" } */
+}
-- 
2.26.3


^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH] c: fix ICE with -fanalyzer and -Wunused-macros [PR107711]
  2022-11-17  3:05           ` [PATCH] c: fix ICE with -fanalyzer and -Wunused-macros [PR107711] David Malcolm
@ 2022-11-17 15:30             ` Marek Polacek
  0 siblings, 0 replies; 12+ messages in thread
From: Marek Polacek @ 2022-11-17 15:30 UTC (permalink / raw)
  To: David Malcolm; +Cc: gcc-patches, Joseph Myers

On Wed, Nov 16, 2022 at 10:05:30PM -0500, David Malcolm wrote:
> PR analyzer/107711 reports an ICE since r13-4073-gd8aba860b34203 with
> the combination of -fanalyzer and -Wunused-macros.
> 
> The issue is that in c_translation_unit::consider_macro's call to
> cpp_create_reader I was passing "ident_hash" for use by the the new
> reader, but that takes ownership of that hash_table, so that ident_hash
> erroneously gets freed when c_translation_unit::consider_macro calls
> cpp_destroy, leading to a use-after-free in -Wunused-macros, where:
> 
> (gdb) p pfile->hash_table->pfile == pfile
> $23 = false
> 
> and it's instead pointing at the freed reader from consider_macro,
> leading to a use-after-free ICE.

Ah, sneaky.

> Fixed thusly.
> 
> Successfully bootstrapped & regrtested on x86_64-pc-linux-gnu.
> 
> OK for trunk?
 
OK.

> Thanks
> Dave
> 
> 
> gcc/c/ChangeLog:
> 	PR analyzer/107711
> 	* c-parser.cc (ana::c_translation_unit::consider_macro): Pass NULL
> 	to cpp_create_reader, rather than ident_hash, so that the new
> 	reader gets its own hash table.
> 
> gcc/testsuite/ChangeLog:
> 	PR analyzer/107711
> 	* gcc.dg/analyzer/named-constants-Wunused-macros.c: New test.
> 
> Signed-off-by: David Malcolm <dmalcolm@redhat.com>
> ---
>  gcc/c/c-parser.cc                             |  2 +-
>  .../analyzer/named-constants-Wunused-macros.c | 19 +++++++++++++++++++
>  2 files changed, 20 insertions(+), 1 deletion(-)
>  create mode 100644 gcc/testsuite/gcc.dg/analyzer/named-constants-Wunused-macros.c
> 
> diff --git a/gcc/c/c-parser.cc b/gcc/c/c-parser.cc
> index f3c79996fb0..1bbb39f9b08 100644
> --- a/gcc/c/c-parser.cc
> +++ b/gcc/c/c-parser.cc
> @@ -1717,7 +1717,7 @@ private:
>        return NULL_TREE;
>  
>      cpp_reader *old_parse_in = parse_in;
> -    parse_in = cpp_create_reader (CLK_GNUC89, ident_hash, line_table);
> +    parse_in = cpp_create_reader (CLK_GNUC89, NULL, line_table);
>  
>      pretty_printer pp;
>      pp_string (&pp, (const char *) tok.val.str.text);
> diff --git a/gcc/testsuite/gcc.dg/analyzer/named-constants-Wunused-macros.c b/gcc/testsuite/gcc.dg/analyzer/named-constants-Wunused-macros.c
> new file mode 100644
> index 00000000000..0b31cc42d78
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/analyzer/named-constants-Wunused-macros.c
> @@ -0,0 +1,19 @@
> +/* Regression test for interaction of named constants in -fanalyzer with
> +   -Wunused-macros (PR analyzer/107711).  */
> +
> +/* { dg-additional-options "-Wunused-macros" } */
> +
> +#include "analyzer-decls.h"
> +
> +/* Various constants used by the fd state machine.  */
> +
> +#define O_ACCMODE 42   /* { dg-warning "-: macro \"O_ACCMODE\" is not used" } */
> +#define O_RDONLY  0x1  /* { dg-warning "-: macro \"O_RDONLY\" is not used" } */
> +#define O_WRONLY  010  /* { dg-warning "-: macro \"O_WRONLY\" is not used" } */
> +
> +void test_sm_fd_constants (void)
> +{
> +  __analyzer_dump_named_constant ("O_ACCMODE"); /* { dg-warning "named constant 'O_ACCMODE' has value '42'" } */
> +  __analyzer_dump_named_constant ("O_RDONLY"); /* { dg-warning "named constant 'O_RDONLY' has value '1'" } */
> +  __analyzer_dump_named_constant ("O_WRONLY"); /* { dg-warning "named constant 'O_WRONLY' has value '8'" } */
> +}
> -- 
> 2.26.3
> 

Marek


^ permalink raw reply	[flat|nested] 12+ messages in thread

end of thread, other threads:[~2022-11-17 15:30 UTC | newest]

Thread overview: 12+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-10-31 19:07 [PATCH] c, analyzer: support named constants in analyzer [PR106302] David Malcolm
2022-11-08  3:02 ` [PATCH] analyzer: add warnings relating to sockets [PR106140] David Malcolm
2022-11-12  3:27   ` [PATCH v2] " David Malcolm
2022-11-15 19:02     ` David Malcolm
2022-11-08  3:06 ` PING: Re: [PATCH] c, analyzer: support named constants in analyzer [PR106302] David Malcolm
2022-11-12  3:23   ` [PATCH v2] " David Malcolm
2022-11-12  3:32     ` David Malcolm
2022-11-14 20:42     ` Marek Polacek
2022-11-15 18:35       ` [PATCH v3] " David Malcolm
2022-11-15 18:39         ` Marek Polacek
2022-11-17  3:05           ` [PATCH] c: fix ICE with -fanalyzer and -Wunused-macros [PR107711] David Malcolm
2022-11-17 15:30             ` Marek Polacek

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).