| /* Handling for the known behavior of various specific functions. |
| Copyright (C) 2020-2025 Free Software Foundation, Inc. |
| Contributed by David Malcolm <dmalcolm@redhat.com>. |
| |
| This file is part of GCC. |
| |
| GCC is free software; you can redistribute it and/or modify it |
| under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 3, or (at your option) |
| any later version. |
| |
| GCC is distributed in the hope that it will be useful, but |
| WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with GCC; see the file COPYING3. If not see |
| <http://www.gnu.org/licenses/>. */ |
| |
| #include "analyzer/common.h" |
| |
| #include "diagnostic.h" |
| |
| #include "analyzer/analyzer-logging.h" |
| #include "analyzer/region-model.h" |
| #include "analyzer/call-details.h" |
| #include "analyzer/call-info.h" |
| |
| #if ENABLE_ANALYZER |
| |
| namespace ana { |
| |
| /* Abstract subclass for describing undefined behavior of an API. */ |
| |
| class undefined_function_behavior |
| : public pending_diagnostic_subclass<undefined_function_behavior> |
| { |
| public: |
| undefined_function_behavior (const call_details &cd) |
| : m_call_stmt (cd.get_call_stmt ()), |
| m_callee_fndecl (cd.get_fndecl_for_call ()) |
| { |
| gcc_assert (m_callee_fndecl); |
| } |
| |
| const char *get_kind () const final override |
| { |
| return "undefined_behavior"; |
| } |
| |
| bool operator== (const undefined_function_behavior &other) const |
| { |
| return (&m_call_stmt == &other.m_call_stmt |
| && m_callee_fndecl == other.m_callee_fndecl); |
| } |
| |
| bool terminate_path_p () const final override { return true; } |
| |
| tree get_callee_fndecl () const { return m_callee_fndecl; } |
| |
| private: |
| const gimple &m_call_stmt; |
| tree m_callee_fndecl; |
| }; |
| |
| /* class pure_known_function_with_default_return : public known_function. */ |
| |
| void |
| pure_known_function_with_default_return:: |
| impl_call_pre (const call_details &cd) const |
| { |
| cd.set_any_lhs_with_defaults (); |
| } |
| |
| /* Implementations of specific functions. */ |
| |
| /* Handler for "alloca". */ |
| |
| class kf_alloca : public builtin_known_function |
| { |
| public: |
| bool matches_call_types_p (const call_details &cd) const final override |
| { |
| return cd.num_args () == 1; |
| } |
| enum built_in_function builtin_code () const final override |
| { |
| return BUILT_IN_ALLOCA; |
| } |
| void impl_call_pre (const call_details &cd) const final override; |
| }; |
| |
| void |
| kf_alloca::impl_call_pre (const call_details &cd) const |
| { |
| const svalue *size_sval = cd.get_arg_svalue (0); |
| |
| region_model *model = cd.get_model (); |
| region_model_manager *mgr = cd.get_manager (); |
| |
| const region *new_reg |
| = model->create_region_for_alloca (size_sval, cd.get_ctxt ()); |
| const svalue *ptr_sval |
| = mgr->get_ptr_svalue (cd.get_lhs_type (), new_reg); |
| cd.maybe_set_lhs (ptr_sval); |
| } |
| |
| /* Handler for __atomic_exchange. |
| Although the user-facing documentation specifies it as having this |
| signature: |
| void __atomic_exchange (type *ptr, type *val, type *ret, int memorder) |
| |
| by the time the C/C++ frontends have acted on it, any calls that |
| can't be mapped to a _N variation end up with this signature: |
| |
| void |
| __atomic_exchange (size_t sz, void *ptr, void *val, void *ret, |
| int memorder) |
| |
| as seen in the gimple seen by the analyzer, and as specified |
| in sync-builtins.def. */ |
| |
| class kf_atomic_exchange : public internal_known_function |
| { |
| public: |
| /* This is effectively: |
| tmpA = *PTR; |
| tmpB = *VAL; |
| *PTR = tmpB; |
| *RET = tmpA; |
| */ |
| void impl_call_pre (const call_details &cd) const final override |
| { |
| const svalue *num_bytes_sval = cd.get_arg_svalue (0); |
| const svalue *ptr_sval = cd.get_arg_svalue (1); |
| tree ptr_tree = cd.get_arg_tree (1); |
| const svalue *val_sval = cd.get_arg_svalue (2); |
| tree val_tree = cd.get_arg_tree (2); |
| const svalue *ret_sval = cd.get_arg_svalue (3); |
| tree ret_tree = cd.get_arg_tree (3); |
| /* Ignore the memorder param. */ |
| |
| region_model *model = cd.get_model (); |
| region_model_context *ctxt = cd.get_ctxt (); |
| |
| const region *ptr_reg = model->deref_rvalue (ptr_sval, ptr_tree, ctxt); |
| const region *val_reg = model->deref_rvalue (val_sval, val_tree, ctxt); |
| const region *ret_reg = model->deref_rvalue (ret_sval, ret_tree, ctxt); |
| |
| const svalue *tmp_a_sval |
| = model->read_bytes (ptr_reg, ptr_tree, num_bytes_sval, ctxt); |
| const svalue *tmp_b_sval |
| = model->read_bytes (val_reg, val_tree, num_bytes_sval, ctxt); |
| model->write_bytes (ptr_reg, num_bytes_sval, tmp_b_sval, ctxt); |
| model->write_bytes (ret_reg, num_bytes_sval, tmp_a_sval, ctxt); |
| } |
| }; |
| |
| /* Handler for: |
| __atomic_exchange_n (type *ptr, type val, int memorder). */ |
| |
| class kf_atomic_exchange_n : public internal_known_function |
| { |
| public: |
| /* This is effectively: |
| RET = *PTR; |
| *PTR = VAL; |
| return RET; |
| */ |
| void impl_call_pre (const call_details &cd) const final override |
| { |
| const svalue *ptr_sval = cd.get_arg_svalue (0); |
| tree ptr_tree = cd.get_arg_tree (0); |
| const svalue *set_sval = cd.get_arg_svalue (1); |
| /* Ignore the memorder param. */ |
| |
| region_model *model = cd.get_model (); |
| region_model_context *ctxt = cd.get_ctxt (); |
| |
| const region *dst_region = model->deref_rvalue (ptr_sval, ptr_tree, ctxt); |
| const svalue *ret_sval = model->get_store_value (dst_region, ctxt); |
| model->set_value (dst_region, set_sval, ctxt); |
| cd.maybe_set_lhs (ret_sval); |
| } |
| }; |
| |
| /* Handler for: |
| type __atomic_fetch_add (type *ptr, type val, int memorder); |
| type __atomic_fetch_sub (type *ptr, type val, int memorder); |
| type __atomic_fetch_and (type *ptr, type val, int memorder); |
| type __atomic_fetch_xor (type *ptr, type val, int memorder); |
| type __atomic_fetch_or (type *ptr, type val, int memorder); |
| */ |
| |
| class kf_atomic_fetch_op : public internal_known_function |
| { |
| public: |
| kf_atomic_fetch_op (enum tree_code op): m_op (op) {} |
| |
| /* This is effectively: |
| RET = *PTR; |
| *PTR = RET OP VAL; |
| return RET; |
| */ |
| void impl_call_pre (const call_details &cd) const final override |
| { |
| const svalue *ptr_sval = cd.get_arg_svalue (0); |
| tree ptr_tree = cd.get_arg_tree (0); |
| const svalue *val_sval = cd.get_arg_svalue (1); |
| /* Ignore the memorder param. */ |
| |
| region_model *model = cd.get_model (); |
| region_model_manager *mgr = cd.get_manager (); |
| region_model_context *ctxt = cd.get_ctxt (); |
| |
| const region *star_ptr_region |
| = model->deref_rvalue (ptr_sval, ptr_tree, ctxt); |
| const svalue *old_sval = model->get_store_value (star_ptr_region, ctxt); |
| const svalue *new_sval = mgr->get_or_create_binop (old_sval->get_type (), |
| m_op, |
| old_sval, val_sval); |
| model->set_value (star_ptr_region, new_sval, ctxt); |
| cd.maybe_set_lhs (old_sval); |
| } |
| |
| private: |
| enum tree_code m_op; |
| }; |
| |
| /* Handler for: |
| type __atomic_add_fetch (type *ptr, type val, int memorder); |
| type __atomic_sub_fetch (type *ptr, type val, int memorder); |
| type __atomic_and_fetch (type *ptr, type val, int memorder); |
| type __atomic_xor_fetch (type *ptr, type val, int memorder); |
| type __atomic_or_fetch (type *ptr, type val, int memorder); |
| */ |
| |
| class kf_atomic_op_fetch : public internal_known_function |
| { |
| public: |
| kf_atomic_op_fetch (enum tree_code op): m_op (op) {} |
| |
| /* This is effectively: |
| *PTR = RET OP VAL; |
| return *PTR; |
| */ |
| void impl_call_pre (const call_details &cd) const final override |
| { |
| const svalue *ptr_sval = cd.get_arg_svalue (0); |
| tree ptr_tree = cd.get_arg_tree (0); |
| const svalue *val_sval = cd.get_arg_svalue (1); |
| /* Ignore the memorder param. */ |
| |
| region_model *model = cd.get_model (); |
| region_model_manager *mgr = cd.get_manager (); |
| region_model_context *ctxt = cd.get_ctxt (); |
| |
| const region *star_ptr_region |
| = model->deref_rvalue (ptr_sval, ptr_tree, ctxt); |
| const svalue *old_sval = model->get_store_value (star_ptr_region, ctxt); |
| const svalue *new_sval = mgr->get_or_create_binop (old_sval->get_type (), |
| m_op, |
| old_sval, val_sval); |
| model->set_value (star_ptr_region, new_sval, ctxt); |
| cd.maybe_set_lhs (new_sval); |
| } |
| |
| private: |
| enum tree_code m_op; |
| }; |
| |
| /* Handler for __atomic_load. |
| Although the user-facing documentation specifies it as having this |
| signature: |
| |
| void __atomic_load (type *ptr, type *ret, int memorder) |
| |
| by the time the C/C++ frontends have acted on it, any calls that |
| can't be mapped to a _N variation end up with this signature: |
| |
| void __atomic_load (size_t sz, const void *src, void *dst, int memorder); |
| |
| as seen in the gimple seen by the analyzer, and as specified |
| in sync-builtins.def. */ |
| |
| class kf_atomic_load : public internal_known_function |
| { |
| public: |
| /* This is effectively: |
| memmove (dst, src, sz); |
| */ |
| void impl_call_pre (const call_details &cd) const final override |
| { |
| const svalue *num_bytes_sval = cd.get_arg_svalue (0); |
| const svalue *src_sval = cd.get_arg_svalue (1); |
| tree src_tree = cd.get_arg_tree (1); |
| const svalue *dst_sval = cd.get_arg_svalue (2); |
| tree dst_tree = cd.get_arg_tree (2); |
| /* Ignore the memorder param. */ |
| |
| region_model *model = cd.get_model (); |
| region_model_context *ctxt = cd.get_ctxt (); |
| |
| const region *dst_reg = model->deref_rvalue (dst_sval, dst_tree, ctxt); |
| const region *src_reg = model->deref_rvalue (src_sval, src_tree, ctxt); |
| |
| const svalue *data_sval |
| = model->read_bytes (src_reg, src_tree, num_bytes_sval, ctxt); |
| model->write_bytes (dst_reg, num_bytes_sval, data_sval, ctxt); |
| } |
| }; |
| |
| /* Handler for __atomic_store. |
| Although the user-facing documentation specifies it as having this |
| signature: |
| |
| void __atomic_store (type *ptr, type *val, int memorder) |
| |
| by the time the C/C++ frontends have acted on it, any calls that |
| can't be mapped to a _N variation end up with this signature: |
| |
| void __atomic_store (size_t sz, type *dst, type *src, int memorder) |
| |
| as seen in the gimple seen by the analyzer, and as specified |
| in sync-builtins.def. */ |
| |
| class kf_atomic_store : public internal_known_function |
| { |
| public: |
| /* This is effectively: |
| memmove (dst, src, sz); |
| */ |
| void impl_call_pre (const call_details &cd) const final override |
| { |
| const svalue *num_bytes_sval = cd.get_arg_svalue (0); |
| const svalue *dst_sval = cd.get_arg_svalue (1); |
| tree dst_tree = cd.get_arg_tree (1); |
| const svalue *src_sval = cd.get_arg_svalue (2); |
| tree src_tree = cd.get_arg_tree (2); |
| /* Ignore the memorder param. */ |
| |
| region_model *model = cd.get_model (); |
| region_model_context *ctxt = cd.get_ctxt (); |
| |
| const region *dst_reg = model->deref_rvalue (dst_sval, dst_tree, ctxt); |
| const region *src_reg = model->deref_rvalue (src_sval, src_tree, ctxt); |
| |
| const svalue *data_sval |
| = model->read_bytes (src_reg, src_tree, num_bytes_sval, ctxt); |
| model->write_bytes (dst_reg, num_bytes_sval, data_sval, ctxt); |
| } |
| }; |
| |
| /* Handler for: |
| type __atomic_load_n (type *ptr, int memorder) */ |
| |
| class kf_atomic_load_n : public internal_known_function |
| { |
| public: |
| /* This is effectively: |
| RET = *PTR; |
| return RET; |
| */ |
| void impl_call_pre (const call_details &cd) const final override |
| { |
| const svalue *ptr_ptr_sval = cd.get_arg_svalue (0); |
| tree ptr_ptr_tree = cd.get_arg_tree (0); |
| /* Ignore the memorder param. */ |
| |
| region_model *model = cd.get_model (); |
| region_model_context *ctxt = cd.get_ctxt (); |
| |
| const region *ptr_region |
| = model->deref_rvalue (ptr_ptr_sval, ptr_ptr_tree, ctxt); |
| const svalue *star_ptr_sval = model->get_store_value (ptr_region, ctxt); |
| cd.maybe_set_lhs (star_ptr_sval); |
| } |
| }; |
| |
| /* Handler for: |
| void __atomic_store_n (type *ptr, type val, int memorder) */ |
| |
| class kf_atomic_store_n : public internal_known_function |
| { |
| public: |
| /* This is effectively: |
| *PTR = VAL; |
| */ |
| void impl_call_pre (const call_details &cd) const final override |
| { |
| const svalue *ptr_sval = cd.get_arg_svalue (0); |
| tree ptr_tree = cd.get_arg_tree (0); |
| const svalue *new_sval = cd.get_arg_svalue (1); |
| /* Ignore the memorder param. */ |
| |
| region_model *model = cd.get_model (); |
| region_model_context *ctxt = cd.get_ctxt (); |
| |
| const region *star_ptr_region |
| = model->deref_rvalue (ptr_sval, ptr_tree, ctxt); |
| model->set_value (star_ptr_region, new_sval, ctxt); |
| } |
| }; |
| |
| /* Handler for "__builtin_expect" etc. */ |
| |
| class kf_expect : public internal_known_function |
| { |
| public: |
| void impl_call_pre (const call_details &cd) const final override |
| { |
| /* __builtin_expect's return value is its initial argument. */ |
| const svalue *sval = cd.get_arg_svalue (0); |
| cd.maybe_set_lhs (sval); |
| } |
| }; |
| |
| /* Handler for "calloc". */ |
| |
| class kf_calloc : 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_size_p (0) |
| && cd.arg_is_size_p (1)); |
| } |
| enum built_in_function builtin_code () const final override |
| { |
| return BUILT_IN_CALLOC; |
| } |
| |
| void impl_call_pre (const call_details &cd) const final override; |
| }; |
| |
| void |
| kf_calloc::impl_call_pre (const call_details &cd) const |
| { |
| region_model *model = cd.get_model (); |
| region_model_manager *mgr = cd.get_manager (); |
| const svalue *nmemb_sval = cd.get_arg_svalue (0); |
| const svalue *size_sval = cd.get_arg_svalue (1); |
| /* TODO: check for overflow here? */ |
| const svalue *prod_sval |
| = mgr->get_or_create_binop (size_type_node, MULT_EXPR, |
| nmemb_sval, size_sval); |
| const region *new_reg |
| = model->get_or_create_region_for_heap_alloc (prod_sval, cd.get_ctxt ()); |
| const region *sized_reg |
| = mgr->get_sized_region (new_reg, NULL_TREE, prod_sval); |
| model->zero_fill_region (sized_reg, cd.get_ctxt ()); |
| if (cd.get_lhs_type ()) |
| { |
| const svalue *ptr_sval |
| = mgr->get_ptr_svalue (cd.get_lhs_type (), new_reg); |
| cd.maybe_set_lhs (ptr_sval); |
| } |
| } |
| |
| /* Handler for glibc's "__errno_location". */ |
| |
| class kf_errno_location : public known_function |
| { |
| public: |
| bool matches_call_types_p (const call_details &cd) const final override |
| { |
| return cd.num_args () == 0; |
| } |
| |
| void impl_call_pre (const call_details &cd) const final override |
| { |
| if (cd.get_lhs_region ()) |
| { |
| region_model_manager *mgr = cd.get_manager (); |
| const region *errno_reg = mgr->get_errno_region (); |
| const svalue *errno_ptr = mgr->get_ptr_svalue (cd.get_lhs_type (), |
| errno_reg); |
| cd.maybe_set_lhs (errno_ptr); |
| } |
| } |
| }; |
| |
| /* Handler for "error" and "error_at_line" from GNU's non-standard <error.h>. |
| MIN_ARGS identifies the minimum number of expected arguments |
| to be consistent with such a call (3 and 5 respectively). */ |
| |
| class kf_error : public known_function |
| { |
| public: |
| kf_error (unsigned min_args) : m_min_args (min_args) {} |
| |
| bool matches_call_types_p (const call_details &cd) const final override |
| { |
| return (cd.num_args () >= m_min_args |
| && cd.get_arg_type (0) == integer_type_node); |
| } |
| |
| void impl_call_pre (const call_details &cd) const final override; |
| |
| private: |
| unsigned m_min_args; |
| }; |
| |
| void |
| kf_error::impl_call_pre (const call_details &cd) const |
| { |
| /* The process exits if status != 0, so it only continues |
| for the case where status == 0. |
| Add that constraint, or terminate this analysis path. */ |
| tree status = cd.get_arg_tree (0); |
| region_model_context *ctxt = cd.get_ctxt (); |
| region_model *model = cd.get_model (); |
| if (!model->add_constraint (status, EQ_EXPR, integer_zero_node, ctxt)) |
| if (ctxt) |
| ctxt->terminate_path (); |
| |
| /* Check "format" arg. */ |
| const int fmt_arg_idx = (m_min_args == 3) ? 2 : 4; |
| model->check_for_null_terminated_string_arg (cd, fmt_arg_idx); |
| } |
| |
| /* Handler for fopen. |
| FILE *fopen (const char *filename, const char *mode); |
| See e.g. https://en.cppreference.com/w/c/io/fopen |
| https://www.man7.org/linux/man-pages/man3/fopen.3.html |
| https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/fopen-wfopen?view=msvc-170 */ |
| |
| class kf_fopen : public 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)); |
| } |
| |
| 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); |
| cd.set_any_lhs_with_defaults (); |
| |
| /* fopen's mode param is effectively a mini-DSL, but there are various |
| non-standard extensions, so we don't bother to check it. */ |
| } |
| }; |
| |
| /* Handler for "free", after sm-handling. |
| |
| If the ptr points to an underlying heap region, delete the region, |
| poisoning pointers to it and regions within it. |
| |
| We delay this until after sm-state has been updated so that the |
| sm-handling can transition all of the various casts of the pointer |
| to a "freed" state *before* we delete the related region here. |
| |
| This has to be done here so that the sm-handling can use the fact |
| that they point to the same region to establish that they are equal |
| (in region_model::eval_condition), and thus transition |
| all pointers to the region to the "freed" state together, regardless |
| of casts. */ |
| |
| class kf_free : public builtin_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)); |
| } |
| enum built_in_function builtin_code () const final override |
| { |
| return BUILT_IN_FREE; |
| } |
| void impl_call_post (const call_details &cd) const final override; |
| }; |
| |
| void |
| kf_free::impl_call_post (const call_details &cd) const |
| { |
| const svalue *ptr_sval = cd.get_arg_svalue (0); |
| if (const region *freed_reg = ptr_sval->maybe_get_region ()) |
| { |
| /* If the ptr points to an underlying heap region, delete it, |
| poisoning pointers. */ |
| region_model *model = cd.get_model (); |
| model->unbind_region_and_descendents (freed_reg, poison_kind::freed); |
| model->unset_dynamic_extents (freed_reg); |
| } |
| } |
| |
| /* Handle the on_call_pre part of "malloc". */ |
| |
| class kf_malloc : public builtin_known_function |
| { |
| public: |
| bool matches_call_types_p (const call_details &cd) const final override |
| { |
| return (cd.num_args () == 1 |
| && cd.arg_is_size_p (0)); |
| } |
| enum built_in_function builtin_code () const final override |
| { |
| return BUILT_IN_MALLOC; |
| } |
| void impl_call_pre (const call_details &cd) const final override; |
| }; |
| |
| void |
| kf_malloc::impl_call_pre (const call_details &cd) const |
| { |
| region_model *model = cd.get_model (); |
| region_model_manager *mgr = cd.get_manager (); |
| const svalue *size_sval = cd.get_arg_svalue (0); |
| const region *new_reg |
| = model->get_or_create_region_for_heap_alloc (size_sval, cd.get_ctxt ()); |
| if (cd.get_lhs_type ()) |
| { |
| const svalue *ptr_sval |
| = mgr->get_ptr_svalue (cd.get_lhs_type (), new_reg); |
| cd.maybe_set_lhs (ptr_sval); |
| } |
| } |
| |
| /* Handler for "memcpy" and "__builtin_memcpy", |
| "memmove", and "__builtin_memmove". */ |
| |
| class kf_memcpy_memmove : public builtin_known_function |
| { |
| public: |
| enum kf_memcpy_memmove_variant |
| { |
| KF_MEMCPY, |
| KF_MEMCPY_CHK, |
| KF_MEMMOVE, |
| KF_MEMMOVE_CHK, |
| }; |
| kf_memcpy_memmove (enum kf_memcpy_memmove_variant variant) |
| : m_variant (variant) {}; |
| 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_pointer_p (1) |
| && cd.arg_is_size_p (2)); |
| } |
| enum built_in_function builtin_code () const final override |
| { |
| switch (m_variant) |
| { |
| case KF_MEMCPY: |
| return BUILT_IN_MEMCPY; |
| case KF_MEMCPY_CHK: |
| return BUILT_IN_MEMCPY_CHK; |
| case KF_MEMMOVE: |
| return BUILT_IN_MEMMOVE; |
| case KF_MEMMOVE_CHK: |
| return BUILT_IN_MEMMOVE_CHK; |
| default: |
| gcc_unreachable (); |
| } |
| } |
| void impl_call_pre (const call_details &cd) const final override; |
| private: |
| const enum kf_memcpy_memmove_variant m_variant; |
| }; |
| |
| void |
| kf_memcpy_memmove::impl_call_pre (const call_details &cd) const |
| { |
| const svalue *dest_ptr_sval = cd.get_arg_svalue (0); |
| const svalue *src_ptr_sval = cd.get_arg_svalue (1); |
| const svalue *num_bytes_sval = cd.get_arg_svalue (2); |
| |
| region_model *model = cd.get_model (); |
| |
| const region *dest_reg |
| = model->deref_rvalue (dest_ptr_sval, cd.get_arg_tree (0), cd.get_ctxt ()); |
| const region *src_reg |
| = model->deref_rvalue (src_ptr_sval, cd.get_arg_tree (1), cd.get_ctxt ()); |
| |
| cd.maybe_set_lhs (dest_ptr_sval); |
| /* Check for overlap. */ |
| switch (m_variant) |
| { |
| case KF_MEMCPY: |
| case KF_MEMCPY_CHK: |
| cd.complain_about_overlap (0, 1, num_bytes_sval); |
| break; |
| |
| case KF_MEMMOVE: |
| case KF_MEMMOVE_CHK: |
| /* It's OK for memmove's arguments to overlap. */ |
| break; |
| |
| default: |
| gcc_unreachable (); |
| } |
| model->copy_bytes (dest_reg, |
| src_reg, cd.get_arg_tree (1), |
| num_bytes_sval, |
| cd.get_ctxt ()); |
| } |
| |
| /* Handler for "memset" and "__builtin_memset". */ |
| |
| class kf_memset : public builtin_known_function |
| { |
| public: |
| kf_memset (bool chk_variant) : m_chk_variant (chk_variant) {} |
| bool matches_call_types_p (const call_details &cd) const final override |
| { |
| return (cd.num_args () == 3 && cd.arg_is_pointer_p (0)); |
| } |
| enum built_in_function builtin_code () const final override |
| { |
| return m_chk_variant ? BUILT_IN_MEMSET_CHK : BUILT_IN_MEMSET; |
| } |
| void impl_call_pre (const call_details &cd) const final override; |
| private: |
| const bool m_chk_variant; |
| }; |
| |
| void |
| kf_memset::impl_call_pre (const call_details &cd) const |
| { |
| const svalue *dest_sval = cd.get_arg_svalue (0); |
| const svalue *fill_value_sval = cd.get_arg_svalue (1); |
| const svalue *num_bytes_sval = cd.get_arg_svalue (2); |
| |
| region_model *model = cd.get_model (); |
| region_model_manager *mgr = cd.get_manager (); |
| |
| const region *dest_reg |
| = model->deref_rvalue (dest_sval, cd.get_arg_tree (0), cd.get_ctxt ()); |
| |
| const svalue *fill_value_u8 |
| = mgr->get_or_create_cast (unsigned_char_type_node, fill_value_sval); |
| |
| const region *sized_dest_reg = mgr->get_sized_region (dest_reg, |
| NULL_TREE, |
| num_bytes_sval); |
| model->fill_region (sized_dest_reg, fill_value_u8, cd.get_ctxt ()); |
| |
| cd.maybe_set_lhs (dest_sval); |
| } |
| |
| /* A subclass of pending_diagnostic for complaining about 'putenv' |
| called on an auto var. */ |
| |
| class putenv_of_auto_var |
| : public pending_diagnostic_subclass<putenv_of_auto_var> |
| { |
| public: |
| putenv_of_auto_var (tree fndecl, const region *reg) |
| : m_fndecl (fndecl), m_reg (reg), |
| m_var_decl (reg->get_base_region ()->maybe_get_decl ()) |
| { |
| } |
| |
| const char *get_kind () const final override |
| { |
| return "putenv_of_auto_var"; |
| } |
| |
| bool operator== (const putenv_of_auto_var &other) const |
| { |
| return (m_fndecl == other.m_fndecl |
| && m_reg == other.m_reg |
| && same_tree_p (m_var_decl, other.m_var_decl)); |
| } |
| |
| int get_controlling_option () const final override |
| { |
| return OPT_Wanalyzer_putenv_of_auto_var; |
| } |
| |
| bool emit (diagnostic_emission_context &ctxt) final override |
| { |
| auto_diagnostic_group d; |
| |
| /* SEI CERT C Coding Standard: "POS34-C. Do not call putenv() with a |
| pointer to an automatic variable as the argument". */ |
| diagnostics::metadata::precanned_rule |
| rule ("POS34-C", "https://wiki.sei.cmu.edu/confluence/x/6NYxBQ"); |
| ctxt.add_rule (rule); |
| |
| bool warned; |
| if (m_var_decl) |
| warned = ctxt.warn ("%qE on a pointer to automatic variable %qE", |
| m_fndecl, m_var_decl); |
| else |
| warned = ctxt.warn ("%qE on a pointer to an on-stack buffer", |
| m_fndecl); |
| if (warned) |
| { |
| if (m_var_decl) |
| inform (DECL_SOURCE_LOCATION (m_var_decl), |
| "%qE declared on stack here", m_var_decl); |
| inform (ctxt.get_location (), "perhaps use %qs rather than %qE", |
| "setenv", m_fndecl); |
| } |
| |
| return warned; |
| } |
| |
| bool |
| describe_final_event (pretty_printer &pp, |
| const evdesc::final_event &) final override |
| { |
| if (m_var_decl) |
| pp_printf (&pp, |
| "%qE on a pointer to automatic variable %qE", |
| m_fndecl, m_var_decl); |
| else |
| pp_printf (&pp, |
| "%qE on a pointer to an on-stack buffer", |
| m_fndecl); |
| return true; |
| } |
| |
| void mark_interesting_stuff (interesting_t *interest) final override |
| { |
| if (!m_var_decl) |
| interest->add_region_creation (m_reg->get_base_region ()); |
| } |
| |
| private: |
| tree m_fndecl; // non-NULL |
| const region *m_reg; // non-NULL |
| tree m_var_decl; // could be NULL |
| }; |
| |
| /* Handler for calls to "putenv". |
| |
| In theory we could try to model the state of the environment variables |
| for the process; for now we merely complain about putenv of regions |
| on the stack. */ |
| |
| class kf_putenv : 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 |
| { |
| tree fndecl = cd.get_fndecl_for_call (); |
| gcc_assert (fndecl); |
| region_model_context *ctxt = cd.get_ctxt (); |
| region_model *model = cd.get_model (); |
| model->check_for_null_terminated_string_arg (cd, 0); |
| const svalue *ptr_sval = cd.get_arg_svalue (0); |
| const region *reg |
| = model->deref_rvalue (ptr_sval, cd.get_arg_tree (0), ctxt); |
| model->get_store ()->mark_as_escaped (reg); |
| enum memory_space mem_space = reg->get_memory_space (); |
| switch (mem_space) |
| { |
| default: |
| gcc_unreachable (); |
| case MEMSPACE_UNKNOWN: |
| case MEMSPACE_CODE: |
| case MEMSPACE_GLOBALS: |
| case MEMSPACE_HEAP: |
| case MEMSPACE_READONLY_DATA: |
| break; |
| case MEMSPACE_STACK: |
| if (ctxt) |
| ctxt->warn (std::make_unique<putenv_of_auto_var> (fndecl, reg)); |
| break; |
| } |
| cd.set_any_lhs_with_defaults (); |
| } |
| }; |
| |
| /* Handler for "realloc": |
| |
| void *realloc(void *ptr, size_t size); |
| |
| realloc(3) is awkward, since it has various different outcomes |
| that are best modelled as separate exploded nodes/edges. |
| |
| We first check for sm-state, in |
| malloc_state_machine::on_realloc_call, so that we |
| can complain about issues such as realloc of a non-heap |
| pointer, and terminate the path for such cases (and issue |
| the complaints at the call's exploded node). |
| |
| Assuming that these checks pass, we split the path here into |
| three special cases (and terminate the "standard" path): |
| (A) failure, returning NULL |
| (B) success, growing the buffer in-place without moving it |
| (C) success, allocating a new buffer, copying the content |
| of the old buffer to it, and freeing the old buffer. |
| |
| Each of these has a custom_edge_info subclass, which updates |
| the region_model and sm-state of the destination state. */ |
| |
| class kf_realloc : 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_size_p (1)); |
| } |
| |
| enum built_in_function builtin_code () const final override |
| { |
| return BUILT_IN_REALLOC; |
| } |
| |
| void impl_call_post (const call_details &cd) const final override; |
| }; |
| |
| void |
| kf_realloc::impl_call_post (const call_details &cd) const |
| { |
| /* Three custom subclasses of custom_edge_info, for handling the various |
| outcomes of "realloc". */ |
| |
| /* Concrete custom_edge_info: a realloc call that fails, returning NULL. */ |
| class failure : public failed_call_info |
| { |
| public: |
| failure (const call_details &cd) |
| : failed_call_info (cd) |
| { |
| } |
| |
| bool update_model (region_model *model, |
| const exploded_edge *, |
| region_model_context *ctxt) const final override |
| { |
| /* Return NULL; everything else is unchanged. */ |
| const call_details cd (get_call_details (model, ctxt)); |
| region_model_manager *mgr = cd.get_manager (); |
| if (cd.get_lhs_type ()) |
| { |
| const svalue *zero |
| = mgr->get_or_create_int_cst (cd.get_lhs_type (), 0); |
| model->set_value (cd.get_lhs_region (), |
| zero, |
| cd.get_ctxt ()); |
| } |
| return true; |
| } |
| }; |
| |
| /* Concrete custom_edge_info: a realloc call that succeeds, growing |
| the existing buffer without moving it. */ |
| class success_no_move : public call_info |
| { |
| public: |
| success_no_move (const call_details &cd) |
| : call_info (cd) |
| { |
| } |
| |
| void print_desc (pretty_printer &pp) const final override |
| { |
| pp_printf (&pp, |
| "when %qE succeeds, without moving buffer", |
| get_fndecl ()); |
| } |
| |
| bool update_model (region_model *model, |
| const exploded_edge *, |
| region_model_context *ctxt) const final override |
| { |
| /* Update size of buffer and return the ptr unchanged. */ |
| const call_details cd (get_call_details (model, ctxt)); |
| region_model_manager *mgr = cd.get_manager (); |
| const svalue *ptr_sval = cd.get_arg_svalue (0); |
| const svalue *size_sval = cd.get_arg_svalue (1); |
| |
| /* We can only grow in place with a non-NULL pointer. */ |
| { |
| const svalue *null_ptr |
| = mgr->get_or_create_int_cst (ptr_sval->get_type (), 0); |
| if (!model->add_constraint (ptr_sval, NE_EXPR, null_ptr, |
| cd.get_ctxt ())) |
| return false; |
| } |
| |
| if (const region *buffer_reg = model->deref_rvalue (ptr_sval, NULL_TREE, |
| ctxt)) |
| if (compat_types_p (size_sval->get_type (), size_type_node)) |
| model->set_dynamic_extents (buffer_reg, size_sval, ctxt); |
| if (cd.get_lhs_region ()) |
| { |
| model->set_value (cd.get_lhs_region (), ptr_sval, cd.get_ctxt ()); |
| const svalue *zero |
| = mgr->get_or_create_int_cst (cd.get_lhs_type (), 0); |
| return model->add_constraint (ptr_sval, NE_EXPR, zero, ctxt); |
| } |
| else |
| return true; |
| } |
| }; |
| |
| /* Concrete custom_edge_info: a realloc call that succeeds, freeing |
| the existing buffer and moving the content to a freshly allocated |
| buffer. */ |
| class success_with_move : public call_info |
| { |
| public: |
| success_with_move (const call_details &cd) |
| : call_info (cd) |
| { |
| } |
| |
| void print_desc (pretty_printer &pp) const final override |
| { |
| pp_printf (&pp, |
| "when %qE succeeds, moving buffer", |
| 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)); |
| region_model_manager *mgr = cd.get_manager (); |
| const svalue *old_ptr_sval = cd.get_arg_svalue (0); |
| const svalue *new_size_sval = cd.get_arg_svalue (1); |
| |
| /* Create the new region. */ |
| const region *new_reg |
| = model->get_or_create_region_for_heap_alloc (new_size_sval, ctxt); |
| const svalue *new_ptr_sval |
| = mgr->get_ptr_svalue (cd.get_lhs_type (), new_reg); |
| if (!model->add_constraint (new_ptr_sval, NE_EXPR, old_ptr_sval, |
| cd.get_ctxt ())) |
| return false; |
| |
| if (cd.get_lhs_type ()) |
| cd.maybe_set_lhs (new_ptr_sval); |
| |
| if (const region *freed_reg = model->deref_rvalue (old_ptr_sval, |
| NULL_TREE, ctxt)) |
| { |
| /* Copy the data. */ |
| const svalue *old_size_sval = model->get_dynamic_extents (freed_reg); |
| if (old_size_sval) |
| { |
| const svalue *copied_size_sval |
| = get_copied_size (model, old_size_sval, new_size_sval); |
| const region *copied_old_reg |
| = mgr->get_sized_region (freed_reg, nullptr, copied_size_sval); |
| const svalue *buffer_content_sval |
| = model->get_store_value (copied_old_reg, cd.get_ctxt ()); |
| const region *copied_new_reg |
| = mgr->get_sized_region (new_reg, nullptr, copied_size_sval); |
| model->set_value (copied_new_reg, buffer_content_sval, |
| cd.get_ctxt ()); |
| } |
| else |
| { |
| /* We don't know how big the old region was; |
| mark the new region as having been touched to avoid uninit |
| issues. */ |
| model->mark_region_as_unknown (new_reg, cd.get_uncertainty ()); |
| } |
| |
| /* Free the old region, so that pointers to the old buffer become |
| invalid. */ |
| |
| /* If the ptr points to an underlying heap region, delete it, |
| poisoning pointers. */ |
| model->unbind_region_and_descendents (freed_reg, poison_kind::freed); |
| model->unset_dynamic_extents (freed_reg); |
| } |
| |
| /* Update the sm-state: mark the old_ptr_sval as "freed", |
| and the new_ptr_sval as "nonnull". */ |
| model->on_realloc_with_move (cd, old_ptr_sval, new_ptr_sval); |
| |
| if (cd.get_lhs_type ()) |
| { |
| const svalue *zero |
| = mgr->get_or_create_int_cst (cd.get_lhs_type (), 0); |
| return model->add_constraint (new_ptr_sval, NE_EXPR, zero, |
| cd.get_ctxt ()); |
| } |
| else |
| return true; |
| } |
| |
| private: |
| /* Return the lesser of OLD_SIZE_SVAL and NEW_SIZE_SVAL. |
| If unknown, OLD_SIZE_SVAL is returned. */ |
| const svalue *get_copied_size (region_model *model, |
| const svalue *old_size_sval, |
| const svalue *new_size_sval) const |
| { |
| tristate res |
| = model->eval_condition (old_size_sval, GT_EXPR, new_size_sval); |
| switch (res.get_value ()) |
| { |
| case tristate::TS_TRUE: |
| return new_size_sval; |
| case tristate::TS_FALSE: |
| case tristate::TS_UNKNOWN: |
| return old_size_sval; |
| default: |
| gcc_unreachable (); |
| } |
| } |
| }; |
| |
| /* Body of kf_realloc::impl_call_post. */ |
| |
| if (cd.get_ctxt ()) |
| { |
| cd.get_ctxt ()->bifurcate (std::make_unique<failure> (cd)); |
| cd.get_ctxt ()->bifurcate (std::make_unique<success_no_move> (cd)); |
| cd.get_ctxt ()->bifurcate (std::make_unique<success_with_move> (cd)); |
| cd.get_ctxt ()->terminate_path (); |
| } |
| } |
| |
| /* Handler for "strchr" and "__builtin_strchr". */ |
| |
| class kf_strchr : 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)); |
| } |
| void impl_call_pre (const call_details &cd) const final override |
| { |
| cd.check_for_null_terminated_string_arg (0); |
| } |
| |
| enum built_in_function builtin_code () const final override |
| { |
| return BUILT_IN_STRCHR; |
| } |
| void impl_call_post (const call_details &cd) const final override; |
| }; |
| |
| void |
| kf_strchr::impl_call_post (const call_details &cd) const |
| { |
| class strchr_call_info : public call_info |
| { |
| public: |
| strchr_call_info (const call_details &cd, bool found) |
| : call_info (cd), m_found (found) |
| { |
| } |
| |
| void print_desc (pretty_printer &pp) const final override |
| { |
| if (m_found) |
| pp_printf (&pp, |
| "when %qE returns non-NULL", |
| get_fndecl ()); |
| else |
| pp_printf (&pp, |
| "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_strchr::impl_call_post. */ |
| if (cd.get_ctxt ()) |
| { |
| cd.get_ctxt ()->bifurcate (std::make_unique<strchr_call_info> (cd, false)); |
| cd.get_ctxt ()->bifurcate (std::make_unique<strchr_call_info> (cd, true)); |
| cd.get_ctxt ()->terminate_path (); |
| } |
| } |
| |
| /* Handler for "sprintf". |
| int sprintf(char *str, const char *format, ...); |
| */ |
| |
| class kf_sprintf : 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_SPRINTF; |
| } |
| |
| void impl_call_pre (const call_details &cd) const final override |
| { |
| /* For now, merely assume that the destination buffer gets set to a |
| new svalue. */ |
| region_model *model = cd.get_model (); |
| region_model_context *ctxt = cd.get_ctxt (); |
| const svalue *dst_ptr = cd.get_arg_svalue (0); |
| const region *dst_reg |
| = model->deref_rvalue (dst_ptr, cd.get_arg_tree (0), ctxt); |
| const svalue *content = cd.get_or_create_conjured_svalue (dst_reg); |
| model->set_value (dst_reg, content, ctxt); |
| cd.set_any_lhs_with_defaults (); |
| } |
| }; |
| |
| /* Handler for "__builtin_stack_restore". */ |
| |
| class kf_stack_restore : public pure_known_function_with_default_return |
| { |
| public: |
| bool matches_call_types_p (const call_details &) const final override |
| { |
| return true; |
| } |
| |
| /* Currently a no-op. */ |
| }; |
| |
| /* Handler for "__builtin_stack_save". */ |
| |
| class kf_stack_save : public pure_known_function_with_default_return |
| { |
| public: |
| bool matches_call_types_p (const call_details &) const final override |
| { |
| return true; |
| } |
| |
| /* Currently a no-op. */ |
| }; |
| |
| /* Handler for "__builtin_eh_pointer". */ |
| |
| class kf_eh_pointer : public builtin_known_function |
| { |
| public: |
| bool matches_call_types_p (const call_details &) const final override |
| { |
| return true; |
| } |
| |
| enum built_in_function builtin_code () const final override |
| { |
| return BUILT_IN_EH_POINTER; |
| } |
| |
| void impl_call_pre (const call_details &cd) const final override |
| { |
| cd.set_any_lhs_with_defaults (); |
| } |
| }; |
| |
| /* Handler for "strcat" and "__builtin_strcat_chk". */ |
| |
| class kf_strcat : public builtin_known_function |
| { |
| public: |
| kf_strcat (unsigned int num_args, bool chk_variant) |
| : m_num_args (num_args), |
| m_chk_variant (chk_variant) {} |
| bool matches_call_types_p (const call_details &cd) const final override |
| { |
| return (cd.num_args () == m_num_args |
| && cd.arg_is_pointer_p (0) |
| && cd.arg_is_pointer_p (1)); |
| } |
| |
| enum built_in_function builtin_code () const final override |
| { |
| return m_chk_variant ? BUILT_IN_STRCAT_CHK : BUILT_IN_STRCAT; |
| } |
| |
| void impl_call_pre (const call_details &cd) const final override |
| { |
| region_model *model = cd.get_model (); |
| region_model_manager *mgr = cd.get_manager (); |
| |
| const svalue *dest_sval = cd.get_arg_svalue (0); |
| const region *dest_reg = model->deref_rvalue (dest_sval, cd.get_arg_tree (0), |
| cd.get_ctxt ()); |
| |
| const svalue *dst_strlen_sval |
| = cd.check_for_null_terminated_string_arg (0, false, nullptr); |
| if (!dst_strlen_sval) |
| { |
| if (cd.get_ctxt ()) |
| cd.get_ctxt ()->terminate_path (); |
| return; |
| } |
| |
| const svalue *bytes_to_copy; |
| const svalue *num_src_bytes_read_sval |
| = cd.check_for_null_terminated_string_arg (1, true, &bytes_to_copy); |
| if (!num_src_bytes_read_sval) |
| { |
| if (cd.get_ctxt ()) |
| cd.get_ctxt ()->terminate_path (); |
| return; |
| } |
| |
| cd.maybe_set_lhs (dest_sval); |
| cd.complain_about_overlap (0, 1, num_src_bytes_read_sval); |
| |
| const region *offset_reg |
| = mgr->get_offset_region (dest_reg, NULL_TREE, dst_strlen_sval); |
| model->write_bytes (offset_reg, |
| num_src_bytes_read_sval, |
| bytes_to_copy, |
| cd.get_ctxt ()); |
| } |
| |
| private: |
| unsigned int m_num_args; |
| const bool m_chk_variant; |
| }; |
| |
| /* Handler for "strcpy" and "__builtin_strcpy_chk". */ |
| |
| class kf_strcpy : public builtin_known_function |
| { |
| public: |
| kf_strcpy (unsigned int num_args, bool chk_variant) |
| : m_num_args (num_args), |
| m_chk_variant (chk_variant) {} |
| bool matches_call_types_p (const call_details &cd) const final override |
| { |
| return (cd.num_args () == m_num_args |
| && cd.arg_is_pointer_p (0) |
| && cd.arg_is_pointer_p (1)); |
| } |
| enum built_in_function builtin_code () const final override |
| { |
| return m_chk_variant ? BUILT_IN_STRCPY_CHK : BUILT_IN_STRCPY; |
| } |
| void impl_call_pre (const call_details &cd) const final override; |
| |
| private: |
| unsigned int m_num_args; |
| const bool m_chk_variant; |
| }; |
| |
| void |
| kf_strcpy::impl_call_pre (const call_details &cd) const |
| { |
| region_model *model = cd.get_model (); |
| region_model_context *ctxt = cd.get_ctxt (); |
| |
| const svalue *dest_sval = cd.get_arg_svalue (0); |
| const region *dest_reg = model->deref_rvalue (dest_sval, cd.get_arg_tree (0), |
| ctxt); |
| /* strcpy returns the initial param. */ |
| cd.maybe_set_lhs (dest_sval); |
| |
| const svalue *bytes_to_copy; |
| if (const svalue *num_bytes_read_sval |
| = cd.check_for_null_terminated_string_arg (1, true, &bytes_to_copy)) |
| { |
| cd.complain_about_overlap (0, 1, num_bytes_read_sval); |
| model->write_bytes (dest_reg, num_bytes_read_sval, bytes_to_copy, ctxt); |
| } |
| else |
| { |
| if (cd.get_ctxt ()) |
| cd.get_ctxt ()->terminate_path (); |
| } |
| } |
| |
| /* Handler for "strdup" and "__builtin_strdup". */ |
| |
| class kf_strdup : public builtin_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)); |
| } |
| enum built_in_function builtin_code () const final override |
| { |
| return BUILT_IN_STRDUP; |
| } |
| void impl_call_pre (const call_details &cd) const final override |
| { |
| region_model *model = cd.get_model (); |
| region_model_context *ctxt = cd.get_ctxt (); |
| region_model_manager *mgr = cd.get_manager (); |
| const svalue *bytes_to_copy; |
| if (const svalue *num_bytes_read_sval |
| = cd.check_for_null_terminated_string_arg (0, true, &bytes_to_copy)) |
| { |
| const region *new_reg |
| = model->get_or_create_region_for_heap_alloc (num_bytes_read_sval, |
| ctxt); |
| model->write_bytes (new_reg, num_bytes_read_sval, bytes_to_copy, ctxt); |
| if (cd.get_lhs_type ()) |
| { |
| const svalue *ptr_sval |
| = mgr->get_ptr_svalue (cd.get_lhs_type (), new_reg); |
| cd.maybe_set_lhs (ptr_sval); |
| } |
| } |
| else |
| { |
| if (ctxt) |
| ctxt->terminate_path (); |
| } |
| } |
| }; |
| |
| /* Handler for "strlen" and for "__analyzer_get_strlen". */ |
| |
| class kf_strlen : public builtin_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)); |
| } |
| enum built_in_function builtin_code () const final override |
| { |
| return BUILT_IN_STRLEN; |
| } |
| |
| void impl_call_pre (const call_details &cd) const final override |
| { |
| if (const svalue *strlen_sval |
| = cd.check_for_null_terminated_string_arg (0, false, nullptr)) |
| if (strlen_sval->get_kind () != SK_UNKNOWN) |
| { |
| cd.maybe_set_lhs (strlen_sval); |
| return; |
| } |
| |
| /* Use a conjured svalue. */ |
| cd.set_any_lhs_with_defaults (); |
| } |
| }; |
| |
| /* Factory function, so that kf-analyzer.cc can use this class. */ |
| |
| std::unique_ptr<known_function> |
| make_kf_strlen () |
| { |
| return std::make_unique<kf_strlen> (); |
| } |
| |
| /* Handler for "strncpy" and "__builtin_strncpy". |
| See e.g. https://en.cppreference.com/w/c/string/byte/strncpy |
| |
| extern char *strncpy (char *dst, const char *src, size_t count); |
| |
| Handle this by splitting into two outcomes: |
| (a) truncated read from "src" of "count" bytes, |
| writing "count" bytes to "dst" |
| (b) read from "src" of up to (and including) the null terminator, |
| where the number of bytes read < "count" bytes, |
| writing those bytes to "dst", and zero-filling the rest, |
| up to "count". */ |
| |
| class kf_strncpy : public builtin_known_function |
| { |
| 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_pointer_p (1) |
| && cd.arg_is_integral_p (2)); |
| } |
| enum built_in_function builtin_code () const final override |
| { |
| return BUILT_IN_STRNCPY; |
| } |
| void impl_call_post (const call_details &cd) const final override; |
| }; |
| |
| void |
| kf_strncpy::impl_call_post (const call_details &cd) const |
| { |
| class strncpy_call_info : public call_info |
| { |
| public: |
| strncpy_call_info (const call_details &cd, |
| const svalue *num_bytes_with_terminator_sval, |
| bool truncated_read) |
| : call_info (cd), |
| m_num_bytes_with_terminator_sval (num_bytes_with_terminator_sval), |
| m_truncated_read (truncated_read) |
| { |
| } |
| |
| void print_desc (pretty_printer &pp) const final override |
| { |
| if (m_truncated_read) |
| pp_printf (&pp, |
| "when %qE truncates the source string", |
| get_fndecl ()); |
| else |
| pp_printf (&pp, |
| "when %qE copies the full source string", |
| 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)); |
| |
| const svalue *dest_sval = cd.get_arg_svalue (0); |
| const region *dest_reg |
| = model->deref_rvalue (dest_sval, cd.get_arg_tree (0), ctxt); |
| |
| const svalue *src_sval = cd.get_arg_svalue (1); |
| const region *src_reg |
| = model->deref_rvalue (src_sval, cd.get_arg_tree (1), ctxt); |
| |
| const svalue *count_sval = cd.get_arg_svalue (2); |
| |
| /* strncpy returns the initial param. */ |
| cd.maybe_set_lhs (dest_sval); |
| |
| const svalue *num_bytes_read_sval; |
| if (m_truncated_read) |
| { |
| /* Truncated read. */ |
| num_bytes_read_sval = count_sval; |
| |
| if (m_num_bytes_with_terminator_sval) |
| { |
| /* The terminator is after the limit. */ |
| if (!model->add_constraint (m_num_bytes_with_terminator_sval, |
| GT_EXPR, |
| count_sval, |
| ctxt)) |
| return false; |
| } |
| else |
| { |
| /* We don't know where the terminator is, or if there is one. |
| In theory we know that the first COUNT bytes are non-zero, |
| but we don't have a way to record that constraint. */ |
| } |
| } |
| else |
| { |
| /* Full read of the src string before reaching the limit, |
| so there must be a terminator and it must be at or before |
| the limit. */ |
| if (m_num_bytes_with_terminator_sval) |
| { |
| if (!model->add_constraint (m_num_bytes_with_terminator_sval, |
| LE_EXPR, |
| count_sval, |
| ctxt)) |
| return false; |
| num_bytes_read_sval = m_num_bytes_with_terminator_sval; |
| |
| /* First, zero-fill the dest buffer. |
| We don't need to do this for the truncation case, as |
| this fully populates the dest buffer. */ |
| const region *sized_dest_reg |
| = model->get_manager ()->get_sized_region (dest_reg, |
| NULL_TREE, |
| count_sval); |
| model->zero_fill_region (sized_dest_reg, ctxt); |
| } |
| else |
| { |
| /* Don't analyze this case; the other case will |
| assume a "truncated" read up to the limit. */ |
| return false; |
| } |
| } |
| |
| gcc_assert (num_bytes_read_sval); |
| |
| const svalue *bytes_to_copy |
| = model->read_bytes (src_reg, |
| cd.get_arg_tree (1), |
| num_bytes_read_sval, |
| ctxt); |
| cd.complain_about_overlap (0, 1, num_bytes_read_sval); |
| model->write_bytes (dest_reg, |
| num_bytes_read_sval, |
| bytes_to_copy, |
| ctxt); |
| |
| return true; |
| } |
| private: |
| /* (strlen + 1) of the source string if it has a terminator, |
| or nullptr for the case where UB would happen before |
| finding any terminator. */ |
| const svalue *m_num_bytes_with_terminator_sval; |
| |
| /* true: if this is the outcome where the limit was reached before |
| the null terminator |
| false: if the null terminator was reached before the limit. */ |
| bool m_truncated_read; |
| }; |
| |
| /* Body of kf_strncpy::impl_call_post. */ |
| if (cd.get_ctxt ()) |
| { |
| /* First, scan for a null terminator as if there were no limit, |
| with a null ctxt so no errors are reported. */ |
| const region_model *model = cd.get_model (); |
| const svalue *ptr_arg_sval = cd.get_arg_svalue (1); |
| const region *buf_reg |
| = model->deref_rvalue (ptr_arg_sval, cd.get_arg_tree (1), nullptr); |
| const svalue *num_bytes_with_terminator_sval |
| = model->scan_for_null_terminator (buf_reg, |
| cd.get_arg_tree (1), |
| nullptr, |
| nullptr); |
| cd.get_ctxt ()->bifurcate |
| (std::make_unique<strncpy_call_info> |
| (cd, num_bytes_with_terminator_sval, |
| false)); |
| cd.get_ctxt ()->bifurcate |
| (std::make_unique<strncpy_call_info> |
| (cd, num_bytes_with_terminator_sval, |
| true)); |
| cd.get_ctxt ()->terminate_path (); |
| } |
| }; |
| |
| /* Handler for "strndup" and "__builtin_strndup". */ |
| |
| class kf_strndup : 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)); |
| } |
| enum built_in_function builtin_code () const final override |
| { |
| return BUILT_IN_STRNDUP; |
| } |
| void impl_call_pre (const call_details &cd) const final override |
| { |
| region_model *model = cd.get_model (); |
| region_model_manager *mgr = cd.get_manager (); |
| /* Ideally we'd get the size here, and simulate copying the bytes. */ |
| const region *new_reg |
| = model->get_or_create_region_for_heap_alloc (nullptr, cd.get_ctxt ()); |
| model->mark_region_as_unknown (new_reg, nullptr); |
| if (cd.get_lhs_type ()) |
| { |
| const svalue *ptr_sval |
| = mgr->get_ptr_svalue (cd.get_lhs_type (), new_reg); |
| cd.maybe_set_lhs (ptr_sval); |
| } |
| } |
| }; |
| |
| /* 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) |
| { |
| } |
| |
| void print_desc (pretty_printer &pp) const final override |
| { |
| if (m_found) |
| pp_printf (&pp, |
| "when %qE returns non-NULL", |
| get_fndecl ()); |
| else |
| pp_printf (&pp, |
| "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 (std::make_unique<strstr_call_info> (cd, false)); |
| cd.get_ctxt ()->bifurcate (std::make_unique<strstr_call_info> (cd, true)); |
| cd.get_ctxt ()->terminate_path (); |
| } |
| } |
| |
| /* Handle calls to "strtok". |
| See e.g. |
| https://en.cppreference.com/w/c/string/byte/strtok |
| https://man7.org/linux/man-pages/man3/strtok.3.html */ |
| |
| class kf_strtok : public known_function |
| { |
| public: |
| class undefined_behavior : public undefined_function_behavior |
| { |
| public: |
| undefined_behavior (const call_details &cd) |
| : undefined_function_behavior (cd) |
| { |
| } |
| int get_controlling_option () const final override |
| { |
| return OPT_Wanalyzer_undefined_behavior_strtok; |
| } |
| |
| bool emit (diagnostic_emission_context &ctxt) final override |
| { |
| /* CWE-476: NULL Pointer Dereference. */ |
| ctxt.add_cwe (476); |
| if (ctxt.warn ("calling %qD for first time with NULL as argument 1" |
| " has undefined behavior", |
| get_callee_fndecl ())) |
| { |
| inform (ctxt.get_location (), |
| "some implementations of %qD may crash on such input", |
| get_callee_fndecl ()); |
| return true; |
| } |
| return false; |
| } |
| |
| bool |
| describe_final_event (pretty_printer &pp, |
| const evdesc::final_event &) final override |
| { |
| pp_printf (&pp, |
| "calling %qD for first time with NULL as argument 1" |
| " has undefined behavior", |
| get_callee_fndecl ()); |
| return true; |
| } |
| }; |
| |
| /* An outcome of a "strtok" call. |
| We have a four-way bifurcation of the analysis via the |
| 4 combinations of two flags: |
| - m_nonnull_str covers whether the "str" param was null or non-null |
| - m_found covers whether the result is null or non-null |
| */ |
| class strtok_call_info : public call_info |
| { |
| public: |
| strtok_call_info (const call_details &cd, |
| const private_region &private_reg, |
| bool nonnull_str, |
| bool found) |
| : call_info (cd), |
| m_private_reg (private_reg), |
| m_nonnull_str (nonnull_str), |
| m_found (found) |
| { |
| } |
| |
| void print_desc (pretty_printer &pp) const final override |
| { |
| if (m_nonnull_str) |
| { |
| if (m_found) |
| pp_printf (&pp, |
| "when %qE on non-NULL string returns non-NULL", |
| get_fndecl ()); |
| else |
| pp_printf (&pp, |
| "when %qE on non-NULL string returns NULL", |
| get_fndecl ()); |
| } |
| else |
| { |
| if (m_found) |
| pp_printf (&pp, |
| "when %qE with NULL string (using prior) returns" |
| " non-NULL", |
| get_fndecl ()); |
| else |
| pp_printf (&pp, |
| "when %qE with NULL string (using prior) returns NULL", |
| get_fndecl ()); |
| } |
| } |
| |
| bool update_model (region_model *model, |
| const exploded_edge *, |
| region_model_context *ctxt) const final override |
| { |
| region_model_manager *mgr = model->get_manager (); |
| const call_details cd (get_call_details (model, ctxt)); |
| const svalue *str_sval = cd.get_arg_svalue (0); |
| /* const svalue *delim_sval = cd.get_arg_svalue (1); */ |
| |
| cd.check_for_null_terminated_string_arg (1); |
| /* We check that either arg 0 or the private region is null |
| terminated below. */ |
| |
| const svalue *null_ptr_sval |
| = mgr->get_or_create_null_ptr (cd.get_arg_type (0));; |
| if (!model->add_constraint (str_sval, |
| m_nonnull_str ? NE_EXPR : EQ_EXPR, |
| null_ptr_sval, |
| cd.get_ctxt ())) |
| return false; |
| |
| if (m_nonnull_str) |
| { |
| /* Update internal buffer. */ |
| model->set_value (&m_private_reg, |
| mgr->get_or_create_unmergeable (str_sval), |
| ctxt); |
| } |
| else |
| { |
| /* Read from internal buffer. */ |
| str_sval = model->get_store_value (&m_private_reg, ctxt); |
| |
| /* The initial value of the private region is NULL when we're |
| on a path from main. */ |
| if (const initial_svalue *initial_sval |
| = str_sval->dyn_cast_initial_svalue ()) |
| if (initial_sval->get_region () == &m_private_reg |
| && model->called_from_main_p ()) |
| { |
| /* Implementations of strtok do not necessarily check for NULL |
| here, and may crash; see PR analyzer/107573. |
| Warn for this, if we were definitely passed NULL. */ |
| if (cd.get_arg_svalue (0)->all_zeroes_p ()) |
| { |
| if (ctxt) |
| ctxt->warn (::std::make_unique<undefined_behavior> (cd)); |
| } |
| |
| /* Assume that "str" was actually non-null; terminate |
| this path. */ |
| return false; |
| } |
| |
| /* Now assume str_sval is non-null. */ |
| if (!model->add_constraint (str_sval, |
| NE_EXPR, |
| null_ptr_sval, |
| cd.get_ctxt ())) |
| return false; |
| } |
| |
| const region *buf_reg = model->deref_rvalue (str_sval, NULL_TREE, ctxt); |
| model->scan_for_null_terminator (buf_reg, |
| NULL_TREE, |
| nullptr, |
| ctxt); |
| |
| if (m_found) |
| { |
| const region *str_reg |
| = model->deref_rvalue (str_sval, cd.get_arg_tree (0), |
| cd.get_ctxt ()); |
| /* We want to figure out the start and nul terminator |
| for the token. |
| For each, 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 *start_offset |
| = mgr->get_or_create_conjured_svalue (size_type_node, |
| &cd.get_call_stmt (), |
| str_reg, |
| conjured_purge (model, |
| ctxt), |
| 0); |
| const svalue *nul_offset |
| = mgr->get_or_create_conjured_svalue (size_type_node, |
| &cd.get_call_stmt (), |
| str_reg, |
| conjured_purge (model, |
| ctxt), |
| 1); |
| |
| tree char_ptr_type = build_pointer_type (char_type_node); |
| const svalue *result |
| = mgr->get_or_create_binop (char_ptr_type, POINTER_PLUS_EXPR, |
| str_sval, start_offset); |
| cd.maybe_set_lhs (result); |
| |
| /* nul_offset + 1; the offset to use for the next call. */ |
| const svalue *next_offset |
| = mgr->get_or_create_binop (size_type_node, PLUS_EXPR, |
| nul_offset, |
| mgr->get_or_create_int_cst |
| (char_type_node, 1)); |
| |
| /* Write '\0' to str_sval[nul_offset]. */ |
| const svalue *ptr_to_term |
| = mgr->get_or_create_binop (char_ptr_type, POINTER_PLUS_EXPR, |
| str_sval, nul_offset); |
| const region *terminator_reg |
| = model->deref_rvalue (ptr_to_term, NULL_TREE, cd.get_ctxt ()); |
| model->set_value (terminator_reg, |
| mgr->get_or_create_unmergeable |
| (mgr->get_or_create_int_cst (char_type_node, |
| 0)), |
| cd.get_ctxt ()); |
| |
| /* Update saved ptr to be at [nul_offset + 1]. */ |
| const svalue *ptr_to_next |
| = mgr->get_or_create_binop (cd.get_lhs_type (), POINTER_PLUS_EXPR, |
| str_sval, next_offset); |
| model->set_value (&m_private_reg, ptr_to_next, ctxt); |
| } |
| else |
| if (tree lhs_type = cd.get_lhs_type ()) |
| { |
| const svalue *result |
| = mgr->get_or_create_int_cst (lhs_type, 0); |
| cd.maybe_set_lhs (result); |
| } |
| return true; |
| } |
| private: |
| const private_region &m_private_reg; |
| bool m_nonnull_str; |
| bool m_found; |
| }; // class strtok_call_info |
| |
| kf_strtok (region_model_manager &mgr) |
| : m_private_reg (mgr.alloc_symbol_id (), |
| mgr.get_root_region (), |
| get_region_type (), |
| "strtok buffer") |
| { |
| } |
| |
| bool matches_call_types_p (const call_details &cd) const final override |
| { |
| return (cd.num_args () == 2 |
| && POINTER_TYPE_P (cd.get_arg_type (0)) |
| && POINTER_TYPE_P (cd.get_arg_type (1))); |
| } |
| |
| void impl_call_post (const call_details &cd) const final override |
| { |
| if (cd.get_ctxt ()) |
| { |
| /* Four-way bifurcation, based on whether: |
| - the str is non-null |
| - the result is non-null |
| Typically the str is either null or non-null at a particular site, |
| so hopefully this will generally just lead to two out-edges. */ |
| cd.get_ctxt ()->bifurcate |
| (std::make_unique<strtok_call_info> (cd, m_private_reg, false, false)); |
| cd.get_ctxt ()->bifurcate |
| (std::make_unique<strtok_call_info> (cd, m_private_reg, false, true)); |
| cd.get_ctxt ()->bifurcate |
| (std::make_unique<strtok_call_info> (cd, m_private_reg, true, false)); |
| cd.get_ctxt ()->bifurcate |
| (std::make_unique<strtok_call_info> (cd, m_private_reg, true, true)); |
| cd.get_ctxt ()->terminate_path (); |
| } |
| } |
| |
| private: |
| static tree get_region_type () |
| { |
| return build_pointer_type (char_type_node); |
| } |
| const private_region m_private_reg; |
| }; |
| |
| /* Handle calls to functions referenced by |
| __attribute__((malloc(FOO))). */ |
| |
| void |
| region_model::impl_deallocation_call (const call_details &cd) |
| { |
| kf_free kf; |
| kf.impl_call_post (cd); |
| } |
| |
| static void |
| register_atomic_builtins (known_function_manager &kfm) |
| { |
| kfm.add (BUILT_IN_ATOMIC_EXCHANGE, std::make_unique<kf_atomic_exchange> ()); |
| kfm.add (BUILT_IN_ATOMIC_EXCHANGE_N, std::make_unique<kf_atomic_exchange_n> ()); |
| kfm.add (BUILT_IN_ATOMIC_EXCHANGE_1, std::make_unique<kf_atomic_exchange_n> ()); |
| kfm.add (BUILT_IN_ATOMIC_EXCHANGE_2, std::make_unique<kf_atomic_exchange_n> ()); |
| kfm.add (BUILT_IN_ATOMIC_EXCHANGE_4, std::make_unique<kf_atomic_exchange_n> ()); |
| kfm.add (BUILT_IN_ATOMIC_EXCHANGE_8, std::make_unique<kf_atomic_exchange_n> ()); |
| kfm.add (BUILT_IN_ATOMIC_EXCHANGE_16, std::make_unique<kf_atomic_exchange_n> ()); |
| kfm.add (BUILT_IN_ATOMIC_LOAD, std::make_unique<kf_atomic_load> ()); |
| kfm.add (BUILT_IN_ATOMIC_LOAD_N, std::make_unique<kf_atomic_load_n> ()); |
| kfm.add (BUILT_IN_ATOMIC_LOAD_1, std::make_unique<kf_atomic_load_n> ()); |
| kfm.add (BUILT_IN_ATOMIC_LOAD_2, std::make_unique<kf_atomic_load_n> ()); |
| kfm.add (BUILT_IN_ATOMIC_LOAD_4, std::make_unique<kf_atomic_load_n> ()); |
| kfm.add (BUILT_IN_ATOMIC_LOAD_8, std::make_unique<kf_atomic_load_n> ()); |
| kfm.add (BUILT_IN_ATOMIC_LOAD_16, std::make_unique<kf_atomic_load_n> ()); |
| kfm.add (BUILT_IN_ATOMIC_STORE, std::make_unique<kf_atomic_store> ()); |
| kfm.add (BUILT_IN_ATOMIC_STORE_N, std::make_unique<kf_atomic_store_n> ()); |
| kfm.add (BUILT_IN_ATOMIC_STORE_1, std::make_unique<kf_atomic_store_n> ()); |
| kfm.add (BUILT_IN_ATOMIC_STORE_2, std::make_unique<kf_atomic_store_n> ()); |
| kfm.add (BUILT_IN_ATOMIC_STORE_4, std::make_unique<kf_atomic_store_n> ()); |
| kfm.add (BUILT_IN_ATOMIC_STORE_8, std::make_unique<kf_atomic_store_n> ()); |
| kfm.add (BUILT_IN_ATOMIC_STORE_16, std::make_unique<kf_atomic_store_n> ()); |
| kfm.add (BUILT_IN_ATOMIC_ADD_FETCH_1, |
| std::make_unique<kf_atomic_op_fetch> (PLUS_EXPR)); |
| kfm.add (BUILT_IN_ATOMIC_ADD_FETCH_2, |
| std::make_unique<kf_atomic_op_fetch> (PLUS_EXPR)); |
| kfm.add (BUILT_IN_ATOMIC_ADD_FETCH_4, |
| std::make_unique<kf_atomic_op_fetch> (PLUS_EXPR)); |
| kfm.add (BUILT_IN_ATOMIC_ADD_FETCH_8, |
| std::make_unique<kf_atomic_op_fetch> (PLUS_EXPR)); |
| kfm.add (BUILT_IN_ATOMIC_ADD_FETCH_16, |
| std::make_unique<kf_atomic_op_fetch> (PLUS_EXPR)); |
| kfm.add (BUILT_IN_ATOMIC_SUB_FETCH_1, |
| std::make_unique<kf_atomic_op_fetch> (MINUS_EXPR)); |
| kfm.add (BUILT_IN_ATOMIC_SUB_FETCH_2, |
| std::make_unique<kf_atomic_op_fetch> (MINUS_EXPR)); |
| kfm.add (BUILT_IN_ATOMIC_SUB_FETCH_4, |
| std::make_unique<kf_atomic_op_fetch> (MINUS_EXPR)); |
| kfm.add (BUILT_IN_ATOMIC_SUB_FETCH_8, |
| std::make_unique<kf_atomic_op_fetch> (MINUS_EXPR)); |
| kfm.add (BUILT_IN_ATOMIC_SUB_FETCH_16, |
| std::make_unique<kf_atomic_op_fetch> (MINUS_EXPR)); |
| kfm.add (BUILT_IN_ATOMIC_AND_FETCH_1, |
| std::make_unique<kf_atomic_op_fetch> (BIT_AND_EXPR)); |
| kfm.add (BUILT_IN_ATOMIC_AND_FETCH_2, |
| std::make_unique<kf_atomic_op_fetch> (BIT_AND_EXPR)); |
| kfm.add (BUILT_IN_ATOMIC_AND_FETCH_4, |
| std::make_unique<kf_atomic_op_fetch> (BIT_AND_EXPR)); |
| kfm.add (BUILT_IN_ATOMIC_AND_FETCH_8, |
| std::make_unique<kf_atomic_op_fetch> (BIT_AND_EXPR)); |
| kfm.add (BUILT_IN_ATOMIC_AND_FETCH_16, |
| std::make_unique<kf_atomic_op_fetch> (BIT_AND_EXPR)); |
| kfm.add (BUILT_IN_ATOMIC_XOR_FETCH_1, |
| std::make_unique<kf_atomic_op_fetch> (BIT_XOR_EXPR)); |
| kfm.add (BUILT_IN_ATOMIC_XOR_FETCH_2, |
| std::make_unique<kf_atomic_op_fetch> (BIT_XOR_EXPR)); |
| kfm.add (BUILT_IN_ATOMIC_XOR_FETCH_4, |
| std::make_unique<kf_atomic_op_fetch> (BIT_XOR_EXPR)); |
| kfm.add (BUILT_IN_ATOMIC_XOR_FETCH_8, |
| std::make_unique<kf_atomic_op_fetch> (BIT_XOR_EXPR)); |
| kfm.add (BUILT_IN_ATOMIC_XOR_FETCH_16, |
| std::make_unique<kf_atomic_op_fetch> (BIT_XOR_EXPR)); |
| kfm.add (BUILT_IN_ATOMIC_OR_FETCH_1, |
| std::make_unique<kf_atomic_op_fetch> (BIT_IOR_EXPR)); |
| kfm.add (BUILT_IN_ATOMIC_OR_FETCH_2, |
| std::make_unique<kf_atomic_op_fetch> (BIT_IOR_EXPR)); |
| kfm.add (BUILT_IN_ATOMIC_OR_FETCH_4, |
| std::make_unique<kf_atomic_op_fetch> (BIT_IOR_EXPR)); |
| kfm.add (BUILT_IN_ATOMIC_OR_FETCH_8, |
| std::make_unique<kf_atomic_op_fetch> (BIT_IOR_EXPR)); |
| kfm.add (BUILT_IN_ATOMIC_OR_FETCH_16, |
| std::make_unique<kf_atomic_op_fetch> (BIT_IOR_EXPR)); |
| kfm.add (BUILT_IN_ATOMIC_FETCH_ADD_1, |
| std::make_unique<kf_atomic_fetch_op> (PLUS_EXPR)); |
| kfm.add (BUILT_IN_ATOMIC_FETCH_ADD_2, |
| std::make_unique<kf_atomic_fetch_op> (PLUS_EXPR)); |
| kfm.add (BUILT_IN_ATOMIC_FETCH_ADD_4, |
| std::make_unique<kf_atomic_fetch_op> (PLUS_EXPR)); |
| kfm.add (BUILT_IN_ATOMIC_FETCH_ADD_8, |
| std::make_unique<kf_atomic_fetch_op> (PLUS_EXPR)); |
| kfm.add (BUILT_IN_ATOMIC_FETCH_ADD_16, |
| std::make_unique<kf_atomic_fetch_op> (PLUS_EXPR)); |
| kfm.add (BUILT_IN_ATOMIC_FETCH_SUB_1, |
| std::make_unique<kf_atomic_fetch_op> (MINUS_EXPR)); |
| kfm.add (BUILT_IN_ATOMIC_FETCH_SUB_2, |
| std::make_unique<kf_atomic_fetch_op> (MINUS_EXPR)); |
| kfm.add (BUILT_IN_ATOMIC_FETCH_SUB_4, |
| std::make_unique<kf_atomic_fetch_op> (MINUS_EXPR)); |
| kfm.add (BUILT_IN_ATOMIC_FETCH_SUB_8, |
| std::make_unique<kf_atomic_fetch_op> (MINUS_EXPR)); |
| kfm.add (BUILT_IN_ATOMIC_FETCH_SUB_16, |
| std::make_unique<kf_atomic_fetch_op> (MINUS_EXPR)); |
| kfm.add (BUILT_IN_ATOMIC_FETCH_AND_1, |
| std::make_unique<kf_atomic_fetch_op> (BIT_AND_EXPR)); |
| kfm.add (BUILT_IN_ATOMIC_FETCH_AND_2, |
| std::make_unique<kf_atomic_fetch_op> (BIT_AND_EXPR)); |
| kfm.add (BUILT_IN_ATOMIC_FETCH_AND_4, |
| std::make_unique<kf_atomic_fetch_op> (BIT_AND_EXPR)); |
| kfm.add (BUILT_IN_ATOMIC_FETCH_AND_8, |
| std::make_unique<kf_atomic_fetch_op> (BIT_AND_EXPR)); |
| kfm.add (BUILT_IN_ATOMIC_FETCH_AND_16, |
| std::make_unique<kf_atomic_fetch_op> (BIT_AND_EXPR)); |
| kfm.add (BUILT_IN_ATOMIC_FETCH_XOR_1, |
| std::make_unique<kf_atomic_fetch_op> (BIT_XOR_EXPR)); |
| kfm.add (BUILT_IN_ATOMIC_FETCH_XOR_2, |
| std::make_unique<kf_atomic_fetch_op> (BIT_XOR_EXPR)); |
| kfm.add (BUILT_IN_ATOMIC_FETCH_XOR_4, |
| std::make_unique<kf_atomic_fetch_op> (BIT_XOR_EXPR)); |
| kfm.add (BUILT_IN_ATOMIC_FETCH_XOR_8, |
| std::make_unique<kf_atomic_fetch_op> (BIT_XOR_EXPR)); |
| kfm.add (BUILT_IN_ATOMIC_FETCH_XOR_16, |
| std::make_unique<kf_atomic_fetch_op> (BIT_XOR_EXPR)); |
| kfm.add (BUILT_IN_ATOMIC_FETCH_OR_1, |
| std::make_unique<kf_atomic_fetch_op> (BIT_IOR_EXPR)); |
| kfm.add (BUILT_IN_ATOMIC_FETCH_OR_2, |
| std::make_unique<kf_atomic_fetch_op> (BIT_IOR_EXPR)); |
| kfm.add (BUILT_IN_ATOMIC_FETCH_OR_4, |
| std::make_unique<kf_atomic_fetch_op> (BIT_IOR_EXPR)); |
| kfm.add (BUILT_IN_ATOMIC_FETCH_OR_8, |
| std::make_unique<kf_atomic_fetch_op> (BIT_IOR_EXPR)); |
| kfm.add (BUILT_IN_ATOMIC_FETCH_OR_16, |
| std::make_unique<kf_atomic_fetch_op> (BIT_IOR_EXPR)); |
| } |
| |
| /* Handle calls to the various IFN_UBSAN_* with no return value. |
| For now, treat these as no-ops. */ |
| |
| class kf_ubsan_noop : public internal_known_function |
| { |
| }; |
| |
| /* Handle calls to the various __builtin___ubsan_handle_*. |
| These can return, but continuing after such a return |
| isn't likely to be interesting to the user of the analyzer. |
| Hence we terminate the analysis path at one of these calls. */ |
| |
| class kf_ubsan_handler : public internal_known_function |
| { |
| void impl_call_post (const call_details &cd) const final override |
| { |
| if (cd.get_ctxt ()) |
| cd.get_ctxt ()->terminate_path (); |
| } |
| }; |
| |
| static void |
| register_sanitizer_builtins (known_function_manager &kfm) |
| { |
| /* Handle calls to the various IFN_UBSAN_* with no return value. |
| For now, treat these as no-ops. */ |
| kfm.add (IFN_UBSAN_NULL, |
| std::make_unique<kf_ubsan_noop> ()); |
| kfm.add (IFN_UBSAN_BOUNDS, |
| std::make_unique<kf_ubsan_noop> ()); |
| kfm.add (IFN_UBSAN_PTR, |
| std::make_unique<kf_ubsan_noop> ()); |
| |
| kfm.add (BUILT_IN_UBSAN_HANDLE_NONNULL_ARG, |
| std::make_unique<kf_ubsan_handler> ()); |
| } |
| |
| /* Populate KFM with instances of known functions supported by the core of the |
| analyzer (as opposed to plugins). */ |
| |
| void |
| register_known_functions (known_function_manager &kfm, |
| region_model_manager &rmm) |
| { |
| /* Debugging/test support functions, all with a "__analyzer_" prefix. */ |
| register_known_analyzer_functions (kfm); |
| |
| /* Internal fns the analyzer has known_functions for. */ |
| { |
| kfm.add (IFN_BUILTIN_EXPECT, std::make_unique<kf_expect> ()); |
| } |
| |
| /* GCC built-ins that do not correspond to a function |
| in the standard library. */ |
| { |
| kfm.add (BUILT_IN_EXPECT, std::make_unique<kf_expect> ()); |
| kfm.add (BUILT_IN_EXPECT_WITH_PROBABILITY, std::make_unique<kf_expect> ()); |
| kfm.add (BUILT_IN_ALLOCA_WITH_ALIGN, std::make_unique<kf_alloca> ()); |
| kfm.add (BUILT_IN_STACK_RESTORE, std::make_unique<kf_stack_restore> ()); |
| kfm.add (BUILT_IN_STACK_SAVE, std::make_unique<kf_stack_save> ()); |
| |
| kfm.add (BUILT_IN_EH_POINTER, std::make_unique<kf_eh_pointer> ()); |
| |
| register_atomic_builtins (kfm); |
| register_sanitizer_builtins (kfm); |
| register_varargs_builtins (kfm); |
| } |
| |
| /* Known builtins and C standard library functions |
| the analyzer has known functions for. */ |
| { |
| kfm.add ("alloca", std::make_unique<kf_alloca> ()); |
| kfm.add ("__builtin_alloca", std::make_unique<kf_alloca> ()); |
| kfm.add ("calloc", std::make_unique<kf_calloc> ()); |
| kfm.add ("__builtin_calloc", std::make_unique<kf_calloc> ()); |
| kfm.add ("free", std::make_unique<kf_free> ()); |
| kfm.add ("__builtin_free", std::make_unique<kf_free> ()); |
| kfm.add ("malloc", std::make_unique<kf_malloc> ()); |
| kfm.add ("__builtin_malloc", std::make_unique<kf_malloc> ()); |
| kfm.add ("memcpy", |
| std::make_unique<kf_memcpy_memmove> (kf_memcpy_memmove::KF_MEMCPY)); |
| kfm.add ("__builtin_memcpy", |
| std::make_unique<kf_memcpy_memmove> (kf_memcpy_memmove::KF_MEMCPY)); |
| kfm.add ("__memcpy_chk", std::make_unique<kf_memcpy_memmove> |
| (kf_memcpy_memmove::KF_MEMCPY_CHK)); |
| kfm.add ("__builtin___memcpy_chk", std::make_unique<kf_memcpy_memmove> |
| (kf_memcpy_memmove::KF_MEMCPY_CHK)); |
| kfm.add ("memmove", |
| std::make_unique<kf_memcpy_memmove> (kf_memcpy_memmove::KF_MEMMOVE)); |
| kfm.add ("__builtin_memmove", |
| std::make_unique<kf_memcpy_memmove> (kf_memcpy_memmove::KF_MEMMOVE)); |
| kfm.add ("__memmove_chk", std::make_unique<kf_memcpy_memmove> |
| (kf_memcpy_memmove::KF_MEMMOVE_CHK)); |
| kfm.add ("__builtin___memmove_chk", std::make_unique<kf_memcpy_memmove> |
| (kf_memcpy_memmove::KF_MEMMOVE_CHK)); |
| kfm.add ("memset", std::make_unique<kf_memset> (false)); |
| kfm.add ("__builtin_memset", std::make_unique<kf_memset> (false)); |
| kfm.add ("__memset_chk", std::make_unique<kf_memset> (true)); |
| kfm.add ("__builtin___memset_chk", std::make_unique<kf_memset> (true)); |
| kfm.add ("realloc", std::make_unique<kf_realloc> ()); |
| kfm.add ("__builtin_realloc", std::make_unique<kf_realloc> ()); |
| kfm.add ("sprintf", std::make_unique<kf_sprintf> ()); |
| kfm.add ("__builtin_sprintf", std::make_unique<kf_sprintf> ()); |
| kfm.add ("strchr", std::make_unique<kf_strchr> ()); |
| kfm.add ("__builtin_strchr", std::make_unique<kf_strchr> ()); |
| kfm.add ("strcpy", std::make_unique<kf_strcpy> (2, false)); |
| kfm.add ("__builtin_strcpy", std::make_unique<kf_strcpy> (2, false)); |
| kfm.add ("__strcpy_chk", std::make_unique<kf_strcpy> (3, true)); |
| kfm.add ("__builtin___strcpy_chk", std::make_unique<kf_strcpy> (3, true)); |
| kfm.add ("strcat", std::make_unique<kf_strcat> (2, false)); |
| kfm.add ("__builtin_strcat", std::make_unique<kf_strcat> (2, false)); |
| kfm.add ("__strcat_chk", std::make_unique<kf_strcat> (3, true)); |
| kfm.add ("__builtin___strcat_chk", std::make_unique<kf_strcat> (3, true)); |
| kfm.add ("strdup", std::make_unique<kf_strdup> ()); |
| kfm.add ("__builtin_strdup", std::make_unique<kf_strdup> ()); |
| kfm.add ("strncpy", std::make_unique<kf_strncpy> ()); |
| kfm.add ("__builtin_strncpy", std::make_unique<kf_strncpy> ()); |
| kfm.add ("strndup", std::make_unique<kf_strndup> ()); |
| kfm.add ("__builtin_strndup", std::make_unique<kf_strndup> ()); |
| kfm.add ("strlen", std::make_unique<kf_strlen> ()); |
| kfm.add ("__builtin_strlen", std::make_unique<kf_strlen> ()); |
| kfm.add ("strstr", std::make_unique<kf_strstr> ()); |
| kfm.add ("__builtin_strstr", std::make_unique<kf_strstr> ()); |
| |
| register_atomic_builtins (kfm); |
| register_varargs_builtins (kfm); |
| } |
| |
| /* Known POSIX functions, and some non-standard extensions. */ |
| { |
| kfm.add ("fopen", std::make_unique<kf_fopen> ()); |
| kfm.add ("putenv", std::make_unique<kf_putenv> ()); |
| kfm.add ("strtok", std::make_unique<kf_strtok> (rmm)); |
| |
| register_known_fd_functions (kfm); |
| register_known_file_functions (kfm); |
| } |
| |
| /* glibc functions. */ |
| { |
| kfm.add ("__errno_location", std::make_unique<kf_errno_location> ()); |
| kfm.add ("error", std::make_unique<kf_error> (3)); |
| kfm.add ("error_at_line", std::make_unique<kf_error> (5)); |
| /* Variants of "error" and "error_at_line" seen by the |
| analyzer at -O0 (PR analyzer/115724). */ |
| kfm.add ("__error_alias", std::make_unique<kf_error> (3)); |
| kfm.add ("__error_at_line_alias", std::make_unique<kf_error> (5)); |
| } |
| |
| /* Other implementations of C standard library. */ |
| { |
| /* According to PR 107807 comment #2, Solaris implements "errno" |
| like this: |
| extern int *___errno(void) __attribute__((__const__)); |
| #define errno (*(___errno())) |
| and macOS like this: |
| extern int * __error(void); |
| #define errno (*__error()) |
| and similarly __errno for newlib. |
| Add these as synonyms for "__errno_location". */ |
| kfm.add ("___errno", std::make_unique<kf_errno_location> ()); |
| kfm.add ("__error", std::make_unique<kf_errno_location> ()); |
| kfm.add ("__errno", std::make_unique<kf_errno_location> ()); |
| } |
| |
| /* Language-specific support functions. */ |
| register_known_functions_lang_cp (kfm); |
| |
| /* Some C++ implementations use the std:: copies of these functions |
| from <cstdlib> etc for the C spellings of these headers (e.g. <stdlib.h>), |
| so we must match against these too. */ |
| { |
| kfm.add_std_ns ("malloc", std::make_unique<kf_malloc> ()); |
| kfm.add_std_ns ("free", std::make_unique<kf_free> ()); |
| kfm.add_std_ns ("realloc", std::make_unique<kf_realloc> ()); |
| kfm.add_std_ns ("calloc", std::make_unique<kf_calloc> ()); |
| kfm.add_std_ns |
| ("memcpy", |
| std::make_unique<kf_memcpy_memmove> (kf_memcpy_memmove::KF_MEMCPY)); |
| kfm.add_std_ns |
| ("memmove", |
| std::make_unique<kf_memcpy_memmove> (kf_memcpy_memmove::KF_MEMMOVE)); |
| kfm.add_std_ns ("memset", std::make_unique<kf_memset> (false)); |
| kfm.add_std_ns ("strcat", std::make_unique<kf_strcat> (2, false)); |
| kfm.add_std_ns ("strcpy", std::make_unique<kf_strcpy> (2, false)); |
| kfm.add_std_ns ("strlen", std::make_unique<kf_strlen> ()); |
| kfm.add_std_ns ("strncpy", std::make_unique<kf_strncpy> ()); |
| kfm.add_std_ns ("strtok", std::make_unique<kf_strtok> (rmm)); |
| } |
| } |
| |
| } // namespace ana |
| |
| #endif /* #if ENABLE_ANALYZER */ |