Skip to content

Commit c954340

Browse files
committed
analyzer: fix equivalence class state purging [PR103533]
Whilst debugging state explosions seen when enabling taint detection with -fanalyzer (PR analyzer/103533), I noticed that constraint manager instances could contain stray, redundant constants, such as this instance: constraint_manager: equiv classes: ec0: {(int)0 == [m_constant]‘0’} ec1: {(size_t)4 == [m_constant]‘4’} constraints: where there are two equivalence classes, each just containing a constant, with no constraints using them. This patch makes constraint_manager::canonicalize more aggressive about purging state, handling the case of purging a redundant EC containing just a constant. gcc/analyzer/ChangeLog: PR analyzer/103533 * constraint-manager.cc (equiv_class::contains_non_constant_p): New. (constraint_manager::canonicalize): Call it when determining redundant ECs. (selftest::test_purging): New selftest. (selftest::run_constraint_manager_tests): Likewise. * constraint-manager.h (equiv_class::contains_non_constant_p): New decl. Signed-off-by: David Malcolm <[email protected]>
1 parent 325c616 commit c954340

File tree

2 files changed

+149
-2
lines changed

2 files changed

+149
-2
lines changed

Diff for: gcc/analyzer/constraint-manager.cc

+147-2
Original file line numberDiff line numberDiff line change
@@ -1145,6 +1145,30 @@ equiv_class::canonicalize ()
11451145
m_vars.qsort (svalue::cmp_ptr_ptr);
11461146
}
11471147

1148+
/* Return true if this EC contains a variable, false if it merely
1149+
contains constants.
1150+
Subroutine of constraint_manager::canonicalize, for removing
1151+
redundant ECs. */
1152+
1153+
bool
1154+
equiv_class::contains_non_constant_p () const
1155+
{
1156+
if (m_constant)
1157+
{
1158+
for (auto iter : m_vars)
1159+
if (iter->maybe_get_constant ())
1160+
continue;
1161+
else
1162+
/* We have {non-constant == constant}. */
1163+
return true;
1164+
/* We only have constants. */
1165+
return false;
1166+
}
1167+
else
1168+
/* Return true if we have {non-constant == non-constant}. */
1169+
return m_vars.length () > 1;
1170+
}
1171+
11481172
/* Get a debug string for C_OP. */
11491173

11501174
const char *
@@ -2718,8 +2742,7 @@ constraint_manager::canonicalize ()
27182742
{
27192743
equiv_class *ec = m_equiv_classes[i];
27202744
if (!used_ecs.contains (ec)
2721-
&& ((ec->m_vars.length () < 2 && ec->m_constant == NULL_TREE)
2722-
|| (ec->m_vars.length () == 0)))
2745+
&& !ec->contains_non_constant_p ())
27232746
{
27242747
m_equiv_classes.unordered_remove (i);
27252748
delete ec;
@@ -3704,6 +3727,127 @@ test_many_constants ()
37043727
}
37053728
}
37063729

3730+
/* Verify that purging state relating to a variable doesn't leave stray
3731+
equivalence classes (after canonicalization). */
3732+
3733+
static void
3734+
test_purging (void)
3735+
{
3736+
tree int_0 = build_int_cst (integer_type_node, 0);
3737+
tree a = build_global_decl ("a", integer_type_node);
3738+
tree b = build_global_decl ("b", integer_type_node);
3739+
3740+
/* "a != 0". */
3741+
{
3742+
region_model_manager mgr;
3743+
region_model model (&mgr);
3744+
ADD_SAT_CONSTRAINT (model, a, NE_EXPR, int_0);
3745+
ASSERT_EQ (model.get_constraints ()->m_equiv_classes.length (), 2);
3746+
ASSERT_EQ (model.get_constraints ()->m_constraints.length (), 1);
3747+
3748+
/* Purge state for "a". */
3749+
const svalue *sval_a = model.get_rvalue (a, NULL);
3750+
model.purge_state_involving (sval_a, NULL);
3751+
model.canonicalize ();
3752+
/* We should have an empty constraint_manager. */
3753+
ASSERT_EQ (model.get_constraints ()->m_equiv_classes.length (), 0);
3754+
ASSERT_EQ (model.get_constraints ()->m_constraints.length (), 0);
3755+
}
3756+
3757+
/* "a != 0" && "b != 0". */
3758+
{
3759+
region_model_manager mgr;
3760+
region_model model (&mgr);
3761+
ADD_SAT_CONSTRAINT (model, a, NE_EXPR, int_0);
3762+
ADD_SAT_CONSTRAINT (model, b, NE_EXPR, int_0);
3763+
ASSERT_EQ (model.get_constraints ()->m_equiv_classes.length (), 3);
3764+
ASSERT_EQ (model.get_constraints ()->m_constraints.length (), 2);
3765+
3766+
/* Purge state for "a". */
3767+
const svalue *sval_a = model.get_rvalue (a, NULL);
3768+
model.purge_state_involving (sval_a, NULL);
3769+
model.canonicalize ();
3770+
/* We should just have the constraint/ECs involving b != 0. */
3771+
ASSERT_EQ (model.get_constraints ()->m_equiv_classes.length (), 2);
3772+
ASSERT_EQ (model.get_constraints ()->m_constraints.length (), 1);
3773+
ASSERT_CONDITION_TRUE (model, b, NE_EXPR, int_0);
3774+
}
3775+
3776+
/* "a != 0" && "b == 0". */
3777+
{
3778+
region_model_manager mgr;
3779+
region_model model (&mgr);
3780+
ADD_SAT_CONSTRAINT (model, a, NE_EXPR, int_0);
3781+
ADD_SAT_CONSTRAINT (model, b, EQ_EXPR, int_0);
3782+
ASSERT_EQ (model.get_constraints ()->m_equiv_classes.length (), 2);
3783+
ASSERT_EQ (model.get_constraints ()->m_constraints.length (), 1);
3784+
3785+
/* Purge state for "a". */
3786+
const svalue *sval_a = model.get_rvalue (a, NULL);
3787+
model.purge_state_involving (sval_a, NULL);
3788+
model.canonicalize ();
3789+
/* We should just have the EC involving b == 0. */
3790+
ASSERT_EQ (model.get_constraints ()->m_equiv_classes.length (), 1);
3791+
ASSERT_EQ (model.get_constraints ()->m_constraints.length (), 0);
3792+
ASSERT_CONDITION_TRUE (model, b, EQ_EXPR, int_0);
3793+
}
3794+
3795+
/* "a == 0". */
3796+
{
3797+
region_model_manager mgr;
3798+
region_model model (&mgr);
3799+
ADD_SAT_CONSTRAINT (model, a, EQ_EXPR, int_0);
3800+
ASSERT_EQ (model.get_constraints ()->m_equiv_classes.length (), 1);
3801+
ASSERT_EQ (model.get_constraints ()->m_constraints.length (), 0);
3802+
3803+
/* Purge state for "a". */
3804+
const svalue *sval_a = model.get_rvalue (a, NULL);
3805+
model.purge_state_involving (sval_a, NULL);
3806+
model.canonicalize ();
3807+
/* We should have an empty constraint_manager. */
3808+
ASSERT_EQ (model.get_constraints ()->m_equiv_classes.length (), 0);
3809+
ASSERT_EQ (model.get_constraints ()->m_constraints.length (), 0);
3810+
}
3811+
3812+
/* "a == 0" && "b != 0". */
3813+
{
3814+
region_model_manager mgr;
3815+
region_model model (&mgr);
3816+
ADD_SAT_CONSTRAINT (model, a, EQ_EXPR, int_0);
3817+
ADD_SAT_CONSTRAINT (model, b, NE_EXPR, int_0);
3818+
ASSERT_EQ (model.get_constraints ()->m_equiv_classes.length (), 2);
3819+
ASSERT_EQ (model.get_constraints ()->m_constraints.length (), 1);
3820+
3821+
/* Purge state for "a". */
3822+
const svalue *sval_a = model.get_rvalue (a, NULL);
3823+
model.purge_state_involving (sval_a, NULL);
3824+
model.canonicalize ();
3825+
/* We should just have the constraint/ECs involving b != 0. */
3826+
ASSERT_EQ (model.get_constraints ()->m_equiv_classes.length (), 2);
3827+
ASSERT_EQ (model.get_constraints ()->m_constraints.length (), 1);
3828+
ASSERT_CONDITION_TRUE (model, b, NE_EXPR, int_0);
3829+
}
3830+
3831+
/* "a == 0" && "b == 0". */
3832+
{
3833+
region_model_manager mgr;
3834+
region_model model (&mgr);
3835+
ADD_SAT_CONSTRAINT (model, a, EQ_EXPR, int_0);
3836+
ADD_SAT_CONSTRAINT (model, b, EQ_EXPR, int_0);
3837+
ASSERT_EQ (model.get_constraints ()->m_equiv_classes.length (), 1);
3838+
ASSERT_EQ (model.get_constraints ()->m_constraints.length (), 0);
3839+
3840+
/* Purge state for "a". */
3841+
const svalue *sval_a = model.get_rvalue (a, NULL);
3842+
model.purge_state_involving (sval_a, NULL);
3843+
model.canonicalize ();
3844+
/* We should just have the EC involving b == 0. */
3845+
ASSERT_EQ (model.get_constraints ()->m_equiv_classes.length (), 1);
3846+
ASSERT_EQ (model.get_constraints ()->m_constraints.length (), 0);
3847+
ASSERT_CONDITION_TRUE (model, b, EQ_EXPR, int_0);
3848+
}
3849+
}
3850+
37073851
/* Implementation detail of ASSERT_DUMP_BOUNDED_RANGES_EQ. */
37083852

37093853
static void
@@ -4035,6 +4179,7 @@ run_constraint_manager_tests (bool transitivity)
40354179
test_constraint_impl ();
40364180
test_equality ();
40374181
test_many_constants ();
4182+
test_purging ();
40384183
test_bounded_range ();
40394184
test_bounded_ranges ();
40404185

Diff for: gcc/analyzer/constraint-manager.h

+2
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,8 @@ class equiv_class
248248

249249
json::object *to_json () const;
250250

251+
bool contains_non_constant_p () const;
252+
251253
/* An equivalence class can contain multiple constants (e.g. multiple
252254
different zeroes, for different types); these are just for the last
253255
constant added. */

0 commit comments

Comments
 (0)