blob: 2631ea255593c18625cb66ec9880663916fbafcd [file] [log] [blame]
/* Classes for modeling the state of memory.
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 "bitmap.h"
#include "selftest.h"
#include "analyzer/analyzer.h"
#include "analyzer/analyzer-logging.h"
#include "ordered-hash-map.h"
#include "options.h"
#include "cfg.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-summary.h"
#include "analyzer/analyzer-selftests.h"
#include "stor-layout.h"
#if ENABLE_ANALYZER
namespace ana {
/* Dump SVALS to PP, sorting them to ensure determinism. */
static void
dump_svalue_set (const hash_set <const svalue *> &svals,
pretty_printer *pp, bool simple)
{
auto_vec <const svalue *> v;
for (hash_set<const svalue *>::iterator iter = svals.begin ();
iter != svals.end (); ++iter)
{
v.safe_push (*iter);
}
v.qsort (svalue::cmp_ptr_ptr);
pp_character (pp, '{');
const svalue *sval;
unsigned i;
FOR_EACH_VEC_ELT (v, i, sval)
{
if (i > 0)
pp_string (pp, ", ");
sval->dump_to_pp (pp, simple);
}
pp_character (pp, '}');
}
/* class uncertainty_t. */
/* Dump this object to PP. */
void
uncertainty_t::dump_to_pp (pretty_printer *pp, bool simple) const
{
pp_string (pp, "{m_maybe_bound_svals: ");
dump_svalue_set (m_maybe_bound_svals, pp, simple);
pp_string (pp, ", m_mutable_at_unknown_call_svals: ");
dump_svalue_set (m_mutable_at_unknown_call_svals, pp, simple);
pp_string (pp, "}");
}
/* Dump this object to stderr. */
DEBUG_FUNCTION void
uncertainty_t::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_newline (&pp);
pp_flush (&pp);
}
/* class binding_key. */
const binding_key *
binding_key::make (store_manager *mgr, const region *r)
{
region_offset offset = r->get_offset (mgr->get_svalue_manager ());
if (offset.symbolic_p ())
return mgr->get_symbolic_binding (r);
else
{
bit_size_t bit_size;
if (r->get_bit_size (&bit_size))
return mgr->get_concrete_binding (offset.get_bit_offset (),
bit_size);
else
return mgr->get_symbolic_binding (r);
}
}
/* Dump this binding_key to stderr. */
DEBUG_FUNCTION void
binding_key::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_newline (&pp);
pp_flush (&pp);
}
/* Get a description of this binding_key. */
label_text
binding_key::get_desc (bool simple) const
{
pretty_printer pp;
pp_format_decoder (&pp) = default_tree_printer;
dump_to_pp (&pp, simple);
return label_text::take (xstrdup (pp_formatted_text (&pp)));
}
/* qsort callback. */
int
binding_key::cmp_ptrs (const void *p1, const void *p2)
{
const binding_key * const *pk1 = (const binding_key * const *)p1;
const binding_key * const *pk2 = (const binding_key * const *)p2;
return cmp (*pk1, *pk2);
}
/* Comparator for binding_keys. */
int
binding_key::cmp (const binding_key *k1, const binding_key *k2)
{
int concrete1 = k1->concrete_p ();
int concrete2 = k2->concrete_p ();
if (int concrete_cmp = concrete1 - concrete2)
return concrete_cmp;
if (concrete1)
{
const concrete_binding *b1 = (const concrete_binding *)k1;
const concrete_binding *b2 = (const concrete_binding *)k2;
if (int start_cmp = wi::cmp (b1->get_start_bit_offset (),
b2->get_start_bit_offset (),
SIGNED))
return start_cmp;
return wi::cmp (b1->get_next_bit_offset (), b2->get_next_bit_offset (),
SIGNED);
}
else
{
const symbolic_binding *s1 = (const symbolic_binding *)k1;
const symbolic_binding *s2 = (const symbolic_binding *)k2;
if (s1 > s2)
return 1;
if (s1 < s2)
return -1;
return 0;
}
}
/* struct bit_range. */
void
bit_range::dump_to_pp (pretty_printer *pp) const
{
byte_range bytes (0, 0);
if (as_byte_range (&bytes))
bytes.dump_to_pp (pp);
else
{
pp_string (pp, "start: ");
pp_wide_int (pp, m_start_bit_offset, SIGNED);
pp_string (pp, ", size: ");
pp_wide_int (pp, m_size_in_bits, SIGNED);
pp_string (pp, ", next: ");
pp_wide_int (pp, get_next_bit_offset (), SIGNED);
}
}
/* Dump this object to stderr. */
DEBUG_FUNCTION void
bit_range::dump () const
{
pretty_printer pp;
pp.buffer->stream = stderr;
dump_to_pp (&pp);
pp_newline (&pp);
pp_flush (&pp);
}
/* If OTHER is a subset of this, return true and write
to *OUT the relative range of OTHER within this.
Otherwise return false. */
bool
bit_range::contains_p (const bit_range &other, bit_range *out) const
{
if (contains_p (other.get_start_bit_offset ())
&& contains_p (other.get_last_bit_offset ()))
{
out->m_start_bit_offset = other.m_start_bit_offset - m_start_bit_offset;
out->m_size_in_bits = other.m_size_in_bits;
return true;
}
else
return false;
}
/* If OTHER intersects this, return true and write
the relative range of OTHER within THIS to *OUT_THIS,
and the relative range of THIS within OTHER to *OUT_OTHER.
Otherwise return false. */
bool
bit_range::intersects_p (const bit_range &other,
bit_range *out_this,
bit_range *out_other) const
{
if (get_start_bit_offset () < other.get_next_bit_offset ()
&& other.get_start_bit_offset () < get_next_bit_offset ())
{
bit_offset_t overlap_start
= MAX (get_start_bit_offset (),
other.get_start_bit_offset ());
bit_offset_t overlap_next
= MIN (get_next_bit_offset (),
other.get_next_bit_offset ());
gcc_assert (overlap_next > overlap_start);
bit_range abs_overlap_bits (overlap_start, overlap_next - overlap_start);
*out_this = abs_overlap_bits - get_start_bit_offset ();
*out_other = abs_overlap_bits - other.get_start_bit_offset ();
return true;
}
else
return false;
}
int
bit_range::cmp (const bit_range &br1, const bit_range &br2)
{
if (int start_cmp = wi::cmps (br1.m_start_bit_offset,
br2.m_start_bit_offset))
return start_cmp;
return wi::cmpu (br1.m_size_in_bits, br2.m_size_in_bits);
}
/* Offset this range by OFFSET. */
bit_range
bit_range::operator- (bit_offset_t offset) const
{
return bit_range (m_start_bit_offset - offset, m_size_in_bits);
}
/* If MASK is a contiguous range of set bits, write them
to *OUT and return true.
Otherwise return false. */
bool
bit_range::from_mask (unsigned HOST_WIDE_INT mask, bit_range *out)
{
unsigned iter_bit_idx = 0;
unsigned HOST_WIDE_INT iter_bit_mask = 1;
/* Find the first contiguous run of set bits in MASK. */
/* Find first set bit in MASK. */
while (iter_bit_idx < HOST_BITS_PER_WIDE_INT)
{
if (mask & iter_bit_mask)
break;
iter_bit_idx++;
iter_bit_mask <<= 1;
}
if (iter_bit_idx == HOST_BITS_PER_WIDE_INT)
/* MASK is zero. */
return false;
unsigned first_set_iter_bit_idx = iter_bit_idx;
unsigned num_set_bits = 1;
iter_bit_idx++;
iter_bit_mask <<= 1;
/* Find next unset bit in MASK. */
while (iter_bit_idx < HOST_BITS_PER_WIDE_INT)
{
if (!(mask & iter_bit_mask))
break;
num_set_bits++;
iter_bit_idx++;
iter_bit_mask <<= 1;
}
if (iter_bit_idx == HOST_BITS_PER_WIDE_INT)
{
*out = bit_range (first_set_iter_bit_idx, num_set_bits);
return true;
}
/* We now have the first contiguous run of set bits in MASK.
Fail if any other bits are set. */
while (iter_bit_idx < HOST_BITS_PER_WIDE_INT)
{
if (mask & iter_bit_mask)
return false;
iter_bit_idx++;
iter_bit_mask <<= 1;
}
*out = bit_range (first_set_iter_bit_idx, num_set_bits);
return true;
}
/* Attempt to convert this bit_range to a byte_range.
Return true if it is possible, writing the result to *OUT.
Otherwise return false. */
bool
bit_range::as_byte_range (byte_range *out) const
{
if (m_start_bit_offset % BITS_PER_UNIT == 0
&& m_size_in_bits % BITS_PER_UNIT == 0)
{
out->m_start_byte_offset = m_start_bit_offset / BITS_PER_UNIT;
out->m_size_in_bytes = m_size_in_bits / BITS_PER_UNIT;
return true;
}
return false;
}
/* Dump this object to PP. */
void
byte_range::dump_to_pp (pretty_printer *pp) const
{
if (m_size_in_bytes == 0)
{
pp_string (pp, "empty");
}
else if (m_size_in_bytes == 1)
{
pp_string (pp, "byte ");
pp_wide_int (pp, m_start_byte_offset, SIGNED);
}
else
{
pp_string (pp, "bytes ");
pp_wide_int (pp, m_start_byte_offset, SIGNED);
pp_string (pp, "-");
pp_wide_int (pp, get_last_byte_offset (), SIGNED);
}
}
/* Dump this object to stderr. */
DEBUG_FUNCTION void
byte_range::dump () const
{
pretty_printer pp;
pp.buffer->stream = stderr;
dump_to_pp (&pp);
pp_newline (&pp);
pp_flush (&pp);
}
/* If OTHER is a subset of this, return true and write
to *OUT the relative range of OTHER within this.
Otherwise return false. */
bool
byte_range::contains_p (const byte_range &other, byte_range *out) const
{
if (contains_p (other.get_start_byte_offset ())
&& contains_p (other.get_last_byte_offset ()))
{
out->m_start_byte_offset = other.m_start_byte_offset - m_start_byte_offset;
out->m_size_in_bytes = other.m_size_in_bytes;
return true;
}
else
return false;
}
/* Return true if THIS and OTHER intersect and write the number
of bytes both buffers overlap to *OUT_NUM_OVERLAP_BYTES.
Otherwise return false. */
bool
byte_range::intersects_p (const byte_range &other,
byte_size_t *out_num_overlap_bytes) const
{
if (get_start_byte_offset () < other.get_next_byte_offset ()
&& other.get_start_byte_offset () < get_next_byte_offset ())
{
byte_offset_t overlap_start = MAX (get_start_byte_offset (),
other.get_start_byte_offset ());
byte_offset_t overlap_next = MIN (get_next_byte_offset (),
other.get_next_byte_offset ());
gcc_assert (overlap_next > overlap_start);
*out_num_overlap_bytes = overlap_next - overlap_start;
return true;
}
else
return false;
}
/* Return true if THIS exceeds OTHER and write the overhanging
byte range to OUT_OVERHANGING_BYTE_RANGE. */
bool
byte_range::exceeds_p (const byte_range &other,
byte_range *out_overhanging_byte_range) const
{
gcc_assert (!empty_p ());
if (other.get_next_byte_offset () < get_next_byte_offset ())
{
/* THIS definitely exceeds OTHER. */
byte_offset_t start = MAX (get_start_byte_offset (),
other.get_next_byte_offset ());
byte_offset_t size = get_next_byte_offset () - start;
gcc_assert (size > 0);
out_overhanging_byte_range->m_start_byte_offset = start;
out_overhanging_byte_range->m_size_in_bytes = size;
return true;
}
else
return false;
}
/* Return true if THIS falls short of OFFSET and write the
byte range fallen short to OUT_FALL_SHORT_BYTES. */
bool
byte_range::falls_short_of_p (byte_offset_t offset,
byte_range *out_fall_short_bytes) const
{
gcc_assert (!empty_p ());
if (get_start_byte_offset () < offset)
{
/* THIS falls short of OFFSET. */
byte_offset_t start = get_start_byte_offset ();
byte_offset_t size = MIN (offset, get_next_byte_offset ()) - start;
gcc_assert (size > 0);
out_fall_short_bytes->m_start_byte_offset = start;
out_fall_short_bytes->m_size_in_bytes = size;
return true;
}
else
return false;
}
/* qsort comparator for byte ranges. */
int
byte_range::cmp (const byte_range &br1, const byte_range &br2)
{
/* Order first by offset. */
if (int start_cmp = wi::cmps (br1.m_start_byte_offset,
br2.m_start_byte_offset))
return start_cmp;
/* ...then by size. */
return wi::cmpu (br1.m_size_in_bytes, br2.m_size_in_bytes);
}
/* class concrete_binding : public binding_key. */
/* Implementation of binding_key::dump_to_pp vfunc for concrete_binding. */
void
concrete_binding::dump_to_pp (pretty_printer *pp, bool) const
{
m_bit_range.dump_to_pp (pp);
}
/* Return true if this binding overlaps with OTHER. */
bool
concrete_binding::overlaps_p (const concrete_binding &other) const
{
if (get_start_bit_offset () < other.get_next_bit_offset ()
&& get_next_bit_offset () > other.get_start_bit_offset ())
return true;
return false;
}
/* Comparator for use by vec<const concrete_binding *>::qsort. */
int
concrete_binding::cmp_ptr_ptr (const void *p1, const void *p2)
{
const concrete_binding *b1 = *(const concrete_binding * const *)p1;
const concrete_binding *b2 = *(const concrete_binding * const *)p2;
return bit_range::cmp (b1->m_bit_range, b2->m_bit_range);
}
/* class symbolic_binding : public binding_key. */
void
symbolic_binding::dump_to_pp (pretty_printer *pp, bool simple) const
{
//binding_key::dump_to_pp (pp, simple);
pp_string (pp, "region: ");
m_region->dump_to_pp (pp, simple);
}
/* Comparator for use by vec<const symbolic_binding *>::qsort. */
int
symbolic_binding::cmp_ptr_ptr (const void *p1, const void *p2)
{
const symbolic_binding *b1 = *(const symbolic_binding * const *)p1;
const symbolic_binding *b2 = *(const symbolic_binding * const *)p2;
return region::cmp_ids (b1->get_region (), b2->get_region ());
}
/* The store is oblivious to the types of the svalues bound within
it: any type can get bound at any location.
Simplify any casts before binding.
For example, if we have:
struct big { int ia[1024]; };
struct big src, dst;
memcpy (&dst, &src, sizeof (struct big));
this reaches us in gimple form as:
MEM <unsigned char[4096]> [(char * {ref-all})&dst]
= MEM <unsigned char[4096]> [(char * {ref-all})&src];
Using cast_region when handling the MEM_REF would give us:
INIT_VAL(CAST_REG(unsigned char[4096], src))
as rhs_sval, but we can fold that into a cast svalue:
CAST(unsigned char[4096], INIT_VAL(src))
We can discard that cast from the svalue when binding it in
the store for "dst", and simply store:
cluster for: dst
key: {kind: direct, start: 0, size: 32768, next: 32768}
value: ‘struct big’ {INIT_VAL(src)}. */
static const svalue *
simplify_for_binding (const svalue *sval)
{
if (const svalue *cast_sval = sval->maybe_undo_cast ())
sval = cast_sval;
return sval;
}
/* class binding_map. */
/* binding_map's copy ctor. */
binding_map::binding_map (const binding_map &other)
: m_map (other.m_map)
{
}
/* binding_map's assignment operator. */
binding_map&
binding_map::operator=(const binding_map &other)
{
/* For now, assume we only ever copy to an empty cluster. */
gcc_assert (m_map.elements () == 0);
for (map_t::iterator iter = other.m_map.begin (); iter != other.m_map.end ();
++iter)
{
const binding_key *key = (*iter).first;
const svalue *sval = (*iter).second;
m_map.put (key, sval);
}
return *this;
}
/* binding_map's equality operator. */
bool
binding_map::operator== (const binding_map &other) const
{
if (m_map.elements () != other.m_map.elements ())
return false;
for (map_t::iterator iter = m_map.begin (); iter != m_map.end (); ++iter)
{
const binding_key *key = (*iter).first;
const svalue *sval = (*iter).second;
const svalue **other_slot
= const_cast <map_t &> (other.m_map).get (key);
if (other_slot == NULL)
return false;
if (sval != *other_slot)
return false;
}
gcc_checking_assert (hash () == other.hash ());
return true;
}
/* Generate a hash value for this binding_map. */
hashval_t
binding_map::hash () const
{
hashval_t result = 0;
for (map_t::iterator iter = m_map.begin (); iter != m_map.end (); ++iter)
{
/* Use a new hasher for each key to avoid depending on the ordering
of keys when accumulating the result. */
inchash::hash hstate;
hstate.add_ptr ((*iter).first);
hstate.add_ptr ((*iter).second);
result ^= hstate.end ();
}
return result;
}
/* Dump a representation of this binding_map to PP.
SIMPLE controls how values and regions are to be printed.
If MULTILINE, then split the dump over multiple lines and
use whitespace for readability, otherwise put all on one line. */
void
binding_map::dump_to_pp (pretty_printer *pp, bool simple,
bool multiline) const
{
auto_vec <const binding_key *> binding_keys;
for (map_t::iterator iter = m_map.begin ();
iter != m_map.end (); ++iter)
{
const binding_key *key = (*iter).first;
binding_keys.safe_push (key);
}
binding_keys.qsort (binding_key::cmp_ptrs);
const binding_key *key;
unsigned i;
FOR_EACH_VEC_ELT (binding_keys, i, key)
{
const svalue *value = *const_cast <map_t &> (m_map).get (key);
if (multiline)
{
pp_string (pp, " key: {");
key->dump_to_pp (pp, simple);
pp_string (pp, "}");
pp_newline (pp);
pp_string (pp, " value: ");
if (tree t = value->get_type ())
dump_quoted_tree (pp, t);
pp_string (pp, " {");
value->dump_to_pp (pp, simple);
pp_string (pp, "}");
pp_newline (pp);
}
else
{
if (i > 0)
pp_string (pp, ", ");
pp_string (pp, "binding key: {");
key->dump_to_pp (pp, simple);
pp_string (pp, "}, value: {");
value->dump_to_pp (pp, simple);
pp_string (pp, "}");
}
}
}
/* Dump a multiline representation of this binding_map to stderr. */
DEBUG_FUNCTION void
binding_map::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, true);
pp_newline (&pp);
pp_flush (&pp);
}
/* Return a new json::object of the form
{KEY_DESC : SVALUE_DESC,
...for the various key/value pairs in this binding_map}. */
json::object *
binding_map::to_json () const
{
json::object *map_obj = new json::object ();
auto_vec <const binding_key *> binding_keys;
for (map_t::iterator iter = m_map.begin ();
iter != m_map.end (); ++iter)
{
const binding_key *key = (*iter).first;
binding_keys.safe_push (key);
}
binding_keys.qsort (binding_key::cmp_ptrs);
const binding_key *key;
unsigned i;
FOR_EACH_VEC_ELT (binding_keys, i, key)
{
const svalue *value = *const_cast <map_t &> (m_map).get (key);
label_text key_desc = key->get_desc ();
map_obj->set (key_desc.get (), value->to_json ());
}
return map_obj;
}
/* Comparator for imposing an order on binding_maps. */
int
binding_map::cmp (const binding_map &map1, const binding_map &map2)
{
if (int count_cmp = map1.elements () - map2.elements ())
return count_cmp;
auto_vec <const binding_key *> keys1 (map1.elements ());
for (map_t::iterator iter = map1.begin ();
iter != map1.end (); ++iter)
keys1.quick_push ((*iter).first);
keys1.qsort (binding_key::cmp_ptrs);
auto_vec <const binding_key *> keys2 (map2.elements ());
for (map_t::iterator iter = map2.begin ();
iter != map2.end (); ++iter)
keys2.quick_push ((*iter).first);
keys2.qsort (binding_key::cmp_ptrs);
for (size_t i = 0; i < keys1.length (); i++)
{
const binding_key *k1 = keys1[i];
const binding_key *k2 = keys2[i];
if (int key_cmp = binding_key::cmp (k1, k2))
return key_cmp;
gcc_assert (k1 == k2);
if (int sval_cmp = svalue::cmp_ptr (map1.get (k1), map2.get (k2)))
return sval_cmp;
}
return 0;
}
/* Get the child region of PARENT_REG based upon INDEX within a
CONSTRUCTOR. */
static const region *
get_subregion_within_ctor (const region *parent_reg, tree index,
region_model_manager *mgr)
{
switch (TREE_CODE (index))
{
default:
gcc_unreachable ();
case INTEGER_CST:
{
const svalue *index_sval
= mgr->get_or_create_constant_svalue (index);
return mgr->get_element_region (parent_reg,
TREE_TYPE (parent_reg->get_type ()),
index_sval);
}
break;
case FIELD_DECL:
return mgr->get_field_region (parent_reg, index);
}
}
/* Get the svalue for VAL, a non-CONSTRUCTOR value within a CONSTRUCTOR. */
static const svalue *
get_svalue_for_ctor_val (tree val, region_model_manager *mgr)
{
/* Reuse the get_rvalue logic from region_model. */
region_model m (mgr);
return m.get_rvalue (path_var (val, 0), NULL);
}
/* Bind values from CONSTRUCTOR to this map, relative to
PARENT_REG's relationship to its base region.
Return true if successful, false if there was a problem (e.g. due
to hitting a complexity limit). */
bool
binding_map::apply_ctor_to_region (const region *parent_reg, tree ctor,
region_model_manager *mgr)
{
gcc_assert (parent_reg);
gcc_assert (TREE_CODE (ctor) == CONSTRUCTOR);
unsigned ix;
tree index;
tree val;
tree parent_type = parent_reg->get_type ();
tree field;
if (TREE_CODE (parent_type) == RECORD_TYPE)
field = TYPE_FIELDS (parent_type);
else
field = NULL_TREE;
FOR_EACH_CONSTRUCTOR_ELT (CONSTRUCTOR_ELTS (ctor), ix, index, val)
{
if (!index)
{
/* If index is NULL, then iterate through the fields for
a RECORD_TYPE, or use an INTEGER_CST otherwise.
Compare with similar logic in output_constructor. */
if (field)
{
index = field;
field = DECL_CHAIN (field);
}
else
index = build_int_cst (integer_type_node, ix);
}
else if (TREE_CODE (index) == RANGE_EXPR)
{
tree min_index = TREE_OPERAND (index, 0);
tree max_index = TREE_OPERAND (index, 1);
if (min_index == max_index)
{
if (!apply_ctor_pair_to_child_region (parent_reg, mgr,
min_index, val))
return false;
}
else
{
if (!apply_ctor_val_to_range (parent_reg, mgr,
min_index, max_index, val))
return false;
}
continue;
}
if (!apply_ctor_pair_to_child_region (parent_reg, mgr, index, val))
return false;
}
return true;
}
/* Bind the value VAL into the range of elements within PARENT_REF
from MIN_INDEX to MAX_INDEX (including endpoints).
For use in handling RANGE_EXPR within a CONSTRUCTOR.
Return true if successful, false if there was a problem (e.g. due
to hitting a complexity limit). */
bool
binding_map::apply_ctor_val_to_range (const region *parent_reg,
region_model_manager *mgr,
tree min_index, tree max_index,
tree val)
{
gcc_assert (TREE_CODE (min_index) == INTEGER_CST);
gcc_assert (TREE_CODE (max_index) == INTEGER_CST);
/* Generate a binding key for the range. */
const region *min_element
= get_subregion_within_ctor (parent_reg, min_index, mgr);
const region *max_element
= get_subregion_within_ctor (parent_reg, max_index, mgr);
region_offset min_offset = min_element->get_offset (mgr);
if (min_offset.symbolic_p ())
return false;
bit_offset_t start_bit_offset = min_offset.get_bit_offset ();
store_manager *smgr = mgr->get_store_manager ();
const binding_key *max_element_key = binding_key::make (smgr, max_element);
if (max_element_key->symbolic_p ())
return false;
const concrete_binding *max_element_ckey
= max_element_key->dyn_cast_concrete_binding ();
bit_size_t range_size_in_bits
= max_element_ckey->get_next_bit_offset () - start_bit_offset;
const concrete_binding *range_key
= smgr->get_concrete_binding (start_bit_offset, range_size_in_bits);
if (range_key->symbolic_p ())
return false;
/* Get the value. */
if (TREE_CODE (val) == CONSTRUCTOR)
return false;
const svalue *sval = get_svalue_for_ctor_val (val, mgr);
/* Bind the value to the range. */
put (range_key, sval);
return true;
}
/* Bind the value VAL into INDEX within PARENT_REF.
For use in handling a pair of entries within a CONSTRUCTOR.
Return true if successful, false if there was a problem (e.g. due
to hitting a complexity limit). */
bool
binding_map::apply_ctor_pair_to_child_region (const region *parent_reg,
region_model_manager *mgr,
tree index, tree val)
{
const region *child_reg
= get_subregion_within_ctor (parent_reg, index, mgr);
if (TREE_CODE (val) == CONSTRUCTOR)
return apply_ctor_to_region (child_reg, val, mgr);
else
{
const svalue *sval = get_svalue_for_ctor_val (val, mgr);
const binding_key *k
= binding_key::make (mgr->get_store_manager (), child_reg);
/* Handle the case where we have an unknown size for child_reg
(e.g. due to it being a trailing field with incomplete array
type. */
if (!k->concrete_p ())
{
/* Assume that sval has a well-defined size for this case. */
tree sval_type = sval->get_type ();
gcc_assert (sval_type);
HOST_WIDE_INT sval_byte_size = int_size_in_bytes (sval_type);
gcc_assert (sval_byte_size != -1);
bit_size_t sval_bit_size = sval_byte_size * BITS_PER_UNIT;
/* Get offset of child relative to base region. */
region_offset child_base_offset = child_reg->get_offset (mgr);
if (child_base_offset.symbolic_p ())
return false;
/* Convert to an offset relative to the parent region. */
region_offset parent_base_offset = parent_reg->get_offset (mgr);
gcc_assert (!parent_base_offset.symbolic_p ());
bit_offset_t child_parent_offset
= (child_base_offset.get_bit_offset ()
- parent_base_offset.get_bit_offset ());
/* Create a concrete key for the child within the parent. */
k = mgr->get_store_manager ()->get_concrete_binding
(child_parent_offset, sval_bit_size);
}
gcc_assert (k->concrete_p ());
put (k, sval);
return true;
}
}
/* Populate OUT with all bindings within this map that overlap KEY. */
void
binding_map::get_overlapping_bindings (const binding_key *key,
auto_vec<const binding_key *> *out)
{
for (auto iter : *this)
{
const binding_key *iter_key = iter.first;
if (const concrete_binding *ckey
= key->dyn_cast_concrete_binding ())
{
if (const concrete_binding *iter_ckey
= iter_key->dyn_cast_concrete_binding ())
{
if (ckey->overlaps_p (*iter_ckey))
out->safe_push (iter_key);
}
else
{
/* Assume overlap. */
out->safe_push (iter_key);
}
}
else
{
/* Assume overlap. */
out->safe_push (iter_key);
}
}
}
/* Remove, truncate, and/or split any bindings within this map that
overlap DROP_KEY.
For example, if we have:
+------------------------------------+
| old binding |
+------------------------------------+
which is to be overwritten with:
.......+----------------------+.......
.......| new binding |.......
.......+----------------------+.......
this function "cuts a hole" out of the old binding:
+------+......................+------+
|prefix| hole for new binding |suffix|
+------+......................+------+
into which the new binding can be added without
overlapping the prefix or suffix.
The prefix and suffix (if added) will be bound to the pertinent
parts of the value of the old binding.
For example, given:
struct s5
{
char arr[8];
};
void test_5 (struct s5 *p)
{
struct s5 f = *p;
f.arr[3] = 42;
}
then after the "f = *p;" we have:
cluster for: f: INIT_VAL((*INIT_VAL(p_33(D))))
and at the "f.arr[3] = 42;" we remove the bindings overlapping
"f.arr[3]", replacing it with a prefix (bytes 0-2) and suffix (bytes 4-7)
giving:
cluster for: f
key: {bytes 0-2}
value: {BITS_WITHIN(bytes 0-2, inner_val: INIT_VAL((*INIT_VAL(p_33(D))).arr))}
key: {bytes 4-7}
value: {BITS_WITHIN(bytes 4-7, inner_val: INIT_VAL((*INIT_VAL(p_33(D))).arr))}
punching a hole into which the new value can be written at byte 3:
cluster for: f
key: {bytes 0-2}
value: {BITS_WITHIN(bytes 0-2, inner_val: INIT_VAL((*INIT_VAL(p_33(D))).arr))}
key: {byte 3}
value: 'char' {(char)42}
key: {bytes 4-7}
value: {BITS_WITHIN(bytes 4-7, inner_val: INIT_VAL((*INIT_VAL(p_33(D))).arr))}
If UNCERTAINTY is non-NULL, use it to record any svalues that
were removed, as being maybe-bound.
If ALWAYS_OVERLAP, then assume that DROP_KEY can overlap anything
in the map, due to one or both of the underlying clusters being
symbolic (but not the same symbolic region). Hence even if DROP_KEY is a
concrete binding it could actually be referring to the same memory as
distinct concrete bindings in the map. Remove all bindings, but
register any svalues with *UNCERTAINTY. */
void
binding_map::remove_overlapping_bindings (store_manager *mgr,
const binding_key *drop_key,
uncertainty_t *uncertainty,
bool always_overlap)
{
/* Get the bindings of interest within this map. */
auto_vec<const binding_key *> bindings;
if (always_overlap)
for (auto iter : *this)
bindings.safe_push (iter.first); /* Add all bindings. */
else
/* Just add overlapping bindings. */
get_overlapping_bindings (drop_key, &bindings);
unsigned i;
const binding_key *iter_binding;
FOR_EACH_VEC_ELT (bindings, i, iter_binding)
{
/* Record any svalues that were removed to *UNCERTAINTY as being
maybe-bound, provided at least some part of the binding is symbolic.
Specifically, if at least one of the bindings is symbolic, or we
have ALWAYS_OVERLAP for the case where we have possibly aliasing
regions, then we don't know that the svalue has been overwritten,
and should record that to *UNCERTAINTY.
However, if we have concrete keys accessing within the same symbolic
region, then we *know* that the symbolic region has been overwritten,
so we don't record it to *UNCERTAINTY, as this could be a genuine
leak. */
const svalue *old_sval = get (iter_binding);
if (uncertainty
&& (drop_key->symbolic_p ()
|| iter_binding->symbolic_p ()
|| always_overlap))
uncertainty->on_maybe_bound_sval (old_sval);
/* Begin by removing the old binding. */
m_map.remove (iter_binding);
/* Don't attempt to handle prefixes/suffixes for the
"always_overlap" case; everything's being removed. */
if (always_overlap)
continue;
/* Now potentially add the prefix and suffix. */
if (const concrete_binding *drop_ckey
= drop_key->dyn_cast_concrete_binding ())
if (const concrete_binding *iter_ckey
= iter_binding->dyn_cast_concrete_binding ())
{
gcc_assert (drop_ckey->overlaps_p (*iter_ckey));
const bit_range &drop_bits = drop_ckey->get_bit_range ();
const bit_range &iter_bits = iter_ckey->get_bit_range ();
if (iter_bits.get_start_bit_offset ()
< drop_bits.get_start_bit_offset ())
{
/* We have a truncated prefix. */
bit_range prefix_bits (iter_bits.get_start_bit_offset (),
(drop_bits.get_start_bit_offset ()
- iter_bits.get_start_bit_offset ()));
const concrete_binding *prefix_key
= mgr->get_concrete_binding (prefix_bits);
bit_range rel_prefix (0, prefix_bits.m_size_in_bits);
const svalue *prefix_sval
= old_sval->extract_bit_range (NULL_TREE,
rel_prefix,
mgr->get_svalue_manager ());
m_map.put (prefix_key, prefix_sval);
}
if (iter_bits.get_next_bit_offset ()
> drop_bits.get_next_bit_offset ())
{
/* We have a truncated suffix. */
bit_range suffix_bits (drop_bits.get_next_bit_offset (),
(iter_bits.get_next_bit_offset ()
- drop_bits.get_next_bit_offset ()));
const concrete_binding *suffix_key
= mgr->get_concrete_binding (suffix_bits);
bit_range rel_suffix (drop_bits.get_next_bit_offset ()
- iter_bits.get_start_bit_offset (),
suffix_bits.m_size_in_bits);
const svalue *suffix_sval
= old_sval->extract_bit_range (NULL_TREE,
rel_suffix,
mgr->get_svalue_manager ());
m_map.put (suffix_key, suffix_sval);
}
}
}
}
/* class binding_cluster. */
binding_cluster::binding_cluster (const region *base_region)
: m_base_region (base_region), m_map (),
m_escaped (false), m_touched (false)
{
}
/* binding_cluster's copy ctor. */
binding_cluster::binding_cluster (const binding_cluster &other)
: m_base_region (other.m_base_region), m_map (other.m_map),
m_escaped (other.m_escaped), m_touched (other.m_touched)
{
}
/* binding_cluster's assignment operator. */
binding_cluster&
binding_cluster::operator= (const binding_cluster &other)
{
gcc_assert (m_base_region == other.m_base_region);
m_map = other.m_map;
m_escaped = other.m_escaped;
m_touched = other.m_touched;
return *this;
}
/* binding_cluster's equality operator. */
bool
binding_cluster::operator== (const binding_cluster &other) const
{
if (m_map != other.m_map)
return false;
if (m_base_region != other.m_base_region)
return false;
if (m_escaped != other.m_escaped)
return false;
if (m_touched != other.m_touched)
return false;
gcc_checking_assert (hash () == other.hash ());
return true;
}
/* Generate a hash value for this binding_cluster. */
hashval_t
binding_cluster::hash () const
{
return m_map.hash ();
}
/* Return true if this binding_cluster is symbolic
i.e. its base region is symbolic. */
bool
binding_cluster::symbolic_p () const
{
return m_base_region->get_kind () == RK_SYMBOLIC;
}
/* Dump a representation of this binding_cluster to PP.
SIMPLE controls how values and regions are to be printed.
If MULTILINE, then split the dump over multiple lines and
use whitespace for readability, otherwise put all on one line. */
void
binding_cluster::dump_to_pp (pretty_printer *pp, bool simple,
bool multiline) const
{
if (m_escaped)
{
if (multiline)
{
pp_string (pp, " ESCAPED");
pp_newline (pp);
}
else
pp_string (pp, "(ESCAPED)");
}
if (m_touched)
{
if (multiline)
{
pp_string (pp, " TOUCHED");
pp_newline (pp);
}
else
pp_string (pp, "(TOUCHED)");
}
m_map.dump_to_pp (pp, simple, multiline);
}
/* Dump a multiline representation of this binding_cluster to stderr. */
DEBUG_FUNCTION void
binding_cluster::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;
pp_string (&pp, " cluster for: ");
m_base_region->dump_to_pp (&pp, simple);
pp_string (&pp, ": ");
pp_newline (&pp);
dump_to_pp (&pp, simple, true);
pp_newline (&pp);
pp_flush (&pp);
}
/* Assert that this object is valid. */
void
binding_cluster::validate () const
{
int num_symbolic = 0;
int num_concrete = 0;
for (auto iter : m_map)
{
if (iter.first->symbolic_p ())
num_symbolic++;
else
num_concrete++;
}
/* We shouldn't have more than one symbolic key per cluster
(or one would have clobbered the other). */
gcc_assert (num_symbolic < 2);
/* We can't have both concrete and symbolic keys. */
gcc_assert (num_concrete == 0 || num_symbolic == 0);
}
/* Return a new json::object of the form
{"escaped": true/false,
"touched": true/false,
"map" : object for the binding_map. */
json::object *
binding_cluster::to_json () const
{
json::object *cluster_obj = new json::object ();
cluster_obj->set ("escaped", new json::literal (m_escaped));
cluster_obj->set ("touched", new json::literal (m_touched));
cluster_obj->set ("map", m_map.to_json ());
return cluster_obj;
}
/* Add a binding of SVAL of kind KIND to REG, unpacking SVAL if it is a
compound_sval. */
void
binding_cluster::bind (store_manager *mgr,
const region *reg, const svalue *sval)
{
if (const compound_svalue *compound_sval
= sval->dyn_cast_compound_svalue ())
{
bind_compound_sval (mgr, reg, compound_sval);
return;
}
const binding_key *binding = binding_key::make (mgr, reg);
bind_key (binding, sval);
}
/* Bind SVAL to KEY.
Unpacking of compound_svalues should already have been done by the
time this is called. */
void
binding_cluster::bind_key (const binding_key *key, const svalue *sval)
{
gcc_assert (sval->get_kind () != SK_COMPOUND);
m_map.put (key, sval);
if (key->symbolic_p ())
m_touched = true;
}
/* Subroutine of binding_cluster::bind.
Unpack compound_svals when binding them, so that we bind them
element-wise. */
void
binding_cluster::bind_compound_sval (store_manager *mgr,
const region *reg,
const compound_svalue *compound_sval)
{
region_offset reg_offset
= reg->get_offset (mgr->get_svalue_manager ());
if (reg_offset.symbolic_p ())
{
m_touched = true;
clobber_region (mgr, reg);
return;
}
for (map_t::iterator iter = compound_sval->begin ();
iter != compound_sval->end (); ++iter)
{
const binding_key *iter_key = (*iter).first;
const svalue *iter_sval = (*iter).second;
if (const concrete_binding *concrete_key
= iter_key->dyn_cast_concrete_binding ())
{
bit_offset_t effective_start
= (concrete_key->get_start_bit_offset ()
+ reg_offset.get_bit_offset ());
const concrete_binding *effective_concrete_key
= mgr->get_concrete_binding (effective_start,
concrete_key->get_size_in_bits ());
bind_key (effective_concrete_key, iter_sval);
}
else
gcc_unreachable ();
}
}
/* Remove all bindings overlapping REG within this cluster. */
void
binding_cluster::clobber_region (store_manager *mgr, const region *reg)
{
remove_overlapping_bindings (mgr, reg, NULL);
}
/* Remove any bindings for REG within this cluster. */
void
binding_cluster::purge_region (store_manager *mgr, const region *reg)
{
gcc_assert (reg->get_kind () == RK_DECL);
const binding_key *binding
= binding_key::make (mgr, const_cast<region *> (reg));
m_map.remove (binding);
}
/* Clobber REG and fill it with repeated copies of SVAL. */
void
binding_cluster::fill_region (store_manager *mgr,
const region *reg,
const svalue *sval)
{
clobber_region (mgr, reg);
region_model_manager *sval_mgr = mgr->get_svalue_manager ();
const svalue *byte_size_sval = reg->get_byte_size_sval (sval_mgr);
const svalue *fill_sval
= sval_mgr->get_or_create_repeated_svalue (reg->get_type (),
byte_size_sval, sval);
bind (mgr, reg, fill_sval);
}
/* Clobber REG within this cluster and fill it with zeroes. */
void
binding_cluster::zero_fill_region (store_manager *mgr, const region *reg)
{
region_model_manager *sval_mgr = mgr->get_svalue_manager ();
const svalue *zero_sval = sval_mgr->get_or_create_int_cst (char_type_node, 0);
fill_region (mgr, reg, zero_sval);
}
/* Mark REG_TO_BIND within this cluster as being unknown.
Remove any bindings overlapping REG_FOR_OVERLAP.
If UNCERTAINTY is non-NULL, use it to record any svalues that
had bindings to them removed, as being maybe-bound.
REG_TO_BIND and REG_FOR_OVERLAP are the same for
store::mark_region_as_unknown, but are different in
store::set_value's alias handling, for handling the case where
we have a write to a symbolic REG_FOR_OVERLAP. */
void
binding_cluster::mark_region_as_unknown (store_manager *mgr,
const region *reg_to_bind,
const region *reg_for_overlap,
uncertainty_t *uncertainty)
{
remove_overlapping_bindings (mgr, reg_for_overlap, uncertainty);
/* Add a default binding to "unknown". */
region_model_manager *sval_mgr = mgr->get_svalue_manager ();
const svalue *sval
= sval_mgr->get_or_create_unknown_svalue (reg_to_bind->get_type ());
bind (mgr, reg_to_bind, sval);
}
/* Purge state involving SVAL. */
void
binding_cluster::purge_state_involving (const svalue *sval,
region_model_manager *sval_mgr)
{
auto_vec<const binding_key *> to_remove;
auto_vec<std::pair<const binding_key *, tree> > to_make_unknown;
for (auto iter : m_map)
{
const binding_key *iter_key = iter.first;
if (const symbolic_binding *symbolic_key
= iter_key->dyn_cast_symbolic_binding ())
{
const region *reg = symbolic_key->get_region ();
if (reg->involves_p (sval))
to_remove.safe_push (iter_key);
}
const svalue *iter_sval = iter.second;
if (iter_sval->involves_p (sval))
to_make_unknown.safe_push (std::make_pair(iter_key,
iter_sval->get_type ()));
}
for (auto iter : to_remove)
{
m_map.remove (iter);
m_touched = true;
}
for (auto iter : to_make_unknown)
{
const svalue *new_sval
= sval_mgr->get_or_create_unknown_svalue (iter.second);
m_map.put (iter.first, new_sval);
}
}
/* Get any SVAL bound to REG within this cluster via kind KIND,
without checking parent regions of REG. */
const svalue *
binding_cluster::get_binding (store_manager *mgr,
const region *reg) const
{
const binding_key *reg_binding = binding_key::make (mgr, reg);
const svalue *sval = m_map.get (reg_binding);
if (sval)
{
/* If we have a struct with a single field, then the binding of
the field will equal that of the struct, and looking up e.g.
PARENT_REG.field within:
cluster for PARENT_REG: INIT_VAL(OTHER_REG)
will erroneously return INIT_VAL(OTHER_REG), rather than
SUB_VALUE(INIT_VAL(OTHER_REG), FIELD) == INIT_VAL(OTHER_REG.FIELD).
Fix this issue by iterating upwards whilst the bindings are equal,
expressing the lookups as subvalues.
We have to gather a list of subregion accesses, then walk it
in reverse to get the subvalues. */
auto_vec<const region *> regions;
while (const region *parent_reg = reg->get_parent_region ())
{
const binding_key *parent_reg_binding
= binding_key::make (mgr, parent_reg);
if (parent_reg_binding == reg_binding
&& sval->get_type ()
&& reg->get_type ()
&& sval->get_type () != reg->get_type ())
{
regions.safe_push (reg);
reg = parent_reg;
}
else
break;
}
if (sval->get_type ()
&& reg->get_type ()
&& sval->get_type () == reg->get_type ())
{
unsigned i;
const region *iter_reg;
FOR_EACH_VEC_ELT_REVERSE (regions, i, iter_reg)
{
region_model_manager *rmm_mgr = mgr->get_svalue_manager ();
sval = rmm_mgr->get_or_create_sub_svalue (iter_reg->get_type (),
sval, iter_reg);
}
}
}
return sval;
}
/* Get any SVAL bound to REG within this cluster,
either directly for REG, or recursively checking for bindings within
parent regions and extracting subvalues if need be. */
const svalue *
binding_cluster::get_binding_recursive (store_manager *mgr,
const region *reg) const
{
if (const svalue *sval = get_binding (mgr, reg))
return sval;
if (reg != m_base_region)
if (const region *parent_reg = reg->get_parent_region ())
if (const svalue *parent_sval
= get_binding_recursive (mgr, parent_reg))
{
/* Extract child svalue from parent svalue. */
region_model_manager *rmm_mgr = mgr->get_svalue_manager ();
return rmm_mgr->get_or_create_sub_svalue (reg->get_type (),
parent_sval, reg);
}
return NULL;
}
/* Get any value bound for REG within this cluster. */
const svalue *
binding_cluster::get_any_binding (store_manager *mgr,
const region *reg) const
{
/* Look for a direct binding. */
if (const svalue *direct_sval
= get_binding_recursive (mgr, reg))
return direct_sval;
/* If we had a write to a cluster of unknown size, we might
have a self-binding of the whole base region with an svalue,
where the base region is symbolic.
Handle such cases by returning sub_svalue instances. */
if (const svalue *cluster_sval = maybe_get_simple_value (mgr))
{
/* Extract child svalue from parent svalue. */
region_model_manager *rmm_mgr = mgr->get_svalue_manager ();
return rmm_mgr->get_or_create_sub_svalue (reg->get_type (),
cluster_sval, reg);
}
/* If this cluster has been touched by a symbolic write, then the content
of any subregion not currently specifically bound is "UNKNOWN". */
if (m_touched)
{
region_model_manager *rmm_mgr = mgr->get_svalue_manager ();
return rmm_mgr->get_or_create_unknown_svalue (reg->get_type ());
}
/* Alternatively, if this is a symbolic read and the cluster has any bindings,
then we don't know if we're reading those values or not, so the result
is also "UNKNOWN". */
if (reg->get_offset (mgr->get_svalue_manager ()).symbolic_p ()
&& m_map.elements () > 0)
{
region_model_manager *rmm_mgr = mgr->get_svalue_manager ();
return rmm_mgr->get_or_create_unknown_svalue (reg->get_type ());
}
if (const svalue *compound_sval = maybe_get_compound_binding (mgr, reg))
return compound_sval;
/* Otherwise, the initial value, or uninitialized. */
return NULL;
}
/* Attempt to get a compound_svalue for the bindings within the cluster
affecting REG (which could be the base region itself).
Create a compound_svalue with the subset of bindings the affect REG,
offsetting them so that the offsets are relative to the start of REG
within the cluster.
For example, REG could be one element within an array of structs.
Return the resulting compound_svalue, or NULL if there's a problem. */
const svalue *
binding_cluster::maybe_get_compound_binding (store_manager *mgr,
const region *reg) const
{
region_offset cluster_offset
= m_base_region->get_offset (mgr->get_svalue_manager ());
if (cluster_offset.symbolic_p ())
return NULL;
region_offset reg_offset = reg->get_offset (mgr->get_svalue_manager ());
if (reg_offset.symbolic_p ())
return NULL;
region_model_manager *sval_mgr = mgr->get_svalue_manager ();
/* We will a build the result map in two parts:
(a) result_map, holding the concrete keys from this cluster,
(b) default_map, holding the initial values for the region
(e.g. uninitialized, initializer values, or zero), unless this
cluster has been touched.
We will populate (a), and as we do, clobber (b), trimming and
splitting its bindings as necessary.
Finally, we will merge (b) into (a), giving a concrete map
that merges both the initial values and the bound values from
the binding_cluster.
Doing it this way reduces N for the O(N^2) intersection-finding,
perhaps we should have a spatial-organized data structure for
concrete keys, though. */
binding_map result_map;
binding_map default_map;
/* Set up default values in default_map. */
const svalue *default_sval;
if (m_touched)
default_sval = sval_mgr->get_or_create_unknown_svalue (reg->get_type ());
else
default_sval = sval_mgr->get_or_create_initial_value (reg);
const binding_key *default_key = binding_key::make (mgr, reg);
default_map.put (default_key, default_sval);
for (map_t::iterator iter = m_map.begin (); iter != m_map.end (); ++iter)
{
const binding_key *key = (*iter).first;
const svalue *sval = (*iter).second;
if (const concrete_binding *concrete_key
= key->dyn_cast_concrete_binding ())
{
const bit_range &bound_range = concrete_key->get_bit_range ();
bit_size_t reg_bit_size;
if (!reg->get_bit_size (&reg_bit_size))
return NULL;
bit_range reg_range (reg_offset.get_bit_offset (),
reg_bit_size);
/* Skip bindings that are outside the bit range of REG. */
if (!bound_range.intersects_p (reg_range))
continue;
/* We shouldn't have an exact match; that should have been
handled already. */
gcc_assert (!(reg_range == bound_range));
bit_range subrange (0, 0);
if (reg_range.contains_p (bound_range, &subrange))
{
/* We have a bound range fully within REG.
Add it to map, offsetting accordingly. */
/* Get offset of KEY relative to REG, rather than to
the cluster. */
const concrete_binding *offset_concrete_key
= mgr->get_concrete_binding (subrange);
result_map.put (offset_concrete_key, sval);
/* Clobber default_map, removing/trimming/spliting where
it overlaps with offset_concrete_key. */
default_map.remove_overlapping_bindings (mgr,
offset_concrete_key,
NULL, false);
}
else if (bound_range.contains_p (reg_range, &subrange))
{
/* REG is fully within the bound range, but
is not equal to it; we're extracting a subvalue. */
return sval->extract_bit_range (reg->get_type (),
subrange,
mgr->get_svalue_manager ());
}
else
{
/* REG and the bound range partially overlap. */
bit_range reg_subrange (0, 0);
bit_range bound_subrange (0, 0);
reg_range.intersects_p (bound_range,
&reg_subrange, &bound_subrange);
/* Get the bits from the bound value for the bits at the
intersection (relative to the bound value). */
const svalue *overlap_sval
= sval->extract_bit_range (NULL_TREE,
bound_subrange,
mgr->get_svalue_manager ());
/* Get key for overlap, relative to the REG. */
const concrete_binding *overlap_concrete_key
= mgr->get_concrete_binding (reg_subrange);
result_map.put (overlap_concrete_key, overlap_sval);
/* Clobber default_map, removing/trimming/spliting where
it overlaps with overlap_concrete_key. */
default_map.remove_overlapping_bindings (mgr,
overlap_concrete_key,
NULL, false);
}
}
else
/* Can't handle symbolic bindings. */
return NULL;
}
if (result_map.elements () == 0)
return NULL;
/* Merge any bindings from default_map into result_map. */
for (auto iter : default_map)
{
const binding_key *key = iter.first;
const svalue *sval = iter.second;
result_map.put (key, sval);
}
return sval_mgr->get_or_create_compound_svalue (reg->get_type (), result_map);
}
/* Remove, truncate, and/or split any bindings within this map that
could overlap REG.
If REG's base region or this cluster is symbolic and they're different
base regions, then remove everything in this cluster's map, on the
grounds that REG could be referring to the same memory as anything
in the map.
If UNCERTAINTY is non-NULL, use it to record any svalues that
were removed, as being maybe-bound. */
void
binding_cluster::remove_overlapping_bindings (store_manager *mgr,
const region *reg,
uncertainty_t *uncertainty)
{
const binding_key *reg_binding = binding_key::make (mgr, reg);
const region *cluster_base_reg = get_base_region ();
const region *other_base_reg = reg->get_base_region ();
/* If at least one of the base regions involved is symbolic, and they're
not the same base region, then consider everything in the map as
potentially overlapping with reg_binding (even if it's a concrete
binding and things in the map are concrete - they could be referring
to the same memory when the symbolic base regions are taken into
account). */
bool always_overlap = (cluster_base_reg != other_base_reg
&& (cluster_base_reg->get_kind () == RK_SYMBOLIC
|| other_base_reg->get_kind () == RK_SYMBOLIC));
m_map.remove_overlapping_bindings (mgr, reg_binding, uncertainty,
always_overlap);
}
/* Attempt to merge CLUSTER_A and CLUSTER_B into OUT_CLUSTER, using
MGR and MERGER.
Return true if they can be merged, false otherwise. */
bool
binding_cluster::can_merge_p (const binding_cluster *cluster_a,
const binding_cluster *cluster_b,
binding_cluster *out_cluster,
store *out_store,
store_manager *mgr,
model_merger *merger)
{
gcc_assert (out_cluster);
/* Merge flags ("ESCAPED" and "TOUCHED") by setting the merged flag to
true if either of the inputs is true. */
if ((cluster_a && cluster_a->m_escaped)
|| (cluster_b && cluster_b->m_escaped))
out_cluster->m_escaped = true;
if ((cluster_a && cluster_a->m_touched)
|| (cluster_b && cluster_b->m_touched))
out_cluster->m_touched = true;
/* At least one of CLUSTER_A and CLUSTER_B are non-NULL, but either
could be NULL. Handle these cases. */
if (cluster_a == NULL)
{
gcc_assert (cluster_b != NULL);
gcc_assert (cluster_b->m_base_region == out_cluster->m_base_region);
out_cluster->make_unknown_relative_to (cluster_b, out_store, mgr);
return true;
}
if (cluster_b == NULL)
{
gcc_assert (cluster_a != NULL);
gcc_assert (cluster_a->m_base_region == out_cluster->m_base_region);
out_cluster->make_unknown_relative_to (cluster_a, out_store, mgr);
return true;
}
/* The "both inputs are non-NULL" case. */
gcc_assert (cluster_a != NULL && cluster_b != NULL);
gcc_assert (cluster_a->m_base_region == out_cluster->m_base_region);
gcc_assert (cluster_b->m_base_region == out_cluster->m_base_region);
hash_set<const binding_key *> keys;
for (map_t::iterator iter_a = cluster_a->m_map.begin ();
iter_a != cluster_a->m_map.end (); ++iter_a)
{
const binding_key *key_a = (*iter_a).first;
keys.add (key_a);
}
for (map_t::iterator iter_b = cluster_b->m_map.begin ();
iter_b != cluster_b->m_map.end (); ++iter_b)
{
const binding_key *key_b = (*iter_b).first;
keys.add (key_b);
}
int num_symbolic_keys = 0;
int num_concrete_keys = 0;
for (hash_set<const binding_key *>::iterator iter = keys.begin ();
iter != keys.end (); ++iter)
{
region_model_manager *sval_mgr = mgr->get_svalue_manager ();
const binding_key *key = *iter;
const svalue *sval_a = cluster_a->get_any_value (key);
const svalue *sval_b = cluster_b->get_any_value (key);
if (key->symbolic_p ())
num_symbolic_keys++;
else
num_concrete_keys++;
if (sval_a == sval_b)
{
gcc_assert (sval_a);
out_cluster->m_map.put (key, sval_a);
continue;
}
else if (sval_a && sval_b)
{
if (const svalue *merged_sval
= sval_a->can_merge_p (sval_b, sval_mgr, merger))
{
out_cluster->m_map.put (key, merged_sval);
continue;
}
/* Merger of the svalues failed. Reject merger of the cluster. */
return false;
}
/* If we get here, then one cluster binds this key and the other
doesn't; merge them as "UNKNOWN". */
gcc_assert (sval_a || sval_b);
const svalue *bound_sval = sval_a ? sval_a : sval_b;
tree type = bound_sval->get_type ();
const svalue *unknown_sval
= mgr->get_svalue_manager ()->get_or_create_unknown_svalue (type);
/* ...but reject the merger if this sval shouldn't be mergeable
(e.g. reject merging svalues that have non-purgable sm-state,
to avoid falsely reporting memory leaks by merging them
with something else). */
if (!bound_sval->can_merge_p (unknown_sval, sval_mgr, merger))
return false;
out_cluster->m_map.put (key, unknown_sval);
}
/* We can only have at most one symbolic key per cluster,
and if we do, we can't have any concrete keys.
If this happens, mark the cluster as touched, with no keys. */
if (num_symbolic_keys >= 2
|| (num_concrete_keys > 0 && num_symbolic_keys > 0))
{
out_cluster->m_touched = true;
out_cluster->m_map.empty ();
}
/* We don't handle other kinds of overlaps yet. */
return true;
}
/* Update this cluster to reflect an attempt to merge OTHER where there
is no other cluster to merge with, and so we're notionally merging the
bound values in OTHER with the initial value of the relevant regions.
Any bound keys in OTHER should be bound to unknown in this. */
void
binding_cluster::make_unknown_relative_to (const binding_cluster *other,
store *out_store,
store_manager *mgr)
{
for (map_t::iterator iter = other->m_map.begin ();
iter != other->m_map.end (); ++iter)
{
const binding_key *iter_key = (*iter).first;
const svalue *iter_sval = (*iter).second;
const svalue *unknown_sval
= mgr->get_svalue_manager ()->get_or_create_unknown_svalue
(iter_sval->get_type ());
m_map.put (iter_key, unknown_sval);
/* For any pointers in OTHER, the merger means that the
concrete pointer becomes an unknown value, which could
show up as a false report of a leak when considering what
pointers are live before vs after.
Avoid this by marking the base regions they point to as having
escaped. */
if (const region_svalue *region_sval
= iter_sval->dyn_cast_region_svalue ())
{
const region *base_reg
= region_sval->get_pointee ()->get_base_region ();
if (base_reg->tracked_p ()
&& !base_reg->symbolic_for_unknown_ptr_p ())
{
binding_cluster *c = out_store->get_or_create_cluster (base_reg);
c->mark_as_escaped ();
}
}
}
}
/* Mark this cluster as having escaped. */
void
binding_cluster::mark_as_escaped ()
{
m_escaped = true;
}
/* If this cluster has escaped (by this call, or by an earlier one, or
by being an external param), then unbind all values and mark it
as "touched", so that it has a conjured value, rather than an
initial_svalue.
Use P to purge state involving conjured_svalues. */
void
binding_cluster::on_unknown_fncall (const gcall *call,
store_manager *mgr,
const conjured_purge &p)
{
if (m_escaped)
{
m_map.empty ();
/* Bind it to a new "conjured" value using CALL. */
const svalue *sval
= mgr->get_svalue_manager ()->get_or_create_conjured_svalue
(m_base_region->get_type (), call, m_base_region, p);
bind (mgr, m_base_region, sval);
m_touched = true;
}
}
/* Mark this cluster as having been clobbered by STMT.
Use P to purge state involving conjured_svalues. */
void
binding_cluster::on_asm (const gasm *stmt,
store_manager *mgr,
const conjured_purge &p)
{
m_map.empty ();
/* Bind it to a new "conjured" value using CALL. */
const svalue *sval
= mgr->get_svalue_manager ()->get_or_create_conjured_svalue
(m_base_region->get_type (), stmt, m_base_region, p);
bind (mgr, m_base_region, sval);
m_touched = true;
}
/* Return true if this binding_cluster has no information
i.e. if there are no bindings, and it hasn't been marked as having
escaped, or touched symbolically. */
bool
binding_cluster::redundant_p () const
{
return (m_map.elements () == 0
&& !m_escaped
&& !m_touched);
}
/* Add PV to OUT_PVS, casting it to TYPE if it is not already of that type. */
static void
append_pathvar_with_type (path_var pv,
tree type,
auto_vec<path_var> *out_pvs)
{
gcc_assert (pv.m_tree);
if (TREE_TYPE (pv.m_tree) != type)
pv.m_tree = build1 (NOP_EXPR, type, pv.m_tree);
out_pvs->safe_push (pv);
}
/* Find representative path_vars for SVAL within this binding of BASE_REG,
appending the results to OUT_PVS. */
void
binding_cluster::get_representative_path_vars (const region_model *model,
svalue_set *visited,
const region *base_reg,
const svalue *sval,
auto_vec<path_var> *out_pvs)
const
{
sval = simplify_for_binding (sval);
for (map_t::iterator iter = m_map.begin (); iter != m_map.end (); ++iter)
{
const binding_key *key = (*iter).first;
const svalue *bound_sval = (*iter).second;
if (bound_sval == sval)
{
if (const concrete_binding *ckey
= key->dyn_cast_concrete_binding ())
{
auto_vec <const region *> subregions;
base_reg->get_subregions_for_binding
(model->get_manager (),
ckey->get_start_bit_offset (),
ckey->get_size_in_bits (),
sval->get_type (),
&subregions);
unsigned i;
const region *subregion;
FOR_EACH_VEC_ELT (subregions, i, subregion)
{
if (path_var pv
= model->get_representative_path_var (subregion,
visited))
append_pathvar_with_type (pv, sval->get_type (), out_pvs);
}
}
else
{
const symbolic_binding *skey = (const symbolic_binding *)key;
if (path_var pv
= model->get_representative_path_var (skey->get_region (),
visited))
append_pathvar_with_type (pv, sval->get_type (), out_pvs);
}
}
}
}
/* Get any svalue bound to KEY, or NULL. */
const svalue *
binding_cluster::get_any_value (const binding_key *key) const
{
return m_map.get (key);
}
/* If this cluster has a single direct binding for the whole of the region,
return it.
For use in simplifying dumps. */
const svalue *
binding_cluster::maybe_get_simple_value (store_manager *mgr) const
{
/* Fail gracefully if MGR is NULL to make it easier to dump store
instances in the debugger. */
if (mgr == NULL)
return NULL;
if (m_map.elements () != 1)
return NULL;
const binding_key *key = binding_key::make (mgr, m_base_region);
return get_any_value (key);
}
/* class store_manager. */
logger *
store_manager::get_logger () const
{
return m_mgr->get_logger ();
}
/* binding consolidation. */
const concrete_binding *
store_manager::get_concrete_binding (bit_offset_t start_bit_offset,
bit_offset_t size_in_bits)
{
concrete_binding b (start_bit_offset, size_in_bits);
if (concrete_binding *existing = m_concrete_binding_key_mgr.get (b))
return existing;
concrete_binding *to_save = new concrete_binding (b);
m_concrete_binding_key_mgr.put (b, to_save);
return to_save;
}
const symbolic_binding *
store_manager::get_symbolic_binding (const region *reg)
{
symbolic_binding b (reg);
if (symbolic_binding *existing = m_symbolic_binding_key_mgr.get (b))
return existing;
symbolic_binding *to_save = new symbolic_binding (b);
m_symbolic_binding_key_mgr.put (b, to_save);
return to_save;
}
/* class store. */
/* store's default ctor. */
store::store ()
: m_called_unknown_fn (false)
{
}
/* store's copy ctor. */
store::store (const store &other)
: m_cluster_map (other.m_cluster_map.elements ()),
m_called_unknown_fn (other.m_called_unknown_fn)
{
for (cluster_map_t::iterator iter = other.m_cluster_map.begin ();
iter != other.m_cluster_map.end ();
++iter)
{
const region *reg = (*iter).first;
gcc_assert (reg);
binding_cluster *c = (*iter).second;
gcc_assert (c);
m_cluster_map.put (reg, new binding_cluster (*c));
}
}
/* store's dtor. */
store::~store ()
{
for (cluster_map_t::iterator iter = m_cluster_map.begin ();
iter != m_cluster_map.end ();
++iter)
delete (*iter).second;
}
/* store's assignment operator. */
store &
store::operator= (const store &other)
{
/* Delete existing cluster map. */
for (cluster_map_t::iterator iter = m_cluster_map.begin ();
iter != m_cluster_map.end ();
++iter)
delete (*iter).second;
m_cluster_map.empty ();
m_called_unknown_fn = other.m_called_unknown_fn;
for (cluster_map_t::iterator iter = other.m_cluster_map.begin ();
iter != other.m_cluster_map.end ();
++iter)
{
const region *reg = (*iter).first;
gcc_assert (reg);
binding_cluster *c = (*iter).second;
gcc_assert (c);
m_cluster_map.put (reg, new binding_cluster (*c));
}
return *this;
}
/* store's equality operator. */
bool
store::operator== (const store &other) const
{
if (m_called_unknown_fn != other.m_called_unknown_fn)
return false;
if (m_cluster_map.elements () != other.m_cluster_map.elements ())
return false;
for (cluster_map_t::iterator iter = m_cluster_map.begin ();
iter != m_cluster_map.end ();
++iter)
{
const region *reg = (*iter).first;
binding_cluster *c = (*iter).second;
binding_cluster **other_slot
= const_cast <cluster_map_t &> (other.m_cluster_map).get (reg);
if (other_slot == NULL)
return false;
if (*c != **other_slot)
return false;
}
gcc_checking_assert (hash () == other.hash ());
return true;
}
/* Get a hash value for this store. */
hashval_t
store::hash () const
{
hashval_t result = 0;
for (cluster_map_t::iterator iter = m_cluster_map.begin ();
iter != m_cluster_map.end ();
++iter)
result ^= (*iter).second->hash ();
return result;
}
/* Populate OUT with a sorted list of parent regions for the regions in IN,
removing duplicate parents. */
static void
get_sorted_parent_regions (auto_vec<const region *> *out,
auto_vec<const region *> &in)
{
/* Get the set of parent regions. */
hash_set<const region *> parent_regions;
const region *iter_reg;
unsigned i;
FOR_EACH_VEC_ELT (in, i, iter_reg)
{
const region *parent_reg = iter_reg->get_parent_region ();
gcc_assert (parent_reg);
parent_regions.add (parent_reg);
}
/* Write to OUT. */
for (hash_set<const region *>::iterator iter = parent_regions.begin();
iter != parent_regions.end(); ++iter)
out->safe_push (*iter);
/* Sort OUT. */
out->qsort (region::cmp_ptr_ptr);
}
/* Dump a representation of this store to PP, using SIMPLE to control how
svalues and regions are printed.
MGR is used for simplifying dumps if non-NULL, but can also be NULL
(to make it easier to use from the debugger). */
void
store::dump_to_pp (pretty_printer *pp, bool simple, bool multiline,
store_manager *mgr) const
{
/* Sort into some deterministic order. */
auto_vec<const region *> base_regions;
for (cluster_map_t::iterator iter = m_cluster_map.begin ();
iter != m_cluster_map.end (); ++iter)
{
const region *base_reg = (*iter).first;
base_regions.safe_push (base_reg);
}
base_regions.qsort (region::cmp_ptr_ptr);
/* Gather clusters, organize by parent region, so that we can group
together locals, globals, etc. */
auto_vec<const region *> parent_regions;
get_sorted_parent_regions (&parent_regions, base_regions);
const region *parent_reg;
unsigned i;
FOR_EACH_VEC_ELT (parent_regions, i, parent_reg)
{
gcc_assert (parent_reg);
pp_string (pp, "clusters within ");
parent_reg->dump_to_pp (pp, simple);
if (multiline)
pp_newline (pp);
else
pp_string (pp, " {");
const region *base_reg;
unsigned j;
FOR_EACH_VEC_ELT (base_regions, j, base_reg)
{
/* This is O(N * M), but N ought to be small. */
if (base_reg->get_parent_region () != parent_reg)
continue;
binding_cluster *cluster
= *const_cast<cluster_map_t &> (m_cluster_map).get (base_reg);
if (!multiline)
{
if (j > 0)
pp_string (pp, ", ");
}
if (const svalue *sval = cluster->maybe_get_simple_value (mgr))
{
/* Special-case to simplify dumps for the common case where
we just have one value directly bound to the whole of a
region. */
if (multiline)
{
pp_string (pp, " cluster for: ");
base_reg->dump_to_pp (pp, simple);
pp_string (pp, ": ");
sval->dump_to_pp (pp, simple);
if (cluster->escaped_p ())
pp_string (pp, " (ESCAPED)");
if (cluster->touched_p ())
pp_string (pp, " (TOUCHED)");
pp_newline (pp);
}
else
{
pp_string (pp, "region: {");
base_reg->dump_to_pp (pp, simple);
pp_string (pp, ", value: ");
sval->dump_to_pp (pp, simple);
if (cluster->escaped_p ())
pp_string (pp, " (ESCAPED)");
if (cluster->touched_p ())
pp_string (pp, " (TOUCHED)");
pp_string (pp, "}");
}
}
else if (multiline)
{
pp_string (pp, " cluster for: ");
base_reg->dump_to_pp (pp, simple);
pp_newline (pp);
cluster->dump_to_pp (pp, simple, multiline);
}
else
{
pp_string (pp, "base region: {");
base_reg->dump_to_pp (pp, simple);
pp_string (pp, "} has cluster: {");
cluster->dump_to_pp (pp, simple, multiline);
pp_string (pp, "}");
}
}
if (!multiline)
pp_string (pp, "}");
}
pp_printf (pp, "m_called_unknown_fn: %s",
m_called_unknown_fn ? "TRUE" : "FALSE");
if (multiline)
pp_newline (pp);
}
/* Dump a multiline representation of this store to stderr. */
DEBUG_FUNCTION void
store::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, true, NULL);
pp_newline (&pp);
pp_flush (&pp);
}
/* Assert that this object is valid. */
void
store::validate () const
{
for (auto iter : m_cluster_map)
iter.second->validate ();
}
/* Return a new json::object of the form
{PARENT_REGION_DESC: {BASE_REGION_DESC: object for binding_map,
... for each cluster within parent region},
...for each parent region,
"called_unknown_fn": true/false}. */
json::object *
store::to_json () const
{
json::object *store_obj = new json::object ();
/* Sort into some deterministic order. */
auto_vec<const region *> base_regions;
for (cluster_map_t::iterator iter = m_cluster_map.begin ();
iter != m_cluster_map.end (); ++iter)
{
const region *base_reg = (*iter).first;
base_regions.safe_push (base_reg);
}
base_regions.qsort (region::cmp_ptr_ptr);
/* Gather clusters, organize by parent region, so that we can group
together locals, globals, etc. */
auto_vec<const region *> parent_regions;
get_sorted_parent_regions (&parent_regions, base_regions);
const region *parent_reg;
unsigned i;
FOR_EACH_VEC_ELT (parent_regions, i, parent_reg)
{
gcc_assert (parent_reg);
json::object *clusters_in_parent_reg_obj = new json::object ();
const region *base_reg;
unsigned j;
FOR_EACH_VEC_ELT (base_regions, j, base_reg)
{
/* This is O(N * M), but N ought to be small. */
if (base_reg->get_parent_region () != parent_reg)
continue;
binding_cluster *cluster
= *const_cast<cluster_map_t &> (m_cluster_map).get (base_reg);
label_text base_reg_desc = base_reg->get_desc ();
clusters_in_parent_reg_obj->set (base_reg_desc.get (),
cluster->to_json ());
}
label_text parent_reg_desc = parent_reg->get_desc ();
store_obj->set (parent_reg_desc.get (), clusters_in_parent_reg_obj);
}
store_obj->set ("called_unknown_fn", new json::literal (m_called_unknown_fn));
return store_obj;
}
/* Get any svalue bound to REG, or NULL. */
const svalue *
store::get_any_binding (store_manager *mgr, const region *reg) const
{
const region *base_reg = reg->get_base_region ();
binding_cluster **cluster_slot
= const_cast <cluster_map_t &> (m_cluster_map).get (base_reg);
if (!cluster_slot)
return NULL;
return (*cluster_slot)->get_any_binding (mgr, reg);
}
/* Set the value of LHS_REG to RHS_SVAL. */
void
store::set_value (store_manager *mgr, const region *lhs_reg,
const svalue *rhs_sval,
uncertainty_t *uncertainty)
{
logger *logger = mgr->get_logger ();
LOG_SCOPE (logger);
remove_overlapping_bindings (mgr, lhs_reg, uncertainty);
if (lhs_reg->get_type ())
rhs_sval = simplify_for_binding (rhs_sval);
/* ...but if we have no type for the region, retain any cast. */
const region *lhs_base_reg = lhs_reg->get_base_region ();
binding_cluster *lhs_cluster;
if (lhs_base_reg->symbolic_for_unknown_ptr_p ())
{
/* Reject attempting to bind values into a symbolic region
for an unknown ptr; merely invalidate values below. */
lhs_cluster = NULL;
/* The LHS of the write is *UNKNOWN. If the RHS is a pointer,
then treat the region being pointed to as having escaped. */
if (const region_svalue *ptr_sval = rhs_sval->dyn_cast_region_svalue ())
{
const region *ptr_dst = ptr_sval->get_pointee ();
const region *ptr_base_reg = ptr_dst->get_base_region ();
mark_as_escaped (ptr_base_reg);
}
}
else if (lhs_base_reg->tracked_p ())
{
lhs_cluster = get_or_create_cluster (lhs_base_reg);
lhs_cluster->bind (mgr, lhs_reg, rhs_sval);
}
else
{
/* Reject attempting to bind values into an untracked region;
merely invalidate values below. */
lhs_cluster = NULL;
}
/* Bindings to a cluster can affect other clusters if a symbolic
base region is involved.
Writes to concrete clusters can't affect other concrete clusters,
but can affect symbolic clusters.
Writes to symbolic clusters can affect both concrete and symbolic
clusters.
Invalidate our knowledge of other clusters that might have been
affected by the write. */
for (cluster_map_t::iterator iter = m_cluster_map.begin ();
iter != m_cluster_map.end (); ++iter)
{
const region *iter_base_reg = (*iter).first;
binding_cluster *iter_cluster = (*iter).second;
if (iter_base_reg != lhs_base_reg
&& (lhs_cluster == NULL
|| lhs_cluster->symbolic_p ()
|| iter_cluster->symbolic_p ()))
{
tristate t_alias = eval_alias (lhs_base_reg, iter_base_reg);
switch (t_alias.get_value ())
{
default:
gcc_unreachable ();
case tristate::TS_UNKNOWN:
if (logger)
{
pretty_printer *pp = logger->get_printer ();
logger->start_log_line ();
logger->log_partial ("possible aliasing of ");
iter_base_reg->dump_to_pp (pp, true);
logger->log_partial (" when writing SVAL: ");
rhs_sval->dump_to_pp (pp, true);
logger->log_partial (" to LHS_REG: ");
lhs_reg->dump_to_pp (pp, true);
logger->end_log_line ();
}
/* Mark all of iter_cluster's iter_base_reg as unknown,
using LHS_REG when considering overlaps, to handle
symbolic vs concrete issues. */
iter_cluster->mark_region_as_unknown
(mgr,
iter_base_reg, /* reg_to_bind */
lhs_reg, /* reg_for_overlap */
uncertainty);
break;
case tristate::TS_TRUE:
gcc_unreachable ();
break;
case tristate::TS_FALSE:
/* If they can't be aliases, then don't invalidate this
cluster. */
break;
}
}
}
}
/* Determine if BASE_REG_A could be an alias of BASE_REG_B. */
tristate
store::eval_alias (const region *base_reg_a,
const region *base_reg_b) const
{
/* SSA names can't alias. */
tree decl_a = base_reg_a->maybe_get_decl ();
if (decl_a && TREE_CODE (decl_a) == SSA_NAME)
return tristate::TS_FALSE;
tree decl_b = base_reg_b->maybe_get_decl ();
if (decl_b && TREE_CODE (decl_b) == SSA_NAME)
return tristate::TS_FALSE;
/* Try both ways, for symmetry. */
tristate ts_ab = eval_alias_1 (base_reg_a, base_reg_b);
if (ts_ab.is_false ())
return tristate::TS_FALSE;
tristate ts_ba = eval_alias_1 (base_reg_b, base_reg_a);
if (ts_ba.is_false ())
return tristate::TS_FALSE;
return tristate::TS_UNKNOWN;
}
/* Half of store::eval_alias; called twice for symmetry. */
tristate
store::eval_alias_1 (const region *base_reg_a,
const region *base_reg_b) const
{
if (const symbolic_region *sym_reg_a
= base_reg_a->dyn_cast_symbolic_region ())
{
const svalue *sval_a = sym_reg_a->get_pointer ();
if (tree decl_b = base_reg_b->maybe_get_decl ())
{
if (!may_be_aliased (decl_b))
return tristate::TS_FALSE;
if (sval_a->get_kind () == SK_INITIAL)
if (!is_global_var (decl_b))
{
/* The initial value of a pointer can't point to a local. */
return tristate::TS_FALSE;
}
}
if (sval_a->get_kind () == SK_INITIAL
&& base_reg_b->get_kind () == RK_HEAP_ALLOCATED)
{
/* The initial value of a pointer can't point to a
region that was allocated on the heap after the beginning of the
path. */
return tristate::TS_FALSE;
}
if (const widening_svalue *widening_sval_a
= sval_a->dyn_cast_widening_svalue ())
{
const svalue *base = widening_sval_a->get_base_svalue ();
if (const region_svalue *region_sval
= base->dyn_cast_region_svalue ())
{
const region *pointee = region_sval->get_pointee ();
/* If we have sval_a is WIDENING(&REGION, OP), and
B can't alias REGION, then B can't alias A either.
For example, A might arise from
for (ptr = &REGION; ...; ptr++)
where sval_a is ptr in the 2nd iteration of the loop.
We want to ensure that "*ptr" can only clobber things
within REGION's base region. */
tristate ts = eval_alias (pointee->get_base_region (),
base_reg_b);
if (ts.is_false ())
return tristate::TS_FALSE;
}
}
}
return tristate::TS_UNKNOWN;
}
/* Remove all bindings overlapping REG within this store. */
void
store::clobber_region (store_manager *mgr, const region *reg)
{
const region *base_reg = reg->get_base_region ();
binding_cluster **slot = m_cluster_map.get (base_reg);
if (!slot)
return;
binding_cluster *cluster = *slot;
cluster->clobber_region (mgr, reg);
if (cluster->redundant_p ())
{
delete cluster;
m_cluster_map.remove (base_reg);
}
}
/* Remove any bindings for REG within this store. */
void
store::purge_region (store_manager *mgr, const region *reg)
{
const region *base_reg = reg->get_base_region ();
binding_cluster **slot = m_cluster_map.get (base_reg);
if (!slot)
return;
binding_cluster *cluster = *slot;
cluster->purge_region (mgr, reg);
if (cluster->redundant_p ())
{
delete cluster;
m_cluster_map.remove (base_reg);
}
}
/* Fill REG with SVAL. */
void
store::fill_region (store_manager *mgr, const region *reg, const svalue *sval)
{
const region *base_reg = reg->get_base_region ();
if (base_reg->symbolic_for_unknown_ptr_p ()
|| !base_reg->tracked_p ())
return;
binding_cluster *cluster = get_or_create_cluster (base_reg);
cluster->fill_region (mgr, reg, sval);
}
/* Zero-fill REG. */
void
store::zero_fill_region (store_manager *mgr, const region *reg)
{
region_model_manager *sval_mgr = mgr->get_svalue_manager ();
const svalue *zero_sval = sval_mgr->get_or_create_int_cst (char_type_node, 0);
fill_region (mgr, reg, zero_sval);
}
/* Mark REG as having unknown content. */
void
store::mark_region_as_unknown (store_manager *mgr, const region *reg,
uncertainty_t *uncertainty)
{
const region *base_reg = reg->get_base_region ();
if (base_reg->symbolic_for_unknown_ptr_p ()
|| !base_reg->tracked_p ())
return;
binding_cluster *cluster = get_or_create_cluster (base_reg);
cluster->mark_region_as_unknown (mgr, reg, reg, uncertainty);
}
/* Purge state involving SVAL. */
void
store::purge_state_involving (const svalue *sval,
region_model_manager *sval_mgr)
{
auto_vec <const region *> base_regs_to_purge;
for (auto iter : m_cluster_map)
{
const region *base_reg = iter.first;
if (base_reg->involves_p (sval))
base_regs_to_purge.safe_push (base_reg);
else
{
binding_cluster *cluster = iter.second;
cluster->purge_state_involving (sval, sval_mgr);
}
}
for (auto iter : base_regs_to_purge)
purge_cluster (iter);
}
/* Get the cluster for BASE_REG, or NULL (const version). */
const binding_cluster *
store::get_cluster (const region *base_reg) const
{
gcc_assert (base_reg);
gcc_assert (base_reg->get_base_region () == base_reg);
if (binding_cluster **slot
= const_cast <cluster_map_t &> (m_cluster_map).get (base_reg))
return *slot;
else
return NULL;
}
/* Get the cluster for BASE_REG, or NULL (non-const version). */
binding_cluster *
store::get_cluster (const region *base_reg)
{
gcc_assert (base_reg);
gcc_assert (base_reg->get_base_region () == base_reg);
if (binding_cluster **slot = m_cluster_map.get (base_reg))
return *slot;
else
return NULL;
}
/* Get the cluster for BASE_REG, creating it if doesn't already exist. */
binding_cluster *
store::get_or_create_cluster (const region *base_reg)
{
gcc_assert (base_reg);
gcc_assert (base_reg->get_base_region () == base_reg);
/* We shouldn't create clusters for dereferencing an UNKNOWN ptr. */
gcc_assert (!base_reg->symbolic_for_unknown_ptr_p ());
/* We shouldn't create clusters for base regions that aren't trackable. */
gcc_assert (base_reg->tracked_p ());
if (binding_cluster **slot = m_cluster_map.get (base_reg))
return *slot;
binding_cluster *cluster = new binding_cluster (base_reg);
m_cluster_map.put (base_reg, cluster);
return cluster;
}
/* Remove any cluster for BASE_REG, for use by
region_model::unbind_region_and_descendents
when popping stack frames and handling deleted heap regions. */
void
store::purge_cluster (const region *base_reg)
{
gcc_assert (base_reg->get_base_region () == base_reg);
binding_cluster **slot = m_cluster_map.get (base_reg);
if (!slot)
return;
binding_cluster *cluster = *slot;
delete cluster;
m_cluster_map.remove (base_reg);
}
/* Attempt to merge STORE_A and STORE_B into OUT_STORE.
Return true if successful, or false if the stores can't be merged. */
bool
store::can_merge_p (const store *store_a, const store *store_b,
store *out_store, store_manager *mgr,
model_merger *merger)
{
if (store_a->m_called_unknown_fn || store_b->m_called_unknown_fn)
out_store->m_called_unknown_fn = true;
/* Get the union of all base regions for STORE_A and STORE_B. */
hash_set<const region *> base_regions;
for (cluster_map_t::iterator iter_a = store_a->m_cluster_map.begin ();
iter_a != store_a->m_cluster_map.end (); ++iter_a)
{
const region *base_reg_a = (*iter_a).first;
base_regions.add (base_reg_a);
}
for (cluster_map_t::iterator iter_b = store_b->m_cluster_map.begin ();
iter_b != store_b->m_cluster_map.end (); ++iter_b)
{
const region *base_reg_b = (*iter_b).first;
base_regions.add (base_reg_b);
}
/* Sort the base regions before considering them. This ought not to
affect the results, but can affect which types UNKNOWN_REGIONs are
created for in a run; sorting them thus avoids minor differences
in logfiles. */
auto_vec<const region *> vec_base_regions (base_regions.elements ());
for (hash_set<const region *>::iterator iter = base_regions.begin ();
iter != base_regions.end (); ++iter)
vec_base_regions.quick_push (*iter);
vec_base_regions.qsort (region::cmp_ptr_ptr);
unsigned i;
const region *base_reg;
FOR_EACH_VEC_ELT (vec_base_regions, i, base_reg)
{
const binding_cluster *cluster_a = store_a->get_cluster (base_reg);
const binding_cluster *cluster_b = store_b->get_cluster (base_reg);
/* At least one of cluster_a and cluster_b must be non-NULL. */
binding_cluster *out_cluster
= out_store->get_or_create_cluster (base_reg);
if (!binding_cluster::can_merge_p (cluster_a, cluster_b,
out_cluster, out_store, mgr, merger))
return false;
}
return true;
}
/* Mark the cluster for BASE_REG as having escaped.
For use when handling an unrecognized function call, and
for params to "top-level" calls.
Further unknown function calls could touch it, even if the cluster
isn't reachable from args of those calls. */
void
store::mark_as_escaped (const region *base_reg)
{
gcc_assert (base_reg);
gcc_assert (base_reg->get_base_region () == base_reg);
if (base_reg->symbolic_for_unknown_ptr_p ()
|| !base_reg->tracked_p ())
return;
binding_cluster *cluster = get_or_create_cluster (base_reg);
cluster->mark_as_escaped ();
}
/* Handle an unknown fncall by updating any clusters that have escaped
(either in this fncall, or in a prior one). */
void
store::on_unknown_fncall (const gcall *call, store_manager *mgr,
const conjured_purge &p)
{
m_called_unknown_fn = true;
for (cluster_map_t::iterator iter = m_cluster_map.begin ();
iter != m_cluster_map.end (); ++iter)
(*iter).second->on_unknown_fncall (call, mgr, p);
}
/* Return true if a non-const pointer to BASE_REG (or something within it)
has escaped to code outside of the TU being analyzed. */
bool
store::escaped_p (const region *base_reg) const
{
gcc_assert (base_reg);
gcc_assert (base_reg->get_base_region () == base_reg);
if (binding_cluster **cluster_slot
= const_cast <cluster_map_t &>(m_cluster_map).get (base_reg))
return (*cluster_slot)->escaped_p ();
return false;
}
/* Populate OUT_PVS with a list of path_vars for describing SVAL based on
this store, using VISITED to ensure the traversal terminates. */
void
store::get_representative_path_vars (const region_model *model,
svalue_set *visited,
const svalue *sval,
auto_vec<path_var> *out_pvs) const
{
gcc_assert (sval);
/* Find all bindings that reference SVAL. */
for (cluster_map_t::iterator iter = m_cluster_map.begin ();
iter != m_cluster_map.end (); ++iter)
{
const region *base_reg = (*iter).first;
binding_cluster *cluster = (*iter).second;
cluster->get_representative_path_vars (model, visited, base_reg, sval,
out_pvs);
}
if (const initial_svalue *init_sval = sval->dyn_cast_initial_svalue ())
{
const region *reg = init_sval->get_region ();
if (path_var pv = model->get_representative_path_var (reg,
visited))
out_pvs->safe_push (pv);
}
}
/* Remove all bindings overlapping REG within this store, removing
any clusters that become redundant.
If UNCERTAINTY is non-NULL, use it to record any svalues that
were removed, as being maybe-bound. */
void
store::remove_overlapping_bindings (store_manager *mgr, const region *reg,
uncertainty_t *uncertainty)
{
const region *base_reg = reg->get_base_region ();
if (binding_cluster **cluster_slot = m_cluster_map.get (base_reg))
{
binding_cluster *cluster = *cluster_slot;
if (reg == base_reg && !escaped_p (base_reg))
{
/* Remove whole cluster. */
m_cluster_map.remove (base_reg);
delete cluster;
return;
}
cluster->remove_overlapping_bindings (mgr, reg, uncertainty);
}
}
/* Subclass of visitor that accumulates a hash_set of the regions that
were visited. */
struct region_finder : public visitor
{
void visit_region (const region *reg) final override
{
m_regs.add (reg);
}
hash_set<const region *> m_regs;
};
/* Canonicalize this store, to maximize the chance of equality between
instances. */
void
store::canonicalize (store_manager *mgr)
{
/* If we have e.g.:
cluster for: HEAP_ALLOCATED_REGION(543)
ESCAPED
TOUCHED
where the heap region is empty and unreferenced, then purge that
cluster, to avoid unbounded state chains involving these. */
/* Find regions that are referenced by bound values in the store. */
region_finder s;
for (cluster_map_t::iterator iter = m_cluster_map.begin ();
iter != m_cluster_map.end (); ++iter)
{
binding_cluster *cluster = (*iter).second;
for (binding_cluster::iterator_t bind_iter = cluster->m_map.begin ();
bind_iter != cluster->m_map.end (); ++bind_iter)
(*bind_iter).second->accept (&s);
}
/* Locate heap-allocated regions that have empty bindings that weren't
found above. */
hash_set<const region *> purgeable_regions;
for (cluster_map_t::iterator iter = m_cluster_map.begin ();
iter != m_cluster_map.end (); ++iter)
{
const region *base_reg = (*iter).first;
binding_cluster *cluster = (*iter).second;
if (base_reg->get_kind () == RK_HEAP_ALLOCATED)
{
if (cluster->empty_p ())
if (!s.m_regs.contains (base_reg))
purgeable_regions.add (base_reg);
/* Also cover the UNKNOWN case. */
if (const svalue *sval = cluster->maybe_get_simple_value (mgr))
if (sval->get_kind () == SK_UNKNOWN)
if (!s.m_regs.contains (base_reg))
purgeable_regions.add (base_reg);
}
}
/* Purge them. */
for (hash_set<const region *>::iterator iter = purgeable_regions.begin ();
iter != purgeable_regions.end (); ++iter)
{
const region *base_reg = *iter;
purge_cluster (base_reg);
}
}
/* Subroutine for use by exploded_path::feasible_p.
We need to deal with state differences between:
(a) when the exploded_graph is being initially constructed and
(b) when replaying the state changes along a specific path in
in exploded_path::feasible_p.
In (a), state merging happens, so when exploring a loop
for (i = 0; i < 1024; i++)
on successive iterations we have i == 0, then i == WIDENING.
In (b), no state merging happens, so naively replaying the path
that goes twice through the loop then exits it
would lead to i == 0, then i == 1, and then a (i >= 1024) eedge
that exits the loop, which would be found to be infeasible as i == 1,
and the path would be rejected.
We need to fix up state during replay. This subroutine is
called whenever we enter a supernode that we've already
visited along this exploded_path, passing in OTHER_STORE
from the destination enode's state.
Find bindings to widening values in OTHER_STORE.
For all that are found, update the binding in this store to UNKNOWN. */
void
store::loop_replay_fixup (const store *other_store,
region_model_manager *mgr)
{
gcc_assert (other_store);
for (cluster_map_t::iterator iter = other_store->m_cluster_map.begin ();
iter != other_store->m_cluster_map.end (); ++iter)
{
const region *base_reg = (*iter).first;
binding_cluster *cluster = (*iter).second;
for (binding_cluster::iterator_t bind_iter = cluster->m_map.begin ();
bind_iter != cluster->m_map.end (); ++bind_iter)
{
const binding_key *key = (*bind_iter).first;
const svalue *sval = (*bind_iter).second;
if (sval->get_kind () == SK_WIDENING)
{
binding_cluster *this_cluster
= get_or_create_cluster (base_reg);
const svalue *unknown
= mgr->get_or_create_unknown_svalue (sval->get_type ());
this_cluster->bind_key (key, unknown);
}
}
}
}
/* Use R to replay the bindings from SUMMARY into this object. */
void
store::replay_call_summary (call_summary_replay &r,
const store &summary)
{
if (summary.m_called_unknown_fn)
{
/* A call to an external function occurred in the summary.
Hence we need to invalidate our knownledge of globals,
escaped regions, etc. */
on_unknown_fncall (r.get_call_stmt (),
r.get_store_manager (),
conjured_purge (r.get_caller_model (),
r.get_ctxt ()));
}
auto_vec<const region *> keys (summary.m_cluster_map.elements ());
for (auto kv : summary.m_cluster_map)
keys.quick_push (kv.first);
keys.qsort (region::cmp_ptr_ptr);
for (auto base_reg : keys)
replay_call_summary_cluster (r, summary, base_reg);
}
/* Use R and SUMMARY to replay the bindings in SUMMARY_CLUSTER
into this object. */
void
store::replay_call_summary_cluster (call_summary_replay &r,
const store &summary,
const region *summary_base_reg)
{
const call_details &cd = r.get_call_details ();
region_model_manager *reg_mgr = r.get_manager ();
store_manager *mgr = reg_mgr->get_store_manager ();
const binding_cluster *summary_cluster
= summary.get_cluster (summary_base_reg);
/* Handle "ESCAPED" and "TOUCHED" flags. */
if (summary_cluster->escaped_p () || summary_cluster->touched_p ())
if (const region *caller_reg
= r.convert_region_from_summary (summary_base_reg))
{
const region *caller_base_reg = caller_reg->get_base_region ();
if (caller_base_reg->tracked_p ()
&& !caller_base_reg->symbolic_for_unknown_ptr_p ())
{
binding_cluster *caller_cluster
= get_or_create_cluster (caller_base_reg);
if (summary_cluster->escaped_p ())
caller_cluster->mark_as_escaped ();
if (summary_cluster->touched_p ())
caller_cluster->m_touched = true;
}
}
switch (summary_base_reg->get_kind ())
{
/* Top-level regions. */
case RK_FRAME:
case RK_GLOBALS:
case RK_CODE:
case RK_STACK:
case RK_HEAP:
case RK_ROOT:
/* Child regions. */
case RK_FIELD:
case RK_ELEMENT:
case RK_OFFSET:
case RK_SIZED:
case RK_CAST:
case RK_BIT_RANGE:
/* Other regions. */
case RK_VAR_ARG:
case RK_UNKNOWN:
/* These should never be the base region of a binding cluster. */
gcc_unreachable ();
break;
case RK_FUNCTION:
case RK_LABEL:
case RK_STRING:
/* These can be marked as escaping. */
break;
case RK_SYMBOLIC:
{
const symbolic_region *summary_symbolic_reg
= as_a <const symbolic_region *> (summary_base_reg);
const svalue *summary_ptr_sval = summary_symbolic_reg->get_pointer ();
const svalue *caller_ptr_sval
= r.convert_svalue_from_summary (summary_ptr_sval);
if (!caller_ptr_sval)
return;
const region *caller_dest_reg
= cd.get_model ()->deref_rvalue (caller_ptr_sval,
NULL_TREE,
cd.get_ctxt ());
const svalue *summary_sval
= summary.get_any_binding (mgr, summary_base_reg);
if (!summary_sval)
return;
const svalue *caller_sval
= r.convert_svalue_from_summary (summary_sval);
if (!caller_sval)
caller_sval =
reg_mgr->get_or_create_unknown_svalue (summary_sval->get_type ());
set_value (mgr, caller_dest_reg,
caller_sval, NULL /* uncertainty_t * */);
}
break;
case RK_HEAP_ALLOCATED:
case RK_DECL:
{
const region *caller_dest_reg
= r.convert_region_from_summary (summary_base_reg);
if (!caller_dest_reg)
return;
const svalue *summary_sval
= summary.get_any_binding (mgr, summary_base_reg);
if (!summary_sval)
summary_sval = reg_mgr->get_or_create_compound_svalue
(summary_base_reg->get_type (),
summary_cluster->get_map ());
const svalue *caller_sval
= r.convert_svalue_from_summary (summary_sval);
if (!caller_sval)
caller_sval =
reg_mgr->get_or_create_unknown_svalue (summary_sval->get_type ());
set_value (mgr, caller_dest_reg,
caller_sval, NULL /* uncertainty_t * */);
}
break;
case RK_ALLOCA:
/* Ignore bindings of alloca regions in the summary. */
break;
}
}
#if CHECKING_P
namespace selftest {
/* Verify that bit_range::intersects_p works as expected. */
static void
test_bit_range_intersects_p ()
{
bit_range b0 (0, 1);
bit_range b1 (1, 1);
bit_range b2 (2, 1);
bit_range b3 (3, 1);
bit_range b4 (4, 1);
bit_range b5 (5, 1);
bit_range b6 (6, 1);
bit_range b7 (7, 1);
bit_range b1_to_6 (1, 6);
bit_range b0_to_7 (0, 8);
bit_range b3_to_5 (3, 3);
bit_range b6_to_7 (6, 2);
/* self-intersection is true. */
ASSERT_TRUE (b0.intersects_p (b0));
ASSERT_TRUE (b7.intersects_p (b7));
ASSERT_TRUE (b1_to_6.intersects_p (b1_to_6));
ASSERT_TRUE (b0_to_7.intersects_p (b0_to_7));
ASSERT_FALSE (b0.intersects_p (b1));
ASSERT_FALSE (b1.intersects_p (b0));
ASSERT_FALSE (b0.intersects_p (b7));
ASSERT_FALSE (b7.intersects_p (b0));
ASSERT_TRUE (b0_to_7.intersects_p (b0));
ASSERT_TRUE (b0_to_7.intersects_p (b7));
ASSERT_TRUE (b0.intersects_p (b0_to_7));
ASSERT_TRUE (b7.intersects_p (b0_to_7));
ASSERT_FALSE (b0.intersects_p (b1_to_6));
ASSERT_FALSE (b1_to_6.intersects_p (b0));
ASSERT_TRUE (b1.intersects_p (b1_to_6));
ASSERT_TRUE (b1_to_6.intersects_p (b1));
ASSERT_TRUE (b1_to_6.intersects_p (b6));
ASSERT_FALSE (b1_to_6.intersects_p (b7));
ASSERT_TRUE (b1_to_6.intersects_p (b0_to_7));
ASSERT_TRUE (b0_to_7.intersects_p (b1_to_6));
ASSERT_FALSE (b3_to_5.intersects_p (b6_to_7));
ASSERT_FALSE (b6_to_7.intersects_p (b3_to_5));
bit_range r1 (0,0);
bit_range r2 (0,0);
ASSERT_TRUE (b1_to_6.intersects_p (b0_to_7, &r1, &r2));
ASSERT_EQ (r1.get_start_bit_offset (), 0);
ASSERT_EQ (r1.m_size_in_bits, 6);
ASSERT_EQ (r2.get_start_bit_offset (), 1);
ASSERT_EQ (r2.m_size_in_bits, 6);
ASSERT_TRUE (b0_to_7.intersects_p (b1_to_6, &r1, &r2));
ASSERT_EQ (r1.get_start_bit_offset (), 1);
ASSERT_EQ (r1.m_size_in_bits, 6);
ASSERT_EQ (r2.get_start_bit_offset (), 0);
ASSERT_EQ (r2.m_size_in_bits, 6);
}
/* Implementation detail of ASSERT_BIT_RANGE_FROM_MASK_EQ. */
static void
assert_bit_range_from_mask_eq (const location &loc,
unsigned HOST_WIDE_INT mask,
const bit_range &expected)
{
bit_range actual (0, 0);
bool ok = bit_range::from_mask (mask, &actual);
ASSERT_TRUE_AT (loc, ok);
ASSERT_EQ_AT (loc, actual, expected);
}
/* Assert that bit_range::from_mask (MASK) returns true, and writes
out EXPECTED_BIT_RANGE. */
#define ASSERT_BIT_RANGE_FROM_MASK_EQ(MASK, EXPECTED_BIT_RANGE) \
SELFTEST_BEGIN_STMT \
assert_bit_range_from_mask_eq (SELFTEST_LOCATION, MASK, \
EXPECTED_BIT_RANGE); \
SELFTEST_END_STMT
/* Implementation detail of ASSERT_NO_BIT_RANGE_FROM_MASK. */
static void
assert_no_bit_range_from_mask_eq (const location &loc,
unsigned HOST_WIDE_INT mask)
{
bit_range actual (0, 0);
bool ok = bit_range::from_mask (mask, &actual);
ASSERT_FALSE_AT (loc, ok);
}
/* Assert that bit_range::from_mask (MASK) returns false. */
#define ASSERT_NO_BIT_RANGE_FROM_MASK(MASK) \
SELFTEST_BEGIN_STMT \
assert_no_bit_range_from_mask_eq (SELFTEST_LOCATION, MASK); \
SELFTEST_END_STMT
/* Verify that bit_range::from_mask works as expected. */
static void
test_bit_range_from_mask ()
{
/* Should fail on zero. */
ASSERT_NO_BIT_RANGE_FROM_MASK (0);
/* Verify 1-bit masks. */
ASSERT_BIT_RANGE_FROM_MASK_EQ (1, bit_range (0, 1));
ASSERT_BIT_RANGE_FROM_MASK_EQ (2, bit_range (1, 1));
ASSERT_BIT_RANGE_FROM_MASK_EQ (4, bit_range (2, 1));
ASSERT_BIT_RANGE_FROM_MASK_EQ (8, bit_range (3, 1));
ASSERT_BIT_RANGE_FROM_MASK_EQ (16, bit_range (4, 1));
ASSERT_BIT_RANGE_FROM_MASK_EQ (32, bit_range (5, 1));
ASSERT_BIT_RANGE_FROM_MASK_EQ (64, bit_range (6, 1));
ASSERT_BIT_RANGE_FROM_MASK_EQ (128, bit_range (7, 1));
/* Verify N-bit masks starting at bit 0. */
ASSERT_BIT_RANGE_FROM_MASK_EQ (3, bit_range (0, 2));
ASSERT_BIT_RANGE_FROM_MASK_EQ (7, bit_range (0, 3));
ASSERT_BIT_RANGE_FROM_MASK_EQ (15, bit_range (0, 4));
ASSERT_BIT_RANGE_FROM_MASK_EQ (31, bit_range (0, 5));
ASSERT_BIT_RANGE_FROM_MASK_EQ (63, bit_range (0, 6));
ASSERT_BIT_RANGE_FROM_MASK_EQ (127, bit_range (0, 7));
ASSERT_BIT_RANGE_FROM_MASK_EQ (255, bit_range (0, 8));
ASSERT_BIT_RANGE_FROM_MASK_EQ (0xffff, bit_range (0, 16));
/* Various other tests. */
ASSERT_BIT_RANGE_FROM_MASK_EQ (0x30, bit_range (4, 2));
ASSERT_BIT_RANGE_FROM_MASK_EQ (0x700, bit_range (8, 3));
ASSERT_BIT_RANGE_FROM_MASK_EQ (0x600, bit_range (9, 2));
/* Multiple ranges of set bits should fail. */
ASSERT_NO_BIT_RANGE_FROM_MASK (0x101);
ASSERT_NO_BIT_RANGE_FROM_MASK (0xf0f0f0f0);
}
/* Implementation detail of ASSERT_OVERLAP. */
static void
assert_overlap (const location &loc,
const concrete_binding *b1,
const concrete_binding *b2)
{
ASSERT_TRUE_AT (loc, b1->overlaps_p (*b2));
ASSERT_TRUE_AT (loc, b2->overlaps_p (*b1));
}
/* Implementation detail of ASSERT_DISJOINT. */
static void
assert_disjoint (const location &loc,
const concrete_binding *b1,
const concrete_binding *b2)
{
ASSERT_FALSE_AT (loc, b1->overlaps_p (*b2));
ASSERT_FALSE_AT (loc, b2->overlaps_p (*b1));
}
/* Assert that B1 and B2 overlap, checking both ways. */
#define ASSERT_OVERLAP(B1, B2) \
SELFTEST_BEGIN_STMT \
assert_overlap (SELFTEST_LOCATION, B1, B2); \
SELFTEST_END_STMT
/* Assert that B1 and B2 do not overlap, checking both ways. */
#define ASSERT_DISJOINT(B1, B2) \
SELFTEST_BEGIN_STMT \
assert_disjoint (SELFTEST_LOCATION, B1, B2); \
SELFTEST_END_STMT
/* Verify that concrete_binding::overlaps_p works as expected. */
static void
test_binding_key_overlap ()
{
store_manager mgr (NULL);
/* Various 8-bit bindings. */
const concrete_binding *cb_0_7 = mgr.get_concrete_binding (0, 8);
const concrete_binding *cb_8_15 = mgr.get_concrete_binding (8, 8);
const concrete_binding *cb_16_23 = mgr.get_concrete_binding (16, 8);
const concrete_binding *cb_24_31 = mgr.get_concrete_binding (24, 8);
/* 16-bit bindings. */
const concrete_binding *cb_0_15 = mgr.get_concrete_binding (0, 16);
const concrete_binding *cb_8_23 = mgr.get_concrete_binding (8, 16);
const concrete_binding *cb_16_31 = mgr.get_concrete_binding (16, 16);
/* 32-bit binding. */
const concrete_binding *cb_0_31 = mgr.get_concrete_binding (0, 32);
/* Everything should self-overlap. */
ASSERT_OVERLAP (cb_0_7, cb_0_7);
ASSERT_OVERLAP (cb_8_15, cb_8_15);
ASSERT_OVERLAP (cb_16_23, cb_16_23);
ASSERT_OVERLAP (cb_24_31, cb_24_31);
ASSERT_OVERLAP (cb_0_15, cb_0_15);
ASSERT_OVERLAP (cb_8_23, cb_8_23);
ASSERT_OVERLAP (cb_16_31, cb_16_31);
ASSERT_OVERLAP (cb_0_31, cb_0_31);
/* Verify the 8-bit bindings that don't overlap each other. */
ASSERT_DISJOINT (cb_0_7, cb_8_15);
ASSERT_DISJOINT (cb_8_15, cb_16_23);
/* Check for overlap of differently-sized bindings. */
ASSERT_OVERLAP (cb_0_7, cb_0_31);
/* ...and with differing start points. */
ASSERT_OVERLAP (cb_8_15, cb_0_31);
ASSERT_DISJOINT (cb_8_15, cb_16_31);
ASSERT_OVERLAP (cb_16_23, cb_0_31);
ASSERT_OVERLAP (cb_16_31, cb_0_31);
ASSERT_DISJOINT (cb_0_7, cb_8_23);
ASSERT_OVERLAP (cb_8_23, cb_16_23);
ASSERT_OVERLAP (cb_8_23, cb_16_31);
ASSERT_DISJOINT (cb_8_23, cb_24_31);
}
/* Run all of the selftests within this file. */
void
analyzer_store_cc_tests ()
{
test_bit_range_intersects_p ();
test_bit_range_from_mask ();
test_binding_key_overlap ();
}
} // namespace selftest
#endif /* CHECKING_P */
} // namespace ana
#endif /* #if ENABLE_ANALYZER */