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" } */
+}
