This patch adds two new analyzer warnings for misuse of mkstemp(3):

  -Wanalyzer-mkstemp-of-string-literal warns when a string literal is
  passed to mkstemp.  Since mkstemp modifies its argument in place,
  passing a string literal is undefined behavior (SEI CERT C rule
  STR30-C).  The diagnostic suggests using a writable character array
  instead.

  -Wanalyzer-mkstemp-missing-suffix warns when the template argument
  does not end with the required "XXXXXX" suffix.  This addresses PR
  analyzer/105890.

Both warnings are enabled by default under -fanalyzer.

The checks are in the analyzer rather than -Wformat because mkstemp
does not use a format attribute.  Placing the checks in the analyzer
could also allow interprocedural analysis in the future, once the
analyzer can fully track string contents across function calls.

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

gcc/analyzer/ChangeLog:

        PR analyzer/105890
        * analyzer.opt: Add -Wanalyzer-mkstemp-missing-suffix and
        -Wanalyzer-mkstemp-of-string-literal.
        * analyzer.opt.urls: Add URL entries for the new warnings.
        * kf.cc (class mkstemp_of_string_literal): New diagnostic class
        for mkstemp called on a string literal.
        (class mkstemp_missing_suffix): New diagnostic class for mkstemp
        called with a template missing the "XXXXXX" suffix.
        (class kf_mkstemp): New known_function handler for mkstemp.
        (register_known_functions): Register kf_mkstemp.

gcc/testsuite/ChangeLog:

        * gcc.dg/analyzer/mkstemp-1.c: New test.

From d55184be560979d1ba2880edc0dab84b78faa256 Mon Sep 17 00:00:00 2001
From: "Tomas Ortin Fernandez (quanrong)" <[email protected]>
Date: Mon, 9 Mar 2026 21:28:22 +0100
Subject: [PATCH] analyzer: new warnings -Wanalyzer-mkstemp-missing-suffix and
 -Wanalyzer-mkstemp-of-string-literal [PR105890]

This patch adds two new analyzer warnings for misuse of mkstemp(3):

  -Wanalyzer-mkstemp-of-string-literal warns when a string literal is
  passed to mkstemp.  Since mkstemp modifies its argument in place,
  passing a string literal is undefined behavior (SEI CERT C rule
  STR30-C).  The diagnostic suggests using a writable character array
  instead.

  -Wanalyzer-mkstemp-missing-suffix warns when the template argument
  does not end with the required "XXXXXX" suffix, as mkstemp requires
  the last six characters of the template to be 'X's, which it
  replaces to create a unique filename.  This addresses PR
  analyzer/105890.

Both warnings are enabled by default under -fanalyzer.

The checks are in the analyzer rather than -Wformat because mkstemp
does not use a format attribute.  Placing the checks in the analyzer
could also allow interprocedural analysis in the future, once the
analyzer can fully track string contents across function calls.

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

gcc/analyzer/ChangeLog:

        PR analyzer/105890
        * analyzer.opt: Add -Wanalyzer-mkstemp-missing-suffix and
        -Wanalyzer-mkstemp-of-string-literal.
        * analyzer.opt.urls: Add URL entries for the new warnings.
        * kf.cc (class mkstemp_of_string_literal): New diagnostic class
        for mkstemp called on a string literal.
        (class mkstemp_missing_suffix): New diagnostic class for mkstemp
        called with a template missing the "XXXXXX" suffix.
        (class kf_mkstemp): New known_function handler for mkstemp.
        (register_known_functions): Register kf_mkstemp.

gcc/testsuite/ChangeLog:

        * gcc.dg/analyzer/mkstemp-1.c: New test.
---
 gcc/analyzer/analyzer.opt                 |   8 +
 gcc/analyzer/analyzer.opt.urls            |   6 +
 gcc/analyzer/kf.cc                        | 185 ++++++++++++++++++++++
 gcc/testsuite/gcc.dg/analyzer/mkstemp-1.c |  82 ++++++++++
 4 files changed, 281 insertions(+)
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/mkstemp-1.c

diff --git a/gcc/analyzer/analyzer.opt b/gcc/analyzer/analyzer.opt
index 3c5dd0849c6..ad4d97b3fbf 100644
--- a/gcc/analyzer/analyzer.opt
+++ b/gcc/analyzer/analyzer.opt
@@ -182,6 +182,14 @@ 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 1a698f9c6d9..fe26d92f494 100644
--- a/gcc/analyzer/analyzer.opt.urls
+++ b/gcc/analyzer/analyzer.opt.urls
@@ -84,6 +84,12 @@ 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-Wanalzyer-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 b1ccbd6584a..173c52b58bd 100644
--- a/gcc/analyzer/kf.cc
+++ b/gcc/analyzer/kf.cc
@@ -747,6 +747,190 @@ 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.  */
+
+class mkstemp_of_string_literal : public undefined_function_behavior
+{
+public:
+  mkstemp_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;
+  }
+
+  bool
+  emit (diagnostic_emission_context &ctxt) final override
+  {
+    auto_diagnostic_group d;
+
+ /* SEI CERT C Coding Standard: "STR30-C. Do not attempt to modify string
+       literals.  */
+    diagnostics::metadata::precanned_rule rule (
+      "STR30-C", "https://wiki.sei.cmu.edu/confluence/x/VtYxBQ";);
+    ctxt.add_rule (rule);
+
+ bool warned = ctxt.warn ("%qE on a string literal", get_callee_fndecl ());
+    if (warned)
+      inform (ctxt.get_location (),
+             "use a writable character array as the template argument,"
+             " e.g. %<char tmpl[] = \"/tmp/fooXXXXXX\"%>");
+    return warned;
+  }
+
+  bool
+  describe_final_event (pretty_printer &pp,
+                       const evdesc::final_event &) final override
+  {
+    pp_printf (&pp, "%qE on a string literal", get_callee_fndecl ());
+    return true;
+  }
+};
+
+/* A subclass of pending_diagnostic for complaining about 'mkstemp'
+   called with a template that does not end with XXXXXX.  */
+
+class mkstemp_missing_suffix
+    : public pending_diagnostic_subclass<mkstemp_missing_suffix>
+{
+public:
+  mkstemp_missing_suffix (const call_details &cd)
+ : m_call_stmt (cd.get_call_stmt ()), m_fndecl (cd.get_fndecl_for_call ())
+  {
+    gcc_assert (m_fndecl);
+  }
+
+  const char *
+  get_kind () const final override
+  {
+    return "mkstemp_missing_suffix";
+  }
+
+  bool
+  operator== (const mkstemp_missing_suffix &other) const
+  {
+    return &m_call_stmt == &other.m_call_stmt;
+  }
+
+  int
+  get_controlling_option () const final override
+  {
+    return OPT_Wanalyzer_mkstemp_missing_suffix;
+  }
+
+  bool
+  emit (diagnostic_emission_context &ctxt) final override
+  {
+    return ctxt.warn ("%qE template does not end with XXXXXX", m_fndecl);
+  }
+
+  bool
+  describe_final_event (pretty_printer &pp,
+                       const evdesc::final_event &) final override
+  {
+    pp_printf (&pp, "%qE template does not end with XXXXXX", m_fndecl);
+    return true;
+  }
+
+private:
+  const gimple &m_call_stmt; // non-NULL
+  tree m_fndecl;            // non-NULL
+};
+
+/* Handler for calls to "mkstemp":
+
+     int mkstemp(char *template);
+
+   The template must not be a string constant, and its last six
+   characters must be "XXXXXX".  */
+
+class kf_mkstemp : public known_function
+{
+public:
+  bool
+  matches_call_types_p (const call_details &cd) const final override
+  {
+    return (cd.num_args () == 1 && cd.arg_is_pointer_p (0));
+  }
+
+  void
+  impl_call_pre (const call_details &cd) const final override
+  {
+    const svalue *ptr_sval = cd.get_arg_svalue (0);
+    region_model_context *ctxt = cd.get_ctxt ();
+    region_model *model = cd.get_model ();
+
+    const svalue *strlen_sval
+ = model->check_for_null_terminated_string_arg (cd, 0, false, nullptr);
+    if (!strlen_sval)
+      {
+       if (ctxt)
+         ctxt->terminate_path ();
+       return;
+      }
+
+    if (cd.get_arg_string_literal (0))
+      {
+       if (ctxt)
+         ctxt->warn (std::make_unique<mkstemp_of_string_literal> (cd));
+      }
+    else if (check_suffix (cd, ptr_sval, strlen_sval).is_false ())
+      {
+       if (ctxt)
+         ctxt->warn (std::make_unique<mkstemp_missing_suffix> (cd));
+      }
+
+    cd.set_any_lhs_with_defaults ();
+  }
+
+private:
+  /* 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 ();
+
+ 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 < 6)
+      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 ());
+    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 ();
+    if (1 + str_len > TREE_STRING_LENGTH (cst))
+      return tristate::TS_UNKNOWN;
+
+    if (memcmp (TREE_STRING_POINTER (cst) + str_len - 6, "XXXXXX", 6) != 0)
+      return tristate::TS_FALSE;
+
+    return tristate::TS_TRUE;
+  }
+};
+
 /* A subclass of pending_diagnostic for complaining about 'putenv'
    called on an auto var.  */

@@ -2346,6 +2530,7 @@ 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 ("putenv", std::make_unique<kf_putenv> ());
     kfm.add ("strtok", std::make_unique<kf_strtok> (rmm));

diff --git a/gcc/testsuite/gcc.dg/analyzer/mkstemp-1.c b/gcc/testsuite/gcc.dg/analyzer/mkstemp-1.c
new file mode 100644
index 00000000000..2b6c82e3311
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/mkstemp-1.c
@@ -0,0 +1,82 @@
+/* { dg-additional-options "-Wno-analyzer-null-argument" } */
+/* { dg-skip-if "has no putenv" { "avr-*-*" } } */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+extern void populate (char *buf);
+
+void test_passthrough (char *s)
+{
+  mkstemp (s);
+}
+
+void test_string_literal_correct_suffix (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)
+{
+ mkstemp ("/tmp/foo"); /* { dg-warning "'mkstemp' on a string literal \\\[STR30-C\\\]" } */
+}
+
+void test_string_literal_empty (void)
+{
+ mkstemp (""); /* { dg-warning "'mkstemp' on a string literal \\\[STR30-C\\\]" } */
+}
+
+void test_correct (void)
+{
+  char tmpl[] = "/tmp/fooXXXXXX";
+  mkstemp (tmpl);
+}
+
+void test_correct_minimal (void)
+{
+  char tmpl[] = "XXXXXX";
+  mkstemp (tmpl);
+}
+
+void test_missing_suffix (void)
+{
+  char tmpl[] = "/tmp/foo";
+ mkstemp (tmpl); /* { dg-warning "'mkstemp' template does not end with XXXXXX" } */
+}
+
+void test_too_short (void)
+{
+  char tmpl[] = "XXX";
+ mkstemp (tmpl); /* { dg-warning "'mkstemp' template does not end with XXXXXX" } */
+}
+
+void test_empty_buffer (void)
+{
+  char tmpl[] = "";
+ mkstemp (tmpl); /* { dg-warning "'mkstemp' template does not end with XXXXXX" } */
+}
+
+void test_partial_suffix (void)
+{
+  char tmpl[] = "/tmp/fooXXXXX_";
+ mkstemp (tmpl); /* { dg-warning "'mkstemp' template does not end with XXXXXX" } */
+}
+
+void test_five_xs (void)
+{
+  char tmpl[] = "/tmp/fooXXXXX";
+ mkstemp (tmpl); /* { dg-warning "'mkstemp' template does not end with XXXXXX" } */
+}
+
+void test_populated_buf (void)
+{
+  char tmpl[20];
+  populate (tmpl);
+  mkstemp (tmpl);
+}
+
+void test_NULL (void)
+{
+  mkstemp (NULL); /* possibly -Wanalyzer-null-argument */
+}
--
2.52.0

Reply via email to