| /* Handling for the known behavior of various specific functions. |
| Copyright (C) 2020-2022 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 "config.h" |
| #include "system.h" |
| #include "coretypes.h" |
| #include "tree.h" |
| #include "function.h" |
| #include "basic-block.h" |
| #include "gimple.h" |
| #include "gimple-iterator.h" |
| #include "diagnostic-core.h" |
| #include "graphviz.h" |
| #include "options.h" |
| #include "cgraph.h" |
| #include "tree-dfa.h" |
| #include "stringpool.h" |
| #include "convert.h" |
| #include "target.h" |
| #include "fold-const.h" |
| #include "tree-pretty-print.h" |
| #include "diagnostic-color.h" |
| #include "diagnostic-metadata.h" |
| #include "tristate.h" |
| #include "bitmap.h" |
| #include "selftest.h" |
| #include "function.h" |
| #include "json.h" |
| #include "analyzer/analyzer.h" |
| #include "analyzer/analyzer-logging.h" |
| #include "ordered-hash-map.h" |
| #include "options.h" |
| #include "cgraph.h" |
| #include "cfg.h" |
| #include "digraph.h" |
| #include "analyzer/supergraph.h" |
| #include "sbitmap.h" |
| #include "analyzer/call-string.h" |
| #include "analyzer/program-point.h" |
| #include "analyzer/store.h" |
| #include "analyzer/region-model.h" |
| #include "analyzer/call-info.h" |
| #include "gimple-pretty-print.h" |
| |
| #if ENABLE_ANALYZER |
| |
| namespace ana { |
| |
| /* class call_details. */ |
| |
| /* call_details's ctor. */ |
| |
| call_details::call_details (const gcall *call, region_model *model, |
| region_model_context *ctxt) |
| : m_call (call), m_model (model), m_ctxt (ctxt), |
| m_lhs_type (NULL_TREE), m_lhs_region (NULL) |
| { |
| m_lhs_type = NULL_TREE; |
| if (tree lhs = gimple_call_lhs (call)) |
| { |
| m_lhs_region = model->get_lvalue (lhs, ctxt); |
| m_lhs_type = TREE_TYPE (lhs); |
| } |
| } |
| |
| /* Get the manager from m_model. */ |
| |
| region_model_manager * |
| call_details::get_manager () const |
| { |
| return m_model->get_manager (); |
| } |
| |
| /* Get any uncertainty_t associated with the region_model_context. */ |
| |
| uncertainty_t * |
| call_details::get_uncertainty () const |
| { |
| if (m_ctxt) |
| return m_ctxt->get_uncertainty (); |
| else |
| return NULL; |
| } |
| |
| /* If the callsite has a left-hand-side region, set it to RESULT |
| and return true. |
| Otherwise do nothing and return false. */ |
| |
| bool |
| call_details::maybe_set_lhs (const svalue *result) const |
| { |
| gcc_assert (result); |
| if (m_lhs_region) |
| { |
| m_model->set_value (m_lhs_region, result, m_ctxt); |
| return true; |
| } |
| else |
| return false; |
| } |
| |
| /* Return the number of arguments used by the call statement. */ |
| |
| unsigned |
| call_details::num_args () const |
| { |
| return gimple_call_num_args (m_call); |
| } |
| |
| /* Get argument IDX at the callsite as a tree. */ |
| |
| tree |
| call_details::get_arg_tree (unsigned idx) const |
| { |
| return gimple_call_arg (m_call, idx); |
| } |
| |
| /* Get the type of argument IDX. */ |
| |
| tree |
| call_details::get_arg_type (unsigned idx) const |
| { |
| return TREE_TYPE (gimple_call_arg (m_call, idx)); |
| } |
| |
| /* Get argument IDX at the callsite as an svalue. */ |
| |
| const svalue * |
| call_details::get_arg_svalue (unsigned idx) const |
| { |
| tree arg = get_arg_tree (idx); |
| return m_model->get_rvalue (arg, m_ctxt); |
| } |
| |
| /* Attempt to get the string literal for argument IDX, or return NULL |
| otherwise. |
| For use when implementing "__analyzer_*" functions that take |
| string literals. */ |
| |
| const char * |
| call_details::get_arg_string_literal (unsigned idx) const |
| { |
| const svalue *str_arg = get_arg_svalue (idx); |
| if (const region *pointee = str_arg->maybe_get_region ()) |
| if (const string_region *string_reg = pointee->dyn_cast_string_region ()) |
| { |
| tree string_cst = string_reg->get_string_cst (); |
| return TREE_STRING_POINTER (string_cst); |
| } |
| return NULL; |
| } |
| |
| /* Attempt to get the fndecl used at this call, if known, or NULL_TREE |
| otherwise. */ |
| |
| tree |
| call_details::get_fndecl_for_call () const |
| { |
| return m_model->get_fndecl_for_call (m_call, m_ctxt); |
| } |
| |
| /* Dump a multiline representation of this call to PP. */ |
| |
| void |
| call_details::dump_to_pp (pretty_printer *pp, bool simple) const |
| { |
| pp_string (pp, "gcall: "); |
| pp_gimple_stmt_1 (pp, m_call, 0 /* spc */, TDF_NONE /* flags */); |
| pp_newline (pp); |
| pp_string (pp, "return region: "); |
| if (m_lhs_region) |
| m_lhs_region->dump_to_pp (pp, simple); |
| else |
| pp_string (pp, "NULL"); |
| pp_newline (pp); |
| for (unsigned i = 0; i < gimple_call_num_args (m_call); i++) |
| { |
| const svalue *arg_sval = get_arg_svalue (i); |
| pp_printf (pp, "arg %i: ", i); |
| arg_sval->dump_to_pp (pp, simple); |
| pp_newline (pp); |
| } |
| } |
| |
| /* Dump a multiline representation of this call to stderr. */ |
| |
| DEBUG_FUNCTION void |
| call_details::dump (bool simple) const |
| { |
| pretty_printer pp; |
| pp_format_decoder (&pp) = default_tree_printer; |
| pp_show_color (&pp) = pp_show_color (global_dc->printer); |
| pp.buffer->stream = stderr; |
| dump_to_pp (&pp, simple); |
| pp_flush (&pp); |
| } |
| |
| /* Get a conjured_svalue for this call for REG, |
| and purge any state already relating to that conjured_svalue. */ |
| |
| const svalue * |
| call_details::get_or_create_conjured_svalue (const region *reg) const |
| { |
| region_model_manager *mgr = m_model->get_manager (); |
| return mgr->get_or_create_conjured_svalue (reg->get_type (), m_call, reg, |
| conjured_purge (m_model, m_ctxt)); |
| } |
| |
| /* Implementations of specific functions. */ |
| |
| /* Handle the on_call_pre part of "alloca". */ |
| |
| void |
| region_model::impl_call_alloca (const call_details &cd) |
| { |
| const svalue *size_sval = cd.get_arg_svalue (0); |
| const region *new_reg = create_region_for_alloca (size_sval, cd.get_ctxt ()); |
| const svalue *ptr_sval |
| = m_mgr->get_ptr_svalue (cd.get_lhs_type (), new_reg); |
| cd.maybe_set_lhs (ptr_sval); |
| } |
| |
| /* Handle a call to "__analyzer_describe". |
| |
| Emit a warning describing the 2nd argument (which can be of any |
| type), at the given verbosity level. This is for use when |
| debugging, and may be of use in DejaGnu tests. */ |
| |
| void |
| region_model::impl_call_analyzer_describe (const gcall *call, |
| region_model_context *ctxt) |
| { |
| tree t_verbosity = gimple_call_arg (call, 0); |
| tree t_val = gimple_call_arg (call, 1); |
| const svalue *sval = get_rvalue (t_val, ctxt); |
| bool simple = zerop (t_verbosity); |
| label_text desc = sval->get_desc (simple); |
| warning_at (call->location, 0, "svalue: %qs", desc.m_buffer); |
| } |
| |
| /* Handle a call to "__analyzer_dump_capacity". |
| |
| Emit a warning describing the capacity of the base region of |
| the region pointed to by the 1st argument. |
| This is for use when debugging, and may be of use in DejaGnu tests. */ |
| |
| void |
| region_model::impl_call_analyzer_dump_capacity (const gcall *call, |
| region_model_context *ctxt) |
| { |
| tree t_ptr = gimple_call_arg (call, 0); |
| const svalue *sval_ptr = get_rvalue (t_ptr, ctxt); |
| const region *reg = deref_rvalue (sval_ptr, t_ptr, ctxt); |
| const region *base_reg = reg->get_base_region (); |
| const svalue *capacity = get_capacity (base_reg); |
| label_text desc = capacity->get_desc (true); |
| warning_at (call->location, 0, "capacity: %qs", desc.m_buffer); |
| } |
| |
| /* Compare D1 and D2 using their names, and then IDs to order them. */ |
| |
| static int |
| cmp_decls (tree d1, tree d2) |
| { |
| gcc_assert (DECL_P (d1)); |
| gcc_assert (DECL_P (d2)); |
| if (DECL_NAME (d1) && DECL_NAME (d2)) |
| if (int cmp = strcmp (IDENTIFIER_POINTER (DECL_NAME (d1)), |
| IDENTIFIER_POINTER (DECL_NAME (d2)))) |
| return cmp; |
| return (int)DECL_UID (d1) - (int)DECL_UID (d2); |
| } |
| |
| /* Comparator for use by vec<tree>::qsort, |
| using their names, and then IDs to order them. */ |
| |
| static int |
| cmp_decls_ptr_ptr (const void *p1, const void *p2) |
| { |
| tree const *d1 = (tree const *)p1; |
| tree const *d2 = (tree const *)p2; |
| |
| return cmp_decls (*d1, *d2); |
| } |
| |
| /* Handle a call to "__analyzer_dump_escaped". |
| |
| Emit a warning giving the number of decls that have escaped, followed |
| by a comma-separated list of their names, in alphabetical order. |
| |
| This is for use when debugging, and may be of use in DejaGnu tests. */ |
| |
| void |
| region_model::impl_call_analyzer_dump_escaped (const gcall *call) |
| { |
| auto_vec<tree> escaped_decls; |
| for (auto iter : m_store) |
| { |
| const binding_cluster *c = iter.second; |
| if (!c->escaped_p ()) |
| continue; |
| if (tree decl = c->get_base_region ()->maybe_get_decl ()) |
| escaped_decls.safe_push (decl); |
| } |
| |
| /* Sort them into deterministic order; alphabetical is |
| probably most user-friendly. */ |
| escaped_decls.qsort (cmp_decls_ptr_ptr); |
| |
| pretty_printer pp; |
| pp_format_decoder (&pp) = default_tree_printer; |
| pp_show_color (&pp) = pp_show_color (global_dc->printer); |
| bool first = true; |
| for (auto iter : escaped_decls) |
| { |
| if (first) |
| first = false; |
| else |
| pp_string (&pp, ", "); |
| pp_printf (&pp, "%qD", iter); |
| } |
| /* Print the number to make it easier to write DejaGnu tests for |
| the "nothing has escaped" case. */ |
| warning_at (call->location, 0, "escaped: %i: %s", |
| escaped_decls.length (), |
| pp_formatted_text (&pp)); |
| } |
| |
| /* Handle a call to "__analyzer_eval" by evaluating the input |
| and dumping as a dummy warning, so that test cases can use |
| dg-warning to validate the result (and so unexpected warnings will |
| lead to DejaGnu failures). |
| Broken out as a subroutine to make it easier to put a breakpoint on it |
| - though typically this doesn't help, as we have an SSA name as the arg, |
| and what's more interesting is usually the def stmt for that name. */ |
| |
| void |
| region_model::impl_call_analyzer_eval (const gcall *call, |
| region_model_context *ctxt) |
| { |
| tree t_arg = gimple_call_arg (call, 0); |
| tristate t = eval_condition (t_arg, NE_EXPR, integer_zero_node, ctxt); |
| warning_at (call->location, 0, "%s", t.as_string ()); |
| } |
| |
| /* Handle the on_call_pre part of "__builtin_expect" etc. */ |
| |
| void |
| region_model::impl_call_builtin_expect (const call_details &cd) |
| { |
| /* __builtin_expect's return value is its initial argument. */ |
| const svalue *sval = cd.get_arg_svalue (0); |
| cd.maybe_set_lhs (sval); |
| } |
| |
| /* Handle the on_call_pre part of "calloc". */ |
| |
| void |
| region_model::impl_call_calloc (const call_details &cd) |
| { |
| 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 |
| = m_mgr->get_or_create_binop (size_type_node, MULT_EXPR, |
| nmemb_sval, size_sval); |
| const region *new_reg |
| = create_region_for_heap_alloc (prod_sval, cd.get_ctxt ()); |
| const region *sized_reg |
| = m_mgr->get_sized_region (new_reg, NULL_TREE, prod_sval); |
| zero_fill_region (sized_reg); |
| if (cd.get_lhs_type ()) |
| { |
| const svalue *ptr_sval |
| = m_mgr->get_ptr_svalue (cd.get_lhs_type (), new_reg); |
| cd.maybe_set_lhs (ptr_sval); |
| } |
| } |
| |
| /* Handle the on_call_pre part of "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). |
| Return true if handling it as one of these functions. |
| Write true to *OUT_TERMINATE_PATH if this execution path should be |
| terminated (e.g. the function call terminates the process). */ |
| |
| bool |
| region_model::impl_call_error (const call_details &cd, unsigned min_args, |
| bool *out_terminate_path) |
| { |
| /* Bail if not enough args. */ |
| if (cd.num_args () < min_args) |
| return false; |
| |
| /* Initial argument ought to be of type "int". */ |
| if (cd.get_arg_type (0) != integer_type_node) |
| return false; |
| |
| /* 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); |
| if (!add_constraint (status, EQ_EXPR, integer_zero_node, cd.get_ctxt ())) |
| *out_terminate_path = true; |
| |
| return true; |
| } |
| |
| /* Handle the on_call_pre part of "fgets" and "fgets_unlocked". */ |
| |
| void |
| region_model::impl_call_fgets (const call_details &cd) |
| { |
| /* Ideally we would bifurcate state here between the |
| error vs no error cases. */ |
| const svalue *ptr_sval = cd.get_arg_svalue (0); |
| if (const region *reg = ptr_sval->maybe_get_region ()) |
| { |
| const region *base_reg = reg->get_base_region (); |
| const svalue *new_sval = cd.get_or_create_conjured_svalue (base_reg); |
| set_value (base_reg, new_sval, cd.get_ctxt ()); |
| } |
| } |
| |
| /* Handle the on_call_pre part of "fread". */ |
| |
| void |
| region_model::impl_call_fread (const call_details &cd) |
| { |
| const svalue *ptr_sval = cd.get_arg_svalue (0); |
| if (const region *reg = ptr_sval->maybe_get_region ()) |
| { |
| const region *base_reg = reg->get_base_region (); |
| const svalue *new_sval = cd.get_or_create_conjured_svalue (base_reg); |
| set_value (base_reg, new_sval, cd.get_ctxt ()); |
| } |
| } |
| |
| /* Handle the on_call_post part of "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_without_cm), and thus transition |
| all pointers to the region to the "freed" state together, regardless |
| of casts. */ |
| |
| void |
| region_model::impl_call_free (const call_details &cd) |
| { |
| 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. */ |
| unbind_region_and_descendents (freed_reg, POISON_KIND_FREED); |
| m_dynamic_extents.remove (freed_reg); |
| } |
| } |
| |
| /* Handle the on_call_pre part of "malloc". */ |
| |
| void |
| region_model::impl_call_malloc (const call_details &cd) |
| { |
| const svalue *size_sval = cd.get_arg_svalue (0); |
| const region *new_reg |
| = create_region_for_heap_alloc (size_sval, cd.get_ctxt ()); |
| if (cd.get_lhs_type ()) |
| { |
| const svalue *ptr_sval |
| = m_mgr->get_ptr_svalue (cd.get_lhs_type (), new_reg); |
| cd.maybe_set_lhs (ptr_sval); |
| } |
| } |
| |
| /* Handle the on_call_pre part of "memcpy" and "__builtin_memcpy". */ |
| // TODO: complain about overlapping src and dest. |
| |
| void |
| region_model::impl_call_memcpy (const call_details &cd) |
| { |
| 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); |
| |
| const region *dest_reg = deref_rvalue (dest_ptr_sval, cd.get_arg_tree (0), |
| cd.get_ctxt ()); |
| const region *src_reg = deref_rvalue (src_ptr_sval, cd.get_arg_tree (1), |
| cd.get_ctxt ()); |
| |
| cd.maybe_set_lhs (dest_ptr_sval); |
| |
| const region *sized_src_reg |
| = m_mgr->get_sized_region (src_reg, NULL_TREE, num_bytes_sval); |
| const region *sized_dest_reg |
| = m_mgr->get_sized_region (dest_reg, NULL_TREE, num_bytes_sval); |
| const svalue *src_contents_sval |
| = get_store_value (sized_src_reg, cd.get_ctxt ()); |
| set_value (sized_dest_reg, src_contents_sval, cd.get_ctxt ()); |
| } |
| |
| /* Handle the on_call_pre part of "memset" and "__builtin_memset". */ |
| |
| void |
| region_model::impl_call_memset (const call_details &cd) |
| { |
| 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); |
| |
| const region *dest_reg = deref_rvalue (dest_sval, cd.get_arg_tree (0), |
| cd.get_ctxt ()); |
| |
| const svalue *fill_value_u8 |
| = m_mgr->get_or_create_cast (unsigned_char_type_node, fill_value_sval); |
| |
| const region *sized_dest_reg = m_mgr->get_sized_region (dest_reg, |
| NULL_TREE, |
| num_bytes_sval); |
| check_region_for_write (sized_dest_reg, cd.get_ctxt ()); |
| fill_region (sized_dest_reg, fill_value_u8); |
| } |
| |
| /* Handle the on_call_pre part of "operator new". */ |
| |
| void |
| region_model::impl_call_operator_new (const call_details &cd) |
| { |
| const svalue *size_sval = cd.get_arg_svalue (0); |
| const region *new_reg |
| = create_region_for_heap_alloc (size_sval, cd.get_ctxt ()); |
| if (cd.get_lhs_type ()) |
| { |
| const svalue *ptr_sval |
| = m_mgr->get_ptr_svalue (cd.get_lhs_type (), new_reg); |
| cd.maybe_set_lhs (ptr_sval); |
| } |
| } |
| |
| /* Handle the on_call_pre part of "operator delete", which comes in |
| both sized and unsized variants (2 arguments and 1 argument |
| respectively). */ |
| |
| void |
| region_model::impl_call_operator_delete (const call_details &cd) |
| { |
| 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. */ |
| unbind_region_and_descendents (freed_reg, POISON_KIND_FREED); |
| } |
| } |
| |
| /* Handle the on_call_post part of "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. */ |
| |
| void |
| region_model::impl_call_realloc (const call_details &cd) |
| { |
| /* 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)); |
| if (cd.get_lhs_type ()) |
| { |
| const svalue *zero |
| = model->m_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) |
| { |
| } |
| |
| label_text get_desc (bool can_colorize) const FINAL OVERRIDE |
| { |
| return make_label_text (can_colorize, |
| "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)); |
| 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 |
| = model->m_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 |
| = model->m_mgr->get_or_create_int_cst (cd.get_lhs_type (), 0); |
| return model->add_constraint (ptr_sval, NE_EXPR, zero, cd.get_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) |
| { |
| } |
| |
| label_text get_desc (bool can_colorize) const FINAL OVERRIDE |
| { |
| return make_label_text (can_colorize, |
| "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)); |
| 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->create_region_for_heap_alloc (new_size_sval, ctxt); |
| const svalue *new_ptr_sval |
| = model->m_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 region *sized_old_reg |
| = model->m_mgr->get_sized_region (freed_reg, NULL, |
| old_size_sval); |
| const svalue *buffer_content_sval |
| = model->get_store_value (sized_old_reg, cd.get_ctxt ()); |
| const region *sized_new_reg |
| = model->m_mgr->get_sized_region (new_reg, NULL, |
| old_size_sval); |
| model->set_value (sized_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->m_dynamic_extents.remove (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 |
| = model->m_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; |
| } |
| }; |
| |
| /* Body of region_model::impl_call_realloc. */ |
| |
| if (cd.get_ctxt ()) |
| { |
| cd.get_ctxt ()->bifurcate (new failure (cd)); |
| cd.get_ctxt ()->bifurcate (new success_no_move (cd)); |
| cd.get_ctxt ()->bifurcate (new success_with_move (cd)); |
| cd.get_ctxt ()->terminate_path (); |
| } |
| } |
| |
| /* Handle the on_call_pre part of "strchr" and "__builtin_strchr". */ |
| |
| void |
| region_model::impl_call_strchr (const call_details &cd) |
| { |
| class strchr_call_info : public call_info |
| { |
| public: |
| strchr_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; |
| }; |
| |
| /* Bifurcate state, creating a "not found" out-edge. */ |
| if (cd.get_ctxt ()) |
| cd.get_ctxt ()->bifurcate (new strchr_call_info (cd, false)); |
| |
| /* The "unbifurcated" state is the "found" case. */ |
| strchr_call_info found (cd, true); |
| found.update_model (this, NULL, cd.get_ctxt ()); |
| } |
| |
| /* Handle the on_call_pre part of "strcpy" and "__builtin_strcpy_chk". */ |
| |
| void |
| region_model::impl_call_strcpy (const call_details &cd) |
| { |
| const svalue *dest_sval = cd.get_arg_svalue (0); |
| const region *dest_reg = deref_rvalue (dest_sval, cd.get_arg_tree (0), |
| cd.get_ctxt ()); |
| |
| cd.maybe_set_lhs (dest_sval); |
| |
| check_region_for_write (dest_reg, cd.get_ctxt ()); |
| |
| /* For now, just mark region's contents as unknown. */ |
| mark_region_as_unknown (dest_reg, cd.get_uncertainty ()); |
| } |
| |
| /* Handle the on_call_pre part of "strlen". */ |
| |
| void |
| region_model::impl_call_strlen (const call_details &cd) |
| { |
| region_model_context *ctxt = cd.get_ctxt (); |
| const svalue *arg_sval = cd.get_arg_svalue (0); |
| const region *buf_reg = deref_rvalue (arg_sval, cd.get_arg_tree (0), ctxt); |
| if (const string_region *str_reg |
| = buf_reg->dyn_cast_string_region ()) |
| { |
| tree str_cst = str_reg->get_string_cst (); |
| /* TREE_STRING_LENGTH is sizeof, not strlen. */ |
| int sizeof_cst = TREE_STRING_LENGTH (str_cst); |
| int strlen_cst = sizeof_cst - 1; |
| if (cd.get_lhs_type ()) |
| { |
| tree t_cst = build_int_cst (cd.get_lhs_type (), strlen_cst); |
| const svalue *result_sval |
| = m_mgr->get_or_create_constant_svalue (t_cst); |
| cd.maybe_set_lhs (result_sval); |
| return; |
| } |
| } |
| /* Otherwise a conjured value. */ |
| } |
| |
| /* Handle calls to functions referenced by |
| __attribute__((malloc(FOO))). */ |
| |
| void |
| region_model::impl_deallocation_call (const call_details &cd) |
| { |
| impl_call_free (cd); |
| } |
| |
| } // namespace ana |
| |
| #endif /* #if ENABLE_ANALYZER */ |