blob: 4052bb393f82dcb68f740429ced42369d1095a6b [file] [log] [blame]
/* 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 (&reg_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 */