blob: 2f4faae5e9c88cfd42f8dcf8d132e03efcd29370 [file]
/* Typesafe symbolic ranges of values (e.g. "bytes m-n").
Copyright (C) 2019-2026 Free Software Foundation, Inc.
Contributed by David Malcolm <dmalcolm@redhat.com>.
This file is part of GCC.
GCC is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
GCC is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with GCC; see the file COPYING3. If not see
<http://www.gnu.org/licenses/>. */
#include "analyzer/common.h"
#include "selftest.h"
#include "analyzer/symbolic-ranges.h"
#include "analyzer/region-model-manager.h"
#include "analyzer/region-model.h"
#include "analyzer/analyzer-selftests.h"
#if ENABLE_ANALYZER
namespace ana {
template<typename ConcreteValueType>
std::unique_ptr<text_art::tree_widget>
symbolic::range<ConcreteValueType>::
make_dump_widget (symbolic::node_dump_state &nds,
const char *prefix) const
{
using text_art::tree_widget;
std::unique_ptr<tree_widget> widget;
if (prefix)
widget = tree_widget::from_fmt (nds, nullptr, "%s: symbolic range", prefix);
else
widget = tree_widget::from_fmt (nds, nullptr, "symbolic range");
widget->add_child (m_start_offset->make_dump_widget (nds, "start offset"));
widget->add_child (m_size->make_dump_widget (nds, "size"));
return widget;
}
template
std::unique_ptr<text_art::tree_widget>
symbolic::range<integer_with_unit<units::bit>>::
make_dump_widget (symbolic::node_dump_state &nds,
const char *prefix) const;
template<typename ConcreteValueType>
void
symbolic::range<ConcreteValueType>::accept_node_visitor (node_visitor &v) const
{
m_start_offset->accept_node_visitor (v);
m_size->accept_node_visitor (v);
}
template
void
symbolic::range<integer_with_unit<units::bit>>::
accept_node_visitor (node_visitor &v) const;
template<>
DEBUG_FUNCTION void
symbolic_bit_range::dump () const
{
symbolic::dump_node_tree_to_stderr<symbolic_bit_range> (*this);
}
template<>
DEBUG_FUNCTION void
symbolic_byte_range::dump () const
{
symbolic::dump_node_tree_to_stderr<symbolic_byte_range> (*this);
}
template<typename ConcreteValueType>
void
symbolic::range<ConcreteValueType>::log (const char *label, logger &log) const
{
pretty_printer *pp = log.get_printer ();
log.start_log_line ();
pp_printf (pp, "%s: ", label);
dump_inline_to_pp (pp);
log.end_log_line ();
}
template
void
symbolic_bit_range::log (const char *label, logger &log) const;
template<typename ConcreteValueType>
std::unique_ptr<json::object>
symbolic::range<ConcreteValueType>::to_json () const
{
auto obj = std::make_unique<json::object> ();
obj->set ("start_offset", m_start_offset->to_json ());
obj->set ("size", m_size->to_json ());
return obj;
}
template
std::unique_ptr<json::object>
symbolic_bit_range::to_json () const;
template
std::unique_ptr<json::object>
symbolic_byte_range::to_json () const;
template<typename ConcreteValueType>
void
symbolic::range<ConcreteValueType>::dump_inline_to_pp (pretty_printer *pp) const
{
pp_character (pp, '[');
if (m_size->is_zero_p ())
{
pp_character (pp, ']');
return;
}
if (m_size->is_one_p ())
{
m_start_offset->dump_inline_to_pp (pp, units::role::offset);
pp_character (pp, ']');
return;
}
m_start_offset->dump_inline_to_pp (pp, units::role::offset);
pp_string (pp, "..");
get_next_offset ().dump_inline_to_pp (pp, units::role::offset);
pp_character (pp, ')');
}
template
void
symbolic_bit_range::dump_inline_to_pp (pretty_printer *pp) const;
symbolic_bit_range
bits_from_bytes (const symbolic_byte_range &byte_range)
{
return symbolic_bit_range (bits_from_bytes (byte_range.get_start_offset ()),
bits_from_bytes (byte_range.get_size ()));
}
/* Attempt to determine if THIS range intersects OTHER,
using constraints from MODEL. */
template<typename ConcreteValueType>
tristate
symbolic::range<ConcreteValueType>::
intersects (const symbolic::range<ConcreteValueType> &other,
const region_model &model) const
{
/* If either is empty, then there is no intersection. */
if (this->empty_p ())
return tristate::TS_FALSE;
if (other.empty_p ())
return tristate::TS_FALSE;
/* For brevity, consider THIS to be "range A", and OTHER to be "range B". */
region_model_manager *mgr = model.get_manager ();
auto &first_offset_a = get_start_offset ();
auto &first_offset_b = other.get_start_offset ();
if (&first_offset_a == &first_offset_b)
return tristate::TS_TRUE;
auto &last_offset_a = get_last_offset ();
auto &last_offset_b = other.get_last_offset ();
auto *first_offset_sval_a
= mgr->get_svalue_from_scalar<ConcreteValueType> (size_type_node,
first_offset_a);
auto *first_offset_sval_b
= mgr->get_svalue_from_scalar<ConcreteValueType> (size_type_node,
first_offset_b);
auto *last_offset_sval_a
= mgr->get_svalue_from_scalar<ConcreteValueType> (size_type_node,
last_offset_a);
auto *last_offset_sval_b
= mgr->get_svalue_from_scalar<ConcreteValueType> (size_type_node,
last_offset_b);
/* Is B fully before A? */
tristate b_fully_before_a = model.eval_condition (last_offset_sval_b,
LT_EXPR,
first_offset_sval_a);
/* Is B fully after A? */
tristate b_fully_after_a = model.eval_condition (first_offset_sval_b,
GT_EXPR,
last_offset_sval_a);
if (b_fully_before_a.is_true ()
|| b_fully_after_a.is_true ())
return tristate::TS_FALSE;
if (b_fully_before_a.is_unknown ()
|| b_fully_after_a.is_unknown ())
return tristate::TS_UNKNOWN;
return tristate::TS_TRUE;
}
template
tristate
symbolic_byte_range::intersects (const symbolic_byte_range &other,
const region_model &model) const;
bool
maybe_bytes_from_bits (const symbolic_bit_range &bit_range,
symbolic_byte_range *out)
{
const symbolic_byte_offset *start_byte_offset
= maybe_bytes_from_bits (bit_range.get_start_offset ());
if (!start_byte_offset)
return false;
const symbolic_byte_size *byte_size
= maybe_bytes_from_bits (bit_range.get_size ());
if (!byte_size)
return false;
*out = symbolic_byte_range (*start_byte_offset, *byte_size);
return true;
}
#if CHECKING_P
namespace selftest {
static void
test_concrete ()
{
symbolic::node_manager node_mgr;
auto &bit_mgr = node_mgr.get_scalars ().get_bits ();
auto &byte_mgr = node_mgr.get_scalars ().get_bytes ();
symbolic_bit_range bits_16_to_23
(bit_mgr.get_concrete (16_bits),
bit_mgr.get_concrete (8_bits));
ASSERT_SAME_SYMBOLIC_NODE (bits_16_to_23.get_start_offset (),
bit_mgr.get_concrete (16_bits));
ASSERT_SAME_SYMBOLIC_NODE (bits_16_to_23.get_size (),
bit_mgr.get_concrete (8_bits));
ASSERT_SAME_SYMBOLIC_NODE (bits_16_to_23.get_last_offset (),
bit_mgr.get_concrete (23_bits));
ASSERT_SAME_SYMBOLIC_NODE (bits_16_to_23.get_next_offset (),
bit_mgr.get_concrete (24_bits));
bits_16_to_23.to_json ();
ASSERT_EQ
(symbolic_bit_range::from_endpoints (bit_mgr.get_concrete (16_bits),
bit_mgr.get_concrete (24_bits)),
bits_16_to_23);
symbolic_byte_range bytes_16_to_23
(byte_mgr.get_concrete (16_bytes),
byte_mgr.get_concrete (8_bytes));
ASSERT_SAME_SYMBOLIC_NODE (bytes_16_to_23.get_start_offset (),
byte_mgr.get_concrete (16_bytes));
ASSERT_SAME_SYMBOLIC_NODE (bytes_16_to_23.get_size (),
byte_mgr.get_concrete (8_bytes));
ASSERT_SAME_SYMBOLIC_NODE (bytes_16_to_23.get_last_offset (),
byte_mgr.get_concrete (23_bytes));
ASSERT_SAME_SYMBOLIC_NODE (bytes_16_to_23.get_next_offset (),
byte_mgr.get_concrete (24_bytes));
bytes_16_to_23.to_json ();
ASSERT_EQ
(symbolic_byte_range::from_endpoints (byte_mgr.get_concrete (16_bytes),
byte_mgr.get_concrete (24_bytes)),
bytes_16_to_23);
}
static void
test_intersects (void)
{
symbolic::node_manager node_mgr;
auto &byte_mgr = node_mgr.get_scalars ().get_bytes ();
region_model m (node_mgr);
/* Test various concrete ranges. */
auto &zero = byte_mgr.get_concrete (0_bytes);
auto &one = byte_mgr.get_concrete (1_byte);
auto &five = byte_mgr.get_concrete (5_bytes);
auto &nine = byte_mgr.get_concrete (9_bytes);
auto &ten = byte_mgr.get_concrete (10_bytes);
symbolic_byte_range r0_9 (zero, ten);
symbolic_byte_range r0 (zero, one);
symbolic_byte_range r5_9 (five, five);
symbolic_byte_range r9 (nine, one);
symbolic_byte_range r10 (ten, one);
symbolic_byte_range r10_19 (ten, ten);
ASSERT_EQ (r0_9.get_start_offset (), zero);
ASSERT_EQ (r0_9.get_size (), ten);
ASSERT_EQ (r0_9.get_next_offset (), ten);
ASSERT_EQ (r0_9.get_last_offset (), nine);
symbolic_byte_range concrete_empty (zero, zero);
ASSERT_TRUE (concrete_empty.empty_p ());
ASSERT_EQ (r0_9.intersects (r0, m), tristate::TS_TRUE);
ASSERT_EQ (r0.intersects (r0_9, m), tristate::TS_TRUE);
ASSERT_EQ (r0_9.intersects (r9, m), tristate::TS_TRUE);
ASSERT_EQ (r9.intersects (r0_9, m), tristate::TS_TRUE);
ASSERT_EQ (r0_9.intersects (r10, m), tristate::TS_FALSE);
ASSERT_EQ (r10.intersects (r0_9, m), tristate::TS_FALSE);
ASSERT_EQ (concrete_empty.intersects (r0_9, m), tristate::TS_FALSE);
ASSERT_EQ (r0_9.intersects (concrete_empty, m), tristate::TS_FALSE);
ASSERT_EQ (r5_9.intersects (r0, m), tristate::TS_FALSE);
ASSERT_EQ (r0.intersects (r5_9, m), tristate::TS_FALSE);
ASSERT_EQ (r9.intersects (r5_9, m), tristate::TS_TRUE);
ASSERT_EQ (r10.intersects (r5_9, m), tristate::TS_FALSE);
/* Test various symbolic ranges. */
tree x = build_global_decl ("x", size_type_node);
const svalue *x_init_sval = m.get_rvalue (x, nullptr);
auto &x_init = byte_mgr.get_scalar_from_svalue (*x_init_sval);
tree y = build_global_decl ("y", size_type_node);
const svalue *y_init_sval = m.get_rvalue (y, nullptr);
auto &y_init = byte_mgr.get_scalar_from_svalue (*y_init_sval);
symbolic_byte_range r0_x_minus_1 (zero, x_init);
symbolic_byte_range rx (x_init, one);
symbolic_byte_range r0_y_minus_1 (zero, y_init);
symbolic_byte_range ry (y_init, one);
symbolic_byte_range rx_x_plus_y_minus_1 (x_init, y_init);
symbolic_byte_range symbolic_empty (x_init, zero);
ASSERT_TRUE (symbolic_empty.empty_p ());
ASSERT_SAME_SYMBOLIC_NODE (rx_x_plus_y_minus_1.get_start_offset (), x_init);
ASSERT_SAME_SYMBOLIC_NODE (rx_x_plus_y_minus_1.get_size (), y_init);
ASSERT_EQ
(rx_x_plus_y_minus_1.get_next_offset ().get_kind (),
symbolic_byte_offset::kind::sum);
ASSERT_EQ
(rx_x_plus_y_minus_1.get_last_offset ().get_kind (),
symbolic_byte_offset::kind::sum);
ASSERT_EQ (rx.intersects (ry, m), tristate::TS_UNKNOWN);
ASSERT_EQ (rx.intersects (concrete_empty, m), tristate::TS_FALSE);
ASSERT_EQ (concrete_empty.intersects (rx, m), tristate::TS_FALSE);
ASSERT_EQ (rx.intersects (symbolic_empty, m), tristate::TS_FALSE);
ASSERT_EQ (symbolic_empty.intersects (rx, m), tristate::TS_FALSE);
ASSERT_EQ (r0_x_minus_1.intersects (r0, m), tristate::TS_TRUE);
ASSERT_EQ (r0_x_minus_1.intersects (r0_y_minus_1, m), tristate::TS_TRUE);
}
/* Run all of the selftests within this file. */
void
analyzer_symbolic_ranges_cc_tests ()
{
test_concrete ();
test_intersects ();
}
} // namespace selftest
#endif /* CHECKING_P */
} // namespace ana
#endif /* #if ENABLE_ANALYZER */