blob: a53f38f0e47bf9678b0161884f16a6d676287264 [file]
/* A (base_region, symbolic byte offset) pair.
Copyright (C) 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-pointers.h"
#include "analyzer/region-model.h"
#include "analyzer/analyzer-selftests.h"
#if ENABLE_ANALYZER
namespace ana {
namespace symbolic {
// class pointer
DEBUG_FUNCTION void
pointer::dump () const
{
symbolic::dump_node_tree_to_stderr<pointer> (*this);
}
std::unique_ptr<text_art::tree_widget>
pointer::make_dump_widget (symbolic::node_dump_state &nds) const
{
using text_art::tree_widget;
std::unique_ptr<tree_widget> widget
(tree_widget::from_fmt (nds, nullptr, "Pointer"));
widget->add_child (m_base_reg.make_dump_widget (nds, "base region"));
widget->add_child (m_byte_offset.make_dump_widget (nds, "byte offset"));
return widget;
}
void
pointer::accept_node_visitor (symbolic::node_visitor &v) const
{
m_base_reg.accept_node_visitor (v);
m_byte_offset.accept_node_visitor (v);
}
std::unique_ptr<json::object>
pointer::to_json () const
{
auto obj = std::make_unique<json::object> ();
obj->set ("base_reg", m_base_reg.to_json ());
obj->set ("byte_offset", m_byte_offset.to_json ());
return obj;
}
int
pointer::cmp (const pointer &other) const
{
if (int c = symbolic::node::cmp (m_base_reg, other.m_base_reg))
return c;
return symbolic::node::cmp (m_byte_offset, other.m_byte_offset);
}
/* A symbolic::pointer is a non-NULL pointer, except in certain
special cases (see the comment for region::non_null_p). */
bool
pointer::non_null_p () const
{
if (!zero_offset_p ())
return true;
return m_base_reg.non_null_p ();
}
/* Compare byte offsets OFFSET_A and OFFSET_B. */
static tristate
eval_byte_offset_comparison (const pointer::symbolic_byte_offset &offset_a,
enum tree_code op,
const pointer::symbolic_byte_offset &offset_b,
const region_model &model)
{
return symbolic_byte_offset::get_partial_ordering (offset_a, op, offset_b, &model);
}
/* Evaluate the condition LHS OP RHS for comparing symbolic pointers.
See if they point to the same base region, and if we know about
the offsets within their regions.
Use MODEL for aliasing information and any knowledge from
constraint_manager about ordering of pointers. */
tristate
pointer::eval_condition (const pointer &lhs,
enum tree_code op,
const pointer &rhs,
const region_model &model)
{
/* Try to determine if they're the same base region. */
const tristate same_base_region
= model.get_store ()->eval_alias (&lhs.get_base_region (),
&rhs.get_base_region (),
model);
/* Try to determine if they're the same offset relative to their
base region. */
const tristate same_byte_offset
= eval_byte_offset_comparison (lhs.get_byte_offset (),
EQ_EXPR,
rhs.get_byte_offset (),
model);
/* With that, we might know if they're equal/non-equal. */
const tristate equality = same_base_region.and_(same_byte_offset);
switch (op)
{
default:
gcc_unreachable ();
case EQ_EXPR:
return equality;
case NE_EXPR:
return equality.not_ ();
case GE_EXPR:
case LE_EXPR:
if (equality.is_true ())
return tristate (true);
else if (same_base_region.is_true ())
return eval_byte_offset_comparison (lhs.get_byte_offset (),
op,
rhs.get_byte_offset (),
model);
else
return tristate::unknown ();
case GT_EXPR:
case LT_EXPR:
if (equality.is_true ())
return tristate (false);
else if (same_base_region.is_true ())
return eval_byte_offset_comparison (lhs.get_byte_offset (),
op,
rhs.get_byte_offset (),
model);
else
return tristate::unknown ();
}
}
} // namespace ana::symbolic
#if CHECKING_P
namespace selftest {
static void
test_eval_condition ()
{
symbolic::node_manager node_mgr;
auto &rmm = node_mgr.get_svalue_manager ();
region_model model (node_mgr);
coord_test ct;
tree c1 = build_global_decl ("c1", ct.m_coord_type);
tree c2 = build_global_decl ("c2", ct.m_coord_type);
tree c1_x = build3 (COMPONENT_REF, TREE_TYPE (ct.m_x_field),
c1, ct.m_x_field, NULL_TREE);
tree c1_y = build3 (COMPONENT_REF, TREE_TYPE (ct.m_y_field),
c1, ct.m_y_field, NULL_TREE);
const region *c1_reg = model.get_lvalue (c1, nullptr);
const region *c1_x_reg = model.get_lvalue (c1_x, nullptr);
const region *c1_y_reg = model.get_lvalue (c1_y, nullptr);
const region *c2_reg = model.get_lvalue (c2, nullptr);
auto ptr_to_c1 = c1_reg->get_pointer (rmm);
auto ptr_to_c1_x = c1_x_reg->get_pointer (rmm);
auto ptr_to_c1_y = c1_y_reg->get_pointer (rmm);
auto ptr_to_c2 = c2_reg->get_pointer (rmm);
// Self-comparison of pointer with concrete offset:
{
ASSERT_EQ (symbolic::pointer::eval_condition (ptr_to_c1,
EQ_EXPR,
ptr_to_c1,
model),
tristate (true));
ASSERT_EQ (symbolic::pointer::eval_condition (ptr_to_c1,
NE_EXPR,
ptr_to_c1,
model),
tristate (false));
ASSERT_EQ (symbolic::pointer::eval_condition (ptr_to_c1,
GE_EXPR,
ptr_to_c1,
model),
tristate (true));
ASSERT_EQ (symbolic::pointer::eval_condition (ptr_to_c1,
LE_EXPR,
ptr_to_c1,
model),
tristate (true));
ASSERT_EQ (symbolic::pointer::eval_condition (ptr_to_c1,
GT_EXPR,
ptr_to_c1,
model),
tristate (false));
ASSERT_EQ (symbolic::pointer::eval_condition (ptr_to_c1,
LT_EXPR,
ptr_to_c1,
model),
tristate (false));
}
// Pointer to struct should equal pointer to initial field of that struct:
ASSERT_EQ (ptr_to_c1, ptr_to_c1_x);
/* Comparison of pointers to different fields in the same struct
(same base region, different concrete offset). */
{
ASSERT_EQ (symbolic::pointer::eval_condition (ptr_to_c1_x,
EQ_EXPR,
ptr_to_c1_y,
model),
tristate (false));
ASSERT_EQ (symbolic::pointer::eval_condition (ptr_to_c1_x,
NE_EXPR,
ptr_to_c1_y,
model),
tristate (true));
ASSERT_EQ (symbolic::pointer::eval_condition (ptr_to_c1_x,
GE_EXPR,
ptr_to_c1_y,
model),
tristate (false));
ASSERT_EQ (symbolic::pointer::eval_condition (ptr_to_c1_x,
LE_EXPR,
ptr_to_c1_y,
model),
tristate (true));
ASSERT_EQ (symbolic::pointer::eval_condition (ptr_to_c1_x,
GT_EXPR,
ptr_to_c1_y,
model),
tristate (false));
ASSERT_EQ (symbolic::pointer::eval_condition (ptr_to_c1_x,
LT_EXPR,
ptr_to_c1_y,
model),
tristate (true));
}
/* Different base regions where we know nothing, but know
they are concrete base regions. */
{
ASSERT_EQ (symbolic::pointer::eval_condition (ptr_to_c1,
EQ_EXPR,
ptr_to_c2,
model),
tristate (false));
ASSERT_EQ (symbolic::pointer::eval_condition (ptr_to_c1,
NE_EXPR,
ptr_to_c2,
model),
tristate (true));
ASSERT_EQ (symbolic::pointer::eval_condition (ptr_to_c1,
GE_EXPR,
ptr_to_c2,
model),
tristate::unknown ());
ASSERT_EQ (symbolic::pointer::eval_condition (ptr_to_c1,
LE_EXPR,
ptr_to_c2,
model),
tristate::unknown ());
ASSERT_EQ (symbolic::pointer::eval_condition (ptr_to_c1,
GT_EXPR,
ptr_to_c2,
model),
tristate::unknown ());
ASSERT_EQ (symbolic::pointer::eval_condition (ptr_to_c1,
LT_EXPR,
ptr_to_c2,
model),
tristate::unknown ());
}
}
/* Run all of the selftests within this file. */
void
analyzer_symbolic_pointers_cc_tests ()
{
test_eval_condition ();
}
} // namespace selftest
#endif /* CHECKING_P */
} // namespace ana
#endif /* #if ENABLE_ANALYZER */