From f2d7a4001a33884bc1dfd8da58e58dee18e3cd71 Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Wed, 6 Sep 2023 09:32:07 -0400 Subject: [PATCH] analyzer: implement kf_strstr [PR105899] gcc/analyzer/ChangeLog: PR analyzer/105899 * kf.cc (class kf_strstr): New. (kf_strstr::impl_call_post): New. (register_known_functions): Register it. gcc/testsuite/ChangeLog: PR analyzer/105899 * c-c++-common/analyzer/strstr-1.c: New test. Signed-off-by: David Malcolm --- gcc/analyzer/kf.cc | 96 ++++++++++++++++++++++++++ gcc/testsuite/c-c++-common/analyzer/strstr-1.c | 54 +++++++++++++++ 2 files changed, 150 insertions(+) create mode 100644 gcc/testsuite/c-c++-common/analyzer/strstr-1.c diff --git a/gcc/analyzer/kf.cc b/gcc/analyzer/kf.cc index 8a45c329c28..92959891fe4 100644 --- a/gcc/analyzer/kf.cc +++ b/gcc/analyzer/kf.cc @@ -1585,6 +1585,100 @@ public: } }; +/* Handler for "strstr" and "__builtin_strstr". + extern char *strstr (const char* str, const char* substr); + See e.g. https://en.cppreference.com/w/c/string/byte/strstr */ + +class kf_strstr : public builtin_known_function +{ +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_pointer_p (1)); + } + enum built_in_function builtin_code () const final override + { + return BUILT_IN_STRSTR; + } + void impl_call_pre (const call_details &cd) const final override + { + cd.check_for_null_terminated_string_arg (0); + cd.check_for_null_terminated_string_arg (1); + } + void impl_call_post (const call_details &cd) const final override; +}; + +void +kf_strstr::impl_call_post (const call_details &cd) const +{ + class strstr_call_info : public call_info + { + public: + strstr_call_info (const call_details &cd, bool found) + : call_info (cd), m_found (found) + { + } + + label_text get_desc (bool can_colorize) const final override + { + if (m_found) + return make_label_text (can_colorize, + "when %qE returns non-NULL", + get_fndecl ()); + else + return make_label_text (can_colorize, + "when %qE returns NULL", + get_fndecl ()); + } + + 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)); + if (tree lhs_type = cd.get_lhs_type ()) + { + region_model_manager *mgr = model->get_manager (); + const svalue *result; + if (m_found) + { + const svalue *str_sval = cd.get_arg_svalue (0); + const region *str_reg + = model->deref_rvalue (str_sval, cd.get_arg_tree (0), + cd.get_ctxt ()); + /* We want str_sval + OFFSET for some unknown OFFSET. + Use a conjured_svalue to represent the offset, + using the str_reg as the id of the conjured_svalue. */ + const svalue *offset + = mgr->get_or_create_conjured_svalue (size_type_node, + cd.get_call_stmt (), + str_reg, + conjured_purge (model, + ctxt)); + result = mgr->get_or_create_binop (lhs_type, POINTER_PLUS_EXPR, + str_sval, offset); + } + else + result = mgr->get_or_create_int_cst (lhs_type, 0); + cd.maybe_set_lhs (result); + } + return true; + } + private: + bool m_found; + }; + + /* Body of kf_strstr::impl_call_post. */ + if (cd.get_ctxt ()) + { + cd.get_ctxt ()->bifurcate (make_unique (cd, false)); + cd.get_ctxt ()->bifurcate (make_unique (cd, true)); + cd.get_ctxt ()->terminate_path (); + } +} + class kf_ubsan_bounds : public internal_known_function { /* Empty. */ @@ -1806,6 +1900,8 @@ register_known_functions (known_function_manager &kfm) kfm.add ("__builtin_strndup", make_unique ()); kfm.add ("strlen", make_unique ()); kfm.add ("__builtin_strlen", make_unique ()); + kfm.add ("strstr", make_unique ()); + kfm.add ("__builtin_strstr", make_unique ()); register_atomic_builtins (kfm); register_varargs_builtins (kfm); diff --git a/gcc/testsuite/c-c++-common/analyzer/strstr-1.c b/gcc/testsuite/c-c++-common/analyzer/strstr-1.c new file mode 100644 index 00000000000..469e6a817d0 --- /dev/null +++ b/gcc/testsuite/c-c++-common/analyzer/strstr-1.c @@ -0,0 +1,54 @@ +/* See e.g. https://en.cppreference.com/w/c/string/byte/strstr */ + +/* { dg-additional-options "-fpermissive" { target c++ } } */ + +#include "../../gcc.dg/analyzer/analyzer-decls.h" + +extern char *strstr (const char* str, const char* substr); + +char * +test_passthrough (const char* str, const char* substr) +{ + return strstr (str, substr); +} + +char * +test_NULL_str (const char *substr) +{ + return strstr (NULL, substr); /* { dg-warning "use of NULL where non-null expected" } */ +} + +char * +test_unterminated_str (const char *substr) +{ + char str[3] = "abc"; /* { dg-warning "initializer-string for '\[^\n\]*' is too long" "" { target c++ } } */ + return strstr (str, substr); /* { dg-warning "stack-based buffer over-read" } */ + /* { dg-message "while looking for null terminator for argument 1" "note" { target *-*-* } .-1 } */ +} + +char * +test_uninitialized_str (const char *substr) +{ + char str[16]; + return strstr (str, substr); /* { dg-warning "use of uninitialized value 'str\\\[0\\\]'" } */ +} + +char * +test_NULL_substr (const char *str) +{ + return strstr (str, NULL); /* { dg-warning "use of NULL where non-null expected" } */ +} + +char * +test_unterminated_substr (const char *str) +{ + char substr[3] = "abc"; /* { dg-warning "initializer-string for '\[^\n\]*' is too long" "" { target c++ } } */ + return strstr (str, substr); /* { dg-warning "stack-based buffer over-read" } */ + /* { dg-message "while looking for null terminator for argument 2" "note" { target *-*-* } .-1 } */ +} + +char *test_uninitialized_substr (const char *str) +{ + char substr[16]; + return strstr (str, substr); /* { dg-warning "use of uninitialized value 'substr\\\[0\\\]'" } */ +} -- 2.11.4.GIT