The patch "analyzer: new warnings -Wanalyzer-mkstemp-missing-suffix
and -Wanalyzer-mkstemp-of-string-literal [PR105890]" added those two
warnings for mkstemp only.  This patch generalizes them to the whole
mktemp family (including GNU extensions): mktemp, mkstemp, mkostemp,
mkstemps, mkostemps, and mkdtemp.

The two warnings are renamed to reflect their broader scope:

  -Wanalyzer-mkstemp-missing-suffix becomes
  -Wanalyzer-mktemp-missing-placeholder.  For the suffixed variants
  (mkstemps, mkostemps), the diagnostic accounts for the suffix length
  when locating the "XXXXXX" placeholder.

  -Wanalyzer-mkstemp-of-string-literal becomes
  -Wanalyzer-mktemp-of-string-literal.

A new warning is also added:

  -Wanalyzer-mkostemp-redundant-flags warns when mkostemp or
  mkostemps is called with flags that include O_RDWR, O_CREAT, or
  O_EXCL, which are already implied by these functions and produce
  errors on some systems.

All three warnings are enabled by default under -fanalyzer.

Bootstrapped and tested on x86_64-pc-linux-gnu.

gcc/analyzer/ChangeLog:

        PR analyzer/105890
        * analyzer-language.cc (stash_named_constants): Stash O_CREAT,
        O_EXCL, and O_RDWR for use by kf.cc.
        * analyzer.opt: Rename -Wanalyzer-mkstemp-missing-suffix to
        -Wanalyzer-mktemp-missing-placeholder and
        -Wanalyzer-mkstemp-of-string-literal to
        -Wanalyzer-mktemp-of-string-literal.  Add
        -Wanalyzer-mkostemp-redundant-flags.  Fix alphabetical ordering.
        * analyzer.opt.urls: Regenerate.
        * kf.cc (class mkstemp_of_string_literal): Rename to...
        (class mktemp_of_string_literal): ...this.
        (class mkstemp_missing_suffix): Rename to...
        (class mktemp_missing_placeholder): ...this.  Add trailing_len
        parameter for suffixed variants.
        (class mkostemp_redundant_flags): New diagnostic class.
        (class kf_mktemp_family): New base class with shared template
        and flags checking logic.
        (kf_mktemp_family::check_template_with_suffixlen_arg): New.
        (kf_mktemp_family::check_template): New.
        (kf_mktemp_family::check_flags): New.
        (kf_mktemp_family::check_placeholder): New.
        (class kf_mkstemp): Rename to...
        (class kf_mktemp_simple): ...this.  Generalize to handle mktemp,
        mkstemp, and mkdtemp.
        (class kf_mkostemp): New known_function handler.
        (class kf_mkostemps): New known_function handler.
        (class kf_mkstemps): New known_function handler.
        (register_known_functions): Register all mktemp family handlers.

gcc/ChangeLog:

        PR analyzer/105890
        * doc/invoke.texi: Rename -Wanalyzer-mkstemp-missing-suffix to
        -Wanalyzer-mktemp-missing-placeholder and
        -Wanalyzer-mkstemp-of-string-literal to
        -Wanalyzer-mktemp-of-string-literal.  Add
        -Wanalyzer-mkostemp-redundant-flags.  Fix alphabetical ordering
        of detailed descriptions.

gcc/testsuite/ChangeLog:

        PR analyzer/105890
        * gcc.dg/analyzer/mkstemp-1.c: Update terminology from "suffix"
        to "placeholder".
        * gcc.dg/analyzer/mkdtemp-1.c: New test.
        * gcc.dg/analyzer/mkostemp-1.c: New test.
        * gcc.dg/analyzer/mkostemps-1.c: New test.
        * gcc.dg/analyzer/mkstemps-1.c: New test.
        * gcc.dg/analyzer/mktemp-1.c: New test.

Signed-off-by: Tomas Ortin Fernandez (quanrong) <[email protected]>
---
 gcc/analyzer/analyzer-language.cc           |   5 +
 gcc/analyzer/analyzer.opt                   |  20 +-
 gcc/analyzer/analyzer.opt.urls              |  15 +-
 gcc/analyzer/kf.cc                          | 414 ++++++++++++++++----
 gcc/doc/invoke.texi                         |  69 ++--
 gcc/testsuite/gcc.dg/analyzer/mkdtemp-1.c   |  87 ++++
 gcc/testsuite/gcc.dg/analyzer/mkostemp-1.c  | 132 +++++++
 gcc/testsuite/gcc.dg/analyzer/mkostemps-1.c | 138 +++++++
 gcc/testsuite/gcc.dg/analyzer/mkstemp-1.c   |  14 +-
 gcc/testsuite/gcc.dg/analyzer/mkstemps-1.c  |  93 +++++
 gcc/testsuite/gcc.dg/analyzer/mktemp-1.c    |  99 +++++
 11 files changed, 958 insertions(+), 128 deletions(-)
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/mkdtemp-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/mkostemp-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/mkostemps-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/mkstemps-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/mktemp-1.c

diff --git a/gcc/analyzer/analyzer-language.cc b/gcc/analyzer/analyzer-language.cc
index aa8125e0654..99a829aa759 100644
--- a/gcc/analyzer/analyzer-language.cc
+++ b/gcc/analyzer/analyzer-language.cc
@@ -77,6 +77,11 @@ stash_named_constants (logger *logger, const translation_unit &tu)
   maybe_stash_named_constant (logger, tu, "O_WRONLY");
   maybe_stash_named_constant (logger, tu, "SOCK_STREAM");
   maybe_stash_named_constant (logger, tu, "SOCK_DGRAM");
+
+  /* Stash named constants for use by kf.cc */
+  maybe_stash_named_constant (logger, tu, "O_CREAT");
+  maybe_stash_named_constant (logger, tu, "O_EXCL");
+  maybe_stash_named_constant (logger, tu, "O_RDWR");
 }

 /* Hook for frontend to call into analyzer when TU finishes.
diff --git a/gcc/analyzer/analyzer.opt b/gcc/analyzer/analyzer.opt
index 8b683b4cb6a..389f4ea946a 100644
--- a/gcc/analyzer/analyzer.opt
+++ b/gcc/analyzer/analyzer.opt
@@ -154,6 +154,18 @@ Wanalyzer-mismatching-deallocation
 Common Var(warn_analyzer_mismatching_deallocation) Init(1) Warning
 Warn about code paths in which the wrong deallocation function is called.

+Wanalyzer-mkostemp-redundant-flags
+Common Var(warn_analyzer_mkostemp_redundant_flags) Init(1) Warning
+Warn about code paths in which mkostemp or mkostemps are called with flags that are already included internally.
+
+Wanalyzer-mktemp-missing-placeholder
+Common Var(warn_analyzer_mktemp_missing_placeholder) Init(1) Warning
+Warn about code paths in which a function in the mktemp family is called with a template not containing the placeholder \"XXXXXX\".
+
+Wanalyzer-mktemp-of-string-literal
+Common Var(warn_analyzer_mktemp_of_string_literal) Init(1) Warning
+Warn about code paths in which a string literal is passed to a function in the mktemp family.
+
 Wanalyzer-out-of-bounds
 Common Var(warn_analyzer_out_of_bounds) Init(1) Warning
Warn about code paths in which a write or read to a buffer is out-of-bounds.
@@ -182,14 +194,6 @@ Wanalyzer-null-dereference
 Common Var(warn_analyzer_null_dereference) Init(1) Warning
 Warn about code paths in which a NULL pointer is dereferenced.

-Wanalyzer-mkstemp-missing-suffix
-Common Var(warn_analyzer_mkstemp_missing_suffix) Init(1) Warning
-Warn about code paths in which mkstemp is called with a template not ending in \"XXXXXX\".
-
-Wanalyzer-mkstemp-of-string-literal
-Common Var(warn_analyzer_mkstemp_of_string_literal) Init(1) Warning
-Warn about code paths in which a string literal is passed to mkstemp.
-
 Wanalyzer-putenv-of-auto-var
 Common Var(warn_analyzer_putenv_of_auto_var) Init(1) Warning
 Warn about code paths in which an on-stack buffer is passed to putenv.
diff --git a/gcc/analyzer/analyzer.opt.urls b/gcc/analyzer/analyzer.opt.urls
index d98174a00d6..db56a7209e9 100644
--- a/gcc/analyzer/analyzer.opt.urls
+++ b/gcc/analyzer/analyzer.opt.urls
@@ -63,6 +63,15 @@ UrlSuffix(gcc/Static-Analyzer-Options.html#index-Wanalyzer-malloc-leak)
 Wanalyzer-mismatching-deallocation
 
UrlSuffix(gcc/Static-Analyzer-Options.html#index-Wanalyzer-mismatching-deallocation)

+Wanalyzer-mkostemp-redundant-flags
+UrlSuffix(gcc/Static-Analyzer-Options.html#index-Wanalyzer-mkostemp-redundant-flags)
+
+Wanalyzer-mktemp-missing-placeholder
+UrlSuffix(gcc/Static-Analyzer-Options.html#index-Wanalyzer-mktemp-missing-placeholder)
+
+Wanalyzer-mktemp-of-string-literal
+UrlSuffix(gcc/Static-Analyzer-Options.html#index-Wanalyzer-mktemp-of-string-literal)
+
 Wanalyzer-out-of-bounds
 UrlSuffix(gcc/Static-Analyzer-Options.html#index-Wanalyzer-out-of-bounds)

@@ -84,12 +93,6 @@ UrlSuffix(gcc/Static-Analyzer-Options.html#index-Wanalyzer-null-argument)
 Wanalyzer-null-dereference
 UrlSuffix(gcc/Static-Analyzer-Options.html#index-Wanalyzer-null-dereference)

-Wanalyzer-mkstemp-missing-suffix
-UrlSuffix(gcc/Static-Analyzer-Options.html#index-Wanalyzer-mkstemp-missing-suffix)
-
-Wanalyzer-mkstemp-of-string-literal
-UrlSuffix(gcc/Static-Analyzer-Options.html#index-Wanalyzer-mkstemp-of-string-literal)
-
 Wanalyzer-putenv-of-auto-var
 UrlSuffix(gcc/Static-Analyzer-Options.html#index-Wanalyzer-putenv-of-auto-var)

diff --git a/gcc/analyzer/kf.cc b/gcc/analyzer/kf.cc
index 6ecdf5c4006..1c99176e5ff 100644
--- a/gcc/analyzer/kf.cc
+++ b/gcc/analyzer/kf.cc
@@ -747,21 +747,21 @@ kf_memset::impl_call_pre (const call_details &cd) const
   cd.maybe_set_lhs (dest_sval);
 }

-/* A subclass of pending_diagnostic for complaining about 'mkstemp'
-   called on a string literal.  */
+/* A subclass of pending_diagnostic for complaining about functions on the
+   'mktemp' family called on a string literal.  */

-class mkstemp_of_string_literal : public undefined_function_behavior
+class mktemp_of_string_literal : public undefined_function_behavior
 {
 public:
-  mkstemp_of_string_literal (const call_details &cd)
-      : undefined_function_behavior (cd)
+  mktemp_of_string_literal (const call_details &cd)
+    : undefined_function_behavior (cd)
   {
   }

   int
   get_controlling_option () const final override
   {
-    return OPT_Wanalyzer_mkstemp_of_string_literal;
+    return OPT_Wanalyzer_mktemp_of_string_literal;
   }

   bool
@@ -792,15 +792,82 @@ public:
   }
 };

-/* A subclass of pending_diagnostic for complaining about 'mkstemp'
-   called with a template that does not end with "XXXXXX".  */
+/* A subclass of pending_diagnostic for complaining about functions in the
+ 'mktemp' family called with a template that does not contain the expected
+   "XXXXXX" placeholder.  */
+
+class mktemp_missing_placeholder
+  : public pending_diagnostic_subclass<mktemp_missing_placeholder>
+{
+public:
+  mktemp_missing_placeholder (const call_details &cd, size_t trailing_len)
+ : m_call_stmt (cd.get_call_stmt ()), m_fndecl (cd.get_fndecl_for_call ()),
+      m_trailing_len (trailing_len)
+  {
+    gcc_assert (m_fndecl);
+  }
+
+  const char *
+  get_kind () const final override
+  {
+    return "mktemp_missing_placeholder";
+  }
+
+  bool
+  operator== (const mktemp_missing_placeholder &other) const
+  {
+    return &m_call_stmt == &other.m_call_stmt;
+  }
+
+  int
+  get_controlling_option () const final override
+  {
+    return OPT_Wanalyzer_mktemp_missing_placeholder;
+  }
+
+  bool
+  emit (diagnostic_emission_context &ctxt) final override
+  {
+    if (m_trailing_len == 0)
+ return ctxt.warn ("%qE template string does not end with %qs", m_fndecl,
+                       "XXXXXX");
+    else
+      return ctxt.warn ("%qE template string does not contain %qs"
+                       " before a %zu-character suffix",
+                       m_fndecl, "XXXXXX", m_trailing_len);
+  }
+
+  bool
+  describe_final_event (pretty_printer &pp,
+                       const evdesc::final_event &) final override
+  {
+    if (m_trailing_len == 0)
+ pp_printf (&pp, "%qE template string does not end with %qs", m_fndecl,
+                "XXXXXX");
+    else
+      pp_printf (&pp,
+                "%qE template string does not contain %qs"
+                " before a %zu-character suffix",
+                m_fndecl, "XXXXXX", m_trailing_len);
+    return true;
+  }
+
+private:
+  const gimple &m_call_stmt;
+  tree m_fndecl; // non-NULL
+  size_t m_trailing_len;
+};
+
+/* A subclass of pending_diagnostic for complaining about 'mkostemp'
+   or 'mkostemps' called with flags that are already included
+   internally (O_CREAT, O_EXCL, O_RDWR).  */

-class mkstemp_missing_suffix
-    : public pending_diagnostic_subclass<mkstemp_missing_suffix>
+class mkostemp_redundant_flags
+  : public pending_diagnostic_subclass<mkostemp_redundant_flags>
 {
 public:
-  mkstemp_missing_suffix (const call_details &cd)
- : m_call_stmt (cd.get_call_stmt ()), m_fndecl (cd.get_fndecl_for_call ())
+  mkostemp_redundant_flags (const call_details &cd)
+ : m_call_stmt (cd.get_call_stmt ()), m_fndecl (cd.get_fndecl_for_call ())
   {
     gcc_assert (m_fndecl);
   }
@@ -808,11 +875,11 @@ public:
   const char *
   get_kind () const final override
   {
-    return "mkstemp_missing_suffix";
+    return "mkostemp_redundant_flags";
   }

   bool
-  operator== (const mkstemp_missing_suffix &other) const
+  operator== (const mkostemp_redundant_flags &other) const
   {
     return &m_call_stmt == &other.m_call_stmt;
   }
@@ -820,22 +887,26 @@ public:
   int
   get_controlling_option () const final override
   {
-    return OPT_Wanalyzer_mkstemp_missing_suffix;
+    return OPT_Wanalyzer_mkostemp_redundant_flags;
   }

   bool
   emit (diagnostic_emission_context &ctxt) final override
   {
- return ctxt.warn ("%qE template string does not end with %qs", m_fndecl,
-                     "XXXXXX");
+    return ctxt.warn (
+      "%qE flags argument should not include %<O_RDWR%>, %<O_CREAT%>,"
+      " or %<O_EXCL%> as these are already implied",
+      m_fndecl);
   }

   bool
   describe_final_event (pretty_printer &pp,
                        const evdesc::final_event &) final override
   {
-    pp_printf (&pp, "%qE template string does not end with %qs", m_fndecl,
-              "XXXXXX");
+    pp_printf (&pp,
+              "%qE flags argument should not include %<O_RDWR%>, %<O_CREAT%>,"
+              " or %<O_EXCL%> as these are already implied",
+              m_fndecl);
     return true;
   }

@@ -844,14 +915,173 @@ private:
   tree m_fndecl; // non-NULL
 };

-/* Handler for calls to "mkstemp":
+class kf_mktemp_family : public known_function
+{
+protected:
+  /* Extract the suffixlen from the call argument at SUFFIXLEN_ARG_IDX
+     and check the template.  If the suffixlen is not a compile-time
+     constant, the check is skipped.  */
+  static void
+  check_template_with_suffixlen_arg (const call_details &cd,
+                                    unsigned int suffixlen_arg_idx);
+
+  /* Check that the template argument (arg 0) is not a string literal
+     and contains the "XXXXXX" placeholder at the expected position.
+     TRAILING_LEN is the number of characters after the placeholder
+     (0 for mkstemp/mkdtemp/mktemp/mkostemp, or the suffixlen value
+     for mkstemps/mkostemps).  */
+  static void check_template (const call_details &cd, size_t trailing_len);
+
+  /* Check whether the flags argument at FLAGS_ARG_IDX contains any of
+     O_RDWR, O_CREAT, or O_EXCL, which are already included internally
+     by mkostemp/mkostemps.  */
+ static void check_flags (const call_details &cd, unsigned int flags_arg_idx);
+
+private:
+  static const int PLACEHOLDER_LEN = 6;
+
+ /* Return true if the placeholder is "XXXXXX", false if it definitely isn't,
+   or unknown if we can't determine.  */
+  static tristate check_placeholder (const call_details &cd,
+                                    size_t trailing_len,
+                                    const svalue *ptr_sval,
+                                    const svalue *strlen_sval);
+};
+
+void
+kf_mktemp_family::check_template_with_suffixlen_arg (
+  const call_details &cd, unsigned int suffixlen_arg_idx)
+{
+  const svalue *suffixlen_sval = cd.get_arg_svalue (suffixlen_arg_idx);
+  const constant_svalue *cst_sval
+    = suffixlen_sval->dyn_cast_constant_svalue ();
+  if (!cst_sval)
+    {
+ /* Suffix length unknown, but we still need to check whether the template
+        argument is a null-terminated string.  */
+      region_model *model = cd.get_model ();
+      model->check_for_null_terminated_string_arg (cd, 0, false, nullptr);
+      return;
+    }
+
+  tree cst = cst_sval->get_constant ();
+ /* TODO: Negative suffixlen is always wrong and potentially OOB, maybe add a
+     warning in the future?  */
+  if (tree_int_cst_sgn (cst) < 0)
+    return;
+
+  unsigned HOST_WIDE_INT suffixlen = TREE_INT_CST_LOW (cst);
+  check_template (cd, suffixlen);
+}
+
+void
+kf_mktemp_family::check_template (const call_details &cd, size_t trailing_len)
+{
+  region_model_context *ctxt = cd.get_ctxt ();
+  gcc_assert (ctxt);
+  const svalue *ptr_sval = cd.get_arg_svalue (0);
+  region_model *model = cd.get_model ();
+
+  const svalue *strlen_sval
+    = model->check_for_null_terminated_string_arg (cd, 0, false, nullptr);
+  if (!strlen_sval)
+    return;
+
+  if (cd.get_arg_string_literal (0))
+      ctxt->warn (std::make_unique<mktemp_of_string_literal> (cd));
+  else if (check_placeholder (cd, trailing_len, ptr_sval, strlen_sval)
+            .is_false ())
+      ctxt->warn (
+       std::make_unique<mktemp_missing_placeholder> (cd, trailing_len));
+}
+
+void
+kf_mktemp_family::check_flags (const call_details &cd,
+                              unsigned int flags_arg_idx)
+{
+  region_model_context *ctxt = cd.get_ctxt ();
+  gcc_assert (ctxt);
+
+  const svalue *flags_sval = cd.get_arg_svalue (flags_arg_idx);
+  const constant_svalue *cst = flags_sval->dyn_cast_constant_svalue ();
+  if (!cst)
+    return;
+
+  unsigned HOST_WIDE_INT flags = TREE_INT_CST_LOW (cst->get_constant ());

-     int mkstemp(char *template);
+  /* Check whether any of the implicit flags are redundantly specified. */
+  unsigned HOST_WIDE_INT implicit_flags = 0;
+  for (const char *name : { "O_RDWR", "O_CREAT", "O_EXCL" })
+    if (tree cst_tree = get_stashed_constant_by_name (name))
+      implicit_flags |= TREE_INT_CST_LOW (cst_tree);
+
+  if (flags & implicit_flags)
+    ctxt->warn (std::make_unique<mkostemp_redundant_flags> (cd));
+}
+
+tristate
+kf_mktemp_family::check_placeholder (const call_details &cd,
+                                    size_t trailing_len,
+                                    const svalue *ptr_sval,
+                                    const svalue *strlen_sval)
+{
+  region_model *model = cd.get_model ();
+
+ const constant_svalue *len_cst = strlen_sval->dyn_cast_constant_svalue ();
+  if (!len_cst)
+    return tristate::TS_UNKNOWN;
+
+  byte_offset_t len = TREE_INT_CST_LOW (len_cst->get_constant ());
+  if (len < PLACEHOLDER_LEN + trailing_len)
+    return tristate::TS_FALSE;
+
+  tree arg_tree = cd.get_arg_tree (0);
+ const region *reg = model->deref_rvalue (ptr_sval, arg_tree, cd.get_ctxt ());
+
+  /* Find the byte offset of the pointed-to region. */
+  region_offset reg_offset = reg->get_offset (cd.get_manager ());
+  if (reg_offset.symbolic_p ())
+    return tristate::TS_UNKNOWN;
+  byte_offset_t ptr_byte_offset;
+  if (!reg_offset.get_concrete_byte_offset (&ptr_byte_offset))
+    return tristate::TS_UNKNOWN;
+
+  const region *base_reg = reg->get_base_region ();
+ const svalue *base_sval = model->get_store_value (base_reg, cd.get_ctxt ());
+
+  const constant_svalue *cst_sval = base_sval->dyn_cast_constant_svalue ();
+  if (!cst_sval)
+    return tristate::TS_UNKNOWN;
+
+  tree cst = cst_sval->get_constant ();
+  if (TREE_CODE (cst) != STRING_CST)
+    return tristate::TS_UNKNOWN;
+
+  HOST_WIDE_INT str_len = len.to_shwi ();
+  HOST_WIDE_INT start = ptr_byte_offset.to_shwi ();
+
+  /* Ensure we can read up to and including the NUL terminator at position
+     [start + str_len - trailing_len] within the STRING_CST.  */
+  HOST_WIDE_INT range = start + str_len - trailing_len + 1;
+  if (range > TREE_STRING_LENGTH (cst))
+    return tristate::TS_UNKNOWN;
+
+  if (memcmp (TREE_STRING_POINTER (cst) + start + str_len - trailing_len
+               - PLACEHOLDER_LEN,
+             "XXXXXX", PLACEHOLDER_LEN)
+      != 0)
+    return tristate::TS_FALSE;
+
+  return tristate::TS_TRUE;
+}
+
+/* Handler for calls to "mkdtemp", "mkstemp", and "mktemp", which all
+   take a single char * template argument.

    The template must not be a string constant, and its last six
    characters must be "XXXXXX".  */

-class kf_mkstemp : public known_function
+class kf_mktemp_simple : public kf_mktemp_family
 {
 public:
   bool
@@ -864,86 +1094,99 @@ public:
   impl_call_pre (const call_details &cd) const final override
   {
     if (cd.get_ctxt ())
-      check_template (cd);
+      check_template (cd, 0);

     cd.set_any_lhs_with_defaults ();
   }
+};

-private:
-  static void
-  check_template (const call_details &cd)
-  {
-    region_model_context *ctxt = cd.get_ctxt ();
-    const svalue *ptr_sval = cd.get_arg_svalue (0);
-    region_model *model = cd.get_model ();
+/* Handler for calls to "mkostemp":

-    const svalue *strlen_sval
- = model->check_for_null_terminated_string_arg (cd, 0, false, nullptr);
-    if (!strlen_sval)
-      return;
+     int mkostemp(char *template, int flags);

-    if (cd.get_arg_string_literal (0))
-      {
-       ctxt->warn (std::make_unique<mkstemp_of_string_literal> (cd));
-      }
-    else if (check_suffix (cd, ptr_sval, strlen_sval).is_false ())
+   The template must not be a string constant, and its last six
+   characters must be "XXXXXX".  Warns when flags contains O_RDWR,
+   O_CREAT, or O_EXCL, which are already included internally.  */
+
+class kf_mkostemp : public kf_mktemp_family
+{
+public:
+  bool
+  matches_call_types_p (const call_details &cd) const final override
+  {
+    return (cd.num_args () == 2 && cd.arg_is_pointer_p (0)
+           && cd.arg_is_integral_p (1));
+  }
+
+  void
+  impl_call_pre (const call_details &cd) const final override
+  {
+    if (cd.get_ctxt ())
       {
-       ctxt->warn (std::make_unique<mkstemp_missing_suffix> (cd));
+       check_template (cd, 0);
+       check_flags (cd, 1);
       }
+
+    cd.set_any_lhs_with_defaults ();
   }
+};

-  /* Return true if the template ends with "XXXXXX", false if it
-     definitely does not, or unknown if we can't determine.  */
-  static tristate
-  check_suffix (const call_details &cd, const svalue *ptr_sval,
-               const svalue *strlen_sval)
-  {
-    region_model *model = cd.get_model ();
+/* Handler for calls to "mkostemps":

- const constant_svalue *len_cst = strlen_sval->dyn_cast_constant_svalue ();
-    if (!len_cst)
-      return tristate::TS_UNKNOWN;
+     int mkostemps(char *template, int suffixlen, int flags);

-    byte_offset_t len = TREE_INT_CST_LOW (len_cst->get_constant ());
-    if (len < 6)
-      return tristate::TS_FALSE;
+   The template must not be a string constant, and must contain
+   "XXXXXX" before a suffixlen-character suffix.  Warns when flags
+   contains O_RDWR, O_CREAT, or O_EXCL, which are already included
+   internally.  */

-    tree arg_tree = cd.get_arg_tree (0);
-    const region *reg
-      = model->deref_rvalue (ptr_sval, arg_tree, cd.get_ctxt ());
+class kf_mkostemps : public kf_mktemp_family
+{
+public:
+  bool
+  matches_call_types_p (const call_details &cd) const final override
+  {
+    return (cd.num_args () == 3 && cd.arg_is_pointer_p (0)
+           && cd.arg_is_integral_p (1) && cd.arg_is_integral_p (2));
+  }

-    /* Find the byte offset of the pointed-to region. */
-    region_offset reg_offset = reg->get_offset (cd.get_manager ());
-    if (reg_offset.symbolic_p ())
-      return tristate::TS_UNKNOWN;
-    byte_offset_t ptr_byte_offset;
-    if (!reg_offset.get_concrete_byte_offset (&ptr_byte_offset))
-      return tristate::TS_UNKNOWN;
+  void
+  impl_call_pre (const call_details &cd) const final override
+  {
+    if (cd.get_ctxt ())
+      {
+       check_template_with_suffixlen_arg (cd, 1);
+       check_flags (cd, 2);
+      }

-    const region *base_reg = reg->get_base_region ();
-    const svalue *base_sval
-      = model->get_store_value (base_reg, cd.get_ctxt ());
+    cd.set_any_lhs_with_defaults ();
+  }
+};

- const constant_svalue *cst_sval = base_sval->dyn_cast_constant_svalue ();
-    if (!cst_sval)
-      return tristate::TS_UNKNOWN;
+/* Handler for calls to "mkstemps":

-    tree cst = cst_sval->get_constant ();
-    if (TREE_CODE (cst) != STRING_CST)
-      return tristate::TS_UNKNOWN;
+     int mkstemps(char *template, int suffixlen);

-    HOST_WIDE_INT str_len = len.to_shwi ();
-    HOST_WIDE_INT start = ptr_byte_offset.to_shwi ();
+   The template must not be a string constant, and must contain
+   "XXXXXX" before a suffixlen-character suffix.  */

-    /* Ensure the range [start, start + str_len] fits. */
-    if (1 + start + str_len > TREE_STRING_LENGTH (cst))
-      return tristate::TS_UNKNOWN;
+class kf_mkstemps : public kf_mktemp_family
+{
+public:
+  bool
+  matches_call_types_p (const call_details &cd) const final override
+  {
+    return (cd.num_args () == 2 && cd.arg_is_pointer_p (0)
+           && cd.arg_is_integral_p (1));
+  }

- if (memcmp (TREE_STRING_POINTER (cst) + start + str_len - 6, "XXXXXX", 6)
-       != 0)
-      return tristate::TS_FALSE;
+  void
+  impl_call_pre (const call_details &cd) const final override
+  {
+    if (cd.get_ctxt ())
+      check_template_with_suffixlen_arg (cd, 1);

-    return tristate::TS_TRUE;
+    cd.set_any_lhs_with_defaults ();
   }
 };

@@ -2546,7 +2789,14 @@ register_known_functions (known_function_manager &kfm,
   /* Known POSIX functions, and some non-standard extensions.  */
   {
     kfm.add ("fopen", std::make_unique<kf_fopen> ());
-    kfm.add ("mkstemp", std::make_unique<kf_mkstemp> ());
+    kfm.add ("mkdtemp", std::make_unique<kf_mktemp_simple> ());
+    kfm.add ("mkostemp", std::make_unique<kf_mkostemp> ());
+    kfm.add ("mkostemps", std::make_unique<kf_mkostemps> ());
+    kfm.add ("mkstemps", std::make_unique<kf_mkstemps> ());
+    kfm.add ("mkstemp", std::make_unique<kf_mktemp_simple> ());
+    /* TODO: Report mktemp as deprecated per MSC24-C
+       (https://wiki.sei.cmu.edu/confluence/x/hNYxBQ).  */
+    kfm.add ("mktemp", std::make_unique<kf_mktemp_simple> ());
     kfm.add ("putenv", std::make_unique<kf_putenv> ());
     kfm.add ("strtok", std::make_unique<kf_strtok> (rmm));

diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 6f97323f5b8..81f62ccdda9 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -527,8 +527,9 @@ Objective-C and Objective-C++ Dialects}.
 -Wno-analyzer-jump-through-null
 -Wno-analyzer-malloc-leak
 -Wno-analyzer-mismatching-deallocation
--Wno-analyzer-mkstemp-of-string-literal
--Wno-analyzer-mkstemp-missing-suffix
+-Wno-analyzer-mkostemp-redundant-flags
+-Wno-analyzer-mktemp-missing-placeholder
+-Wno-analyzer-mktemp-of-string-literal
 -Wno-analyzer-null-argument
 -Wno-analyzer-null-dereference
 -Wno-analyzer-out-of-bounds
@@ -11561,8 +11562,9 @@ Enabling this option effectively enables the following warnings:
 -Wanalyzer-jump-through-null
 -Wanalyzer-malloc-leak
 -Wanalyzer-mismatching-deallocation
--Wanalyzer-mkstemp-of-string-literal
--Wanalyzer-mkstemp-missing-suffix
+-Wanalyzer-mkostemp-redundant-flags
+-Wanalyzer-mktemp-missing-placeholder
+-Wanalyzer-mktemp-of-string-literal
 -Wanalyzer-null-argument
 -Wanalyzer-null-dereference
 -Wanalyzer-out-of-bounds
@@ -11939,27 +11941,6 @@ or a function marked with attribute @code{malloc}.

See @uref{https://cwe.mitre.org/data/definitions/401.html, CWE-401: Missing Release of Memory after Effective Lifetime}.

-@opindex Wanalyzer-mkstemp-missing-suffix
-@opindex Wno-analyzer-mkstemp-missing-suffix
-@item -Wno-analyzer-mkstemp-missing-suffix
-This warning requires @option{-fanalyzer}, which enables it; use
-@option{-Wno-analyzer-mkstemp-missing-suffix} to disable it.
-
-This diagnostic warns for paths through the code in which the template
-string passed to @code{mkstemp} does not end with @samp{XXXXXX}.
-
-@opindex Wanalyzer-mkstemp-of-string-literal
-@opindex Wno-analyzer-mkstemp-of-string-literal
-@item -Wno-analyzer-mkstemp-of-string-literal
-This warning requires @option{-fanalyzer}, which enables it; use
-@option{-Wno-analyzer-mkstemp-of-string-literal} to disable it.
-
-This diagnostic warns for paths through the code in which @code{mkstemp}
-is called on a string literal.  Since @code{mkstemp} modifies its
-argument in place, passing a string literal leads to undefined behavior.
-
-See @uref{https://wiki.sei.cmu.edu/confluence/x/VtYxBQ, STR30-C. Do not attempt to modify string literals}.
-
 @opindex Wanalyzer-mismatching-deallocation
 @opindex Wno-analyzer-mismatching-deallocation
 @item -Wno-analyzer-mismatching-deallocation
@@ -11976,6 +11957,44 @@ pairs using attribute @code{malloc}.

See @uref{https://cwe.mitre.org/data/definitions/762.html, CWE-762: Mismatched Memory Management Routines}.

+@opindex Wanalyzer-mkostemp-redundant-flags
+@opindex Wno-analyzer-mkostemp-redundant-flags
+@item -Wno-analyzer-mkostemp-redundant-flags
+This warning requires @option{-fanalyzer}, which enables it; use
+@option{-Wno-analyzer-mkostemp-redundant-flags} to disable it.
+
+This diagnostic warns for paths through the code in which
+@code{mkostemp} or @code{mkostemps} is called with flags that include
+@code{O_RDWR}, @code{O_CREAT}, or @code{O_EXCL}.  These flags are
+already implied by the function and specifying them is unnecessary, and
+produces errors on some systems.
+
+@opindex Wanalyzer-mktemp-missing-placeholder
+@opindex Wno-analyzer-mktemp-missing-placeholder
+@item -Wno-analyzer-mktemp-missing-placeholder
+This warning requires @option{-fanalyzer}, which enables it; use
+@option{-Wno-analyzer-mktemp-missing-placeholder} to disable it.
+
+This diagnostic warns for paths through the code in which a function in
+the @code{mktemp} family (@code{mkstemp}, @code{mkostemp},
+@code{mkstemps}, @code{mkostemps}, @code{mkdtemp}, @code{mktemp}) is
+called with a template string that does not contain the placeholder
+@samp{XXXXXX} at the expected position.
+
+@opindex Wanalyzer-mktemp-of-string-literal
+@opindex Wno-analyzer-mktemp-of-string-literal
+@item -Wno-analyzer-mktemp-of-string-literal
+This warning requires @option{-fanalyzer}, which enables it; use
+@option{-Wno-analyzer-mktemp-of-string-literal} to disable it.
+
+This diagnostic warns for paths through the code in which a function in
+the @code{mktemp} family (@code{mkstemp}, @code{mkostemp},
+@code{mkstemps}, @code{mkostemps}, @code{mkdtemp}, @code{mktemp}) is
+called on a string literal.  Since these functions modify their argument
+in place, passing a string literal leads to undefined behavior.
+
+See @uref{https://wiki.sei.cmu.edu/confluence/x/VtYxBQ, STR30-C. Do not attempt to modify string literals}.
+
 @opindex Wanalyzer-out-of-bounds
 @opindex Wno-analyzer-out-of-bounds
 @item -Wno-analyzer-out-of-bounds
diff --git a/gcc/testsuite/gcc.dg/analyzer/mkdtemp-1.c b/gcc/testsuite/gcc.dg/analyzer/mkdtemp-1.c
new file mode 100644
index 00000000000..0614fe2390c
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/mkdtemp-1.c
@@ -0,0 +1,87 @@
+/* { dg-additional-options "-Wno-analyzer-null-argument" } */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+extern void populate (char *buf);
+
+void test_passthrough (char *s)
+{
+  mkdtemp (s);
+}
+
+void test_string_literal_correct_placeholder (void)
+{
+ mkdtemp ("/var/tmp/dirXXXXXX"); /* { dg-warning "'mkdtemp' on a string literal \\\[STR30-C\\\]" } */ + /* { dg-message "use a writable character array" "fix suggestion" { target *-*-* } .-1 } */
+}
+
+void test_string_literal_missing_placeholder (void)
+{
+ mkdtemp ("/var/tmp/dir"); /* { dg-warning "'mkdtemp' on a string literal \\\[STR30-C\\\]" } */
+}
+
+void test_string_literal_empty (void)
+{
+ mkdtemp (""); /* { dg-warning "'mkdtemp' on a string literal \\\[STR30-C\\\]" } */
+}
+
+void test_correct (void)
+{
+  char tmpl[] = "/var/tmp/mydir.XXXXXX";
+  mkdtemp (tmpl);
+}
+
+void test_correct_minimal (void)
+{
+  char tmpl[] = "XXXXXX";
+  mkdtemp (tmpl);
+}
+
+void test_correct_offset_into_buffer (void)
+{
+  char buf[] = "prefixXXXXXX";
+  mkdtemp (buf + 6);
+}
+
+void test_missing_placeholder (void)
+{
+  char tmpl[] = "/var/tmp/dir";
+ mkdtemp (tmpl); /* { dg-warning "'mkdtemp' template string does not end with 'XXXXXX'" } */
+}
+
+void test_too_short (void)
+{
+  char tmpl[] = "XX";
+ mkdtemp (tmpl); /* { dg-warning "'mkdtemp' template string does not end with 'XXXXXX'" } */
+}
+
+void test_empty_buffer (void)
+{
+  char tmpl[] = "";
+ mkdtemp (tmpl); /* { dg-warning "'mkdtemp' template string does not end with 'XXXXXX'" } */
+}
+
+void test_partial_placeholder (void)
+{
+  char tmpl[] = "/var/tmp/dirXXXXX.";
+ mkdtemp (tmpl); /* { dg-warning "'mkdtemp' template string does not end with 'XXXXXX'" } */
+}
+
+void test_four_xs (void)
+{
+  char tmpl[] = "/var/tmp/dirXXXX";
+ mkdtemp (tmpl); /* { dg-warning "'mkdtemp' template string does not end with 'XXXXXX'" } */
+}
+
+void test_populated_buf (void)
+{
+  char tmpl[32];
+  populate (tmpl);
+  mkdtemp (tmpl);
+}
+
+void test_NULL (void)
+{
+  mkdtemp (NULL); /* possibly -Wanalyzer-null-argument */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/mkostemp-1.c b/gcc/testsuite/gcc.dg/analyzer/mkostemp-1.c
new file mode 100644
index 00000000000..057e3694730
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/mkostemp-1.c
@@ -0,0 +1,132 @@
+/* { dg-additional-options "-Wno-analyzer-null-argument" } */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <fcntl.h>
+
+extern int mkostemp (char *, int);
+extern void populate (char *buf);
+
+void test_passthrough (char *s, int flags)
+{
+  mkostemp (s, flags);
+}
+
+void test_string_literal_correct_placeholder (void)
+{
+ mkostemp ("/tmp/testXXXXXX", O_CLOEXEC); /* { dg-warning "'mkostemp' on a string literal \\\[STR30-C\\\]" } */ + /* { dg-message "use a writable character array" "fix suggestion" { target *-*-* } .-1 } */
+}
+
+void test_string_literal_missing_placeholder (void)
+{
+ mkostemp ("/tmp/test", O_CLOEXEC); /* { dg-warning "'mkostemp' on a string literal \\\[STR30-C\\\]" } */
+}
+
+void test_string_literal_empty (void)
+{
+ mkostemp ("", 0); /* { dg-warning "'mkostemp' on a string literal \\\[STR30-C\\\]" } */
+}
+
+void test_correct (void)
+{
+  char tmpl[] = "/tmp/test.XXXXXX";
+  mkostemp (tmpl, O_CLOEXEC);
+}
+
+void test_correct_minimal (void)
+{
+  char tmpl[] = "XXXXXX";
+  mkostemp (tmpl, 0);
+}
+
+void test_correct_offset_into_buffer (void)
+{
+  char buf[] = "////XXXXXX";
+  mkostemp (buf + 4, O_CLOEXEC);
+}
+
+void test_missing_placeholder (void)
+{
+  char tmpl[] = "/tmp/test";
+ mkostemp (tmpl, O_CLOEXEC); /* { dg-warning "'mkostemp' template string does not end with 'XXXXXX'" } */
+}
+
+void test_too_short (void)
+{
+  char tmpl[] = "XXXX";
+ mkostemp (tmpl, 0); /* { dg-warning "'mkostemp' template string does not end with 'XXXXXX'" } */
+}
+
+void test_empty_buffer (void)
+{
+  char tmpl[] = "";
+ mkostemp (tmpl, O_CLOEXEC); /* { dg-warning "'mkostemp' template string does not end with 'XXXXXX'" } */
+}
+
+void test_trailing_nul_after_placeholder (void)
+{
+  char tmpl[] = "/tmp/testXXXXXXz";
+ mkostemp (tmpl, 0); /* { dg-warning "'mkostemp' template string does not end with 'XXXXXX'" } */
+}
+
+void test_five_xs (void)
+{
+  char tmpl[] = "/tmp/testXXXXX";
+ mkostemp (tmpl, O_CLOEXEC); /* { dg-warning "'mkostemp' template string does not end with 'XXXXXX'" } */
+}
+
+void test_populated_buf (void)
+{
+  char tmpl[24];
+  populate (tmpl);
+  mkostemp (tmpl, O_CLOEXEC);
+}
+
+void test_NULL (void)
+{
+  mkostemp (NULL, 0); /* possibly -Wanalyzer-null-argument */
+}
+
+/* Getting a stashed constant can currently fail depending on the system
+headers; see e.g.  https://gcc.gnu.org/bugzilla/show_bug.cgi?id=108708
+
+It may be needed to add a dg-skip for `test_redundant_o_rdwr`,
+`test_redundant_o_creat`, `test_redundant_o_excl`, and
+`test_redundant_combined` on some systems.  */
+
+void test_redundant_o_rdwr (void)
+{
+  char tmpl[] = "/tmp/testXXXXXX";
+ mkostemp (tmpl, O_RDWR); /* { dg-warning "'mkostemp' flags argument should not include 'O_RDWR', 'O_CREAT', or 'O_EXCL' as these are already implied" } */
+}
+
+void test_redundant_o_creat (void)
+{
+  char tmpl[] = "/tmp/testXXXXXX";
+ mkostemp (tmpl, O_CREAT); /* { dg-warning "'mkostemp' flags argument should not include 'O_RDWR', 'O_CREAT', or 'O_EXCL' as these are already implied" } */
+}
+
+void test_redundant_o_excl (void)
+{
+  char tmpl[] = "/tmp/testXXXXXX";
+ mkostemp (tmpl, O_EXCL); /* { dg-warning "'mkostemp' flags argument should not include 'O_RDWR', 'O_CREAT', or 'O_EXCL' as these are already implied" } */
+}
+
+void test_redundant_combined (void)
+{
+  char tmpl[] = "/tmp/testXXXXXX";
+ mkostemp (tmpl, O_RDWR | O_CREAT); /* { dg-warning "'mkostemp' flags argument should not include 'O_RDWR', 'O_CREAT', or 'O_EXCL' as these are already implied" } */
+}
+
+void test_valid_flags (void)
+{
+  char tmpl[] = "/tmp/testXXXXXX";
+  mkostemp (tmpl, O_CLOEXEC);
+}
+
+void test_zero_flags (void)
+{
+  char tmpl[] = "/tmp/testXXXXXX";
+  mkostemp (tmpl, 0);
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/mkostemps-1.c b/gcc/testsuite/gcc.dg/analyzer/mkostemps-1.c
new file mode 100644
index 00000000000..da571c41a24
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/mkostemps-1.c
@@ -0,0 +1,138 @@
+/* { dg-additional-options "-Wno-analyzer-null-argument" } */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <fcntl.h>
+
+extern int mkostemps (char *, int, int);
+extern void populate (char *buf);
+
+void test_passthrough (char *s, int suffixlen, int flags)
+{
+  mkostemps (s, suffixlen, flags);
+}
+
+void test_string_literal_correct_placeholder (void)
+{
+ mkostemps ("/tmp/dataXXXXXX.db", 3, O_CLOEXEC); /* { dg-warning "'mkostemps' on a string literal \\\[STR30-C\\\]" } */ + /* { dg-message "use a writable character array" "fix suggestion" { target *-*-* } .-1 } */
+}
+
+void test_string_literal_suffix_off_by_one (void)
+{
+ mkostemps ("/tmp/dataXXXXXX.db", 2, O_CLOEXEC); /* { dg-warning "'mkostemps' on a string literal \\\[STR30-C\\\]" } */
+}
+
+void test_string_literal_missing_placeholder (void)
+{
+ mkostemps ("/tmp/data.db", 3, O_CLOEXEC); /* { dg-warning "'mkostemps' on a string literal \\\[STR30-C\\\]" } */
+}
+
+void test_string_literal_empty (void)
+{
+ mkostemps ("", 0, 0); /* { dg-warning "'mkostemps' on a string literal \\\[STR30-C\\\]" } */
+}
+
+void test_correct_with_suffix (void)
+{
+  char tmpl[] = "/tmp/dataXXXXXX.db";
+  mkostemps (tmpl, 3, O_CLOEXEC);
+}
+
+void test_correct_zero_suffix (void)
+{
+  char tmpl[] = "/tmp/logXXXXXX";
+  mkostemps (tmpl, 0, O_CLOEXEC);
+}
+
+void test_correct_long_suffix (void)
+{
+  char tmpl[] = "XXXXXX.json";
+  mkostemps (tmpl, 5, 0);
+}
+
+void test_missing_placeholder_with_suffix (void)
+{
+  char tmpl[] = "/tmp/data.json";
+ mkostemps (tmpl, 5, O_CLOEXEC); /* { dg-warning "'mkostemps' template string does not contain 'XXXXXX' before a 5-character suffix" } */
+}
+
+void test_placeholder_at_wrong_position (void)
+{
+ /* "XXXXXX" is at the end, but suffixlen says 2 chars should follow it. */
+  char tmpl[] = "/tmp/dataXXXXXX";
+ mkostemps (tmpl, 2, 0); /* { dg-warning "'mkostemps' template string does not contain 'XXXXXX' before a 2-character suffix" } */
+}
+
+void test_too_short_for_suffix (void)
+{
+  char tmpl[] = "XY";
+ mkostemps (tmpl, 1, O_CLOEXEC); /* { dg-warning "'mkostemps' template string does not contain 'XXXXXX' before a 1-character suffix" } */
+}
+
+void test_empty_buffer_with_suffix (void)
+{
+  char tmpl[] = "";
+ mkostemps (tmpl, 2, 0); /* { dg-warning "'mkostemps' template string does not contain 'XXXXXX' before a 2-character suffix" } */
+}
+
+void test_suffix_consumes_placeholder (void)
+{
+  char tmpl[] = "XXXXXX.c";
+ mkostemps (tmpl, 6, O_CLOEXEC); /* { dg-warning "'mkostemps' template string does not contain 'XXXXXX' before a 6-character suffix" } */
+}
+
+void test_populated_buf (void)
+{
+  char tmpl[28];
+  populate (tmpl);
+  mkostemps (tmpl, 3, O_CLOEXEC);
+}
+
+void test_NULL (void)
+{
+  mkostemps (NULL, 0, 0); /* possibly -Wanalyzer-null-argument */
+}
+
+/* Getting a stashed constant can currently fail depending on the system
+headers; see e.g.  https://gcc.gnu.org/bugzilla/show_bug.cgi?id=108708
+
+It may be needed to add a dg-skip for `test_redundant_o_rdwr`,
+`test_redundant_o_creat`, `test_redundant_o_excl`, and
+`test_redundant_combined` on some systems.  */
+
+void test_redundant_o_rdwr (void)
+{
+  char tmpl[] = "/tmp/dataXXXXXX.db";
+ mkostemps (tmpl, 3, O_RDWR); /* { dg-warning "'mkostemps' flags argument should not include 'O_RDWR', 'O_CREAT', or 'O_EXCL' as these are already implied" } */
+}
+
+void test_redundant_o_creat (void)
+{
+  char tmpl[] = "/tmp/dataXXXXXX.db";
+ mkostemps (tmpl, 3, O_CREAT); /* { dg-warning "'mkostemps' flags argument should not include 'O_RDWR', 'O_CREAT', or 'O_EXCL' as these are already implied" } */
+}
+
+void test_redundant_o_excl (void)
+{
+  char tmpl[] = "/tmp/dataXXXXXX.db";
+ mkostemps (tmpl, 3, O_EXCL); /* { dg-warning "'mkostemps' flags argument should not include 'O_RDWR', 'O_CREAT', or 'O_EXCL' as these are already implied" } */
+}
+
+void test_redundant_combined (void)
+{
+  char tmpl[] = "/tmp/dataXXXXXX.db";
+ mkostemps (tmpl, 3, O_RDWR | O_CREAT | O_EXCL); /* { dg-warning "'mkostemps' flags argument should not include 'O_RDWR', 'O_CREAT', or 'O_EXCL' as these are already implied" } */
+}
+
+void test_valid_flags (void)
+{
+  char tmpl[] = "/tmp/dataXXXXXX.db";
+  mkostemps (tmpl, 3, O_CLOEXEC);
+}
+
+void test_zero_flags (void)
+{
+  char tmpl[] = "/tmp/dataXXXXXX.db";
+  mkostemps (tmpl, 3, 0);
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/mkstemp-1.c b/gcc/testsuite/gcc.dg/analyzer/mkstemp-1.c
index 2eda175f29f..dc8ce9e2b92 100644
--- a/gcc/testsuite/gcc.dg/analyzer/mkstemp-1.c
+++ b/gcc/testsuite/gcc.dg/analyzer/mkstemp-1.c
@@ -10,13 +10,13 @@ void test_passthrough (char *s)
   mkstemp (s);
 }

-void test_string_literal_correct_suffix (void)
+void test_string_literal_correct_placeholder (void)
 {
mkstemp ("/tmp/fooXXXXXX"); /* { dg-warning "'mkstemp' on a string literal \\\[STR30-C\\\]" } */ /* { dg-message "use a writable character array" "fix suggestion" { target *-*-* } .-1 } */
 }

-void test_string_literal_missing_suffix (void)
+void test_string_literal_missing_placeholder (void)
 {
mkstemp ("/tmp/foo"); /* { dg-warning "'mkstemp' on a string literal \\\[STR30-C\\\]" } */
 }
@@ -41,18 +41,18 @@ void test_correct_minimal (void)
 void test_correct_offset_into_buffer (void)
 {
   char buf[] = "/tmp/XXXXXX";
-  /* Suffix is still correct from the pointer's perspective. */
+  /* Placeholder is still correct from the pointer's perspective.  */
   mkstemp (buf + 5);
 }

-void test_missing_suffix_offset_into_buffer (void)
+void test_missing_placeholder_offset_into_buffer (void)
 {
   char buf[] = "/tmp/XXXXXX";
-  /* Suffix is incorrect from the pointer's perspective. */
+  /* Placeholder is incorrect from the pointer's perspective.  */
mkstemp (buf + 6); /* { dg-warning "'mkstemp' template string does not end with 'XXXXXX'" } */
 }

-void test_missing_suffix (void)
+void test_missing_placeholder (void)
 {
   char tmpl[] = "/tmp/foo";
mkstemp (tmpl); /* { dg-warning "'mkstemp' template string does not end with 'XXXXXX'" } */
@@ -70,7 +70,7 @@ void test_empty_buffer (void)
mkstemp (tmpl); /* { dg-warning "'mkstemp' template string does not end with 'XXXXXX'" } */
 }

-void test_partial_suffix (void)
+void test_partial_placeholder (void)
 {
   char tmpl[] = "/tmp/fooXXXXX_";
mkstemp (tmpl); /* { dg-warning "'mkstemp' template string does not end with 'XXXXXX'" } */ diff --git a/gcc/testsuite/gcc.dg/analyzer/mkstemps-1.c b/gcc/testsuite/gcc.dg/analyzer/mkstemps-1.c
new file mode 100644
index 00000000000..10699ef53a2
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/mkstemps-1.c
@@ -0,0 +1,93 @@
+/* { dg-additional-options "-Wno-analyzer-null-argument" } */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+extern void populate (char *buf);
+
+void test_passthrough (char *s, int suffixlen)
+{
+  mkstemps (s, suffixlen);
+}
+
+void test_string_literal_correct_placeholder (void)
+{
+ mkstemps ("/tmp/fooXXXXXX.txt", 4); /* { dg-warning "'mkstemps' on a string literal \\\[STR30-C\\\]" } */ + /* { dg-message "use a writable character array" "fix suggestion" { target *-*-* } .-1 } */
+}
+
+void test_string_literal_suffix_off_by_one (void)
+{
+ mkstemps ("/tmp/fooXXXXXX.txt", 3); /* { dg-warning "'mkstemps' on a string literal \\\[STR30-C\\\]" } */
+}
+
+void test_string_literal_missing_placeholder (void)
+{
+ mkstemps ("/tmp/foo.txt", 4); /* { dg-warning "'mkstemps' on a string literal \\\[STR30-C\\\]" } */
+}
+
+void test_string_literal_empty (void)
+{
+ mkstemps ("", 0); /* { dg-warning "'mkstemps' on a string literal \\\[STR30-C\\\]" } */
+}
+
+void test_correct_with_suffix (void)
+{
+  char tmpl[] = "/tmp/fooXXXXXX.txt";
+  mkstemps (tmpl, 4);
+}
+
+void test_correct_zero_suffix (void)
+{
+  char tmpl[] = "/tmp/barXXXXXX";
+  mkstemps (tmpl, 0);
+}
+
+void test_correct_single_char_suffix (void)
+{
+  char tmpl[] = "XXXXXXZ";
+  mkstemps (tmpl, 1);
+}
+
+void test_missing_placeholder_with_suffix (void)
+{
+  char tmpl[] = "/tmp/foo.conf";
+ mkstemps (tmpl, 5); /* { dg-warning "'mkstemps' template string does not contain 'XXXXXX' before a 5-character suffix" } */
+}
+
+void test_placeholder_at_wrong_position (void)
+{
+ /* "XXXXXX" is at the end, but suffixlen says 3 chars should follow it. */
+  char tmpl[] = "/tmp/fooXXXXXX";
+ mkstemps (tmpl, 3); /* { dg-warning "'mkstemps' template string does not contain 'XXXXXX' before a 3-character suffix" } */
+}
+
+void test_too_short_for_suffix (void)
+{
+  char tmpl[] = "abc";
+ mkstemps (tmpl, 2); /* { dg-warning "'mkstemps' template string does not contain 'XXXXXX' before a 2-character suffix" } */
+}
+
+void test_empty_buffer_with_suffix (void)
+{
+  char tmpl[] = "";
+ mkstemps (tmpl, 3); /* { dg-warning "'mkstemps' template string does not contain 'XXXXXX' before a 3-character suffix" } */
+}
+
+void test_suffix_too_large (void)
+{
+  char tmpl[] = "XXXXXXAB";
+ mkstemps (tmpl, 4); /* { dg-warning "'mkstemps' template string does not contain 'XXXXXX' before a 4-character suffix" } */
+}
+
+void test_populated_buf (void)
+{
+  char tmpl[30];
+  populate (tmpl);
+  mkstemps (tmpl, 4);
+}
+
+void test_NULL (void)
+{
+  mkstemps (NULL, 0); /* possibly -Wanalyzer-null-argument */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/mktemp-1.c b/gcc/testsuite/gcc.dg/analyzer/mktemp-1.c
new file mode 100644
index 00000000000..19b565c5321
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/mktemp-1.c
@@ -0,0 +1,99 @@
+/* { dg-additional-options "-Wno-analyzer-null-argument" } */
+
+/* TODO: mktemp is deprecated per MSC24-C
+   (https://wiki.sei.cmu.edu/confluence/x/hNYxBQ).
+   Once a warning for deprecated functions exists, mktemp should
+   also warn about its use.  */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+extern void populate (char *buf);
+
+void test_passthrough (char *s)
+{
+  mktemp (s);
+}
+
+void test_string_literal_correct_placeholder (void)
+{
+ mktemp ("/home/user/sessXXXXXX"); /* { dg-warning "'mktemp' on a string literal \\\[STR30-C\\\]" } */ + /* { dg-message "use a writable character array" "fix suggestion" { target *-*-* } .-1 } */
+}
+
+void test_string_literal_missing_placeholder (void)
+{
+ mktemp ("/home/user/sess"); /* { dg-warning "'mktemp' on a string literal \\\[STR30-C\\\]" } */
+}
+
+void test_string_literal_empty (void)
+{
+ mktemp (""); /* { dg-warning "'mktemp' on a string literal \\\[STR30-C\\\]" } */
+}
+
+void test_correct (void)
+{
+  char tmpl[] = "/var/run/sock.XXXXXX";
+  mktemp (tmpl);
+}
+
+void test_correct_minimal (void)
+{
+  char tmpl[] = "XXXXXX";
+  mktemp (tmpl);
+}
+
+void test_correct_offset_into_buffer (void)
+{
+  char buf[] = "////XXXXXX";
+  mktemp (buf + 4);
+}
+
+void test_missing_placeholder_offset_into_buffer (void)
+{
+  char buf[] = "////XXXXXX";
+  /* Placeholder is incorrect from the pointer's perspective.  */
+ mktemp (buf + 5); /* { dg-warning "'mktemp' template string does not end with 'XXXXXX'" } */
+}
+
+void test_missing_placeholder (void)
+{
+  char tmpl[] = "/var/run/sock";
+ mktemp (tmpl); /* { dg-warning "'mktemp' template string does not end with 'XXXXXX'" } */
+}
+
+void test_too_short (void)
+{
+  char tmpl[] = "XY";
+ mktemp (tmpl); /* { dg-warning "'mktemp' template string does not end with 'XXXXXX'" } */
+}
+
+void test_empty_buffer (void)
+{
+  char tmpl[] = "";
+ mktemp (tmpl); /* { dg-warning "'mktemp' template string does not end with 'XXXXXX'" } */
+}
+
+void test_partial_placeholder (void)
+{
+  char tmpl[] = "/var/run/sockXXXXX-";
+ mktemp (tmpl); /* { dg-warning "'mktemp' template string does not end with 'XXXXXX'" } */
+}
+
+void test_four_xs (void)
+{
+  char tmpl[] = "/var/run/sockXXXX";
+ mktemp (tmpl); /* { dg-warning "'mktemp' template string does not end with 'XXXXXX'" } */
+}
+
+void test_populated_buf (void)
+{
+  char tmpl[24];
+  populate (tmpl);
+  mktemp (tmpl);
+}
+
+void test_NULL (void)
+{
+  mktemp (NULL); /* possibly -Wanalyzer-null-argument */
+}
--
2.52.0


Reply via email to