From cfe0e48381a18a5af9a4168163ba011b9e41c811 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Ort=C3=ADn=20Fern=C3=A1ndez?= Date: Tue, 17 Mar 2026 09:59:41 +0100 Subject: [PATCH 17/38] analyzer: generalize mktemp-family warnings; add -Wanalyzer-mkostemp-redundant-flags [PR105890] 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) --- 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 18c7abf0470..a7d5b90d3a4 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 +{ +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 +class mkostemp_redundant_flags + : public pending_diagnostic_subclass { 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 %, %," + " or % 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 %, %," + " or % 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 (cd)); + else if (check_placeholder (cd, trailing_len, ptr_sval, strlen_sval) + .is_false ()) + ctxt->warn ( + std::make_unique (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 (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 (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 (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 (); } }; @@ -2569,7 +2812,14 @@ register_known_functions (known_function_manager &kfm, /* Known POSIX functions, and some non-standard extensions. */ { kfm.add ("fopen", std::make_unique ()); - kfm.add ("mkstemp", std::make_unique ()); + kfm.add ("mkdtemp", std::make_unique ()); + kfm.add ("mkostemp", std::make_unique ()); + kfm.add ("mkostemps", std::make_unique ()); + kfm.add ("mkstemps", std::make_unique ()); + kfm.add ("mkstemp", std::make_unique ()); + /* TODO: Report mktemp as deprecated per MSC24-C + (https://wiki.sei.cmu.edu/confluence/x/hNYxBQ). */ + kfm.add ("mktemp", std::make_unique ()); kfm.add ("putenv", std::make_unique ()); kfm.add ("strtok", std::make_unique (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 +#include + +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 +#include +#include + +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 +#include +#include + +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 +#include + +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 +#include + +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.49.0