From 34e3f187a258c09ca96d4355d856e9d307cf733d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Ort=C3=ADn=20Fern=C3=A1ndez?= Date: Fri, 20 Mar 2026 15:57:13 +0100 Subject: [PATCH 14/98] analyzer: model mktemp-family success/failure outcomes [PR105890] The known_function handlers for the mktemp family all use set_any_lhs_with_defaults, leaving the return value unconstrained. This means the analyzer cannot distinguish the success path from the failure path and cannot, for example, detect use of an invalid file descriptor returned by mkstemp. This patch makes the analyzer aware of each function's return convention. A nested enum kf_mktemp_family::outcome describes the three conventions used by the family: fd -- returns a non-negative fd on success, -1 on failure (mkstemp, mkostemp, mkstemps, mkostemps). null_ptr -- returns a pointer on success, NULL on failure (mkdtemp). modif_tmpl -- returns the template pointer; sets template[0] to '\0' on failure (mktemp). Each call is bifurcated into success and failure paths, modeling the return value and errno according to the outcome. This enables fd leak and double-close detection for the fd-returning variants. A new helper region_model::update_for_null_return is added for the null_ptr failure path. The template placeholder check is now in impl_call_post and influences bifurcation: when the placeholder is definitely invalid, only the failure path is explored. Bootstrapped and tested on x86_64-pc-linux-gnu. gcc/analyzer/ChangeLog: PR analyzer/105890 * kf.cc (class kf_mktemp_family): Add nested outcome enum, constructor, and nested failure and success classes. (kf_mktemp_family::check_template_with_suffixlen_arg): Remove. (kf_mktemp_family::check_template): Remove. Both replaced by... (kf_mktemp_family::check_for_string_literal_arg): ...this. (kf_mktemp_family::get_trailing_len): New. (kf_mktemp_family::impl_call_post): New. (class kf_mktemp_simple): Add constructor taking outcome. Replace check_template with check_for_string_literal_arg. Remove set_any_lhs_with_defaults call. (class kf_mkostemp): Add constructor. Replace check_template with check_for_string_literal_arg. Remove set_any_lhs_with_defaults call. (class kf_mkostemps): Likewise. (class kf_mkstemps): Likewise. (register_known_functions): Pass outcome to kf_mktemp_simple instantiations. * region-model.cc (region_model::update_for_null_return): New. * region-model.h (class region_model): Add update_for_null_return. gcc/testsuite/ChangeLog: PR analyzer/105890 * gcc.dg/analyzer/mkdtemp-1.c: Add tests for errno on success/failure, non-null return identity, and no-lhs call. * gcc.dg/analyzer/mkostemp-1.c: Prune fd leak warnings. * gcc.dg/analyzer/mkostemps-1.c: Likewise. * gcc.dg/analyzer/mkstemp-1.c: Likewise. * gcc.dg/analyzer/mkstemps-1.c: Likewise. * gcc.dg/analyzer/mktemp-1.c: Add errno, failure, and success path tests. * gcc.dg/analyzer/fd-mktemp-family.c: New test. Signed-off-by: Tomas Ortin Fernandez (quanrong) --- gcc/analyzer/kf.cc | 343 ++++++++++++++---- gcc/analyzer/region-model.cc | 18 + gcc/analyzer/region-model.h | 2 + .../gcc.dg/analyzer/fd-mktemp-family.c | 121 ++++++ gcc/testsuite/gcc.dg/analyzer/mkdtemp-1.c | 30 ++ gcc/testsuite/gcc.dg/analyzer/mkostemp-1.c | 1 + gcc/testsuite/gcc.dg/analyzer/mkostemps-1.c | 1 + gcc/testsuite/gcc.dg/analyzer/mkstemp-1.c | 1 + gcc/testsuite/gcc.dg/analyzer/mkstemps-1.c | 1 + gcc/testsuite/gcc.dg/analyzer/mktemp-1.c | 26 ++ 10 files changed, 477 insertions(+), 67 deletions(-) create mode 100644 gcc/testsuite/gcc.dg/analyzer/fd-mktemp-family.c diff --git a/gcc/analyzer/kf.cc b/gcc/analyzer/kf.cc index a7d5b90d3a45..3860a763f745 100644 --- a/gcc/analyzer/kf.cc +++ b/gcc/analyzer/kf.cc @@ -917,27 +917,48 @@ private: class kf_mktemp_family : public known_function { +public: + /* Describes how the mktemp-family function signals success or failure + through its return value. */ + enum class outcome + { + /* Returns fd on success, -1 on failure (mkstemp, mkostemp, etc.). */ + fd, + + /* Returns pointer on success, NULL on failure (mkdtemp). */ + null_ptr, + + /* Returns template pointer; first byte \0 on failure (mktemp). */ + modif_tmpl + }; + 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); + /* suffixlen_arg_idx is the index of the suffixlen argument, or -1 + if there is none (trailing_len is implicitly 0). */ + kf_mktemp_family (outcome oc, int suffixlen_arg_idx) + : m_outcome (oc), m_suffixlen_arg_idx (suffixlen_arg_idx) + { + } + + class failure; + class success; + + static void check_for_string_literal_arg (const call_details &cd); /* 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); + HOST_WIDE_INT get_trailing_len (const call_details &cd, + tristate &valid) const; + + void impl_call_post (const call_details &cd) const final override; + private: + outcome m_outcome; + int m_suffixlen_arg_idx; + static const int PLACEHOLDER_LEN = 6; /* Return true if the placeholder is "XXXXXX", false if it definitely isn't, @@ -949,50 +970,17 @@ private: }; 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) +kf_mktemp_family::check_for_string_literal_arg (const call_details &cd) { 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; - + cd.get_model ()->check_for_null_terminated_string_arg (cd, 0, false, + nullptr); 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)); + ctxt->terminate_path (); + } } void @@ -1019,6 +1007,224 @@ kf_mktemp_family::check_flags (const call_details &cd, ctxt->warn (std::make_unique (cd)); } +/* Extract the trailing length from the suffixlen argument. + + Returns the trailing length on success, or -1 if the suffixlen is + not a compile-time constant. Sets VALID to TS_FALSE if the + suffixlen is a negative constant (always invalid). */ + +HOST_WIDE_INT +kf_mktemp_family::get_trailing_len (const call_details &cd, + tristate &valid) const +{ + if (m_suffixlen_arg_idx < 0) + return 0; + + const svalue *suffixlen_sval = cd.get_arg_svalue (m_suffixlen_arg_idx); + const constant_svalue *cst = suffixlen_sval->dyn_cast_constant_svalue (); + if (!cst) + return -1; + + /* TODO: Negative suffixlen is always wrong and potentially OOB, maybe add a + warning in the future? */ + if (tree_int_cst_sgn (cst->get_constant ()) < 0) + { + valid = tristate::TS_FALSE; + return -1; + } + + return TREE_INT_CST_LOW (cst->get_constant ()); +} + +/* Model the failure outcome of a mktemp-family function call. + + Sets the return value according to the function's convention (fd == -1, NULL + pointer, or '\0' in template[0]) and sets errno. */ + +class kf_mktemp_family::failure : public failed_call_info +{ +public: + failure (const call_details &cd, outcome oc) + : failed_call_info (cd), m_outcome (oc) + { + } + + bool + update_model (region_model *model, const exploded_edge *, + region_model_context *ctxt) const final override + { + const call_details cd (get_call_details (model, ctxt)); + + switch (m_outcome) + { + case outcome::fd: + model->update_for_int_cst_return (cd, -1, true); + break; + case outcome::null_ptr: + model->update_for_null_return (cd, true); + break; + case outcome::modif_tmpl: + { + const svalue *first_arg_svalue = cd.get_arg_svalue (0); + + /* Return the same pointer that was passed in. */ + cd.maybe_set_lhs (first_arg_svalue); + + region_model_manager *mgr = cd.get_manager (); + const region *template_reg = model->deref_rvalue ( + first_arg_svalue, cd.get_arg_tree (0), ctxt); + + /* mktemp may have modified the X positions before failing; + invalidate the old buffer contents. */ + model->mark_region_as_unknown (template_reg, nullptr); + + /* Then: template[0] = '\0'. */ + const svalue *nul = mgr->get_or_create_int_cst (char_type_node, 0); + model->set_value (template_reg, nul, ctxt); + break; + } + default: + gcc_unreachable (); + } + + model->set_errno (cd); + return true; + } + +private: + outcome m_outcome; +}; + +/* Model the success outcome of a mktemp-family function call. + + For fd-returning functions, conjures a non-negative fd and marks it valid. + For pointer-returning functions, returns the template pointer and marks the + template region as modified. */ + +class kf_mktemp_family::success : public success_call_info +{ +public: + success (const call_details &cd, outcome oc) + : success_call_info (cd), m_outcome (oc) + { + } + + bool + update_model (region_model *model, const exploded_edge *, + region_model_context *ctxt) const final override + { + const call_details cd (get_call_details (model, ctxt)); + + switch (m_outcome) + { + case outcome::fd: + { + region_model_manager *mgr = cd.get_manager (); + const region *lhs_reg = cd.get_lhs_region (); + /* conjured_svalue needs a non-null id_reg. When there is + no LHS, use an unknown symbolic region; we still conjure + and mark the fd so the leak checker can see it. */ + const region *id_reg + = lhs_reg ? lhs_reg + : mgr->get_unknown_symbolic_region (integer_type_node); + + tree lhs_type = cd.get_lhs_type (); + if (!lhs_type) + lhs_type = integer_type_node; + + conjured_purge p (model, ctxt); + const svalue *fd_sval = mgr->get_or_create_conjured_svalue ( + lhs_type, &cd.get_call_stmt (), id_reg, p); + const svalue *zero = mgr->get_or_create_int_cst (lhs_type, 0); + + if (!model->add_constraint (fd_sval, GE_EXPR, zero, ctxt)) + return false; + + if (lhs_reg) + model->set_value (lhs_reg, fd_sval, ctxt); + + /* TODO: transition to an unchecked state so that + use-without-check can be detected. */ + model->mark_as_valid_fd (fd_sval, ctxt); + break; + } + case outcome::null_ptr: + { + region_model_manager *mgr = cd.get_manager (); + const svalue *first_arg_svalue = cd.get_arg_svalue (0); + cd.maybe_set_lhs (first_arg_svalue); + + /* On success, mkdtemp returns non-NULL. */ + const svalue *null_ptr + = mgr->get_or_create_int_cst (first_arg_svalue->get_type (), 0); + if (!model->add_constraint (first_arg_svalue, NE_EXPR, null_ptr, + ctxt)) + return false; + + const region *template_reg = model->deref_rvalue ( + first_arg_svalue, cd.get_arg_tree (0), ctxt); + model->mark_region_as_unknown (template_reg, nullptr); + break; + } + case outcome::modif_tmpl: + { + const svalue *first_arg_svalue = cd.get_arg_svalue (0); + cd.maybe_set_lhs (first_arg_svalue); + const region *template_reg = model->deref_rvalue ( + first_arg_svalue, cd.get_arg_tree (0), ctxt); + model->mark_region_as_unknown (template_reg, nullptr); + break; + } + default: + gcc_unreachable (); + } + + return true; + } + +private: + outcome m_outcome; +}; + +/* Bifurcate into success and failure paths. The template placeholder is + validated when possible. If definitely invalid, only the failure path is + explored. */ + +void +kf_mktemp_family::impl_call_post (const call_details &cd) const +{ + if (!cd.get_ctxt ()) + return; + + tristate valid = tristate::TS_UNKNOWN; + HOST_WIDE_INT trailing_len = get_trailing_len (cd, valid); + + /* Determine whether the template placeholder is valid. */ + const svalue *strlen_sval = nullptr; + const svalue *ptr_sval = nullptr; + if (trailing_len >= 0 && !valid.is_false ()) + { + ptr_sval = cd.get_arg_svalue (0); + strlen_sval = cd.get_model ()->check_for_null_terminated_string_arg ( + cd, 0, false, nullptr); + } + + if (strlen_sval) + { + valid = check_placeholder (cd, trailing_len, ptr_sval, strlen_sval); + if (valid.is_false ()) + cd.get_ctxt ()->warn ( + std::make_unique (cd, trailing_len)); + } + + /* Failure is always possible (bad template or runtime failure). */ + cd.get_ctxt ()->bifurcate (std::make_unique (cd, m_outcome)); + /* Success is only possible if the template is not definitely invalid. */ + if (!valid.is_false ()) + cd.get_ctxt ()->bifurcate (std::make_unique (cd, m_outcome)); + cd.get_ctxt ()->terminate_path (); +} + tristate kf_mktemp_family::check_placeholder (const call_details &cd, size_t trailing_len, @@ -1084,6 +1290,8 @@ kf_mktemp_family::check_placeholder (const call_details &cd, class kf_mktemp_simple : public kf_mktemp_family { public: + kf_mktemp_simple (outcome oc) : kf_mktemp_family (oc, -1) {} + bool matches_call_types_p (const call_details &cd) const final override { @@ -1094,9 +1302,7 @@ public: impl_call_pre (const call_details &cd) const final override { if (cd.get_ctxt ()) - check_template (cd, 0); - - cd.set_any_lhs_with_defaults (); + check_for_string_literal_arg (cd); } }; @@ -1111,6 +1317,8 @@ public: class kf_mkostemp : public kf_mktemp_family { public: + kf_mkostemp () : kf_mktemp_family (outcome::fd, -1) {} + bool matches_call_types_p (const call_details &cd) const final override { @@ -1123,11 +1331,9 @@ public: { if (cd.get_ctxt ()) { - check_template (cd, 0); + check_for_string_literal_arg (cd); check_flags (cd, 1); } - - cd.set_any_lhs_with_defaults (); } }; @@ -1143,6 +1349,8 @@ public: class kf_mkostemps : public kf_mktemp_family { public: + kf_mkostemps () : kf_mktemp_family (outcome::fd, 1) {} + bool matches_call_types_p (const call_details &cd) const final override { @@ -1155,11 +1363,9 @@ public: { if (cd.get_ctxt ()) { - check_template_with_suffixlen_arg (cd, 1); + check_for_string_literal_arg (cd); check_flags (cd, 2); } - - cd.set_any_lhs_with_defaults (); } }; @@ -1173,6 +1379,8 @@ public: class kf_mkstemps : public kf_mktemp_family { public: + kf_mkstemps () : kf_mktemp_family (outcome::fd, 1) {} + bool matches_call_types_p (const call_details &cd) const final override { @@ -1184,9 +1392,7 @@ public: impl_call_pre (const call_details &cd) const final override { if (cd.get_ctxt ()) - check_template_with_suffixlen_arg (cd, 1); - - cd.set_any_lhs_with_defaults (); + check_for_string_literal_arg (cd); } }; @@ -2812,14 +3018,17 @@ register_known_functions (known_function_manager &kfm, /* Known POSIX functions, and some non-standard extensions. */ { kfm.add ("fopen", std::make_unique ()); - kfm.add ("mkdtemp", std::make_unique ()); + kfm.add ("mkdtemp", std::make_unique ( + kf_mktemp_family::outcome::null_ptr)); kfm.add ("mkostemp", std::make_unique ()); kfm.add ("mkostemps", std::make_unique ()); kfm.add ("mkstemps", std::make_unique ()); - kfm.add ("mkstemp", std::make_unique ()); + kfm.add ("mkstemp", std::make_unique ( + kf_mktemp_family::outcome::fd)); /* TODO: Report mktemp as deprecated per MSC24-C (https://wiki.sei.cmu.edu/confluence/x/hNYxBQ). */ - kfm.add ("mktemp", std::make_unique ()); + kfm.add ("mktemp", std::make_unique ( + kf_mktemp_family::outcome::modif_tmpl)); kfm.add ("putenv", std::make_unique ()); kfm.add ("strtok", std::make_unique (rmm)); diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc index 4a6f5b4800c0..d46b130b00d9 100644 --- a/gcc/analyzer/region-model.cc +++ b/gcc/analyzer/region-model.cc @@ -1930,6 +1930,24 @@ region_model::update_for_zero_return (const call_details &cd, update_for_int_cst_return (cd, 0, unmergeable); } +/* Update this model for an outcome of a call that returns a NULL + pointer. + If UNMERGEABLE, then make the result unmergeable, e.g. to prevent + the state-merger code from merging success and failure outcomes. */ + +void +region_model::update_for_null_return (const call_details &cd, bool unmergeable) +{ + if (!cd.get_lhs_type ()) + return; + if (!POINTER_TYPE_P (cd.get_lhs_type ())) + return; + const svalue *result = m_mgr->get_or_create_null_ptr (cd.get_lhs_type ()); + if (unmergeable) + result = m_mgr->get_or_create_unmergeable (result); + set_value (cd.get_lhs_region (), result, cd.get_ctxt ()); +} + /* Update this model for an outcome of a call that returns non-zero. Specifically, assign an svalue to the LHS, and add a constraint that that svalue is non-zero. */ diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h index fb15afb71d07..f4cf38e3e0fb 100644 --- a/gcc/analyzer/region-model.h +++ b/gcc/analyzer/region-model.h @@ -352,6 +352,8 @@ class region_model bool unmergeable); void update_for_zero_return (const call_details &cd, bool unmergeable); + void update_for_null_return (const call_details &cd, + bool unmergeable); void update_for_nonzero_return (const call_details &cd); void handle_unrecognized_call (const gcall &call, diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-mktemp-family.c b/gcc/testsuite/gcc.dg/analyzer/fd-mktemp-family.c new file mode 100644 index 000000000000..db690256c059 --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/fd-mktemp-family.c @@ -0,0 +1,121 @@ +/* Tests for fd leak and errno handling of mktemp-family functions. */ +/* { dg-additional-options "-Wno-analyzer-null-argument" } */ + +#include +#include +#include +#include +#include "analyzer-decls.h" + +extern int mkostemp (char *, int); +extern int mkostemps (char *, int, int); + +/* For the following 8 tests, leak warnings may be reported at the call or at + the closing brace, perhaps depending on system headers. Keep each body on + one line so the dg-warning directive matches either location. */ + +void test_mkstemp_leak_no_lhs (char *s) +{ mkstemp (s); } /* { dg-warning "leak of file descriptor" } */ + +void test_mkostemp_leak_no_lhs (char *s, int flags) +{ mkostemp (s, flags); } /* { dg-warning "leak of file descriptor" } */ + +void test_mkstemps_leak_no_lhs (char *s, int suffixlen) +{ mkstemps (s, suffixlen); } /* { dg-warning "leak of file descriptor" } */ + +void test_mkostemps_leak_no_lhs (char *s, int suffixlen, int flags) +{ mkostemps (s, suffixlen, flags); } /* { dg-warning "leak of file descriptor" } */ + +void test_mkstemp_leak_unchecked (char *s) +{ int fd = mkstemp (s); } /* { dg-warning "leak of file descriptor 'fd'" } */ + +void test_mkostemp_leak_unchecked (char *s, int flags) +{ int fd = mkostemp (s, flags); } /* { dg-warning "leak of file descriptor 'fd'" } */ + +void test_mkstemps_leak_unchecked (char *s, int suffixlen) +{ int fd = mkstemps (s, suffixlen); } /* { dg-warning "leak of file descriptor 'fd'" } */ + +void test_mkostemps_leak_unchecked (char *s, int suffixlen, int flags) +{ int fd = mkostemps (s, suffixlen, flags); } /* { dg-warning "leak of file descriptor 'fd'" } */ + +void test_mkstemp_leak_checked (char *s) +{ + int fd = mkstemp (s); /* { dg-message "opened here" } */ + if (fd == -1) + return; +} /* { dg-warning "leak of file descriptor 'fd'" } */ + +void test_mkostemp_leak_checked (char *s, int flags) +{ + int fd = mkostemp (s, flags); /* { dg-message "opened here" } */ + if (fd == -1) + return; +} /* { dg-warning "leak of file descriptor 'fd'" } */ + +void test_mkstemps_leak_checked (char *s, int suffixlen) +{ + int fd = mkstemps (s, suffixlen); /* { dg-message "opened here" } */ + if (fd == -1) + return; +} /* { dg-warning "leak of file descriptor 'fd'" } */ + +void test_mkostemps_leak_checked (char *s, int suffixlen, int flags) +{ + int fd = mkostemps (s, suffixlen, flags); /* { dg-message "opened here" } */ + if (fd == -1) + return; +} /* { dg-warning "leak of file descriptor 'fd'" } */ + +/* TODO: test for 'close' on unchecked mkstemp-family fd. */ + +void test_mkstemp_errno (char *s) +{ + errno = 0; + int fd = mkstemp (s); + if (fd == -1) + { + __analyzer_eval (errno > 0); /* { dg-warning "TRUE" } */ + return; + } + __analyzer_eval (errno == 0); /* { dg-warning "TRUE" } */ + close (fd); +} + +void test_mkostemp_errno (char *s, int flags) +{ + errno = 0; + int fd = mkostemp (s, flags); + if (fd == -1) + { + __analyzer_eval (errno > 0); /* { dg-warning "TRUE" } */ + return; + } + __analyzer_eval (errno == 0); /* { dg-warning "TRUE" } */ + close (fd); +} + +void test_mkstemps_errno (char *s, int suffixlen) +{ + errno = 0; + int fd = mkstemps (s, suffixlen); + if (fd == -1) + { + __analyzer_eval (errno > 0); /* { dg-warning "TRUE" } */ + return; + } + __analyzer_eval (errno == 0); /* { dg-warning "TRUE" } */ + close (fd); +} + +void test_mkostemps_errno (char *s, int suffixlen, int flags) +{ + errno = 0; + int fd = mkostemps (s, suffixlen, flags); + if (fd == -1) + { + __analyzer_eval (errno > 0); /* { dg-warning "TRUE" } */ + return; + } + __analyzer_eval (errno == 0); /* { dg-warning "TRUE" } */ + close (fd); +} diff --git a/gcc/testsuite/gcc.dg/analyzer/mkdtemp-1.c b/gcc/testsuite/gcc.dg/analyzer/mkdtemp-1.c index 0614fe2390c6..da77ce786516 100644 --- a/gcc/testsuite/gcc.dg/analyzer/mkdtemp-1.c +++ b/gcc/testsuite/gcc.dg/analyzer/mkdtemp-1.c @@ -2,6 +2,8 @@ #include #include +#include +#include "analyzer-decls.h" extern void populate (char *buf); @@ -85,3 +87,31 @@ void test_NULL (void) { mkdtemp (NULL); /* possibly -Wanalyzer-null-argument */ } + +void test_errno (char *s) +{ + errno = 0; + char *result = mkdtemp (s); + if (result == NULL) + { + __analyzer_eval (errno > 0); /* { dg-warning "TRUE" } */ + return; + } + __analyzer_eval (errno == 0); /* { dg-warning "TRUE" } */ +} + +void test_success_non_null (void) +{ + char tmpl[] = "/var/tmp/dirXXXXXX"; + char *result = mkdtemp (tmpl); + if (result != NULL) + { + __analyzer_eval (result == tmpl); /* { dg-warning "TRUE" } */ + __analyzer_eval (result != NULL); /* { dg-warning "TRUE" } */ + } +} + +void test_no_lhs (char *s) +{ + mkdtemp (s); /* { dg-bogus "leak" } */ +} diff --git a/gcc/testsuite/gcc.dg/analyzer/mkostemp-1.c b/gcc/testsuite/gcc.dg/analyzer/mkostemp-1.c index 057e36947302..55135ffd9479 100644 --- a/gcc/testsuite/gcc.dg/analyzer/mkostemp-1.c +++ b/gcc/testsuite/gcc.dg/analyzer/mkostemp-1.c @@ -1,4 +1,5 @@ /* { dg-additional-options "-Wno-analyzer-null-argument" } */ +/* { dg-prune-output "leak of file descriptor" } */ #include #include diff --git a/gcc/testsuite/gcc.dg/analyzer/mkostemps-1.c b/gcc/testsuite/gcc.dg/analyzer/mkostemps-1.c index da571c41a24d..013906092d53 100644 --- a/gcc/testsuite/gcc.dg/analyzer/mkostemps-1.c +++ b/gcc/testsuite/gcc.dg/analyzer/mkostemps-1.c @@ -1,4 +1,5 @@ /* { dg-additional-options "-Wno-analyzer-null-argument" } */ +/* { dg-prune-output "leak of file descriptor" } */ #include #include diff --git a/gcc/testsuite/gcc.dg/analyzer/mkstemp-1.c b/gcc/testsuite/gcc.dg/analyzer/mkstemp-1.c index dc8ce9e2b929..e7a7a724abfe 100644 --- a/gcc/testsuite/gcc.dg/analyzer/mkstemp-1.c +++ b/gcc/testsuite/gcc.dg/analyzer/mkstemp-1.c @@ -1,4 +1,5 @@ /* { dg-additional-options "-Wno-analyzer-null-argument" } */ +/* { dg-prune-output "leak of file descriptor" } */ #include #include diff --git a/gcc/testsuite/gcc.dg/analyzer/mkstemps-1.c b/gcc/testsuite/gcc.dg/analyzer/mkstemps-1.c index 10699ef53a22..75fcb9dbb08d 100644 --- a/gcc/testsuite/gcc.dg/analyzer/mkstemps-1.c +++ b/gcc/testsuite/gcc.dg/analyzer/mkstemps-1.c @@ -1,4 +1,5 @@ /* { dg-additional-options "-Wno-analyzer-null-argument" } */ +/* { dg-prune-output "leak of file descriptor" } */ #include #include diff --git a/gcc/testsuite/gcc.dg/analyzer/mktemp-1.c b/gcc/testsuite/gcc.dg/analyzer/mktemp-1.c index 19b565c53218..0f4b448a6aa6 100644 --- a/gcc/testsuite/gcc.dg/analyzer/mktemp-1.c +++ b/gcc/testsuite/gcc.dg/analyzer/mktemp-1.c @@ -7,6 +7,8 @@ #include #include +#include +#include "analyzer-decls.h" extern void populate (char *buf); @@ -97,3 +99,27 @@ void test_NULL (void) { mktemp (NULL); /* possibly -Wanalyzer-null-argument */ } + +void test_errno_bad_template (void) +{ + errno = 0; + char tmpl[] = "/tmp/foo"; + char *result = mktemp (tmpl); /* { dg-warning "'mktemp' template string does not end with 'XXXXXX'" } */ + __analyzer_eval (errno > 0); /* { dg-warning "TRUE" } */ +} + +void test_failure_nul_byte (void) +{ + char tmpl[] = "/tmp/foo"; + char *result = mktemp (tmpl); /* { dg-warning "'mktemp' template string does not end with 'XXXXXX'" } */ + __analyzer_eval (result[0] == '\0'); /* { dg-warning "TRUE" } */ + __analyzer_eval (result == tmpl); /* { dg-warning "TRUE" } */ +} + +void test_success_path (void) +{ + char tmpl[] = "/tmp/testXXXXXX"; + char *result = mktemp (tmpl); + if (result[0] != '\0') + __analyzer_eval (result == tmpl); /* { dg-warning "TRUE" } */ +} -- 2.49.0