@@ -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,
new file mode 100644
@@ -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"
new file mode 100644
@@ -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 */
@@ -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,
@@ -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. */
@@ -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
@@ -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
@@ -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);
@@ -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;
}
@@ -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).
@@ -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
@@ -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);
@@ -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
new file mode 100644
@@ -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);
+}
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
@@ -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\\)\\)\\)'" } */
new file mode 100644
@@ -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\]+'" } */
+}
@@ -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)
{
new file mode 100644
@@ -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'" } */
+}
new file mode 100644
@@ -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" } */
+}
new file mode 100644
@@ -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" } */
+}
new file mode 100644
@@ -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" } */
+}