508 lines
14 KiB
C++
508 lines
14 KiB
C++
/* Handling for the known behavior of various specific functions.
|
|
Copyright (C) 2020-2021 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 "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 any uncertainty_t associated with the region_model_context. */
|
|
|
|
uncertainty_t *
|
|
call_details::get_uncertainty () const
|
|
{
|
|
return m_ctxt->get_uncertainty ();
|
|
}
|
|
|
|
/* 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);
|
|
}
|
|
|
|
/* 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);
|
|
}
|
|
|
|
/* Implementations of specific functions. */
|
|
|
|
/* Handle the on_call_pre part of "alloca". */
|
|
|
|
bool
|
|
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);
|
|
const svalue *ptr_sval
|
|
= m_mgr->get_ptr_svalue (cd.get_lhs_type (), new_reg);
|
|
cd.maybe_set_lhs (ptr_sval);
|
|
return true;
|
|
}
|
|
|
|
/* 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_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. */
|
|
|
|
bool
|
|
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);
|
|
return false;
|
|
}
|
|
|
|
/* Handle the on_call_pre part of "calloc". */
|
|
|
|
bool
|
|
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);
|
|
zero_fill_region (new_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);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/* 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_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_svalue *ptr_to_region_sval
|
|
= ptr_sval->dyn_cast_region_svalue ())
|
|
{
|
|
/* If the ptr points to an underlying heap region, delete it,
|
|
poisoning pointers. */
|
|
const region *freed_reg = ptr_to_region_sval->get_pointee ();
|
|
unbind_region_and_descendents (freed_reg, POISON_KIND_FREED);
|
|
}
|
|
}
|
|
|
|
/* Handle the on_call_pre part of "malloc". */
|
|
|
|
bool
|
|
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);
|
|
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);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/* Handle the on_call_pre part of "memcpy" and "__builtin_memcpy". */
|
|
|
|
void
|
|
region_model::impl_call_memcpy (const call_details &cd)
|
|
{
|
|
const svalue *dest_sval = cd.get_arg_svalue (0);
|
|
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 ());
|
|
|
|
cd.maybe_set_lhs (dest_sval);
|
|
|
|
if (tree num_bytes = num_bytes_sval->maybe_get_constant ())
|
|
{
|
|
/* "memcpy" of zero size is a no-op. */
|
|
if (zerop (num_bytes))
|
|
return;
|
|
}
|
|
|
|
check_for_writable_region (dest_reg, cd.get_ctxt ());
|
|
|
|
/* Otherwise, mark region's contents as unknown. */
|
|
mark_region_as_unknown (dest_reg, cd.get_uncertainty ());
|
|
}
|
|
|
|
/* Handle the on_call_pre part of "memset" and "__builtin_memset". */
|
|
|
|
bool
|
|
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 ());
|
|
|
|
if (tree num_bytes = num_bytes_sval->maybe_get_constant ())
|
|
{
|
|
/* "memset" of zero size is a no-op. */
|
|
if (zerop (num_bytes))
|
|
return true;
|
|
|
|
/* Set with known amount. */
|
|
byte_size_t reg_size;
|
|
if (dest_reg->get_byte_size (®_size))
|
|
{
|
|
/* Check for an exact size match. */
|
|
if (reg_size == wi::to_offset (num_bytes))
|
|
{
|
|
if (tree cst = fill_value_sval->maybe_get_constant ())
|
|
{
|
|
if (zerop (cst))
|
|
{
|
|
zero_fill_region (dest_reg);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
check_for_writable_region (dest_reg, cd.get_ctxt ());
|
|
|
|
/* Otherwise, mark region's contents as unknown. */
|
|
mark_region_as_unknown (dest_reg, cd.get_uncertainty ());
|
|
return false;
|
|
}
|
|
|
|
/* Handle the on_call_pre part of "operator new". */
|
|
|
|
bool
|
|
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);
|
|
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);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/* Handle the on_call_pre part of "operator delete", which comes in
|
|
both sized and unsized variants (2 arguments and 1 argument
|
|
respectively). */
|
|
|
|
bool
|
|
region_model::impl_call_operator_delete (const call_details &cd)
|
|
{
|
|
const svalue *ptr_sval = cd.get_arg_svalue (0);
|
|
if (const region_svalue *ptr_to_region_sval
|
|
= ptr_sval->dyn_cast_region_svalue ())
|
|
{
|
|
/* If the ptr points to an underlying heap region, delete it,
|
|
poisoning pointers. */
|
|
const region *freed_reg = ptr_to_region_sval->get_pointee ();
|
|
unbind_region_and_descendents (freed_reg, POISON_KIND_FREED);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/* Handle the on_call_pre part of "realloc". */
|
|
|
|
void
|
|
region_model::impl_call_realloc (const call_details &)
|
|
{
|
|
/* Currently we don't support bifurcating state, so there's no good
|
|
way to implement realloc(3).
|
|
For now, malloc_state_machine::on_realloc_call has a minimal
|
|
implementation to suppress false positives. */
|
|
}
|
|
|
|
/* 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_for_writable_region (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".
|
|
Return true if the LHS is updated. */
|
|
|
|
bool
|
|
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 true;
|
|
}
|
|
}
|
|
/* Otherwise an unknown value. */
|
|
return true;
|
|
}
|
|
|
|
/* 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 */
|